代码执行
execute_code 工具允许智能体编写可编程调用 Hermes 工具的 Python 脚本,把多步工作流压缩到单次 LLM 轮次中。脚本会在智能体宿主机上的子进程中运行,并通过 Unix domain socket RPC 与 Hermes 通信。
工作原理
- 智能体编写一个使用
from hermes_tools import ...的 Python 脚本 - Hermes 自动生成一个带 RPC 函数的
hermes_tools.pystub 模块 - Hermes 打开 Unix domain socket,并启动 RPC 监听线程
- 脚本在子进程中运行,工具调用通过 socket 回传到 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() 输出会返回,大幅降低 token 消耗。
实用示例
数据处理流水线
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 由 ~/.hermes/config.yaml 中的 code_execution.mode 控制,支持两种执行模式:
| 模式 | 工作目录 | Python 解释器 |
|---|---|---|
project(默认) | 会话工作目录(与 terminal() 相同) | 当前 VIRTUAL_ENV / CONDA_PREFIX 的 Python,找不到时回退到 Hermes 自己的 Python |
strict | 与用户项目隔离的临时 staging 目录 | sys.executable(Hermes 自己的 Python) |
何时保留 project: 当你希望 import pandas、from my_project import foo、open(".env") 这类行为与 terminal() 完全一致时。绝大多数情况下这就是你想要的模式。
何时切换到 strict: 当你需要最高可复现性,希望每次都使用同一个解释器,而不受用户当前激活的虚拟环境影响;同时也希望脚本与项目树隔离,避免通过相对路径误读项目文件。
# ~/.hermes/config.yaml
code_execution:
mode: project # 或 "strict"
project 模式下的回退行为:如果 VIRTUAL_ENV / CONDA_PREFIX 未设置、损坏,或指向的 Python 版本低于 3.8,解析器会平稳回退到 sys.executable,不会让智能体失去可用解释器。
两种模式下有一些关键安全不变量保持一致:
- 环境变量清洗(API keys、tokens、凭据默认移除)
- 工具白名单(脚本不能递归调用
execute_code、delegate_task或 MCP 工具) - 资源限制(超时、stdout 上限、工具调用次数上限)
切换模式只会改变脚本在哪里运行,以及由哪个解释器运行,不会改变它能看到哪些凭据,也不会改变它能调用哪些工具。
资源限制
| 资源 | 限制 | 说明 |
|---|---|---|
| Timeout | 5 分钟(300s) | 超时后先发 SIGTERM,宽限 5 秒后 SIGKILL |
| Stdout | 50 KB | 超出后截断,并附带 [output truncated at 50KB] 提示 |
| Stderr | 10 KB | 非零退出时会附带 stderr 用于调试 |
| Tool calls | 每次执行最多 50 次 | 超限后返回错误 |
这些限制都可通过 config.yaml 配置:
# In ~/.hermes/config.yaml
code_execution:
mode: project # project(默认)| strict
timeout: 300 # 每个脚本最大秒数(默认 300)
max_tool_calls: 50 # 每次执行最多工具调用次数(默认 50)
脚本内工具调用如何工作
当你的脚本调用 web_search("query") 这类函数时:
- 调用会被序列化为 JSON,并通过 Unix domain socket 发给父进程
- 父进程走标准
handle_function_call分发逻辑 - 结果再通过 socket 传回
- 脚本函数返回解析后的结果
因此,脚本中的工具调用与普通工具调用在行为上是完全一致的,拥有相同的限流、错误处理与能力范围。唯一限制是 terminal() 只能前台运行,不支持 background 或 pty 参数。
错误处理
当脚本失败时,智能体会收到结构化错误信息:
- 非零退出码:输出中会包含 stderr,让智能体看到完整 traceback
- 超时:脚本会被杀掉,智能体会看到
"Script timed out after 300s and was killed." - 中断:如果用户在执行中发送新消息,脚本会终止,智能体会看到
[execution interrupted — user sent a new message] - 工具调用上限:达到 50 次上限后,后续工具调用会返回错误消息
返回结果始终包含 status(success / error / timeout / interrupted)、output、tool_calls_made 和 duration_seconds。
安全性
子进程会在 最小化环境变量 下运行。API keys、tokens 与各类凭据默认都会被剥离。脚本只能通过 RPC 通道访问工具,除非被显式允许,否则无法从环境变量中直接读取这些秘密信息。
名称中包含 KEY、TOKEN、SECRET、PASSWORD、CREDENTIAL、PASSWD 或 AUTH 的环境变量默认会被排除。只有 PATH、HOME、LANG、SHELL、PYTHONPATH、VIRTUAL_ENV 等安全系统变量会透传。
技能环境变量透传
当某个技能在 frontmatter 中声明了 required_environment_variables,这些变量会在技能加载后自动透传给 execute_code 与 terminal 子进程。这允许技能脚本使用它声明过的 API key,而不需要放宽对任意代码的默认安全边界。
对于非技能场景,你也可以在 config.yaml 中显式配置白名单:
terminal:
env_passthrough:
- MY_CUSTOM_KEY
- ANOTHER_TOKEN
详见 Security guide。
Hermes 总会把脚本文件和自动生成的 hermes_tools.py RPC stub 写入一个临时 staging 目录,并在执行后清理。在 strict 模式中,脚本本身也在该目录中运行;在 project 模式中,脚本在当前会话工作目录运行,但 staging 目录仍会加入 PYTHONPATH,以保证导入可用。子进程运行在独立进程组中,便于在超时或中断时整体清理。
execute_code vs terminal
| 使用场景 | execute_code | terminal |
|---|---|---|
| 带工具调用与中间逻辑的多步工作流 | ✅ | ❌ |
| 简单 shell 命令 | ❌ | ✅ |
| 过滤 / 处理大工具输出 | ✅ | ❌ |
| 跑构建或测试套件 | ❌ | ✅ |
| 遍历搜索结果做循环处理 | ✅ | ❌ |
| 交互式 / 后台进程 | ❌ | ✅ |
| 需要环境中的 API key | ⚠️ 仅能通过 passthrough | ✅(多数会透传) |
经验法则: 当你需要“程序化调用 Hermes 工具,并在调用之间加入逻辑处理”时,使用 execute_code;当你只是想执行 shell 命令、构建流程或系统进程时,使用 terminal。
平台支持
代码执行依赖 Unix domain socket,因此仅在 Linux 与 macOS 上可用。在 Windows 上会自动禁用,智能体会回退到常规的顺序式工具调用。