Перейти к основному содержимому

Внутреннее устройство цикла агента

Основным механизмом оркестрации является класс AIAgent из файла run_agent.py — большой файл, который обрабатывает всё: от сборки промптов до диспетчеризации инструментов и переключения провайдеров при сбоях.

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

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

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

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

# Простой интерфейс — возвращает итоговую строку ответа
response = agent.chat("Исправь ошибку в main.py")

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

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

Режимы API​

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

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

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

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

  1. Явный аргумент конструктора api_mode (наивысший приоритет)
  2. Обнаружение по провайдеру (например, провайдер anthropic → anthropic_messages)
  3. Эвристики базового URL (например, api.anthropic.com → anthropic_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 завершается (ответ отбрасывается)
  • Агент может обработать новый ввод или корректно завершить работу
  • Частичный ответ не вставляется в историю диалога

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

Последовательное и параллельное выполнение​

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

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

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

для каждого tool_call в 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": результат} в историю

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

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

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

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

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

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

Обратный вызовКогда вызываетсяИспользуется
tool_progress_callbackДо/после каждого выполнения инструментаСпиннер CLI, сообщения о прогрессе шлюза
thinking_callbackКогда модель начинает/заканчивает думатьИндикатор «думает...» в CLI
reasoning_callbackКогда модель возвращает содержимое рассужденийОтображение рассуждений в CLI, блоки рассуждений шлюза
clarify_callbackКогда вызывается инструмент clarifyПриглашение ввода в CLI, интерактивное сообщение шлюза
step_callbackПосле каждого полного шага агентаОтслеживание шагов шлюза, прогресс 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 попытаться обновить учётные данные перед переключением

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

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

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

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

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

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

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

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

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

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

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

Связанная документация​