☰
Structured Output 学习笔记
2026/10/19 5:18:31 网站建设 项目流程

Structured Output 学习笔记

1. 本章目标

这一章解决的问题是:

如何让 LLM 不只是返回自然语言,而是稳定返回程序可以直接解析和使用的数据结构。

普通 LLM:

User ↓ LLM ↓ 自然语言

Structured Output:

User ↓ LLM ↓ JSON / Schema ↓ Parser ↓ Pydantic Validation ↓ Python Object ↓ 业务逻辑

2. 为什么需要 Structured Output

例如模型返回:

I recommend AI Application Engineer. You should learn Python, FastAPI and RAG. The match score is about 87%.

人可以理解,但程序不好稳定处理。

更希望得到:

{"job":"AI Application Engineer","skills":["Python","FastAPI","RAG"],"match_score":0.87}

这样后端可以直接:

result.job result.skills result.match_score

Structured Output 的本质是:

把 LLM 从“文本生成器”变成软件系统中的数据生成组件。


3. Pydantic Model = 输出数据契约

例如:

frompydanticimportBaseModel,FieldclassJobRecommendation(BaseModel):job:strskills:list[str]match_score:float=Field(ge=0.0,le=1.0)

这里规定:

job → str skills → list[str] match_score → float → 0 <= score <= 1

它就是一个 Schema / Data Contract。


4. JSON String → Python Dict

模型通常先返回字符串:

answer=""" { "job": "AI Engineer", "skills": ["Python", "RAG"], "match_score": 0.9 } """

使用:

json.loads(answer)

完成:

JSON String ↓ Python Object

例如:

data=json.loads(answer)

得到:

{"job":"AI Engineer","skills":["Python","RAG"],"match_score":0.9}

5.**dict解包

学习了:

JobRecommendation(**data)

如果:

data={"job":"AI Engineer","skills":["Python"],"match_score":0.9}

则:

JobRecommendation(**data)

相当于:

JobRecommendation(job="AI Engineer",skills=["Python"],match_score=0.9)

所以:

*list → 解包 iterable **dict → 解包 keyword arguments

6. Structured Output 的两层基础验证

第一层:

json.loads(answer)

负责:

JSON Syntax 是否正确。

如果失败:

json.JSONDecodeError

第二层:

JobRecommendation(...)

或者:

JobRecommendation.model_validate(data)

负责:

JSON 的 Schema / 类型 / 约束是否正确。

如果失败:

ValidationError

因此:

LLM Answer ↓ json.loads() ↓ JSON Syntax Validation ↓ Pydantic ↓ Schema Validation

7. 自定义异常

定义:

classStructuredOutputError(Exception):pass

然后把底层异常统一抽象:

defparse_job_recommendation(answer:str)->JobRecommendation:try:returnJobRecommendation(**json.loads(answer))exceptjson.JSONDecodeErrorase:raiseStructuredOutputError(f"LLM returned invalid JSON:{e}")fromeexceptValidationErrorase:raiseStructuredOutputError(f"LLM returned invalid schema:{e}")frome

形成:

JSONDecodeError \ → StructuredOutputError ValidationError /

这是:

Exception Abstraction

上层业务不需要处理所有底层细节。


8.raise ... from e

学习了:

raiseStructuredOutputError("...")frome

作用:

原始异常 ↓ 异常链 ↓ 新的业务异常

既能给业务层更清晰的错误,也保留底层 traceback。


9. Parse → Validate → Retry

LLM 输出是不确定的,所以第一次格式错误时,可以给它有限次数的修复机会。

流程:

LLM ↓ Answer ↓ Parse ↓ Validate ↓ 成功? ├── Yes → return └── No ↓ StructuredOutputError ↓ 还有 Retry? ├── No → raise └── Yes ↓ 构造修复 Prompt ↓ 重新调用 LLM

典型结构:

forattemptinrange(max_retries+1):

如果:

max_retries=2

表示:

Initial Call + Retry 1 + Retry 2 = 最多 3 次调用

10. Bounded Retry

这一章再次强化了:

AI 系统里的 Retry 必须有限制。

不能:

whileTrue:

否则可能导致:

无限 API 调用 无限费用 无限延迟 程序无法终止

所以:

max_retries=2

属于 bounded retry。


11. Retry Prompt

失败后可以把三类信息重新告诉模型:

1. 上次答案 2. 错误原因 3. 正确 Schema

例如:

Your previous response was invalid. Error: match_score must be <= 1 Previous response: ... Return ONLY valid JSON matching this schema: ...

这叫:

Self-correction / Repair Prompt


12. JSON Schema

之前给 LLM 的:

{"job":"string","skills":["string"]}

只是示例。

真正机器可理解的 Schema 是:

JSON Schema

Pydantic 可以自动生成:

JobRecommendation.model_json_schema()

返回dict。

然后:

json.dumps(schema,indent=2,ensure_ascii=False)

转成 JSON Schema 文本。

这里复习了:

json.loads() String → Python json.dumps() Python → String

13. Dynamic Schema

Structured Output 不应该把 Schema 写死。

可以:

model_class.model_json_schema()

这样传入不同 Pydantic Model,就自动得到不同 Schema。

例如:

JobRecommendation

产生 Job Schema。

ToolDecision

产生 Tool Schema。


14. Generic Parser

最开始:

parse_job_recommendation()

只能解析一个业务模型。

后来升级成:

parse_structured_output(answer,model_class)

可以解析任何 Pydantic Model。


15.type[BaseModel]

学习了 class 和 instance 的区别。

client:LLMClient

表示:

client 是 LLMClient instance

而:

model_class:type[BaseModel]

表示:

model_class 本身是一个 class

例如传:

JobRecommendation

不是:

JobRecommendation(...)

这和 C# 中的:

Type vs object instance

可以直接对应。


16. TypeVar

进一步学习:

fromtypingimportTypeVar T=TypeVar("T",bound=BaseModel)

意思:

T 可以代表任意 BaseModel 子类。

于是:

defparse_structured_output(answer:str,model_class:type[T])->T:

表达:

输入哪个 Pydantic class ↓ 返回对应 class 的 instance

例如:

result=parse_structured_output(answer,JobRecommendation)

则:

T = JobRecommendation

返回:

JobRecommendation instance

17. Generic Structured Output

最终目标变成:

awaitget_structured_output(client,prompt,JobRecommendation)

或者:

awaitget_structured_output(client,prompt,ToolDecision)

同一个函数处理不同 Schema。

核心组成:

Generic Parser + Generic Retry + Dynamic JSON Schema

18. build_structured_prompt()

第一次请求就应该给模型 Schema,而不是等模型第一次犯错。

defbuild_structured_prompt(prompt:str,model_class:type[T])->str:schema_text=json.dumps(model_class.model_json_schema(),indent=2,ensure_ascii=False)returnf"""{prompt}Return ONLY valid JSON matching this JSON Schema:{schema_text}Do not include markdown or explanations. """

19. build_retry_prompt()

失败时重新构造:

defbuild_retry_prompt(invalid_answer:str,error_message:str,model_class:type[T])->str:

内容包括:

Previous Answer + Validation Error + Dynamic JSON Schema + 严格输出要求

20. Genericget_structured_output()

最终核心架构:

Prompt ↓ build_structured_prompt() ↓ Dynamic JSON Schema ↓ LLM ↓ Answer ↓ parse_structured_output() ↓ json.loads() ↓ Pydantic Validation ↓ 成功? ├── Yes → return T └── No ↓ build_retry_prompt() ↓ bounded retry ↓ 最终成功 or raise

21. Prompt-based vs Native Structured Output

我们还区分了两种方案。

Prompt-based Structured Output

Prompt: “请输出 JSON” ↓ LLM ↓ json.loads() ↓ Pydantic ↓ Retry

属于:

Soft Constraint

优点:

兼容性强 几乎任何 LLM 都能使用 适合不同供应商和本地模型

Native Schema-constrained Output

部分现代 LLM API 支持:

Pydantic / JSON Schema ↓ API ↓ 生成阶段约束输出格式

属于更强的生成约束。

真实项目一般优先:

Native Structured Output + Pydantic Validation + Retry / Fallback

但仍然应该掌握手动版本,因为不同模型和推理框架的支持并不完全一致。


22. Schema = Contract

这一章最重要的软件工程思想之一:

Schema 是 LLM 与程序之间的数据契约。

例如:

classJobRecommendation(BaseModel):

规定了:

LLM 内部可以变化 模型供应商可以变化 Prompt 可以变化 但是 Backend 对外输出结构保持稳定

因此:

GPT Qwen DeepSeek Local Model ↓ 统一 Structured Output Layer ↓ JobRecommendation ↓ Unity / Web / App

客户端不需要知道后端使用哪个模型。


23. 本章涉及的 Python 知识

这一章把之前的知识进一步组合起来:

JSON ├── loads() └── dumps() OOP ├── class └── instance Pydantic ├── BaseModel ├── Field ├── ValidationError ├── model_validate() └── model_json_schema() Exception ├── try / except ├── custom exception ├── raise └── raise ... from e Typing ├── type[T] ├── TypeVar └── generic function Async ├── async def └── await Loop ├── range() └── bounded retry

24. 本章最终模块

Structured Output │ ├── Pydantic Schema ✅ ├── JSON Parsing ✅ ├── Schema Validation ✅ ├── StructuredOutputError ✅ ├── Exception Chaining ✅ ├── Retry ✅ ├── Bounded Retry ✅ ├── Repair Prompt ✅ ├── JSON Schema ✅ ├── model_json_schema() ✅ ├── Generic Parser ✅ ├── TypeVar ✅ ├── Dynamic Schema ✅ └── Generic Structured Output Pipeline ✅

25. Stage 2 当前进度

Stage 2:LLM Application Engineering ├── LLMClient ✅ ├── Messages / Roles ✅ ├── Multi-turn Chat ✅ ├── Context Management ✅ ├── Structured Output ✅ 本章完成 ├── Tool Calling ← 下一章 └── Streaming / Robustness ⏳

下一章正式进入:

Tool Calling

核心问题将从:

“如何让 LLM 返回可靠的数据?”

升级成:

“如何让 LLM 根据任务选择并调用程序中的工具?”

最终会得到:

User ↓ LLM ↓ Tool Decision ↓ Python Function ↓ Tool Result ↓ LLM ↓ Final Answer

这也是后面Agent Engineering的直接基础。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询