LangChain提示词工程与结构化输出实战

LangChain提示词工程与结构化输出实战 1. Prompt 模板与提示词工程概述在大语言模型LLM应用开发中Prompt提示词是用户与模型之间沟通的桥梁。而提示词工程Prompt Engineering则是通过精心设计 Prompt 来引导模型生成高质量输出的实践方法论。随着 LangChain 等框架的普及Prompt 模板成为工程化构建 Prompt 的核心工具——它让我们从手工拼接字符串中解放出来实现可复用、可维护的提示词管理。同样重要的还有结构化输出当我们需要模型返回 JSON、特定字段或固定格式的数据时Output Parser就派上了用场。它负责将模型生成的原始文本解析为程序可直接消费的结构化对象打通模型输出 → 业务代码的最后一公里。本文将从 PromptTemplate 的基本使用讲起逐步深入到 ChatPromptTemplate、Prompt 编写技巧、实战案例再到结构化输出与 Output Parser 的完整实践帮助你系统掌握这两个关键技能。2. 什么是 Prompt为什么需要 Prompt 模板Prompt就是你发给大语言模型的输入文本。它可以是一句话、一段指令也可以是一组对话消息。Prompt 的质量直接决定了模型输出的质量——同样的模型不同 Prompt 得到的回答可能天差地别。但在实际项目中我们往往需要动态构建Prompt根据用户输入、业务上下文动态填充某些变量。比如电商场景中商品名称、卖点、风格要求都是变化的。如果每次都用 f-string 拼接prompt f请为{product}生成一段{style}风格的营销文案突出{feature}卖点这样做有几个明显的问题难以维护Prompt 模板散落在各处修改时需要全局搜索容易出错变量多了以后引号、换行、缩进很容易搞混无法复用每个场景都要重写拼接逻辑缺乏验证没法自动检查 Prompt 中是否遗漏了必填变量因此LangChain 提供了PromptTemplate和ChatPromptTemplate这两个核心类让我们用声明式的方式管理 Prompt。3. PromptTemplate 基本使用PromptTemplate是最基础的 Prompt 模板类适用于纯文本补全模型或只需要一个字符串作为输入的场景。它的使用非常简单from langchain_core.prompts import PromptTemplate 定义模板用 {变量名} 占位 template 请用{language}语言写一段代码实现{functionality}功能 prompt PromptTemplate( templatetemplate, input_variables[language, functionality] ) 填充变量得到最终 Prompt final_prompt prompt.format( languagePython, functionality快速排序 ) print(final_prompt) 输出请用Python语言写一段代码实现快速排序功能核心要点使用{变量名}作为占位符input_variables声明需要哪些变量LangChain 会做校验调用.format()即可得到最终 Prompt 字符串如果变量缺失会直接报错避免静默失败4. ChatPromptTemplate 基本使用现代大模型如 GPT-4、Claude大多是对话模型输入输出以消息列表的形式组织。每条消息包含role角色和content内容常见的角色有system系统级指令设定模型的行为和角色human用户输入ai模型回复用于多轮对话的历史记录ChatPromptTemplate就是为这种消息列表结构设计的from langchain_core.prompts import ChatPromptTemplate chat_prompt ChatPromptTemplate.from_messages([ (system, 你是一位经验丰富的{role}工程师擅长{skill}。), (human, 请解释一下{concept}的核心原理用通俗易懂的方式。), ]) messages chat_prompt.format_messages( role后端, skill系统架构设计, concept消息队列 ) for msg in messages: print(f[{msg.type}] {msg.content}) [system] 你是一位经验丰富的后端工程师擅长系统架构设计。 [human] 请解释一下消息队列的核心原理用通俗易懂的方式。5. 核心本质差异很多初学者容易混淆 PromptTemplate 和 ChatPromptTemplate这里用一张对比表说清楚# PromptTemplate只生成一个字符串 prompt PromptTemplate(template你好{name}, input_variables[name]) result prompt.format(name小明) # 返回 str: 你好小明 ChatPromptTemplate生成消息列表 chat_prompt ChatPromptTemplate.from_messages([ (system, 你是助手), (human, 你好{name}), ]) result chat_prompt.format_messages(name小明) # 返回 List[BaseMessage]维度PromptTemplateChatPromptTemplate输出类型字符串str消息列表List[BaseMessage]适用场景旧版补全模型、简单文本对话模型GPT/Claude 等结构单一文本块system/human/ai 多角色多轮对话支持需手动拼接历史天然支持 MessagesPlaceholder推荐程度了解即可建议优先使用一句话总结除非你确定在用一个只接受字符串的老模型否则直接用 ChatPromptTemplate它更现代、更灵活也是 LangChain 官方推荐的方式。6. Prompt 编写建议写好 Prompt 是一门手艺活。下面四条建议来自大量实战总结能显著提升模型输出的质量和稳定性。6.1 明确角色给模型设定一个具体、专业的角色能大幅提升回答的专业度和风格匹配度。# ❌ 模糊 帮我写一份简历 ✅ 明确角色 你是一位有10年经验的资深HR和职业规划师擅长为技术岗位优化简历。角色越具体模型越容易进入状态。可以结合领域、经验年限、技能特长来刻画角色。6.2 明确任务任务描述要具体、可操作避免笼统的指令。# ❌ 笼统 分析一下这段代码 ✅ 具体 分析下面这段 Python 代码从以下三个角度给出建议 性能瓶颈 潜在 Bug 代码风格改进用编号列表明确任务维度既能引导模型思考也方便你验收结果。6.3 明确约束告诉模型不要做什么往往和告诉它要做什么同样重要。# 常用的约束示例 请遵守以下约束 - 回答长度控制在 200 字以内 - 不要使用专业术语用大白话解释 - 如果问题超出你的知识范围直接说「我不确定」不要编造 - 输出格式为 Markdown6.4 给出输入字段在模板中明确标注哪些是动态输入并使用清晰易懂的变量名。chat_prompt ChatPromptTemplate.from_messages([ (system, 你是{company}的{role}风格{style}。), (human, 产品名称{product_name}\n目标人群{audience}\n核心卖点{selling_point}\n请生成一段营销文案。), ])变量名要见名知意比如用 product_name 而不是 p1用 audience 而不是 aud。这样不仅你自己好维护后续团队成员也能快速理解。7. 实战案例一商品文案生成器综合运用上述技巧我们来做一个完整的商品文案生成器from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI 1. 定义 Prompt 模板 prompt ChatPromptTemplate.from_messages([ (system, 你是一位资深电商文案策划有8年品牌营销经验。 你擅长根据产品特点创作富有感染力的营销文案。 风格要求{style}), (human, 请为以下商品生成营销文案 商品名称{product_name} 目标人群{audience} 核心卖点{selling_point} 额外要求{requirements}), ]) 2. 填充变量 messages prompt.format_messages( style简洁有力口语化带emoji, product_name云感记忆枕, audience25-40岁久坐办公人群, selling_point3D分区承托自适应颈椎曲线透气凝胶材质, requirements文案分两段第一段戳痛点第二段讲解决方案。总共不超过150字。 ) 3. 调用模型 llm ChatOpenAI(modelgpt-4o, temperature0.7) response llm.invoke(messages) print(response.content)模型输出示例每天对着屏幕10小时脖子僵得像根钢筋翻来覆去睡不着早上起来肩膀更酸了云感记忆枕3D分区承托你的每一寸颈椎曲线自适应贴合不悬空。透气凝胶材质整晚清凉不闷汗让你一觉醒来像做了SPA一样轻松~ ☁️这个案例把角色、任务、约束、输入字段四条建议全部落地了。模板里变量清晰、结构分明改一个参数就能适配不同商品。8. 减少重复代码封装模型初始化在实际项目中如果每个功能都要写一遍初始化模型 → 定义模板 → 填充变量 → 调用代码会非常冗余。最佳实践是封装一个工厂函数from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI class LLMChain: 轻量级 Chain 封装减少样板代码 def __init__(self, model_namegpt-4o, temperature0.7): self.llm ChatOpenAI(modelmodel_name, temperaturetemperature) def create_chain(self, system_template, human_template): 根据 system 和 human 模板创建可调用的链 prompt ChatPromptTemplate.from_messages([ (system, system_template), (human, human_template), ]) return prompt | self.llm # LCEL 链式调用 使用示例一行创建一行调用 chain LLMChain().create_chain( system_template你是{role}专家。, human_template请解释{concept}。 ) result chain.invoke({role: 数据库, concept: 索引优化}) print(result.content)使用LCELLangChain Expression Language的管道操作符|可以把 Prompt 和模型串成一个可调用的链。后续所有案例都可以复用这个封装代码量减少 70% 以上。9. 实战案例二学习计划生成器这个案例展示如何用更复杂的约束和多维度输入来生成个性化内容prompt ChatPromptTemplate.from_messages([ (system, 你是一位专业的学习规划师擅长为不同背景的学习者定制学习路径。 请严格遵循以下规则 根据学习者当前的{current_level}水平制定计划 总学习周期为{duration}周 每天可用学习时间约{hours_per_day}小时 最终目标{goal} 输出格式按周列出学习主题和关键任务使用 Markdown 格式), (human, 请为我想学习{subject}制定一份详细的学习计划。), ]) messages prompt.format_messages( subjectPython 数据分析, current_level有其他编程语言基础但 Python 零基础, duration4, hours_per_day2, goal能够独立完成数据清洗、可视化和基础统计分析做出可交付的数据报告 ) response llm.invoke(messages)这里的关键是把约束条件模板化。current_level、duration、hours_per_day、goal 这些变量在不同学员之间各不相同但 Prompt 结构是稳定的。模板化之后你甚至可以用它来批量生成学习计划。10. 实战案例三客服回复生成器客服场景除了要生成回复往往还需要情绪判断和上下文理解。这个案例还展示了如何把MessagesPlaceholder融入真实业务from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder customer_service_prompt ChatPromptTemplate.from_messages([ (system, 你是{company_name}的客服代表。 回复风格{style} 公司政策{policy} 请根据用户问题和历史对话生成专业、友善的回复。 如果问题无法解决请引导用户拨打客服热线 400-xxx-xxxx。), MessagesPlaceholder(variable_namehistory), # 历史对话 (human, {user_query}), ]) 使用示例 messages customer_service_prompt.format_messages( company_name云笔记科技, style亲切但不啰嗦每句话不超过30字, policy7天无理由退货30天换货1年保修, history[], # 第一轮对话暂时为空 user_query我昨天买的 Pro 版会员为什么还是不能用 AI 功能 ) response llm.invoke(messages)这个模板在真实场景中可以直接对接对话历史管理系统。每轮对话把历史消息存起来下一轮丢进 history 变量模型就能记住之前的上下文。11. MessagesPlaceholder 详解MessagesPlaceholder是 ChatPromptTemplate 中的一个特殊占位符用于动态插入一组消息而不是一个字符串。它的典型应用场景有多轮对话历史把之前的 human/ai 消息列表放进去让模型记住上下文Few-shot 示例插入若干条示例对话教模型如何回答动态指令根据业务条件动态决定是否添加某条 system 消息Few-shot 示例的用法examples [ (human, 我的产品有质量问题), (ai, 很抱歉给您带来不便。请问具体是什么问题呢我会尽快为您处理。), (human, 收到后屏幕有坏点), (ai, 非常抱歉根据我们的售后政策您可以申请换货。请提供订单号我马上为您办理。), ] few_shot_prompt ChatPromptTemplate.from_messages([ (system, 你是专业客服。参考以下示例来回复用户。), MessagesPlaceholder(variable_nameexamples), # 示例对话 (human, {user_query}), ]) messages few_shot_prompt.format_messages( examplesexamples, user_query我买的键盘有几个键不灵敏 )注意MessagesPlaceholder 的 variable_name 必须在 format_messages 时传入一个消息列表List[tuple] 或 List[BaseMessage]不能传字符串。12. 本章重点回顾在进入结构化输出部分之前先快速回顾 Prompt 模板的核心要点ChatPromptTemplate 优先现代对话模型的最佳搭档支持多角色消息变量用 {变量名} 占位声明 input_variables 做校验避免遗漏编写四步法明确角色 → 明确任务 → 明确约束 → 给出输入字段MessagesPlaceholder动态插入消息列表支持多轮对话和 Few-shot封装复用用工厂函数或 LCEL 链减少样板代码常见问题Q: Prompt 越长越好吗不是。Prompt 越长模型的注意力越分散容易遗忘中间的指令。最佳实践是简短有力角色设定 1-2 句任务描述 2-3 句约束条件用列表形式输入字段集中放在末尾。如果确实需要大量上下文优先使用 RAG 把资料放在检索结果里而不是全部塞进 Prompt。Q: 为什么模型没有完全按要求输出常见原因有几个指令不够明确写一篇好文章和写一篇 800 字的技术教程包含 3 个代码示例效果天差地别约束之间冲突比如同时要求详细解释和在 50 字以内模型会无所适从模型能力限制小模型在复杂指令上的遵循度天然低于大模型需要调整期望输出格式未显式要求如果你需要 JSON务必在 Prompt 里说请返回 JSON 格式并通过 Output Parser 约束——这正是下一章要讲的内容13. 结构化输出为什么要用 Output Parser到目前为止我们的模型输出都是自由文本——一段话、一篇文章。但在实际业务中我们往往需要模型返回结构化数据比如从简历中抽取姓名、电话、工作经历字典对商品评论输出情感、评分、关键词JSON从合同文本中提取甲方、乙方、金额、日期Pydantic 模型如果直接让模型返回 JSON结果可能是# 模型输出的原始文本 好的这是抽取结果\n{\n name: 张三,\n phone: 138xxxx\n}\n希望对你有所帮助这种包裹了额外文字的 JSON 无法直接用 json.loads() 解析。这就是Output Parser要解决的问题——把模型的原始输出清洗、提取、校验、转换为程序可消费的数据结构。14. StrOutputParser最简单的解析器StrOutputParser是最基础的解析器它做的事情很简单把模型返回的 AIMessage 对象提取出纯文本内容。from langchain_core.output_parsers import StrOutputParser 构建链Prompt → 模型 → 解析器 chain prompt | llm | StrOutputParser() 直接得到字符串不需要 .content result chain.invoke({text: 人工智能的未来发展趋势}) print(type(result)) # class str print(result) # 直接是文本内容在 LCEL 链中StrOutputParser 通常作为最后一个节点使得下游代码直接拿到字符串无需关心消息对象的内部结构。案例文本总结summarize_prompt ChatPromptTemplate.from_messages([ (system, 请用一句话总结以下文本的核心内容。), (human, {text}), ]) summarize_chain summarize_prompt | llm | StrOutputParser() summary summarize_chain.invoke({ text: LangChain 是一个用于构建 LLM 应用的开源框架它提供了 Prompt 管理、Chain 编排、Agent 调度等核心能力大幅降低了开发门槛。 }) print(summary) LangChain是一个简化LLM应用开发的开源框架提供Prompt管理、Chain编排和Agent调度等核心功能。15. 使用 Pydantic 定义输出结构当输出结构比较复杂时用Pydantic定义数据模型是最佳选择。Pydantic 提供类型校验、默认值、字段描述LangChain 能自动把字段描述注入 Prompt引导模型按格式输出。from pydantic import BaseModel, Field from typing import List, Optional class ResumeInfo(BaseModel): 简历信息结构 name: str Field(description求职者姓名) phone: str Field(description手机号码) email: Optional[str] Field(defaultNone, description电子邮箱) education: List[str] Field(description教育经历列表每项包含学校、专业、学位) skills: List[str] Field(description技能标签列表) work_experience: List[str] Field(description工作经历列表)Field 中的description会被自动取出来告诉模型这是结构化输出的关键——让模型理解每个字段的含义和期望的值类型。16. PydanticOutputParser 完整案例简历信息抽取from langchain_core.output_parsers import PydanticOutputParser from langchain_core.prompts import ChatPromptTemplate from pydantic import BaseModel, Field from typing import List class ResumeInfo(BaseModel): name: str Field(description求职者姓名) phone: str Field(description手机号码) skills: List[str] Field(description技能列表) 1. 创建解析器 parser PydanticOutputParser(pydantic_objectResumeInfo) 2. 获取格式化指令自动生成 format_instructions parser.get_format_instructions() print(format_instructions) 输出类似 The output should be formatted as a JSON instance that conforms to the JSON schema below. {properties: {name: {description: 求职者姓名, type: string}, ...}} 3. 把格式化指令注入 Prompt prompt ChatPromptTemplate.from_messages([ (system, 你是简历解析助手。\n{format_instructions}), (human, 请从以下简历文本中抽取信息\n{resume_text}), ]) 4. 构建链 chain prompt | llm | parser # parser 会把 JSON 解析为 ResumeInfo 对象 5. 调用 result chain.invoke({ format_instructions: format_instructions, resume_text: 张三手机13812345678熟练掌握Python、LangChain、FastAPI 有3年后端开发经验。擅长系统架构设计和性能优化。 }) print(type(result)) # class ResumeInfo print(result.name) # 张三 print(result.phone) # 13812345678 print(result.skills) # [Python, LangChain, FastAPI, 系统架构设计, 性能优化]关键步骤创建 Pydantic 模型 → 创建 PydanticOutputParser → 获取格式化指令 → 注入 Prompt → 链式调用。parser.get_format_instructions() 会自动生成 JSON Schema 描述省去了手动编写请返回以下 JSON 格式的麻烦。17. with_structured_output更简洁的方式LangChain 还提供了with_structured_output方法它把要求模型按指定结构输出的指令直接嵌入模型调用层语法更简洁# 方式一直接用 Pydantic 模型 structured_llm ChatOpenAI(modelgpt-4o).with_structured_output(ResumeInfo) 不需要手动写 Prompt 模板不需要 get_format_instructions result structured_llm.invoke( 李四电话13987654321精通Java、Spring Boot、MySQL 5年电商系统开发经验曾主导双11大促系统架构。 ) print(type(result)) # class ResumeInfo print(result.name) # 李四你甚至可以把 Prompt 模板和结构化输出组合使用structured_llm ChatOpenAI(modelgpt-4o).with_structured_output(ResumeInfo) prompt ChatPromptTemplate.from_messages([ (system, 你是一个简历解析助手请从以下文本中提取信息。), (human, {resume_text}), ]) chain prompt | structured_llm # 链末端是结构化 LLM result chain.invoke({resume_text: 王五电话18800001111擅长React、TypeScript...})18. 实战案例三商品评论分析这个案例综合使用 PydanticOutputParser对商品评论进行情感分析 结构化提取from pydantic import BaseModel, Field from typing import List, Literal from langchain_core.output_parsers import PydanticOutputParser from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate 1. 定义输出结构 class ReviewAnalysis(BaseModel): sentiment: Literal[positive, negative, neutral] Field( description评论情感倾向positive 正面negative 负面neutral 中性 ) score: int Field(description评分 1-5 分, ge1, le5) keywords: List[str] Field(description从评论中提取的关键词列表) summary: str Field(description一句话总结评论核心观点) actionable: bool Field(description是否需要客服跟进处理) 2. 创建解析器和链 parser PydanticOutputParser(pydantic_objectReviewAnalysis) llm ChatOpenAI(modelgpt-4o, temperature0) prompt ChatPromptTemplate.from_messages([ (system, 你是电商评论分析助手。\n{format_instructions}), (human, 请分析以下商品评论\n{review}), ]) chain prompt | llm | parser 3. 分析 result chain.invoke({ format_instructions: parser.get_format_instructions(), review: 用了两周了续航确实不错屏幕也清晰。就是充电器发热有点厉害有点担心安全问题。总体来说还行吧。, }) print(f情感: {result.sentiment}) print(f评分: {result.score}) print(f关键词: {result.keywords}) print(f总结: {result.summary}) print(f需跟进: {result.actionable})模型输出结果# 情感: neutral # 评分: 3 # 关键词: [续航, 屏幕, 充电器, 发热, 安全] # 总结: 续航和屏幕表现好但充电器发热存在安全隐患整体满意但有顾虑。 # 需跟进: True19. 两种结构化方式怎么选维度PydanticOutputParserwith_structured_output实现方式通过 Prompt 注入 JSON Schema 指令利用模型原生 Function Calling 能力依赖任何模型只依赖文本生成能力需要模型支持 Tool Calling / JSON Mode可靠性中等模型可能输出不合规 JSON高模型直接返回结构化数据灵活性高可自定义格式指令和解析逻辑中依赖模型厂商实现进度提示需要在 Prompt 中手动描述格式自动处理代码更简洁推荐场景小众模型、需要精细控制格式时GPT-4/Claude 等主流模型首选建议如果你用的是 GPT-4、Claude 等支持结构化输出的模型优先使用with_structured_output代码更少、可靠性更高。如果模型不支持或者你需要非常定制的输出格式再使用 PydanticOutputParser。20. 处理解析错误使用 PydanticOutputParser 时模型偶尔会输出不合规的 JSON比如多了前后文、少了引号导致解析失败。需要用OutputFixingParser来自动修复from langchain.output_parsers import OutputFixingParser 创建修复解析器包装原始 parser 一个 LLM 用于修复 fixing_parser OutputFixingParser.from_llm( llmChatOpenAI(modelgpt-4o, temperature0), parserparser, # 原始的 PydanticOutputParser ) 使用修复解析器替代原始解析器 chain prompt | llm | fixing_parser 即使模型输出格式有小问题fixing_parser 也会用 LLM 自动修复后重新解析它的工作原理是如果第一次解析失败就把原始输出 错误信息 期望的格式一起发给 LLM让 LLM 修复格式后重新解析。这在生产环境中非常实用能显著降低解析失败率。21. 总结本文从 Prompt 模板和结构化输出两条主线出发覆盖了 LangChain 提示词工程的核心知识点PromptTemplate vs ChatPromptTemplate前者返回字符串后者返回消息列表优先用后者编写四步法明确角色、明确任务、明确约束、给出输入字段MessagesPlaceholder动态插入消息列表支持多轮对话和 Few-shot封装复用用 LLMChain 封装或 LCEL 管道减少样板代码结构化输出用 Pydantic 定义数据模型Output Parser 做解析校验两种方式对比主流模型优先用 with_structured_output需精细控制或用小众模型时用 PydanticOutputParser错误处理OutputFixingParser 自动修复解析失败提升鲁棒性掌握这些技能后你就能工程化地构建 LLM 应用——Prompt 模板化管理输出结构化消费让模型真正融入业务流水线。