结构化输出与 Pydantic:把大模型结果变成可靠的数据接口

大模型天然输出文本,业务系统天然需要数据。这两者之间存在一条关键鸿沟:模型说得再流畅,如果程序无法解析、校验、存储和执行,它就只能停留在聊天层面。结构化输出的目标,是把模型生成内容转成可被系统消费的数据。

在信息抽取、意图识别、工具调用、RAG 推荐、多 Agent 路由等场景中,结构化输出不是锦上添花,而是应用能否工程化的基础能力。

1. 为什么自然语言输出不够

假设模型回答:

用户应该是想查询北京明天的天气。

这句话对人很好理解,但程序接下来要怎么做?它需要的是:

{
  "intent": "weather",
  "city": "北京",
  "date": "tomorrow"
}

再比如模型说:

这位候选人大约有五年经验,比较适合。

如果系统要按经验过滤、排序、入库或展示,就需要字段:

{
  "work_experience": 5,
  "matched": true,
  "reason": "具备相关经验"
}

结构化输出的意义,就是让模型从“表达判断”变成“提交数据”。

2. JSON 是事实标准,但不是安全保证

JSON 是大模型应用里最常见的结构化格式,因为它轻量、通用、易解析。Prompt 往往会要求:

请严格输出 JSON,不要添加额外解释。

但真实输出经常出现偏差:

  • 外层包了 Markdown 代码块。
  • JSON 前后加了解释。
  • 字段缺失。
  • 字段类型错误。
  • 枚举值写成近义词。
  • 使用中文引号或尾随逗号。
  • 把整个 JSON Schema 当成结果输出。

因此,JSON 只是目标格式,不是可靠性保证。真正可靠的结构化输出至少需要三层:

Prompt 约束 -> 输出清洗 -> 类型校验

3. Pydantic 的核心作用

Pydantic 的价值不只是“定义一个类”,而是把模型输出纳入类型系统。它能帮助应用确认:字段是否存在、类型是否正确、默认值是否合理、异常能否捕获。

例如动作模型可以这样定义:

from pydantic import BaseModel, Field
from typing import Any

class Action(BaseModel):
    tool_name: str = Field(description="要调用的工具名称")
    args: dict[str, Any] = Field(default_factory=dict, description="工具参数")

模型输出通过 JSON 解析成字典后,再交给 Pydantic:

action = Action(**data)

如果模型把 args 输出成 null、列表或字符串,程序就能在校验阶段发现问题,而不是等到工具执行时报错。

4. 解析容错:不要假设模型总是完美

应用层应预设模型会犯格式错误。常见容错策略包括:

  1. 去掉 Markdown 代码块标记。
  2. 提取最后一个 JSON 片段。
  3. 对常见字段类型做兼容转换。
  4. 对空值设置默认值。
  5. 对无法解析的输出进入 fallback。
  6. 必要时使用二次修复模型重新格式化。

例如,工具参数本来应该是字典,但小模型可能输出:

{
  "tool_name": "Search",
  "args": ["北京天气"]
}

如果业务约定单参数工具使用 query 字段,可以在校验前将其规范化:

{
  "tool_name": "Search",
  "args": {
    "query": "北京天气"
  }
}

这种兼容不是纵容模型乱输出,而是在真实系统中提高韧性。

5. Schema 设计要面向演进

结构化输出一旦进入业务流程,就会形成隐式 API。字段名、字段类型和枚举值后续都不应轻易改动。

设计 Schema 时建议:

  • 字段名使用英文或统一命名风格。
  • 枚举值固定,不要让模型自由创造。
  • 数值字段明确单位和类型。
  • 可选字段允许 null,但语义要清楚。
  • 对无法判断的字段使用统一占位。
  • 复杂对象拆成小结构,避免一次输出过深 JSON。

例如参数抽取可以定义:

{
  "count": 3,
  "gender": null,
  "age_min": null,
  "age_max": null,
  "experience_min": 5,
  "experience_max": null
}

这样下游可以直接构造过滤条件,不需要再从自然语言里二次猜测。

6. 结构化输出与业务决策要分离

模型可以帮助提取字段和生成判断,但最终业务动作应该由程序控制。

例如模型可以输出:

{
  "status": "input_required",
  "message": "请提供具体日期"
}

程序再根据 status 决定是追问用户、调用工具还是结束任务。不要让模型直接决定高风险动作是否执行,尤其是涉及写数据库、下单、删除、发送消息等操作时。

比较稳健的设计是:

模型生成候选决策 -> 程序校验 -> 权限检查 -> 执行动作

模型负责理解,程序负责治理。

7. 结构化输出在 Agent 中的特殊意义

Agent 系统里,结构化输出通常对应“动作”。如果动作格式不稳定,Agent 就无法运行。

常见动作结构包括:

{
  "tool_name": "KnowledgeSearch",
  "args": {
    "query": "用户问题"
  }
}

执行器会根据 tool_name 查找工具,根据 args 调用工具,再把 Observation 放回上下文。这里最怕的是模型输出了不存在的工具、参数缺失或把解释混入 JSON。

因此 Agent 场景下应额外加入:

  • 工具白名单校验。
  • 参数模型校验。
  • 最大执行轮数。
  • 工具失败反馈。
  • 未知动作 fallback。

结构化输出不是为了美观,而是为了让 Agent 循环不断裂。

8. 评估结构化输出质量

结构化输出不能只看几次人工测试。可以从这些指标评估:

  • JSON 可解析率。
  • Schema 校验通过率。
  • 必填字段缺失率。
  • 枚举值非法率。
  • 业务执行成功率。
  • fallback 触发率。
  • 修复后二次成功率。

这些指标能帮助判断问题出在 Prompt、模型能力、Schema 复杂度还是后处理逻辑。

9. 小结

结构化输出是大模型应用走向工程系统的分水岭。它把不稳定的自然语言结果变成可验证的数据接口。

完整闭环应是:

定义 Schema
  -> Prompt 注入格式要求
  -> 模型生成
  -> 清洗与提取
  -> JSON 解析
  -> Pydantic 校验
  -> 业务决策
  -> 记录失败样例继续优化

当这个闭环建立起来,大模型就不再只是“会说”,而是能稳定参与业务系统的读取、判断、调用和交付。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐