Перейти к содержанию

Loop

На этой странице Основной оркестрационный движок — класс AIAgent из run_agent.py — примерно 13 700 строк, отвечающих за всё: от сборки промптов до диспетчеризации инструментов и переключения провайдеров.

Основные обязанности

AIAgent отвечает за:

  • Сборку эффективного системного промпта и схем инструментов через prompt_builder.py
  • Выбор правильного провайдера/режима API (chat_completions, codex_responses, anthropic_messages)
  • Прерываемые вызовы модели с поддержкой отмены
  • Выполнение вызовов инструментов (последовательно или параллельно через пул потоков)
  • Ведение истории диалога в формате сообщений OpenAI
  • Обработку сжатия, повторных попыток и переключения на запасную модель
  • Отслеживание лимита итераций для родительских и дочерних агентов
  • Сохранение персистентной памяти перед потерей контекста

Две точки входа

# Простой интерфейс — возвращает итоговую строку ответа
response = agent.chat("Fix the bug in main.py")

# Полный интерфейс — возвращает словарь с сообщениями, метаданными и статистикой использования
result = agent.run_conversation(
    user_message="Fix the bug in main.py",
    system_message=None,           # собирается автоматически, если опущен
    conversation_history=None,      # автоматически загружается из сессии, если опущена
    task_id="task_abc123"
)

chat() — это тонкая обёртка над run_conversation(), извлекающая поле final_response из результирующего словаря.

Режимы API

Hermes поддерживает три режима выполнения API, определяемые на основе выбора провайдера, явных аргументов и эвристик по базовому URL:

Режим API Используется для Тип клиента
chat_completions Эндпоинты, совместимые с OpenAI (OpenRouter, кастомные, большинство провайдеров) openai.OpenAI
codex_responses OpenAI Codex / Responses API openai.OpenAI с форматом Responses
anthropic_messages Нативный Anthropic Messages API anthropic.Anthropic через адаптер

Режим определяет, как форматируются сообщения, как структурируются вызовы инструментов, как парсятся ответы и как работают кэширование/стриминг. Все три режима сходятся к одному и тому же внутреннему формату сообщений (словари в стиле OpenAI с role/content/tool_calls) до и после вызовов API.

Порядок определения режима:

  1. Явный аргумент конструктора api_mode (наивысший приоритет)
  2. Определение по провайдеру (например, провайдер anthropicanthropic_messages)
  3. Эвристики по базовому URL (например, api.anthropic.comanthropic_messages)
  4. По умолчанию: chat_completions

Жизненный цикл шага

Каждая итерация цикла агента следует такой последовательности:

run_conversation()
  1. Генерация task_id, если не предоставлен
  2. Добавление сообщения пользователя в историю диалога
  3. Сборка или повторное использование кэшированного системного промпта (prompt_builder.py)
  4. Проверка необходимости сжатия перед запросом (контекст > 50%)
  5. Формирование API-сообщений из истории диалога
     - chat_completions: формат OpenAI как есть
     - codex_responses: преобразование во входные элементы Responses API
     - anthropic_messages: преобразование через anthropic_adapter.py
  6. Внедрение эфемерных слоёв промпта (предупреждения о лимитах, давление контекста)
  7. Применение маркеров кэширования промпта (если Anthropic)
  8. Прерываемый вызов API (_interruptible_api_call)
  9. Обработка ответа:
     - Если есть tool_calls: выполнить их, добавить результаты, вернуться к шагу 5
     - Если текстовый ответ: сохранить сессию, сбросить память при необходимости, вернуть результат

Формат сообщений

Все сообщения внутри используют совместимый с OpenAI формат:

{"role": "system", "content": "..."}
{"role": "user", "content": "..."}
{"role": "assistant", "content": "...", "tool_calls": [...]}
{"role": "tool", "tool_call_id": "...", "content": "..."}

Содержимое рассуждений (от моделей с поддержкой расширенного мышления) хранится в assistant_msg["reasoning"] и опционально отображается через reasoning_callback.

Правила чередования сообщений

Цикл агента обеспечивает строгое чередование ролей сообщений:

  • После системного сообщения: Пользователь → Ассистент → Пользователь → Ассистент → ...
  • Во время вызова инструментов: Ассистент (с tool_calls) → Инструмент → Инструмент → ... → Ассистент
  • Никогда два сообщения ассистента подряд
  • Никогда два сообщения пользователя подряд
  • Только роль tool может иметь последовательные записи (результаты параллельных инструментов)

Провайдеры проверяют эти последовательности и могут отклонить некорректную историю.

Прерываемые вызовы API

API-запросы обёрнуты в _interruptible_api_call(), которая запускает фактический HTTP-запрос в фоновом потоке, одновременно отслеживая событие прерывания:

┌────────────────────────────────────────────────────┐
│  Главный поток              Поток API              │
│                                                     │
│   Ожидание:                   HTTP POST             │
│    - готовности ответа  ───▶  к провайдеру          │
│    - события прерывания                              │
│    - тайм-аута                                       │
└────────────────────────────────────────────────────┘

При прерывании (пользователь отправил новое сообщение, команда /stop или сигнал):

  • Поток API завершается (ответ отбрасывается)
  • Агент может обработать новый ввод или завершить работу корректно
  • Частичный ответ не вставляется в историю диалога

Выполнение инструментов

Последовательное vs параллельное

Когда модель возвращает вызовы инструментов:

  • Одиночный вызов инструмента → выполняется непосредственно в главном потоке
  • Несколько вызовов инструментов → выполняются параллельно через ThreadPoolExecutor
  • Исключение: инструменты, помеченные как интерактивные (например, clarify), принудительно выполняются последовательно
  • Результаты вставляются в исходном порядке вызовов инструментов независимо от порядка завершения

Поток выполнения

for each tool_call in response.tool_calls:
    1. Определить обработчик из tools/registry.py
    2. Запустить хук плагина pre_tool_call
    3. Проверить, является ли команда опасной (tools/approval.py)
       - Если опасная: вызвать approval_callback, ожидать пользователя
    4. Выполнить обработчик с аргументами + task_id
    5. Запустить хук плагина post_tool_call
    6. Добавить {"role": "tool", "content": result} в историю

Инструменты уровня агента

Некоторые инструменты перехватываются run_agent.py до того, как они достигают handle_function_call():

Инструмент Почему перехватывается
todo Чтение/запись локального состояния задач агента
memory Запись в файлы персистентной памяти с ограничениями по символам
session_search Поиск по истории сессий через БД сессий агента
delegate_task Создание подчинённых агентов с изолированным контекстом

Эти инструменты изменяют состояние агента напрямую и возвращают синтетические результаты инструментов, минуя реестр.

Поверхности обратных вызовов

AIAgent поддерживает обратные вызовы для конкретных платформ, обеспечивающие отображение прогресса в реальном времени в CLI, gateway и ACP-интеграциях:

Callback Когда вызывается Используется
tool_progress_callback До/после каждого выполнения инструмента Спиннер CLI, сообщения о прогрессе в gateway
thinking_callback Когда модель начинает/заканчивает размышление Индикатор "thinking..." в CLI
reasoning_callback Когда модель возвращает содержимое рассуждений Отображение рассуждений в CLI, блоки рассуждений в gateway
clarify_callback Когда вызывается инструмент clarify Запрос ввода в CLI, интерактивное сообщение в gateway
step_callback После каждого полного шага агента Отслеживание шагов в gateway, прогресс в ACP
stream_delta_callback Каждый токен стриминга (когда включено) Стриминговое отображение в CLI
tool_gen_callback Когда вызов инструмента извлекается из стрима Предпросмотр инструмента в спиннере CLI
status_callback Изменения состояния (размышление, выполнение и т.д.) Обновления статуса в ACP

Лимиты и запасное поведение

Лимит итераций

Агент отслеживает итерации через IterationBudget:

  • По умолчанию: 90 итераций (настраивается через agent.max_turns)
  • Каждый агент получает собственный лимит. Подчинённые агенты получают независимые лимиты, ограниченные delegation.max_iterations (по умолчанию 50) — общее количество итераций родительского и дочерних агентов может превышать лимит родителя
  • При достижении 100% агент останавливается и возвращает сводку выполненной работы

Запасная модель

Когда основная модель выдаёт ошибку (429 — превышение лимита запросов, 5xx — серверная ошибка, 401/403 — ошибка аутентификации):

  1. Проверяется список fallback_providers в конфигурации
  2. Каждая запасная модель пробуется по порядку
  3. При успехе разговор продолжается с новым провайдером
  4. При ошибках 401/403 выполняется попытка обновления учётных данных перед переключением

Система запасных моделей также независимо покрывает вспомогательные задачи — для vision, сжатия, веб-извлечения и поиска по сессиям существует своя цепочка запасных моделей, настраиваемая через секцию конфигурации auxiliary.*.

Сжатие и сохранение состояния

Когда срабатывает сжатие

  • Предварительное (перед вызовом API): Если диалог превышает 50% контекстного окна модели
  • Авто-сжатие в gateway: Если диалог превышает 85% (более агрессивно, выполняется между шагами)

Что происходит во время сжатия

  1. Память сначала сохраняется на диск (предотвращение потери данных)
  2. Средние шаги диалога обобщаются в компактную сводку
  3. Последние N сообщений сохраняются нетронутыми (compression.protect_last_n, по умолчанию: 20)
  4. Пары сообщений вызов инструмента/результат сохраняются вместе (никогда не разделяются)
  5. Генерируется новый идентификатор линии сессии (сжатие создаёт «дочернюю» сессию)

Сохранение сессии

После каждого шага:

  • Сообщения сохраняются в хранилище сессий (SQLite через hermes_state.py)
  • Изменения в памяти сбрасываются в MEMORY.md / USER.md
  • Сессию можно возобновить позже через /resume или hermes chat --resume

Ключевые исходные файлы

Файл Назначение
run_agent.py Класс AIAgent — полный цикл агента (~13 700 строк)
agent/prompt_builder.py Сборка системного промпта из памяти, навыков, контекстных файлов, личности
agent/context_engine.py ABC ContextEngine — подключаемое управление контекстом
agent/context_compressor.py Движок по умолчанию — алгоритм сжатия с потерями
agent/prompt_caching.py Маркеры кэширования промптов Anthropic и метрики кэша
agent/auxiliary_client.py Вспомогательный LLM-клиент для побочных задач (vision, суммаризация)
model_tools.py Сбор схем инструментов, диспетчеризация handle_function_call()

Связанные документы