Гуглить баги — это нормально. Как AI Debugger освоил этот навык и сам исправляет ошибки. ai.. ai. python.. ai. python. Блог компании BotHub.. ai. python. Блог компании BotHub. дебаггинг.. ai. python. Блог компании BotHub. дебаггинг. ИИ.. ai. python. Блог компании BotHub. дебаггинг. ИИ. искусственный интеллект.. ai. python. Блог компании BotHub. дебаггинг. ИИ. искусственный интеллект. Машинное обучение.. ai. python. Блог компании BotHub. дебаггинг. ИИ. искусственный интеллект. Машинное обучение. Ненормальное программирование.
Гуглить баги — это нормально. Как AI Debugger освоил этот навык и сам исправляет ошибки - 1

Автоматический дебаг с помощью языковых моделей уже не новость, и разработчики используют LLM‑модели и среды разработки с интегрированным ИИ, чтобы анализировать код и предлагать исправления. Но что если встроить в этот процесс ещё один мощный инструмент — поиск в интернете?

В этой статье мы рассмотрим необычный подход: автоматический дебаггер, который не просто анализирует код с помощью нейросети, но и при необходимости отправляет сформулированные моделью поисковые запросы в интернете. Если модель не уверена в исправлении, она делает то же, что и человек, — ищет похожие ошибки и готовые решения в сети.

Код на GitHub

Если вы хотите посмотреть всю магию в действии, загляните в мой репозиторий GitHub — там хранится полная реализация.

Как всё организовано? Вот дерево файлов — оно подскажет, что к чему:

AI-Debugger
│   ├── default_config.py      # Настройки по умолчанию
├── requirements.txt           # Список зависимостей Python для проекта
└── src/
    ├── cli/
    │   ├── main.py            # Точка входа в интерфейс командной строки
    ├── core/
    │   ├── debugger.py        # Главная логика отладки с помощью LLM
    │   ├── llm.py             # Абстрактный базовый класс для взаимодействия с LLM
    │   ├── llm_factory.py     # Фабрика создания экземпляров LLM
    │   ├── utils.py           # Утилитарные функции
    │   │                      # (например, вычисление различий в коде)
    ├── internet/
    │   ├── search.py          # Модуль для выполнения интернет-поиска
    ├── llms/
    │   ├── gemini_llm.py      # Реализация взаимодействия с Google Gemini
    │   ├── huggingface_llm.py # Реализация взаимодействия с Hugging Face
    │   ├── openai_llm.py      # Реализация взаимодействия с OpenAI
    ├── prompts/
    │   ├── query_prompts.py   # Шаблоны промтов для поисковых задач
    └───┴── system_prompts.py  # Системные промты для LLM

Обзор проекта

Как выглядит работа этого бесконечного цикла отладки ИИ? Представим процесс визуально.

Гуглить баги — это нормально. Как AI Debugger освоил этот навык и сам исправляет ошибки - 2

Бесконечный ИИ‑дебаггер начинает с обнаружения потенциальных проблем и проверки наличия ошибок. Если ошибок не обнаружено, процесс завершается. Если ошибка найдена, дебаггер оценивает, требуется ли использование внешней информации. При необходимости он выполняет поиск через такие источники, как Google или Mozilla, извлекает релевантные данные и комбинирует их с внутренними входными данными.

Собранные данные передаются в выбранную LLM (например, HuggingFace, OpenAI, Gemini или локальную модель), которая генерирует ответ для устранения ошибки. Система применяет это решение к коду и снова проверяет наличие ошибок. Если проблема сохраняется, процесс повторяется до её устранения.

Цикл повторяется до тех пор, пока не будут исправлены все проблемы. Такая схема делает процесс максимально эффективным: внешние данные и искусственный интеллект работают в тандеме.

Установка модуля

Начнём с установки. Сначала клонируем репозиторий:

git clone https://github.com/FareedKhan-dev/ai-debugger.git
cd ai-debugger

Установим все необходимые зависимости этой командой:

pip install -r requirements.txt

Импорт библиотек

Давайте импортируем все нужные библиотеки:

import os  # Для взаимодействия с операционной системой
import subprocess  # Позволяет запускать новые процессы
import time  # Предоставляет функции для работы со временем
import re  # Поддержка операций с регулярными выражениями
import transformers  # Библиотека Hugging Face для языковых моделей (LLM)
import torch  # Для тензорных вычислений и глубокого обучения
import logging  # Инструменты для логирования
from scrapling import Fetcher  # Веб-скрейпинг

Получение структуры дерева проекта

Первое, с чего начинается процесс отладки, — это знакомство с проектом. Нужно показать LLM, что у нас есть, и как это организовано. Для этого создадим функцию, которая генерирует структуру дерева каталогов. Давайте напишем её.

def get_dir_tree(start_path, indent=""):
    """
    Рекурсивно создаёт строковое представление структуры дерева каталогов.

    Args:
        start_path (str): Путь к начальной директории.
        indent (str): Отступ текущего уровня (используется для рекурсивных вызовов).

    Returns:
        str: Структура дерева каталогов в виде строки.
    """
    # Инициализация пустой строки для хранения структуры дерева каталогов
    tree_structure = ""

    # Попытка получить список всех элементов в директории
    try:
        items = os.listdir(start_path)
    except PermissionError:
        # Обработка ошибки доступа с указанием ограничения
        return f"{indent}[Доступ запрещён]n"

    # Перебор всех элементов в текущей директории
    for index, item in enumerate(items):
        # Формирование полного пути к каждому элементу
        item_path = os.path.join(start_path, item)

        # Проверка, является ли текущий элемент последним, для корректного оформления дерева
        is_last_item = index == len(items) - 1

        # Определение префикса для текущего элемента (оформление ветвей дерева)
        prefix = "└── " if is_last_item else "├── "

        # Добавление текущего элемента в структуру дерева с соответствующим отступом
        tree_structure += f"{indent}{prefix}{item}n"

        # Если текущий элемент — директория, рекурсивно получить её структуру
        if os.path.isdir(item_path):
            # Корректировка отступа для следующего уровня дерева
            new_indent = indent + ("    " if is_last_item else "│   ")
            tree_structure += get_dir_tree(item_path, new_indent)

    return tree_structure

Функция get_dir_tree() — наш проводник: она принимает один аргумент, корневую директорию, и возвращает чёткую структуру всех вложенных папок и файлов. Теперь можно легко визуализировать, как устроен проект. Давайте протестируем, вызвав её для тестовой папки. Вот результат:

# Указываем директорию, структуру которой хотим отобразить
start_directory = "project_code"

# Получаем структуру дерева для указанной директории
directory_tree = get_dir_tree(start_directory)

# Выводим полученную структуру дерева каталогов
print(directory_tree)
### ВЫВОД ###

project_code
├── requirements.txt
├── main.py
└── some_folder/
    ├── sub_folder/
        ...
...

Чтение содержимого

Что дальше? Разумеется, следующий шаг — открыть файлы и понять, что в них внутри. Лишь имея на руках содержимое самих файлов, LLM сможет понять код, который необходимо анализировать и отлаживать. Создадим функцию для выполнения этой задачи.

def read_code_files(directory, files_to_debug=None):
    """
    Считывает содержимое файлов с кодом на Python из указанной директории и возвращает его в виде строки.

    Args:
        directory (str): Директория, содержащая файлы с кодом.
        files_to_debug (list, optional): Список конкретных файлов для отладки. По умолчанию — None.

    Returns:
        str: Строка, содержащая содержимое всех файлов с кодом.
    """
    # Инициализация пустой строки для хранения содержимого всех файлов
    all_code = ""

    # Если список файлов не указан, загружаем все Python-файлы из директории
    if not files_to_debug:
        files_to_debug = [f for f in os.listdir(directory) if f.endswith(".py")]

    # Перебор списка файлов для отладки
    for code_filename in files_to_debug:
        try:
            # Открытие файла для чтения
            with open(os.path.join(directory, code_filename), "r") as file:
                # Добавление имени файла в виде комментария для наглядности
                all_code += f"# {code_filename}n"
                # Чтение содержимого файла
                code = file.read()
                # Добавление содержимого файла к общей строке
                all_code += code + "n"
                # Добавление разделителя для удобства восприятия между файлами
                all_code += "_________________nn"
        except Exception as e:
            # Обработка ошибок при чтении конкретного файла
            print(f"Ошибка при чтении {code_filename}: {e}")

    # Возврат объединённого содержимого всех файлов
    return all_code

Функция read_code_files() принимает два аргумента: каталог, где находятся файлы кода, и список файлов для отладки, если они переданы в аргументах командной строки. Если же конкретные файлы не указаны, функция будет считывать все Python‑файлы из указанного каталога.

Тестируем. Предположим, что в каталоге проекта два файла: main.py и utils.py.

# Указываем папку с кодом
code_directory = "project_folder"

# Получаем содержимое файлов с кодом
code_content = read_code_files(code_directory)
print(code_content)
### ВЫВОД ###

# main.py
def main():
    print("Здравствуй, свет!")

if __name__ == "__main__":
    main()
_________________

# utils.py
def add(a, b):
    return a + b
_________________

Как видно, файлы были успешно загружены и представлены в виде одной строки. Теперь LLM сможет разобраться в нашем коде.

Логирование ошибок

Следующим этапом мы займёмся поиском ошибок. Нам нужно выполнить каждый Python‑файл, чтобы проверить наличие ошибок. Для этого мы используем питонский модуль subprocess, который позволяет запускать файлы и захватывать их вывод. Эта функция поможет нам точно определить, где и какие ошибки присутствуют в коде, а также предоставит ключевую информацию для отладки.

def run_code_and_capture_errors(directory, files_to_debug=None):
    """
    Выполняет Python-файлы в указанной директории, фиксируя их вывод или ошибки.

    Args:
        directory (str): Директория, где находятся Python-файлы.
        files_to_debug (list, optional): Список файлов для выполнения. Если None, выполняются все Python-файлы в директории.

    Returns:
        str: Отформатированная строка с результатами выполнения для каждого файла.
    """
    results = []  # Список для хранения результатов выполнения

    try:
        # Если список файлов не указан, выбираем все Python-файлы в директории
        if files_to_debug is None:
            files_to_debug = [f for f in os.listdir(directory) if f.endswith('.py')]

        # Перебор каждого файла и его выполнение
        for code_filename in files_to_debug:
            results.append(f"# {code_filename}")
            try:
                # Запуск Python-файла через subprocess с захватом вывода и ошибок
                result = subprocess.run(
                    ["python", os.path.join(directory, code_filename)],
                    capture_output=True,
                    text=True,
                    check=True,
                )
                # Добавление успешного вывода (stdout)
                if result.stdout.strip():
                    results.append(result.stdout.strip())
                else:
                    results.append("ошибок нет")
            except subprocess.CalledProcessError as e:
                # Добавление сообщения об ошибке (stderr)
                if e.stderr.strip():
                    results.append(f"Произошла ошибка: {e.stderr.strip()}")
                else:
                    results.append("Неизвестная ошибка")

    except Exception as e:
        # Обработка неожиданных ошибок (например, неверная директория)
        results.append(f"Произошла ошибка: {str(e)}")

    # Возврат результатов в формате строки
    return "nn".join(results)

Функция run_code_and_capture_errors() принимает два аргумента: каталог (directory) и необязательный список имён файлов Python (files_to_debug). Она выполняет каждый файл, захватывая либо вывод, либо ошибки, и возвращает форматированную строку. Результаты возвращаются в формате, где имя файла предваряется символом #, а далее следуют детали вывода или ошибки.

Давайте протестируем эту функцию на каталоге проекта, который содержит только один скрипт test_error.py, содержащий преднамеренную ошибку.

# test_error.py с ошибкой
def test():
    a = 10
    b = "20"
    print (a + b)

test()

Вызовем этот метод для запуска нашего Python‑кода.

# Указываем директорию
code_directory = "project_dor"

# Запуск кода и захват ошибок
error_output = run_code_and_capture_error(code_directory)
print(error_output)
### ВЫВОД ###

# test_error.py
Traceback (most recent call last):
  File "../project_dir/test_error.py", line 3, in test
    print (a + b)
           ~~^~~
TypeError: неподдерживаемые операнды для +: 'int' и 'str'

Системный промт для отладки

Теперь, когда мы можем сообщить модели, какие ошибки существуют, необходимо объяснить ей, как их исправить. Здесь нам и пригодится системный промт — своего рода руководство для LLM.

DEBUG_SYSTEM_PROMPT = """
Вы — эксперт по Python, специализирующийся на обработке ошибок и защитном программировании.
Проанализируйте все предоставленные файлы кода, а также информацию, найденную в интернете.
Исправьте ошибки и улучшите код, усилив его защитные механизмы. Используйте строго следующий формат:

# имя_файла_кода
```python  
# исправленный код
```  

Пропускайте файлы, в которых ошибок не обнаружено.
Применяйте лучшие практики и интегрируйте релевантные данные из поиска.
"""

Добавляем взаимодействие с LLM

Нам нужно создать функцию для LLM, которая будет генерировать код на основе системного и юзер‑промта. Давайте её разработаем.

def HF_llm(model_name, system_prompt, user_prompt):
    """
    Генерирует ответ с использованием языковой модели Hugging Face на основе заданных системного и юзер-промта.

    Args:
    model_name (str): Имя или путь к предобученной языковой модели (например, 'gpt-3', 't5-base').
    system_prompt (str): Промт, задающий роль и поведение модели.
    user_prompt (str): Входные данные пользователя, на которые нужно сгенерировать ответ.

    Returns:
    str: Сгенерированный ответ модели.
    """
    
    # Формируем массив, содержащий системный и юзер-промт
    messages = [
        {"role": "system", "content": system_prompt},  # Системная инструкция или контекст
        {"role": "user", "content": user_prompt},  # Юзер-промт
    ]

    # Инициализируем пайплайн модели Hugging Face для генерации текста
    pipeline = transformers.pipeline(
        "text-generation",  # Задача: генерация текста
        model=model_name,  # Имя модели или путь к ней
        model_kwargs={"torch_dtype": torch.bfloat16},  # Используем bfloat16 для повышения эффективности
        device_map="auto",  # Автоматический выбор устройства (CPU или GPU)
    )

    # Генерируем ответ модели на основе входных данных
    outputs = pipeline(messages)

    # Возвращаем только сгенерированный текст, без метаданных
    return outputs[0]["generated_text"][-1]['content']

Сейчас мы сосредоточимся на LLM, размещённой на Hugging Face: это открытая платформа, на которой можно легко найти и подключить подходящую модель. В то же время GitHub‑репозиторий AIDebugger поддерживает разные модели, в частности OpenAI, Gemini и локальные.

Исправление кода

После того как LLM предоставит исправления, их нужно внести в кодовую базу. Для этого мы создадим функцию, которая заменит проблемные участки.

def update_code_from_llm_response(llm_response):
    """
    Извлекает код из ответа языковой модели и обновляет соответствующие файлы.

    Функция выполняет два основных действия:
    1. Извлекает фрагменты кода Python из ответа языковой модели.
    2. Обновляет соответствующие файлы, заменяя их содержимое.

    Аргументы:
        llm_response (str): Текстовый ответ языковой модели.

    Возвращает:
        None: Функция напрямую обновляет файлы, не возвращая значений.
    """
    # Регулярное выражение для поиска имен файлов и фрагментов кода внутри markdown-блоков
    pattern = r"#s(S+)n```pythonn(.*?)n```"

    # Находим все совпадения в ответе модели (имя файла и соответствующий код)
    matches = re.findall(pattern, llm_response, re.DOTALL)

    # Создаем словарь, сопоставляющий имена файлов с извлеченным кодом
    code_changes = {filename: code.strip() for filename, code in matches}

    # Обновляем содержимое файлов на основе извлеченного кода
    for filename, code in code_changes.items():
        # Полный путь к файлу
        file_path = os.path.join(self.code_dir, filename)

        # Открываем файл в режиме записи и заменяем его содержимое новым кодом
        with open(file_path, "w") as file:
            file.write(code)

Теперь настроены все компоненты: мы можем выявлять ошибки, спрашивать LLM об их исправлении и на основе ответов обновлять файлы исходного кода. Запустим наши функции и посмотрим, как дебаггер исправит код.

Исправляем небольшую кодовую базу

У нас есть небольшая кодовая база, состоящая всего из двух файлов, один из которых содержит ошибку.

# code1.py
import code2

def greet(name):
    print("Привет, " + name)

greet("Алиса")

result = code2.add(10, "5")  # Эта строка — причина ошибки TypeError 
print(result)

# code2.py
def add(a, b):
    return a + b  # В этом месте будет вызвана TypeError

Итак, в файле code1.py вызывается функция add() из подключённого модуля code2, в которую передаются два аргумента: целое число 10 и строка "5". Поскольку эти аргументы имеют разные типы, а функция add() в code2.py не выполняет проверку типов и просто пытается сложить переданные значения, возникает ошибка TypeError. Ошибка происходит внутри функции add() в code2.py, так как операция сложения между числом и строкой является недопустимой без явного приведения типов.

Сначала генерируем юзер‑промт (USER_PROMPT), объединяющий в одну программную строку следующие данные: структуру папок (get_dir_tree(codebase)), файлы исходного кода (read_code_files(codebase)) и логи возникших при выполнении ошибок (run_code_and_capture_error(codebase)).

# Указываем каталог с кодом
codebase = "project_code"

# Получаем структуру каталогов
directory_tree = get_dir_tree(codebase)

# Читаем содержимое файлов с кодом
code_content = read_code_files(codebase)

# Запускаем код и фиксируем ошибки
error_output = run_code_and_capture_error(codebase)

# Формируем юзер-промт для LLMs
USER_PROMPT = f'''Структура файлового дерева проекта:
                   {directory_tree}
                    
                   Содержимое файлов:
                   {code_content}

                   Обнаруженные ошибки:
                   {error_output}
               '''

Вот как будет выглядеть этот юзер‑промт:

Структура файлового дерева проекта:
                    
project_code
├── code1.py
├── code2.py


Содержимое файлов:

# code1.py
import code2

def greet(name):
    print("Привет, " + name)

greet("Алиса")

result = code2.add(10, "5")  # Эта строка — причина ошибки TypeError 
print(result)

# code2.py
def add(a, b):
    return a + b  # В этом месте будет вызвана TypeError

Обнаруженные ошибки:

TypeError: неподдерживаемые операнды для +: 'int' и 'str'

Теперь, наконец, передаём сгенерированный промт в языковую модель Hugging Face (HF_llm) для анализа и генерации исправлений. Модель определит ошибки в нашем коде и предложит способы их устранения.

# Вызываем функцию HF_llm с системным и пользовательским промтами
llm_response = HF_llm(
    model_name="meta-llama/Meta-Llama-3.1-3B-Instruct",  # Пример модели
    system_prompt=DEBUG_SYSTEM_PROMPT,
    user_prompt=USER_PROMPT
)

# Выводим ответ для просмотра исправлений
print(llm_response)


### ВЫВОД ###
code1.py
import code2
...

Мы выбрали модель Llama-3.2–3B*, которая оптимизирована для запуска даже на бюджетных видеокартах благодаря небольшому количеству параметров — 3 миллиарда. Несмотря на компактность, она поддерживает контекстное окно объёмом 128K, что делает её подходящей для анализа длинных фрагментов исходного кода.

Запустим функцию обновления файлов кода и посмотрим, как изменился файл code1.py.

# Обновляем код на основе ответа LLM
update_code_from_llm_response(llm_response)
Файл code1.py был обновлён — взглянем, как он выглядит после внесения исправлений:
# code1.py
import code2

def greet(name):
    print("Привет, " + name)

greet("Алиса")

result = code2.add(10, 5) # Исправлена ошибка типов
print(result)

Ура, ошибка типа устранена. Но заметим, что пока мы не применяли механизм бесконечного цикла, а значит, LLM не будет повторно корректировать код до полного их истребления.

Создание бесконечного цикла отладки

Итак, давайте разработаем этот бесконечный цикл, который будет работать до тех пор, пока в нашем коде не останется ошибок.

def infinite_debug_loop(codebase_dir, model_name, system_prompt):
    """
    Запускает бесконечный цикл отладки, в котором LLM исправляет код до полного устранения ошибок.

    Args:
    codebase_dir (str): Директория с кодом для отладки.
    model_name (str): Название модели Hugging Face для генерации исправлений.
    system_prompt (str): Системные инструкции для LLM.
    
    Returns:
    None: Функция напрямую обновляет кодовую базу.
    """
    iteration = 1
    while True:
        # Шаг 1: Получаем структуру файлов и содержимое кода
        directory_tree = get_dir_tree(codebase_dir)
        code_content = read_code_files(codebase_dir)

        # Шаг 2: Запускаем код и фиксируем ошибки
        error_output = run_code_and_capture_errors(codebase_dir)

        # Если ошибок нет, завершаем цикл
        if "Error occurred" not in error_output and "Traceback" not in error_output:
            print("Ошибок в коде не найдено, отладка завершена.")
            break

        # Шаг 3: Формируем запрос для LLM
        user_prompt = f'''Структура файлов проекта:
                          {directory_tree}

                          Содержимое файлов:
                          {code_content}

                          Обнаруженные ошибки:
                          {error_output}
                       '''

        # Шаг 4: Отправляем запрос LLM для исправления кода
        print(f"Итерация {iteration}: Отправка запроса в LLM для исправлений.")
        llm_response = HF_llm(model_name=model_name, system_prompt=system_prompt, user_prompt=user_prompt)

        # Шаг 5: Обновляем код на основе ответа LLM
        update_code_from_llm_response(llm_response)

        # Шаг 6: Небольшая пауза перед следующей итерацией
        iteration += 1
        print(f"Итерация {iteration}: Код обновлён. Переход к следующей итерации...n")
        time.sleep(2)  # Опционально: можно изменить время ожидания

Пройдёмся ещё раз по коду. В начале каждой итерации применяется функция get_dir_tree(), которая сканирует структуру каталога, создавая текстовое представление проекта. Затем метод read_code_files() извлекает содержимое файлов, чтобы их можно было передать в юзер‑промт. Код проверяемого проекта запускается функцией run_code_and_capture_errors(), которая проверяет, содержатся ли в выводе ключевые слова, сигнализирующие об ошибка: Error occurred либо Traceback. Если ошибок больше не проявляется, то цикл завершается.

Приведённый код демонстрирует простой бесконечный цикл, который работает до тех пор, пока в нашем коде не останется ошибок. Однако есть и немного расширенная версия в GitHub‑репозитории AI Debugger, где обрабатываются более сложные сценарии и ситуации.

Тестирование цикла

Давайте добавим новый, чуть более сложный сценарий кода. Этот проект будет состоять из трёх файлов, каждый из которых содержит различные типы ошибок.

# calculator.py
def add(a, b):
    return a + b
def subtract(a, b):
  return a - b
def multiply(a,b):
    return a * b
def divide(a,b):
   return a/b


# main.py
import calculator
def main():
    x = 10
    y = "5"
    z = 0
    result_add = calculator.add(x, y)
    print(f"Сложение: {result_add}")
    result_sub = calculator.subtract(x, y)
    print(f"Вычитание: {result_sub}")
    result_mul = calculator.multiply(x,y)
    print(f"Умножение: {result_mul}")
    result_div = calculator.divide(x,z)
    print(f"Деление: {result_div}")
if __name__ == "__main__":
    main()


# utils.py
def greet(name):
    message = "Привет, " + name
  return message

Ошибки, которые мы намереваемся обнаружить:

  • TypeError: функции add(), subtract() и multiply(), где ожидаются целые числа, вызываются с аргументами строкового типа.

  • ZeroDivisionError: ошибка деления на ноль в функции divide(), так как z = 0.

  • IndentationError: Оператор return неправильно выровнен по отступу.

Сценарий подготовлен, вызовем функцию infinite_debug_loop(), чтобы она занялась исправлением:

# Указываем директорию с кодом
codebase_dir = "complex_project_dir"

# Задаём название модели и системный промпт для LLM
model_name = "meta-llama/Meta-Llama-3.1-3B-Instruct"  # или другая модель HF

system_prompt = DEBUG_SYSTEM_PROMPT

# Запускаем бесконечный цикл отладки для исправления кода
infinite_debug_loop(codebase_dir, model_name, system_prompt)

Этот фрагмент кода задаёт директорию проекта, выбирает модель LLM и её системный промт, а затем вызывает функцию infinite_debug_loop(). Что происходит дальше? Запускается процесс отладки, в ходе которого система сканирует проект, обнаруживает ошибки, исправляет их с помощью LLM и повторяет цикл, пока все проблемы не будут устранены.

После запуска вы увидите результат каждой итерации цикла: какие ошибки найдены, какой ответ дала LLM (включая обновлённые блоки кода) и какие изменения внесены в файлы. По завершении цикла код должен быть исправлен.

Итерация 1: Отправка запроса в LLM для исправлений...
Итерация 1: Код обновлён. Переход к следующей итерации...

Итерация 2: Отправка запроса в LLM для исправлений...
Итерация 2: Код обновлён. Переход к следующей итерации...

Ошибок в коде не найдено, отладка завершена.

На всё про всё потребовалось две итерации. А теперь взглянем на файлы после исправлений и убедимся, что ошибки действительно устранены.

# calculator.py
def add(a, b):
    return a + b
def subtract(a, b):
    return a - b
def multiply(a,b):
    return a * b
def divide(a,b):
   if b == 0:
       return "Деление на ноль невозможно"
   return a/b

# main.py
import calculator
def main():
    x = 10
    y = 5
    z = 0

    result_add = calculator.add(x, y)
    print(f"Сложение: {result_add}")

    result_sub = calculator.subtract(x, y)
    print(f"Вычитание: {result_sub}")

    result_mul = calculator.multiply(x,y)
    print(f"Умножение: {result_mul}")

    result_div = calculator.divide(x,z)
    print(f"Деление: {result_div}")

if __name__ == "__main__":
    main()

# utils.py
def greet(name):
    message = "Привет, " + name
    return message

Как видно, проблемы вроде TypeError из файла main.py, ZeroDivisionError из того же файла и даже IndentationError из utils.py благополучно ушли в прошлое и наш бесконечный цикл справился с этим нелёгким сценарием. Шах и мат, баги.

Поиск в интернете

Иногда одной лишь LLM может не хватить, чтобы справиться с ошибкой. Здесь на помощь приходит интернет — незаменимый инструмент в поиске решений. Почему бы не встроить в цикл возможность обращения к поисковикам, как это делаем мы, когда гуглим ошибки?

Первый шаг — преобразовать юзер‑промт в Google‑запрос. Для этого можно использовать ту же функцию LLM, передав ей тщательно составленный системный промт, направленный на превращение описанной ошибки в однострочный запрос для поисковика. Создадим системный промт для LLM, который будет заточен на это преобразование, и посмотрим, как он преобразует детализированную ошибку в запрос для Google.

QUERY_SYSTEM_PROMPT = """
    Вы высококвалифицированный ассистент для разработчиков программного обеспечения, специализирующийся на поиске и устранении ошибок в коде. Ваша задача — составить **однострочный поисковый запрос в Google** на основе переданного фрагмента кода и связанного с ним сообщения об ошибке. Запрос должен соответствовать следующим правилам:

    1. Включать название языка программирования или фреймворка (если известно).
    2. Содержать ключевые детали из сообщения об ошибке.
    3. Упоминать любые релевантные функции, методы или библиотеки из кода.
    4. Быть лаконичным, точным и оформленным в виде **одной строки**.

    **Формат входных данных:**
    1. **Фрагмент кода:** (например, Python-код, вызывающий исключение)
    2. **Сообщение об ошибке:** (например, `TypeError: неподдерживаемые операнды для +: 'int' и 'str'`)

    **Формат выходных данных:**
    Строка в точности в следующем формате:
    `search_query: "<ваш поисковый запрос в Google>"`
    """

Пример запроса, созданного моделью LLM, размером 3B, для одной из наших ошибок:

 ### НАША ОШИБКА
TypeError: неподдерживаемые операнды для +: 'int' и 'str'

# Вызов функции HF_llm с системным и пользовательским промтами
llm_response = HF_llm(
    model_name="meta-llama/Meta-Llama-3.1-3B-Instruct",  # Пример модели
    system_prompt=QUERY_SYSTEM_PROMPT,
    user_prompt=USER_PROMPT
)

print(llm_response)

### ВЫВОД ###
search_query: python "TypeError" convert int to string addition

Выглядит корректно: python "TypeError" convert int to string addition. Затем мы передадим это в поисковую систему, ведь велика вероятность, что интернет выдаст решение, которого нет в LLM‑модели.

Для получения результатов поиска Google мы будем использовать библиотеку scrapling. С её помощью создадим функцию для парсинга результатов поиска:

async def internet_search(search_query):
    """
    Асинхронный поиск в интернете с использованием Google, возвращает первые найденные ссылки.

    Args:
         search_query (str): Поисковый запрос для Google.

    Returns:
         list: Список первых найденных URL.
    """
    search_query = f'"{search_query}"'
    search_url = f"https://www.google.com/search?q={search_query}"

    fetcher = StealthyFetcher()

    try:
        page = await fetcher.async_fetch(search_url)
        all_urls = page.css("#search a::attr(href)")

        # Фильтруем список, оставляя только ссылки, начинающиеся с https
        all_urls = [url for url in all_urls if url.startswith("https")]

        return all_urls
    except Exception as e:
        print(f"Произошла ошибка: {e}")

Эта функция асинхронная, ведь интернет не всегда торопится с ответами. Она извлекает URL‑адреса из верхних результатов Google и возвращает их в виде списка. Далее мы сможем получить содержимое этих URL‑адресов с помощью нескольких дополнительных функций scrapling.

async def fetch_internet_content(search_query, num_urls = 5):
    """
    Асинхронно выполняет поиск в интернете и собирает содержимое веб-страниц по заданному запросу.

    Проверяет возможность выполнения интернет-поиска, получает URL-адреса и извлекает текстовое содержимое с нескольких веб-страниц, обрабатывая возможные ошибки.

    Args:
        search_query (str): Поисковый запрос для поиска веб-страниц.
        num_urls (int): Максимальное количество URL-адресов для загрузки (default: 5)

    Returns:
        str: Объединенный текст, полученный с веб-страниц,
              или пустую строку, если поиск отключен/результаты не найдены.
    """
    # Проверяем, включен ли интернет-поиск
    if not self.enable_internet_search:
        return ""

    # Выполняем интернет-поиск для получения URL-адресов
    urls = await internet_search(search_query)
    
    # Возвращаем пустую строку, если не найдено ни одного URL
    if not urls:
        return ""

    # Инициализируем парсер без автоматического сопоставления
    fetcher = Fetcher(auto_match=False)
    
    # Переменная для хранения объединенного содержимого веб-страниц
    combined_content = ""

    # Перебираем первые num_urls URL-адресов
    for url in urls[:num_urls]:
        try:
            # Загружаем страницу с заголовками, маскирующими бота
            page = fetcher.get(url, stealthy_headers=True)
            
            # Извлекаем текстовое содержимое, игнорируя теги <script> и <style>
            content = page.get_all_text(ignore_tags=("script", "style"))
            
            # Добавляем текст с заголовком URL-источника для отслеживания
            combined_content += f"### СОДЕРЖИМОЕ С: {url}n{content}nn"
        
        except Exception as e:
            # Логируем ошибки при загрузке URL, не прерывая выполнение
            logging.error(f"Ошибка при загрузке {url}: {e}")

    return combined_content

Так как ссылок может быть много, разумно ограничить их количество, взяв несколько первых результатов. После эти данные нужно будет добавить в юзер‑промт.

Цикл отладки с использованием интернет-поиска

Давайте наконец‑то усовершенствуем наш бесконечный цикл отладки, добавив в него суперсилу — интернет‑поиск. Если одна и та же ошибка повторяется пять или более раз подряд, система будет искать решения в сети.

def infinite_debug_loop_with_internet(codebase_dir, 
                                      model_name,
                                      system_prompt,
                                      max_attempts=10):
    """
    Запускает расширенный цикл отладки, который использует интернет-поиск 
    для устранения повторяющихся ошибок.

    Args:
    codebase_dir (str): Каталог с Python-кодом, который нужно отладить.
    model_name (str): Название модели Hugging Face, используемой для исправления ошибок.
    system_prompt (str): Системные инструкции для языковой модели (LLM).
    max_attempts (int): Максимальное число попыток перед обращением к интернету.    

    Returns:
    None: Функция напрямую обновляет кодовую базу.
    """
    iteration = 1
    error_tracking = {}  # Отслеживание ошибок на протяжении итераций

    while True:
        # Шаг 1: Получаем структуру файлов и содержимое кода
        directory_tree = get_dir_tree(codebase_dir)
        code_content = read_code_files(codebase_dir)

        # Шаг 2: Запускаем код и фиксируем ошибки
        error_output = run_code_and_capture_errors(codebase_dir)

        # Если ошибок нет, завершаем цикл
        if "Error occurred" not in error_output and "Traceback" not in error_output:
            print("Ошибок в коде не найдено, отладка завершена.")
            break

        # Track error persistence
        if error_output in error_tracking:
            error_tracking[error_output] += 1
        else:
            error_tracking[error_output] = 1

        # Формируем пользовательский запрос, включая возможные результаты интернет-поиска
        user_prompt = Структура файлов проекта:
                          {directory_tree}

                          Содержимое файлов:
                          {code_content}

                          Обнаруженные ошибки:
                          {error_output}
                       '''

        # Если ошибка повторяется более max_attempts раз, обращаемся к интернет-поиску
        if error_tracking[error_output] > max_attempts:
            # Генерируем поисковый запрос для ошибки
            search_query_prompt = f" Фрагмент кода:n{code_content}nnСообщение об ошибке:n{error_output}"
            search_query_response = HF_llm(
                model_name=model_name, 
                system_prompt=QUERY_SYSTEM_PROMPT, 
                user_prompt=search_query_prompt
            )
            
            # Извлекаем поисковый запрос
            search_query = search_query_response.split(": ")[-1].strip('"')
            
            # Получаем содержимое с найденных веб-страниц
            internet_content = await fetch_internet_content(search_query)
            
            # Добавляем найденную информацию к юзер-промту
            user_prompt += f"nnРезультаты интернет-поиска:n{internet_content}"

        # Step 4: Call the LLM to generate fixes
        print(f"Итерация {iteration}: Отправка запроса в LLM для исправлений.")
        llm_response = HF_llm(
            model_name=model_name, 
            system_prompt=system_prompt, 
            user_prompt=user_prompt
        )

        # Шаг 5: Обновление кода на основе ответа LLM
        update_code_from_llm_response(llm_response)

        # Сбрасываем счетчик для ошибки, если LLM предложила исправление
        error_tracking[error_output] = 0

        # Шаг 6: Продолжаем отладку
        iteration += 1
        print(f"Итерация {iteration}: Код обновлён. Переход к следующей итерации...n")
        time.sleep(2)  # Опциональная пауза между итерациями

Итак, функция infinite_debug_loop_with_internet_search() пытается по кругу исправить ошибки с помощью языковой модели LLM, но, если «локальные» исправления не помогают и количество попыток для одной и той же ошибки превысило max_attempts, она начинает искать решения в интернете. Для этого применяется динамическое генерирование запросов.

Тестирование на GitHub

Мой репозиторий на GitHub предлагает более детализированное решение, которое поддерживает различные типы LLM, обрабатывает логи и предоставляет другие полезные функции. Здесь мы рассмотрим общий процесс. Так, протестируем репозиторий на более сложной кодовой базе и посмотрим, как он справляется.

python -m src.cli.main 
 --llm_type huggingface 
 --huggingface_model "meta-llama/Llama-3.2-3B-Instruct" 
 --code_dir "ai-debugger" 
 --huggingface_device "cuda" 
 --max_attempts 10 
 --enable_internet_search True 
 --num_search_urls 5 
 --internet_search_threshold 5

Этот скрипт, доступный в репозитории, предоставляет гибкие настройки: к примеру, можно включать или отключать интернет‑поиск или задавать параметры для моделей. Я использовал его на своей кодовой базе, содержащей сложные скрипты для обучения трансформеров, и в коде действительно обнаружились ошибки. Запустим процесс и посмотрим, как начинается отладка:

Гуглить баги — это нормально. Как AI Debugger освоил этот навык и сам исправляет ошибки - 3
## Вывод в консоли

2025-01-23 19:46:54,070 - INFO - HuggingFace-модель инициализирована
2025-01-23 19:46:54,070 - INFO - Файлы не переданы. Загружаем все файлы

2025-01-23 19:46:54,076 - INFO - Попытка 1...

2025-01-23 19:46:59,339 - INFO - Загружается модель LLaMA-3.1-3B-instruct
2025-01-23 19:46:59,358 - INFO - ИИ внес следующие изменения:

Файл: code2.py
  + from caleb import transform
  + return x + z
  ...
  + except Exception as e:
  + print("Произошла непредвиденная ошибка: ", str(e))

File: code3.py
  - from llmc import transformer_module
  + from code2 import transformer_module_
  + try:
  - print(parms.round())
  ...
  + print(llm.cab("Обучены следующие параметры:", parms)
  + except Exception as e:
  + print("Произошла непредвиденная ошибка: ", str(e))

...

2025-01-23 19:46:59,581 - INFO - Все ошибки исправлены.

После долгого ожидания, почти 34 итераций, система смогла устранить множество ошибок в моём коде, одновременно записывая детальные логи об удалённых или добавленных фрагментах. Эти логи сохраняются в директории проекта, который вы отлаживаете.


Дебаггинг с помощью LLM, который ещё и гуглит ошибки, скорее экспериментальный, так как его сложно применять в реальных проектах, особенно в крупных. Но более важная проблема здесь — безопасность: в интернете полно сомнительных советов, и почти ничто не мешает модели «починить» ваш код так, что он станет напичканным уязвимостями. На деле куда эффективнее использовать среды разработки с интегрированными LLM, чем пытаться автоматизировать гугление ошибок таким неординарным способом.

Что скажете по поводу этой реализации? Добавили бы вы в неё что‑то ещё?

* Продукт Llama принадлежит организации Meta, которая признана в России террористической и запрещена.

Автор: dmitrifriend

Источник

Рейтинг@Mail.ru
Rambler's Top100