LLM 结构化输出全解:从 Prompt 约束到 Schema 硬保证,三层实现怎么选

LLM 结构化输出全解:从 Prompt 约束到 Schema 硬保证,三层实现怎么选

LLM 系列又一篇。Function Calling 篇讲过「模型输出结构化请求」的协议细节------但那个循环里有个前提没展开:模型输出本来就是不可靠的散文,怎么让它变成代码能直接用的数据? 这篇讲透结构化输出。

一、为什么结构化输出是刚需

Agent 的每次 LLM 调用,下游接的都是代码:分类结果要 if-else、抽取结果要入库、工具参数要执行。而模型的本能是写散文------「分类结果:这可能是一个 Bug,因为......」------下游代码直接崩。

工程化的 LLM 应用,90% 的输出都应该是结构化的。 结构化输出的可靠度分三层,从软到硬,失败率天差地别:

层 手段 合法 JSON 率 字段符合 Schema
层 1 Prompt 约束 ~95% ~85%
层 2 JSON mode ~100% ~90%
层 3 Schema 约束生成 ~100% ~100%

(数字是量级示意,具体看模型------但顺序是稳定的:约束越靠底层,保证越强。)

二、层 1:Prompt 约束(软约束)

在 System Prompt 里定义格式 + few-shot 示范------零 API 依赖,所有模型都支持:

python 复制代码
messages = [
    {"role": "system", "content":
     "分析用户反馈,只返回 JSON,不要任何其他文字。\n"
     '格式:{"category": "Bug|需求|咨询", "severity": 1-5, "summary": "一句话"}\n'
     '示例输入「登录按钮点不动」→ 输出 {"category": "Bug", "severity": 3, "summary": "登录按钮失效"}'},
    {"role": "user", "content": feedback},
]

问题:模型偶尔会「加戏」------前后包 ```json 代码块、加一句解释、字段名擅自改。解析失败率几个百分点,量大会很难受。

什么时候用:原型、低频调用、下游有人工兜底。生产高频场景别停在这一层。

三、层 2:JSON mode(API 层保证合法 JSON)

主流 API 都有 JSON 模式:保证返回合法 JSON(不会包代码块、不会夹散文),但不保证字段符合你的 Schema:

python 复制代码
resp = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    response_format={"type": "json_object"},   # 合法 JSON 硬保证
)
data = json.loads(resp.choices[0].message.content)  # 不会解析失败

JSON mode 的实现原理是约束采样:解码时只允许产生合法 JSON 的 token------这是解码层的硬保证,不是 prompt 恳求。

残留问题 :category 可能返回一个 Schema 里没有的值(「问题反馈」?),severity 可能是字符串不是数字。合法 JSON ≠ 符合 Schema。

四、层 3:Schema 约束生成(硬保证)

把完整 Schema 交给 API,解码层直接禁止生成 Schema 之外的 token:

python 复制代码
resp = client.chat.completions.create(
    model=MODEL,
    messages=messages,
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "feedback_analysis",
            "strict": True,      # 硬保证:字段、类型、枚举全锁定
            "schema": {
                "type": "object",
                "properties": {
                    "category": {"type": "string", "enum": ["Bug", "需求", "咨询"]},
                    "severity": {"type": "integer", "minimum": 1, "maximum": 5},
                    "summary": {"type": "string"},
                },
                "required": ["category", "severity", "summary"],
                "additionalProperties": False,
            },
        },
    },
)
data = resp.choices[0].message.content   # 100% 符合 Schema,直接用

strict 模式下枚举就是真枚举、类型就是真类型 ------「category 返回了 Schema 外的值」这种失败模式直接消失。Function Calling 篇的工具参数(input_schema)就是同一套机制:工具参数是最重要的结构化输出场景。

注意代价:strict Schema 会限制模型自由度,Schema 写太死(枚举太少、字段太碎)会挤歪模型的判断空间------Schema 设计本身是提示词工程的一部分。

五、兜底回路:Pydantic 校验 + 失败重试

即使层 3,也要留一道校验兜底(语义校验是 Schema 管不了的:severity=3 但 summary 说「很严重」,语法对语义错):

python 复制代码
from pydantic import BaseModel, field_validator

class Feedback(BaseModel):
    category: str
    severity: int
    summary: str

    @field_validator("category")
    @classmethod
    def check_category(cls, v):
        if v not in {"Bug", "需求", "咨询"}:
            raise ValueError(f"非法类别: {v}")
        return v

def extract(feedback: str, max_retries: int = 2) -> Feedback:
    last_err = None
    for _ in range(max_retries + 1):
        resp = call_llm(feedback)               # 任意层的结构化输出
        try:
            return Feedback.model_validate_json(resp)
        except Exception as e:
            last_err = e                        # 把校验错误回喂模型,让它自我修正
    raise RuntimeError(f"结构化输出失败: {last_err}")

校验失败把错误信息回喂重试------和 Function Calling 篇的「错误当 tool_result 回填」同一个原理:模型拿到错误描述会自我修正,两轮重试后失败率趋近于零。

六、三层怎么选

场景 用哪层 理由
Agent 工具调用 FC 的 Schema(层 3) 参数直接执行,必须硬保证
抽取入库、分类打标 层 3 或 层 2 + Pydantic 量大,人工兜不起
原型验证、低频任务 层 1 零依赖,先跑通
模型不支持 JSON mode 层 1 + Pydantic 重试 软约束 + 兜底回路

一句话:能上硬保证就上硬保证,上了硬保证也要留 Pydantic 兜底------语义错误是任何解码层约束都拦不住的。

七、踩坑提醒

  1. 别在 prompt 里写「请返回 JSON」就完事:软约束的失败率是按百分比算的,生产量级下每天都会崩几次------至少上 JSON mode
  2. Schema 别设计太碎:二十个嵌套字段会把模型挤得顾此失彼------扁平、少字段、枚举适度
  3. 温度对结构化输出仍然有效:分类任务低温更稳,别因为「反正有 Schema 兜底」就放任高温
  4. 校验错误信息要具体:回喂「校验失败」模型修不好,回喂「category 收到「问题反馈」,只允许 Bug/需求/咨询」才能自我修正

总结

层 手段 一句话
层 1 Prompt 约束 零依赖软保证,原型用
层 2 JSON mode 合法 JSON 硬保证,字段仍可能歪
层 3 Schema 约束生成 字段类型枚举全锁定,生产首选
兜底 Pydantic + 错误回喂重试 语义错误最后防线,两轮重试归零

结合系列:Function Calling 的工具参数、Jev 的 Choice/Score、评测篇的判分------全是结构化输出的应用场景。Agent 工程化的一半工作,就是把模型的散文变成代码能信的数据。觉得有用点个关注。

相关推荐
七牛开发者3 小时前
从 GLM 到 GPT,HarnessPro 如何用 RSI 降低 Agent 成本
人工智能·gpt·agent·harness·rsi
hpoenixf5 小时前
局部改善不等于整体进步:怎样判断 Agent 真的变好了?
agent
dawnsky.liu6 小时前
在 RHEL 中使用 goose 实现远程主机管理
linux·运维·ai
在世修行6 小时前
智学文枢:变式题生成引擎
python·ai·pyside·题目生成
Industio_触觉智能6 小时前
把NPU检测直接嵌进采集终端|RK3576 AI采集终端全链路拆解教程-下
嵌入式硬件·ai·硬件架构·npu·ai视觉·rk3576·采集终端
岁月宁静8 小时前
如何设计支持 Skills 的 Agent?一套可扩展的四层架构
python·agent
岁月宁静8 小时前
CrewAI 快速上手:10 分钟搭建你的第一个 Agent 团队
python·agent
染指11109 小时前
158.Agent-LangGraph-子图(共享状态和不共享状态模式)
开发语言·人工智能·python·ui·agent·agents
汤姆宝库9 小时前
安全深拆:越狱物理、注入攻击与纵深防御
人工智能·安全·机器学习·ai