《大模型实战》第 5/10 篇
上篇:MCP 协议实战(同系列第 4 篇,发布后可替换为正式链接)
下篇预告:多 Agent 协作
上篇把 MCP 讲成「工具的标准插座」。插座有了,工具本身能不能稳、能不能审计、能不能失败可恢复,才是生产分水岭。
Demo 里 tool 能跑,和生产里 tool 能扛流量,中间差一整层工程。
本篇聚焦 Schema、沙箱超时、重试降级、human-in-loop 与可观测;前端怎么展示进度,请看《AI 前端实战》第 4 篇:Tool Calling 前端接入。
你将学到
- OpenAI / Anthropic tool 格式差异与统一抽象
- Schema 设计原则:少而精、可组合、可验证
- 沙箱、超时、并发与资源隔离
- 失败重试、降级、人工确认策略
- 可观测:调用链日志与指标
- 「查库 + 发通知」双工具 Demo 思路
一、Tool Calling 在生产里的真实链路
text
用户意图
→ 模型选择 tool + 生成 arguments(JSON)
→ 网关校验 Schema / 权限
→ 执行器(沙箱内)
→ 结果规范化 + 脱敏
→ 回灌模型 → 最终自然语言回复
→ (可选)前端展示 tool part 状态
最容易忽略的环节:校验、执行、回灌、可观测 。
模型只负责「想调什么」,不负责「能不能调、调坏了怎么办」。
二、格式对比:先统一,再适配
| 项 | OpenAI 风格 | Anthropic 风格 |
|---|---|---|
| 定义位置 | tools[] + function |
tools[] + input_schema |
| 调用标识 | tool_calls[].id |
tool_use.id |
| 结果回传 | role: tool + tool_call_id |
tool_result content block |
| 并行调用 | 支持多 tool_calls | 支持多 tool_use |
工程建议:内部用一套 ToolDefinition(name, description, parameters, risk_level) ,出口再适配各厂商 SDK。
否则每换一个模型,BFF 里全是 if-else。
三、Schema 设计原则
1. 少而精,不要「万能工具」
反例:do_everything(action, payload) ------ 模型参数漂移、审计困难。
正例:get_order_status(order_id)、send_slack_message(channel, text)。
2. 参数可验证、可缺省
json
{
"type": "object",
"properties": {
"order_id": { "type": "string", "pattern": "^ORD-[0-9]{8}$" },
"include_items": { "type": "boolean", "default": false }
},
"required": ["order_id"],
"additionalProperties": false
}
additionalProperties: false 能拦住模型「顺手多塞字段」。
3~5. 描述、组合与风险
- description 写清 何时用 / 何时不用 / 返回什么
- 多步用多个小 tool,别做
do_everything(action, payload) - 风险分级见下表;L2+ 需 human-in-loop(前端见 Tool Calling 前端接入)
| 级别 | 示例 | 策略 |
|---|---|---|
| L0 只读 | 查天气、查订单 | 自动执行 |
| L1 写内部 | 写备注、建草稿 | 自动 + 审计 |
| L2 写外部 | 发邮件、公开发布 | 人工确认 |
| L3 高危 | 转账、删数据 | 禁止或双人复核 |
四、沙箱与超时
工具执行不要和 API 进程同生死。
| 手段 | 适用 |
|---|---|
| 子进程 + 超时 kill | 本地脚本、CLI |
| 容器 / 轻量 VM | 不可信代码、多租户 |
| 独立 Worker 队列 | 慢任务、批量通知 |
| 网络 egress 白名单 | 防 SSRF、防内网扫描 |
推荐:读 3~10s、写 10~30s + 幂等键、每会话限制并行 tool 数。超时返回 {ok:false, error, hint, retryable},别只抛 Timeout。
五、失败重试与降级
重试谁来做?
| 层 | 做什么 |
|---|---|
| 执行器 | 幂等写操作有限次重试(如 2 次,指数退避) |
| 编排层 | 换 tool 路径(查缓存 → 查库) |
| 模型层 | 读错误 hint 改参数再调(需限制轮数) |
不要三层同时无脑重试,成本会指数涨。
降级策略
- 主 tool 失败 → 返回部分字段 + 建议用户手动步骤
- RAG 检索失败 → 仅模型常识回答并声明「未检索到内部资料」
- 通知失败 → 落库待发送队列,对话里告知「已排队」
L2/L3:tool_call → pending_confirm → 用户 confirm/reject → 执行或回灌拒绝原因。MCP Server 只暴露定义与执行,鉴权 / 重试 / 确认 / 日志 应在 Host/BFF 统一做。
六、可观测:没有日志就没有治理
每次 tool 调用至少记录:
| 字段 | 用途 |
|---|---|
| trace_id / session_id | 串起多轮 |
| tool_name / arguments(脱敏) | 排错 |
| latency / status / retry_count | SLO |
| user_id / risk_level | 审计 |
| model_name / token_cost | 成本归因 |
指标:tool_success_rate、tool_p95_latency、human_confirm_rate、tool_retry_tokens。
七、双工具 Demo:查库 + 发通知
场景:用户问「订单 123 发货了吗?顺便 Slack 通知客服。」
工具定义
get_shipment_status(order_id: string)
- 只读,L0
- 调内部 REST / SQL,返回
{status, carrier, eta}
notify_slack(channel: string, message: string)
- 写外部,L2
- 需
pending_confirm;message 由模型生成,网关可模板化防注入
编排:get_shipment_status(L0) → notify_slack(L2, pending_confirm) → 用户确认 → 汇总回复。网关伪代码:validate → permission → pending_confirm? → sandbox(timeout) → sanitize。
踩坑清单
- 模型参数 JSON 偶发非法 → 网关 schema 校验 + 让模型修一次,仍失败则降级
- tool 结果太长塞爆上下文 → 摘要后再回灌,原始结果存对象存储
- 前端直连内网 tool → SSRF;执行必须在服务端
- 停止生成后 tool 仍在跑 → abort 流 + 取消进行中的 HTTP
- 同一 tool 并发写 → 加会话级 mutex 或幂等键
- 错误信息太技术化 → 给模型的是 hint,给用户的是人话
成本 / 风险提示
| 项 | 影响 |
|---|---|
| 失败重试 | 单次对话 token 可 ×2~×4 |
| 大 tool 结果回灌 | 输入 token 暴涨 |
| 过多 tool 定义 | 每轮都占 system/tools 前缀 token |
| 人工确认等待 | 会话变长,摘要窗口压力上升 |
| 沙箱 | CPU/内存固定开销,但比裸跑安全便宜 |
控制手段:工具数量控制在 10 个以内/域、结果 max_tokens 截断、重试上限写死、L2 以上默认不自动重试。
系列导航
| 篇 | 主题 |
|---|---|
| 第 4 篇 | MCP 协议实战 |
| 第 5 篇 | Tool Calling 工程化(本篇) |
| 第 6 篇 | 多 Agent 协作 |
| 第 7 篇 | 大模型成本治理 |
跨系列内链 :Tool Calling 前端怎么接 · Agent 前端状态机(Generative UI 篇,Agent 并发工具)
小结
Schema 写给模型也写给审计;执行在沙箱、权限在网关;失败结构化、重试有上限;高危必 human-in-loop。
下篇预告 :《大模型实战》第 6/10 篇
多 Agent 协作:编排、分工、冲突与成本。