-
Notifications
You must be signed in to change notification settings - Fork 2
Tool System
工具是 LLM 触达系统能力的唯一出口。v2 回答"工具是什么"(schema / 元数据 / 风险), v3 回答"工具在真实 Agent 循环里如何不死、不炸、可观测"。 本文是 docs/tool-system-v2.md 与 docs/tool-system-v3.md 的 Wiki 化整理。
flowchart LR
M["模型 tool_call"] --> R{"ToolRegistry 查找"}
R -->|未命中| S["ToolSuggester:Levenshtein 相近建议<br/>(不再倾倒全量 id 清单)"]
R -->|命中| P["v3 EnhancedToolExecutor 管线"]
P --> P1["环境门 ToolEnvironmentGate(fail-open)"]
P1 --> P2["风险门 RiskAwareToolGate / selfGated 跳过重复弹窗"]
P2 --> P3["schema 校验 ToolSchema(声明即校验)"]
P3 --> P4["限流 ToolRateLimiter(令牌桶,超限即拒)"]
P4 --> P5["熔断 ToolCircuitBreaker"]
P5 --> P6["超时 + 重试 ToolRunPolicy(全抖动指数退避)"]
P6 --> T["真实工具实现"]
T --> X["ToolResult(成 / 结构化错误码)"]
P -.每次尝试.-> TR["ToolTraceRecorder:一个 ToolTraceSpan"]
P -.聚合.-> UT["ToolUsageTracker:次数/成败/耗时"]
设计三原则(继承 v2):
- 全部可选注入 —— 不接 v3 组件时行为与 v2 逐字节一致,既有测试零迁移;
- 错误即内容 —— 所有拒绝(限流/熔断/环境/超时)返回模型可读的修复指引,而不是抛异常中断循环;
- fail-open —— 环境遥测缺失或过期时放行,绝不让推断性门控硬禁用工具。
| # | 缺口 | 症状 |
|---|---|---|
| 1 | schema 只是文档 |
parametersSchema 是手写 JSON 字符串,运行时零校验,模型靠猜参数 |
| 2 | 失败没有形状 | 工具返回裸字符串,失败靠 "Error: " 前缀识别,引擎分不清"参数错了改了再试"和"权限拒绝永远别试" |
| 3 | 未知工具错误倾倒全量清单 | 打错工具名 → 错误信息塞进 40+ id 墙 → 模型继续猜 |
| 4 | 风险不分级 |
shell_execute 有命令级确认,app_uninstall / delete_file 什么都不问 |
| 5 | 元数据不存在 | prompt 里是字母序平铺长列表(40+ 行噪音),也没有"哪个工具在被用/总在失败"的观测面 |
| 文件 | 职责 |
|---|---|
ToolMetadata.kt |
类别(16 类)/ 风险(LOW·MEDIUM·HIGH,3 级)/ 标签 —— 按 id 推断,v1 工具零迁移 |
ToolResult.kt |
ToolErrorCode(9 个)+ 结构化结果 + StructuredAgentTool 契约 |
ToolSchema.kt |
schema DSL:声明即校验(单一真源) + v1 JSON 宽松导入 |
ToolArguments.kt |
强类型参数读取器:缺参/类型错 → 带字段名的结构化错误 |
ToolExecutionGate.kt |
执行门接口 + ToolPermissionManager 会话状态机 |
ToolUsageTracker.kt |
每工具次数 / 成败 / 耗时 / 最近错误(无锁统计) |
ToolSuggester.kt |
Levenshtein 相近建议(修复"倾倒全量 id") |
ToolRegistry.kt |
注册表加固 + DefaultToolExecutor v2 管线 |
SafeAgentTool.kt |
元数据透传包装器(不吞流式,有专门回归测试) |
-
字符串协议保持一等公民:
ToolResult.render()产出引擎已在解析的"Error: …"字符串,下游零改动;结构化信息是增量而非替代; -
接口新增全部带默认实现:
AgentTool.metadata默认按 id 推断;ToolRegistry.register(tool, policy)默认 REPLACE(v1 语义),测试里的FakeToolRegistry/StubAgentTool一行未改仍能编译; -
DSL 声明即校验:
toolSchema { string("path", required = true) }同时是渲染源与校验规则,结构上不可能漂移;v1 手写 schema 经fromRendered宽松导入,解析失败静默跳过(绝不误伤); - 校验对未知字段宽容:模型爱塞垃圾字段,拒绝只会造成重试循环,只强制声明过的约束;
-
selfGated 跳过双重弹窗:
shell_execute自带命令级确认(CommandPermissionGate),工具级再弹一次是骚扰,故在ToolPermissionManager.selfGatedToolIds预置放行; -
交互闭环留在 app 层:core 不依赖引擎问题桥(依赖方向约束),由 app 的
RiskAwareToolGate注入UserQuestionGateway回调驱动同一状态机;headless/测试注入自己的 confirm 即可复用。
INVALID_ARGUMENT·PERMISSION_DENIED·NOT_FOUND·TIMEOUT·RATE_LIMITED·CIRCUIT_OPEN·EXECUTION_FAILED·UNSUPPORTED·UNKNOWN含义:
INVALID_ARGUMENT提示"Fix the arguments and retry";PERMISSION_DENIED提示 "换方案,勿重试" —— 这正是 v1 表达不出来的差别。
v3 的动机来自对 7 个业界方案的源码级调研(MCP spec 2025-06-18 · LangGraph ToolNode · Anthropic computer-use · OpenAI CU · AutoGPT · LangGraph · AndroidWorld):
| # | 缺口 | 业界基准 | v2 现状 |
|---|---|---|---|
| 1 | 工具级超时 | MCP 规范 MUST;AndroidWorld 全工具 30s | 无 —— http_request 挂死 = Agent 循环挂死 |
| 2 | 瞬态失败重试 | LangGraph wrap_tool_call 指数退避 |
无 —— 一次网络抖动 = 一步失败 |
| 3 | 速率限制 | 成熟平台标配 | 无 —— 模型失败循环可刷 50 次/分钟 |
| 4 | 熔断器 | Hystrix 形态 | 无 —— 已损坏工具持续烧 token |
| 5 | 逐调用追踪 | LangSmith / NodeExecutionStats | 仅聚合计数(ToolUsageTracker) |
| 6 | 批量顺序执行 | Anthropic CU actions[],首错即停 |
无 —— 每步一次 LLM 往返 |
| 7 | 组合动作(自进化) | Mobile-Agent-E Shortcut | 无 |
| 8 | 环境态门控 | Mobile-Agent 键盘门控 | 无 —— 键盘未弹出时 input_text 必败 |
| 9 | MCP 注解词汇 |
readOnly/destructive/idempotent/openWorld 四 hints |
仅 LOW/MEDIUM/HIGH 粗粒度 |
另外修了存量 bug:ToolModule 中 MCP 三工具被重复注册三次(REPLACE 静默互踩)。
ToolAnnotations(readOnlyHint, destructiveHint, idempotentHint, openWorldHint, sensitiveAction)-
retrySafe派生:readOnly || idempotent—— 自动重试同一 payload 的安全充要条件 (重放已成功的破坏性操作 = 数据丢失); -
零迁移推断:
infer(id, risk)覆盖全部存量 id 族(与引擎 T76 幂等注册表同源口径),显式声明优先; - 挂在
ToolMetadata.annotations(带默认值,data class 向后兼容)。
| 预设 | 超时 | 重试 | 限流 |
|---|---|---|---|
quickRead |
30s | 1 次 | 120 rpm |
network |
90s | 2 次 | 30 rpm |
mutating |
60s | 不盲重试 | — |
legacy |
v1/v2 语义 | — | — |
-
全抖动指数退避(AWS 风格):
delay = random(0 .. base * 2^attempt),避免恢复中后端的惊群; -
RetryClassifier:权限 / 沙箱 / 参数类错误判为终态(重发同 payload 无意义);超时 / IO / 执行错误可重试; -
ToolRateLimiter:连续回填令牌桶(capacity = 每分钟配额,tokens/ms 回填),超限立即拒绝而非排队 —— 失控循环要立刻看见错误。
CLOSED ──N 连败──▶ OPEN ──冷却结束──▶ HALF_OPEN(单探测)
├── 成功 ─▶ CLOSED
└── 失败 ─▶ OPEN(冷却 2× 加宽,上限 maxCooldownMs)
纯惰性状态机(无定时器线程),check() 即求值。Deny 文案带"上次错误 + 探测倒计时 + 勿立即重试"指引。
- 每次尝试一个
ToolTraceSpan(callId, toolId, durationMs, outcome, attempt, argsDigest, errorSlug); - args 摘要而非原文(长度 / 可打印字符计数)—— 诊断导出不含用户粘贴的机密;
- 环形有界缓冲(默认 200,app 配 300)+ 监听器分发 + JSON 导出 + 直方图报告;
- 幂等完成:同一 handle 二次 complete 不产生幻影 span;
- 与
ToolUsageTracker并存:tracker 答"多常/多快",tracer 答"具体哪次、什么参数、第几轮重试"。
- 严格顺序、首错即停,未执行步骤统一返回契约原文
"Not executed: an earlier action in this turn failed."; -
{n}步间引用:整引用 / 模板内插值,头截断 4KB 防爆炸;前向 / 越界引用在执行前拒绝(调用计数为零); - 整批墙钟预算默认 120s(上限 600s);步骤上限 16;
- 模型入口
tool_batch_run(steps 支持内联数组或字符串编码两种形态)。
-
ShortcutDefinition:id(shortcut.*命名空间)/ 前置条件 / 声明参数 / 步骤模板({param}占位符 +{0}步间引用); - 解析期全量校验:未知工具、未声明占位符、死参数(声明未使用)、超步数全部字段级拒绝;
- JSON 感知替换:字符串参数按 JSON string content 转义,数值/布尔裸插入 —— 渲染出的步骤参数永远是合法 JSON;
-
风险继承:编译工具的风险 = 所含步骤最坏风险(含
app_uninstall的快捷方式必为 HIGH); - 模型入口三件套:
shortcut_define(定义 + 热注册为一等工具)/shortcut_list(清单 + 挖掘建议)/shortcut_run(显式执行兜底); -
ShortcutSuggester:从ToolTraceRecorder的成功相邻 bigram 中挖掘高频序列(≥3 次),向模型建议打包成 Shortcut。
环境遥测(如 IME 是否弹出、当前前台包名)缺失或过期 → fail-open 放行; 命中风险场景 → 返回结构化错误 + 修复指引(例如"请先打开输入框")。
Note
数量随版本演进;README 当前口径为 109 个。CI 有工具 ID 唯一性门禁(ci.yml),
撞 id 会直接红。以下分组为 Registry 注册基线,动态安装的 Skill 经 SkillToolAdapter
注册为运行时工具(如 web_scrape)。
| 类别 | 工具 |
|---|---|
| 🖥️ Shell |
shell_execute(三级权限链 + 流式输出) |
| 📁 文件 |
read_file write_file edit_file list_files glob_files search_files copy_move_file delete_file
|
| 🌐 网络 |
web_fetch web_search http_request download_file(流式进度) |
| 🧠 文件记忆 |
memorize recall forget
|
| 📱 应用 |
app_list app_launch app_install app_uninstall app_force_stop app_info
|
| ⚙️ 系统 |
get_device_info get_set_settings control_media clipboard get_time logcat
|
| 🖱️ UI 自动化 |
ui_tap ui_swipe ui_dump screenshot input_text
|
| 🧮 实用 |
calculate text_transform get_location notification_read
|
| 🧩 技能 |
skill_search skill_install skill_create skill_list skill_uninstall
|
| 🛰️ MCP |
mcp_connect mcp_list mcp_call
|
| 🧱 结构化 v2(15) |
regex_extract regex_replace text_diff json_path xml_extract csv_query base_convert unit_convert duration_convert string_distance random_generate uuid_generate file_hash datetime cron_next(全部纯 JVM / 离线 / 确定性) |
| ⚡ 执行硬化 v3(7) |
wait(≤300s 可取消等待)· json_transform(jq 风格七操作管线)· version_compare(SemVer 排序)· tool_batch_run · shortcut_define / shortcut_list / shortcut_run
|
结构化 v2 工具的代表性实现文件:RegexTools JsonPathTool TextDiffTool XmlExtractTool
UnitConvertTool JsonTransformTool WaitTool VersionCompareTool ShortcutTools …
| 类别 | 工具 |
|---|---|
| 🌐 浏览器(15) |
browser_navigate browser_click browser_input browser_scroll browser_select browser_screenshot browser_snapshot browser_toggle browser_show browser_download_list browser_file_upload browser_date_input browser_context_summary browser_network_log browser_debug_dump
|
| 🐙 GitHub(7) |
github_get_user github_list_repos github_read_file github_write_file github_create_issue github_list_issues github_search_code(配置 PAT 后才注册) |
terminal.create / run / write / observe / snapshot / wait / resize / signal / close / workspaces / backends / linux_bootstrap / linux_status / linux_packages / linux_network / ubuntu_install,以及 T82 新增的 terminal.ubuntu.ensure / terminal.ubuntu.status。
详见 终端运行时。
| 工具 | 用途 |
|---|---|
memory_search_nodes |
按文本/角色搜索语义节点(支持迁移别名解析) |
memory_recent_episodes |
近期情景回顾 |
memory_recall_macro |
宏技能召回 |
flowchart TD
A["写实现:继承 BaseTool / StructuredAgentTool"] --> B["toolSchema { … } 声明参数(同时是渲染与校验)"]
B --> C["ToolMetadata:类别 / 风险 / 标注(annotations)"]
C --> D["arguments 校验:缺参/类型错 → INVALID_ARGUMENT(带字段名)"]
D --> E["异常兜底:EXECUTION_FAILED,绝不冒泡炸掉 agent 循环"]
E --> F["注册:ToolRegistry.register(tool, policy)"]
F --> G{"ID 唯一?(CI 门禁)"}
G -->|否| H["改名 / 合并重复实现"]
G -->|是| I["补测试:happy path + 参数错误 + 权限/环境拒绝"]
清单自查:
- id 全局唯一(CI 强制);
- 需要 Android API 的实现必须在
platform:*或:app,并在di/ToolModule注入; - 破坏性操作声明
destructiveHint/ 风险 ≥ MEDIUM,App 侧会走RiskAwareToolGate; - 幂等信息声明正确(决定是否允许重试);
- 有流式输出的能力实现
StreamingTool,并确认SafeAgentTool包装不吞流式; - 失败错误信息是模型可执行的指引,而不是"出错了"。
-
McpManager+McpClient+McpHttpTransport/McpStdioTransport+McpConfigImport实现 Model Context Protocol 客户端; - 三个入口工具:
mcp_connect/mcp_list/mcp_call; -
不要在
ToolModule里重复注册(历史 bug:三工具被注册三次,REPLACE 静默互踩)。
- Agent 引擎 —— 谁在调用工具
- 任务编排器 —— 批量与谁协同
- 技能与斜杠命令 —— 工具的上一层层封装
- cs-mem 认知记忆 —— 记忆工具能被 recalled
- 测试体系 —— 工具层测试要求
-
MCP 生态总览
新 - (沙箱 MCP · 官方 Hub · 逆向 Host · 门控语义)
- 终端运行时
- 终端 API 契约
- SDK 边界
- Termux 能力矩阵
- Ubuntu rootfs 供给
- Ubuntu 生命周期
- PRoot 二进制溯源
- VT100/ANSI 模拟器
- 终端性能
- 终端迁移
- 原生层 C++/JNI