前几天一个学 Agent 的朋友在群里问了我一个问题:
Tool Call 似乎是 Agent 循环的灵魂,如果 JSON 总返回不太对的话,那当前这么繁荣的 Agent 生态不可能存在呀。所以不知道什么时候开始这个事情就被解决了,是 Function Calling 时代就解决了吗?

说实话,这个问题问得特别好。因为它直接触及了 Agent 能跑起来的一个底层前提------模型输出的 JSON 必须是合法的。
你想想,一个 Agent 跑一个任务,可能要调用几十次工具。每次调用都是一段 JSON。如果有 5% 的概率格式出错,跑 20 轮就几乎必崩一次。这种东西要是不靠谱,整个 Agent 生态确实不可能存在。
那大模型到底是怎么保证 JSON 输出的正确性的?这个问题其实涉及到模型最底层的生成机制。今天就来好好聊聊。
先搞清楚一件事:模型是一个字一个字蹦出来的
很多人对大模型有个误解,觉得它是"想好了整段话再说出来的"。
不是的。大模型生成文本的过程,是一个 token 一个 token 往外蹦的。它先生成第 1 个 token,然后基于第 1 个 token 再生成第 2 个,再基于前 2 个生成第 3 个......一直到输出一个结束标记。
这个过程叫自回归生成。
你在 ChatGPT 或者 Claude 里面看到的那个"打字机效果"------文字一个一个蹦出来,这不是什么动画特效,这就是模型真实的生成过程。
那问题来了:当模型要输出一段 Function Call 的 JSON 时,它也是一个 token 一个 token 蹦出来的。先蹦一个 {,再蹦一个 ",再蹦 n、a、m、e......
你收到的不是一个完整的 JSON,而是一堆碎片:
javascript
{"na ← 第一批 token
me": "get ← 第二批
_weather", ← 第三批
"input": ← 第四批
{"city": ← 第五批
"北京"}} ← 最后一批,JSON 才完整
得等最后一个 } 蹦出来了,你才能解析这段 JSON,才能去执行工具。
好,记住这个过程,接下来是重点。
每次蹦一个 token,本质上是在"抽签"
模型每生成一个 token,内部实际发生的事情是这样的:
模型的词汇表里有几万甚至十几万个 token。每到要生成下一个 token 的时候,模型会给词汇表里的每一个 token 打一个分(叫 logits),表示"根据前面的上下文,这个 token 接下来出现的可能性有多大"。
然后这些分数经过一个叫 softmax 的函数,变成概率分布------所有候选 token 的概率加起来等于 1,我们把这个叫归一化过程。
最后从这个概率分布里"抽签",选出下一个 token。
概率高的更容易被选中,但不是 100% 确定。这也是为什么你问模型同一个问题,有时候得到不同的回答。
这个"打分 → 概率 → 抽签"的过程,就是理解后面所有事情的关键。
光靠训练,JSON 正确率只有 93%
现在我们知道了模型是怎么生成 token 的,那它输出 JSON 的时候,凭什么能保证格式正确?
第一层保障是训练。
模型在训练阶段看过海量的 JSON 数据,也看过大量"给定一个 JSON Schema,输出符合 Schema 的 JSON"的样本。所以它确实学会了 JSON 的语法规则------什么时候该输出引号,什么时候该输出逗号,什么时候该闭合花括号。
但光靠训练够吗?
OpenAI 公布过一个数据:**单靠训练,JSON Schema 的符合率只有百分之 93
93% 听起来挺高的?但你换算一下:100 次工具调用里有 7 次格式出错。一个 Agent 跑一个稍微复杂点的任务,10 轮工具调用就大概率崩一次。
在生产环境里,93% 远远不够。
那怎么办?
约束解码:把不合法的选项提前排除掉
这就是约束解码(Constrained Decoding) 登场的时候了。
还记得刚才说的吗?模型每次生成 token 的时候,会给所有候选 token 打分,然后从概率分布里抽签。
约束解码做的事情非常直觉------在抽签之前,把不合法的选项直接排除掉。
具体是这样的:
- 先把 JSON Schema 编译成一套语法规则
- 每生成一个 token,检查:根据已经生成的内容,接下来哪些 token 在语法上是合法的
- 不合法的 token,概率直接设为 0
- 剩下的合法 token 重新归一化(Softmax),正常抽签
举个例子。假设 Schema 要求有个 city 字段,类型是 string。当模型已经生成到 "city": 的时候,下一个 token 按照 JSON 语法只能是 "(字符串的开始引号)。数字、布尔值、null,这些 token 的概率全被设为 0 了。模型想选也选不了。
再比如,模型已经输出了 {"name": "get_weather", "input": {"city": "北京",这时候下一个 token 只能是 } 或者 ,(如果还有其他字段的话)。绝对不可能蹦出一个不合法的字符。
训练让模型"想"输出正确的 JSON,约束解码让模型"只能"输出正确的 JSON。
两者加在一起,JSON 格式的正确率就是 100% 了。
这就是为什么现在 Agent 生态能繁荣起来。Function Calling 在底层做了约束解码,从机制上保证了格式不会出错,而不是靠"模型比较聪明所以一般不会错"这种概率性的东西。
但约束解码只管格式,不管语义
这里要补一个很重要的点。
约束解码能保证的是格式上的正确性------JSON 语法一定是合法的,字段类型一定是对的。
但它不能保证语义上的正确性。
什么意思?比如你的工具有个参数 city,类型是 string。约束解码能保证模型输出的确实是一个字符串,但它不能保证这个字符串是一个真实存在的城市。模型完全可以填个"哥谭市"进去------格式完全正确,JSON 解析没问题,但执行必然失败。
再比如,模型可能编一个看起来像 UUID 但完全不存在的 ID,可能拼一个看起来合理但并不存在的文件路径。这些都是格式正确但语义错误的情况。
所以在实际的 Agent 系统里,光有约束解码还不够,你还需要参数校验、执行前检查、以及清晰的错误反馈。这些是另外一个话题了。
那 System Prompt 约束 JSON 输出靠谱吗?
那个朋友还追问了一个问题:如果用 OpenSpec 那种基于 system prompt 的 CLI 系统,约束相对 Tool Call 会弱很多吧?

确实会弱很多。
System prompt 里写"请用 JSON 格式输出",这种约束本质上是自然语言层面的引导。模型会"尽力"按你说的格式输出,但它没有任何机制层面的保障。模型在生成过程中,每一步选 token 的时候,所有 token 都是可选的------包括那些会破坏 JSON 格式的 token。
而 Function Calling / Tool Call 的约束解码是机制层面的保障。在 token 抽签之前,不合法的选项已经被排除了。模型不是"尽力"不犯错,而是"不可能"犯错。
举个形象的例子,这就像一个是告诉司机"请走右边车道",另一个是直接在路中间立了隔离带。前者靠自觉,后者直接靠物理约束,效果立竿见影。
所以如果你在做 Agent 开发,涉及到工具调用的场景,一定要用模型原生的 Function Calling 能力,而不是用 system prompt 来约束输出格式。这不是"好不好用"的问题,是"能不能在生产环境跑"的问题。
再往深想一层
其实理解了约束解码,你会发现它的应用不止 Function Calling。
比如结构化输出(Structured Output)------你想让模型按固定格式输出一个评分结果、一段摘要、一组提取的实体,都可以用同样的技术。给一个 JSON Schema,模型的输出就被限制在这个 Schema 的语法范围内。
再比如 Manus 用的 response prefill 技巧。既然模型是基于前面所有 token 来决定下一个 token 的,那你提前替模型把前几个 token 写好,模型就只能顺着你的开头继续往下生成。比如你在 assistant 消息里预填 {"name": "browser_,模型就只能在 browser_ 开头的工具名里选了。这也是一种约束,只不过是另外一种思路。
这些技巧背后的原理是一脉相承的:理解了模型"一个 token 一个 token 抽签"的生成机制,你就能理解所有这些控制手段为什么有效。
最后
回到开头那个问题------Tool Call 的 JSON 输出凭什么能保证正确?
答案就三句话:
- 模型是一个 token 一个 token 生成的,每次都在概率分布里抽签
- 约束解码在每次抽签前把不合法的选项排除掉
- 训练 + 约束解码 = 100% 格式正确
这不是模型"聪明",这是机制设计。
其实这个问题之所以值得拿出来聊,是因为它触及到了 Agent 开发里一个非常底层但又非常关键的知识点。做 Agent 不是简单地调调 API、写写 prompt 就完了,底层这些机制你不理解,很多决策你做不对,你会很困惑。相信这个朋友的疑惑并不是个例,其实非常普遍。
最近也确实有很多读者朋友找我来学 Agent。我最近准备开一门「吃透 Agent 知识体系」以及相关实战的课,即将开始预售。我们从大模型底层的原理讲起,一直到 Agent Loop、Tool Call 系统设计、Context Engineering 进阶等六大板块,彻底学通 Agent 开发领域,并且还有硬核的实战项目。
想参与早鸟特惠的,可以加我微信 sanyuan0704,了解更多信息,备注"agent 早鸟学习"即可。