English translation is not available yet. Showing Russian content.
Реализовать Agent Loop с нуля
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать Agent Loop с нуля
1. Цель задачи
Научиться проектировать и реализовывать базовый цикл работы агента (цикл агента|цикл агента|Agent Loop) без использования высокоуровневых фреймворков (LangChain, CrewAI). Вы напишете на Python управляющий цикл, который последовательно вызывает LLM, парсит ответ на наличие tool calls, исполняет выбранные инструменты и записывает результат в память (историю диалога). Ключевой результат Работающий агент, способный выполнить как минимум 3 шага (call|вызов LLM → tool → вывод) исключительно на вашем коде.
2. Исходные данные
Перед началом необходимо иметь:
| Что нужно | Откуда взять |
|---|---|
| Python 3.10+ и опыт работы с ним | Установить / проверить |
| Базовый LLM API (OpenAI / Anthropic / Ollama) | Ключ API или локальная модель (через OpenAI-совместимый endpoint) |
| Описание инструментов (tools) в формате JSON Schema | Разработать самостоятельно (например: get_weather, calculator, search_wiki) |
| Системный промпт для агента | Написать, объяснить агенту, как и когда вызывать инструменты |
| Среда для тестирования (jupyter / скрипт) | Любая |
Если нет доступа к платному LLM API — симулируем:
- Используйте локальную модель через llama-cpp-python или ollama (например, llama3.2:1b).
- Для теста можно подменить LLM вызов заглушкой: функция, которая всегда возвращает предопределённый JSON с tool call.
- В production-ready решении заглушка заменяется на реальный вызов LLM.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Язык программирования | Python 3.10+ | Реализация цикла |
| LLM API | OpenAI / Anthropic / Ollama | Генерация ответов и tool calls |
| Формат сообщений | OpenAI ChatCompletions API (messages) | Единый интерфейс |
| Парсинг ответа | json.loads / pydantic | Извлечение tool_call из ответа LLM |
| Выполнение инструментов | Python functions + inspect.signature | Динамический вызов с валидацией аргументов |
| Память (история) | list[dict] in-memory | Хранение сообщений |
| Логирование | logging / rich | Отладка цикла |
| Тестирование | pytest | Unit-тесты для каждого компонента |
4. Этапы выполнения
Этап 1: Проектирование структуры агента (30 мин)
Действия
- Спроектируйте архитектуру цикла:
- Главная функция agent_loop(user_input, tools, system_prompt, max_steps=5).
- Внутри неё инициализируйте историю сообщений messages.
- Цикл while step < max_steps:
- Получить ответ от LLM.
- Если ответ содержит
tool_calls— извлечь, выполнить, добавить результаты в messages. - Иначе — вернуть ответ пользователю и завершить.
- Определите интерфейс инструмента:
class Tool: name: str description: str parameters: dict # JSON schema func: callable - Создайте 2-3 простых инструмента для демонстрации (например,
get_current_timeбез аргументов,multiply(a,b)). - Напишите системный промпт, который инструктирует LLM отвечать в формате:
Ожидаемый результат этапа Чёткая схема цикла, определённые классы и инструменты.
Этап 2: Реализация вызова LLM и парсинга ответа (1 час)
Действия
- Напишите функцию call_llm(messages: list) -> dict, которая отправляет запрос к API и возвращает ответ (содержимое
choices[0].message). - Реализуйте парсинг:
- Обработайте крайние случаи:
- LLM вернул невалидный JSON → повторный запрос с сообщением об ошибке.
- LLM вызвал несуществующий инструмент → уведомить и пропустить.
- Протестируйте с простым запросом: "Сколько будет 3 умножить на 4?"
Ожидаемый результат этапа Функция parse_tool_call корректно извлекает вызов инструмента.
Этап 3: Выполнение инструментов и обработка результатов (1 час)
Действия
- Напишите функцию execute_tool(tool_name, arguments, tools) -> str:
- Найдите инструмент по имени.
- Проверьте аргументы согласно JSON schema (можно использовать pydantic или просто
isinstance). - Вызовите tool.func(**arguments) и верните результат (преобразованный в строку).
- Обработайте исключения времени выполнения (деление на ноль, таймаут) — верните сообщение об ошибке.
- Добавьте результат вызова инструмента в messages как сообщение с ролью "tool":
(если используете OpenAI-формат) или просто {"role": "tool", "name": tool_name, "content": result}.messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result}) - Убедитесь, что после каждого tool call цикл снова вызывает LLM, чтобы агент мог продолжить.
Ожидаемый результат этапа Агент может выполнить цепочку «LLM → tool → LLM → tool → …».
Этап 4: Интеграция памяти и финальный цикл (1 час)
Действия
- Реализуйте управление историей:
- Напишите основной цикл с условием выхода:
- Если в ответе нет tool call → сформировать финальный ответ пользователю.
- Если превышен
max_steps→ вернуть сообщение о лимите.
- Протестируйте сценарий из нескольких шагов, например:
- "Узнай текущее время, затем умножь его на 2 (часы) и скажи результат".
- Добавьте логирование каждого шага с помощью
logging(INFO-level) для отладки.
Ожидаемый результат этапа Полный рабочий цикл, сохраняющий контекст между шагами.
Этап 5: Тестирование и документирование (30 мин)
Действия
- Напишите три unit-теста с помощью
pytest: - Проверьте, что агент корректно обрабатывает ошибки (невалидный JSON, неизвестный инструмент).
- Напишите краткую документацию (README), описывающую как запустить цикл и добавить новый инструмент.
Ожидаемый результат этапа Код покрыт тестами, документирован, готов к демонстрации.
5. Критерии приемки (Definition of Done)
- Цикл полностью написан без использования LangChain / CrewAI / других agent-фреймворков.
- Агент успешно выполняет последовательность из 3 шагов (например, «вызвать tool → получить результат → вызвать другой tool → ответить»).
- Реализовано не менее 2 различных инструментов (с разными сигнатурами).
- Обработаны случаи: невалидный JSON от LLM, неизвестный инструмент, ошибка выполнения инструмента.
- История сообщений правильно аккумулируется и передаётся в каждый последующий вызов LLM.
- Цикл имеет настраиваемый лимит шагов (max_steps) и корректно завершается при его превышении.
- Код покрыт минимум 3 unit-тестами, все проходят.
- Проект опубликован в репозитории (GitHub) или сдан в виде архива с README.
6. Ожидаемый результат
Основной артефакт
- Python-файл
agent_loop.py(или модуль), содержащий:- Определение класса
Tool. - Функции
call_llm(реальная или заглушка),parse_tool_call,execute_tool,agent_loop. - Реализацию двух демонстрационных инструментов.
- Определение класса
- Файл
test_agent.pyс тестами. - Файл
README.mdс инструкцией по запуску и примером использования.
Опционально
- Поддержка стриминга ответов LLM.
- Логирование через
richдля наглядного вывода. - Возможность загрузки инструментов из внешнего конфига (YAML/JSON).
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| LLM не всегда возвращает tool call в нужном формате | Использовать structured output (OpenAI response_format / json_mode) или итеративные исправления: при ошибке парсинга отправить LLM сообщение: «Твой ответ не является валидным JSON. Повтори попытку». |
| Аргументы инструмента не проходят валидацию | Использовать pydantic.BaseModel для параметров и генерировать JSON schema автоматически. |
| Бесконечный цикл из-за повторяющихся tool calls | Внедрить детектор повторений (если последние 3 вызова одинаковы — прервать). |
| Ограничение контекста (слишком длинная история) | Реализовать суммирование (summarization) старых сообщений или обрезать историю до последних N токенов. |
| Зависимость от внешнего API (таймауты, сбои) | Добавить retry с exponential backoff и фолбэк на заглушку в тестах. |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| Этап 1: Проектирование | 30 минут |
| Этап 2: LLM вызов + парсинг | 1 час |
| Этап 3: Выполнение инструментов | 1 час |
| Этап 4: Интеграция памяти + цикл | 1 час |
| Этап 5: Тестирование + документация | 30 минут |
| Итого | 4 часа |
Примечание Для первого выполнения задачи может потребоваться до 6 часов из-за отладки парсинга и edge cases.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 12 | Какие паттерны проектирования используются в agent loops? |
| 45 | Как реализовать tool calling без фреймворков? |
| 78 | Разница между ReAct и Plan-and-Execute агентами |
| 102 | Обработка ошибок при выполнении инструментов |
| 156 | Метрики оценки производительности агента |
| 211 | Управление историей диалога в памяти |
| 305 | Structured output в OpenAI API |
| 419 | Тестирование агентов с заглушками |
| 567 | Как избежать infinite loops в агентах |
| 689 | Сравнение форматов tool calls: OpenAI vs Anthropic |
10. Чек-лист самопроверки
- Я определил класс
Toolс полями name, description, parameters, func. - Моя функция
call_llmпринимает список сообщений и возвращает словарь. - Парсер корректно извлекает tool call как из JSON-строки, так и из нативного формата.
- После выполнения tool я добавляю результат в историю с правильной ролью.
- Цикл завершается, если LLM не вернул tool call или превышен max_steps.
- Я написал хотя бы два инструмента с разным количеством обязательных аргументов.
- Я покрыл тестами парсинг, выполнение и полный сценарий из 3 шагов.
- Код не содержит зависимостей от agent-фреймворков (LangChain, CrewAI).
- README содержит пример запуска и описание архитектуры.
- Проект запускается без ошибок на чистом Python 3.10+ с установленным
openai(или аналогичным).