LangChain 实战第 4 章让模型输出程序能直接用的数据结构化输出 系列第 4 篇。前三章我们把 Prompt 写得明明白白模型也能乖乖回答了。但模型默认吐出来的是自然语言——人读着舒服程序读着犯难。这一章解决的就是这个问题把模型输出变成程序能直接resume.name、result.priority这样用的结构化数据。一、本章目标学完本章你应该能理解自然语言输出和结构化输出的区别用StrOutputParser拿到字符串结果用 Pydantic 定义输出结构用PydanticOutputParser解析模型输出用with_structured_output直接拿结构化对象处理结构化输出解析失败的情况完成简历信息抽取和商品评论分析两个实战案例二、为什么需要结构化输出假设系统要从一份简历里提取姓名、工作年限、技能、目标岗位。如果模型返回自然语言候选人姓名是张三工作 3 年熟悉 Python、FastAPI 和 MySQL希望应聘后端开发工程师。程序还得再写一堆正则/规则去抠字段。如果模型返回结构化数据{ name: 张三, years_of_experience: 3, skills: [Python, FastAPI, MySQL], target_position: 后端开发工程师 }程序就能直接读resume.name resume.skills 结构化输出常见用途信息抽取、文本分类、情感分析、工单分类、内容审核、数据入库、调用其他业务接口。一句话——凡是模型结果还要进业务流程的都该用结构化输出。三、Output Parser 是什么Output Parser 专门负责处理模型输出。可以理解成一条流水线模型原始输出 → Output Parser → 程序可用的数据本章重点讲三种方式方式作用StrOutputParser把模型回复转换成字符串PydanticOutputParser把模型回复解析成Pydantic 对象with_structured_output让模型直接按指定结构返回四、StrOutputParser拿到纯文本模型调用返回的通常是一个消息对象不是裸字符串response model.invoke(请介绍 LangChain) print(response.content) # 从 AIMessage 里取文本StrOutputParser可以把这个消息对象转换成普通字符串from langchain_core.output_parsers import StrOutputParser parser StrOutputParser() text parser.invoke(response) print(text)它适合文案生成、内容总结、普通问答、翻译——只要业务只需要文本不一定要上复杂的结构化。 思考一个问题print(response.content)和parser.invoke(response)打印出来都是字符串有啥区别图片无法显示关键区别在这里model.invoke()返回的是AIMessage对象StrOutputParser是 LangChain 标准解析器传入AIMessage时等价于取.content但它还支持多种输入类型不止AIMessage。最重要的实战差异支持链式Pipeline拼接。LangChain 推崇用|把组件串成管道管道里每个组件都得遵守统一协议都继承自Runnable。model、prompt、parser都是Runnable。❌ 错误写法管道里不能直接写.content因为它是属性不是Runnablechain model | response.content✅ 正确写法from langchain_core.output_parsers import StrOutputParser parser StrOutputParser() chain model | parser # Runnable 链式官方标准 result chain.invoke(请介绍 LangChain) print(result) # result 直接就是字符串 这就是StrOutputParser最大的价值作为 Runnable 参与链式拼接。如果只是单独调model.invoke()再打印两者几乎没差别一旦要搭链路必须用解析器。五、案例一文本总结创建01_text_summary.pyfrom langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from utils.model_factory import get_deepSeek_model model get_deepSeek_model() chat_prompt ChatPromptTemplate.from_messages([ ( system, 你是一个内容编辑工程师擅长提炼文本重点, ), (human, 请将下面内容总结成一句话不超过 50 字。 内容{question}), ]) prompt chat_prompt.invoke({ question: LangChain 是一个用于开发大模型应用的框架 提供模型调用、Prompt 管理、文档处理、检索和工具调用等能力。 }) resp model.invoke(prompt) # 第一种写法 print(resp.content) # 第二种写法也是建议写法 parser StrOutputParser() text parser.invoke(resp) print(text)运行python 01_text_summary.py这个案例最终得到的是普通字符串。六、用 Pydantic 定义输出结构Pydantic 用来定义模型应该返回哪些字段、各是什么类型。 小知识Pydantic里的Pydantic源自pedantic/pɪˈdæntɪk/意为严谨教条的、拘泥规则的——正好契合它严格校验数据的脾气。以简历信息为例from pydantic import BaseModel, Field class ResumeInfo(BaseModel): name: str Field(description候选人姓名) years_of_experience: int Field(description工作年限) skills: list[str] Field(description掌握的技术技能) target_position: str Field(description目标岗位)这个模型既描述了字段类型也描述了字段含义通过description。如果模型返回的数据不符合字段类型解析时就会报错——这反而是好事能帮你挡住脏数据。七、PydanticOutputParser让模型按格式返回PydanticOutputParser能根据 Pydantic 模型自动生成格式要求并解析模型的返回结果。创建 Parser 并拿到格式说明from langchain_core.output_parsers import PydanticOutputParser parser PydanticOutputParser(pydantic_objectResumeInfo) format_instructions parser.get_format_instructions()把格式说明塞进 Prompt 的 system 里prompt_template ChatPromptTemplate.from_messages([ ( system, 你是一名招聘信息分析助手。\n{format_instructions}, ), (human, 请从下面简历中提取信息\n{resume_text}), ])最后解析result parser.parse(response.content)八、完整案例简历信息抽取创建02_resume_extractor.pyfrom langchain_core.output_parsers import PydanticOutputParser from langchain_core.prompts import ChatPromptTemplate from pydantic import BaseModel, Field from utils.model_factory import get_deepSeek_model model get_deepSeek_model() class ResumeInfo(BaseModel): name: str Field(description姓名) years_of_experience: int Field(description工作年限) skills: list[str] Field(description掌握的技术技能) target_position: str Field(description目标岗位) parser PydanticOutputParser(pydantic_objectResumeInfo) 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, }) # 调用大模型拿到 AIMessage response model.invoke(prompt) # 把大模型返回值解析成 Pydantic 对象 result parser.invoke(response) print(result) print(result.name) print(result.years_of_experience) print(result.skills) print(result.target_position)预期得到类似name张三 years_of_experience10 skills[python, langchain, fastapi] target_position智能体开发九、with_structured_output更简洁的方式较新的 LangChain 模型组件直接提供了with_structured_output让模型按 Pydantic 模型返回结构化结果structured_model model.with_structured_output(ResumeInfo) result structured_model.invoke(从简历中提取信息)比手动拿格式说明 调 Parser简洁得多。是否支持、底层用哪种方式取决于模型服务能力。⚠️用 DeepSeek 的 OpenAI 兼容接口时要加methodjson_modestructured_model model.with_structured_output( ResumeInfo, methodjson_mode, )Prompt 里要明确要求模型返回 JSON。否则会报这个错openai.BadRequestError: Error code: 400 - {error: {message: This response_format type is unavailable now, ...}}十、案例三商品评论分析本案例用with_structured_output分析一条商品评论。创建03_review_analyzer.pyfrom typing import Literal from langchain_core.prompts import ChatPromptTemplate from pydantic import BaseModel, Field from utils.model_factory import get_deepSeek_model class ReviewAnalysis(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: 鼠标手感不错也很安静但是用了两周滚轮就有异响。 }) structured_model model.with_structured_output( ReviewAnalysis, methodjson_mode, ) response structured_model.invoke(prompt) print(response) print(response.sentiment) print(response.keywords) print(response.summary) print(response.needs_reply) 小贴士Literal[正面, 负面, 中性]是字面量类型约束意思是这个字段只能取这三个值之一类似枚举模型瞎写别的字符串会被拦下。预期结果sentiment负面 keywords[手感, 静音, 滚轮异响] summary用户认可鼠标手感和静音效果但反馈滚轮出现质量问题。 needs_replyTrue 你可能会问methodjson_mode我代码里没看到任何 JSON 啊——json_mode不是让你手动处理 JSON 字符串底层流程是① 模型输出一段合法 JSON 文本② LangChain 内部自动把它解析成你的ReviewAnalysis对象③ 你拿到手直接就是对象所以直观上看不到原始 JSON。十一、两种结构化方式怎么选方式特点适合场景PydanticOutputParser通过 Prompt 要求格式再解析文本想弄清 Parser 工作原理时with_structured_output调用更简洁模型服务支持结构化输出时简单建议先掌握PydanticOutputParser理解格式要求怎么传、解析为什么失败实际项目优先考虑with_structured_output使用前先确认模型服务是否支持对应方式一句话区分PydanticOutputParser是让模型按提示输出 JSON我再本地解析with_structured_output是把结构化能力直接绑在模型调用上让模型/API 尽量按 schema 生成。十二、案例四工单分类智能体客服接到用户反馈后自动把问题分到不同类别、并标优先级。创建04_ticket_classifier.pyfrom typing import Literal from langchain_core.output_parsers import PydanticOutputParser from langchain_core.prompts import ChatPromptTemplate from pydantic import BaseModel, Field from utils.model_factory import get_deepSeek_model class TicketResult(BaseModel): category: Literal[订单, 物流, 退款, 产品, 其他] Field( description工单分类 ) priority: Literal[低, 中, 高] Field(description工单优先级) reason: str Field(description分类原因) model get_deepSeek_model() parser PydanticOutputParser(pydantic_objectTicketResult) 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) if result.priority 高: print(转人工客服优先处理)这个结果能继续进业务流程if result.priority 高: print(转人工客服优先处理)这就是结构化输出的实际价值模型结果能直接驱动后面的程序逻辑。十三、处理解析错误模型输出不稳定时可能解析失败。可以捕获OutputParserExceptionfrom langchain_core.exceptions import OutputParserException try: result parser.parse(response.content) print(result) except OutputParserException as exc: print(结构化输出解析失败) print(模型原始输出, response.content) print(错误信息, exc)处理建议保存模型原始输出方便排查记录错误日志优化 Prompt必要时重新请求模型重要业务不能完全依赖模型自行保证格式下面是带异常处理的完整版在第十二节基础上包一层try/exceptfrom typing import Literal from langchain_core.output_parsers import PydanticOutputParser from langchain_core.prompts import ChatPromptTemplate from pydantic import BaseModel, Field from utils.model_factory import get_deepSeek_model class TicketResult(BaseModel): category: Literal[订单, 物流, 退款, 产品, 其他] Field( description工单分类 ) priority: Literal[低, 中, 高] Field(description工单优先级) reason: str Field(description分类原因) model get_deepSeek_model() parser PydanticOutputParser(pydantic_objectTicketResult) 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) if result.priority 高: print(转人工客服优先处理) except Exception as exception: print(失败的原因是, exception) print(大模型响应的内容是, response.content)十四、本章重点速记普通文本适合人读结构化数据适合程序处理StrOutputParser用于获取字符串Pydantic 模型用于定义输出结构PydanticOutputParser用于解析模型文本with_structured_output能更直接地拿结构化对象DeepSeek 要加methodjson_mode结构化输出失败时要做异常处理十五、常见问题Q1为什么温度要设为 0信息抽取、分类这类任务需要输出稳定所以用temperature0减少随机性。Q2有了 with_structured_output为什么还要学 Parser因为 Parser 能帮你理解模型返回的原始内容是什么、格式要求怎么传给模型、输出解析为什么失败。而且不同模型服务对结构化输出的支持也不同。Q3Pydantic 校验失败怎么办捕获解析异常并记录模型原始输出。重要业务还应增加重试、默认值、人工确认、程序规则校验。目录LangChain 实战第 4 章让模型输出程序能直接用的数据结构化输出一、本章目标二、为什么需要结构化输出三、Output Parser 是什么四、StrOutputParser拿到纯文本五、案例一文本总结六、用 Pydantic 定义输出结构七、PydanticOutputParser让模型按格式返回八、完整案例简历信息抽取九、with_structured_output更简洁的方式十、案例三商品评论分析十一、两种结构化方式怎么选十二、案例四工单分类十三、处理解析错误十四、本章重点速记十五、常见问题