# 14 - 输出解析器 --- **本章课程目标:** - 理解**输出解析器(Output Parser)**是什么、为什么需要它,以及它在 **Model I/O** 中所处的位置。 - 会使用 **StrOutputParser**、**JsonOutputParser** 处理字符串与 JSON 输出。 - 理解 LangChain 官方的**结构化输出(Structured Output)**思路,掌握 **TypedDict**、**Pydantic**、**JSON Schema** 三种 schema 方式的差异。 **学习建议:** 这章要解决的是“模型说的话,程序怎么放心使用”。可以从 `StrOutputParser` 跑到 `JsonOutputParser`,再跑到 Pydantic,边跑边看哪里只是解析,哪里才发生校验。读完后能分清 Parser、Structured Output、TypedDict、Pydantic、JSON Schema 的边界,就已经抓住重点了。 **官方文档与资源**:详见 [工具导航与参考资料索引 - 提示词与结构化输出](工具导航与参考资料索引.md#提示词与结构化输出)。 --- ## 1、输出解析器简介 本章对应 [Model I/O](11-Model-I-O与模型接入.md) 中的**输出解析(Parse)**部分:把模型的**文本输出**转成程序易用的**结构化数据**(如字符串、JSON、强类型对象)。与 [第 13 章 提示词](13-提示词与消息模板.md)(**Format(输入格式化)**)、第 11 章模型(**Predict(模型调用)**)组合,即形成「输入 → 模型 → 输出解析」完整链路,[第 15 章 LCEL](15-LCEL与链式调用.md) 会用管道符将三者串成一条链。 ### 1.1 定义 **输出解析器(Output Parser)**,就是站在**模型输出**和**程序最终要用的数据**之间的一层转换器。 它的核心任务是:把模型返回的内容,从“面向人阅读”的文本,转成“面向程序处理”的结构化结果。 这条链可以拆成: - Prompt 负责把输入整理好,告诉模型“**你要怎么回答**”; - Model 负责生成结果,真正“**生成回答**”; - Output Parser 负责把结果转成字符串、JSON 或对象,把这份回答“**整理成程序好用的样子**”。 - 第 [15 章 LCEL 与链式调用](15-LCEL与链式调用.md) 会把这条链真正串起来。 ![输出解析器在 Model I/O 中的位置:承接模型文本输出,解析为程序可用的结构化数据](images/14/14-1-1-1.jpeg) 放到实际使用中,根据解析器类型不同,输出解析器通常会承担下面几类工作: - **格式转换**:把模型回答转成 `str`、`dict`、Pydantic 对象等。 - **结构约束**:引导模型尽量按指定字段输出。 - **结果校验**:检查字段类型、范围、必填项是否符合预期。 - **工程衔接**:让模型输出能被数据库、接口、前端、工作流直接消费。 ### 1.2 输出解析器的作用 初学大模型时,最容易把模型回答理解成“它回我一段文字就结束了”。但在真实项目里,模型输出通常还要继续流向别的程序模块,例如: - 存进数据库; - 返回给前端页面渲染; - 交给下一个工作流节点; - 作为 Agent、工具、接口调用的参数; - 进入风控、审核、统计、报表逻辑。 这时,“一段自然语言”通常就不够用了。程序更希望拿到的是: - 一个字符串; - 一个 JSON 字典; - 一个字段固定的对象; - 一个带校验规则的强类型数据结构。 如果只靠 `split()`、正则、字符串截取去拆模型输出,代码往往会变得脆弱:模型多加一句解释、少一个逗号、换一个字段名,程序就可能出错。 ### 1.3 常见输出解析器分类 | 解析器 | 最终结果 | 适用场景 | 说明 | | ---------------------- | ---------------------- | ------------------------------------- | ---------------------------------------------- | | `StrOutputParser` | 字符串 `str` | 只需要展示文本,不需要拆字段 | 最简单,通常就是取出模型输出正文内容 | | `JsonOutputParser` | Python `dict` / `list` | 希望模型返回 JSON,再交给程序继续处理 | 适合字段抽取、接口返回、工作流参数传递 | | `PydanticOutputParser` | Pydantic 对象 | 需要强类型和运行时校验 | 适合对字段类型、长度、范围有明确要求的业务场景 | ### 1.4 常见结构化输出方案 除了“输出解析器”以外,LangChain 还提供了更进一步的**结构化输出(Structured Output)**能力。 它的重点不是“事后把结果解析出来”,而是“事前就规定模型应该按什么结构输出”。这里会经常出现一个词:**schema**。 **schema** 可以看成“**数据结构说明书**”或“**输出格式规范**”。 它描述的是:一份数据应该包含哪些字段、每个字段是什么类型、哪些字段必填,以及有时还会补充字段长度、取值范围、枚举值等约束。 例如,如果规定模型输出一个人物信息: ```json { "name": "张三", "age": 20 } ``` 那么这个 schema 想表达的其实就是: - 必须有 `name` 字段; - 必须有 `age` 字段; - `name` 应该是字符串; - `age` 应该是整数。 所以,**结构化输出的本质,就是先定义 schema,再让模型按这个 schema 输出结果。** 官方常见的结构化输出方案有三种: | 方案 | 定义方式 | 最终结果 | 是否支持运行时校验 | 适用场景 | | ------------- | -------------------------------- | ------------- | ------------------ | ---------------------------------- | | `TypedDict` | Python 标准库 `typing.TypedDict` | `dict` | 否 | 只需要固定字段结构,不需要严格校验 | | `Pydantic` | `BaseModel` + `Field(...)` | Pydantic 对象 | 是 | 需要强类型、范围、长度、必填项校验 | | `JSON Schema` | 标准 JSON Schema 字典 | 通常为 `dict` | 视具体实现而定 | 需要跨语言、跨系统共享结构协议 | 在 LangChain 中,这些结构通常会和下面这种方式配合使用: ```python model.with_structured_output(...) ``` 也就是说: - `TypedDict` / `Pydantic` / `JSON Schema` 是“定义输出结构”的方式; - `with_structured_output(...)` 是“让模型按这个结构输出并自动解析”的入口。 ### 1.5 输出解析器与结构化输出的关系 这两个概念很容易混,但可以这样区分: - **输出解析器**:更强调“模型已经输出了,我怎么把它转成程序可用的数据”; - **结构化输出**:更强调“在模型输出之前,我先规定好它应该长什么样”。 你也可以把它们理解成: - 输出解析器偏**后处理**; - 结构化输出偏**前约束 + 自动解析**。 但它们不是“必须二选一”的关系,也不是“必须同时使用”的关系。 常见情况主要有三种: #### 第一种:只用输出解析器 适合模型先正常输出,再由程序把结果转成字符串、JSON 或对象。 ```python from langchain_core.output_parsers import JsonOutputParser parser = JsonOutputParser() result = model.invoke("请用 JSON 返回:name、age") data = parser.invoke(result) print(data) ``` 这种方式的特点很直接:先让模型生成结果,再由 parser 负责解析,所以它特别适合传统的 `prompt | model | parser` 链式调用。 #### 第二种:只用结构化输出 适合模型本身支持 `with_structured_output(...)`,LangChain 直接约束模型输出结构并自动解析。 ```python from typing import TypedDict class Person(TypedDict): name: str age: int structured_model = model.with_structured_output(Person) result = structured_model.invoke("请返回一个人物信息") print(result) ``` 这种方式不再需要单独手写 parser,而是由 `with_structured_output(...)` 统一完成“约束输出 + 自动解析”,更适合现代模型已经支持原生结构化输出的场景。 #### 第三种:结构化输出 + 额外校验 / 处理 在更严格的工程场景中,也可以做结构化输出,再补额外处理逻辑。 ```python structured_model = model.with_structured_output(Person) result = structured_model.invoke("请返回一个人物信息") # 这里可以继续做业务校验、入库前清洗、字段转换等处理 print(result) ``` 这一类场景不一定非要再接一个 Output Parser,但常常会继续补:业务规则校验;字段清洗;入库前转换;接口返回前二次封装。 所以这里的关键结论是: - **输出解析器**和**结构化输出**可以独立使用; - 也可以组合使用; - 是否同时使用,取决于模型能力和业务要求。 它们的目标是一致的:**让模型输出稳定地进入程序系统,而不是只停留在自然语言层面。** ### 1.6 实际使用场景 | 场景 | 如果不做解析,常见问题 | 更合适的做法 | | ----------------------------------------------- | ------------------------------ | ------------------------------------------------------------ | | 聊天问答、文案润色、摘要展示 | 只需要显示文本,结构要求低 | `StrOutputParser` | | 从文本里抽取字段,如“问题/答案”“时间/人物/事件” | 文本不稳定,后续逻辑难写 | `JsonOutputParser` | | 给前端卡片、表单、列表页返回固定字段 | 字段缺失或字段名漂移会影响渲染 | `with_structured_output(TypedDict)` | | 给数据库、审批流、订单系统写入严格数据 | 需要校验类型、范围、长度 | `PydanticOutputParser` 或 `with_structured_output(Pydantic)` | | 与外部系统按统一协议对接 | 需要语言无关、协议明确 | `JSON Schema` | --- ## 2、输出解析器常用方法 ### 2.1 动作一:解析输出 在 LangChain 里,解析器最常见的两种使用方式是: - `parser.invoke(...)`:更偏 LangChain / Runnable 风格,适合和 `prompt | model | parser` 链式组合。本章案例大多是这种写法。 - `parser.parse(text)`:更偏“我已经拿到一段纯文本了,现在只想解析这段文本”。 这两个方法的区别很简单: - 如果你还在 LangChain 的调用链里,常用 `invoke(...)`; - 如果你手里已经是 `result.content` 这种字符串,常用 `parse(text)`。 > **说明**:很多入门教程会笼统说“`parse(result)` 用来解析模型结果”。从概念上这样理解没问题,但 `parse(...)` 更偏向解析**文本字符串**,而链式开发中我们常直接使用 `parser.invoke(result)`。 ### 2.2 动作二:给模型“格式说明” 解析器不只是“事后处理”,很多时候还会**事前帮你约束模型输出**。 最常见的方法就是: ```python parser.get_format_instructions() ``` 它会返回一段格式说明文字,告诉模型:应该输出什么结构;有哪些字段;每个字段是什么类型;是否只能返回 JSON;是否不能加额外解释文字。 这段说明通常会被拼进 Prompt 中,让模型一开始就尽量按可解析格式输出,从而降低解析失败率。 --- ## 3、常见解析器用法与案例 ### 3.1 StrOutputParser `StrOutputParser` 是 LangChain 里最简单的输出解析器。它做的事情非常直接:**把模型返回内容取出来,当作字符串使用**。它**不做结构化解析**,也不关心字段、键名、数据类型,只关心“把最终文本拿到手”。 它特别适合这些任务: - 问答机器人直接展示文本; - 文章摘要、标题生成、改写润色; - 翻译、续写、营销文案; - 只需要把结果显示到前端,不需要拆字段的场景。 一句话来说:**如果下游只需要一段文本,而不是结构化字段,就用它。** 【案例源码】`案例与源码-2-LangChain框架/05_parser/StrOutputParserDemo.py` [StrOutputParserDemo.py](案例与源码-2-LangChain框架/05_parser/StrOutputParserDemo.py ":include :type=code") 你可能会问:既然 `AIMessage.content` 也能直接拿文本,为什么还要 `StrOutputParser`? 原因主要有三点: - **链式统一**:后续可以自然写成 `prompt | model | parser`。 - **接口一致**:今天是字符串,明天想切成 JSON,只要换 parser,不必重写整体结构。 - **可读性更强**:代码语义变成“这里是输出解析环节”,对初学者和团队协作都更友好。 ### 3.2 JsonOutputParser JsonOutputParser(JSON 解析器)可以把模型输出中可解析的 JSON 内容,转换成程序可直接使用的结构化数据。 这里要注意:它不是“任意一段自然语言都能稳定转成 JSON”的魔法。Prompt 仍然需要提前说明输出格式;如果模型输出完全偏离 JSON,解析依然可能失败。 在项目开发里,JSON 是最常见的结构化数据格式。因为它:适合前后端传输;适合接口返回;适合数据库中间层处理;适合继续转成对象或表单数据。 所以很多时候,我们希望模型不要只回答一句话,而是直接返回: ```json { "q": "...", "a": "..." } ``` 或者: ```json { "time": "...", "person": "...", "event": "..." } ``` 这时就可以使用 `JsonOutputParser`。 #### 3.2.1 用法一:直接在提示词里手写 JSON 要求 这是最直观的方式:在 Prompt 中明确写出“请返回 JSON,并包含哪些字段”。 【案例源码】`案例与源码-2-LangChain框架/05_parser/JsonOutputParserDemo.py` [JsonOutputParserDemo.py](案例与源码-2-LangChain框架/05_parser/JsonOutputParserDemo.py ":include :type=code") 这个案例适合帮助你理解最基础的工作流: 1. Prompt 明确要求输出 JSON; 2. 模型返回一段“长得像 JSON 的文本”; 3. `JsonOutputParser` 把它解析成 Python 里的 `dict`。 这种方式适合:字段很少;结构很简单;自己能一句话把格式说明白;主要目的是快速演示、快速打通流程。 #### 3.2.2 用法二:用 get_format_instructions() 自动生成格式说明 当结构开始复杂时,手写“请返回 JSON,包含 a、b、c 字段……”就容易写漏、写乱、写不严谨。 这时更稳妥的办法是:**让解析器自己生成格式说明,再拼进 Prompt**。 【案例源码】`案例与源码-2-LangChain框架/05_parser/JsonOutputParser_GetFormatInstructions.py` [JsonOutputParser_GetFormatInstructions.py](案例与源码-2-LangChain框架/05_parser/JsonOutputParser_GetFormatInstructions.py ":include :type=code") 这个案例里,用了一个 `Person` Pydantic 模型来描述 JSON 结构,再让: ```python parser.get_format_instructions() ``` 自动生成一段规范的格式说明给模型看。 你要抓住的重点是: - `JsonOutputParser` 本质上还是把结果解析成 **JSON / dict**; - 这里引入 Pydantic 模型,主要是为了更方便地**描述输出结构**; - 如果你要的最终结果是 **Pydantic 实例** 而不是 `dict`,通常应该看下一节的 `PydanticOutputParser` 或 `with_structured_output(Pydantic模型)`。 --- ## 4、结构化输出 ### 4.1 定义 所谓**结构化输出(Structured Output)**,就是不满足于“模型输出一段 JSON 文本”,而是进一步要求: - 输出必须符合某个明确 schema; - LangChain 直接帮你解析成字典或对象; - 必要时还能做字段验证。 也就是说,普通 JSON 解析更像是: > “请尽量按这个格式说。” 而结构化输出更像是: > “你必须按这个 schema 交付结果。” ![从普通文本、Parser、JSON 到结构化输出和 Pydantic 校验的逐步增强路线](images/14/14-4-1-1.svg) LangChain 官方现在特别强调:**很多现代模型已经支持原生结构化输出**。这意味着,在支持的模型上,优先使用: ```python model.with_structured_output(...) ``` 往往会比传统“手写 Prompt + Parser 解析”的方式更稳、更省心。 但输出解析器并没有过时,它仍然很有价值,尤其是在:使用不支持原生结构化输出的模型时;需要把结果继续做解析 / 清洗时;需要用 Pydantic 追加严格校验时;想把输出解析作为 LCEL 链的一环统一管理时。 ### 4.2 常见方式 LangChain 官方文档里,结构化输出常见有三种 schema 方式: | 方式 | 返回结果常见形态 | 是否自带运行时校验 | 适合什么场景 | | --------------- | ---------------- | ------------------ | ---------------------------------------- | | **TypedDict** | `dict` | 否 | 结构清晰、字段固定,但校验要求不高 | | **Pydantic** | Pydantic 对象 | 是 | 需要强类型、范围校验、长度校验、字段验证 | | **JSON Schema** | `dict` | 取决于具体使用方式 | 需要与外部协议对齐、跨语言协作 | 这里还经常会配合一个写法:**Annotated**,它不是第四种 schema,而是给字段补充说明信息的一种方式。 --- ## 5、TypedDict 与 Annotated ### 5.1 TypedDict:描述“这个字典长什么样” `TypedDict` 来自 Python 标准库 `typing`,它的作用是:描述一个字典应该有哪些键、每个键是什么类型。它更像是一张**结构说明书**。 对 LangChain 来说,这张说明书很有用,因为它可以据此引导模型输出固定结构,再解析成 Python 字典。 但要特别注意:**TypedDict 不负责真正的运行时校验。**也就是说,它更偏“说明结构”,不是“强制验证”。 ### 5.2 Annotated:给字段加解释说明 `Annotated` 也是 Python 标准库 `typing` 里的能力。它的作用不是“换一种类型”,而是:在原有类型上附加一段元数据或说明。 例如: ```python Annotated[str, "动物名称"] ``` 它本质上还是 `str`,只是附带了一段“这是动物名称”的说明。LangChain 可以利用这些说明生成更清晰的 schema 描述,让模型更容易理解每个字段该填什么。 ### 5.3 案例:TypedDict 版结构化输出 【案例源码】`案例与源码-2-LangChain框架/05_parser/StructuredOutput_TypedDict.py` [StructuredOutput_TypedDict.py](案例与源码-2-LangChain框架/05_parser/StructuredOutput_TypedDict.py ":include :type=code") 这个案例可以作为“现代 LangChain 结构化输出”的第一课: 1. 用 `TypedDict` 定义 `Animal` 与 `AnimalList`; 2. 用 `Annotated` 给字段添加说明; 3. 用 `llm.with_structured_output(AnimalList)` 直接告诉模型输出目标结构; 4. 调用 `.invoke(...)` 后直接得到解析好的 `dict`。 这比“先要求 JSON,再手动解析”更像真实项目中的推荐写法。 ### 5.4 案例:Annotated 只是描述,不是校验 【案例源码】`案例与源码-2-LangChain框架/05_parser/AnnotatedTypedDict.py` [AnnotatedTypedDict.py](案例与源码-2-LangChain框架/05_parser/AnnotatedTypedDict.py ":include :type=code") 这个案例解决的是一个容易混淆的问题: ```python Age = Annotated[int, "年龄,范围0-150"] ``` 这句话并不意味着 Python 会自动帮你检查“年龄必须在 0 到 150 之间”。它只是说:这个字段的类型是 `int`;附带一段说明“年龄,范围 0-150”。如果没有额外的校验框架,这段说明不会自动变成校验规则。 所以在 TypedDict 场景下:`Annotated` 更像**提示词增强器**;它不是**运行时验证器**。 ### 5.5 使用场景 TypedDict 适合这些情况: - 前端页面需要固定字段,但字段值不用做复杂校验; - 工作流节点之间传递结构化数据; - 想要结构清晰,但不想引入太重的校验逻辑; - 模型本身支持较好的结构化输出能力。 一句话总结:**TypedDict 适合“我要稳定结构”,但暂时不要求“严格数据合法性校验”。** --- ## 6、Pydantic:从结构说明到校验 ### 6.1 定义 如果说 `TypedDict` 主要解决的是“这个字典应该长什么样”,那么 **Pydantic** 解决的是:“这个数据不仅要长得像,而且必须真的合法。” Pydantic 是 Python 生态里很常用的数据校验库。它可以在创建对象时对字段做:类型检查;范围检查;长度检查;自定义校验。真实业务里只要涉及结构化数据,就经常会用到它。 ### 6.2 案例:Annotated + Pydantic 触发校验 【案例源码】`案例与源码-2-LangChain框架/05_parser/AnnotatedPydantic.py` [AnnotatedPydantic.py](案例与源码-2-LangChain框架/05_parser/AnnotatedPydantic.py ":include :type=code") 这个案例和上一个 `AnnotatedTypedDict.py` 正好形成对照: - 在 TypedDict 里,`Annotated[int, "年龄范围0-150"]` 只是描述; - 在 Pydantic 里,`Annotated[int, Field(ge=0, le=150)]` 会真正变成运行时校验。 记住一句话就够了:**Annotated 本身不校验;真正发生校验的是 Pydantic 的 `Field(...)` 规则。** ### 6.3 案例:PydanticOutputParser 的完整流程 【案例源码】`案例与源码-2-LangChain框架/05_parser/StructuredOutput_Pydantic.py` [StructuredOutput_Pydantic.py](案例与源码-2-LangChain框架/05_parser/StructuredOutput_Pydantic.py ":include :type=code") 这个案例体现了 Pydantic 路线最完整、最经典的工作流: 1. 定义一个 `Product` Pydantic 模型; 2. 用 `Field(...)` 给字段加说明; 3. 用 `field_validator(...)` 写更细的校验逻辑; 4. 创建 `PydanticOutputParser(pydantic_object=Product)`; 5. 用 `get_format_instructions()` 生成格式说明; 6. 把说明拼入 Prompt; 7. 模型输出后,解析成 **Pydantic 实例**。 这个流程比单纯的 `JsonOutputParser` 更强,因为它不只是“转成字典”,而是“转成一个经过校验的对象”。 ### 6.4 使用场景 当你遇到下面这些需求时,通常就该优先考虑 Pydantic: - 要把结果写进数据库,不能容忍字段类型乱掉; - 要把模型输出接到订单、审批、风控、报表等业务系统; - 某些字段必须满足范围、枚举、长度等限制; - 希望一旦数据不合法,就立刻抛错,而不是悄悄放过。 一句话总结: **Pydantic 适合“我要的不只是结构化,而是可验证、可托底、可工程化的数据”。** --- ## 7、JSON Schema:和外部协议对齐时很有用 ### 7.1 定义 `JSON Schema` 是一种专门用来描述 JSON 结构和约束规则的标准。 它最大的特点是语言无关,前后端都能理解,也因此很适合跨团队、跨系统、跨语言协作。 LangChain 官方也把它列为结构化输出的三种主流方式之一。 ### 7.2 使用场景 如果你的场景是: - 后端和前端已经约定了一份 JSON 协议; - 你的 Python 服务要和 Java、Go、Node 等系统对接; - 想把数据结构标准化、文档化; - 不想强依赖 Pydantic 或 Python 类型系统; 那么 JSON Schema 就会比 TypedDict 更通用。 ### 7.3 和 TypedDict、Pydantic 的区别 可以简单这样记: - **TypedDict**:最像 Python 内部的“结构说明书”。 - **Pydantic**:最像 Python 内部的“结构 + 校验模型”。 - **JSON Schema**:最像系统之间共享的“协议文档”。 --- ## 8、实际开发选择 这一节给出本章的落地选择。 ### 8.1 从简单到严格的选择路径 你可以按下面这条路径做选择: 1. **只需要文本展示** 用 `StrOutputParser` 2. **需要简单 JSON 字段** 用 `JsonOutputParser` 3. **需要固定结构,且模型支持结构化输出** 优先 `with_structured_output(TypedDict)` 4. **需要严格校验** 用 `Pydantic` + `PydanticOutputParser`,或 `with_structured_output(Pydantic模型)` 5. **需要跨语言 / 外部协议对齐** 用 `JSON Schema` 落到工程选型上,可以记住这句话:**普通文本用 Parser,固定结构优先 `with_structured_output`,强校验上 Pydantic,Agent 最终结果再看 `response_format`。** ### 8.2 一个更贴近项目落地的选型表 | 需求 | 推荐方案 | | ----------------------------------------- | ----------------------------------- | | 聊天机器人回复、文章摘要、翻译 | `StrOutputParser` | | 抽取“问题-答案”“时间-人物-事件”等简单字段 | `JsonOutputParser` | | 给前端接口返回一个固定字段字典 | `with_structured_output(TypedDict)` | | 给数据库、业务系统写入高可靠数据 | `with_structured_output(Pydantic)` / `PydanticOutputParser` | | 与其它语言系统共享统一数据协议 | `JSON Schema` | | 输出格式很特殊,内置解析器覆盖不了 | 自定义 `BaseOutputParser` | ### 8.3 官方建议 LangChain 官方当前的整体方向是: - **如果模型原生支持结构化输出,优先用原生能力**; - **如果模型不支持,或你还需要额外解析/校验,再使用输出解析器**。 ### 8.4 自定义解析器 大多数场景不需要自己写解析器。优先级一般是:先看 `with_structured_output(...)`,再看内置 Parser,最后才考虑自定义。 但有些输出确实比较特殊,例如模型返回的是一段固定格式的清单、日志、命令块,既不是标准 JSON,也不适合上 Pydantic。这时可以继承 `BaseOutputParser`,只实现最关键的 `parse()` 方法。 ```python from langchain_core.output_parsers import BaseOutputParser class LineListParser(BaseOutputParser[list[str]]): def parse(self, text: str) -> list[str]: return [ line.strip("- ").strip() for line in text.splitlines() if line.strip() ] ``` 它适合做“很轻的格式清洗”。如果结果后面要进数据库、审批流、自动执行工具,仍然建议继续接 Pydantic 或业务校验,不要只靠字符串拆分。 --- ## 9、常见误区与排错建议 ### 9.1 误区一:能解析成 JSON,就说明数据没问题 不对。JSON 只说明“格式像样”,不说明“业务正确”。 比如用户年龄输出成了 `999`,它依然是合法 JSON,但显然不合理。 ### 9.2 误区二:Annotated 自带校验 不对。`Annotated` 只是附加元数据。 真正让“范围、长度、约束”生效的是 Pydantic 的 `Field(...)`、validator 等机制。 ### 9.3 误区三:输出解析器能完全代替 Prompt 设计 不对。如果 Prompt 没说清楚字段含义、输出边界、不要加额外说明,解析器很可能仍然失败。 ### 9.4 误区四:所有项目都应该直接上 Pydantic 也不一定。如果你只是做文本展示,或者只是一个简单 JSON 演示,直接上 Pydantic 可能过重。 工程化不是“越复杂越好”,而是“够用且稳定”。 ### 9.5 误区五:学了 Parser 就和结构化输出是两套体系 不是。它们本质上解决的是同一个问题:**让模型输出可被程序稳定消费**。 区别只是在于: - 有的方式偏“后处理解析”; - 有的方式偏“原生结构约束 + 自动解析”; - 有的方式还能进一步做严格校验。 --- **章节思考题:** 1. 为什么“模型输出了 JSON”不等于“程序可以放心使用”? **参考思路:** JSON 语法正确只是第一步,字段是否齐全、类型是否正确、值是否符合业务规则还需要校验。解析解决“能读”,校验解决“能不能信”。 2. 什么时候 `StrOutputParser` 就够了,什么时候应该上 Pydantic? **参考思路:** 只需要纯文本展示时,字符串解析足够;结果要进入数据库、接口、流程分支或自动执行时,应考虑 Pydantic 这类强校验。越靠近业务动作,越不能只靠自然语言。 3. Structured Output 和 Output Parser 的关系应该怎么理解? **参考思路:** Structured Output 更偏让模型按结构生成,Parser 更偏在输出后解析和转换。两者可以配合使用:前面约束生成,后面兜底解析和校验。 4. TypedDict、Pydantic、JSON Schema 的选择取决于什么? **参考思路:** TypedDict 适合轻量类型说明,Pydantic 适合 Python 内部强校验和错误提示,JSON Schema 适合跨语言、接口协议或外部系统对齐。不是越重越好,要看边界在哪里。 **本章小结:** - **输出解析器**是 Model I/O 里的 **Parse** 环节,负责把模型输出转成程序可直接使用的数据。 - 本章的重点不在于背 API,而在于建立工程认知:**大模型输出往往还要继续进入程序链路,因此必须结构化**。 - **StrOutputParser** 适合纯文本场景;**JsonOutputParser** 适合快速得到 `dict`;**PydanticOutputParser** 适合强类型、强校验场景。 - **结构化输出**是本章真正的重点。LangChain 官方常见的三种 schema 方式是:**TypedDict、Pydantic、JSON Schema**。 - **TypedDict** 适合描述结构;**Pydantic** 适合描述结构并做运行时校验;**Annotated** 主要用于补充字段说明,本身不是校验器。 - 现代 LangChain 更推荐在支持的模型上优先考虑 `with_structured_output(...)`,输出解析器则继续在兼容性、后处理、附加校验等方面发挥作用。 - 从掌握结果看,学完本章后,你至少应该:能分清 **输出解析器** 和 **结构化输出** 的关系,知道它们都在解决“模型结果如何稳定交给程序”这个问题;知道 `StrOutputParser`、`JsonOutputParser`、`PydanticOutputParser` 三类方案各自适用什么场景;理解 `TypedDict`、`Annotated`、Pydantic、`JSON Schema` 在“描述结构”和“运行时校验”上的边界。 **建议下一步:** 学习 [第 15 章 LCEL 与链式调用](15-LCEL与链式调用.md),把本章的解析器与 [第 13 章](13-提示词与消息模板.md) 的 Prompt、[第 11 章](11-Model-I-O与模型接入.md) 的 Model 串成真正的 `prompt | model | parser` 链。