Tool Calling:让模型提出请求,而不是获得执行权
Tool Calling 的重点不是让模型生成一段可解析的 JSON,而是把模型的调用意图放进一条由服务端校验、授权、执行、审计和降级的受控协议。
给 AI 助手接入“查询网站内容”的能力,看上去像是一件直接的事:用户提问,模型输出一段 JSON,后端解析 JSON 后去查数据。
真正做进聊天链路后,我发现这套理解过于乐观。问题不在于模型能不能生成 JSON,而在于模型生成的内容是否应该被执行。模型可能拼错工具名,给出异常长参数,尝试访问不该访问的数据,也可能在多轮对话里反复请求同一个工具。把模型输出直接当成命令执行,会让聊天功能变成一条缺少边界的数据访问通道。
因此,我对 Tool Calling 的理解是:模型负责提出调用意图,服务端负责判断、执行和收口。
第四周的目标不是让模型“看起来会用工具”,而是把这个分工接入 my-world 的 assistant v2,并且能说清它实际证明了什么。
从隔离 Demo 开始,但不止于 Demo
最开始做的是隔离 Demo:按关键词查询公开文章。它验证了最小闭环,包括工具注册、参数 schema 校验、公开状态过滤、结果裁剪和失败处理。
但 Demo 通过不代表真实聊天已经支持 Tool Calling。隔离环境可以用 fixture 模拟模型请求工具;真实聊天还要处理持久化任务、流式输出、Provider 返回的调用分片、工具执行后的二次模型请求、前端状态展示和审计事件。两者之间差的不是一段查询代码,而是一条完整的执行协议。
后续把 Demo 的边界迁移进 assistant v2 的 Worker:模型请求工具,服务端校验并执行,受控结果以 tool message 回填模型,最后由模型生成面向用户的自然语言回答。
注册表决定模型能提议什么
当前接入了四个只读公开内容工具:
search_public_articlessearch_public_projectssearch_public_article_seriessearch_public_life_notes
模型看到的不是服务端全部能力,而是由注册表导出的工具声明。它只能在明确列出的工具中选择,不能自行发明“查询后台用户”或“删除文章”一类工具名。
这件事决定了权限边界是否真实存在。模型能看见但服务端无法执行,调用一定失败;服务端能执行但模型没有被声明,该能力就不会被模型主动选择。注册表让工具能力成为可审查、可测试的显式配置,而不是散落在提示词里的自然语言约定。
图片生成和其他写操作没有接入。这些能力会带来成本、异步任务、重复提交和资产管理问题,不适合用来验证第一条受控工具链路。只读公开内容查询的副作用更小,失败也不会修改数据。
参数校验不能交给模型自觉
四个工具共用同一组输入边界:keyword 会先 trim,必须是 1 到 80 个字符;limit 默认 3,限制在 1 到 5 条。
这些限制不是为了让接口看起来严谨,而是为了阻止无意义查询、异常长输入和一次返回过多内容。模型生成的参数仍然是不可信输入。未注册工具和非法参数都不会触发查询,服务端会记录受控失败结果,而不是把原始请求直接交给数据层。
模型负责提议,服务端负责批准。这是 Tool Calling 与“提示模型输出 JSON”最本质的区别。
公开数据也需要双重过滤
工具的数据源来自 Payload 内容集合,但工具名字里有“公开”两个字,并不表示查询天然安全。
每次查询都会强制限定 contentStatus=published,执行器还会再次过滤公开状态。数据层条件负责缩小查询范围,执行层过滤则防止数据源或查询逻辑以后变化时意外带出草稿和隐藏内容。
回填给模型的结果也经过裁剪,只保留内容类型、标题、受限摘要、公开链接和发布时间。正文、后台字段、隐藏内容、Cookie、密钥和 Provider 原始错误都不进入工具结果。这样既控制模型上下文占用,也避免工具结果成为内部数据的旁路出口。
当前搜索使用 Payload 的轻量 contains 查询,不是全文检索,更不是 RAG。它足以验证受控工具调用链路,但命中质量仍依赖标题、摘要和少量公开字段。Embedding、向量检索和可引用的资料回答,是下一阶段要继续解决的问题。
流式 Tool Calling 要能拼回半个参数
真实 Provider 的流式响应不会总是一次给出完整工具调用。工具名、调用 ID 和 JSON 参数都可能拆成多个 tool_calls delta 分片抵达。
Worker 需要先组装分片,在调用结束后执行 schema 校验和工具查询。工具成功后,结果作为受控 tool message 进入下一轮模型请求,最终才产生回答。
在这次验证中,真实 qwen3.7-max 返回了流式工具调用分片;真实 Worker 使用真实 Payload 搜索源完成查询;production 模式浏览器也完成了项目查询并返回公开项目链接。这里验证的不是“代码可以编译”,而是模型请求、服务端执行、结果回填、前端展示和任务持久化可以连成完整链路。
安全上限不该成为用户可见错误
多轮工具调用还有一个容易遗漏的问题:模型可能持续请求工具,形成循环。
服务端保留了工具轮数上限,避免无限调用和不可控成本。但一开始,达到上限会让用户看到内部错误。后续改为达到上限后关闭工具,让模型基于已经获得的公开结果完成最后一轮回答。
这个调整保留了安全边界,也避免把“工具调用次数超出安全限制”这样的内部实现细节暴露给用户。工具无法继续调用时,用户需要的是可用回答或清楚的资料不足说明,而不是系统内部的报错语言。
验证不是单一测试命令
这次实现完成了 47 项 streaming 集成断言,覆盖工具声明、非法参数拒绝、未注册工具、查询失败降级、公开状态过滤、白名单回填、审计、流式参数分片、四类内容工具、取消和超时路径,以及工具循环的降级行为。
隔离 Demo 5/5 通过,数据库迁移、TypeScript 检查和生产构建通过。最后在 production 模式浏览器中验证了真实项目查询,任务事件记录了工具开始和结束,审计中只保存参数摘要的哈希,不保存关键词原文。
这些验证支持的结论是:公开内容只读 Tool Calling 已经完成本地 production 模式技术验收。它不支持“任意工具都可以安全调用”的结论,也不支持“功能已经线上发布”的结论。
执行权必须留在服务端
这次实践留下的最重要结论是:Tool Calling 的难点不在模型会不会调用工具,而在系统能否始终守住执行权。
让模型提出请求很容易。让每个请求都可验证、可限制、可追踪,并在失败时保持用户体验,才是把 AI 能力放进真实产品的开始。
当前代码尚未提交或部署。部署前仍需要复核真实环境模型、密钥、超时和迁移,部署后还需要做一次线上工具调用观察。写操作、图片生成、私有数据访问和更复杂的授权模型也仍然不在这条链路的范围内。
有想法或建议? 联系我