Среда выполнения инструментов
Инструменты VibeOS — это саморегистрирующиеся функции, сгруппированные в наборы инструментов и выполняемые через центральную систему реестра/диспетчеризации.
Основные файлы:
tools/registry.pymodel_tools.pytoolsets.pytools/terminal_tool.pytools/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 и плагинов:
- Инструменты MCP —
tools.mcp_tool.discover_mcp_tools()читает конфигурацию MCP-сервера и регистрирует инструменты из внешних серверов. - Инструменты плагинов —
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):
-
Если предоставлен
enabled_toolsets— включаются только инструменты из этих наборов. Каждое имя набора инструментов разрешается черезresolve_toolset(), который расширяет составные наборы инструментов до отдельных имен инструментов. -
Если предоставлен
disabled_toolsets— начать со ВСЕХ наборов инструментов, затем вычесть отключенные. -
Если не предоставлено ни того, ни другого — включить все известные наборы инструментов.
-
Фильтрация реестра — разрешенный набор имен инструментов передается в
registry.get_definitions(), который применяет фильтрациюcheck_fnи возвращает схемы в формате OpenAI. -
Динамическое исправление схем — после фильтрации схемы
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", ...)
Оборачивание ошибок
Все выполнение инструментов обернуто в обработку ошибок на двух уровнях:
-
registry.dispatch()— перехватывает любое исключение от обработчика и возвращает{"error": "Tool execution failed: ExceptionType: message"}в формате JSON. -
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:
-
Обнаружение шаблонов —
DANGEROUS_PATTERNS— это список кортежей(regex, description), охватывающих деструктивные операции:- Рекурсивное удаление (
rm -rf) - Форматирование файловой системы (
mkfs,dd) - Деструктивные операции SQL (
DROP TABLE,DELETE FROMбезWHERE) - Перезапись системных конфигов (
> /etc/) - Управление службами (
systemctl stop) - Удаленное выполнение кода (
curl | sh) - Форк-бомбы, завершение процессов и т.д.
- Рекурсивное удаление (
-
Обнаружение — перед выполнением любой терминальной команды
detect_dangerous_command(command)проверяет все шаблоны. -
Запрос на одобрение — если найдено совпадение:
- Режим CLI — интерактивный запрос предлагает пользователю одобрить, отклонить или разрешить навсегда
- Режим шлюза — асинхронный обратный вызов одобрения отправляет запрос на платформу обмена сообщениями
- Умное одобрение — опционально, вспомогательная LLM может автоматически одобрять низкорисковые команды, соответствующие шаблонам (например,
rm -rf node_modules/безопасно, но соответствует «рекурсивному удалению»)
-
Состояние сессии — одобрения отслеживаются для каждой сессии. После одобрения «рекурсивного удаления» для сессии последующие команды
rm -rfне запрашивают повторного одобрения. -
Постоянный белый список — опция «разрешить навсегда» записывает шаблон в
command_allowlistвconfig.yaml, сохраняясь между сессиями.
Терминальные/среды выполнения
Терминальная система поддерживает несколько бэкендов:
- local
- docker
- ssh
- singularity
- modal
- daytona
Она также поддерживает:
- переопределение рабочей директории для каждой задачи
- управление фоновыми процессами
- режим PTY
- обратные вызовы одобрения для опасных команд
Параллелизм
Вызовы инструментов могут выполняться последовательно или параллельно в зависимости от состава инструментов и требований взаимодействия.