Выполнение кода (программный вызов инструмента)
Инструмент execute_code Позволяет агенту писать скрипты Python, которые программно вызывают инструменты Hermes, превращая многошаговые рабочие процессы в один шаг LLM. Скрипт выполнения в дочернем процессе на хосте-агенте, общий с Hermes через RPC по сокету домена Unix.
Как это работает
- Агент пишет Python-скрипт, используя
from hermes_tools import... - Hermes текущий модуль-заглушку
hermes_tools.pyс RPC-функциями - Hermes открывает сокет Unix-домена и запускает поток RPC-слушателя.
- Скрипт выполнения в дочернем процессе — вызовы инструментов передаются через сокет обратно в Hermes.
- Только вывод
print()скрипта возвращает LLM; промежуточные результаты инструментов никогда не остаются в контекстном окне
# Агент может писать скрипты вроде:
from hermes_tools import web_search, web_extract
results = web_search("Python 3.13 features", limit=5)
for r in results["data"]["web"]:
content = web_extract([r["url"]])
#... фильтрация и обработка...
print(summary)
Доступные инструменты внутри скриптов: web_search, web_extract, read_file, write_file, search_files, patch, terminal (только передний план).
Когда агент это использует
Агент использует execute_code, когда есть:
- 3+ инструмента вызова с логикой обработки между ними
- Массовая фильтрация данных или условное ветвление
- Циклы по результатам
Ключевое преимущество: инструменты промежуточных результатов никогда не рассматривают контекстное окно — возвращается только выводной вывод print(), что значительно сокращает использование токенов.
Практические образцы
Конвейер обработки данных
from hermes_tools import search_files, read_file
import json
# Найти все конфигурационные файлы и извлечь настройки базы данных
matches = search_files("database", path=".", file_glob="*.yaml", limit=20)
configs = []
for match in matches.get("matches", []):
content = read_file(match["path"])
configs.append({"file": match["path"], "preview": content["content"][:200]})
print(json.dumps(configs, indent=2))
Многошаговое веб-исследование
from hermes_tools import web_search, web_extract
import json
# Поиск, извлечение и обобщение за один шаг
results = web_search("Rust async runtime comparison 2025", limit=5)
summaries = []
for r in results["data"]["web"]:
page = web_extract([r["url"]])
for p in page.get("results", []):
if p.get("content"):
summaries.append({
"title": r["title"],
"url": r["url"],
"excerpt": p["content"][:500]
})
print(json.dumps(summaries, indent=2))
Массовый рефакторинг файлов
from hermes_tools import search_files, read_file, patch
# Найти все Python-файлы, использующие устаревший API, и исправить их
matches = search_files("old_api_call", path="src/", file_glob="*.py")
fixed = 0
for match in matches.get("matches", []):
result = patch(
path=match["path"],
old_string="old_api_call(",
new_string="new_api_call(",
replace_all=True
)
if "error" not in str(result):
fixed += 1
print(f"Fixed {fixed} files out of {len(matches.get('matches', []))} matches")
Конвейер сборки и тестирования
from hermes_tools import terminal, read_file
import json
# Запустить тесты, разобрать результаты и сообщить
result = terminal("cd /project && python -m pytest --tb=short -q 2>&1", timeout=120)
output = result.get("output", "")
# Разобрать вывод тестов
passed = output.count(" passed")
failed = output.count(" failed")
errors = output.count(" error")
report = {
"passed": passed,
"failed": failed,
"errors": errors,
"exit_code": result.get("exit_code", -1),
"summary": output[-500:] if len(output) > 500 else output
}
print(json.dumps(report, indent=2))
Режим выполнения
execute_code имеет два режима выполнения, управляемых code_execution.mode в ~/.hermes/config.yaml:
| Режим | Рабочий каталог | Интерпретатор Python |
|---|---|---|
проект (по умолчанию) |
Рабочий каталог сеанса (тот же, что и terminal()) |
Активный VIRTUAL_ENV / CONDA_PREFIX Python, с откатом к собственному Python Hermes |
строгий |
Временный промежуточный каталог, изолированный от пользователя проекта | sys.executable (собственный Python Hermes) |
Когда остаемся project: вы хотите, чтобы import pandas, from my_project import foo или соответствующие пути, такие как open(".env"), работают так же, как в terminal(). Это почти всегда то, что нужно.
При переключении на «строгое»: вам нужна максимальная воспроизводимость — вам нужен один и тот же интерпретатор в каждом сеансе, независимо от того, какую виртуальную среду активировал пользователь, и вы хотите, чтобы скрипты были изолированы от дерева проекта (нет риска случайного чтения файлов проекта через относительный путь).
# ~/.hermes/config.yaml
code_execution:
mode: project # или "strict"
Поведение отката в режиме project: если VIRTUAL_ENV / CONDA_PREFIX не установлен, сломан или указан на Python старше 3.8, резолвер чисто откатывается к sys.executable — он никогда не оставляет агента без работающего интерпретатора.
Критические для безопасности инварианты происходят в обоих режимах:
- очистка окружения (API-ключи, токены, учетные данные удаляются)
- инструменты белого списка (скрипты не могут включать
execute_codeрекурсивно,delegate_taskили MCP-инструменты) - ограничения ресурсов (инструменты тайм-аут, лимит stdout, лимит вызовов)
Смена режима меняется, где выполняются скрипты, и какой интерпретатор их запускает, при этом нет каких-либо учетных данных, которые они могут видеть, или каких-либо инструментов управления.
Ограничения ресурсов
| Ресурс | Лимит | Примечания |
|---|---|---|
| Тайм-аут | 5 минут (300 с) | Скрипт завершается SIGTERM, затем SIGKILL через 5 с льготным периодом |
| Стандартный | 50 КБ | Вывод усекается с).м [вывод обрезан до 50 КБ] |
| Стдерр | 10 КБ | Включается в вывод при ненулевом коде вывода для отладки |
| Вызовы инструменты | 50 на выполнение | Ошибка возврата при получении лимита |
Все лимиты настраиваются через config.yaml:
# В ~/.hermes/config.yaml
code_execution:
mode: project # project (по умолчанию) | strict
timeout: 300 # Макс. секунд на скрипт (по умолчанию: 300)
max_tool_calls: 50 # Макс. вызовов инструментов на выполнение (по умолчанию: 50)
Как работают вызовы инструментов внутри скриптов
Когда ваш скрипт вызывает функцию, например web_search("query"):
- Вызов сериализуется в формате JSON и продается через сокет Unix-домена родительскому процессу.
- Родительский процесс диспетчеризуется через стандартный обработчик handle_function_call.
- Результат отправляется обратно через сокет.
- функция возвращает разобранный результат
Это означает, что инструменты вызовов внутри скриптов ведут себя идентично обычным инструментам вызова — те же ограничения скорости, те же ошибки обработки, те же самые возможности. Единственное ограничение: terminal() работает только на переднем плане (без параметров background или pty).
Обработка ошибок
Когда скрипт завершается с ошибкой, агент получает структурированную информацию о деньгах:
- Ненулевой вывод: в вывод включается stderr, чтобы агент видел полную трассировку
- Тайм-аут: Скрипт завершается, и агент видит
"Тайм-аут скрипта истек через 300 секунд, и он был уничтожен." - Прерывание: Если пользователь отправляет новое сообщение во время выполнения, скрипт завершается, и агент видит
[выполнение прервано — пользователь отправил новое сообщение] - Ограничение количества вызовов инструментов: При получении лимита в 50 вызовов вызовы инструментов возвращают сообщение об ошибке.
Ответ всегда включает status (успех/ошибка/тайм-аут/прервано), output,tool_calls_made и duration_секунды.
Безопасность:::опасность Модель безопасности
Дочерний результат процесса с минимальным окружением. Ключи API, токены и учетные данные удаляются по умолчанию. Скрипт получает доступ к инструментам исключительно через RPC-канал — он не может читать секреты из окружения протокола, если это явно не разрешено.