Prompt 是 Agent 的任务说明书。它负责把模型需要遵守的规则、当前用户输入、上下文资料和输出要求组织清楚。
这一篇主要看这些内容:
txt
PromptTemplate
-> 模块化拆分
-> partial 预填变量
-> Message / ChatPromptTemplate
-> MessagesPlaceholder
-> FewShotPromptTemplate
-> ExampleSelector
先记住一句话:PromptTemplate 管字符串,ChatPromptTemplate 管消息数组。
Message 是什么
调用聊天模型时,通常不是只传一段孤立文本,而是传一组带角色的消息。
角色的意义是让模型分清楚:哪些是长期规则,哪些是本轮用户问题,哪些是历史回答,哪些是工具结果。
常见角色:
system:长期规则,比如模型身份、回答边界、输出风格。human:当前用户问题,或者本轮任务需要处理的输入。ai:模型之前的回复,常用于多轮对话历史。tool:工具执行结果,后面 Tool 章节再展开。
所以 Message 可以理解成 Agent 的上下文格式。PromptTemplate 先把变量整理成内容,ChatPromptTemplate 再把这些内容分配到不同角色里。
PromptTemplate
PromptTemplate 用 {变量名} 占位,再用 format() 填充变量。
下面用一个很轻的 NBA 案例:给詹姆斯写一段球员观察稿。
python
from langchain_core.prompts import PromptTemplate
observation_template = PromptTemplate.from_template(
"""
你是一位{role},回答风格:{tone}。
面向读者:{audience}。
请写一段 NBA 球员观察:{player}
观察主题:
{topic}
比赛背景:
{game_context}
分析角度:
{angle}
输出要求:
1. 先给出一句核心判断。
2. 再展开 2~3 个观察点。
3. 不要编造具体数据。
4. 控制在 {word_count_hint} 字左右。
""".strip()
)
prompt = observation_template.format(
role="懂 NBA 的内容作者",
tone="清楚、克制、有球迷视角",
audience="喜欢詹姆斯的普通读者",
player="勒布朗·詹姆斯",
topic="老将核心如何影响比赛节奏",
game_context="不引用实时比赛数据,只基于常见比赛观察来写。",
angle="组织进攻、转换节奏、关键回合选择",
word_count_hint="250",
)
print(prompt)
这段代码里有三个关键点:
PromptTemplate:表示一个字符串模板对象。from_template():从一段模板字符串创建PromptTemplate。format():把变量填进模板,得到最终要发给模型的 Prompt 字符串。
这样比手写字符串拼接清楚:
- 模板负责结构。
- 变量负责动态内容。
- 缺哪个字段更容易发现。
这里的重点不是语法,而是思路:把会变化的内容变成参数,把不变的任务结构留在模板里。
模块化拆分
复杂 Prompt 不要一直往一个大字符串里塞,应该拆成多个小模块,再汇总成最终 Prompt。
在 Python 版里,可以直接用多个 PromptTemplate 手动组合。重点是掌握这个思路:拆模块,再汇总。
python
from langchain_core.prompts import PromptTemplate
# 模型身份和回答风格
persona_prompt = PromptTemplate.from_template(
"""你是一位{role},回答风格:{tone}。
面向读者:{audience}。"""
)
# 本次 NBA 观察稿的资料
context_prompt = PromptTemplate.from_template(
"""【观察资料】
球员:{player}
主题:{topic}
比赛背景:{game_context}
分析角度:{angle}"""
)
# 具体写作任务
task_prompt = PromptTemplate.from_template(
"""请写一段 NBA 球员观察稿:
1. 先给出核心判断。
2. 再展开 2~3 个观察点。
3. 不要编造具体数据。"""
)
# 输出格式
format_prompt = PromptTemplate.from_template(
"""输出要求:
- 篇幅:约 {word_count_hint} 字。
- 语气:{tone_constraint}。
- 直接输出正文,不要列太多术语。"""
)
# 最终汇总
final_prompt = PromptTemplate.from_template(
"""{persona_block}
{context_block}
{task_block}
{format_block}"""
)
def format_player_observation(data: dict[str, str]) -> str:
"""把拆开的 Prompt 模块合并成最终提示词。"""
return final_prompt.format(
persona_block=persona_prompt.format(**data),
context_block=context_prompt.format(**data),
task_block=task_prompt.format(),
format_block=format_prompt.format(**data),
)
这段代码里有几个名字容易混:
persona_prompt、context_prompt、task_prompt、format_prompt:每一个都是一个小模板。final_prompt:负责把几个小模板的结果汇总起来。format_player_observation():普通 Python 函数,用来串起整个格式化过程。**data:把字典里的字段展开成模板需要的变量。
重点不是一定要用某个类,而是 Prompt 设计上不要把所有内容堆在一个大字符串里。
partial 预填变量
有些变量每次都一样,比如作者身份、读者身份、输出字数、语气要求。每次 format() 都传一遍会很重复,多余。
partial() 可以先固定一部分变量。
python
persona_prompt_with_defaults = persona_prompt.partial(
role="懂 NBA 的内容作者",
tone="清楚、克制、有球迷视角",
audience="喜欢詹姆斯的普通读者",
)
format_prompt_with_defaults = format_prompt.partial(
word_count_hint="250",
tone_constraint="像赛后短评,不像数据报告",
)
def format_player_observation_with_defaults(player_data: dict[str, str]) -> str:
"""只传本次变化的球员观察资料,稳定规则交给 partial 预填。"""
return final_prompt.format(
persona_block=persona_prompt_with_defaults.format(),
context_block=context_prompt.format(**player_data),
task_block=task_prompt.format(),
format_block=format_prompt_with_defaults.format(),
)
james_prompt = format_player_observation_with_defaults(
{
"player": "勒布朗·詹姆斯",
"topic": "老将核心如何影响比赛节奏",
"game_context": "不引用实时比赛数据,只基于常见比赛观察来写。",
"angle": "组织进攻、转换节奏、关键回合选择",
}
)
这段代码要看懂两步:
partial()不会立刻生成最终 Prompt,它只是先固定一部分变量。- 后面的
format_player_observation_with_defaults()只需要再接收本次变化的变量。
partial() 的价值不是"少写几行代码",而是把稳定规则和动态输入分开:
txt
稳定规则:角色、语气、读者、输出格式
动态输入:球员、主题、比赛背景、分析角度
ChatPromptTemplate
PromptTemplate 生成字符串,但聊天模型更适合接收消息数组。
ChatPromptTemplate 用来给 Prompt 分配消息角色。
python
from langchain_core.prompts import ChatPromptTemplate
chat_prompt = ChatPromptTemplate.from_messages(
[
("system", "{persona_block}"),
(
"human",
"""观察资料:
{context_block}
任务:
{task_block}
输出格式:
{format_block}""",
),
]
)
messages = chat_prompt.format_messages(
persona_block="你是一位懂 NBA 的内容作者,回答要克制、准确。",
context_block="球员:勒布朗·詹姆斯\n主题:老将核心如何影响比赛节奏\n背景:不引用实时比赛数据。",
task_block="写一段 NBA 球员观察稿,重点看组织进攻、转换节奏、关键回合选择。",
format_block="输出 250 字以内中文段落。",
)
print(messages)
这段代码里:
ChatPromptTemplate:表示"消息数组模板"。from_messages():创建一组带角色的消息模板。format_messages():填充变量后,得到真正要传给聊天模型的消息数组。
这里的重点是:system 和 human 不要混在一起。
system放长期规则。human放当前任务和输入。
消息角色类
除了 ("system", "...") 这种 tuple 写法,也可以用角色模板类。
python
from langchain_core.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
)
persona_prompt = SystemMessagePromptTemplate.from_template(
"你是一位{role},回答风格:{tone}。"
)
context_prompt = HumanMessagePromptTemplate.from_template(
"""【观察资料】
球员:{player}
主题:{topic}
比赛背景:{game_context}
分析角度:{angle}"""
)
task_prompt = HumanMessagePromptTemplate.from_template(
"请基于资料写一段 NBA 球员观察稿,注意不要编造具体数据。"
)
intro_chat_prompt = ChatPromptTemplate.from_messages(
[persona_prompt, context_prompt, task_prompt]
)
messages = intro_chat_prompt.format_messages(
role="懂 NBA 的内容作者",
tone="清楚、克制、有球迷视角",
player="勒布朗·詹姆斯",
topic="阅读比赛的经验价值",
game_context="不引用实时比赛数据。",
angle="防守选择、传球路线、关键回合判断",
)
这段代码和 tuple 写法做的是同一件事,只是拆得更明确:
SystemMessagePromptTemplate:专门生成system消息。HumanMessagePromptTemplate:专门生成human消息。ChatPromptTemplate.from_messages():把这些消息模板组合起来。
适合这样理解:
txt
PromptTemplate:生成字符串
SystemMessagePromptTemplate:生成 system 消息
HumanMessagePromptTemplate:生成 human 消息
ChatPromptTemplate:把多条消息模板组装起来
MessagesPlaceholder
MessagesPlaceholder 是"消息插槽"。
它不是普通字符串占位符,而是给一组历史消息留位置。
python
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一名懂 NBA 的内容作者,回答要结合历史对话,避免重复。",
),
MessagesPlaceholder("history"),
("human", "这是用户本轮的新问题:{current_input}"),
]
)
messages = prompt.format_messages(
history=[
HumanMessage(content="这篇观察稿不要编造实时数据。"),
AIMessage(content="明白,只基于常见比赛观察来写。"),
],
current_input="写一段詹姆斯如何影响比赛节奏的观察。",
)
这段代码里最重要的是 MessagesPlaceholder("history"):
history是插槽名字。format_messages()时,必须给它传一组真实消息。- 它适合放多轮对话历史,而不是放普通字符串。
注意:
history必须是一组消息。- 它适合接 Memory 保存的历史对话。
- 不要把历史对话拼成一个巨大字符串再塞进去。
FewShotPromptTemplate
Few-shot 的意思是"给模型几个例子,让它照着格式回答"。
python
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
examples = [
{
"player": "勒布朗·詹姆斯",
"answer": "詹姆斯的价值不只在得分,还在于他能用阅读比赛的能力影响队友站位和进攻节奏。",
},
{
"player": "斯蒂芬·库里",
"answer": "库里的威胁不只来自持球投篮,他的无球跑动也会持续拉扯防守阵型。",
},
]
example_prompt = PromptTemplate.from_template(
"球员:{player}\n解释:{answer}"
)
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
prefix="请参考下面的例子,用同样风格写 NBA 球员观察。",
suffix="球员:{input}\n解释:",
input_variables=["input"],
)
formatted = prompt.format(input="凯文·杜兰特")
print(formatted)
这段代码里:
examples:准备好的示例数据。example_prompt:每个示例应该被格式化成什么样。FewShotPromptTemplate:把多个示例和当前输入合成一个完整 Prompt。prefix/suffix:分别控制示例前后的说明文字。
Few-shot 适合用在:
- 想固定输出风格。
- 想让模型照着格式生成。
- 规则很难说清,但例子很直观。
ExampleSelector
如果例子很多,不一定每次都全部塞给模型。
ExampleSelector 的作用是:根据当前输入,挑几个最合适的例子放进 Prompt。
这里先把名字分清楚:
FewShotPromptTemplate负责"把例子放进 Prompt"。ExampleSelector负责"从很多例子里挑哪些例子"。- 两者经常一起用,但不是一回事。
LengthBasedExampleSelector
按长度控制例子数量,避免 Prompt 太长。
python
from langchain_core.example_selectors import LengthBasedExampleSelector
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
examples = [
{"player": "勒布朗·詹姆斯", "answer": "重点看阅读比赛、组织进攻和关键回合选择。"},
{"player": "斯蒂芬·库里", "answer": "重点看无球牵制、投篮威胁和空间影响。"},
{"player": "尼古拉·约基奇", "answer": "重点看高位策应、传球视野和进攻中轴价值。"},
{"player": "扬尼斯·阿德托昆博", "answer": "重点看冲击篮筐、转换进攻和防守覆盖。"},
]
example_prompt = PromptTemplate.from_template(
"球员:{player}\n解释:{answer}"
)
example_selector = LengthBasedExampleSelector(
examples=examples,
example_prompt=example_prompt,
max_length=120,
)
prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="请用同样风格写 NBA 球员观察。",
suffix="球员:{input}\n解释:",
input_variables=["input"],
)
这个选择器只关心长度,不关心语义相似度。
SemanticSimilarityExampleSelector
语义选择器会根据当前输入,挑语义最接近的例子。
这一块需要向量模型和向量库,依赖比前面更重,先了解用途即可:
txt
用户输入:Agent 怎么查外部资料?
选择器可能挑中:RAG、Retriever、VectorStore 相关例子
适合用在:
- few-shot 例子很多。
- 不同问题需要不同示例。
- 你希望 Prompt 自动挑最相关的例子。
小结
这一篇要记住几个核心点:
PromptTemplate负责字符串模板。- 模块化拆分能让 Prompt 更容易维护。
partial()可以预填稳定变量。ChatPromptTemplate负责生成带角色的消息数组。MessagesPlaceholder给历史消息留位置。FewShotPromptTemplate让模型照着例子回答。ExampleSelector负责从很多例子里挑合适的例子。