在前节中,学习了
PromptTemplate(通用模板)和
FewShotPromptTemplate(示例模板)。它们非常适合处理单次任务,比如翻译、摘要或问答。但是,当我们构建一个聊天机器人 时,情况就变了。用户会说:"再写一首",这时候模型必须知道"上一首是什么"才能接得上话。这就需要我们引入历史会话信息(History) 。
普通的
PromptTemplate只能处理简单的字符串变量,无法优雅地处理动态增长的对话列表。这时,就需要主角登场了:
ChatPromptTemplate。
🧠 三大模板对比
在 LangChain 中,我们有三种主要的提示词模板,各司其职:
| 模板类型 | 核心作用 | 适用场景 |
|---|---|---|
| PromptTemplate | 注入简单变量 (String) | 单次任务:翻译、提取信息 |
| FewShotPromptTemplate | 注入示例数据 (Examples) | 复杂推理、格式模仿 |
| ChatPromptTemplate | 注入历史消息 (Messages) | 多轮对话、角色扮演、Agent |
💡 关键区别:
PromptTemplate.from_template()接收单个字符串。ChatPromptTemplate.from_messages()接收消息列表,可以包含 System、Human、AI 等多种角色。
💻 实战案例:作诗机器人的多轮对话
我们将构建一个诗人 AI。用户先让它写一首唐诗,它写了;然后用户说"再来一首",它需要基于之前的语境继续创作。
代码实现
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_models.tongyi import ChatTongyi
# ---------------------------------------------------------
# 第一步:定义聊天模板
# 使用 from_messages 接收一个列表
# ---------------------------------------------------------
chat_prompt_template = ChatPromptTemplate.from_messages(
[
("system", "你是一个专业的诗人,可以作诗。"),
# 关键点:使用 MessagesPlaceholder 预留历史对话的位置
MessagesPlaceholder(variable_name="history"),
("human", "请再来一首"),
]
)
# ---------------------------------------------------------
# 第二步:准备历史对话数据
# 这是一个动态列表,随着对话进行会越来越长
# 格式通常为:(role, content) 元组列表
# ---------------------------------------------------------
history_data = [
("human", "写一首唐诗"),
("ai", "锄禾日当午,汗滴禾下土。谁知盘中餐,粒粒皆辛苦。"),
("human", "好诗再来一个"),
("ai", "床前明月光,疑是地上霜。举头望明月,低头思故乡。"),
]
# ---------------------------------------------------------
# 第三步:生成提示词 (必须使用 invoke!)
# 将 history_data 注入到 "history" 占位符中
# ---------------------------------------------------------
# .to_string() 是为了打印查看方便,实际传给 ChatModel 时可以省略
prompt_value = chat_prompt_template.invoke(input={"history": history_data})
prompt_text = prompt_value.to_string()
print("📝 组装后的完整提示词预览:\n")
print(prompt_text)
print("\n" + "="*50 + "\n")
# ---------------------------------------------------------
# 第四步:调用聊天模型
# ---------------------------------------------------------
model = ChatTongyi(model="qwen-max")
# 注意:ChatModel 最好直接接收 prompt_value 对象,或者 messages 列表
# 这里为了演示,我们传入了转换后的字符串(虽然不推荐,但也能工作)
# 最佳实践是:res = model.invoke(prompt_value)
res = model.invoke(input=prompt_text)
print("🤖 AI 的新作品:\n")
print(res.content)
️ 运行结果逻辑分析
当代码运行时,ChatPromptTemplate 会将 history_data 中的每一条消息插入到 MessagesPlaceholder 的位置。最终发送给模型的提示词结构如下:
text
System: 你是一个专业的诗人,可以作诗。
Human: 写一首唐诗
AI: 锄禾日当午...
Human: 好诗再来一个
AI: 床前明月光...
Human: 请再来一首 <-- 这是当前最新的问题
模型看到完整的上下文后,就能明白"再来一首"是指"再写一首像前面那样的唐诗",从而给出连贯的回答。
🔍 核心知识点解析
1. MessagesPlaceholder 的作用
这是 ChatPromptTemplate 最强大的功能之一。
- 问题:历史对话是动态的。第一轮对话时 history 为空,第十轮对话时 history 有 20 条消息。我们无法在写代码时把 history 写死在字符串里。
- 解决 :
MessagesPlaceholder(variable_name="history")就像一个插槽 。它在模板中占据一个位置,但在运行时才会被填入真实的history_data列表。
2. 为什么必须用 invoke 而不能用 format?
这是很多初学者容易踩的坑!
.format()的局限 :它只能做简单的字符串替换({key}->value)。如果你传入一个列表给{history},它会试图把整个列表转换成字符串(比如"['...', '...']"),这会破坏消息的结构,模型根本看不懂。.invoke()的强大 :它是 LangChain 的标准接口。它知道MessagesPlaceholder需要的是一个消息列表对象 ,而不是字符串。它会智能地将列表中的每个元组(role, content)转换为模型能理解的BaseMessage对象。
⚠️ 结论 :只要你的模板里用了
MessagesPlaceholder,必须 使用.invoke(),.format()会报错或产生错误结果。
3. 返回值类型:PromptValue
和之前一样,chat_prompt_template.invoke() 返回的是一个 ChatPromptValue 对象。
- 它可以调用
.to_string()变成纯文本(适合调试或 LLM)。 - 它可以调用
.to_messages()变成消息列表(最适合 ChatModel)。
在上面的代码中,虽然我们用了 .to_string() 转成字符串传给模型,但在生产环境中,建议直接传对象:
python
# ✅ 最佳实践
prompt_value = chat_prompt_template.invoke({"history": history_data})
res = model.invoke(prompt_value)
🚀 总结
本节课我们掌握了构建多轮对话系统的关键技术:
- ChatPromptTemplate:专为聊天场景设计的模板类,支持多角色消息列表。
- MessagesPlaceholder:用于动态注入历史对话记录的"插槽",解决了上下文长度变化的问题。
- Invoke vs Format :深刻理解了在处理结构化数据(如消息列表)时,必须使用
invoke方法。
有了 ChatPromptTemplate,我们的 AI 就不再是"金鱼记忆"(只有7秒),而是拥有了长期记忆,能够进行流畅的多轮交互。