从第一篇起,我们就在用 ChatPromptTemplate.from_template("请翻译:{input}") 这种写法来拼提示词。它简单好用,但当你需要维护十几个不同角色的提示词,或者根据运行时状态动态切换风格时,直接写死在代码里的字符串就成了一颗颗定时炸弹。改一个标点可能要翻遍整个项目,团队协作稍有不慎就把提示词改坏。
今天,我们来系统学习 LangChain 中的提示词模板(Prompt Templates)。它不是什么魔法,而是一种将"不确定内容"从"固定框架"中剥离的编程约束------就像 HTML 模板引擎把数据从页面骨架中分离一样。学完这一篇,你将能像管理配置文件一样管理你的提示词,让应用变得更灵活、更健壮、更可维护。
一、为什么需要模板:给不断变化的提示词一个"骨架"
1.1 硬编码之痛
假设你在做一个"读书笔记助手",不同用户可能会要求生成摘要、提取金句、对比书评。没有模板时,你的代码可能是这样的:
python
# 用户要摘要
prompt = f"请为《{book}》写一段 200 字的摘要。"
# 用户要金句
prompt = f"从《{book}》中摘录 5 句最经典的话。"
# 用户要对比
prompt = f"比较《{book_a}》和《{book_b}》的写作风格。"
这种写法有三个致命缺点:提示词散落各处难以统一修改 、角色设定和任务描述纠缠不清 、无法便捷地做 A/B 测试。当提示词从 3 个涨到 30 个时,维护它们就是一场灾难。
1.2 模板的优雅:关注点分离
模板的核心思想很简单:把固定的提示词结构,与每次请求时动态变化的数据,彻底分开。 就像 Python 的 f-string 反过来------不是把变量嵌进字符串,而是先定义好一个带占位符的"模具",调用时再填入变量:
python
# 模具(声明一次,可以复用无数次)
template = "请为《{book}》写一段 {style} 的摘要,不超过 {words} 字。"
# 调用时填入数据
prompt = template.format(book="三体", style="学术风格", words=150)
LangChain 的模板体系就是这一思想的工业级实现。它把"句式框架"抽象为组件,可以存储、复用、组合,甚至从文件或数据库中加载。
二、ChatPromptTemplate:面向对话的模板核心
在第 7 篇文章中,我们经常用 from_template 这个静态方法快速创建模板。现在是时候正面认识它的本体------ChatPromptTemplate。
2.1 不只是字符串:消息列表的模板
ChatPromptTemplate 的全名叫"聊天提示词模板",因为它内部管理的不只是一个字符串,而是一个消息列表。列表中的每一项都可以是静态的消息,也可以是带占位符的消息模板:
python
from langchain_core.prompts import ChatPromptTemplate
# 一个包含系统消息和用户消息的模板
template = ChatPromptTemplate.from_messages([
("system", "你是一位{role},擅长的领域是{domain}。回答方式:{style}"),
("user", "{input}")
])
# 填入变量,模板会生成两条完整消息
messages = template.format_messages(
role="资深代码审查员",
domain="Python 后端开发",
style="犀利但幽默",
input="请审查这段代码:def add(a,b): return a+b"
)
print(messages[0]) # SystemMessage: 你是一位资深代码审查员...
print(messages[1]) # HumanMessage: 请审查这段代码...
from_messages 接收一个列表,每个元素是一个元组 (角色, 内容)。角色可以是 "system"、"user"、"assistant",内容中可以直接使用 {变量名} 占位符。
这种方式让你可以轻松构建多角色、多轮预设的提示词。例如,一个面试模拟器可以直接预设几轮对话骨架:
python
interview_template = ChatPromptTemplate.from_messages([
("system", "你是一位{company}的{position}面试官。面试风格:{style}。"),
("assistant", "你好,欢迎参加今天的面试。请先简单介绍一下你自己。"),
("user", "我叫{name},拥有{years}年{field}经验。"),
("assistant", "{first_question}")
])
填入变量后,一条带有开场白和首个问题的完整面试对话骨架就生成了。
2.2 使用 MessagePlaceholder 注入动态历史
在第 12 篇管理对话历史时,我们一直是手动构造消息列表然后传给模型。当结合模板使用时,历史列表的长度是动态的,不能简单地用 {history} 字符串放入模板。LangChain 提供了 MessagesPlaceholder 来专门处理"消息列表"这种动态容器:
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 占位符 history 将接收一个消息列表,而不是字符串
template = ChatPromptTemplate.from_messages([
("system", "你是一个乐于助人的助手。"),
MessagesPlaceholder(variable_name="history"), # 这里是消息列表的"插槽"
("user", "{input}")
])
# 调用时,把对话历史以消息列表的形式传入
history = [
HumanMessage(content="我叫小明"),
AIMessage(content="你好小明!")
]
messages = template.format_messages(
history=history,
input="还记得我的名字吗?"
)
print(len(messages)) # 4 条:system + 2条历史 + user
MessagesPlaceholder 是构建多轮对话应用的"变速箱"。它让动态的历史消息可以平滑地插入到固定模板的指定位置。后续我们学习 Memory 组件时,就是用它来将自动管理的历史注入链中。
三、少样本提示:给模型几张"小抄"
3.1 什么是少样本提示?
有时候,光靠角色描述还不够让模型准确理解你的格式要求。比如你希望模型把"产品吐槽"精准分类成"质量问题"、"价格问题"、"物流问题",语言描述可能说不清。最简单有效的方法,就是举几个例子,让模型照猫画虎。
这被称为少样本提示(Few-shot Prompting) 。你不需要微调模型,也不需要写复杂规则,只要在提示词里嵌入两三组"输入→输出"样本就行。LangChain 为此专门设计了 FewShotChatMessagePromptTemplate。
3.2 用样本集构建少样本模板
python
from langchain_core.prompts import FewShotChatMessagePromptTemplate, ChatPromptTemplate
# 1. 准备样本集
examples = [
{"input": "快递盒子瘪了", "output": "包装问题"},
{"input": "打开发现屏幕有裂纹", "output": "质量问题"},
{"input": "三天了还没发货", "output": "物流问题"},
]
# 2. 将每个样本转换为一组 User / Assistant 消息对的模板
example_prompt = ChatPromptTemplate.from_messages([
("user", "{input}"),
("assistant", "{output}")
])
# 3. 创建少样本模板
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=examples,
)
# 4. 组合进最终的完整模板
final_template = ChatPromptTemplate.from_messages([
("system", "你是一位客服问题分类器。将用户投诉分为:包装问题、质量问题、物流问题。"),
few_shot_prompt, # 会自动把样本铺开成多组消息
("user", "{input}")
])
# 使用:一条全新投诉
messages = final_template.format_messages(input="我买的是红色,结果发来蓝色")
print("\n---\n".join([str(m) for m in messages]))
运行这段代码,你会发现最终发给模型的消息中,已经铺好了三个样本轮次,紧接着是真实的用户输入。模型经历了这几组"示范",会非常有把握地返回正确分类。
3.3 动态选择样本
如果样本集很大,你不可能把所有样本都塞进提示词(token 成本太高)。LangChain 允许你基于语义相似度,从样本库中动态挑选与当前问题最相关的几个样本。这需要配合嵌入模型和向量检索,我们将下两篇在讲解提示词模板进阶时详细展开。现在你只需要知道:少样本提示是个极其有效的技巧,而 LangChain 把它做得像搭积木一样简单。
四、从文件加载:让提示词走出代码
当应用变复杂时,把提示词硬写在代码里会严重拖累维护效率。理想的方式是把提示词存储在 .yaml、.json 甚至单独的文件中,代码只在运行时加载它们。这样,产品经理也能直接修改提示词,无需翻 Python 代码。
4.1 用 YAML 定义提示词
创建一个 prompts.yaml 文件:
yaml
translation_prompt:
_type: chat
messages:
- role: system
content: "你是一个专业的翻译助手,专门将{source_lang}翻译为{target_lang}。"
- role: user
content: "请翻译:{input}"
在代码中加载:
python
from langchain_core.prompts import load_prompt
# 从 YAML 文件加载模板
prompt = load_prompt("prompts.yaml")
# 使用方式和手写模板完全一样
messages = prompt.format_messages(
source_lang="中文",
target_lang="英文",
input="人生苦短"
)
YAML 文件还支持定义少样本模板、消息占位符等复杂结构。团队协作时,所有人围绕这个 YAML 文件工作,版本管理、Diff 对比、回滚都非常方便。
注意:生产环境中,你也可以把提示词存储在数据库中,启动时加载到内存,或通过配置中心动态下发。模板对象本身是可序列化和可缓存的。
五、实战串联:一个"文章标题生成器"的完整模板设计
最后,我们把本章学到的几个要点串联起来,设计一个稍复杂的模板------文章标题生成器。它需要接收文章摘要,并根据用户指定的风格和平台(如微信公众号、掘金、知乎)生成 3 条候选标题。
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, FewShotChatMessagePromptTemplate
# 1. 准备少样本示例
title_examples = [
{
"summary": "介绍 Python 3.12 的新特性",
"platform": "掘金",
"examples_output": "1. Python 3.12 发布:这 5 个新特性让效率翻倍\n2. 别再用旧版了!Python 3.12 的这些改进你该知道\n3. 升级 Python 3.12 的第一天,我被这 3 个变化震惊了"
},
{
"summary": "分享自己学习 Rust 语言的心得",
"platform": "微信公众号",
"examples_output": "1. 30 岁转行学 Rust,我的第 100 天真实感受\n2. 从 Python 到 Rust:一个开发者的痛与快乐\n3. 为什么我建议所有后端程序员都学一点 Rust?"
}
]
example_prompt = ChatPromptTemplate.from_messages([
("user", "文章摘要:{summary}\n目标平台:{platform}"),
("assistant", "{examples_output}")
])
few_shot = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=title_examples,
)
# 2. 组装完整模板
title_template = ChatPromptTemplate.from_messages([
("system", """你是一位资深内容运营专家。
任务:根据文章摘要,为目标平台生成 3 条吸引人的文章标题。
要求:
- 标题必须契合 {platform} 平台的用户风格
- 字数控制在 20-30 字之间
- 只返回 3 条标题,每行一条,不要编号"""),
few_shot,
("user", "文章摘要:{summary}\n目标平台:{platform}")
])
# 3. 使用
messages = title_template.format_messages(
summary="本文详细讲解 FastAPI 中依赖注入的高级用法,包括依赖嵌套、作用域管理和测试技巧。",
platform="掘金"
)
# 将 messages 发给模型即可拿到 3 条专业标题
这个模板集齐了三大要素:清晰的系统指令 (角色+任务+限制条件)、少样本示例 (用两个案例展示期望输出格式)、动态变量 (summary 和 platform)。它不是一个随手丢弃的字符串,而是一个可以持续迭代的"提示文档"。
六、今日收获与下篇预告
今天,我们给提示词穿上了"工程化"的外衣:
- 你理解了硬编码提示词的三大痛点,以及模板如何通过关注点分离解决它们。
- 你掌握了
ChatPromptTemplate.from_messages的完整用法,包括系统消息、用户消息和动态历史占位符。 - 你学会了用
FewShotChatMessagePromptTemplate精确控制输出格式,让模型"照猫画虎"。 - 你体验了从 YAML 文件加载提示词的工程化实践,以及如何串联出一个复杂的标题生成器模板。
现在,你手中的提示词已经从一段段脆弱的字符串进化为可复用的结构化组件。但模板还有一个更深的维度:当对话历史、工具调用、条件分支这些动态元素大量涌入时,单个模板已经不够用了。我们需要学会像导演一样,编排多个模板的协作,处理复杂的运行时逻辑。
下一篇《模板大师:编排复杂的提示词逻辑》,我们将深入 ChatPromptTemplate 的高级特性,学习如何构建带条件分支、多轮对话面板、以及动态工具列表的复杂提示词系统。提示词工程的真正艺术,将由此展开。
下一篇见!