AI 应用后端工程化:从原型到可交付系统 · 第 06 篇 · 第二章 · 知识与工具
模型提出结构化意图,应用验证并执行工具,再把结果放回对话;这才构成真正的闭环。
普通聊天只改变文本,工具调用却可能查询天气、发送请求、创建文件甚至扣减余额。模型说“我要调用某个函数”并不意味着函数已经执行;中间必须有一层应用代码验证意图、检查权限、执行副作用,再把结果按协议送回模型。
这层边界决定了 Agent 是可控软件,还是一个能随意触碰外部世界的黑箱。
模型只负责提交申请
可以把工具调用理解成员工填写领料单。表单写着工具名称和参数,仓库仍要核对格式、身份、库存和危险等级。批准后发生真实动作,结果再回到员工手里决定下一步。
sequenceDiagram
participant U as 用户
participant M as 模型
participant R as 工具注册表
participant T as 工具
U->>M: 提出任务
M->>R: tool_call(name, arguments)
R->>R: 校验参数与权限
R->>T: 执行
T-->>R: 结构化结果
R-->>M: tool_result
M-->>U: 基于结果回答
模型输出的是结构化意图,注册表才拥有真实函数。两者之间不能用任意字符串 eval 连接。注册表只暴露明确允许的名称,每个工具都有参数 Schema、超时、权限和结果上限。
消息顺序也是协议
一次调用通常包含 assistant 发出的 tool call,以及随后与该 call ID 对应的 tool result。只把结果拼成用户消息,某些模型会丢失调用关联;只保存结果不保存原始调用,则无法恢复对话或审计模型当时提出了什么参数。
def execute_tool_call(call, registry, actor):
tool = registry.get(call.name)
if tool is None:
return {"call_id": call.id, "ok": False, "error": "unknown_tool"}
arguments = tool.schema.validate(call.arguments)
tool.policy.authorize(actor, arguments)
value = tool.run(arguments)
return {"call_id": call.id, "ok": True, "value": value}
示例刻意把查找、校验、授权和执行分开。未来增加审计或限流时,它们可以统一进入同一执行入口,而不是修改每个工具函数。
工具返回什么比调用什么更难
搜索工具可能返回几十个网页,数据库工具可能返回十万行,图片工具可能返回二进制。若把原始结果全部塞进上下文,成本和延迟会迅速失控。结果协议应该同时考虑模型需要的信息与人类需要的证据。
一种做法是返回摘要、关键字段和可恢复引用:模型看到前十条和总数,完整结果保存在对象存储或任务记录中。涉及金额、时间和 ID 时使用结构化字段,不让模型从自然语言中再次猜测。
错误也应是可行动信息。request failed 无法帮助模型修正参数;city 参数不能为空 或 上游超时,可以重试一次 才能支持下一轮决策。但内部堆栈、密钥和原始响应不应直接进入模型上下文。
从链式流程到图式流程
当模型只能回答文本,执行路径通常是输入到输出的一条链。加入工具后,路径开始分叉:模型可能直接回答,也可能调用一个或多个工具,工具失败后还可能重试或换方案。这更接近状态图。
停止条件必须明确,例如收到最终文本、达到最大轮数、连续重复同一调用、消耗超过预算或用户取消。没有停止条件的 Agent 可能在“查天气失败”与“再次查天气”之间循环,既消耗费用又占用工作线程。
并行工具也要谨慎。查询天气和当前时间可以并行,创建订单与支付订单有明确依赖,不能因为模型一次返回两个 call 就盲目同时执行。应用必须掌握业务依赖,而不是把调度权全部交给模型。
最危险的快捷方式
直接把 Python 函数列表交给框架,演示时非常顺滑,却容易遗漏授权。普通用户可能借助一个通用 HTTP 工具访问内部地址,或通过文件工具读取服务账户可见的配置。工具描述中的“请勿访问敏感文件”只是给模型的建议,不是安全控制。
硬边界应放在执行器:路径白名单、网络出口限制、身份权限、参数范围、超时和审计都必须由确定性代码执行。高风险动作还需要人工确认或二阶段提交。
跨领域看工具系统
工具调用与支付网关、插件平台和任务执行器有相同骨架:声明能力、验证请求、授权调用者、执行副作用、记录结果。模型只是一种会自动选择能力的调用者,因此传统 API 安全原则仍然适用。
练习可以实现一个只读天气工具和一个模拟写入的日程工具。为两者定义不同权限,故意传入未知城市、超长标题和重复 call ID,观察执行器是否能拒绝、给出可理解错误并避免重复写入。保存消息数组,检查 assistant call 与 tool result 是否一一对应。
两个实现节点
从工具执行器到工具节点,看似只是功能增加,实质上是系统控制流发生了变化。理解这次变化,后面设计注册表、工作流和 Agent Loop 才不会只停留在框架 API 上。