OpenHuman 工具执行超时机制解析:tool_timeout模块的运行时可变超时策略
【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman
导读
OpenHuman 是一个面向 Mac、Windows、Linux 的开源个人 AI(本地优先的记忆、Agent 编排与深度研究)。在 Agent 长时间运行的过程中,工具调用(如网络请求、MCP 调用、shell 脚本执行)可能因外部服务挂起而拖垮整个会话,因此需要一个进程级、可运行时修改的墙钟(wall-clock)超时策略。本文围绕 src/openhuman/tools/timeout/README.md 展开,深入解析tool_timeout模块的实现、配置项、解析顺序与使用场景。读完本文,你将掌握:如何通过[agent].agent_timeout_secs配置与OPENHUMAN_TOOL_TIMEOUT_SECS环境变量控制工具执行超时,理解"环境变量始终优先"的覆盖语义,以及脚本工具(shell/node_exec/npm_exec)为何默认无超时运行。
模块定位与核心设计
tool_timeout是 OpenHuman 中专门负责工具执行超时的模块,其核心目标可概括为:
- 解析并持有单一的、有界的超时值(秒与
Duration两种形态); - 该值运行时可变:UI 通过
config.update_agent_settingsRPC 即可修改,无需重启 core,且修改在下一次工具调用时生效; - 提供一个进程全局的
AtomicU64作为运行时存储,首次读取时惰性初始化(从环境变量/默认值解析); - 将解析与解析逻辑保持为纯函数、可测试,与全局状态变更隔离。
从源码看,模块文件 src/openhuman/tools/timeout/mod.rs 是整个模块的全部实现(常量、环境变量解析、纯解析函数、原子运行时值、setter、公共访问器以及内联单元测试)。
超时值的解析顺序
模块定义了明确的优先级(最高优先级在前):
OPENHUMAN_TOOL_TIMEOUT_SECS环境变量——操作者(operator)覆盖。当设置为合法值(1..=3600)时始终生效;只要该变量存在,配置推送(config push)就会被忽略。- 持久化配置值
[agent].agent_timeout_secs——由set_tool_timeout_secs在启动时(来自core::jsonrpc::register_domain_subscribers,即始终开启的 core 启动路径)以及每次config.update_agent_settingsRPC 时推入。 - 内置默认值
DEFAULT_TIMEOUT_SECS(120 秒)。
实现层面,resolve_effective(config_secs, env_raw)(mod.rs)正是按此顺序解析:先看环境变量是否有合法值,有则返回环境变量值;否则将配置值经过parse_tool_timeout_secs有界化后返回。而current_secs()(mod.rs)在原子值为 0(即"尚未播种"的哨兵值)时,用resolve_effective(DEFAULT_TIMEOUT_SECS, env)播种;并发首次读取会收敛到同一个种子值。
常量定义与取值范围
模块定义了以下公开常量(mod.rs):
| 常量 | 值 | 含义 |
|---|---|---|
DEFAULT_TIMEOUT_SECS | 120 | 默认工具执行超时(秒) |
MIN_TIMEOUT_SECS | 1 | 最小可接受超时。0意味着"禁用超时",因此被拒绝并回退到默认值 |
MAX_TIMEOUT_SECS | 3600 | 最大可接受超时(1 小时),防止拼写错误导致挂起的工具无限期卡住会话 |
SANDBOX_UNBOUNDED_CAP_SECS | 86_400 | 沙箱后端的"有效无限"上限(24 小时)。脚本工具在原生路径上真正无超时运行,但沙箱路径要求有限 deadline,当未显式请求timeout_secs时用此慷慨上限替代 |
ENV_VAR | "OPENHUMAN_TOOL_TIMEOUT_SECS" | 操作者覆盖环境变量名 |
所有候选值都会被限制到1..=3600秒;缺失、非数值、零、负数或越界输入一律回退到 120 秒默认值。
公共 API 面
模块对外暴露了完整的函数接口:
parse_tool_timeout_secs(raw: Option<&str>) -> u64:纯解析函数,将有界值限制到1..=3600,否则返回默认值。set_tool_timeout_secs(config_secs: u64) -> u64:将配置源值推入运行时原子,尊重环境变量覆盖,返回实际存储的有效值。启动时和每次配置更新时都会调用。env_override_active() -> bool:当OPENHUMAN_TOOL_TIMEOUT_SECS设置为合法覆盖值时返回true(此时 UI 变更被忽略),该状态会暴露给设置面板。tool_execution_timeout_secs() -> u64:读取当前有效超时值(秒),每次调用都会重新读取。tool_execution_timeout_duration() -> Duration:同一有效值以Duration形态返回。explicit_call_timeout_secs(requested: Option<u64>, cap: u64) -> Option<u64>:为默认无界的脚本工具解析显式的每次调用超时。None/Some(0)⇒None(无界运行);任意正数都会钳制到MIN_TIMEOUT_SECS..=cap。调用方传入自己的上限(shell用MAX_TIMEOUT_SECS,node_exec/npm_exec用1800)。explicit_call_timeout_duration(requested: Option<u64>, cap: u64) -> Option<Duration>:同一逻辑的Duration形态,None表示无界。- 另有内部常量
TOOL_TIMEOUT_GRACE_SECS = 5,用于为显式预算加上宽容余量(见下文)。
脚本工具默认无界运行(issue #4023)
这是本模块一个关键设计决策:全局超时只约束非脚本工具——挂起的网络/MCP 调用必须保持有界;而脚本工具(shell、node_exec、npm_exec)默认没有 deadline:一次构建、求解器或测试运行合法地需要数分钟,不应被默认上限强制杀死。
这些脚本工具暴露了每次调用的timeout_secs参数,并通过Tool::timeout_policy返回ToolTimeout::Unbounded(未提供时)或ToolTimeout::Secs(n)(提供时)。OpenHuman 工具适配器将Unbounded映射为"完全不包tokio::time::timeout",将Secs(n)映射为钳制后的 deadline加上 5 秒宽容余量,以便工具自身的内部超时(真正负责杀子进程的超时)先触发。沙箱后端要求有限 deadline,因此在无界场景下用SANDBOX_UNBOUNDED_CAP_SECS(24 小时)替代——足够长不杀死合法长任务,又足够有限以最终回收卡住的沙箱进程。
resolve_tool_deadline(mod.rs)是这一策略的核心实现,返回(deadline, timeout_secs)二元组:Inherit使用全局配置驱动的超时(有限 deadline);Secs(req)将请求钳制到1..=3600,实际 deadline 为s + 5秒(timeout_secs报告的是未加余量的预算);Unbounded返回(None, 0)——无 deadline,工具运行到完成。
配置项与运行时语义
配置 TOML
[agent].agent_timeout_secs:整数秒,合法范围1..=3600,默认120。可在Settings → Agent OS access → Action timeout中实时编辑,或通过config.update_agent_settingsRPC 修改。OPENHUMAN_TOOL_TIMEOUT_SECS(环境变量):操作者覆盖,范围相同。合法时覆盖配置值;非法值被忽略,配置值仍生效。
在 schema 定义中(src/openhuman/config/schema/agent.rs),该字段的文档注释明确说明其作用于"单个工具/动作执行的墙钟超时(以及每 Agent 的委派聊天调用)",并提及 issue #3100——运行大型本地模型的用户无需编辑配置文件即可延长超时。
配置应用与读取
src/openhuman/config/ops/agent.rs 中的apply_agent_settings展示了完整的运行时更新路径:
- 校验
agent_timeout_secs是否在MIN_TIMEOUT_SECS..=MAX_TIMEOUT_SECS范围内,越界则拒绝并返回错误信息; - 持久化配置(
config.save()); - 调用
set_tool_timeout_secs将新值推入运行时原子——无需重启 core,下一次工具调用即生效(除非环境变量覆盖生效,此时推送为 no-op); - 返回更新后的配置快照。
get_agent_settings则返回agent_timeout_secs、effective_timeout_secs、env_override、min_timeout_secs、max_timeout_secs,让 UI 能在环境变量覆盖时向用户解释"此控件当前无效果"。前端设置面板(如 app/src/components/settings/panels)会显示类似 "The OPENHUMAN_TOOL_TIMEOUT_SECS environment variable is overriding this setting, so changes here have no effect until it is unset." 的提示(见 app/src/lib/i18n/en.ts)。
启动路径播种
src/core/jsonrpc.rs 的register_domain_subscribers在始终开启的 core 启动路径(其非门控的INFRA: Once块)中调用set_tool_timeout_secs(config.agent.agent_timeout_secs)(见 src/core/jsonrpc.rs),确保无 channel / 仅 web-chat 的 core也能获得配置的超时值(issue #5027)。相关测试见 src/core/jsonrpc_tests.rs。
使用方(调用链)
- src/openhuman/agent/tinyagents/tools.rs:OpenHuman 工具通过
execute_with_options执行,应用每个工具的Tool::timeout_policy:Inherit使用tool_execution_timeout_secs();Secs(n)使用钳制值加宽容余量;Unbounded无 deadline 运行。 - src/openhuman/tools/impl/system 下的
shell.rs、node_exec.rs、npm_exec.rs:脚本工具默认无界,支持显式timeout_secs。 - src/openhuman/agent/tools/delegate.rs:用
tool_execution_timeout_secs()为委派 provider 的聊天调用设界。 - src/openhuman/config/ops.rs:
apply_agent_settings持久化后调用set_tool_timeout_secs;get_agent_settings上报effective_timeout_secs/env_override。 - src/core/jsonrpc.rs:
register_domain_subscribers在启动时播种运行时值。 - src/openhuman/agent/harness/harness_gap_tests.rs:固定
parse_tool_timeout_secs的默认值/边界行为。
边界行为与易错点
以下是模块文档明确列出的注意事项,也是实际使用中容易踩坑的地方:
- 值在每次工具调用时新鲜读取:配置变更在下一次工具调用生效;一个已在进行中的
tokio::time::timeout保持其捕获的 deadline。 0被刻意拒绝:0本意可能是"禁用超时",但模块选择拒绝并回退到默认值,而不是禁用。- "存在但非法"的环境变量不算覆盖:非数值 /
0/ 越界的环境变量值视为"无覆盖",配置值仍生效;只有合法的环境变量值才能覆盖。 - 默认值(120 秒)必须与前端镜像的超时保持一致:见 app/src/utils/config.ts 中的
TOOL_TIMEOUT_SECS(同样默认 120、最大 3600,非法值回退到默认值)。
从单元测试 src/openhuman/tools/timeout/mod_tests.rs 可以完整验证以上行为:环境缺失/非数值/零/越界/负数均回退默认值(第 4-37 行);边界值1与3600被接受(第 40-43 行);env_override_takes_precedence_over_config验证环境变量优先(第 51-54 行);config_value_used_when_env_absent_or_invalid验证非法环境变量被忽略(第 57-65 行);explicit_call_timeout_enforces_and_clamps_request验证显式调用超时的钳制行为(第 83-103 行)。
依赖与设计取舍
模块依赖极简:仅log(用于配置推送时的 debug 跟踪),其余只用标准库(std::sync::atomic::AtomicU64、std::time::Duration、std::env)。resolve_tool_deadline在 tinyagents 迁移期间(issue #4249)从退役的 legacyengine::tools模块移入此处,与它使用的超时常量放在一起。
从源码结构看,这种"解析纯函数 + 全局原子"的分离设计可以推断出几个明确意图:解析逻辑可被单元测试无竞态地全覆盖;全局状态只承载最终有效值;setter 幂等,可安全地在启动与每次配置更新时重复调用;环境变量覆盖作为"操作者级"安全网,始终优先于用户级配置,防止 UI 误操作绕过运维约束。
总结
tool_timeout是 OpenHuman 工具执行链路中一个小而精的守护模块:它以1..=3600秒的有界窗口统一了工具执行超时,用"环境变量 > 配置 > 默认值"的优先级保证操作者控制权,用运行时可变原子让 UI 修改即时生效,并用"脚本工具默认无界、显式timeout_secs按需设限"的差异化策略兼顾了长任务与挂起防护。理解这一机制,是正确调优 OpenHuman Agent 行为、排查"工具为何被杀死"或"工具为何无限运行"类问题的前提。
【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考