execute_code 工具允许智能体编写 Python 脚本来程序化地调用 Hermes 工具,从而将多步骤的工作流压缩到单个 LLM 轮次中。该脚本在智能体宿主机的子进程中运行,通过 Unix 域套接字 RPC 与 Hermes 进行通信。
- 智能体编写一个使用
from hermes_tools import ...的 Python 脚本。 - Hermes 生成一个带有 RPC 函数的
hermes_tools.py桩(stub)模块。 - 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(仅限前台运行)。
智能体何时使用此功能
Section titled “智能体何时使用此功能”当出现以下情况时,智能体会使用 execute_code:
- 3 次及以上 的工具调用,且它们之间包含处理逻辑。
- 大批量数据的过滤或条件分支。
- 对结果进行循环处理。
核心优势:中间工具的结果永远不会进入上下文窗口 —— 只有最终的 print() 输出会返回,从而大幅减少 token 使用量。
数据处理流水线
Section titled “数据处理流水线”from hermes_tools import search_files, read_fileimport 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))多步骤网络调研
Section titled “多步骤网络调研”from hermes_tools import web_search, web_extractimport 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))批量文件重构
Section titled “批量文件重构”from hermes_tools import search_files, read_file, patch
# 查找所有使用弃用 API 的 Python 文件并修复它们matches = search_files("old_api_call", path="src/", file_glob="*.py")fixed = 0for 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")构建与测试流水线
Section titled “构建与测试流水线”from hermes_tools import terminal, read_fileimport 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 有两种执行模式,由 ~/.hermes/config.yaml 中的 code_execution.mode 控制:
| 模式 | 工作目录 | Python 解释器 |
|---|---|---|
| project (默认) | 会话的工作目录(与 terminal() 相同) | 当前激活的 VIRTUAL_ENV / CONDA_PREFIX 下的 python,若无则回退到 Hermes 自带的 python |
| strict | 隔离于用户项目之外的临时暂存目录 | sys.executable(Hermes 自带的 python) |
何时保留为 project 模式: 当你希望 import pandas、from my_project import foo 或像 open(".env") 这样的相对路径能像在 terminal() 中一样正常工作时。这几乎是绝大多数情况下的需求。
何时切换到 strict 模式: 当你需要最大程度的可复现性时 —— 你希望每一轮会话都使用相同的解释器,无论用户激活了哪个虚拟环境(venv),并且你希望脚本与项目树隔离(不存在通过相对路径意外读取项目文件的风险)。
code_execution: mode: project # 或 "strict"project 模式下的回退行为: 如果 VIRTUAL_ENV / CONDA_PREFIX 未设置、已损坏或指向的 Python 版本低于 3.8,解析器将平稳回退到 sys.executable —— 确保智能体永远不会处于没有可用解释器的状态。
在两种模式下,以下安全关键性约束是完全一致的:
- 环境净化(API 密钥、Token、凭证均被剥离)
- 工具白名单(脚本无法递归调用
execute_code,也无法调用delegate_task或 MCP 工具) - 资源限制(超时限制、标准输出上限、工具调用次数上限)
切换模式只会改变脚本运行的位置以及运行它们的解释器,而不会改变它们能看到的凭证或能调用的工具。
| 资源 | 限制 | 备注 |
|---|---|---|
| 超时 (Timeout) | 5 分钟 (300s) | 脚本将被 SIGTERM 终止,5 秒宽限期后执行 SIGKILL |
| 标准输出 (Stdout) | 50 KB | 输出将被截断并附带 [output truncated at 50KB] 提示 |
| 标准错误 (Stderr) | 10 KB | 在非零退出时包含在输出中,用于调试 |
| 工具调用 (Tool calls) | 每次执行 50 次 | 达到上限时返回错误 |
所有限制均可通过 config.yaml 进行配置:
# 在 ~/.hermes/config.yaml 中code_execution: mode: project # project (默认) | strict timeout: 300 # 每脚本最大秒数 (默认: 300) max_tool_calls: 50 # 每次执行最大工具调用数 (默认: 50)脚本内部的工具调用机制
Section titled “脚本内部的工具调用机制”当你的脚本调用类似 web_search("query") 的函数时:
- 该调用被序列化为 JSON,并通过 Unix 域套接字发送给父进程。
- 父进程通过标准的
handle_function_call处理器进行分发。 - 执行结果通过套接字传回。
- 函数返回解析后的结果。
这意味着脚本内部的工具调用与普通工具调用的行为完全一致——相同的速率限制、相同的错误处理、相同的功能。唯一的限制是 terminal() 仅支持前台运行(不支持 background 或 pty 参数)。
当脚本运行失败时,智能体会收到结构化的错误信息:
- 非零退出代码:标准错误(stderr)会包含在输出中,以便智能体查看完整的堆栈追踪(traceback)。
- 超时:脚本被终止,智能体将看到
"Script timed out after 300s and was killed."。 - 中断:如果用户在执行期间发送了新消息,脚本将终止,智能体将看到
[execution interrupted — user sent a new message]。 - 工具调用限制:当达到 50 次调用的上限时,随后的工具调用将返回错误消息。
响应中始终包含 status(成功/错误/超时/中断)、output、tool_calls_made(已进行的工具调用次数)以及 duration_seconds(持续秒数)。
技能环境变量穿透 (Skill Environment Variable Passthrough)
Section titled “技能环境变量穿透 (Skill Environment Variable Passthrough)”当一个技能在其前置元数据(frontmatter)中声明了 required_environment_variables 时,这些变量在技能加载后会 自动传递 给 execute_code 和 terminal 子进程。这使得技能可以使用其声明的 API 密钥,而不会削弱针对任意代码的安全防护。
对于非技能的使用场景,你可以在 config.yaml 中显式设置变量白名单:
terminal: env_passthrough: - MY_CUSTOM_KEY - ANOTHER_TOKEN有关完整详情,请参阅 安全指南。
Hermes 始终将脚本和自动生成的 hermes_tools.py RPC 桩(stub)写入一个临时暂存目录,并在执行后进行清理。在 strict 模式下,脚本也会在该目录中运行;在 project 模式下,脚本在会话的工作目录中运行(暂存目录仍保留在 PYTHONPATH 中,以便能够正常解析导入)。子进程在其自己的进程组中运行,因此可以在超时或中断时被彻底杀死。
execute_code 对比 terminal
Section titled “execute_code 对比 terminal”| 使用场景 | execute_code | terminal |
|---|---|---|
| 包含工具调用的多步骤工作流 | ✅ | ❌ |
| 简单的 shell 命令 | ❌ | ✅ |
| 过滤/处理大量的工具输出 | ✅ | ❌ |
| 运行构建或测试套件 | ❌ | ✅ |
| 对搜索结果进行循环处理 | ✅ | ❌ |
| 交互式/后台进程 | ❌ | ✅ |
| 环境中需要 API 密钥 | ⚠️ 仅限穿透设置 | ✅ (多数可穿透) |
经验法则: 当你需要通过逻辑控制程序化地调用 Hermes 工具时,请使用 execute_code。当运行 shell 命令、构建任务和进程时,请使用 terminal。
代码执行功能需要 Unix 域套接字支持,因此 仅在 Linux 和 macOS 上可用。在 Windows 上该功能会自动禁用 —— 智能体将回退到常规的顺序工具调用模式。