Agent 经常在输出标准 JSON 的同时,自作聪明地加上一句“好的,以下是您需要的 JSON 数据:”,导致后续代码的 JSON.parse() 直接崩溃。
在工业界,大模型的输出具有概率性,单纯依赖提示词(Prompt)“严禁输出多余废话”是不可靠的。为了保证系统的高可用性,业界已经沉淀了几套成熟的防范和兜底方案。
按控制层级从上到下,通常有以下四种成熟方案:
1. 模型 API 原生层:Structured Outputs (结构化输出) 与 JSON Mode
这是目前商业化大模型(如 Gemini, OpenAI, Anthropic)主推的最强力方案。不再需要靠提示词去“求”模型,而是直接在 API 请求体中定下死规矩。
- JSON Mode (JSON 模式):在 API 参数中设置
response_format: { "type": "json_object" }。这会强制模型输出合法的 JSON 字符串,极大减少了输出前后的废话。 - Structured Outputs (严格结构化输出):比 JSON Mode 更进一步。可以直接把一个 JSON Schema 传给大模型 API,底层推理引擎会保证输出的 JSON 100% 符合你提供的字段名和数据类型,连多一个字段或少一个字段都不行。
- Function Calling 变相使用:把你想让模型输出的格式定义成一个 Function 的参数。模型为了调用这个 Function,必须严格按照参数结构返回 JSON。这在工业界被广泛用作强制格式化的巧妙手段。
2. 工程框架层:校验、解析与自动重试 (Self-Correction Loop)
即使模型尽力了,也可能出错。工业界绝对不会直接信任模型的输出,而是会引入一层工程化的“拦截器”。目前主流框架(如 LangChain, LlamaIndex, Spring AI)都内置了这种机制。
- Pydantic / Zod 数据验证:在 Python 端使用 Pydantic,或在 Node.js 端使用 Zod 定义数据模型。拿到 LLM 的输出后,立刻扔进验证器。
- 自动纠错循环 (Retry Mechanism):如果验证失败(比如缺少某个必填字段,或者格式不合法),代码拦截这个报错信息,并将报错信息作为新的上下文再次发给大模型,对它说:“你的输出不合法,报错信息是 xxx,请修复它并重新输出。” 通常重试 1-3 次,成功率能达到 99% 以上。
3. 提示词工程层:Few-Shot (少样本) 与 XML 标签隔离
如果在无法使用特殊 API 参数的场景下(比如使用较老或较弱的模型),提示词工程依然是第一道防线。
- 提供 Few-Shot 示例:不要只说“请输出 JSON”,直接给它 2 到 3 个输入输出的完整对照例子。大模型是极佳的“模仿者”,看到例子后,偏离格式的概率会骤降。
- 使用 XML 标签包裹:不要指望模型完全不说废话。你可以要求模型把关键数据放在特定的标签里。例如:“请将你的分析过程写在
<think></think>标签内,然后将最终的 JSON 结果严格放在<result></result>标签内。”- 在代码端,不要用原生的 JSON 解析,而是先用正则表达式(Regex)提取
<result>标签中间的内容,再进行解析。这种方法非常鲁棒,是 Anthropic (Claude) 极其推荐的最佳实践。
- 在代码端,不要用原生的 JSON 解析,而是先用正则表达式(Regex)提取
4. 推理引擎层:Constrained Decoding (约束解码)
这是开源模型/私有化部署场景下的终极必杀技。如果你自己部署模型(比如 Llama 3, Qwen 等),可以通过控制底层的生成逻辑,从物理上杜绝格式错误。
- 工作原理:大模型生成文本时,是在词表中计算每个词的概率(Logits)。约束解码工具(如 Outlines, Guidance, 或 Llama.cpp 的 Grammar 机制)会在模型预测下一个词时,强制把不符合语法规则的词的概率强行降为 0。
- 效果:如果你要求它生成 JSON,当它生成了
{ "name": "之后,引擎会强制下一个字符只能是合法的内容,它在物理层面上根本“发不出”除了 JSON 结构之外的任何声音。
工业界最佳实践组合
通常我们不会只用一种,而是组合拳: 模型原生的 Structured Outputs / Function Calling + 代码层的 Pydantic 校验与重试兜底。