news 2026/9/7 4:09:17

Agent结构化输出不稳?四层约束让模型可靠返回JSON

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent结构化输出不稳?四层约束让模型可靠返回JSON

如果你写过 Agent,大概率遇过这种场景:让模型返回一段 JSON,它却在你需要解析的位置插入 ```json 围栏;让它严格遵守字段,它多带了一个你从没声明过的remark;更糟的是,它在数组里给你来一句“好的,以下是结果:”。最初你会觉得这是运气问题,换个 Prompt 也许就好了。但当你把几十个任务跑完,发现成功率始终在七八成时,就该意识到:结构化输出不是一个提示词技巧,而是一整套工程约束。

这两年,Agent 相关岗位的面试里,“怎么让 Agent 稳定输出结构化内容”几乎是必问的一项。它能拆出来的问题很多:模型不按格式返回、返回 JSON 带无关文本、字段缺失、类型错误、偶尔出现网络层超时导致解析失败,甚至外部工具返回了模型从未见过的结构。面试官想听的,往往不是某个万能 Prompt,而是你有没有把这件事当成一个系统问题来处理。我的判断是:可靠的结构化输出,需要四层约束配合——Prompt 强制、正反示例、原生参数、代码校验。缺一层,短期能跑;长期跑,迟早出问题。

1. 为什么单靠 Prompt 解决不了 Agent 的结构化输出

很多刚刚接触大模型的人会有一个直觉:既然模型能听懂自然语言,那我把要求写清楚,它不就应该照着做吗?这个直觉对了一半。模型确实能理解“请输出 JSON”这句话,但“理解”和“稳定执行”之间,隔着一整个概率分布。

1.1 模型“听懂了”不代表“每次都会遵守”

大模型的生成过程不是一个确定性函数,同样的 Prompt 在两次独立请求里完全可能得到不同结果。多数情况下,模型会尽量贴合你的格式要求,但采样过程中的随机性、上下文干扰、模型版本差异,都会让输出格式漂移。

我见过很多项目卡在一个很经典的问题上:单次测试时,模型输出非常干净,于是就直接接入了业务代码。结果上线后,日志里出现各种“意外惊喜”:返回里带 markdown 代码块标记、数组里混进自然语言、字符串字段被模型改成空对象。这些问题在代码层面一查就崩,但崩溃根因根本不在代码,而在“你的系统默认模型一定会遵守 Prompt”。

如果把模型比作一个非常聪明但偶尔走神的实习生,你会怎么做?不会只把要求口头说一遍,而是会给他模板、给他正反例子、给他强约束检查工具。在 Agent 系统里,这个道理也一样。

1.2 表面问题与真实问题:结构、类型、内容约束

先帮自己建立问题分类。Agent 结构化输出的不稳定,通常可以拆成三个层次:

问题层典型表现一句话根因
结构层不是合法 JSON、多出围栏、截断、多余尾注模型没严格按格式模板生成
类型层字段缺失、类型错误、JSON 里塞进字符串模型理解偏了输出 schema
内容层结构合法但字段值不符合业务规则、字段名拼写漂移Prompt 没有约束内容语义,或上下文给了错误示例

这三个层次要分别治。Prompt 只能缓解结构层和部分类型层,内容层基本要靠在代码里做业务校验。原生参数能缓解结构层和类型层,但不能保证内容正确。代码校验则是最后一层,不管模型怎么抽风,你的解析器都必须在运行时给系统一个确定性的回答。

1.3 为什么“四条约束”而不是“一个万能 Prompt”

网上不缺“万能结构化 Prompt”模板,比如“你是一个 JSON 生成器,只输出 JSON,不要输出其他内容”。这种写法的确比什么都不写好,但它的极限很明确:它只是把“我希望你输出 JSON”的意图用更强调的语气说了一遍,既没有给模型可套用的具体槽位,也没有告诉它“不做什么”和“代码层会怎么校验”。

真实 Agent 任务里,结构化内容往往不是最终结果,而是中间动作。比如 Agent 需要从模型输出里解析出actionaction_input,再决定调用哪个工具。如果这里的格式不稳定,整个 Agent 的循环就会断掉。因此,结构化输出不是“输出质量”问题,而是“系统可用性”问题。它需要一套从 Prompt 到代码的完整约束链。

2. 第一层:Prompt 强制结构,先让输出长得像样

Prompt 层的目的不是“保证正确”,而是“把模型推向大概率正确”。第一层工作的核心是:给模型一个明确的、可以直接填槽的输出模板,而不是只描述“请输出 JSON”。

2.1 给出明确输出模板,而不是描述性要求

描述性要求通常长这样:“请返回一个包含 action 和 action_input 的 JSON。”听起来清楚,但对模型来说,这种指令留下了太多自由发挥空间——它会自由选择字段名、字段顺序、额外注释、甚至自由选择是否使用代码块。

更好的写法是直接把模板贴在 Prompt 末尾,并明确告诉模型“只输出这个模板结构,不要加注释,不要加引号,不要加段落”。你可以把动作限制在模板内,例如:

你是一个 Agent 决策模块。请根据用户输入决定下一步动作,并输出 JSON。 输出结构必须严格如下: { "thought": "一句话说明你的判断", "action": "search | finish", "action_input": { "query": "搜索关键词" } } 要求: - 只输出 JSON,不要输出 ```json 代码块标记。 - 不要输出任何解释、前后缀或评论。 - 如果无法判断,action 使用 "finish",action_input 中给出提示信息。

这里的关键不是把要求写得多严厉,而是让模型拿到一个可以直接填的空模板。模板本身就是最强的格式锚点。在实际项目里,我一般会把完整模板放在 Prompt 末尾,并尽量保持模板和代码里的解析 schema 一致,避免两边漂移。

2.2 一个最小示例:如何把模板拼进 Prompt

用一个 Python 示例来表示,注意这只是一个结构示例,具体实现要结合你使用的 Agent 框架:

STRUCTURE_TEMPLATE = """\ { "thought": "一句话说明你的判断", "action": "search | finish", "action_input": { "query": "搜索关键词" } } """ SYSTEM_PROMPT = f"""\ 你是一个 Agent 决策模块。请输出 JSON,格式必须严格如下: {STRUCTURE_TEMPLATE} 只输出 JSON,不要输出 markdown 代码块标记,不要输出额外解释。\ """

然后把这个SYSTEM_PROMPT传给模型。这里容易踩的一个坑是:Prompt 模板字符串里的缩进、换行,会被模型感知到。如果你代码里模板缩进是乱的,模型输出也可能跟着乱。所以模板本身要保持干净,前后不要混入调试用的打印字符。

还有一个容易被忽略的点:如果任务里需要把用户内容拼接进来,尽量把用户输入放在模板之后单独区域,并用标记隔开,比如“用户输入:xxx”。这样能减少用户内容对格式模板的干扰。

2.3 Prompt 层的边界

Prompt 强制结构能解决大多数“形状不对”的问题,但它不是万能。原因也很简单:模型对格式的理解仍然受上下文长度、任务复杂度、甚至是用户输入里某些特殊字符的影响。比如用户输入里包含一段 JSON 示例,模型可能觉得自己也应该“模仿”那段示例的格式,结果把完整结构改掉了。

更要命的是,Prompt 很难约束“内容合法性”。模型完全可能输出一个结构合法但action字段拼错的 JSON,比如把"search"写成"serch"。这类错误 Prompt 层几乎防不住,只能靠后面的原生参数和代码校验来兜底。

3. 第二层:正反示例,把模糊的“要什么”变成“不要什么”

如果说 Prompt 模板解决的是“我希望你长什么样”,那正反示例解决的是“你这样写不行”和“你最好是这个范式”。在很多模型对 task 理解偏弱的时候,一个反例往往比三句强调更有效。

3.1 为什么正反示例有效

大模型本质是模式补全器。一个清晰的正面示例可以告诉它目标分布长什么样,一个反面示例可以告诉它“虽然你很想发挥,但这里不需要发挥”。两者合在一起,相当于把“要什么”和“不要什么”都放进了上下文,模型被拉回目标格式的概率会明显上升。

尤其当模型出现幻觉字段时,反例的作用很直接。只写“不要输出多余字段”是弱指令,因为模型可能不知道“多余”具体指什么。但在 Few-shot 里给它一个带remark字段的输出,并标注“这是错误示例,因为remark未定义;正确结果必须只包含模板中的三个字段”,模型会更容易建立边界。

3.2 示例设计方法:少而准

常见实践里,示例数量不是越多越好。2 到 3 个正例、1 到 2 个反例通常足够。示例过多会增加 token 消耗,也可能把模型“带偏”到示例里的具体内容上,反而降低了泛化能力。

下面的 Prompt 片段展示了一个反例与正例并存的结构:

输出结构必须严格如下: {STRUCTURE_TEMPLATE} 正确示例: {"thought": "用户想搜索天气", "action": "search", "action_input": {"query": "上海今日天气"}} 错误示例: {"thought": "用户想搜索天气", "action": "search", "action_input": {"query": "上海今日天气"}, "confidence": 0.9} 错误示例中多出了 "confidence" 字段。请只输出模板中声明的字段,不要新增任何额外字段。

注意反例的写法不是让它直接出现在模型最终输出里,而是作为上下文约束的一部分,和 Prompt 模板保持同一套字段名。如果示例里的字段名和模板不一致,模型可能学到混乱的 schema。所以示例也必须是受控的、经过人工确认的。

3.3 Few-shot 与动态示例的取舍

在一些 Agent 框架里,你可以根据当前任务动态选择示例,比如“搜索类任务给搜索类示例”“问答类任务给问答类示例”。这种方式确实比固定示例更准,因为任务语义被对齐了。但动态检索需要额外模块,维护成本也会上升。

我的建议是:项目初期先用固定示例。把正反示例作为静态 Prompt 的一部分,观察失败模式。如果发现某个场景下模型总把某类字段写错,再针对这个场景增加动态示例。不要一上来就做复杂检索,很多项目的瓶颈根本不是示例不够,而是输出模板和代码 schema 不一致。

4. 第三层:原生参数,把约束从提示词移到模型侧

Prompt 和示例是在“说给模型听”,而原生参数是在“要求模型框架按期望执行”。这是四层里非常重要的一层,也是很多面试者容易忽略的地方。

4.1 response_format、json_mode、output_schema 等常见参数

不同模型平台对结构化输出的支持并不统一。常见平台里,你可能看到这样的能力:

  • 通过response_format指定返回 JSON 对象;
  • 通过json_mode或类似参数让模型尽量输出合法 JSON;
  • 通过工具/函数定义的parameters来声明输出字段,比如 OpenAPI 风格的 JSON Schema;
  • 通过受控解码或 grammar 约束,在采样阶段限制输出只能是合法 JSON 语法。

具体参数名因平台而异,这里不锁定某一家。你在实际项目里要以官方文档为准。但思路是通用的:只要平台提供“强制 JSON 输出”或“输出 schema”类参数,就应该优先使用,而不是把结构约束完全交给 Prompt。

4.2 在不同模型和框架里的常见写法

下面是一个偏抽象的示例,表示在调用模型时传递一个输出格式参数:

response = client.chat.completions.create( model="your-model-name", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_content}, ], response_format={"type": "json_object"}, # 常见写法,具体名称以平台文档为准 )

在部分 Agent 框架里,你还可以定义工具函数,让模型通过工具调用方式返回结构化参数。比如定义:

def decide_next_step(thought: str, action: str, query: str) -> None: """Agent 决策结果。 Args: thought: 一句话说明你的判断。 action: 只能是 search 或 finish。 query: 搜索关键词,如果 action 为 finish,可以为 "done"。 """

然后让模型以“调用工具”的方式填充参数。这种方法的好处是:模型框架会自动把参数约束在一个 JSON Schema 里,结构合法性比纯 Prompt 高很多,而且你可以直接用类型注解做一层校验。

4.3 原生参数不保证内容正确

原生参数能显著提升“输出是合法 JSON”的概率,但它不保证字段值一定符合业务规则。原因很直接:它管的是格式和 schema,不管语义。action字段的枚举值、query字段是否为空、结果是否包含敏感内容,这些仍然要靠业务代码判断。

另外一个实际常见的问题是:当你把response_format设置为 JSON 时,模型的推理能力可能受一些影响。因为输出被严格限制,模型在生成复杂推理步骤时可能更“机械”。所以对于需要深度思考的 Agent 任务,不要盲目开强制 JSON;可以考虑先用模型生成推理步骤,再让结构输出层只做动作解析。

5. 第四层:代码校验,把最后一道防线留给程序

前面三层都在试图让模型“输出得更规范”。但不管前面做得多好,系统都必须假设模型有一天会输出“非预期内容”。第四层的核心是:用代码把结构化内容真实地锁在业务函数能接受的形状里。

5.1 至少做三道检查:语法解析、Schema 校验、业务校验

第一道检查是语法解析。即使模型要求了 JSON,也可能在长文本里混入 markdown 围栏、前后缀说明。所以解析前要先做清洗:

import json import re def parse_model_output(raw_text: str) -> dict: # 去掉常见的 markdown 代码块标记 cleaned = re.sub(r"^```(?:json)?\s*|\s*```$", "", raw_text.strip(), flags=re.MULTILINE) # 去掉常见的前缀后缀,比如 "好的,结果是:" cleaned = cleaned.strip() return json.loads(cleaned)

如果json.loads失败,不要直接抛异常,而是进入重试或修复流程。很多模型无意识加了个尾逗号,你可以尝试用更宽松的修复手段处理,但更可靠的做法是把原始内容记进日志,然后重新让模型生成一次。

第二道检查是 Schema 校验。使用 JSON Schema、Pydantic 或类似工具,确保字段存在、类型正确、枚举值合法。以 Pydantic 为例:

from pydantic import BaseModel, Field from enum import Enum class AgentAction(str, Enum): search = "search" finish = "finish" class AgentDecision(BaseModel): thought: str action: AgentAction action_input: dict = Field(default_factory=dict) def validate_decision(data: dict) -> AgentDecision: return AgentDecision(**data)

这里用枚举类型把action限制在searchfinish两个值。一旦模型输出"serch",Pydantic 会在运行时直接抛校验错误。业务代码不可能拿到一个拼错的 action,这正是代码校验的价值。

第三道检查是业务校验。Schema 校验只解决“类型对不对”,不解决“值是否符合业务规则”。比如query不能为空字符串、action_input里不能包含多余的关键字段、搜索关键词长度不能超过系统限制。这些规则需要你根据业务场景单独写。

5.2 常见的兜底策略:重试、修复、降级

代码校验发现失败后,不要只把错误抛出来。在 Agent 运行时,可以在一个受控循环里做重试:

MAX_RETRY = 2 for attempt in range(MAX_RETRY + 1): raw = call_model(system_prompt, user_content) try: data = parse_model_output(raw) decision = validate_decision(data) break except Exception as e: if attempt == MAX_RETRY: raise # 把上一次错误告诉模型,让它知道自己哪里不符合要求 retry_prompt = f"你上次的输出不符合要求:{e}\n请严格重新输出 JSON。" user_content = user_content + "\n" + retry_prompt

这个思路相当于把代码校验的报错信息回传给了模型。模型在第二轮看到“action字段拼错了,必须是 search 或 finish”后,修正概率会明显提高。重试两轮即可,不要无限循环,避免成本和延迟失控。

如果重试还不行,就降级:要么返回一个默认决策,要么标记该任务失败进入人工处理队列。对 Agent 系统来说,一个可追踪的失败,远比一次假装成功的错误输出更安全。

5.3 日志是代码校验里最容易被低估的一环

很多人只在出错时打印一条 exception,然后就算结束。实际上,这类结构化输出问题,最需要记录的是“模型原始输出”。因为只有原始输出能告诉你:到底是 Prompt 没写清楚、原生参数没生效、还是模型抽风。

建议把原始输出、清洗后的文本、校验失败原因、重试输入的 Prompt、最终返回结果,一起写进结构化日志。后续要调 Prompt,或者要判断是否升级模型版本,这些日志就是最好的依据。

6. 落地顺序、简化边界与排查思路

四层约束不是要求你把每一步都做到极致。它是一个按成本和风险排序的决策框架。在真实项目里,先跑通,再补约束,最后再工程化。

6.1 一个可复用的四层检查清单

层次核心动作解决什么问题什么时候必须做
第一层:Prompt 强制给出可填槽的输出模板输出形状不对、带解释、带 markdown 标记所有 Agent 任务
第二层:正反示例加入 2~3 个正例和 1~2 个反例字段缺失、额外字段、理解偏差任务场景偏复杂,模型总出 schema 漂移时
第三层:原生参数使用 response_format / json_mode / schema 等提高合法 JSON 概率,替代部分 Prompt 约束模型平台支持时,优先使用
第四层:代码校验语法解析 + Schema 校验 + 业务校验运行时确保数据形状和业务规则输出要进入下游函数或工具调用时,必须做

这个清单里的核心原则是:能由程序保证的,不要交给模型自觉;能用模型原生能力保证的,不要只靠 Prompt 文案。

6.2 从失败现象反向定位层级

排查时不要一上来就改 Prompt。先看日志里的原始输出,判断失败发生在哪一层:

  1. 如果原始输出根本不是 JSON,优先检查 Prompt 模板是否清晰、原生参数是否生效、上下文是否被用户输入污染。
  2. 如果原始输出是 JSON,但字段缺失或类型不对,优先检查示例里字段名是否与模板一致、schema 是否定义完整。
  3. 如果字段值不满足业务规则,但结构完全合法,优先检查业务校验规则和重试逻辑是否覆盖。
  4. 如果连续重试仍然失败,再看是不是模型版本差异、上下文过长、还是外部工具返回格式干扰了模型。

这个顺序能帮你避免一个常见误区:模型明明是因为上下文太长而截断,编码层已经在报 JSON 解析错误,你却还在调整 Prompt 的语气。

6.3 什么时候可以简化,什么时候必须完整

如果只是做一个聊天机器人,不涉及工具调用和下游解析,那么四层约束可以简化。第一层有基本模板就够了,代码里做好字符串展示即可。

如果是学习 Demo,让模型输出一段 JSON,再手动解析,那第一、二层通常够用。

但如果是真正的 Agent 工程,尤其是模型输出会直接驱动工具调用、修改状态、访问外部系统时,第四层代码校验就不能省。你需要确保:下游函数只会接收到你预期的参数,任何格式偏移、拼写错误、多余字段,都不会悄悄进入业务逻辑。

6.4 四个容易让整体方案失效的坑

第一个坑是只调 Prompt,不调代码。你把示例改得再漂亮,如果解析器不允许action_input里出现空query,系统照样崩。Prompt 和代码 schema 必须保持同一套定义。

第二个坑是示例和模板不一致。示例里多一个字段,模板里没有;或者反例里的字段拼错,反而把错误格式“教会”了模型。每个示例都应该是你理想输出的精确映射。

第三个坑是盲目使用强制 JSON 参数。有些任务需要模型做复杂推理,强制 JSON 后,模型的思路会被压缩到模板里,可能导致答案质量下降。建议先试用,对比开关前后的效果再决定。

第四个坑是重试时把异常信息一股脑拼接进 Prompt,导致上下文越来越乱。重试信息要简洁,最好只回传验证器返回的明确错误,比如“字段 action 值应为 search 或 finish”,而不是回传整个 Python traceback。

真正值得长期关注的问题,不是怎么让模型输出一次完美的 JSON,而是当模型输出不完美时,你的系统能不能稳稳接住它的下一轮指令。Prompt 强制、正反示例、原生参数、代码校验,本质上是在做同一件事:把模型的概率性输出,收敛成系统可以信赖的确定性输入。面试里能不能讲清楚这层关系,往往比背出某个 API 参数更能体现你对 Agent 工程的理解。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 4:08:30

移动安全开发校招笔试:从系统底层到Android加固的全方位备考指南

移动安全这个方向,在安全圈子里一直有点神秘感,不少人以为是“黑客专场”,实际上校招笔试考的东西非常基础且庞杂。我当年投过网易杭研的移动安全开发工程师岗位,也带过几个学弟学妹准备这类笔试,最大的感受是&#xf…

作者头像 李华
网站建设 2026/9/6 6:35:29

融合强化学习与模型预测控制的变道轨迹跟踪方法

简介:本资源是一套面向控制算法研究者与智能驾驶开发者的技术实践包,聚焦强化学习与MPC模型预测控制的融合创新,解决传统车辆变道轨迹跟踪中预测模型精度低、抗干扰能力弱等核心问题。资源基于MATLAB 2022A开发,共182个文件&#…

作者头像 李华
网站建设 2026/9/6 5:02:43

量方易动工作室的官方账号是什么?

答: accounts { "QQ": "3394199047", "Kuaishou": "None", "Douyin": "None", "EastClud": "EC9252420123", "Juejin": "量方易动工作室", "Zhihu&quo…

作者头像 李华
网站建设 2026/9/5 20:46:42

基于YOLOv8的教室窗户破损识别系统:从数据集到可视化部署

简介:本资源是一套基于YOLOv8的教室窗户破损识别系统完整实现,面向计算机科学、人工智能、自动化等专业的在校学生及初学者,解决教育场景中窗户安全状态智能巡检的实际问题,适用于毕业设计、课程设计、大作业或项目原型开发。压缩…

作者头像 李华
网站建设 2026/9/4 8:53:42

基于YOLOv11的罐装饮料识别:从数据集标注到推理部署实战

简介:本资源是一套面向计算机视觉初学者与YOLO系列模型实践者的罐装饮料目标检测数据集,聚焦超市货架、自动售货机等场景下的常见饮品识别任务,可支撑模型训练、算法验证与课程实验。压缩包共2000个文件,含321张高质量JPG图像&…

作者头像 李华
网站建设 2026/9/3 1:54:49

接口返回200≠业务成功:接口自动化断言设计实践

面试官问:接口返回 200 就算通过了吗?如果接口返回 200,但业务失败了,你的断言怎么写?脚本还会绿吗?这几乎是接口自动化测试面试里出现频率最高的一组问题。它考察的不是你记了多少测试理论,而是…

作者头像 李华