内容纲要
- 输出解析器核心作用:将大模型自然语言输出转换为结构化数据
- 常用解析器类型
StrOutputParser:提取纯文本,无需格式指令PydanticOutputParser:基于 Pydantic 数据模型,精确控制字段和验证JsonOutputParser:自由 JSON 或结合 Pydantic 生成严格 JSONXMLOutputParser:输出字典,可指定标签约束
- 关键技术点
- 格式指令注入:
get_format_instructions()必须嵌入提示词 - Pydantic v2 版本变化及验证器用法
- 流式输出中的 JSON 完整性保护
- 格式指令注入:
- 完整可运行代码:使用模拟模型演示四种解析器,无需外部 API Key
引言
大模型输出的本质是自然语言文本,但实际应用需要结构化数据(如 JSON 对象、表格、特定字段)传递给下游服务。早期做法是用正则表达式从文本中抽取信息,但模型输出的随机性常导致匹配失败。LangChain 的输出解析器(Output Parsers)提供了一个标准化方案:通过将格式要求预先注入提示词,并结合解析器自动转换,可稳定获得结构化的 Python 对象。本文通过可运行代码演示文本、JSON、Pydantic 和 XML 四种常见解析器的用法。
输出解析器在 IO 管道中的定位
LangChain 的核心数据流由三部分组成:提示词模板、大模型、输出解析器,它们通过 LCEL 管道串联:
#mermaid-svg-NtVJp4jqESE41xxE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NtVJp4jqESE41xxE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NtVJp4jqESE41xxE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NtVJp4jqESE41xxE .error-icon{fill:#552222;}#mermaid-svg-NtVJp4jqESE41xxE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NtVJp4jqESE41xxE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NtVJp4jqESE41xxE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NtVJp4jqESE41xxE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NtVJp4jqESE41xxE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NtVJp4jqESE41xxE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NtVJp4jqESE41xxE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NtVJp4jqESE41xxE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NtVJp4jqESE41xxE .marker.cross{stroke:#333333;}#mermaid-svg-NtVJp4jqESE41xxE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NtVJp4jqESE41xxE p{margin:0;}#mermaid-svg-NtVJp4jqESE41xxE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NtVJp4jqESE41xxE .cluster-label text{fill:#333;}#mermaid-svg-NtVJp4jqESE41xxE .cluster-label span{color:#333;}#mermaid-svg-NtVJp4jqESE41xxE .cluster-label span p{background-color:transparent;}#mermaid-svg-NtVJp4jqESE41xxE .label text,#mermaid-svg-NtVJp4jqESE41xxE span{fill:#333;color:#333;}#mermaid-svg-NtVJp4jqESE41xxE .node rect,#mermaid-svg-NtVJp4jqESE41xxE .node circle,#mermaid-svg-NtVJp4jqESE41xxE .node ellipse,#mermaid-svg-NtVJp4jqESE41xxE .node polygon,#mermaid-svg-NtVJp4jqESE41xxE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NtVJp4jqESE41xxE .rough-node .label text,#mermaid-svg-NtVJp4jqESE41xxE .node .label text,#mermaid-svg-NtVJp4jqESE41xxE .image-shape .label,#mermaid-svg-NtVJp4jqESE41xxE .icon-shape .label{text-anchor:middle;}#mermaid-svg-NtVJp4jqESE41xxE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NtVJp4jqESE41xxE .rough-node .label,#mermaid-svg-NtVJp4jqESE41xxE .node .label,#mermaid-svg-NtVJp4jqESE41xxE .image-shape .label,#mermaid-svg-NtVJp4jqESE41xxE .icon-shape .label{text-align:center;}#mermaid-svg-NtVJp4jqESE41xxE .node.clickable{cursor:pointer;}#mermaid-svg-NtVJp4jqESE41xxE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NtVJp4jqESE41xxE .arrowheadPath{fill:#333333;}#mermaid-svg-NtVJp4jqESE41xxE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NtVJp4jqESE41xxE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NtVJp4jqESE41xxE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NtVJp4jqESE41xxE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NtVJp4jqESE41xxE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NtVJp4jqESE41xxE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NtVJp4jqESE41xxE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NtVJp4jqESE41xxE .cluster text{fill:#333;}#mermaid-svg-NtVJp4jqESE41xxE .cluster span{color:#333;}#mermaid-svg-NtVJp4jqESE41xxE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NtVJp4jqESE41xxE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NtVJp4jqESE41xxE rect.text{fill:none;stroke-width:0;}#mermaid-svg-NtVJp4jqESE41xxE .icon-shape,#mermaid-svg-NtVJp4jqESE41xxE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NtVJp4jqESE41xxE .icon-shape p,#mermaid-svg-NtVJp4jqESE41xxE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NtVJp4jqESE41xxE .icon-shape .label rect,#mermaid-svg-NtVJp4jqESE41xxE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NtVJp4jqESE41xxE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NtVJp4jqESE41xxE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NtVJp4jqESE41xxE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户输入
提示词模板
大模型
输出解析器
结构化数据
下游应用
解析器的作用是将模型输出的自由文本转换为机器易处理的格式,同时保证与 LangChain 生态的其他组件无缝对接。
四种解析器一览
| 解析器 | 输出类型 | 是否需格式指令 | 典型场景 |
|---|---|---|---|
StrOutputParser |
字符串 | 否 | 简单问答、文本摘要 |
PydanticOutputParser |
Pydantic BaseModel 实例 | 是 | 精准字段控制、数据验证 |
JsonOutputParser |
字典 (dict) | 是 | 通用 JSON 数据交互 |
XMLOutputParser |
字典 (dict) | 是 | 兼容 XML 的老系统 |
使用结构化解析器时,务必通过 get_format_instructions() 获取格式指令并嵌入提示词,否则模型可能不遵守格式约定。
环境准备
安装依赖:
bash
pip install langchain langchain-core langchain-community pydantic
以下代码使用 FakeListChatModel 模拟模型输出,因此无需任何 API Key 即可运行。如果希望接入真实模型(如 OpenAI、DeepSeek),只需替换模型初始化部分。
完整可运行代码
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import (
StrOutputParser,
JsonOutputParser,
PydanticOutputParser,
XMLOutputParser,
)
from langchain_community.chat_models.fake import FakeListChatModel
from pydantic import BaseModel, Field, model_validator
# ======================== 1. 文本解析器 ========================
# 模型预设回复
str_model = FakeListChatModel(responses=[" LangChain 是一个用于构建大语言模型应用的开源框架。"])
str_prompt = ChatPromptTemplate.from_template("用一句话介绍{subject}")
str_chain = str_prompt | str_model | StrOutputParser()
result_str = str_chain.invoke({"subject": "LangChain"})
print("StrOutputParser 结果:", result_str)
print()
# ======================== 2. Pydantic 解析器 ========================
class Joke(BaseModel):
setup: str = Field(description="笑话的铺垫,必须以问号结尾")
punchline: str = Field(description="笑话的包袱,回答铺垫问题")
@model_validator(mode='before')
@classmethod
def check_setup_ends_with_question(cls, values: dict) -> dict:
setup = values.get('setup', '')
if not setup.endswith('?'):
raise ValueError(f'setup 必须以问号结尾,当前为: {setup}')
return values
# 模拟模型返回严格符合 Pydantic 的 JSON 字符串
pyd_model_response = '{"setup": "为什么鸡不能过马路?", "punchline": "因为它会被机动车撞到。"}'
pyd_model = FakeListChatModel(responses=[pyd_model_response])
pyd_parser = PydanticOutputParser(pydantic_object=Joke)
format_instructions = pyd_parser.get_format_instructions()
pyd_prompt = ChatPromptTemplate.from_template(
"回答用户的查询\n{format_instructions}\n用户输入:{query}"
)
pyd_prompt = pyd_prompt.partial(format_instructions=format_instructions)
pyd_chain = pyd_prompt | pyd_model | pyd_parser
joke_obj = pyd_chain.invoke({"query": "给我讲一个笑话"})
print("PydanticOutputParser 结果:", joke_obj)
print("字段 setup:", joke_obj.setup)
print("字段 punchline:", joke_obj.punchline)
print()
# ======================== 3. JSON 解析器(自由格式) ========================
json_model_response = '{"joke": "为什么鸡不能过马路?因为它会被机动车撞到。"}'
json_model = FakeListChatModel(responses=[json_model_response])
json_parser = JsonOutputParser()
json_format = json_parser.get_format_instructions()
json_prompt = ChatPromptTemplate.from_template(
"请以 JSON 格式返回一个笑话\n{format_instructions}\n用户输入:{input}"
)
json_prompt = json_prompt.partial(format_instructions=json_format)
json_chain = json_prompt | json_model | json_parser
json_result = json_chain.invoke({"input": "讲个笑话"})
print("JsonOutputParser 结果:", json_result)
print("类型:", type(json_result))
print()
# ======================== 4. XML 解析器(指定标签) ========================
xml_model_response = """<movies>
<movie>
<title>阿甘正传</title>
<year>1994</year>
<actor>汤姆·汉克斯</actor>
</movie>
<movie>
<title>荒岛余生</title>
<year>2000</year>
<actor>汤姆·汉克斯</actor>
</movie>
</movies>"""
xml_model = FakeListChatModel(responses=[xml_model_response])
# 指定顶层标签和内部字段
xml_parser = XMLOutputParser(tags=["movies", "movie", "title", "year", "actor"])
xml_format = xml_parser.get_format_instructions()
xml_prompt = ChatPromptTemplate.from_template(
"根据用户查询生成 XML 列表\n{format_instructions}\n{query}"
)
xml_prompt = xml_prompt.partial(format_instructions=xml_format)
xml_chain = xml_prompt | xml_model | xml_parser
xml_result = xml_chain.invoke({"query": "列出汤姆·汉克斯的电影"})
print("XMLOutputParser 结果 (字典):", xml_result)
print("第一标题:", xml_result["movies"][0]["movie"][0]["title"][0])
print()
# ======================== 5. 流式 JSON 演示(模拟) ========================
# 为演示流式解析,使用一个分段返回的模拟模型
# 由于 FakeListChatModel 不支持 streaming,此处仅给出概念说明。
# 在实际应用中,可使用支持流式的模型替换,解析器会自动处理部分 JSON。
print("流式 JSON 解析概念:解析器在接收到不完整的 JSON 片段时,会等待字段完整再输出。")
结果解读
StrOutputParser直接返回去除多余空白的纯文本。PydanticOutputParser将模型返回的 JSON 反序列化为Joke对象,并执行验证器检查setup是否以问号结尾,失败则抛出异常。JsonOutputParser返回普通字典,适合无需强类型校验的场景;若结合 Pydantic,可生成更严格的 JSON。XMLOutputParser默认将 XML 转为多层嵌套字典,通过tags参数可约束输出结构,避免无关字段。
Pydantic 版本注意事项
LangChain 在不同版本中使用的 Pydantic 版本不同:v0.1 之前同时兼容 Pydantic v1/v2,v0.2 起默认 v2,v0.3 完全弃用 v1。代码示例基于 Pydantic v2,语法与 v1 差异较大(如 model_validator 替代 root_validator),若使用旧版 LangChain 需调整导入和验证器写法。
最佳实践
- 始终将
get_format_instructions()注入提示词,否则模型可能自由发挥。 - 需要严格字段验证时首选
PydanticOutputParser,其错误处理机制能与 LangChain 的OutputFixingParser结合自动修复。 - 处理 XML 时注意解析结果是嵌套字典,访问路径较深,可编写辅助函数提取。
- 流式场景下,
JsonOutputParser能保证任意截断时刻的 JSON 仍为合法片段,便于前端实时渲染。
总结
输出解析器是 LangChain 从模型"模糊输出"到"精确数据"的关键桥梁。文本解析器简单直接,Pydantic 提供强类型保障,JSON 和 XML 覆盖了主流数据交换格式。
使用框架封装好的解析器,不仅能减少重复造轮子,还能充分利用其与模型、提示词模板的深度集成,大幅提升 LLM 应用的工程化水平。