在这里插入图片描述

文章目录


课程导读 & 学习目标

在前面的课程中,我们已经完整掌握了LangChain的提示词工程体系。第5课我们学会了用PromptTemplate构建动态提示,第6课用Few-Shot示例引导模型行为,第7课用ChatPromptTemplate编排系统/用户/历史消息。但有一个关键环节始终没有打通:大模型返回的是非结构化的自然语言字符串,而真实业务系统需要的是可编程的结构化数据(JSON、Python对象、Pydantic模型)

如果用生活场景来类比,大模型是一位博学但“说话随意”的专家——他能滔滔不绝地回答你的问题,但你需要从他的话里手动提取关键信息。输出解析器就是这位专家的“翻译官”和“格式警察”:它确保模型的输出按照严格的Schema进行结构化,并将其自动转换为你的程序可以直接使用的数据类型。

举例来说,当你让模型分析一条用户评论时,你希望得到的是一个包含sentiment(情感)、score(评分)、keywords(关键词)的JSON对象,而不是一段可能包含大量无关描述的文字。输出解析器正是实现这一目标的核心工具。

学完本节课,你将达到以下目标

  1. 深入理解输出解析器的设计哲学:为什么需要解析器?它们如何与LCEL链优雅集成?
  2. 掌握四大核心解析器StrOutputParser(纯文本提取)、JsonOutputParser(JSON解析)、PydanticOutputParser(自动生成Schema并校验)、CommaSeparatedListOutputParser(列表提取)。
  3. 精通输出修复机制:当模型返回格式错误时,如何通过OutputFixingParserRetryWithErrorOutputParser自动修复。
  4. 理解底层运行原理:解析器作为Runnable的执行流程,以及parseparse_result的区别。
  5. 完成三个实战项目:从简单的列表解析,到复杂的JSON和Pydantic结构化输出,再到带重试机制的生产级解析器配置。

本课之后,你将彻底打通LangChain从“输入提示”到“结构化输出”的完整链路,为后续第9课Memory组件(需要持久化结构化记忆)和第10课Retriever组件(需要标准化检索结果)打下坚实基础。

前置知识与环境准备

1.1 环境沿用与依赖安装

继续使用前几课的langchain_course虚拟环境(Python 3.10+)。本课需要安装以下依赖:

# 激活虚拟环境
source venv/bin/activate  # Mac/Linux
# venv\Scripts\activate   # Windows

# 基础依赖(如已安装可跳过)
pip install langchain==0.3.7 langchain-core==0.3.21 langchain-openai==0.2.8 python-dotenv==1.0.1

# 本课需要额外安装
pip install pydantic==2.0.0   # 用于数据验证(LangChain依赖)

# 验证安装
python -c "from langchain_core.output_parsers import StrOutputParser, JsonOutputParser, PydanticOutputParser; print('✓ 解析器导入成功')"

1.2 API密钥与模型选择

沿用前几课的.env配置(智谱GLM-4或Ollama本地模型)。由于本课涉及JSON格式输出,建议使用对格式遵循较好的模型(GPT-4o-mini、GLM-4、Qwen2.5-7B均可)。若使用Ollama,建议使用qwen2.5:7bllama3.2:7b,它们对结构化输出有较好的支持。

1.3 上节课回顾与本课定位

上节课(第7课)我们重点学习了ChatPromptTemplate如何编排System、Human、历史消息。但无论提示词多么精致,模型的输出总是一个字符串。本节课的OutputParser就是用来将这个字符串转换为Python对象的桥梁。两者结合,才构成完整的“提示→生成→解析”闭环。

核心概念深度拆解

2.1 为什么必须使用输出解析器?

在实际应用中,大模型常常出现以下问题:

  • 格式漂移:要求输出JSON,却返回Markdown包裹的字符串json {...}
  • 字段缺失:要求包含nameage,模型只返回name
  • 额外解释:除了要求的数据外,还添加了“根据您的请求,这是结果:”等无关文本。

如果没有输出解析器,你就需要写大量正则表达式和异常处理代码,既繁琐又不健壮。输出解析器的价值在于:

  • 声明式解析:用Schema声明期望的数据结构,框架自动解析。
  • 统一接口:所有解析器都是Runnable,可以无缝接入LCEL链(prompt | model | parser)。
  • 自动修复:当解析失败时,可自动调用模型进行修正或重试。

2.2 解析器的类型层次

LangChain中所有输出解析器都继承自BaseOutputParser抽象基类。根据解析目标的不同,主要分为以下几类:

解析器 输入类型 输出类型 适用场景
StrOutputParser AIMessage str 提取模型回复的纯文本内容
JsonOutputParser AIMessage dict / list 解析JSON格式的模型输出
PydanticOutputParser AIMessage Pydantic.BaseModel 验证并转换为强类型对象
CommaSeparatedListOutputParser AIMessage list[str] 解析逗号分隔的列表
DatetimeOutputParser AIMessage datetime 解析日期时间字符串
OutputFixingParser BaseOutputParser 同包装解析器 自动修复格式错误

2.3 解析器在LCEL链中的位置

在典型的prompt | model | parser链中,model的输出(AIMessage)作为parser的输入。解析器必须实现parse(text: str)parse_result(result: List[Generation])方法,将模型输出转换为目标类型。

# 伪代码示意
class BaseOutputParser(ABC):
    @abstractmethod
    def parse(self, text: str) -> Any:
        """解析字符串为任意对象"""
        
    def parse_result(self, result: List[Generation]) -> Any:
        """默认实现:取第一个Generation的文本调用parse()"""
        return self.parse(result[0].text)

当链执行时,LangChain自动将AIMessage的内容提取出来传给parse方法。

2.4 结构化输出的两条实现路径

路径一:提示词约束 + 通用解析器(JsonOutputParser)
在提示词中明确要求“输出JSON格式”,然后使用JsonOutputParser解析。这种方法简单直接,但对模型的要求较高,需要模型严格遵守格式。

路径二:Pydantic模型 + 自动生成格式指令
使用PydanticOutputParser,它会根据你定义的Pydantic类自动生成说明(包括字段类型、描述、示例),然后将说明注入提示词中,最后将模型输出解析为Pydantic对象。这种方法最专业、最可靠。

底层运行原理剖析

3.1 解析器的完整执行流程

格式化

返回 AIMessage

调用 parse_result

调用 parse

返回

ChatPromptTemplate

LLM

OutputParser

提取 text

Python对象

USER

当链prompt | llm | parser执行.invoke()时:

  1. 链调用llm.invoke(prompt_output),得到AIMessage对象。
  2. 链将该AIMessage传递给parser.invoke()
  3. parserinvoke方法调用parse_result(result),其中resultGeneration列表(LLMResult的一部分)。
  4. parse_result默认取出第一个Generation.text属性,传给parse方法。
  5. parse方法执行具体的解析逻辑(JSON加载、Pydantic验证等),返回Python对象。

3.2 JsonOutputParser的内部实现逻辑

JsonOutputParser的简化源码思路:

class JsonOutputParser(BaseOutputParser[Any]):
    def parse(self, text: str) -> Any:
        # 尝试提取被```json```包裹的内容
        text = self._extract_json(text)
        # 解析JSON
        return json.loads(text)
    
    def _extract_json(self, text: str) -> str:
        # 正则匹配 ```json ... ```或直接解析
        match = re.search(r"```json\n(.*?)\n```", text, re.DOTALL)
        if match:
            return match.group(1)
        return text

但它不会自动生成格式说明,需要你在提示词中手动说明“输出JSON”。

3.3 PydanticOutputParser的自动指令生成

PydanticOutputParser的核心能力是:根据Pydantic模型,自动生成一个详细的格式说明字符串,通过.get_format_instructions()方法获得。该字符串包含字段名称、类型、描述、示例,以及要求“只输出JSON,不添加其他文本”的指令。

例如,对于:

class Person(BaseModel):
    name: str = Field(description="姓名")
    age: int = Field(description="年龄")

生成的格式指令会类似:

The output should be formatted as a JSON instance that conforms to the JSON schema below.

Here is the output schema:
{
  "properties": {"name": {"title": "Name", "description": "姓名", "type": "string"}, ...},
  "required": ["name", "age"]
}

然后将此指令放入System消息或Human消息中。这样模型就知道了精确的输出格式。

3.4 错误处理机制:OutputFixingParser与RetryParser

当解析失败(如JSON格式错误),你可以使用OutputFixingParser,它会将原始输出和错误信息重新发送给模型,让模型自行修正。而RetryWithErrorOutputParser则更进一步:它会将之前的提示词、错误输出和错误原因一并传给模型,要求重新生成。

这两个组件本身就是解析器(包装器),它们接收一个底层解析器实例,并捕获其OutputParserException,然后调用模型进行修复。

核心API/组件源码解读

4.1 StrOutputParser —— 最轻量的文本提取器

from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()
result = parser.invoke(ai_message)  # 返回 ai_message.content

parse实现极为简单:return text。它不进行任何转换,只是提取字符串。这在需要去除AIMessage包装时非常有用。

4.2 JsonOutputParser —— 处理不“纯净”的JSON

from langchain_core.output_parsers import JsonOutputParser

parser = JsonOutputParser()
# 能处理带markdown标记的JSON
result = parser.parse('```json\n{"key": "value"}\n```')  # 返回 {'key': 'value'}

特点:

  • 自动去除常见的包裹字符(json、、`)。
  • 返回Python字典或列表,可直接用于业务逻辑。
  • 不验证JSON Schema(仅保证语法正确)。

4.3 PydanticOutputParser —— 最强类型系统

from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

class User(BaseModel):
    name: str = Field(description="用户名")
    age: int = Field(description="年龄")
    email: str = Field(description="电子邮箱")

parser = PydanticOutputParser(pydantic_object=User)
format_instructions = parser.get_format_instructions()
# 将 format_instructions 添加到提示词中

parsed_user = parser.parse('{"name": "张三", "age": 28, "email": "zhang@example.com"}')
print(parsed_user.name)  # 直接访问属性,类型安全

优势:

  • 自动生成JSON Schema。
  • 解析时自动进行类型校验(年龄是否为int,邮箱是否符合格式)。
  • 返回Pydantic对象,支持IDE自动补全。

4.4 其他常用解析器

  • CommaSeparatedListOutputParser:解析"apple, banana, cherry"["apple", "banana", "cherry"]
  • DatetimeOutputParser:解析日期字符串为datetime对象。
  • EnumOutputParser:将输出限制为枚举值列表中的一项。

4.5 自定义解析器 —— 按需定制

实现自定义解析器只需继承BaseOutputParser并实现parse方法:

from langchain_core.output_parsers import BaseOutputParser

class ReverseOutputParser(BaseOutputParser[str]):
    def parse(self, text: str) -> str:
        return text[::-1]  # 反转字符串

手把手项目实战教学

现在进入实战,通过三个项目逐步掌握输出解析器的核心用法。

实战一:基础解析器 —— StrOutputParser与CommaSeparatedListOutputParser

目标:体验最基础的文本提取和列表解析。

文件01_basic_parsers.py

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, CommaSeparatedListOutputParser

load_dotenv()

llm = ChatOpenAI(
    model="glm-4",
    openai_api_key=os.getenv("ZHIPU_API_KEY"),
    openai_api_base=os.getenv("BASE_URL"),
    temperature=0
)

# 1. StrOutputParser - 只提取纯文本
prompt1 = ChatPromptTemplate.from_template("请用一句话介绍{concept}")
chain1 = prompt1 | llm | StrOutputParser()
result1 = chain1.invoke({"concept": "LangChain"})
print("StrOutputParser 结果:", result1, type(result1))

# 2. CommaSeparatedListOutputParser - 解析逗号列表
prompt2 = ChatPromptTemplate.from_template("列出3个{category}的代表性框架,用逗号分隔")
chain2 = prompt2 | llm | CommaSeparatedListOutputParser()
result2 = chain2.invoke({"category": "前端JavaScript"})
print("CommaSeparatedList 结果:", result2, type(result2))

输出:StrOutputParser返回字符串,CommaSeparatedListOutputParser返回列表。

实战二:JsonOutputParser —— 从非结构化文本中提取JSON

目标:让模型返回标准的JSON,并解析为字典。

文件02_json_parser.py

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser

load_dotenv()

llm = ChatOpenAI(
    model="glm-4",
    openai_api_key=os.getenv("ZHIPU_API_KEY"),
    openai_api_base=os.getenv("BASE_URL"),
    temperature=0
)

# 定义输出解析器
parser = JsonOutputParser()

# 提示词中明确要求输出JSON格式,并说明字段
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个信息提取助手。请严格按照JSON格式输出,不要添加任何其他解释。"),
    ("human", "从以下文本中提取出:姓名、年龄、职业。文本:{text}。输出格式:{{\"name\": \"...\", \"age\": 数字, \"job\": \"...\"}}")
])

chain = prompt | llm | parser

text = "我叫王小明,今年28岁,是一名数据科学家。"
result = chain.invoke({"text": text})
print("解析后的字典:", result)
print("姓名:", result.get("name"))
print("年龄:", result.get("age"))
print("职业:", result.get("job"))

注意:如果模型返回了带Markdown标记的JSON(如json{...}),JsonOutputParser会自动去除。

实战三:PydanticOutputParser —— 强类型结构化输出(最推荐)

目标:使用Pydantic模型定义Schema,自动生成格式指令并解析为对象。

文件03_pydantic_parser.py

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

load_dotenv()

# 定义Pydantic模型
class Resume(BaseModel):
    name: str = Field(description="候选人姓名")
    age: int = Field(description="年龄")
    skills: list[str] = Field(description="掌握的技能列表,至少3项")
    years_experience: float = Field(description="工作年限,可以是非整数")
    education: str = Field(description="最高学历")

# 创建解析器
parser = PydanticOutputParser(pydantic_object=Resume)

# 获取格式说明(自动生成)
format_instructions = parser.get_format_instructions()

print("自动生成的格式指令:")
print(format_instructions)
print("-" * 60)

# 构建提示词模板,使用 partial 将格式指令注入
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个简历信息提取专家。请从用户提供的文本中提取候选人信息。\n{format_instructions}"),
    ("human", "简历内容:\n{resume_text}")
]).partial(format_instructions=format_instructions)

llm = ChatOpenAI(
    model="glm-4",
    openai_api_key=os.getenv("ZHIPU_API_KEY"),
    openai_api_base=os.getenv("BASE_URL"),
    temperature=0
)

chain = prompt | llm | parser

# 测试简历文本
resume_text = """
姓名:陈丽华,32岁,拥有5年Python后端开发经验,熟悉Django和FastAPI。
精通数据库设计和微服务架构。毕业于清华大学计算机科学与技术专业,硕士学历。
额外技能:团队管理、敏捷开发。
"""

try:
    result = chain.invoke({"resume_text": resume_text})
    print("解析成功!得到Resume对象:")
    print(f"姓名: {result.name}")
    print(f"年龄: {result.age}")
    print(f"技能: {', '.join(result.skills)}")
    print(f"工作年限: {result.years_experience}年")
    print(f"最高学历: {result.education}")
except Exception as e:
    print("解析失败:", e)

关键点

  • partial(format_instructions=...)将生成的格式说明插入到System消息中。
  • 解析成功后,resultResume实例,可以点选属性。
  • 如果模型返回的JSON字段类型不匹配(如age是字符串),Pydantic会自动尝试转换,失败则抛异常。

实战四:输出修复与重试机制 —— 生产级可靠性

目标:当模型输出格式不正确时,自动使用OutputFixingParser进行修复。

文件04_retry_fixing_parser.py

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser, OutputFixingParser
from pydantic import BaseModel, Field

load_dotenv()

class Person(BaseModel):
    name: str
    age: int

# 正常创建解析器
base_parser = PydanticOutputParser(pydantic_object=Person)

# 用修复解析器包装
fixing_parser = OutputFixingParser.from_llm(
    llm=ChatOpenAI(model="glm-4", temperature=0),
    parser=base_parser,
    max_retries=1
)

# 故意给一个错误的输出(缺少age字段,且value是字符串)
bad_output = '{"name": "张三", "age": "二十八"}'

# 先尝试用原解析器解析
try:
    r1 = base_parser.parse(bad_output)
except Exception as e:
    print("原始解析器失败:", e)

# 使用修复解析器
try:
    r2 = fixing_parser.parse(bad_output)
    print("修复解析器成功:", r2)
    print(f"姓名: {r2.name}, 年龄: {r2.age}")
except Exception as e:
    print("修复解析器也失败:", e)

原理OutputFixingParser捕获OutputParserException后将错误信息+错误输出一起发给模型,要求修正。

实战五:自定义解析器 —— 逆向输出

演示自定义解析器的实现。

文件05_custom_parser.py

from langchain_core.output_parsers import BaseOutputParser

class ReverseParser(BaseOutputParser[str]):
    def parse(self, text: str) -> str:
        return text[::-1]

# 使用
parser = ReverseParser()
print(parser.parse("Hello World"))  # 输出 "dlroW olleH"

完整可运行Python代码(带逐行详细注释)

将实战二、三、四的核心功能整合为一个脚本,形成完整的演示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
LangChain 第8课:OutputParser 完整演示
包含:JsonOutputParser、PydanticOutputParser、OutputFixingParser
"""

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser, PydanticOutputParser, OutputFixingParser
from pydantic import BaseModel, Field

load_dotenv()

# 初始化模型
llm = ChatOpenAI(
    model="glm-4",
    openai_api_key=os.getenv("ZHIPU_API_KEY"),
    openai_api_base=os.getenv("BASE_URL"),
    temperature=0
)

# ========== 1. JsonOutputParser 演示 ==========
def demo_json_output_parser():
    print("\n【演示1】JsonOutputParser")
    parser = JsonOutputParser()
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个信息提取助手。只输出JSON,不要有其他内容。"),
        ("human", "从以下文本提取:姓名、城市、爱好。文本:{text}\n输出格式:{{\"name\":\"...\", \"city\":\"...\", \"hobby\":\"...\"}}")
    ])
    chain = prompt | llm | parser
    text = "李明住在上海,他喜欢打篮球和游泳。"
    result = chain.invoke({"text": text})
    print("解析结果:", result)
    print("类型:", type(result))
    return result

# ========== 2. PydanticOutputParser 演示 ==========
def demo_pydantic_output_parser():
    print("\n【演示2】PydanticOutputParser")
    class Product(BaseModel):
        name: str = Field(description="产品名称")
        price: float = Field(description="价格(元)")
        in_stock: bool = Field(description="是否有货")
    
    parser = PydanticOutputParser(pydantic_object=Product)
    format_instructions = parser.get_format_instructions()
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个电商信息提取专家。\n{format_instructions}"),
        ("human", "产品描述:{description}")
    ]).partial(format_instructions=format_instructions)
    
    chain = prompt | llm | parser
    desc = "这款无线耳机售价299元,目前有现货。"
    product = chain.invoke({"description": desc})
    print(f"产品: {product.name}, 价格: {product.price}, 有货: {product.in_stock}")
    print("Pydantic对象:", product)
    return product

# ========== 3. OutputFixingParser 演示 ==========
def demo_fixing_parser():
    print("\n【演示3】OutputFixingParser")
    class Address(BaseModel):
        city: str
        street: str
        number: int
    
    base_parser = PydanticOutputParser(pydantic_object=Address)
    fixing_parser = OutputFixingParser.from_llm(llm=llm, parser=base_parser, max_retries=1)
    
    # 错误输出(number 是字符串,缺少city字段)
    bad_output = '{"street": "南京路", "number": "一百二十三"}'
    try:
        fixed = fixing_parser.parse(bad_output)
        print("修正后结果:", fixed)
    except Exception as e:
        print("修正失败:", e)

# ========== 主函数 ==========
def main():
    print("LangChain 第8课:输出解析器完整演示")
    demo_json_output_parser()
    demo_pydantic_output_parser()
    demo_fixing_parser()

if __name__ == "__main__":
    main()

环境依赖安装命令

# 激活虚拟环境
source venv/bin/activate  # Mac/Linux
# venv\Scripts\activate   # Windows

# 完整依赖(如果之前已安装,可以只安装缺失的)
pip install langchain==0.3.7 langchain-core==0.3.21 langchain-openai==0.2.8 python-dotenv==1.0.1

# Pydantic v2(LangChain 0.3.x 需要)
pip install pydantic>=2.0.0

# 验证安装
python -c "from pydantic import BaseModel; print('Pydantic 版本:', BaseModel.__module__)"

常见报错坑点与避坑方案

坑1:OutputParserException 解析失败,但模型输出看起来是合法的JSON

现象json.loads抛出异常,提示“Expecting value”,但肉眼检查输出是合法的。

原因:模型输出中可能包含不可见字符(BOM头、零宽空格),或者JSON被Markdown代码块包裹。另外,某些模型会在JSON后附加注释。

避坑方案:使用JsonOutputParser默认会去除json和标记。如果仍然失败,可以预处理:text = text.strip().strip('').replace(‘json\n’, ‘’)。或者使用OutputFixingParser`。

坑2:Pydantic 验证失败,字段类型不匹配

现象PydanticOutputParser抛出ValidationError,提示age: value is not a valid integer

原因:模型返回了字符串"28"而不是数字28,或者返回了"28岁"

避坑方案

  • 在Prompt的格式指令中明确写出“age字段为数字,不要加单位”。
  • 使用Field(..., examples=[28])强化类型提示。
  • 如果模型能力有限,可以先解析为宽松类型再手动转换。

坑3:OutputFixingParser 循环修复无法收敛

现象OutputFixingParser多次调用模型后仍然返回格式错误的JSON,最终抛出异常。

原因:模型能力不足,或者修复提示词设计不清晰。修复提示词是OutputFixingParser自动生成的(基于原始解析器的错误信息),有时不够明确。

避坑方案

  • 限制max_retries=1避免无限循环。
  • 自定义修复提示:继承OutputFixingParser并重写生成修复消息的逻辑。
  • 升级为更强大的模型。

坑4:StrOutputParser.invoke() 返回类型混淆

现象:使用chain = prompt | llm | StrOutputParser()后,chain.invoke()返回字符串,但忘记.content属性。

原因StrOutputParser已经提取了.content,所以返回的就是纯字符串。

避坑方案:保持一致性——要么不用解析器,手动取.content;要么始终用StrOutputParser,让链的输出统一为字符串。

坑5:在LCEL链中解析器之前使用了多个输出组件

现象:链为prompt | llm | parser1 | parser2,但parser2期望的输入类型与parser1的输出不匹配。

原因:解析器的输出类型需要与下一个组件的输入类型兼容。例如JsonOutputParser输出dict,而后续StrOutputParser期望str,就会出错。

避坑方案:仔细设计链的数据流,必要时使用RunnableLambda做适配。

本节核心知识点总结

📌 输出解析器的核心价值:将非结构化的模型输出转换为可编程的结构化数据(字符串、列表、字典、Pydantic对象),减少后处理代码量,提高系统健壮性。

📌 四大常用解析器

  • StrOutputParser:最轻量,仅提取文本内容。
  • JsonOutputParser:解析JSON字符串为字典/列表,自动清理Markdown标记。
  • PydanticOutputParser:基于Pydantic模型生成格式指令并验证输出,返回强类型对象。
  • CommaSeparatedListOutputParser:解析逗号分隔的列表。

📌 输出修复机制

  • OutputFixingParser捕获解析异常,将错误输出和错误信息发回给模型修正。
  • RetryWithErrorOutputParser进一步将原始Prompt一并发送,让模型重新生成。

📌 解析器作为Runnable:所有解析器都实现Runnable接口,可以直接用|运算符接入LCEL链,形成prompt | llm | parser的完美流水线。

📌 结构化输出最佳实践

  • 优先使用PydanticOutputParser,因为它提供了类型安全、自动指令生成和验证。
  • 始终设置temperature=0或极低值,减少格式漂移。
  • 对于复杂结构,使用Few-Shot示例增强模型对格式的遵循。

课后练习题

选择题

1. 以下哪个解析器会自动去除模型输出中的```json标记?
A. StrOutputParser
B. JsonOutputParser
C. CommaSeparatedListOutputParser
D. PydanticOutputParser

答案及解析:B。JsonOutputParser内部实现了_extract_json方法,可以去除Markdown代码块包裹。

2. PydanticOutputParser 通过哪个方法生成格式说明?
A. parse()
B. get_format_instructions()
C. validate_output()
D. from_llm()

答案及解析:B。get_format_instructions()返回一个字符串,描述期望的JSON Schema。

3. 想要在解析失败时自动让模型修正,应该使用哪个包装类?
A. RetryOutputParser
B. OutputFixingParser
C. AutoCorrectingParser
D. FallbackParser

答案及解析:B。OutputFixingParser会捕获异常并调用LLM进行修正。

简答题

4. 请说明StrOutputParser在LCEL链中的作用,并解释如果不使用它,手动提取文本的方法是什么?

参考答案
StrOutputParser的作用是将模型的AIMessage输出对象中的纯文本内容(.content)提取出来,使得链的最终输出是一个字符串,而不是AIMessage对象。如果不使用它,在调用链后需要手动写result.content,这会破坏链输出类型的一致性,尤其是在链后续还有其他Runnable组件时。在LCEL中,保持输出类型统一有利于组合。

5. 比较JsonOutputParserPydanticOutputParser的优缺点,并说明何时选择哪个。

参考答案

  • JsonOutputParser优点:简单直接,不依赖Pydantic模型,适合快速原型。缺点:返回的是字典,缺乏类型验证和IDE提示,字段错误只能在运行时发现。
  • PydanticOutputParser优点:强类型验证,自动生成格式指令,返回Pydantic对象支持自动补全和校验。缺点:需要预先定义模型,稍显繁琐。
  • 选择建议:对于临时测试或简单结构,用JsonOutputParser;对于生产环境、复杂嵌套结构、需要严格校验的场景,必须用PydanticOutputParser

实践题

6. 设计一个“新闻摘要解析器”,要求:

  • 模型根据新闻内容输出JSON格式,包含:标题(title)、发布时间(publish_date)、摘要(summary)、关键词(keywords,列表)。
  • 使用PydanticOutputParser定义模型并解析。
  • 测试新闻:“北京时间5月20日,OpenAI发布了GPT-5模型,该模型在推理能力上大幅提升。关键词:人工智能、大模型。”
  • 输出解析后的对象属性。

参考答案

from pydantic import BaseModel, Field
from langchain_core.output_parsers import PydanticOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

class News(BaseModel):
    title: str = Field(description="新闻标题")
    publish_date: str = Field(description="发布日期,格式YYYY-MM-DD")
    summary: str = Field(description="一句话摘要")
    keywords: list[str] = Field(description="关键词列表")

parser = PydanticOutputParser(pydantic_object=News)
format_instructions = parser.get_format_instructions()

llm = ChatOpenAI(...)
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个新闻分析专家。{format_instructions}"),
    ("human", "新闻内容:{news}")
]).partial(format_instructions=format_instructions)

chain = prompt | llm | parser
news_text = "北京时间5月20日,OpenAI发布了GPT-5模型,该模型在推理能力上大幅提升。关键词:人工智能、大模型。"
result = chain.invoke({"news": news_text})
print(result.title, result.keywords)

🔗《30节课 LangChain 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐