1. 项目概述为什么我们需要提示模板如果你已经开始接触大语言模型的应用开发无论是用 LangChain 还是直接调用 OpenAI 的 API大概率都写过类似这样的代码prompt f请根据以下用户输入生成一段友好的回复。 用户输入{user_input} 请确保回复的语气热情且专业。看起来很简单对吧但随着项目复杂度的提升你会发现事情开始变得棘手。昨天产品经理说要在回复前加上当前日期今天测试同学反馈说某些场景下需要引用历史对话明天老板又要求支持多语言切换……很快你那原本清晰的代码就会变成一堆令人头疼的字符串拼接和条件判断。这就是“提示工程基础”中Prompt Templates要解决的核心问题。它不是一个炫酷的新算法而是一个朴素却至关重要的工程实践将提示词的结构与内容分离。简单说就是把那些固定不变的框架比如指令、格式要求、示例和动态变化的部分用户输入、查询结果、当前日期拆分开来。我见过太多项目初期因为忽视这一点导致后期提示词维护成本指数级上升最终不得不重构。一个设计良好的提示模板就像是给大语言模型准备的“填空题”试卷。试卷的题目和答题规范是固定的而每次需要填入的空格内容则根据实际情况变化。这样做的好处显而易见提升一致性、便于维护、支持复用并且能系统化地优化提示效果。无论是构建一个简单的问答机器人还是设计复杂的多步推理智能体提示模板都是构建可靠、可扩展应用的地基。2. 核心需求解析从字符串拼接走向工程化在深入技术细节前我们先拆解一下在实际开发中一个原始的提示字符串会遇到哪些具体的“痛点”。理解了这些你才能明白模板化不是“可有可无”而是“势在必行”。2.1 维护噩梦一处改动处处搜索假设你的应用里有十几个地方都用到了类似的欢迎语提示。现在需要把“你好”统一改成“您好”。如果没有模板你就得在代码库里全局搜索所有包含“你好”的字符串逐一检查、修改、测试。这个过程极易出错可能漏掉某个偏僻的文件或者误改了其他业务逻辑中的“你好”。而使用模板后你只需要在一个地方模板定义处修改这个变量所有使用该模板的地方都会自动生效。这本质上是软件开发中“Don‘t Repeat Yourself”原则在提示词领域的体现。2.2 动态内容注入的混乱当提示需要嵌入变量时字符串格式化f-string或.format()是常见做法。但变量一多提示文本就会变得支离破碎可读性急剧下降。# 混乱的示例 prompt f系统角色{system_role}。 请参考以下上下文{context}。 回答用户关于{query_topic}的问题{user_question}。 历史记录{history}。 请用{language}回答并引用{source_name}中的观点。这段代码不仅难以阅读而且当context或history变量为空时提示中可能会留下尴尬的空白或残缺的句子结构影响模型理解。模板系统可以更优雅地处理可选变量和默认值。3. 动态内容注入的混乱当提示需要嵌入变量时字符串格式化f-string或.format()是常见做法。但变量一多提示文本就会变得支离破碎可读性急剧下降。# 混乱的示例 prompt f系统角色{system_role}。 请参考以下上下文{context}。 回答用户关于{query_topic}的问题{user_question}。 历史记录{history}。 请用{language}回答并引用{source_name}中的观点。这段代码不仅难以阅读而且当context或history变量为空时提示中可能会留下尴尬的空白或残缺的句子结构影响模型理解。模板系统可以更优雅地处理可选变量和默认值。3.1 缺乏结构和类型校验纯字符串无法对注入的内容做任何约束。如果你期望max_words变量是一个数字但传入了一个字符串错误可能要到模型返回了奇怪的结果时才会被发现。模板系统可以在填充阶段就进行初步的类型检查或格式化提前规避一类问题。3.2 难以实现高级模式一些高级提示技巧如少样本提示需要在提示中包含多个结构化的输入-输出对。用纯手工拼接来管理这些示例非常繁琐。而模板可以轻松地将示例列表作为一个整体变量进行管理和渲染甚至可以从外部文件加载示例。基于这些痛点我们可以总结出对提示模板系统的核心需求变量管理支持定义变量并能从外部传入值进行填充。结构封装将固定的提示结构和指令封装起来保持代码整洁。可复用性一次定义多处使用。可维护性修改模板定义所有使用点同步更新。可选变量与默认值优雅处理某些场景下可能缺失的变量。类型与格式控制对输入变量进行简单的预处理或校验。支持复杂结构能够方便地嵌入列表、字典等结构以支持少样本示例等模式。4. LangChain PromptTemplate 深度拆解LangChain 的PromptTemplate类是实践上述理念的一个优秀工具。它远不止是一个“字符串格式化工具”而是一个功能丰富的提示词构建器。我们来深入看看它的核心用法和设计哲学。4.1 基础构建从字符串到模板对象最基本的用法是定义一个包含占位符的字符串模板。from langchain.prompts import PromptTemplate # 定义一个简单的模板 template “””你是一个专业的{domain}专家。 请用{style}的风格回答以下问题 问题{question} “”” prompt_template PromptTemplate.from_template(template)这里{domain},{style},{question}就是变量占位符。from_template这个类方法会自动解析字符串提取出所有的变量名。填充模板# 准备输入变量 input_variables { “domain”: “机器学习”, “style”: “简洁明了”, “question”: “过拟合有哪些常见的解决方法” } # 生成最终提示词 final_prompt prompt_template.format(**input_variables) print(final_prompt)输出你是一个专业的机器学习专家。 请用简洁明了的风格回答以下问题 问题过拟合有哪些常见的解决方法注意PromptTemplate会验证你传入的input_variables字典是否包含了模板中定义的所有变量除非变量有默认值如果缺少会抛出KeyError。这是一个很好的安全特性避免了生成残缺的提示。4.2 模板语法进阶不止是{variable}LangChain 的模板语法支持更多功能使其更加强大。1. 默认值这是处理可选变量的利器。比如我们想提供一个“语言”变量但大部分情况下用户可能不指定我们希望默认为中文。template “””请将以下文本翻译成{language}。 文本{text} “”” # 在初始化时指定默认值 prompt_template PromptTemplate.from_template( template, partial_variables{“language”: “中文”} # 为 language 设置默认值 ) # 调用时可以不传 language prompt1 prompt_template.format(text“Hello, world!”) print(prompt1) # 语言默认为“中文” # 调用时也可以覆盖默认值 prompt2 prompt_template.format(text“Hello, world!”, language“法语”) print(prompt2) # 语言被覆盖为“法语”2. 少样本提示模板FewShotPromptTemplate是PromptTemplate的扩展专门用于构建包含示例的提示。这是提示工程中提升模型表现的关键技术之一。from langchain.prompts import FewShotPromptTemplate, PromptTemplate # 1. 首先定义单个示例的模板 example_template “”” 输入{input} 输出{output} “”” example_prompt PromptTemplate.from_template(example_template) # 2. 准备示例列表 examples [ {“input”: “今天天气真好” “output”: “是的阳光明媚适合外出。”}, {“input”: “我有点难过” “output”: “听到你这么说我很难过想聊聊发生了什么吗”}, ] # 3. 定义整体的少样本提示模板 few_shot_template FewShotPromptTemplate( examplesexamples, # 示例列表 example_promptexample_prompt, # 每个示例的格式模板 prefix“你是一个情感支持助手。请根据以下示例回应用户的话。”, # 前缀指令 suffix“输入{user_input}\n输出”, # 后缀包含最终用户输入的变量 input_variables[“user_input”], # 最终模板需要的变量除了examples example_separator“\n\n” # 示例之间的分隔符 ) # 4. 使用 result few_shot_template.format(user_input“这个任务太难了。”) print(result)输出会清晰地展示两个示例然后是当前用户的输入模型通过学习示例中的模式和风格来生成回复。这种方式比在纯文本中描述“请用支持性的语气回答”要有效得多。3. 模板组合与链式调用这才是 LangChain 发挥威力的地方。PromptTemplate可以很容易地与其他组件如LLMChain,OutputParser连接起来形成可执行的“链”。from langchain.chains import LLMChain from langchain_openai import ChatOpenAI # 假设使用 OpenAI 模型 from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate from langchain.schema import SystemMessage # 构建一个更复杂的聊天提示模板使用消息列表 system_template “你是一个{speaker}你的性格特点是{character}。” system_message_prompt SystemMessagePromptTemplate.from_template(system_template) human_template “{text}” human_message_prompt HumanMessagePromptTemplate.from_template(human_template) # 组合成一个聊天提示模板 chat_prompt ChatPromptTemplate.from_messages( [system_message_prompt, human_message_prompt] ) # 创建链 llm ChatOpenAI(temperature0.7) chain LLMChain(llmllm, promptchat_prompt) # 运行链 response chain.run({ “speaker”: “莎士比亚风格的诗人”, “character”: “用词华丽充满比喻和十四行诗的韵律感”, “text”: “写一首关于咖啡的短诗” }) print(response)这种组合方式让你能构建极其复杂和灵活的对话流程例如包含系统指令、上下文历史、工具调用结果和当前用户查询的 RAG 应用提示。5. 实战构建一个可复用的提示模板系统了解了基础组件后我们从一个更高的视角看如何在一个真实项目中设计和组织你的提示模板。这里分享一套我经过多个项目总结出的实践方法。5.1 按功能模块化组织模板不要把所有提示模板都堆在一个文件里。建议按业务功能或提示类型进行划分prompts/chat/存放通用聊天、角色扮演相关模板。prompts/summarization/存放各种摘要任务模板文章摘要、会议纪要、长文本总结。prompts/rag/存放检索增强生成相关的模板上下文压缩、重写、答案生成。prompts/code/存放代码生成、解释、调试相关模板。prompts/generic/存放基础、通用的模板。每个模板文件如prompts/rag/answer_generation.py里可以用字典或类来管理一组相关模板。# prompts/rag/answer_generation.py from langchain.prompts import PromptTemplate ANSWER_PROMPT { “basic”: PromptTemplate.from_template( “””基于以下上下文信息回答问题。如果你不知道答案就说不知道不要编造。 上下文 {context} 问题{question} 答案“”” ), “with_citation”: PromptTemplate.from_template( “””基于以下上下文信息回答问题并在答案中引用相关原文用【】标注。 上下文 {context} 问题{question} 答案“”” ), } # 在业务代码中引用 from prompts.rag import answer_generation prompt answer_generation.ANSWER_PROMPT[“with_citation”]5.2 设计模板时的核心考量点指令清晰度给模型的指令必须明确、无歧义。避免使用“处理好一点”、“用专业的方式”这种模糊词汇。取而代之的是“用不超过三句话总结”、“以项目符号列表形式列出关键点”、“输出格式为 JSON包含 title 和 summary 两个字段”。上下文管理对于 RAG 应用如何将检索到的上下文 (context) 嵌入提示是关键。上下文太长会浪费 Token 并可能分散模型注意力太短可能信息不足。常见的策略有简单拼接直接附在提示后。适用于上下文较短的情况。指令强调用“### 上下文 ###”等明显标记分隔并加上“请严格基于以上上下文回答”的指令。Map-Reduce对于超长上下文先对多个片段分别生成答案或摘要再合并。这需要更复杂的模板链。输出格式控制强烈建议在模板中明确指定输出格式。这对于后续的程序化处理至关重要。template “””... 请将分析结果以如下 JSON 格式输出 {{ “risk_level”: “高/中/低”, “reasons”: [“原因1”, “原因2”], “suggestion”: “你的建议” }} 确保只输出 JSON不要有其他任何文字。“””配合 LangChain 的OutputParser如PydanticOutputParser可以自动将模型输出解析成结构化数据。角色与人格设定通过system_message或提示前缀设定模型的角色能显著改变其输出风格和倾向。例如“你是一个严谨的科学家”和“你是一个风趣的脱口秀演员”对同一问题的回答会截然不同。5.3 模板版本化与 A/B 测试提示模板本身也是重要的“代码”需要版本管理。当你想优化一个提示时不要直接覆盖原模板而是创建一个新版本。# prompts/v1/summarizer.py PROMPT_V1 “总结以下文本{text}” # prompts/v2/summarizer.py PROMPT_V2 “””请扮演编辑的角色为以下文章撰写一个吸引人的摘要要求突出核心观点并在80字以内。 文章{text} 摘要“””然后你可以在系统中通过配置或功能开关让一部分流量使用V1另一部分使用V2对比两者的效果如摘要质量评分、用户满意度用数据驱动决策。这个过程就是提示词的 A/B 测试。6. 常见陷阱与效能优化指南即使使用了模板也可能会踩坑。下面是一些我亲身经历或观察到的常见问题及其解决方案。6.1 陷阱一变量注入导致提示变形问题当动态变量内容包含引号、换行符或与模板语法冲突的字符时可能会破坏提示的整体结构。user_input ‘我说“你好吗”’ # 包含双引号 template “用户说{input}” # 格式化后可能引起混淆解决方案对于简单情况确保变量内容已做适当的转义或清理。更稳健的做法是使用 LangChain 的ChatPromptTemplate和消息对象SystemMessage,HumanMessage,AIMessage它们以结构化的方式管理内容天然避免了字符串拼接问题。在模板设计时考虑使用更明确的分隔符例如### 用户输入 ###\n{input}\n### 结束 ###。6.2 陷阱二Token 超限与成本失控问题模板本身可能很简洁但注入的变量尤其是context可能非常长导致每次调用都消耗大量 Token成本高昂且可能触发模型上下文长度限制。解决方案上下文压缩/摘要在将长文本作为context注入前先使用另一个 LLM 调用对其进行摘要仅保留关键信息。LangChain 的ContextualCompressionRetriever就是干这个的。分而治之采用 Map-Reduce 方法。将长文档拆分成块对每个块用模板生成部分答案再用一个总结模板合并所有部分答案。设定预算在代码层面计算提示词的近似 Token 数例如使用tiktoken库 for OpenAI如果超过阈值则触发压缩流程或直接拒绝。6.3 陷阱三模板过于僵化缺乏灵活性问题为每个细微变化都创建新模板导致模板数量爆炸难以管理。解决方案参数化配置将可配置项作为模板变量。例如一个摘要模板可以包含summary_length“用一句话总结”/“用三段话总结”和target_audience“面向专家”/“面向小学生”等变量。模板组合将提示拆解成更小的、可复用的部分如system_prompt_part,instruction_part,format_part然后在运行时按需组合。这比维护无数个完整模板要灵活得多。6.4 陷阱四忽视少样本示例的质量和代表性问题在少样本提示中随意挑选几个示例导致模型学到了错误的模式或偏见。解决方案精心设计示例示例应清晰、正确并覆盖预期任务的各种边界情况。示例数量通常 2-5 个高质量示例比 10 个普通示例更有效。太多示例会不必要地增加 Token 消耗和成本。动态示例选择根据当前用户输入从示例库中动态选择最相关的几个示例而不是固定使用同一组。这被称为动态少样本提示能显著提升效果。6.5 效能优化技巧模板预热对于高频使用的模板可以在服务启动时提前实例化好PromptTemplate对象避免每次请求时重复解析字符串。缓存生成结果如果某些提示组合特别是带有固定上下文或示例的会被重复使用可以考虑缓存format后的完整提示字符串。异步批量处理当需要为大量数据项应用同一模板时例如批量生成产品描述使用异步并发来调用 LLM可以极大提升吞吐量。确保你的模板和链支持异步操作。7. 超越 LangChain模板设计的通用思想虽然我们以 LangChain 为例但提示模板的思想是通用的即使你在其他框架或直接使用 API。核心思想始终是分离、抽象、复用。在Dify、FastChat或其他 AI 应用平台中它们通常提供了可视化的提示词编排界面其底层逻辑依然是模板化。如果你直接调用OpenAI API你可以自己构建一个轻量级的模板管理系统class SimplePromptManager: def __init__(self): self.templates {} def register(self, name, template_str, default_varsNone): self.templates[name] { “template”: template_str, “defaults”: default_vars or {} } def format(self, name, **kwargs): tpl_info self.templates[name] # 合并默认值和传入值 all_vars {**tpl_info[“defaults”], **kwargs} # 简单的格式化可替换为更安全的实现 return tpl_info[“template”].format(**all_vars) manager SimplePromptManager() manager.register(“greeting”, “Hello, {name}! Welcome to {place}.“, {“place”: “our platform”}) print(manager.format(“greeting”, name“Alice”)) # Hello, Alice! Welcome to our platform.最终评判一个提示模板系统好坏的唯一标准是它是否让你的提示词更易于管理、迭代和优化从而稳定、高效地获得你期望的模型输出。花时间设计一个好的模板系统在项目后期会为你节省数十倍于此刻的调试和重构时间。当你的同事能轻松理解并复用你定义的SUMMARY_V3模板时你就知道这笔投资是值得的。