工具调用是双向契约,一半叫"调",一半叫"读"。整个行业九成精力砸在"调对"——选对工具、传对参数、过 schema 校验、strict 模式锁死 JSON。但另一半"读对"同样致命,且更隐蔽:工具明明返回了正确数据,Agent 却在消费环节读错字段、丢量纲、混三态、漏分页、信伪装、迷嵌套,最终吐出错误答案。Strict schema 只锁住了"调"的出口(arguments 合法),锁不住"读"的入口(返回值被正确理解)。本文实测 OpenAI、Anthropic、LangChain、Pydantic AI、AutoGen、MCP 六大主流栈,在工具返回值解析这一环上的真实表现与六大沉默失效模式。
| 维度 | OpenAI Structured Outputs | Anthropic Claude | LangChain/LangGraph | Pydantic AI/Instructor | AutoGen | MCP |
|---|---|---|---|---|---|---|
| 返回约束机制 | strict:true + JSON Schema | 约束解码 + tool_result 内容块 | ToolMessage + OutputParser | Pydantic 校验驱动 | 自由文本/JSON | content 数组,自由形态 |
| 是否保证读对 | 否(只保调对) | 否 | 否(靠 parser 兜底) | 部分(类型对,语义仍错) | 否 | 否 |
| 空值三态处理 | 无原生 | 无原生 | 需自定义 | 可校验但需手写 | 无 | 无 |
| 分页感知 | 无 | 无 | 无 | 无 | 无 | 无 |
| 错误伪装防护 | 无 | 无 | 无 | 无 | 无 | isError 字段(弱) |
| 上手难度 | 低 | 低-中 | 中 | 中 | 中 | 中-高 |
| 变现潜力 | 高 | 高 | 中 | 中-高 | 中 | 高 |
OpenAI 的 strict: true 是 2024 年 8 月上线的杀手锏,到 2026 已成为生产默认。它在解码层做约束生成(constrained decoding),保证 tool_calls 里的 arguments 严格匹配你给的 JSON Schema——多一个字段、少一个字段、类型不对,都不可能产出。这把"调"这一半做到了近乎零误差。
tools = [{
"type": "function",
"function": {
"name": "get_order",
"strict": True,
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
"additionalProperties": False
}
}
}]
问题在于:strict 只校验 Agent 传给工具的参数,不校验工具返回给 Agent 的内容。当 get_order 返回 {"subtotal":9900,"tax":990,"total":10890,"grand_total":10890},模型完全可能取 subtotal 当总价报给用户——schema 没有任何手段阻止它读错字段。strict 的胜利止步于工具调用边界,返回值一进上下文,就是一片语义裸奔。变现上,这块适合做"结构化输出训练营"的进阶模块,但必须明确告诉学员:strict 不等于可靠,它只解决了三成问题。
Anthropic 的工具用 tool_use 内容块发起调用,执行后以 tool_result 内容块回填。2026 年它也上线了 Structured Outputs(约束解码),但那是给"Agent 最终回复"用的,不是给工具返回值用的。tool_result 的内容本质是一个自由文本/JSON 字符串,塞进 messages 数组:
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_xxx",
"content": json.dumps({"amount": 9900, "currency": "CNY"})
}]
})
这里 content 是字符串,模型要自己再"读"一遍 JSON。量纲丢失这一式在这里最典型:返回 amount: 9900 表示分,模型直接告诉用户"9900 元"或"99 元"——单位是上下文约定,不在结构里。Anthropic 没有 isError 之外的语义层,错误伪装(HTTP 200 + {"ok":false})几乎无防护。优势是多工具并行、内容块组合灵活;劣势是返回值消费完全靠模型自觉。适合做对话型 Agent 训练营,但要配一套"返回值自校验"提示词模板才敢交付。
LangChain 用 ToolMessage 承载工具返回,并提供 PydanticOutputParser、JsonOutputParser 等解析器。理论上 parser 能在 Agent 读取前先做一次结构化校验,把风险挡在门外:
from langchain_core.messages import ToolMessage
msg = ToolMessage(
content=json.dumps(data),
tool_call_id=call_id,
name="search_products"
)
parser = PydanticOutputParser(pydantic_object=SearchResult)
parsed = parser.parse(msg.content) # 校验在这里
但实践中三个坑:第一,parser 只校验"形状对不对"(字段在不在、类型对不对),校验不了"读对没读对"——total 和 subtotal 都是合法数字,parser 全通过,模型照样取错。第二,parser 抛异常会中断链,生产里往往被 try/except 吞掉,退化成裸字符串。第三,分页截断这一式在 LangChain 里几乎无解——search_products 返回 20 条带 has_more:true,Agent 默认认为这就是全集,LangGraph 没有内置"分页跟随"原语。变现适合做企业 RAG + 工具链课程,但要在课程里专门加一章"返回值消费工程"。
Pydantic AI 和 Instructor 把 Pydantic 校验推到极致:模型输出直接绑定类型化对象,校验失败自动重试修复。这是六栈里最接近"保证读对"的,但注意——它保证的是"输出对象合法",不是"语义正确"。
from pydantic_ai import Agent
agent = Agent('openai:gpt-4o', output_type=OrderSummary)
@agent.tool
async def get_order(ctx, order_id: str) -> OrderSummary:
raw = await fetch(order_id)
return OrderSummary.model_validate(raw) # 在工具内提前校验+转换
聪明做法是在工具函数内部就用 Pydantic 把原始返回 model_validate 成强类型对象,把量纲(amount_cents → amount_yuan)、空值三态(None/[]/error 显式区分)在源头消解掉。这样模型拿到的已经是语义清晰的对象,读错概率大幅下降。代价是每个工具都要写转换层,开发量翻倍。变现潜力高——"类型安全 Agent 训练营"是 2026 稀缺品类,Pydantic AI 正好是抓手,但门槛在于讲师要真懂校验≠语义。
AutoGen 的工具返回值直接以消息形式进入多 Agent 对话流,没有强制的结构化层。一个 Agent 调工具、拿结果、转述给另一个 Agent,每转一次都是一次"再读"——嵌套迷失在这一栈最严重。
典型场景:工具返回 {"order":{"customer":{"address":{"city":"上海"}}}},AssistantAgent 提取时把 address 整个对象当城市名报出去,或读到 customer 层就停。多 Agent 链路里,这种读错会被下游 Agent 当事实继续推理,误差级联放大。AutoGen 0.4+ 引入了结构化消息,但默认仍是自由文本。变现上 AutoGen 适合做"多智能体协作"主题训练营,但务必搭配返回值 schema 强约束演示,否则学员做出的 Demo 看着能跑、数据全是错的。
MCP(Model Context Protocol)的 tool result 是 content 数组,可塞文本、图片、资源引用,并有一个 isError 布尔位。这是六栈里唯一原生区分"成功返回"与"工具报错"的——但仅此而已。
{
"content": [{"type":"text","text":"{\"amount\":9900}"}],
"isError": false
}
isError:false 只表示"工具没抛异常",挡不住错误伪装——HTTP 200 + {"ok":false,"error":"余额不足"} 会被标成 isError:false,Agent 照读为成功。分页、量纲、嵌套、字段歧义,MCP 全部不管。它的价值在协议层(统一工具接入),不在语义层(保证读对)。变现上 MCP 是 2026 最热的"工具生态"主题,训练营需求旺盛,但课程必须诚实标注:MCP 解决的是"接得进来",不是"读得明白"。
flowchart TD
A[Agent 决策调用工具] --> B[strict schema 锁参数出口]
B --> C[工具执行]
C --> D[返回值进入上下文]
D --> E{消费环节: 六式失效}
E --> F1[字段歧义: total/subtotal]
E --> F2[量纲丢失: 分/元]
E --> F3[空值三态: null/[]/error]
E --> F4[分页截断: has_more 被忽略]
E --> F5[错误伪装: 200+ok:false]
E --> F6[嵌套迷失: 读错层级]
F1 --> G[错误答案输出]
F2 --> G
F3 --> G
F4 --> G
F5 --> G
F6 --> G
style B fill:#cfe8cf
style E fill:#f5d5d5
style G fill:#f5d5d5
整条流水线里,绿色只有"参数出口"一处被锁死,红色"消费环节"是六式失效的集中爆发区,而行业默认这里"模型自己会读对"。
单工具、强类型场景:首选 Pydantic AI/Instructor,在工具函数内 model_validate 把量纲、空值三态在源头消解,模型拿到的就是干净对象。这是当前唯一能逼近"读对"的路径。
多工具、协议统一场景:MCP 接入 + 每个工具配 Pydantic 转换层。MCP 管"接得进来",Pydantic 管"读得明白",两层分工。
对话型、轻量场景:Anthropic tool_use 即可,但务必在系统提示里植入返回值自校验清单("金额需确认单位""空数组需区分无结果与查询失败""200 不等于成功")。
生产 RAG + 工具链:LangGraph + 自定义分页跟随节点 + isError 显式传播。不要指望 OutputParser 解决语义,它只解决形状。
通用兜底:无论哪栈,都应在工具返回后插入一个"返回值契约"提示——用自然语言把字段语义、单位、空值含义、分页信号、错误判定规则写死,让模型在读取前先对齐契约。这是六栈都缺失、但人人都能补上的一层。Strict schema 解决了"调对",返回值契约才能解决"读对"——前者是平台的胜利,后者是工程师的战场。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
