Tools Runtime

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

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

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

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

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

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

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

registry.register(
    name="terminal",               # Unique tool name (used in API schemas)
    toolset="terminal",            # Toolset this tool belongs to
    schema={...},                  # OpenAI function-calling schema (description, parameters)
    handler=handle_terminal,       # The function that executes when the tool is called
    check_fn=check_terminal,       # Optional: returns True/False for availability
    requires_env=["SOME_VAR"],     # Optional: env vars needed (for UI display)
    is_async=False,                # Whether the handler is an async coroutine
    description="Run commands",    # Human-readable description
    emoji="💻",                    # Emoji for spinner/progress display
)

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

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

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

# tools/registry.py (simplified)
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 check for top-level 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. Инструменты плагиновhermes_cli.plugins.discover_plugins() загружает пользовательские/проектные/pip-плагины, которые могут регистрировать дополнительные инструменты.

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

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

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

# Simplified from registry.py
if entry.check_fn:
    try:
        available = bool(entry.check_fn())
    except Exception:
        available = False   # Exceptions = unavailable
    if not available:
        continue            # Skip this tool entirely

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

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

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

Как 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 для обратной совместимости.

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

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

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

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

Model response with tool_call
    
run_agent.py agent loop
    
model_tools.handle_function_call(name, args, task_id, user_task)
    
[Agent-loop tools?]  handled directly by agent loop (todo, memory, session_search, delegate_task)
    
[Plugin pre-hook]  invoke_hook("pre_tool_call", ...)
    
registry.dispatch(name, args, **kwargs)
    
Look up ToolEntry by name
    
[Async handler?]  bridge via _run_async()
[Sync handler?]   call directly
    
Return result string (or JSON error)
    
[Plugin post-hook]  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 и т.д.):

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

Мост для асинхронных вызовов

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

Поток подтверждения DANGEROUS_PATTERNS

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

  1. Обнаружение шаблоновDANGEROUS_PATTERNS — это список кортежей (regex, description), охватывающий деструктивные операции:
  2. Рекурсивное удаление (rm -rf)
  3. Форматирование файловой системы (mkfs, dd)
  4. Деструктивные SQL-операции (DROP TABLE, DELETE FROM без WHERE)
  5. Перезапись системных конфигов (> /etc/)
  6. Манипуляции с сервисами (systemctl stop)
  7. Удаленное выполнение кода (curl | sh)
  8. Fork-бомбы, завершение процессов и т.д.

  9. Обнаружение — перед выполнением любой терминальной команды detect_dangerous_command(command) проверяет ее по всем шаблонам.

  10. Запрос подтверждения — при совпадении:

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

  14. Состояние сессии — подтверждения отслеживаются для каждой сессии. После подтверждения «рекурсивного удаления» в сессии последующие команды rm -rf повторно не запрашивают подтверждение.

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

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

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

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

Параллельность

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