LangChain 学习记录 03:提示词模板 PromptTemplate 与 ChatPromptTemplate 详解
摘要
在大模型应用开发里,Prompt 不是随手拼出来的一段字符串,而是影响模型输出质量、代码可维护性和项目稳定性的核心资产。
本文围绕 LangChain 的提示词模板展开,重点讲清楚:
- 为什么不要长期依赖 f-string 手动拼 Prompt
PromptTemplate和ChatPromptTemplate分别适合什么场景partial和partial_variables如何减少重复传参MessagesPlaceholder为什么是多轮对话的关键FewShotPromptTemplate如何用示例引导模型输出
一句话概括:
Prompt Template 的作用,是把“怎么问模型”这件事标准化、参数化、可复用。文章目录
- LangChain 学习记录 03:提示词模板 PromptTemplate 与 ChatPromptTemplate 详解
- 摘要
- 一、为什么需要提示词模板
- 二、Prompt Template 的底层逻辑
- 三、PromptTemplate:最基础的字符串模板
- 常见写法对比
- 四、ChatPromptTemplate:聊天模型的主力模板
- system、human、ai 的区别
- 五、partial 与 partial_variables
- partial 的核心价值
- partial 和 partial_variables 区别
- 六、MessagesPlaceholder:给历史对话留位置
- 七、FewShotPromptTemplate:用示例教模型做事
- 八、项目中怎么选
- 九、常见坑
- 1. 模板变量名和 invoke 输入不一致
- 2. 把 ChatPromptTemplate 当成普通字符串
- 3. MessagesPlaceholder 传入的不是消息列表
- 4. Few-shot 示例太多
- 十、小结
- 参考资料
一、为什么需要提示词模板
刚开始调用大模型时,我们很容易这样写:
topic="向量数据库"style="通俗易懂"prompt=f"请用{style}的方式解释什么是{topic}"这当然能跑。
但真实项目里的 Prompt 往往不是一句话,而是包含:
- 角色设定
- 任务目标
- 输入数据
- 输出格式
- 约束条件
- 示例
- 历史对话
- 安全边界
如果全部用字符串拼接,代码会慢慢变成这样:
prompt=("你是一个专业助手。\n"f"请分析下面内容:{text}\n""要求:\n""1. 输出 JSON\n""2. 不要添加解释\n"f"3. 语气要{style}\n")一开始看着还行,后面需求一改,Prompt 四处散落,维护起来就像在旧项目里找一个没有注释的全局变量,心态非常容易裂开。
提示词模板解决的就是这个问题:
把 Prompt 的固定部分和可变部分分离。也就是:
固定模板 + 动态变量 = 最终 Prompt二、Prompt Template 的底层逻辑
先看一张逻辑图。
LangChain 中的提示词模板大概遵循这个流程:
变量输入 ↓ PromptTemplate / ChatPromptTemplate ↓ PromptValue ↓ Chat Model 或 LLM这里有一个容易忽略的点:
PromptTemplate 调用后,不一定直接得到普通字符串。它可能生成一个PromptValue,这个中间对象可以适配不同模型:
- 给普通 LLM 时,可以转成字符串
- 给 Chat Model 时,可以转成消息列表
这就是 LangChain 组件能无缝接入 LCEL 链的原因。
后面你会经常看到:
chain=prompt|model|parser这里的prompt能接到model前面,就是因为它们都遵循统一的输入输出协议。
三、PromptTemplate:最基础的字符串模板
PromptTemplate适合简单文本生成场景。
比如:
fromlangchain_core.promptsimportPromptTemplate prompt=PromptTemplate.from_template("请用{style}的风格解释什么是{topic}。")result=prompt.invoke({"style":"通俗易懂","topic":"向量数据库"})print(result)这里的{style}和{topic}是占位符。
调用时传入:
{"style":"通俗易懂","topic":"向量数据库"}LangChain 会把变量填进模板,生成最终 Prompt。
你也可以把它理解成一个函数:
defprompt(style,topic):returnf"请用{style}的风格解释什么是{topic}。"只不过 LangChain 的模板更适合后续接入模型、解析器和 Chain。
常见写法对比
| 写法 | 说明 | 适合场景 |
|---|---|---|
| f-string | Python 原生字符串拼接 | 临时实验、小脚本 |
PromptTemplate | LangChain 字符串模板 | 单轮文本生成 |
ChatPromptTemplate | 聊天消息模板 | 对话模型、多轮问答 |
FewShotPromptTemplate | 示例驱动模板 | 需要给模型示范输出模式 |
如果你只是写一个极小 demo,f-string 没问题。
但只要准备做项目,就建议尽早换成 Prompt Template。
四、ChatPromptTemplate:聊天模型的主力模板
现在大多数大模型接口都属于 Chat Model。
Chat Model 不只是接收一段字符串,而是接收一组消息:
system:系统规则 human:用户输入 ai:模型历史回复所以更常用的是ChatPromptTemplate。
fromlangchain_core.promptsimportChatPromptTemplate prompt=ChatPromptTemplate.from_messages([("system","你是一名耐心的技术导师,擅长用简单例子解释复杂概念。"),("human","请解释:{question}")])prompt_value=prompt.invoke({"question":"LangChain 中的 PromptTemplate 有什么用?"})print(prompt_value)这段模板里有两条消息:
system:给模型设定身份和行为规则 human:用户真正要问的问题system、human、ai 的区别
| 角色 | 作用 | 示例 |
|---|---|---|
system | 设定模型身份、规则、边界 | 你是一个严谨的代码审查助手 |
human | 用户当前输入 | 请解释这段代码 |
ai | 模型之前的回复,常用于示例或历史 | 这段代码的作用是… |
一个更贴近项目的例子:
review_prompt=ChatPromptTemplate.from_messages([("system","你是一个资深 Python 代码审查助手。"),("human","请审查下面代码,指出潜在问题:\n{code}")])这个模板比普通字符串更清晰:
- 系统身份放在
system - 用户任务放在
human - 变量
{code}独立传入
代码更像在描述业务流程,而不是乱拼字符串。
五、partial 与 partial_variables
有时模板里有些变量是固定的。
比如:
prompt=PromptTemplate.from_template("请用{style}风格解释{topic},要求不超过{max_words}字。")如果style总是“通俗易懂”,我们不想每次都传:
prompt=prompt.partial(style="通俗易懂")之后调用时只需要传剩下的变量:
result=prompt.invoke({"topic":"RAG","max_words":100})partial 的核心价值
提前填充一部分变量,让模板变成更具体的模板。这有点像函数默认参数:
defexplain(topic,style="通俗易懂"):...partial 和 partial_variables 区别
| 方式 | 使用时机 | 特点 |
|---|---|---|
partial() | 模板创建之后再填充变量 | 灵活,适合运行时决定 |
partial_variables | 模板创建时就固定变量 | 更自描述,适合固定配置 |
示例:
prompt=PromptTemplate(template="请用{style}风格解释{topic}",input_variables=["topic"],partial_variables={"style":"通俗易懂"})如果这个固定值来自用户选择、配置文件或运行时上下文,优先用partial()。
如果这个固定值就是模板定义的一部分,可以用partial_variables。
六、MessagesPlaceholder:给历史对话留位置
多轮对话中,我们需要把历史聊天记录插入 Prompt。
这时就要用:
MessagesPlaceholder示例:
fromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholder prompt=ChatPromptTemplate.from_messages([("system","你是一个耐心的学习助手。"),MessagesPlaceholder(variable_name="history"),("human","{input}")])这里的:
MessagesPlaceholder(variable_name="history")可以理解为:
这里先空着,运行时把历史消息列表塞进来。运行时可以传:
prompt.invoke({"history":[("human","我叫张三"),("ai","你好,张三")],"input":"我叫什么?"})最终模型看到的上下文大概是:
system:你是一个耐心的学习助手 human:我叫张三 ai:你好,张三 human:我叫什么?重点:
MessagesPlaceholder 本身不保存历史,它只是给历史消息留一个插槽。真正自动保存和读取历史,后面通常会配合RunnableWithMessageHistory。
七、FewShotPromptTemplate:用示例教模型做事
有些任务光靠一句指令不够稳定。
比如你希望模型把情绪词转换成近义词:
高兴 -> 开心 难过 -> 悲伤 生气 -> 愤怒这时可以用 Few-shot prompting,也就是给模型几个示例。
fromlangchain_core.promptsimportFewShotPromptTemplate,PromptTemplate examples=[{"input":"高兴","output":"开心"},{"input":"难过","output":"悲伤"},{"input":"生气","output":"愤怒"},]example_prompt=PromptTemplate.from_template("输入:{input}\n输出:{output}")prompt=FewShotPromptTemplate(examples=examples,example_prompt=example_prompt,prefix="请把用户输入的情绪词转换成一个近义词。",suffix="输入:{word}\n输出:",input_variables=["word"])result=prompt.invoke({"word":"开心"})print(result)生成的 Prompt 大致是:
请把用户输入的情绪词转换成一个近义词。 输入:高兴 输出:开心 输入:难过 输出:悲伤 输入:生气 输出:愤怒 输入:开心 输出:Few-shot 的底层逻辑是:
不是只告诉模型规则,而是给模型看“输入和输出应该长什么样”。这对以下任务特别有用:
- 分类
- 格式转换
- 风格模仿
- 固定结构输出
- 少量业务规则示范
八、项目中怎么选
可以用这张表快速判断。
| 场景 | 推荐模板 | 原因 |
|---|---|---|
| 简单文本生成 | PromptTemplate | 轻量,适合字符串 Prompt |
| 调用聊天模型 | ChatPromptTemplate | 支持 system/human/ai 角色 |
| 多轮对话 | ChatPromptTemplate + MessagesPlaceholder | 可以插入历史消息 |
| 需要示例引导 | FewShotPromptTemplate | 用样例稳定模型行为 |
| 固定部分变量 | partial() | 减少重复传参 |
我的建议:
如果你现在主要用 ChatOpenAI、ChatOllama 等聊天模型,优先掌握 ChatPromptTemplate。PromptTemplate要会,但真实项目里ChatPromptTemplate更常用。
九、常见坑
1. 模板变量名和 invoke 输入不一致
模板里写的是:
"请解释:{topic}"调用时却传:
{"question":"LangChain"}这样会报错。
正确写法:
{"topic":"LangChain"}变量名要对上,程序不认“差不多”。
2. 把 ChatPromptTemplate 当成普通字符串
ChatPromptTemplate生成的是消息结构,不是简单字符串。
它的优势正是能区分:
system / human / ai不要强行把它当普通字符串拼来拼去。
3. MessagesPlaceholder 传入的不是消息列表
错误思路:
{"history":"用户:你好\nAI:你好"}更推荐:
{"history":[("human","你好"),("ai","你好,有什么可以帮你?")]}因为聊天模型更容易理解结构化消息。
4. Few-shot 示例太多
示例不是越多越好。
示例太多会:
- 增加 token 成本
- 挤占上下文窗口
- 让 Prompt 变难维护
通常先从 2 到 5 个高质量示例开始。
十、小结
这一篇主要讲了 LangChain 的提示词模板。
你可以这样记:
PromptTemplate:普通字符串模板。 ChatPromptTemplate:聊天消息模板。 MessagesPlaceholder:给历史消息留插槽。 FewShotPromptTemplate:用示例引导模型。 partial:提前填充部分变量。最重要的是这句话:
提示词模板不是为了少写字符串,而是为了让 Prompt 变得可复用、可维护、可组合。下一篇继续学习:Output Parser 输出解析器。
如果说 Prompt Template 负责“怎么问”,那么 Output Parser 就负责“怎么把模型回答变成程序能用的数据”。
参考资料
- LangChain Python 官方文档:https://docs.langchain.com/oss/python/langchain
- LangChain Core API Reference:https://reference.langchain.com/python/langchain-core/