Appearance
输出解析器(Output Parsers)
LangChain V1.x
1、为什么需要输出解析器
在对话场景中,大模型直接返回自然语言文本即可。但在实际生产环境中,我们通常需要将大模型用于非对话场景(如数据提取、内容生成流水线等),此时需要模型以结构化的格式(如JSON、列表)输出结果,以便程序进一步处理。
LangChain在 langchain_core.output_parsers 包中提供了一系列输出解析器,专门解决"如何将模型的自然语言输出转换为结构化数据"这一问题。
要让大模型输出结构化数据,有两种基本策略:
| 策略 | 方式 | 可靠性 | 适用场景 |
|---|---|---|---|
| Prompt约束 | 在提示词中要求模型输出指定格式 | 依赖模型能力,可能出现格式错误 | 通用,任何模型都支持 |
| 厂商原生能力 | 使用API提供的结构化输出参数 | 由API层面保证格式正确 | 需要厂商支持(OpenAI、Google等) |
下面分别介绍这两种策略的具体实现。
2、策略一:通过Prompt约束(JsonOutputParser)
JsonOutputParser 的工作原理是:将你定义的JSON Schema转化为一段格式说明文字,插入到Prompt中,引导模型按照指定结构输出JSON。
使用步骤:
- 通过Pydantic定义目标JSON结构
- 构造
JsonOutputParser实例 - 调用
get_format_instructions()获取格式说明,插入到Prompt中 - 调用模型并用解析器解析结果
python
import os
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.0,
base_url=os.getenv("OPENAI_BASE_URL"),
api_key=os.getenv("OPENAI_API_KEY")
)
# 1. 定义目标JSON结构
class Prime(BaseModel):
prime: list[int] = Field(description="素数")
count: list[int] = Field(description="小于该素数的素数个数")
# 2. 构造解析器
json_parser = JsonOutputParser(pydantic_object=Prime)
# 3. 将格式说明放入SystemMessage
res = llm.invoke([
("system", json_parser.get_format_instructions()),
("user", "任意生成5个1000-100000之间的素数,并标出小于该素数的素数个数")
])
print(res.content)
# 4. 解析为Python字典
parsed_res = json_parser.invoke(res)
print(type(parsed_res)) # <class 'dict'>注意:
1、Prompt约束依赖模型的理解能力。参数量较小的模型可能输出不规范的JSON,导致解析失败。对于生产环境,建议优先使用策略二。
2、
partial用来控制"是否允许解析不完整/中途的 JSON 输出"。
partial=True:用于流式输出/增量生成场景。此时模型可能只吐出了半段 JSON(还没闭合括号、还缺字段)。解析时:
- 能解析成"当前已生成的部分 JSON"就返回(
parse_json_markdown尝试提取并解析)。- 还解析不了就返回
None,不抛异常,等后续 token 来了再继续解析。partial=False(默认):用于最终结果场景。要求输出必须是完整合法 JSON。
- 解析失败就抛
OutputParserException,提示Invalid json output,便于你立刻发现模型没按格式输出。一句话:
partial=True适配"边生成边解析";partial=False适配"生成完一次性严格校验"。
3、策略二:通过厂商原生能力
主流大模型厂商的API已经提供了专门的参数,在API层面强制模型输出符合指定Schema的结构化数据,比Prompt约束更加可靠。
OpenAI示例:
python
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
response = client.chat.completions.parse(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": "Alice and Bob are going to a science fair on Friday."}
],
response_format=CalendarEvent
)
print(response.choices[0].message.parsed)
# CalendarEvent(name='Science Fair', date='Friday', participants=['Alice', 'Bob'])注意:它不是给模型"输入的参数",而是告诉 API:把模型生成的内容按你提供的结构(这里是
CalendarEvent这个 Pydantic 模型)进行约束/引导生成 + 结果解析与校验,并把解析后的对象放到response.choices[0].message.parsed。输入阶段仍然是
messages;response_format影响的是模型该如何组织输出
Google Gemini示例:
python
from google import genai
from pydantic import BaseModel
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
client = genai.Client(
api_key="sk-OdqRypFlfJLKYvLmV6GsG9j0u6CRFBYKErn4xV1Wm0R3q0y9",
http_options={
"base_url": "https://api.openai-proxy.org/google"
},
)
response = client.models.generate_content(
model="gemini-2.5-flash-lite",
contents="Alice and Bob are going to a science fair on Friday.",
config={
"response_mime_type": "application/json",
"response_json_schema": CalendarEvent.model_json_schema(),
},
)
event = CalendarEvent.model_validate_json(response.text)
print(event)
CalendarEvent.model_json_schema():输出阶段的约束/说明。把 Pydantic 模型CalendarEvent转成 JSON Schema(字段名、类型、必填项等),传给模型接口用于要求模型按该结构生成 JSON。
CalendarEvent.model_validate_json(response.text):输出阶段的解析+校验。把模型返回的response.text(JSON 字符串)解析成CalendarEvent实例,并按模型规则做类型/必填校验;不符合会抛ValidationError。
可以看到,不同厂商的调用方式各不相同。这正是LangChain封装 with_structured_output 的动机。
4、LangChain统一封装:with_structured_output
LangChain提供了 with_structured_output() 方法,将不同厂商的结构化输出能力统一到同一个接口下。无论底层使用哪家模型,调用方式完全一致:
python
import os
from langchain_openai import ChatOpenAI
from pydantic import BaseModel
# 1. 初始化LLM
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.0,
base_url=os.getenv("OPENAI_BASE_URL"),
api_key=os.getenv("OPENAI_API_KEY")
)
# 2. 定义Pydantic模型
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
# 3. 使用with_structured_output,返回一个新的Runnable
structured_llm = llm.with_structured_output(schema=CalendarEvent)
# 4. 调用,直接返回Pydantic对象
result = structured_llm.invoke("Alice and Bob are going to a science fair on Friday.")
print(result) # CalendarEvent(name='Science Fair', date='Friday', participants=['Alice', 'Bob'])
print(type(result)) # <class 'CalendarEvent'>核心优势:如果后续需要更换模型(如从OpenAI切换到Anthropic),只需修改LLM的初始化代码,with_structured_output 的调用方式无需任何改动。
5、其他常用输出解析器
除了JSON解析,LangChain还提供了多种面向不同数据格式的输出解析器。
https://reference.langchain.com/python/langchain-core/output-parsers
5.1 StrOutputParser
最简单的解析器,将模型输出的 AIMessage 对象提取为纯字符串。它是构建链时最常用的解析器:
python
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
# 通常在LCEL链中使用:chain = prompt | llm | StrOutputParser()5.2 PydanticOutputParser
在介绍这个解析器之前,我们需要先了解 Pydantic 是什么。
Pydantic简介
Pydantic 是 Python 中最流行的数据验证库,它通过 Python 的类型注解自动进行数据校验和转换。简单来说,你可以用它定义一个"数据模型类",Pydantic 会确保传入的数据符合你定义的结构和类型。
最小使用案例:
python
from pydantic import BaseModel, Field
# 定义一个数据模型
class User(BaseModel):
name: str # 名字必须是字符串
age: int # 年龄必须是整数
email: str = Field(description="用户的邮箱地址")
# 创建实例 - 数据会自动校验和转换
user = User(name="张三", age=25, email="zhangsan@example.com")
print(user.name) # 张三
print(user.age) # 25
# 类型不匹配时,Pydantic会自动尝试转换
user2 = User(name="李四", age="30") # age是字符串"30",会自动转为整数30
print(user2.age) # 30 (已自动转换为int)
# 数据不合法时会抛出错误
# User(name="王五", age="abc") # 报错:无法将"abc"转为整数Pydantic的核心价值:
- 类型安全:自动校验数据类型
- 自动转换:能转换的类型会自动处理(如字符串"30" → 整数30)
- 清晰的错误提示:校验失败时给出详细的错误信息
- JSON支持:可以轻松与JSON数据互相转换
了解了Pydantic之后,PydanticOutputParser 的作用就很容易理解了——它让大模型输出符合Pydantic模型定义的结构化数据,并返回经过校验的Pydantic对象实例。
与 JsonOutputParser 返回普通字典不同,PydanticOutputParser 返回的是Pydantic对象,因此支持字段级别的校验规则:
python
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field, field_validator
from langchain_openai import ChatOpenAI
class MovieReview(BaseModel):
"""电影评论结构"""
title: str = Field(description="电影标题")
rating: int = Field(description="评分,1-10分", ge=1, le=10)
summary: str = Field(description="剧情简介")
recommended: bool = Field(description="是否推荐")
@field_validator('rating')
@classmethod
def rating_must_be_valid(cls, v):
if v < 1 or v > 10:
raise ValueError('评分必须在1-10之间')
return v
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
parser = PydanticOutputParser(pydantic_object=MovieReview)
# 将格式说明注入到Prompt中
prompt = ChatPromptTemplate.from_messages([
("system", parser.get_format_instructions()),
("human", "评价电影《盗梦空间》")
])
chain = prompt | llm | parser
result = chain.invoke({})
print(f"电影: {result.title}, 评分: {result.rating}/10")
chain = prompt | llm | parserparser(PydanticOutputParser)拿到 LLM 输出后,会把文本解析成 dict/JSON,然后执行MovieReview.model_validate(...)- 在
model_validate过程中,Pydantic 会对字段做校验:先做内置约束(ge=1, le=10),再运行你定义的@field_validator('rating'),因此rating_must_be_valid此时被调用- 校验失败会抛异常(LangChain 通常包装成
OutputParserException)
_PYDANTIC_FORMAT_INSTRUCTIONSvsJSON_FORMAT_INSTRUCTIONS区别两者都是给模型的"写作要求",目的都是让模型输出"符合 schema 的 JSON 文本"。
_PYDANTIC_FORMAT_INSTRUCTIONS:告诉你"按 schema 输出 JSON",但没强力禁止你加解释、加 Markdown code block。
JSON_FORMAT_INSTRUCTIONS:更严格,明确要求:
- 只能输出 JSON
- 不能有任何额外文字
- 不能用 ``` 包起来
- 必须是单一顶层 JSON 值
所以关键点是: 两个都是提示词(输入的一部分);它们约束的是模型的文本输出格式。输出先是 JSON 文本,之后才可能被解析成 Python 对象。
6、自定义输出解析器
当内置解析器无法满足需求时,可以继承 BaseOutputParser 创建自定义解析器:
python
from langchain_core.output_parsers import BaseOutputParser
class CommaListParser(BaseOutputParser):
"""将逗号分隔的文本解析为列表"""
def parse(self, text: str):
# 去除空白后按逗号分割
return [item.strip() for item in text.split(",")]
# 使用
parser = CommaListParser()
result = parser.parse("苹果, 香蕉, 橘子")
print(result) # ['苹果', '香蕉', '橘子']7、输出解析器选型指南
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 生产环境结构化输出 | with_structured_output() | 厂商原生支持,格式有保证 |
| 通用JSON提取 | JsonOutputParser | 灵活,不依赖特定厂商 |
| 需要字段校验 | PydanticOutputParser | 支持Pydantic校验规则 |
| 简单文本输出 | StrOutputParser | 最轻量,无额外开销 |
| 特殊格式 | 自定义 BaseOutputParser | 完全可控 |
小结:围绕"如何解析模型输出",介绍了从Prompt约束到厂商原生能力,再到LangChain统一封装的多种方案。在实际开发中,我们需要将提示词模板、模型调用、输出解析这三个步骤串联起来使用。如何优雅地组合这些组件?这就是Chains链式调用的核心主题。