LangChain入门:Prompt模板与结构化输出详解

📅 2026/7/24 18:44:03 👁️ 阅读次数 📝 编程学习
LangChain入门:Prompt模板与结构化输出详解

从手写提示词到结构化数据,一文搞定LangChain核心输出解析


📌 引言

在开发大模型应用时,我们常常面临两个核心问题:

  1. 如何高效地组织和管理提示词(Prompt)—— 避免代码中到处拼接字符串

  2. 如何让模型返回程序可直接使用的结构化数据—— 而不是自然语言段落

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:

  1. 明确角色—— 让模型以特定身份思考

  2. 明确任务—— 说清楚要做什么

  3. 明确约束—— 限制输出格式、字数、风格

  4. 给出输入字段—— 让模型清楚每个变量的含义


三、实战案例(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处理聊天历史

  • 三种输出解析器:StrOutputParserPydanticOutputParserwith_structured_output

  • 结构化输出案例:简历抽取、评论分析、工单分类

  • 处理解析错误和异常

掌握这些技能,你就能构建出既易于维护又能与业务系统无缝对接的 LLM 应用。