六层防线:让大模型稳定输出结构化数据的工程实践

在 Agent 开发中,你一定遇到过这样的场景:让大模型返回一段 JSON,它却在结果前面加一句「好的,这是您要的结果」,或者在第 49 步多出一个逗号,导致整条流程直接崩溃。这就像点外卖备注「不要香菜」,老板偏偏贴张纸条写「香菜很香啊」。
单次调用出错还能重试,但在 Agent 系统里,每一步的输出都是下一步的输入——只要某一环格式出错,后续代码解析立刻抛异常,整个任务功亏一篑。这就是「输入→输出→再输入」闭环的脆弱性。本文将系统梳理一套从提示词到硬约束、从单步到全流程兜底的六层防线,帮你把这个「熊孩子」管得服服帖帖。
问题根源:模型天生爱「自由发挥」
大模型本质上是个 token 生成器,训练目标是生成「看起来自然的文本」,而非「精确的数据结构」。所以它大部分时候能返回 JSON,但总有那么几次多个逗号、少个引号,或者把数字写成字符串。这不是故意捣乱,而是训练目标决定的天性。
关键洞察在于:结构化输出不是靠提示词「劝」出来的,而是要建立一套工程化的校验和纠错机制。如果面试时你只会回答「加个 JSON Schema」,面试官一定会追问「加了还是乱输出怎么办?Agent 跑了 50 步怎么保证每步都合规?」答不上来,就暴露了对工程化理解的浅薄。
第一层:约束解码——从物理层杜绝格式错误
约束解码(Constrained Decoding)是最底层、最硬核的防线。原理很直接:模型每生成一个 token,都根据预定义的 JSON Schema,限制它只能从一小部分合法 token 中选择。
举个例子,JSON 对象的第一个字符必须是左花括号 {,那么解码器就把其他所有 token(左中括号、双引号、汉字……)全部物理屏蔽掉,模型只能生成 {。这就像考试的答题卡,每个空只能填规定的选项,连犯错的机会都没有。
约束解码的底层实现依赖于有限状态机(FSM)或上下文无关文法(CFG)。以 JSON Schema 为例,解码器在每个时间步维护一个「当前合法 token 集合」,这个集合由当前生成状态和 Schema 共同决定。具体实现上,Outlines、llama.cpp 的 grammar sampling、vLLM 的 guided decoding 等开源框架都提供了这一能力。值得注意的是,约束解码的计算开销并不均匀——在 JSON 的嵌套层级较深或 Schema 较复杂时,合法 token 集合的计算本身可能成为瓶颈,实际工程中需要对 Schema 复杂度做权衡。

这与提示词层面的「请返回 JSON」完全是两个层面的事:一个是「请求」,一个是「强制」。目前 OpenAI、Anthropic 等主流 API 都提供了 strict 模式,开启后工具调用参数能精确匹配 Schema,类型不匹配、字段缺失等问题从根本上消失,几行配置即可搞定。
副作用:质量退化
约束解码虽强,却有一个值得警惕的代价——质量退化。当强约束把模型本来最想选的高概率 token 全部屏蔽后,模型只能从低概率的替代 token 里勉强挑选,结果可能是「语法百分百正确,语义却完全荒谬」。比如 rating 字段格式完美却写了 100,或者把价格写成字符串「很贵」。
相关研究指出:基础模型通常能从约束中受益(输出更简洁直接),但指令微调过的模型在开放式生成任务上质量会明显下降。因此核心结论是:约束解码保证格式正确,但不保证内容正确,它只是第一道防线。
第二层:验证与重试循环——守住语义正确性
约束解码能保证 rating 是整数,但如果业务要求它只能是 1 到 5,模型输出了 10,它就管不着了。这就需要第二层防线:验证与重试循环。
用 Pydantic 或 JSON Schema 定义类型、取值范围、字段间的逻辑关系,模型输出后先过一遍验证器,把大模型的不确定性限制在业务可控的边界内。语法一道防线,语义一道防线,两层配合才完整。
Pydantic 与 JSON Schema 验证体系在 LLM 工程中已形成事实标准。Pydantic 通过 Python 类型注解自动生成 JSON Schema,并在运行时强制校验数据类型、范围约束、正则表达式匹配等。LangChain 的 with_structured_output、Instructor 库等都以 Pydantic 模型为核心抽象,将「定义期望输出结构」和「调用模型」合并为单一操作。JSON Schema 本身是 IETF 标准,支持 minimum/maximum、enum、pattern、$ref 等丰富的约束表达,能覆盖绝大多数业务语义校验需求。

验证不通过时,把错误信息(如「rating 值为 10,超出 1-5 范围」)连同原始输出一起回传给模型,让它重新生成,形成「验证→反馈→生成」的闭环,直到合格。实际工程中要设置最大重试次数(比如 3 次),避免死循环。
验证重试不是约束解码的替代品,而是互补品:约束解码保语法,验证重试保语义。它还带来业务规则与模型调用的解耦——以后改规则只需调整验证器配置,不用动模型代码,同时验证失败的日志也提供了宝贵的可观测性。
第三层:伪装成工具调用——低成本大幅提升稳定性
这是一个能快速提升稳定性的巧妙技巧。现代模型经过微调,非常擅长按指定 Schema 生成函数调用参数。我们可以利用这一点:不真正调用任何工具,只定义一个虚拟工具,把期望的输出格式写成它的输入参数,然后强制模型调用这个工具,它就会老老实实按 Schema 输出结构化 JSON。
这个方案技术门槛低、几乎所有主流模型都支持工具调用,Claude 的 Agent SDK 和 OpenAI 的 strict 模式都开箱即用。有团队用这个方法把 JSON 输出稳定性从 70% 提升到了 95% 以上。
第四层:Logit Masking——精准控制工具选择
从这一层开始,我们进入 Agent 系统的特有问题:工具选错。Agent 每一步都要从一堆工具里选下一个,工具一多就容易选错——该用计算器却调了搜索。
你可能想「动态增删工具」,但这有个大坑:工具定义在上下文最前面,每次修改都会导致后面所有的 KV Cache 失效,模型要重新计算大量注意力,延迟和成本暴涨,工程上几乎不可行。
KV Cache 与工具定义的关系值得深入理解:KV Cache 是 Transformer 推理优化的核心机制,对于已经计算过注意力的 token 序列,其 Key 和 Value 矩阵会被缓存,后续生成新 token 时直接复用,避免重复计算。工具定义通常位于系统提示的最前端,一旦这部分内容发生变化,从变化位置往后的所有 KV Cache 全部失效,模型必须重新做前向传播。在长上下文场景(如 Agent 跑了 50 步,上下文已有数万 token),这意味着每次修改工具定义都会引发巨量重计算,延迟从毫秒级跃升至秒级,API 成本也成倍增加。这正是 Manus 选择「工具定义永不改动 + Logit Masking」策略的核心工程动机。

前沿通用 Agent 产品 Manus 的做法是 Logit Masking:工具定义一开始就全部写死、永不改动,但在解码时通过屏蔽 logits 值,把当前不需要的工具从概率分布里直接遮掉。他们还把工具命名设计得很规整(浏览器工具以 browse_ 开头、命令行工具以 shell_ 开头),按前缀屏蔽既高效又保证了 KV Cache 完全安全。
第五层:Schema 契约——多 Agent 间的结构化通信
当任务拆给多个 Agent 执行时,它们之间的通信绝不能靠自然语言的模糊描述,而要建立明确的 Schema 契约——就像两个部门用正式 API 文档对接,而不是口头传话。谁生产数据、数据长什么样、谁消费,每个环节都定义清楚,即使某个 Agent 内部改了逻辑,只要契约不变,系统照样稳定。
Manus 采用了类似 MapReduce 的模式:Planner Agent 先统一定义输出 Schema,多个子 Agent 并行填充数据,最后合并结果。MapReduce 最初由 Google 在 2004 年提出,是大规模并行数据处理的经典范式——Map 阶段将任务拆分给多个 Worker 并行处理,Reduce 阶段将结果合并。在多 Agent 系统中,这一思想被自然延伸:Orchestrator Agent 负责任务分解和 Schema 定义,多个 Sub-Agent 并行执行同构子任务,最终按预定义 Schema 合并结果。比如从 100 份简历提取信息,可同时启动 10 个子 Agent 独立处理再汇总,效率极高。这种模式的关键在于:子任务之间完全解耦,单个 Sub-Agent 的失败不会级联影响其他 Agent,但 Reduce 阶段的结果合并逻辑需要仔细设计,尤其当子任务结果存在依赖或冲突时。
OpenCode 的上下文压缩机制也类似:压缩时不随便截断,而是输出固定格式的摘要,包含目标、重要指令、已完成/未完成工作、相关文件列表等,保证每次压缩后关键信息完整且格式统一。
第六层:对抗模式锁定——长步骤下的隐蔽陷阱
这是最容易被忽略的一层。Agent 跑了几十步后,上下文堆满了大量类似的动作和观测结果,模型看多了就开始机械重复,陷入惯性闭环——就像一个员工重复做同样的工作,脑子木了,遇到新情况也不会变通。Manus 在批量审简历时就遇到过:模型不管什么简历都重复「打开文档、提取信息」,遇到异常格式也不调整策略。
自回归模型的模式锁定有其深层机制:自回归语言模型在生成时,每个新 token 的概率分布以所有已生成 token 为条件。当上下文中充斥大量相似的动作-观测对时,模型的条件概率会被历史模式强烈影响,形成「模式吸引子」——即使当前情境需要不同策略,模型也倾向于重复已有模式。这与心理学中的「功能固着」(Functional Fixedness)高度类似。从信息论角度看,长上下文中重复模式降低了新 token 的信息熵,使模型难以「跳出」当前模式。

这个问题很隐蔽,因为模型不报错,而是「太过听话」地服从过去的模式。解决有两种思路:
- 微小变异:Schema 不变,但序列化方式稍微变化(字段顺序随机调整、描述措辞替换),打破模型的自回归惯性;
- 上下文重置:定期做上下文压缩或在关键节点做 Context Reset,比如每执行 10 步就做一次摘要,只保留最关键信息。上下文压缩和周期性重置本质上是通过减少历史偏置来恢复模型的探索能力。
两个常被忽视的工程细节
其一,Schema 里的字段描述是写给模型的指令,不是写给人看的文档。 写「rating:整数」和写「评分,1 表示非常差,5 表示非常好,只根据内容质量打分」效果天差地别。好的描述就像给模型的「上岗培训」,越清楚干活越靠谱。
其二,推理和输出要分阶段。 不要一开始就加死约束,先让模型自由思考,只在最后输出时施加结构化约束。Claude 已经做到思考过程无限制、只有最终输出结构化。更好的实践是:第一轮让模型自由推理,第二轮再把思考过程作为输入要求结构化输出——虽然多一次调用,但质量提升明显。
警惕过度约束:别把架构焊死
Manus 创始人 Peak 提出了一个重要提醒:我们加的所有结构化约束,当下确实能提升稳定性,但模型在飞速进步,今天必要的约束,明天可能反而成为瓶颈。
判断方法很简单:在不同能力等级的模型上跑同一套 Agent 评估。如果换了更强的模型性能却没明显提升,说明你的控制机制可能正在拖累模型。GPT-3.5 需要严格约束,但 GPT-4 可能不需要那么多限制就能输出得很好。因此建议每半年或每次升级模型时重新评估,在安全性与性能之间寻找新的平衡点。
小结
这六层防线——约束解码、验证重试、工具调用伪装、Logit Masking、Schema 契约、对抗模式锁定——构成了一套完整的工程思维框架:从硬约束到软校验,从单一模块到多模块协作,再到长期运行的稳定性。它不仅是面试加分项,更是设计任何依赖大模型的生产级系统时可复用的实践参考。
相关推荐

沃尔沃XC40插混版回归:传感器升级+Gemini AI加持
沃尔沃XC40 PHEV插电式混动版时隔三年重返市场,带来全新外观设计、升级传感器套件及谷歌Gemini AI车机系统。了解这款车型的核心升级亮点、插混回归的市场逻辑及生成式AI进入座舱的深远意义。

暴雪工会赢得历史性合同:游戏业劳工运动迎来转折点
暴雪娱乐员工成功签订历史性工会合同,成为游戏行业劳工运动的里程碑事件。本文深入分析游戏业长期缺乏工会的结构性原因、微软收购后的态度转变,以及这一先例对整个科技和游戏行业劳工权益的深远影响。

AI产品发布新范式:团队心血与用户社区的双向奔赴
探析AI产品发布中情感叙事与社区驱动增长的新趋势。从一条引发行业关注的推文出发,解读AI团队如何通过真诚投入、开放试用和社区建设,实现产品与用户的双向奔赴,构筑长期竞争壁垒。