本文来自《AI 应用开发课程》月份 1 课程文档,已整理为网站文章版本。
学习目标
学完本节后,你应当能够:
- 理解为什么 AI 应用不能只依赖自由文本输出。
- 用 Schema 思维约束模型返回结构。
- 实现月份 1 的最小结构化输出流程。
- 知道模型输出之后程序还需要做什么校验。
前置知识
- 已能发出普通聊天请求
- 已理解 JSON 和 Pydantic
1. 为什么要结构化输出
自由文本适合阅读,但不适合程序继续处理。你后面会经常需要模型返回:
- 标题
- 摘要
- 优先级
- 操作步骤
- 工具参数
如果模型只返回一段散文,程序很难稳定提取。
2. 最简单的结构化目标
例如让模型返回:
{
"summary": "一句话总结",
"priority": "high",
"action_items": ["事项1", "事项2"]
}
3. 结构化输出不是“相信模型”,而是“三层约束”
第一层:Prompt 约束
明确告诉模型必须按 JSON 返回。
第二层:Schema 约束
用明确字段结构告诉模型应该返回什么。
第三层:程序校验
即使模型返回了 JSON,也要用 Pydantic 或解析器校验。
4. 最小示例
先定义 Pydantic 模型:
from pydantic import BaseModel
class TaskAnalysis(BaseModel):
summary: str
priority: str
action_items: list[str]
再提示模型:
你是研发助手。请将结果严格输出为 JSON,字段必须包含:
- summary: 字符串
- priority: 字符串,只能是 low / medium / high
- action_items: 字符串数组
5. 程序侧解析
import json
from pydantic import ValidationError
def parse_task_analysis(raw_text: str) -> TaskAnalysis:
data = json.loads(raw_text)
return TaskAnalysis.model_validate(data)
6. 为什么这里仍然可能失败
因为模型可能:
- 返回了非 JSON 文本
- 多输出解释性语句
- 字段缺失
- 字段类型错误
所以结构化输出的关键不是“让模型尽量对”,而是“当模型不对时程序也能清楚处理”。
7. Schema 的思维方式
当你设计结构化输出时,先回答 4 个问题:
- 我后续程序真正需要哪些字段?
- 哪些字段是必填?
- 哪些字段有枚举范围?
- 哪些字段以后可能扩展?
月份 1 不需要追求非常复杂的 Schema,但一定要把“程序真正需要的字段”先想清楚。
8. 实操任务
- 定义一个
TaskAnalysis模型 - 用模型分析一段需求描述
- 让模型返回 JSON
- 用 Pydantic 验证结果
- 故意让模型漏掉字段,观察程序报错
9. 自测题
- 为什么结构化输出比自由文本更适合工程系统?
- 为什么不能只做 Prompt 约束而不做程序校验?
- 什么叫“面向后续程序消费”设计字段?
10. 作业与验收
作业:
- 在
prompt_lab中实现structured_generate()的最小版本。
验收标准:
- 能成功返回并解析结构化结果
- 失败时有清晰报错
- 你能解释模型约束和程序校验分别负责什么
11. 常见错误
- 字段设计过多,导致模型不稳定
- 输出结构和后续程序需求不一致
- 解析失败后继续当成功结果使用
12. 本章与前文关系
上一章解决了“输入如何组织”,这一章解决“输出如何落地”。这是月份 1 的第一条真正工程分水岭:
- 在普通聊天视角里,模型返回一段文本就够了
- 在应用开发视角里,模型输出往往要继续被程序消费
因此,本章的核心不是“怎么让模型说得更好”,而是“怎么让结果更可处理、更可验证”。
13. 本章在研发助手项目中的位置
研发助手项目里的很多任务,本质都更适合结构化输出,而不是自由文本:
- 抽取待办项
- 生成 commit message 草稿
- 总结错误日志时拆出原因、影响、建议动作
- 为后续工具调用准备参数
所以本章不是附加技巧,而是项目可用性的核心部分。
14. 为什么结构化输出是月份 1 的硬能力
因为你后面会持续面对这个问题:
模型生成的内容,到底是给人看的,还是给程序继续处理的?
如果答案是“给程序继续处理”,那你就必须:
- 定义结构
- 控制输出
- 校验结果
- 处理失败
这就是结构化输出的完整闭环。
15. 错误示例 vs 正确示例
错误示例:只告诉模型“请输出 JSON”,却不定义字段
请把结果输出成 JSON。
这类 Prompt 太模糊,模型可能:
- 字段名不一致
- 多写解释文字
- 结构层级不稳定
正确示例:输出目标和字段边界明确
你是研发助手。请将结果严格输出为 JSON,字段必须包含:
- summary: 字符串
- priority: 只能是 low / medium / high
- action_items: 字符串数组
不要输出任何 JSON 之外的解释文字。
这仍然不保证 100% 成功,但会显著提高结构稳定性。
16. 完整文件级示例:structured_output_demo.py
"""演示结构化输出的最小闭环。
包含:
1. 定义输出模型
2. 调用模型
3. 解析 JSON
4. 用 Pydantic 校验
"""
from __future__ import annotations
import json
from typing import Any
from pydantic import BaseModel, ValidationError
class TaskAnalysis(BaseModel):
"""研发任务分析结果。"""
summary: str
priority: str
action_items: list[str]
def build_structured_prompt(task_text: str) -> str:
"""构造最小结构化输出提示词。"""
return f"""
你是研发助手。
请分析下面的任务描述,并严格输出 JSON。
输出字段要求:
- summary: 字符串,一句话总结任务
- priority: 只能是 low / medium / high
- action_items: 字符串数组,列出 2 到 5 个待办项
任务描述:
{task_text}
""".strip()
def parse_task_analysis(raw_text: str) -> TaskAnalysis:
"""先解析 JSON,再做 Pydantic 校验。"""
data: dict[str, Any] = json.loads(raw_text)
return TaskAnalysis.model_validate(data)
def safe_parse_task_analysis(raw_text: str) -> TaskAnalysis | None:
"""提供一个最小 fallback 入口。
月份 1 阶段先返回 None,后面你可以继续扩成日志记录、重试或修复逻辑。
"""
try:
return parse_task_analysis(raw_text)
except json.JSONDecodeError:
print("模型输出不是合法 JSON")
return None
except ValidationError as error:
print(f"结构校验失败: {error}")
return None
17. 逐段解释这份完整示例
为什么输出模型先定义在程序侧
因为程序需要先知道“自己真正想要什么结构”,再去约束模型。如果程序自己都没有明确定义字段,就不可能稳定得到结果。
为什么解析分成 parse 和 safe_parse
这是为了分离:
- 纯解析逻辑
- 带错误处理的应用逻辑
这种分层在后面写 service 层时会很常见。
为什么 fallback 先只返回 None
因为月份 1 目标是先让你看清失败类型,而不是一开始就把所有恢复机制做得很重。
18. 结构化输出为什么会失败
结构化输出失败通常不是单一原因,而是至少有三类失败源:
第一类:Prompt 不清楚
模型根本不知道你想要哪些字段。
第二类:模型输出偏离
模型多写了解释、少了字段、或者把类型写错。
第三类:程序校验不完整
你只做了 json.loads,却没有进一步校验字段结构。
所以课程强调“结构化输出 = Prompt + Schema + 校验”,三者缺一不可。
19. 一个更接近工程的增强版思路
你可以继续做下面两步增强:
- 为
priority建立更严格的枚举约束 - 在结构化失败时,把原始输出写入日志,方便后续复盘
例如:
from typing import Literal
Priority = Literal["low", "medium", "high"]
这能让结果更清晰,也更贴近后续系统使用。
20. 调试与排错:本章最值得刻意练习的失败案例
案例一:模型输出前后包了说明文字
这会导致 json.loads 直接失败。
案例二:字段缺失
例如少了 action_items,这会在 Pydantic 校验时暴露。
案例三:类型错误
例如 action_items 变成一整个字符串,而不是字符串数组。
这些失败不是坏事,恰恰是你建立稳定结构化输出思维的入口。
21. 本章完成后你应该具备的能力
完成本章后,你应当做到:
- 能定义一个程序真正需要的输出结构。
- 能为该结构写出约束 Prompt。
- 能完成
json.loads + Pydantic校验闭环。 - 能解释结构化输出失败时应该看哪一层。
22. 如果你卡在这里,先回看哪几章
- JSON 读写和异常不稳:回看 04-异常-文件-JSON.md
- Pydantic 模型不稳:回看 03-pydantic与配置建模.md
23. 从本章过渡到下一章的桥接说明
接下来进入 04-流式输出与成本统计.md。
到这里你已经掌握:
- 普通文本输出
- 结构化输出
下一步要处理的是“输出是如何到达用户的”以及“每次调用要记录什么”。这会把模型调用从“能用”进一步推向“可观察、可调试、可持续开发”。