工具调用(Tool Calling)设计:函数签名、参数校验、权限最小化

Tool Calling 的本质:让模型“提案”,系统“执行”

在工程上,你应该把工具调用理解为:

  • 模型负责:提出调用意图与参数(proposal)
  • 系统负责:校验、授权、执行、记录(execution)

如果你让模型直接“执行”,那不是 tool calling,那是风险放大器。

设计目标:可控、可审计、可回滚

一个好的工具调用设计要做到:

  • 可控:模型只能调用你允许的工具,不能越权
  • 可审计:每次调用都能追踪输入、输出、耗时、错误
  • 可回滚:对外部系统的写操作要可撤销或至少可补偿

Tool Spec 模板(最小可用)

下面是一个“只读 GitHub PR”的工具规范示例(伪 spec,重点看结构):

{
  "name": "github.get_pull_request",
  "description": "Fetch pull request metadata and diff summary (read-only).",
  "input_schema": {
    "type": "object",
    "properties": {
      "repo": { "type": "string" },
      "pull_number": { "type": "number" }
    },
    "required": ["repo", "pull_number"],
    "additionalProperties": false
  },
  "permissions": {
    "scope": "read",
    "resources": ["repo"]
  }
}

关键点:

  • additionalProperties: false:防止“幻觉参数”悄悄混进来
  • required:明确必填
  • permissions:把权限当作工具的一部分

参数校验:不要相信模型

即使你写了 schema,也要在执行层做强校验:

  • 类型校验:string/number/enum
  • 范围校验:pull number > 0,max_words 限制
  • 白名单校验:repo 必须在允许列表或当前工作空间
  • 大小校验:字符串长度、列表长度、payload 总大小

校验失败要返回结构化错误(并映射到你的失败枚举)。

权限最小化:读写分离 + 人类确认点

建议做两条硬规则:

  1. 工具按“读/写”拆分
    例如:
  • github.get_pull_request(read)
  • github.create_comment(write)
  1. 写操作默认需要“人类确认”
    让模型先生成“将要写入的内容”,你再决定是否提交。

幂等性与去重:避免重复写入

写操作常见灾难是“重试导致重复”。

建议在写工具里支持:

  • idempotency_key:由上层传入(task_id + tool_call_id)
  • 或者对目标资源做去重(例如同一个 PR 同一段评论不重复发)

动手做:把工具调用接入 Skill 的三段式流程

以 PR 摘要 Skill 为例,一个稳定流程是:

  1. 计划:模型输出“需要哪些工具调用”(只读)
  2. 执行:系统按 schema 校验后调用工具,拿到真实数据
  3. 生成:模型基于真实数据生成输出(结构化输出契约)

如果你把“计划”和“执行”混在一起,排障会非常痛苦。

工程化清单

  • 工具 spec 有 schema 且 additionalProperties=false
  • 执行层强校验,返回结构化错误
  • 读写工具分离,写工具默认人类确认
  • 每次工具调用可审计(输入、输出、耗时、错误、调用链)

常见坑

  • “工具参数靠模型猜”:会发生幻觉参数/错字段
  • “写工具无护栏”:越权、误操作、不可回滚
  • “没有幂等策略”:重试写爆外部系统

下一篇:MCP 入门:把你的服务变成可插拔工具

← 返回博客列表