LangChain 学习记录 03:提示词模板 PromptTemplate 与 ChatPromptTemplate 详解
摘要
在大模型应用开发里,Prompt 不是随手拼出来的一段字符串,而是影响模型输出质量、代码可维护性和项目稳定性的核心资产。
本文围绕 LangChain 的提示词模板展开,重点讲清楚:
- 为什么不要长期依赖 f-string 手动拼 Prompt
PromptTemplate和ChatPromptTemplate分别适合什么场景partial和partial_variables如何减少重复传参MessagesPlaceholder为什么是多轮对话的关键FewShotPromptTemplate如何用示例引导模型输出
一句话概括:
text
Prompt Template 的作用,是把"怎么问模型"这件事标准化、参数化、可复用。
文章目录
- [LangChain 学习记录 03:提示词模板 PromptTemplate 与 ChatPromptTemplate 详解](#LangChain 学习记录 03:提示词模板 PromptTemplate 与 ChatPromptTemplate 详解)
-
- 摘要
- 一、为什么需要提示词模板
- [二、Prompt Template 的底层逻辑](#二、Prompt Template 的底层逻辑)
- 三、PromptTemplate:最基础的字符串模板
- 四、ChatPromptTemplate:聊天模型的主力模板
-
- [system、human、ai 的区别](#system、human、ai 的区别)
- [五、partial 与 partial_variables](#五、partial 与 partial_variables)
-
- [partial 的核心价值](#partial 的核心价值)
- [partial 和 partial_variables 区别](#partial 和 partial_variables 区别)
- 六、MessagesPlaceholder:给历史对话留位置
- 七、FewShotPromptTemplate:用示例教模型做事
- 八、项目中怎么选
- 九、常见坑
-
- [1. 模板变量名和 invoke 输入不一致](#1. 模板变量名和 invoke 输入不一致)
- [2. 把 ChatPromptTemplate 当成普通字符串](#2. 把 ChatPromptTemplate 当成普通字符串)
- [3. MessagesPlaceholder 传入的不是消息列表](#3. MessagesPlaceholder 传入的不是消息列表)
- [4. Few-shot 示例太多](#4. Few-shot 示例太多)
- 十、小结
- 参考资料
一、为什么需要提示词模板
刚开始调用大模型时,我们很容易这样写:
python
topic = "向量数据库"
style = "通俗易懂"
prompt = f"请用{style}的方式解释什么是{topic}"
这当然能跑。
但真实项目里的 Prompt 往往不是一句话,而是包含:
- 角色设定
- 任务目标
- 输入数据
- 输出格式
- 约束条件
- 示例
- 历史对话
- 安全边界
如果全部用字符串拼接,代码会慢慢变成这样:
python
prompt = (
"你是一个专业助手。\n"
f"请分析下面内容:{text}\n"
"要求:\n"
"1. 输出 JSON\n"
"2. 不要添加解释\n"
f"3. 语气要{style}\n"
)
一开始看着还行,后面需求一改,Prompt 四处散落,维护起来就像在旧项目里找一个没有注释的全局变量,心态非常容易裂开。
提示词模板解决的就是这个问题:
text
把 Prompt 的固定部分和可变部分分离。
也就是:
text
固定模板 + 动态变量 = 最终 Prompt
二、Prompt Template 的底层逻辑
先看一张逻辑图。

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