sidebar_position: 8 title: "Code Execution" description: "Programmatic Python execution with RPC tool access — collapse multi-step workflows into a single turn" lang: ru
Выполнение кода (программный вызов инструмента)
Инструмент execute_code позволяет агенту писать сценарии Python, которые программно вызывают инструменты Hermes, сводя многоэтапные рабочие процессы в один цикл LLM. Сценарий выполняется в дочернем процессе на хосте агента, взаимодействуя с Hermes через RPC сокета домена Unix.
Как это работает
- Агент пишет скрипт Python, используя
from hermes_tools import .... - Hermes генерирует модуль-заглушку
hermes_tools.pyс функциями RPC. - Гермес открывает сокет домена Unix и запускает поток прослушивателя RPC.
- Скрипт выполняется в дочернем процессе — вызовы инструментов передаются через сокет обратно в Hermes.
- В LLM возвращается только вывод сценария
print(); промежуточные результаты инструмента никогда не попадают в контекстное окно
# The agent can write scripts like:
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"]])
# ... filter and process ...
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
# Find all config files and extract database settings
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
# Search, extract, and summarize in one turn
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
# Find all Python files using deprecated API and fix them
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
# Run tests, parse results, and report
result = terminal("cd /project && python -m pytest --tb=short -q 2>&1", timeout=120)
output = result.get("output", "")
# Parse test 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 |
|---|---|---|
project (по умолчанию) |
Рабочий каталог сеанса (тот же, что terminal()) |
Активный VIRTUAL_ENV / CONDA_PREFIX питон, возврат к собственному питону Гермеса |
strict |
Временный промежуточный каталог, изолированный от проекта пользователя | sys.executable (собственный питон Гермеса) |
Когда оставить project: вы хотите, чтобы import pandas, from my_project import foo или относительные пути, такие как open(".env"), работали так же, как в terminal(). Это почти всегда то, что вы хотите.
Когда переключаться на strict: вам нужна максимальная воспроизводимость — вам нужен один и тот же интерпретатор в каждом сеансе, независимо от того, какой venv активировал пользователь, и вы хотите, чтобы сценарии были помещены в карантин из дерева проекта (нет риска случайного чтения файлов проекта по относительному пути).
# ~/.hermes/config.yaml
code_execution:
mode: project # or "strict"
Резервное поведение в режиме project: если VIRTUAL_ENV / CONDA_PREFIX не установлено, сломано или указывает на Python старше 3.8, преобразователь полностью возвращается к sys.executable — он никогда не оставляет агента без работающего интерпретатора.
Критически важные для безопасности инварианты идентичны в обоих режимах:
- очистка среды (удалены ключи API, токены, учетные данные)
- белый список инструментов (скрипты не могут рекурсивно вызывать инструменты
execute_code,delegate_taskили MCP) - ограничения ресурсов (тайм-аут, ограничение на стандартный вывод, ограничение на вызов инструментов)
Режим переключения меняет то, где запускаются сценарии и какой интерпретатор их запускает, а не то, какие учетные данные они могут видеть или какие инструменты они могут вызывать.
Ограничения ресурсов
| Ресурс | Лимит | Заметки |
|---|---|---|
| Тайм-аут | 5 минут (300 с) | Скрипт завершается с помощью SIGTERM, затем SIGKILL через 5 секунд |
| Стандартный | 50 КБ | Вывод усечен с уведомлением [output truncated at 50KB] |
| Стдерр | 10 КБ | Включено в вывод при ненулевом выходе для отладки |
| Вызовы инструментов | 50 за исполнение | Ошибка возвращается при достижении лимита |
Все ограничения настраиваются через config.yaml:
# In ~/.hermes/config.yaml
code_execution:
mode: project # project (default) | strict
timeout: 300 # Max seconds per script (default: 300)
max_tool_calls: 50 # Max tool calls per execution (default: 50)
Как вызовы инструментов работают внутри скриптов
Когда ваш скрипт вызывает функцию типа web_search("query"):
- Вызов сериализуется в JSON и отправляется через сокет домена Unix родительскому процессу.
- Родительский объект отправляет через стандартный обработчик
handle_function_call. - Результат отправляется обратно через сокет.
- Функция возвращает разобранный результат
Это означает, что вызовы инструментов внутри скриптов ведут себя идентично обычным вызовам инструментов — те же ограничения скорости, та же обработка ошибок, те же возможности. Единственное ограничение состоит в том, что terminal() используется только на переднем плане (без параметров background или pty).
Обработка ошибок
При сбое сценария агент получает структурированную информацию об ошибке:
- Ненулевой код выхода: stderr включается в выходные данные, поэтому агент видит полную обратную трассировку.
- Тайм-аут: сценарий завершается, и агент видит
"Script timed out after 300s and was killed.". - Прерывание: если пользователь отправляет новое сообщение во время выполнения, сценарий завершается, и агент видит
[execution interrupted — user sent a new message]. - Ограничение вызовов инструментов: при достижении лимита в 50 вызовов последующие вызовы инструментов возвращают сообщение об ошибке.
Ответ всегда включает status (успех/ошибка/время ожидания/прерывание), output, tool_calls_made и duration_seconds.
Безопасность
:::Модель безопасности опасности Дочерний процесс выполняется в минимальной среде. Ключи API, токены и учетные данные по умолчанию удаляются. Скрипт обращается к инструментам исключительно через канал RPC — он не может читать секреты из переменных среды, если это явно не разрешено.