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

Среда выполнения инструментов

Инструменты VibeOS — это саморегистрирующиеся функции, сгруппированные в наборы инструментов и выполняемые через центральную систему реестра/диспетчеризации.

Основные файлы:

  • tools/registry.py
  • model_tools.py
  • toolsets.py
  • tools/terminal_tool.py
  • tools/environments/*

Модель регистрации инструментов​

Каждый модуль инструмента вызывает registry.register(...) во время импорта.

model_tools.py отвечает за импорт/обнаружение модулей инструментов и построение списка схем, используемых моделью.

Как работает registry.register()​

Каждый файл инструмента в tools/ вызывает registry.register() на уровне модуля, чтобы объявить себя. Сигнатура функции:

registry.register(
name="terminal", # Уникальное имя инструмента (используется в схемах API)
toolset="terminal", # Набор инструментов, к которому принадлежит этот инструмент
schema={...}, # Схема вызова функций OpenAI (описание, параметры)
handler=handle_terminal, # Функция, которая выполняется при вызове инструмента
check_fn=check_terminal, # Опционально: возвращает True/False для проверки доступности
requires_env=["SOME_VAR"], # Опционально: необходимые переменные окружения (для отображения в UI)
is_async=False, # Является ли обработчик асинхронной корутиной
description="Run commands", # Человекочитаемое описание
emoji="💻", # Эмодзи для отображения спиннера/прогресса
)

Каждый вызов создает ToolEntry, хранящийся в словаре ToolRegistry._tools синглтона, ключом которого является имя инструмента. Если возникает коллизия имен между наборами инструментов, регистрируется предупреждение, и последняя регистрация имеет приоритет.

Обнаружение: discover_builtin_tools()​

При импорте model_tools.py он вызывает discover_builtin_tools() из tools/registry.py. Эта функция сканирует каждый файл tools/*.py с помощью AST-парсинга, чтобы найти модули, содержащие вызовы registry.register() верхнего уровня, а затем импортирует их:

# tools/registry.py (упрощенно)
def discover_builtin_tools(tools_dir=None):
tools_path = Path(tools_dir) if tools_dir else Path(__file__).parent
for path in sorted(tools_path.glob("*.py")):
if path.name in {"__init__.py", "registry.py", "mcp_tool.py"}:
continue
if _module_registers_tools(path): # AST-проверка на registry.register() верхнего уровня
importlib.import_module(f"tools.{path.stem}")

Это автоматическое обнаружение означает, что новые файлы инструментов подхватываются автоматически — нет необходимости вручную поддерживать список. AST-проверка соответствует только вызовам registry.register() верхнего уровня (не вызовам внутри функций), поэтому вспомогательные модули в tools/ не импортируются.

Каждый импорт запускает вызовы registry.register() модуля. Ошибки в опциональных инструментах (например, отсутствие fal_client для генерации изображений) перехватываются и логируются — они не препятствуют загрузке других инструментов.

После обнаружения основных инструментов также обнаруживаются инструменты MCP и плагинов:

  1. Инструменты MCP — tools.mcp_tool.discover_mcp_tools() читает конфигурацию MCP-сервера и регистрирует инструменты из внешних серверов.
  2. Инструменты плагинов — vibeos_cli.plugins.discover_plugins() загружает пользовательские/проектные/pip-плагины, которые могут регистрировать дополнительные инструменты.

Проверка доступности инструментов (check_fn)​

Каждый инструмент может опционально предоставить check_fn — вызываемый объект, который возвращает True, когда инструмент доступен, и False в противном случае. Типичные проверки включают:

  • Наличие API-ключа — например, lambda: bool(os.environ.get("SERP_API_KEY")) для веб-поиска
  • Работа сервиса — например, проверка, настроен ли сервер Honcho
  • Установленный бинарник — например, проверка доступности playwright для инструментов браузера

Когда registry.get_definitions() строит список схем для модели, он запускает check_fn() каждого инструмента:

# Упрощенно из registry.py
if entry.check_fn:
try:
available = bool(entry.check_fn())
except Exception:
available = False # Исключения = недоступен
if not available:
continue # Полностью пропустить этот инструмент

Ключевые особенности:

  • Результаты проверки кэшируются для каждого вызова — если несколько инструментов используют один и тот же check_fn, он выполняется только один раз.
  • Исключения в check_fn() рассматриваются как «недоступен» (отказоустойчивость).
  • Метод is_toolset_available() проверяет, проходит ли check_fn набора инструментов, используется для отображения в UI и разрешения наборов инструментов.

Разрешение наборов инструментов​

Наборы инструментов — это именованные пакеты инструментов. VibeOS разрешает их через:

  • явные списки включенных/отключенных наборов инструментов
  • предустановки платформы (vibeos-cli, vibeos-telegram и т.д.)
  • динамические наборы инструментов MCP
  • курируемые наборы специального назначения, такие как vibeos-acp

Как get_tool_definitions() фильтрует инструменты​

Основная точка входа — model_tools.get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode):

  1. Если предоставлен enabled_toolsets — включаются только инструменты из этих наборов. Каждое имя набора инструментов разрешается через resolve_toolset(), который расширяет составные наборы инструментов до отдельных имен инструментов.

  2. Если предоставлен disabled_toolsets — начать со ВСЕХ наборов инструментов, затем вычесть отключенные.

  3. Если не предоставлено ни того, ни другого — включить все известные наборы инструментов.

  4. Фильтрация реестра — разрешенный набор имен инструментов передается в registry.get_definitions(), который применяет фильтрацию check_fn и возвращает схемы в формате OpenAI.

  5. Динамическое исправление схем — после фильтрации схемы execute_code и browser_navigate динамически корректируются, чтобы ссылаться только на инструменты, которые фактически прошли фильтрацию (предотвращает галлюцинации модели о недоступных инструментах).

Устаревшие имена наборов инструментов​

Старые имена наборов инструментов с суффиксами _tools (например, web_tools, terminal_tools) сопоставляются с современными именами инструментов через _LEGACY_TOOLSET_MAP для обратной совместимости.

Диспетчеризация​

Во время выполнения инструменты диспетчеризируются через центральный реестр, с исключениями цикла агента для некоторых инструментов уровня агента, таких как обработка памяти/задач/поиска сессий.

Поток диспетчеризации: model tool_call → выполнение обработчика​

Когда модель возвращает tool_call, поток выглядит следующим образом:

Ответ модели с tool_call
↓
Цикл агента run_agent.py
↓
model_tools.handle_function_call(name, args, task_id, user_task)
↓
[Инструменты цикла агента?] → обрабатываются напрямую циклом агента (todo, memory, session_search, delegate_task)
↓
[Хук перед плагином] → invoke_hook("pre_tool_call", ...)
↓
registry.dispatch(name, args, **kwargs)
↓
Поиск ToolEntry по имени
↓
[Асинхронный обработчик?] → мост через _run_async()
[Синхронный обработчик?] → прямой вызов
↓
Возврат строки результата (или JSON-ошибки)
↓
[Хук после плагина] → invoke_hook("post_tool_call", ...)

Оборачивание ошибок​

Все выполнение инструментов обернуто в обработку ошибок на двух уровнях:

  1. registry.dispatch() — перехватывает любое исключение от обработчика и возвращает {"error": "Tool execution failed: ExceptionType: message"} в формате JSON.

  2. handle_function_call() — оборачивает всю диспетчеризацию во вторичный try/except, который возвращает {"error": "Error executing tool_name: message"}.

Это гарантирует, что модель всегда получает правильно сформированную JSON-строку, а не необработанное исключение.

Инструменты цикла агента​

Четыре инструмента перехватываются до диспетчеризации реестра, потому что им требуется состояние уровня агента (TodoStore, MemoryStore и т.д.):

  • todo — планирование/отслеживание задач
  • memory — запись в постоянную память
  • session_search — поиск по сессиям
  • delegate_task — порождение подсессий агента

Схемы этих инструментов по-прежнему регистрируются в реестре (для get_tool_definitions), но их обработчики возвращают фиктивную ошибку, если диспетчеризация каким-то образом достигает их напрямую.

Асинхронный мост​

Когда обработчик инструмента является асинхронным, _run_async() соединяет его с синхронным путем диспетчеризации:

  • Путь CLI (нет запущенного цикла) — использует постоянный цикл событий для поддержания активности кэшированных асинхронных клиентов
  • Путь шлюза (запущенный цикл) — запускает одноразовый поток с asyncio.run()
  • Рабочие потоки (параллельные инструменты) — использует постоянные циклы для каждого потока, хранящиеся в локальном хранилище потоков

Поток одобрения DANGEROUS_PATTERNS​

Терминальный инструмент интегрирует систему одобрения опасных команд, определенную в tools/approval.py:

  1. Обнаружение шаблонов — DANGEROUS_PATTERNS — это список кортежей (regex, description), охватывающих деструктивные операции:

    • Рекурсивное удаление (rm -rf)
    • Форматирование файловой системы (mkfs, dd)
    • Деструктивные операции SQL (DROP TABLE, DELETE FROM без WHERE)
    • Перезапись системных конфигов (> /etc/)
    • Управление службами (systemctl stop)
    • Удаленное выполнение кода (curl | sh)
    • Форк-бомбы, завершение процессов и т.д.
  2. Обнаружение — перед выполнением любой терминальной команды detect_dangerous_command(command) проверяет все шаблоны.

  3. Запрос на одобрение — если найдено совпадение:

    • Режим CLI — интерактивный запрос предлагает пользователю одобрить, отклонить или разрешить навсегда
    • Режим шлюза — асинхронный обратный вызов одобрения отправляет запрос на платформу обмена сообщениями
    • Умное одобрение — опционально, вспомогательная LLM может автоматически одобрять низкорисковые команды, соответствующие шаблонам (например, rm -rf node_modules/ безопасно, но соответствует «рекурсивному удалению»)
  4. Состояние сессии — одобрения отслеживаются для каждой сессии. После одобрения «рекурсивного удаления» для сессии последующие команды rm -rf не запрашивают повторного одобрения.

  5. Постоянный белый список — опция «разрешить навсегда» записывает шаблон в command_allowlist в config.yaml, сохраняясь между сессиями.

Терминальные/среды выполнения​

Терминальная система поддерживает несколько бэкендов:

  • local
  • docker
  • ssh
  • singularity
  • modal
  • daytona

Она также поддерживает:

  • переопределение рабочей директории для каждой задачи
  • управление фоновыми процессами
  • режим PTY
  • обратные вызовы одобрения для опасных команд

Параллелизм​

Вызовы инструментов могут выполняться последовательно или параллельно в зависимости от состава инструментов и требований взаимодействия.

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