第8课:LangChain|OutputParser输出解析器【结构化输出、JSON格式化实战】
OutputParser是LangChain框架中用于处理模型输出的核心组件,能够将非结构化的文本转换为结构化数据(如JSON、字典或自定义对象)。通过预定义解析规则,OutputParser确保模型输出的格式符合下游任务的需求,提升数据可用性。在JSON格式化实战中,OutputParser可将自然语言描述转换为标准JSON结构,适用于API交互或数据存储场景。

文章目录
课程导读 & 学习目标
在前面的课程中,我们已经完整掌握了LangChain的提示词工程体系。第5课我们学会了用PromptTemplate构建动态提示,第6课用Few-Shot示例引导模型行为,第7课用ChatPromptTemplate编排系统/用户/历史消息。但有一个关键环节始终没有打通:大模型返回的是非结构化的自然语言字符串,而真实业务系统需要的是可编程的结构化数据(JSON、Python对象、Pydantic模型)。
如果用生活场景来类比,大模型是一位博学但“说话随意”的专家——他能滔滔不绝地回答你的问题,但你需要从他的话里手动提取关键信息。输出解析器就是这位专家的“翻译官”和“格式警察”:它确保模型的输出按照严格的Schema进行结构化,并将其自动转换为你的程序可以直接使用的数据类型。
举例来说,当你让模型分析一条用户评论时,你希望得到的是一个包含sentiment(情感)、score(评分)、keywords(关键词)的JSON对象,而不是一段可能包含大量无关描述的文字。输出解析器正是实现这一目标的核心工具。
学完本节课,你将达到以下目标:
- 深入理解输出解析器的设计哲学:为什么需要解析器?它们如何与LCEL链优雅集成?
- 掌握四大核心解析器:
StrOutputParser(纯文本提取)、JsonOutputParser(JSON解析)、PydanticOutputParser(自动生成Schema并校验)、CommaSeparatedListOutputParser(列表提取)。 - 精通输出修复机制:当模型返回格式错误时,如何通过
OutputFixingParser和RetryWithErrorOutputParser自动修复。 - 理解底层运行原理:解析器作为Runnable的执行流程,以及
parse与parse_result的区别。 - 完成三个实战项目:从简单的列表解析,到复杂的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:7b或llama3.2:7b,它们对结构化输出有较好的支持。
1.3 上节课回顾与本课定位
上节课(第7课)我们重点学习了ChatPromptTemplate如何编排System、Human、历史消息。但无论提示词多么精致,模型的输出总是一个字符串。本节课的OutputParser就是用来将这个字符串转换为Python对象的桥梁。两者结合,才构成完整的“提示→生成→解析”闭环。
核心概念深度拆解
2.1 为什么必须使用输出解析器?
在实际应用中,大模型常常出现以下问题:
- 格式漂移:要求输出JSON,却返回Markdown包裹的字符串
json {...}。 - 字段缺失:要求包含
name和age,模型只返回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 解析器的完整执行流程
当链prompt | llm | parser执行.invoke()时:
- 链调用
llm.invoke(prompt_output),得到AIMessage对象。 - 链将该
AIMessage传递给parser.invoke()。 parser的invoke方法调用parse_result(result),其中result是Generation列表(LLMResult的一部分)。parse_result默认取出第一个Generation的.text属性,传给parse方法。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消息中。- 解析成功后,
result是Resume实例,可以点选属性。 - 如果模型返回的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. 比较JsonOutputParser和PydanticOutputParser的优缺点,并说明何时选择哪个。
参考答案:
- 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 从入门到精通》系列课程导航
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~
更多推荐


所有评论(0)