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 应用的工程化水平。

相关推荐
goehou3 小时前
LLM 结构化输出全解:从 Prompt 约束到 Schema 硬保证,三层实现怎么选
ai·llm·json·agent·教程·结构化输出
独立开发者阿乐4 小时前
结构化数据 JSON-LD 实战:让 AI 读懂你的网页
人工智能·json·结构化数据·geo优化·json-ld·ai收录·faqpage
骑着蜗牛撵大象3274 小时前
多 Agent 串行流水线:把一个任务拆成可重试、可续跑的 Pipeline 节点链
前端·langchain
Maiko Star6 小时前
* LangChain 文档切分器:切分策略、源码分析与常用切分器
langchain
~kiss~1 天前
LangChain 基础学习
学习·langchain
制造数据与AI践行者老蒋1 天前
排坑笔记:LangChain 多工具 Agent 完整性校验 return_intermediate_steps 事后核对方案
langchain·ai agent·工具调用·agent开发·排坑笔记·多工具协同·工程化 质量保障
打工仔折腾 AI1 天前
System Prompt 替代 Few-shot:用规则约束大模型输出的省钱实践
java·人工智能·python·spring·langchain·prompt·ai agent 实战
空心木偶☜1 天前
LangChain 概述
python·ai·langchain·ai编程
Maiko Star1 天前
* LangChain RAG 入门:核心原理、环境准备与多种文档加载器
langchain