从手写提示词到结构化数据,一文搞定LangChain核心输出解析
📌 引言
在开发大模型应用时,我们常常面临两个核心问题:
如何高效地组织和管理提示词(Prompt)—— 避免代码中到处拼接字符串
如何让模型返回程序可直接使用的结构化数据—— 而不是自然语言段落
LangChain 提供了完善的解决方案:PromptTemplate系列管理输入,OutputParser系列处理输出。本文将带你从零开始,掌握这两大模块,并完成多个实战案例。
一、Prompt 基础与模板
1.1 什么是 Prompt?
Prompt就是给模型的指令。简单示例:
python
"请用中文介绍一下LangChain。"但在实际项目中,Prompt 往往包含角色设定、任务描述、输入数据和约束条件:
python system = "你是一名资深技术顾问,擅长用通俗语言解释复杂概念。" human = "请向非技术人员介绍LangChain,字数控制在200字以内。"如果每次都在代码中拼接,会产生大量重复和易错代码。Prompt 模板将固定部分与变量分离,提高复用性和可维护性。
1.2 PromptTemplate(纯文本模板)
适用于不需要区分系统/用户角色的简单文本任务。
python from langchain_core.prompts import PromptTemplate template = PromptTemplate.from_template( "请为产品'{product_name}'写一句广告语,突出'{feature}'。" ) prompt = template.invoke({"product_name": "智能手环", "feature": "超长续航"}) print(prompt) # 输出:请为产品'智能手环'写一句广告语,突出'超长续航'。1.3 ChatPromptTemplate(聊天消息模板)
推荐使用,因为它可以构建结构化的消息列表(SystemMessage / HumanMessage / AIMessage),更符合现代对话模型的输入习惯。
python from langchain_core.prompts import ChatPromptTemplate chat_template = ChatPromptTemplate.from_messages([ ("system", "你是一位{role},回答要简洁专业。"), ("human", "{question}") ]) prompt_value = chat_template.invoke({ "role": "AI产品经理", "question": "什么是RAG?" }) # 内部会生成 [SystemMessage, HumanMessage]| 模板类型 | 输出形式 | 适用场景 |
|---|---|---|
PromptTemplate | 纯字符串 | 简单文本生成,无角色区分 |
ChatPromptTemplate | 消息列表 | 聊天、Agent、多轮对话 |
二、Prompt 编写最佳实践
掌握以下四个原则,就能写出高质量的 Prompt:
明确角色—— 让模型以特定身份思考
明确任务—— 说清楚要做什么
明确约束—— 限制输出格式、字数、风格
给出输入字段—— 让模型清楚每个变量的含义
三、实战案例(Prompt 篇)
案例1:商品文案生成器
根据商品信息自动生成电商文案。
代码01_product_copywriter.py:
python from langchain_core.prompts import ChatPromptTemplate from utils.model_factory import get_deepSeek_model model = get_deepSeek_model() template = ChatPromptTemplate.from_messages([ ("system", "你是一名资深电商文案写手,擅长用吸引人的语言描述产品。"), ("human", """ 请为以下商品撰写一段电商文案(150字以内),包含标题和卖点。 商品名称:{name} 核心卖点:{feature} 适用人群:{target} """) ]) prompt = template.invoke({ "name": "智能保温杯", "feature": "24小时保温,智能测温,健康提醒", "target": "注重健康的办公族" }) response = model.invoke(prompt) print(response.content)案例2:学习计划生成器
根据学习目标生成分阶段计划。
代码02_study_plan.py:
python from langchain_core.prompts import ChatPromptTemplate from utils.model_factory import get_deepSeek_model model = get_deepSeek_model() template = ChatPromptTemplate.from_messages([ ("system", "你是一位资深学习规划师。"), ("human", """ 为以下学习目标制定一份为期{days}天的学习计划。 目标:{goal} 当前水平:{level} 每日可用时间:{hours}小时 请按阶段列出每日任务,并给出学习建议。 """) ]) prompt = template.invoke({ "goal": "从零开始学习Python数据分析", "days": 30, "level": "零基础", "hours": 2 }) response = model.invoke(prompt) print(response.content)案例3:客服回复生成器
模拟根据用户问题和订单信息生成回复。
代码03_customer_reply.py:
python from langchain_core.prompts import ChatPromptTemplate from utils.model_factory import get_deepSeek_model model = get_deepSeek_model() template = ChatPromptTemplate.from_messages([ ("system", "你是一名专业客服,回复要礼貌且解决问题。"), ("human", """ 用户问题:{question} 订单状态:{status} 物流信息:{logistics} 请生成一段客服回复,给出明确解决方案。 """) ]) prompt = template.invoke({ "question": "我的快递显示签收但我没收到", "status": "已签收", "logistics": "2025-01-15 10:00 已由门卫代收" }) response = model.invoke(prompt) print(response.content)四、MessagesPlaceholder:插入多轮对话历史
在构建聊天应用时,经常需要将历史消息插入到模板中。MessagesPlaceholder专门用于此。
python from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个乐于助人的AI助手。"), MessagesPlaceholder("history", optional=True, n_messages=4), # 最多保留4条 ("human", "{question}") ]) history = [ ("human", "我叫小明"), ("ai", "你好小明,有什么可以帮你?"), ("human", "我喜欢Python"), ("ai", "Python是很好的语言!") ] prompt_value = prompt.invoke({ "history": history, "question": "我刚才说了什么?" }) # 实际只会插入最近4条历史消息(如果超过)| 参数 | 说明 |
|---|---|
variable_name | 输入变量名 |
optional | 是否必须传入(默认False) |
n_messages | 最多保留最近几条消息 |
五、结构化输出:让程序读懂模型结果
模型返回的自然语言虽然易读,但程序难以直接处理。我们需要让模型输出JSON或其他结构化格式。
5.1 StrOutputParser:简单的字符串提取
如果只想要纯文本内容,StrOutputParser可以将AIMessage转为普通字符串,并支持管道组合。
python from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate model = get_deepSeek_model() parser = StrOutputParser() chain = ChatPromptTemplate.from_template("翻译成中文:{text}") | model | parser result = chain.invoke({"text": "LangChain is awesome!"}) print(result) # 直接打印字符串为什么不用response.content?
在管道(|)中,每个组件必须是Runnable,而.content是属性,不是 Runnable。StrOutputParser正是为了合规接入管道。
5.2 Pydantic 定义输出结构
使用Pydantic声明期望的字段及类型,让模型按此格式返回。
python from pydantic import BaseModel, Field from typing import Literal class ResumeInfo(BaseModel): name: str = Field(description="候选人姓名") years_of_experience: int = Field(description="工作年限") skills: list[str] = Field(description="掌握的技术技能") target_position: str = Field(description="目标岗位")5.3 PydanticOutputParser:手动解析
该解析器会根据 Pydantic 模型生成格式说明(get_format_instructions())并嵌入 Prompt,最后将模型输出解析成对象。
案例:简历信息抽取 (02_resume_extractor.py)
python 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 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_object=ResumeInfo) format_instructions = parser.get_format_instructions() template = ChatPromptTemplate.from_messages([ ("system", "你是一名招聘分析助手。\n{format_instructions}"), ("human", "{resume_content}") ]) resume_text = """ 我叫张三,工作10年,擅长Python、LangChain、FastAPI,想找智能体开发岗位。 """ prompt = template.invoke({ "format_instructions": format_instructions, "resume_content": resume_text }) response = model.invoke(prompt) result = parser.invoke(response) print(result.name) # 张三 print(result.years_of_experience) # 10 print(result.skills) # ['Python', 'LangChain', 'FastAPI'] print(result.target_position) # 智能体开发5.4 with_structured_output:更简洁的方式
如果模型服务支持(如 OpenAI、DeepSeek 的 JSON 模式),可以直接在模型上绑定输出结构。
python structured_model = model.with_structured_output(ResumeInfo, method="json_mode") result = structured_model.invoke("从简历中提取信息:...") # 返回的 result 就是 ResumeInfo 实例注意:使用 DeepSeek 时需指定method="json_mode",否则会报错。
案例:商品评论分析 (03_review_analyzer.py)
python from 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() structured_model = model.with_structured_output(ReviewAnalysis, method="json_mode") template = ChatPromptTemplate.from_messages([ ("system", "你是评论分析专家,严格按JSON格式输出。"), ("human", "分析评论:{review}") ]) prompt = template.invoke({ "review": "鼠标手感不错,也很安静,但是用了两周滚轮就有异响。" }) result = structured_model.invoke(prompt) print(result.sentiment) # 负面 print(result.keywords) # ['手感', '安静', '滚轮异响'] print(result.summary) # 用户认可手感但反映滚轮质量问题 print(result.needs_reply)# True案例:工单分类 (04_ticket_classifier.py)
python from 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="分类原因") parser = PydanticOutputParser(pydantic_object=TicketResult) format_instructions = parser.get_format_instructions() template = ChatPromptTemplate.from_messages([ ("system", "客服工单分类助手。\n{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) # 高 if result.priority == "高": print("立即转人工!")六、处理解析错误
模型输出不稳定时,可能返回不符合结构的内容。应捕获OutputParserException或通用异常。
python from langchain_core.exceptions import OutputParserException try: result = parser.parse(response.content) except OutputParserException as e: print("解析失败,原始输出:", response.content) print("错误信息:", e) # 可进行重试、日志记录或人工干预七、结构化方式对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
StrOutputParser | 简单、管道兼容 | 仅返回字符串 | 摘要、翻译、问答 |
PydanticOutputParser | 清晰、可自定义 | 需手动拼接格式说明 | 需要精细控制解析过程 |
with_structured_output | 代码简洁 | 依赖模型服务支持 | 现代大模型(OpenAI/DeepSeek等) |
建议:先掌握
PydanticOutputParser理解原理,实际项目优先使用with_structured_output。
八、工具函数封装:减少重复代码
为了让案例更干净,我们将模型初始化抽到utils/model_factory.py:
python # utils/model_factory.py from langchain_openai import ChatOpenAI import os def get_deepSeek_model(temperature=0): return ChatOpenAI( model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com/v1", temperature=temperature )这样每个案例只需导入get_deepSeek_model即可。
九、常见问题解答
Q1:Prompt 越长越好吗?
不是。应清晰简洁,避免过多无关要求,否则模型可能顾此失彼。
Q2:模型为什么不完全按指令输出?
大模型是概率性生成,不能保证100%服从。若输出格式关键,需配合结构化输出和程序校验。
Q3:PromptTemplatevsChatPromptTemplate?
如果模型支持多角色消息(System/Human),优先用ChatPromptTemplate,本课程所有案例均使用它。
Q4:为什么temperature=0?
对于信息抽取、分类等确定性任务,温度设为0可获得最稳定输出。
Q5:with_structured_output没看到 JSON 字符串?
因为 LangChain 内部已将 JSON 自动解析为 Pydantic 对象,你拿到的直接就是模型实例。
📝 总结
通过本文,你学会了:
使用
PromptTemplate/ChatPromptTemplate管理提示词编写高质量 Prompt 的四个原则
三个业务案例(文案、计划、客服)实战
MessagesPlaceholder处理聊天历史三种输出解析器:
StrOutputParser、PydanticOutputParser、with_structured_output结构化输出案例:简历抽取、评论分析、工单分类
处理解析错误和异常
掌握这些技能,你就能构建出既易于维护又能与业务系统无缝对接的 LLM 应用。