# 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` 链。
