005、提示模板的艺术从基础到高级技巧昨天深夜调试一个RAG应用明明文档里写清楚了操作步骤AI却总在第三步返回些无关内容。盯着屏幕看了半小时才反应过来——问题不在模型而在我的提示词。那个藏在代码里的字符串被我随手写成了一长段“自然语言”变量拼接处连个分隔符都没留。这让我想起刚入行时老工程师的话“别把提示词当字符串要当API接口来设计。”今天我们就聊聊LangChain里提示模板那些事儿。提示模板不是字符串格式化很多人第一次用PromptTemplate直接当成Python的f-string来用# 别这样写后面肯定要踩坑promptf请总结以下文档{doc_text}看起来没问题但等你需要切换模型、调整温度参数、添加系统提示时这堆字符串拼接就会变成维护噩梦。LangChain的模板化思路是把提示词拆成三部分模板结构、输入变量、格式化规则。fromlangchain.promptsimportPromptTemplate# 基础用法声明变量占位符template请用不超过{max_words}个字总结以下内容{content}promptPromptTemplate.from_template(template)# 这才是正经用法formattedprompt.format(max_words50,contentdocument)关键点在于模板是声明式的。你定义的是“这里需要填什么”而不是“怎么填”。后面换用ChatGPT还是Claude调整提示词结构都只需要改模板定义那一处。真实场景里的模板设计上周帮同事调试一个客服系统他的模板长这样template用户说{query} 请回复。问题太明显了——模型根本不知道自己是客服经常用朋友聊天的语气回复。改进后的版本template你是一名专业的客服助手公司是{company_name}。 当前服务场景{scene} 历史对话记录 {history} 用户本次咨询{query} 请以客服身份回复要求 1. 使用{language}语言 2. 涉及产品问题时引用{product_doc}中的描述 3. 如果用户问题需要转人工请说明原因 回复这个模板的巧妙之处在于把系统角色、业务上下文、行为约束都写进了模板结构里。后面即使换模型这些业务逻辑也不会丢。多模态模板的坑处理多轮对话时很多人会掉进这个坑# 这样写history会乱套template历史{history}\n问题{question}Chat模型的消息列表需要区分角色。LangChain的ChatPromptTemplate才是正解fromlangchain.promptsimportChatPromptTemplate,HumanMessagePromptTemplate,SystemMessagePromptTemplate system_templateSystemMessagePromptTemplate.from_template(你是{domain}领域专家回答时请参考{reference})human_templateHumanMessagePromptTemplate.from_template({query})chat_promptChatPromptTemplate.from_messages([system_template,human_template])# 这才是模型真正接收的结构messageschat_prompt.format_prompt(domain医疗,reference《临床指南》第三版,query头痛怎么办).to_messages()注意format_prompt和to_messages的配合——很多新手直接调用format拿到的还是字符串而Chat模型需要的是message对象列表。动态模板的进阶玩法最近做的一个项目需要根据用户选择动态调整提示词。比如用户选了“简洁模式”就要去掉例子部分。传统做法是一堆if-else后来改用partial_variablesbase_template{style_instruction} 请处理以下文本{text}promptPromptTemplate.from_template(base_template)# 预填充部分变量concise_promptprompt.partial(style_instruction请用最简洁的语言回答)detailed_promptprompt.partial(style_instruction请详细说明包含例子)# 使用时只需要传剩下的变量concise_prompt.format(text什么是机器学习)更复杂的场景可以用FewShotPromptTemplate。但要注意示例选择策略——我见过有人硬编码50个示例结果提示词超过token限制。更好的做法是用ExampleSelector动态选最相关的3-5个。模板组合与复用大型项目里提示模板应该像函数一样可复用。我的习惯是按功能模块拆分# 在prompts/目录下# system_prompts.pydefcreate_system_prompt(role,constraintsNone):basef你是一名{role}ifconstraints:basef必须遵守{constraints}returnSystemMessagePromptTemplate.from_template(base)# query_prompts.pydefcreate_qa_prompt(stylestandard):styles{standard:请回答以下问题{question},step_by_step:请分步骤解答{question}}returnHumanMessagePromptTemplate.from_template(styles[style])然后在业务层组合frompromptsimportcreate_system_prompt,create_qa_prompt chat_promptChatPromptTemplate.from_messages([create_system_prompt(AI助手,不讨论政治),create_qa_prompt(stylestep_by_step)])这样改风格只改配置改角色只改角色模板符合单一职责原则。调试技巧看到模型眼中的提示词最让我头疼的bug是我以为的提示词不是模型收到的提示词。两个调试方法第一打开verbose模式前先本地打印# 在调用chain之前print(chat_prompt.format_prompt(**inputs).to_string())第二用自定义回调记录实际发送内容classPromptLogger(BaseCallbackHandler):defon_llm_start(self,serialized,prompts,**kwargs):print(实际发送的提示词,prompts[0][:500])# 截断避免刷屏经常发现的问题特殊字符被转义、换行符数量不对、变量没被替换通常是因为变量名拼写错误。个人经验建议提示模板设计有点像数据库schema设计——早期随意后期还债。我的几个教训永远用from_template而不是手动拼字符串哪怕只有两个变量。三个月后你肯定要加第三个变量。系统提示和用户提示分开定义。后面加“记忆”功能或切换成带function calling的模型时你会感谢这个决定。模板里留“扩展点”。比如{additional_instructions}占位符运行时可以填空字符串或补充要求避免为小调整重写模板。长度控制写在模板里而不是代码里。像“用{max_words}字回答”这样的约束放在模板里更直观。团队统一模板管理。我们吃过亏A同事用“请总结”B同事用“请概括”微调参数时得改十几个地方。最后说个反直觉的观点复杂的业务提示词应该像写配置文件那样写——用YAML或JSON定义然后加载成模板。这样非工程师也能参与优化用版本管理跟踪变更。上周我们刚把客服系统的提示模板抽成YAML文件产品经理自己改了三次每次效果提升都通过Git diff看得清清楚楚。好的提示模板是长出来的不是一次写成的。留好扩展口子做好版本管理剩下的就是不断和实际输出结果对话慢慢磨出那个最懂你业务的提示词结构。