工具调用与安全沙箱:让模型能做事但做不坏

这一篇专门看“Tool 调用”这一块:如何把编译、测试、lint、git 等能力暴露给模型用,同时又不让它在 IDE 里“为所欲为”。

光靠静态上下文和生成,模型能做的事情还是有限的:

  • 它不知道当前分支的 git 状态;
  • 看不到最新一次测试/构建的具体错误;
  • 无法主动运行 lint/format 来验证自己的改动。

如果把这些能力都当成“人类手动完成”的前置/后置步骤,体验会碎成很多段。
更自然的方式是:把这些能力抽象成一组工具(Tool),让模型在合适的时候主动调用。

典型的工具包括:

  • 只读工具:

    • 读取文件/目录列表;
    • 查看 git diff / status / log / blame;
    • 执行构建/测试/静态分析并返回结果。
  • 有副作用的工具:

    • 写文件(带 patch);
    • 运行脚本/命令(有潜在风险);
    • 调用外部服务/API。

两者的设计和安全边界要严格区分。

在编排层,可以把每个工具抽象成类似这样的描述:

 1{
 2  "name": "run_tests",
 3  "description": "运行项目测试并返回失败用例信息",
 4  "inputSchema": {
 5    "type": "object",
 6    "properties": {
 7      "pattern": { "type": "string" }
 8    },
 9    "required": []
10  },
11  "outputSchema": {
12    "type": "object",
13    "properties": {
14      "success": { "type": "boolean" },
15      "failedTests": { "type": "array", "items": { "type": "string" } },
16      "rawOutput": { "type": "string" }
17    }
18  },
19  "sideEffects": "read_only"
20}

关键点:

  • 明确输入输出结构,避免模型随意发起“自由文本命令”;
  • sideEffects 标记工具是否有写入/外部交互等副作用;
  • 对有副作用的工具,在编排层和 IDE 侧增加额外确认步骤。

在工程上常见的两种 Tool 使用方式:

  • 固定流程调用

    • 例如“生成单测”这一任务,流程是:
      1. 模型生成测试代码草稿;
      2. IDE 把草稿以 diff 形式展示;
      3. 用户确认后应用到测试文件;
      4. 由系统自动调用 run_tests 工具,展示结果。
    • 在这种模式下,模型只负责“生成代码”,工具调用由编排逻辑写死。
  • ReAct / Toolformer 风格调用

    • 在 prompt 中告诉模型有哪些工具可用和调用方式;
    • 允许模型在推理过程中显式决定“现在要不要调用某个工具”;
    • 编排层解析模型输出的调用意图,真正触发工具,并把结果再喂回模型。

在 IDE 场景下,通常会混合使用:

  • 对高风险工具(写文件/跑脚本)更多使用固定流程 + 人工确认;
  • 对安全的只读工具(读文件、看 diff、跑测试)可以更多给模型自主选择的空间。

为了避免模型发出危险操作,工具层需要有一套“沙箱约束”:

  • 路径白名单/黑名单

    • 只允许访问工作区根目录以下;
    • 拒绝访问 .git/、敏感配置目录等;
    • 对写入操作进一步缩小范围(例如只允许写 src/tests/)。
  • 命令白名单

    • 对运行命令类工具,只允许调用预定义脚本(例如 npm testyarn lint);
    • 禁止直接执行任意 shell 片段。
  • 资源限制

    • 为每次命令执行设置超时和输出大小上限;
    • 防止测试/构建卡死或输出巨量日志。

这些约束最好都集中在“工具执行层”实现,而不是散落在模型或 IDE 插件里。

对于写文件、改配置、跑有副作用脚本等操作,一个务实的原则是:

  • 模型可以提出建议,但不能静默执行
    • 写文件 → 先给出 patch,再由人确认;
    • 执行脚本 → 先展示将要执行的命令,再由人确认。

一个典型的工作流可能是:

  1. 模型建议对多个文件做重构,并生成 patch 集;
  2. IDE 在多文件 diff 视图里展示 patch;
  3. 用户选择性应用 patch;
  4. 系统自动调用 run_tests / lint 工具,展示结果;
  5. 如有问题,再由模型根据测试/诊断输出提出下一轮 patch。

这样可以确保:

  • 模型确实“能做事”,而不只是给出文字建议;
  • 最终的执行仍在开发者和工具链的控制之下。

在企业环境下,还要考虑“出了问题如何追责和恢复”:

  • 将所有通过 Tool 触发的改动打上 metadata:

    • 来源(哪个用户、哪个任务、哪个模型);
    • 时间戳、关联的测试/诊断结果;
    • 对应的 patch 内容。
  • 提供一键回滚能力:

    • 可以基于 git(如果项目在 git 下);
    • 或基于 IDE 的本地历史记录。

这既是安全需求,也是产品体验的一部分:
让用户敢于“放心用”,因为知道“出了问题还能一键撤销”。

从 IDE 工程师角度看,设计工具调用和安全沙箱时,可以记住一句话:

对模型开放的是“能力接口”,而不是“操作系统 root 权限”。

具体落到实践上,就是:

  • 把工具能力抽象清楚(输入/输出/副作用),由编排层管理调用;
  • 在工具执行层施加严格的路径、命令和资源限制;
  • 对所有有副作用的操作,始终保留人为确认和回滚路径。

这样一来,AI 助手既可以深度参与“编译/测试/修复”的闭环,又不会在 IDE 里变成一个无法约束的“脚本小子”。