AI Agent白手起家37: LangChain 输出解析器实战:文本、JSON、XML 与 Pydantic

内容纲要

  • 输出解析器核心作用:将大模型自然语言输出转换为结构化数据
  • 常用解析器类型
    • StrOutputParser:提取纯文本,无需格式指令
    • PydanticOutputParser:基于 Pydantic 数据模型,精确控制字段和验证
    • JsonOutputParser:自由 JSON 或结合 Pydantic 生成严格 JSON
    • XMLOutputParser:输出字典,可指定标签约束
  • 关键技术点
    • 格式指令注入: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 应用的工程化水平。

相关推荐
灵析表格2 小时前
json_ObjectToKV 函数深度研究报告
json·excel·wps·灵析表格·excel公式盒子
wtsolutions2 小时前
JSON 怎么导入 WPS?怎么转换成 WPS 表格?WPS 怎么打开 JSON?完整教程
json·wps
梦想三三3 小时前
Qwen Function Calling实战:重构电商客服AI Agent
人工智能·python·langchain·大模型·rag
initialize13064 小时前
Oracle数据库 binary XML data类型同步
xml·数据库
hboot13 小时前
AI工程师第六课 - RAG检索增强生成
后端·langchain·llm
用户31268748772019 小时前
AI Agent 开发实战(十一):Multi-Agent 协作编排
langchain·ai编程
(轻舟已过万重山)20 小时前
第27章 框架实操:用 LangChain/LlamaIndex 搭建完整 RAG 系统
人工智能·ai·langchain
御坂嘀喵 白日焰火21 小时前
LINQ之路18:LINQ to XML之导航和查询
xml·solr·linq
残月心殇 请珍惜枸1 天前
LINQ之路17:LINQ to XML之X-DOM介绍
xml·solr·linq