# 17 脚本调用工具(run_script) > 状态:**已实施(2026-10-29)**。决策见 ADR-0026。 ## 1. 问题 MCP 服务的工具往往比较底层,一个分析任务可能要调用几百次(例如查询 500 台设备的状态再汇总)。模型逐次调用时: - 每次调用都要一轮模型推理,慢,token 消耗随次数急剧增长; - 几百个结果堆在上下文里,会触发压缩,或者模型改为抽样,结果不完整。 模型自己的沙箱里写脚本调用不到 MCP 工具(工具只经网关提供给引擎)。而事先为每种分析方式都在服务端做好汇总工具,又不够灵活。 ## 2. 方案 网关新增工具 `run_script(code, tools, description)`:模型写一段 Python 脚本,由**网关**在沙箱中执行。脚本通过 `agent_tools` 模块调用 MCP 工具:每次调用都经过网关(工具策略、结果处理与普通调用相同),只是不经过模型。循环、筛选、汇总、写文件都在脚本中完成,只有脚本打印的内容交回模型。 ```python from agent_tools import call, call_many, call_raw, progress, ToolError rows = call_many("fleet__get_device_status", [{"sn": sn} for sn in sns]) # 平台并发执行 progress(len(rows), len(sns), "汇总") ``` | 函数 | 说明 | |---|---| | `call(tool, args)` | 返回结构化结果(dict)或文本;工具报错时抛出 `ToolError` | | `call_many(tool, args_list, return_errors=False)` | 批量调用同一个工具,按顺序返回;每批 50 个请求,平台按并发上限执行 | | `call_raw(tool, args)` | 返回 `{text, structured, is_error}`,不抛出工具错误 | | `progress(done, total, message)` | 报告进度,显示在对话中 | - 只有 Profile 勾选了第三方服务(有可用的第三方工具)时才提供 `run_script`。 - 工具说明(`RUN_SCRIPT_DESCRIPTION`)中写明了用法和规则,模型据此决定何时使用。 ## 3. 执行 - **沙箱**:bubblewrap,与文档转换相同的配置(`services/office.ts` 的 `sandboxArgs`):系统目录只读、Python 运行环境只读、会话工作区可读写(只读模式下只读)、不能联网、`/tmp` 独立。当前目录为工作区。 - **通信**:脚本经 fd 3 发请求、fd 4 收回复,每行一个 JSON;标准输出留给脚本自己打印。辅助模块在启动时写入 `data/script-runtime/agent_tools.py`,沙箱中只读。 - **限额**(系统设置 `scripts`):每个脚本最多调用 2000 次工具、同时 4 次、最长 30 分钟。超时或取消时结束整个进程树。 - **转后台**:与其他长调用相同(docs/design/15-background-calls.md),超过 `backgroundCalls.autoAfterSec` 转为后台,完成后把结果作为新消息发给发起方;可以在后台调用栏取消。服务重启后中断,与"保持连接等待"的后台调用一样按失败通知。 - **返回模型**:一行概况(是否正常结束、调用次数与失败次数、用时、调用明细路径)+ 脚本打印的内容(超过 2 万字截断,完整输出存为文件);出错时附上错误信息的最后一部分。 ## 4. 权限 | 工具 | 在脚本中 | |---|---| | 只读工具 | 直接调用 | | 写工具(跟随权限模式、直接调用) | 必须在 `tools` 参数中列出;"需审批"模式下,执行前**统一审批一次**(卡片列出这些工具和脚本内容,只有"允许一次"和"拒绝");已"本会话允许"的不再询问;自动模式不询问 | | 破坏性工具(总是审批) | 不能在脚本中调用,请模型直接调用、逐次确认 | | 只能以长任务方式调用的工具 | 不能在脚本中调用 | | 关闭的工具、只读模式下的写工具 | 不提供 | 没有列出的写工具在脚本中调用时,脚本收到 `ToolError`。 ## 5. 记录与进度 - **调用明细**:会话工作区 `.agent/scripts/<脚本 id>/`:`script.py`(脚本)、`calls.jsonl`(每次调用一行:时间、工具、参数、耗时、是否出错、结果摘要)、`output.txt`(完整输出)。 - **审计**:每个脚本一条(`gateway_call`):说明、调用次数与失败次数、各工具的次数、用时、退出码、明细目录、脚本内容。不为每次调用写审计,避免淹没审计页面。 - **进度**:事件 `script.progress`(只推送、不保存,每秒最多一次):已调用次数、失败次数、脚本报告的进度、用时、状态。`` 在时间线底部显示进度(有总数时带进度条),结束后几秒移除。工作组中暂不显示。 ## 6. 不做 - 其他语言(只支持 Python)。 - 脚本中调用网关的内置工具(文档转换、网页读取)和资源读取:需要时由模型直接调用。 - 服务重启后接续脚本。