LangChain 结构化输出实战指南:从 StrOutputParser 到 with_structured_output 的完整解决方案

1. 本章目标

  • 理解区别:明白自然语言输出和结构化输出的不同应用场景
  • 掌握基础:使用StrOutputParser获取纯文本结果
  • 定义结构:使用Pydantic定义模型应该返回的数据格式
  • 解析输出:使用PydanticOutputParser解析模型返回的结构化文本
  • 直接获取:使用with_structured_output更简洁地获取结构化对象
  • 处理异常:处理结构化输出失败的情况
  • 实战应用:完成简历信息抽取和商品评论分析等实际案例

学习建议:本章内容层层递进,建议按顺序学习。先从简单的StrOutputParser开始,再逐步学习更复杂的结构化输出。
:::

2. 为什么需要结构化输出?

想象一下,你正在开发一个简历筛选系统,需要从简历中提取以下信息:

  • 姓名
  • 工作年限
  • 技能
  • 目标岗位

场景对比

场景一:模型返回自然语言

候选人姓名是张三,工作 3 年,熟悉 Python、FastAPI 和 MySQL,希望应聘后端开发工程师。

程序收到这段文字后,还需要:

  1. 用正则表达式或NLP技术提取信息
  2. 处理各种表达方式(“工作3年” vs “有3年工作经验”)
  3. 解析技能列表(逗号分隔、顿号分隔等)

场景二:模型返回结构化数据

{"name":"张三","years_of_experience":3,"skills":["Python","FastAPI","MySQL"],"target_position":"后端开发工程师"}

程序可以直接使用:

# 直接访问属性,无需额外解析print(resume.name)# 输出:张三print(resume.skills)# 输出:['Python', 'FastAPI', 'MySQL']

结构化输出的应用场景

  • 信息抽取:从文档中提取特定信息(如简历、合同、报告)
  • 文本分类:将文本归类到预定义的类别中
  • 情感分析:分析文本的情感倾向(正面/负面/中性)
  • 工单分类:自动将用户问题分类到相应部门
  • 内容审核:识别违规内容并分类
  • 数据入库:将非结构化数据转换为数据库可存储的格式
  • 接口调用:将模型输出作为其他业务系统的输入

关键理解:结构化输出让程序能直接处理模型结果,而不是让去阅读和理解。
:::

3. Output Parser 是什么?

Output Parser(输出解析器)是 LangChain 中处理模型输出的组件。你可以把它想象成一个"翻译官":

模型原始输出(AIMessage) → Output Parser → 程序可用的数据

LangChain 中常见的输出方式

方式作用适用场景
StrOutputParser将模型回复转换成字符串只需要文本内容,不关心结构
PydanticOutputParser将模型回复解析成Pydantic 对象需要结构化数据,且模型返回JSON文本
with_structured_output让模型直接按照指定结构返回模型服务支持结构化输出时使用

本章学习路线

  1. 先学StrOutputParser(最简单)
  2. 再学PydanticOutputParser(最常用)
  3. 最后学with_structured_output(最方便)
    :::

4. StrOutputParser:获取纯文本输出

4.1 基本用法

当调用大模型时,返回的通常是AIMessage对象:

# 直接调用模型response=model.invoke("请介绍 LangChain")print(response.content)# 输出:LangChain是一个用于开发大模型应用的框架...

StrOutputParser的作用就是把AIMessage对象转换成普通字符串:

fromlangchain_core.output_parsersimportStrOutputParser parser=StrOutputParser()text=parser.invoke(response)# 将 AIMessage 转换为字符串print(text)

4.2 适用场景

StrOutputParser适合以下场景:

  • 文案生成:广告文案、文章创作
  • 内容总结:长文本摘要
  • 普通问答:知识问答、咨询回复
  • 翻译任务:文本翻译

重要提示:如果业务只需要文本内容,不关心数据结构,使用StrOutputParser就足够了,不需要复杂的结构化输出。
:::

4.3 深入理解:为什么需要 StrOutputParser?

你可能会有疑问:直接打印response.content和使用StrOutputParser有什么区别?

# 方式一:直接打印response=model.invoke("请介绍 LangChain")print(response.content)# 方式二:使用解析器parser=StrOutputParser()text=parser.invoke(response)print(text)

表面上看:两种方式都打印字符串,效果几乎一样。

实际上StrOutputParser的核心价值在于链式(Pipeline)支持

4.4 链式编程的重要性

LangChain 推崇使用管道(|)连接各个组件,形成处理流水线:

# ❌ 错误写法:.content 是属性,不是 Runnable 对象chain=model|response.content# 报错!# ✅ 正确写法:使用 StrOutputParserfromlangchain_core.output_parsersimportStrOutputParser parser=StrOutputParser()chain=model|parser# 正确:parser 是 Runnable 对象result=chain.invoke("请介绍 LangChain")print(result)# result 直接就是字符串,无需再调用 .content

关键区别

  1. 返回类型model.invoke()返回的是AIMessage对象
  2. 解析器作用StrOutputParser是 LangChain 标准输出解析器
  3. 链式支持parserRunnable对象,可以参与链式拼接

总结

  • 如果只是单独调用model.invoke()然后打印,两种方式效果几乎无差别
  • 一旦需要构建处理链路(如:模型 → 解析器 → 后续处理),必须使用StrOutputParser
  • StrOutputParser的最大价值:作为Runnable参与链式拼接
    :::

5. 案例一:文本总结

让我们通过一个简单的文本总结案例,实践StrOutputParser的使用。

5.1 创建脚本文件

创建01_text_summary.py

fromlangchain_core.output_parsersimportStrOutputParserfromlangchain_core.promptsimportChatPromptTemplatefromutils.model_factoryimportget_deepSeek_model# 1. 获取模型model=get_deepSeek_model()# 2. 创建提示词模板chat_prompt=ChatPromptTemplate.from_messages([("system","你是一个内容编辑工程师,擅长提炼文本重点"),("human","""请将下面内容总结成一句话,不超过 50 字。 内容:{question}""")])# 3. 准备输入prompt=chat_prompt.invoke({"question":"LangChain 是一个用于开发大模型应用的框架,""提供模型调用、Prompt 管理、文档处理、检索和工具调用等能力。"})# 4. 调用模型resp=model.invoke(prompt)# 5. 方式一:直接获取内容(不推荐在链式中使用)print("=== 方式一:直接获取 ===")print(resp.content)print()# 6. 方式二:使用 StrOutputParser(推荐)print("=== 方式二:使用解析器 ===")parser=StrOutputParser()text=parser.invoke(resp)print(text)

5.2 运行脚本

python 01_text_summary.py

5.3 运行结果

两种方式都会输出类似的内容:

LangChain是一个大模型应用开发框架,提供模型调用、Prompt管理、文档处理等能力。

5.4 关键点

  • 这个案例最终得到的是普通字符串
  • 方式一和方式二在单独使用时效果相同
  • 方式二(使用StrOutputParser)更适合在链式编程中使用

练习建议:尝试修改提示词,让模型用不同风格总结(如:技术文档风格、产品介绍风格、朋友圈风格)。
:::

6. 使用 Pydantic 定义输出结构

Pydantic 可以用来定义模型应该返回哪些字段。

dantic 源自 pedantic /pɪˈdæntɪk/

✅ pedantic 释义:迂腐的、严谨教条的、拘泥规则的

:::

例如简历信息:

frompydanticimportBaseModel,FieldclassResumeInfo(BaseModel):name:str=Field(description="候选人姓名")years_of_experience:int=Field(description="工作年限")skills:list[str]=Field(description="掌握的技术技能")target_position:str=Field(description="目标岗位")

这个模型既描述了字段类型,也描述了字段含义。

如果模型返回的数据不符合字段类型,解析时就会报错。

7. PydanticOutputParser [ 帕泽 ]

PydanticOutputParser可以根据 Pydantic 模型生成格式要求,并解析模型返回结果。

创建 Parser:

fromlangchain_core.output_parsersimportPydanticOutputParser parser=PydanticOutputParser(pydantic_object=ResumeInfo)

获取格式说明:

instructions 指令
format_instructions=parser.get_format_instructions()

把格式说明传给 Prompt:

prompt_template=ChatPromptTemplate.from_messages([("system","你是一名招聘信息分析助手。\n{format_instructions}",),("human","请从下面简历中提取信息:\n{resume_text}"),])

最后解析:

result = parser.parse(response.content)

8. 完整案例:简历信息抽取

创建02_resume_extractor.py

fromlangchain_core.output_parsersimportPydanticOutputParserfromlangchain_core.promptsimportChatPromptTemplatefrompydanticimportBaseModel,Fieldfromutils.model_factoryimportget_deepSeek_model model=get_deepSeek_model()classResumeInfo(BaseModel):name:str=Field(description="姓名")years_of_experience:int=Field(description="工作年限")skills:list[str]=Field(description="掌握的技术技能")target_position:str=Field(description="目标岗位")parser=PydanticOutputParser(pydantic_object=ResumeInfo)# 格式化指令format_instructions=parser.get_format_instructions()template=ChatPromptTemplate.from_messages([("system",""" 你是一名招聘信息分析助手。 请严格按照指定格式返回结果。 {format_instructions} """),("human","{resume_content}")])resume_content=""" 我叫张三,我干大模型开发10年了,我擅长的技术是 python,langchain,fastapi等,我比较喜欢养猫养狗,我想找一份智能体开发的工作。 """prompt=template.invoke({"format_instructions":format_instructions,"resume_content":resume_content})# 调用大模型,获取AIMessageresponse=model.invoke(prompt)# 将大模型的返回值,进行格式化输出result=parser.invoke(response)print(result)print(result.name)print(result.years_of_experience)print(result.skills)print(result.target_position)

预期得到类似结果:

姓名:张三 工作年限:3 技能:['Python', 'FastAPI', 'MySQL', 'Redis'] 目标岗位:Python 后端开发工程师

9. with_structured_output

较新的 LangChain 模型组件通常提供:

with_structured_output

它可以让模型按照 Pydantic 模型返回结构化结果。

基本写法:

structured_model=model.with_structured_output(ResumeInfo)result=structured_model.invoke("从简历中提取信息")

这种写法比手动获取格式说明、再调用 Parser 更简洁。

但是否支持、底层使用哪种结构化方式,和模型服务能力有关。

使用 DeepSeek 的 OpenAI 兼容接口时,可以使用 JSON 模式

structured_model=model.with_structured_output(ResumeInfo,method="json_mode",)

Prompt 中需要明确要求模型返回 JSON。

假如你使用了 deepseek 大模型,但是没有使用method=“json_mode”,会报如下错误:

openai.BadRequestError:Error code:400-{'error':{'message':'This response_format type is unavailable now','type':'invalid_request_error','param':None,'code':'invalid_request_error'}}

10. 案例三:商品评论分析

本案例使用with_structured_output分析商品评论。

创建03_review_analyzer.py

Literal[“正面”, “中性”, “负面”]

字面量类型约束

表示这个字段只能取三个值中的一个:

“正面” / “中性” / “负面”

不允许其他任何字符串

有点类似于枚举

:::

fromtypingimportLiteralfromlangchain_core.promptsimportChatPromptTemplatefrompydanticimportBaseModel,Fieldfromutils.model_factoryimportget_deepSeek_modelclassReviewAnalysis(BaseModel):sentiment:Literal["正面","负面","中性"]=Field(description="情感分析的值")keywords:list[str]=Field(description="评论中的关键词")summary:str=Field(description="对评论的简短总结")needs_reply:bool=Field(description="商家是否需要回复")model=get_deepSeek_model()template=ChatPromptTemplate.from_messages([("system","""你是专业商品评论分析助手,严格遵守以下规则,仅输出纯JSON,无任何多余文字、解释、markdown: 1. 输出JSON必须包含4个字段:sentiment、keywords、summary、needs_reply,缺一不可; 2. sentiment 仅允许三个中文值:「正面」「中性」「负面」,绝对不能使用 mixed / positive / negative 等英文; 3. keywords 是字符串数组,提取评论核心描述词; 4. summary 用一句话概括整条评论优缺点; 5. needs_reply:商品存在质量问题、故障、严重不满设为true,单纯好评设为false。 """),("human",""" 请分析下面的商品评论: {review} """)])prompt=template.invoke({"review":"鼠标手感不错,也很安静,但是用了两周滚轮就有异响。"})structurted_model=model.with_structured_output(ReviewAnalysis,method="json_mode")response=structurted_model.invoke(prompt)print(response)print(response.sentiment)print(response.keywords)print(response.summary)print(response.needs_reply)

str Literal : 字符串字面量

number Literal : 数值字面量

:::

预期得到类似结果:

情感:负面 关键词:['手感', '静音', '滚轮异响'] 总结:用户认可鼠标手感和静音效果,但反馈滚轮出现质量问题。 是否需要回复:True

问题解析:

这个代码中的 method=“json_mode” 我怎么没有看到任何的 json 格式呢?

method="json_mode"不是让你代码里手动处理 JSON 字符串。 底层流程是这样:

  1. LLM 收到指令,输出一段合法 JSON 文本
  2. LangChain 内部自动把 JSON 解析 → 实例化成你的ReviewAnalysisPydantic 对象
  3. 你拿到手直接是模型对象,看不到原始 JSON

你打印result得到的是 Pydantic 实例,不是原始 JSON 字符串,所以直观上看不到 JSON。

:::

11. 两种结构化方式怎么选

方式特点适合场景
PydanticOutputParser通过 Prompt 要求格式,再解析文本需要明确学习 Parser 工作方式
with_structured_output调用更简洁模型服务支持结构化输出
  • 先掌握PydanticOutputParser
  • 实际项目优先考虑with_structured_output
  • 使用前确认模型服务是否支持对应方式

PydanticOutputParser是“让模型按提示输出 JSON,然后我在本地解析”;with_structured_output是“直接把结构化输出能力绑定到模型调用上,让模型/API 尽量按 schema 生成”。

12. 案例四:工单分类

本案例将用户问题分类为程序可以使用的数据。

智能体客服,接到用户的问题反馈之后,自动将用户的问题,划分到不同的种类,并且显示优先级。

:::

创建04_ticket_classifier.py

fromtypingimportLiteralfromlangchain_core.output_parsersimportPydanticOutputParserfromlangchain_core.promptsimportChatPromptTemplatefrompydanticimportBaseModel,Fieldfromutils.model_factoryimportget_deepSeek_modelclassTicketResult(BaseModel):category:Literal["订单","物流","退款","产品","其他"]=Field(description="工单分类")priority:Literal["低","中","高"]=Field(description="工单优先级")reason:str=Field(description="分类原因")model=get_deepSeek_model()# 得到解析器parser=PydanticOutputParser(pydantic_object=TicketResult)# 得到解析器的规则format_instructions=parser.get_format_instructions()template=ChatPromptTemplate.from_messages([("system","""你是一名客服工单分类助手。 请根据用户问题完成分类。 {format_instructions}"""),("human","用户问题:{question}")])prompt=template.invoke({"format_instructions":format_instructions,"question":"订单显示已签收,但我没有收到商品,请尽快处理。"})response=model.invoke(prompt)result=parser.invoke(response)print(result.category)print(result.priority)print(result.reason)ifresult.priority=="高":print("转人工客服优先处理")

这个结果可以继续用于程序判断:

if result.priority == "高": print("转人工客服优先处理")

这就是结构化输出的实际价值:模型结果可以继续进入业务流程。

13. 处理解析错误

模型输出不稳定时,可能出现解析失败。

可以捕获OutputParserException

fromlangchain_core.exceptionsimportOutputParserExceptiontry:result=parser.parse(response.content)print(result)exceptOutputParserExceptionasexc:print("结构化输出解析失败")print("模型原始输出:",response.content)print("错误信息:",exc)

处理建议:

  • 保存模型原始输出
  • 记录错误日志
  • 优化 Prompt
  • 必要时重新请求模型
  • 重要业务不能完全依赖模型自行保证格式
fromtypingimportLiteralfromlangchain_core.output_parsersimportPydanticOutputParserfromlangchain_core.promptsimportChatPromptTemplatefrompydanticimportBaseModel,Fieldfromutils.model_factoryimportget_deepSeek_modelclassTicketResult(BaseModel):category:Literal["订单","物流","退款","产品","其他"]=Field(description="工单分类")priority:Literal["低","中","高"]=Field(description="工单优先级")reason:str=Field(description="分类原因")model=get_deepSeek_model()# 得到解析器parser=PydanticOutputParser(pydantic_object=TicketResult)# 得到解析器的规则format_instructions=parser.get_format_instructions()template=ChatPromptTemplate.from_messages([("system","""你是一名客服工单分类助手。 请根据用户问题完成分类。 {format_instructions}"""),("human","用户问题:{question}")])prompt=template.invoke({"format_instructions":format_instructions,"question":"订单显示已签收,但我没有收到商品,请尽快处理。"})response=model.invoke(prompt)try:result=parser.invoke(response)print(result.category)print(result.priority)print(result.reason)ifresult.priority=="高":print("转人工客服优先处理")exceptExceptionasexception:print("失败的原因是:",exception)print("大模型响应的内容是:",response.content)

14. 本章重点

本章最重要的是掌握:

  • 普通文本适合人阅读,结构化数据适合程序处理
  • StrOutputParser用于获取字符串
  • Pydantic模型用于定义输出结构
  • PydanticOutputParser用于解析模型文本
  • with_structured_output可以更直接地获取结构化对象
  • 结构化输出失败时需要异常处理