上一篇介绍了 LangChain 中的消息。我们已经知道,聊天模型接收的通常不是一段孤立文字,而是由 SystemMessage、HumanMessage、AIMessage 等对象组成的消息列表。
接下来会遇到另一个问题:消息结构可以固定,但其中的角色、问题、语言、历史消息和示例会不断变化。如果每次调用都重新拼接字符串和消息列表,代码很快就会变得重复并且难以维护。
提示词模板就是用来解决这个问题的。它把固定结构保留下来,把变化内容声明为变量,运行时再填入具体值。
本文从最简单的 PromptTemplate 开始,逐步学习 ChatPromptTemplate、变量占位符、消息占位符、partial()、Few-shot 和 ICL,最后把模板真实交给本地 Qwen3 验证结果。
先从整体上区分字符串模板和聊天模板的输入、输出:

PromptTemplate 最终组织一段字符串;ChatPromptTemplate 会保留消息角色、顺序和历史结构。两者都可以继续连接聊天模型,但模板渲染完成并不等于模型已经生成回答。
1. 为什么不只使用 Python 字符串
1.1 字符串拼接可以使用,但容易失控
最简单的提示词可以用 f-string:
language = "中文"
question = "什么是提示词模板?"
prompt = f"请用{language}解释:{question}"
只有一两个变量时,这样写没有问题。但当提示词中包含角色、输出格式、历史消息和多个示例时,会逐渐出现这些问题:
- 固定文字和变量混在一起,可读性下降。
- 相同结构分散在多个文件中,修改容易遗漏。
- 变量名写错时,很难快速发现。
- system、human、ai 等角色需要手动组织。
- 历史消息和 Few-shot 示例只能继续手动拼接。
1.2 提示词模板分离固定结构和动态变量
模板可以先声明:
请用{language}解释:{question}
运行时再传入:
language = 中文
question = 什么是提示词模板?
最终得到:
请用中文解释:什么是提示词模板?
模板不会自动提高模型能力,也不能保证模型不产生错误。它的主要价值是让输入结构清楚、可复用、可检查,并且可以继续与 LangChain 模型组件组合。
2. 运行环境与代码目录
代码位于:
~/Documents/git/blog/source/_posts/llm_learning/langchain/p05_prompt_templates
当前共有八个示例:
p05_prompt_templates/
├── 01_prompt_template_basic.py
├── 02_prompt_chain_and_composition.py
├── 03_chat_prompt_template_basic.py
├── 04_messages_placeholder.py
├── 05_partial_and_message_templates.py
├── 06_few_shot_prompt_template.py
├── 07_few_shot_chat_prompt_template.py
└── 08_few_shot_chat_with_model.py
进入项目并激活 Python 3.12 虚拟环境:
cd ~/Documents/git/blog/source/_posts/llm_learning
source .venv/bin/activate
检查版本:
python -c 'import importlib.metadata as m; print(m.version("langchain-core"))'
本文固定并实测的版本:
1.0.2
运行脚本时会看到:
PyTorch was not found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
这是 Transformers 检查本地深度学习框架时产生的提示。六个纯模板示例不加载模型;02 和 08 通过 HTTP 调用独立的 MLX-LM Server,也不依赖 PyTorch,因此不影响本文结果。
3. PromptTemplate:字符串提示词模板
3.1 创建模板
PromptTemplate 用来生成一段普通字符串提示词。
最推荐的基础写法是:
PromptTemplate.from_template("请用{language}解释:{question}")
from_template() 会自动识别 {language} 和 {question},并把它们记录为模板需要的输入变量。
代码文件为 01_prompt_template_basic.py:
# 这个文件演示最基础的 PromptTemplate:查看变量,并比较 format 和 invoke。
from langchain_core.prompts import PromptTemplate
# from_template 会自动识别大括号中的变量名。
prompt = PromptTemplate.from_template("请用{language}解释:{question}")
print("模板需要的变量:", prompt.input_variables)
# format 返回普通字符串,适合先观察最终提示词长什么样。
formatted_prompt = prompt.format(language="中文", question="什么是提示词模板?")
print("format 结果:")
print(formatted_prompt)
print("format 返回类型:", type(formatted_prompt).__name__)
# invoke 返回 StringPromptValue,后续可以直接传给模型。
prompt_value = prompt.invoke({"language": "中文", "question": "什么是变量占位符?"})
print("\ninvoke 结果:")
print(prompt_value)
print("invoke 返回类型:", type(prompt_value).__name__)
运行:
python langchain/p05_prompt_templates/01_prompt_template_basic.py
真实输出:
模板需要的变量: ['language', 'question']
format 结果:
请用中文解释:什么是提示词模板?
format 返回类型: str
invoke 结果:
text='请用中文解释:什么是变量占位符?'
invoke 返回类型: StringPromptValue
3.2 format() 与 invoke() 的区别
| 方法 | 参数形式 | 返回类型 | 适合场景 |
|---|---|---|---|
| format() | 关键字参数 | str | 只需要最终字符串 |
| invoke() | 字典 | StringPromptValue | 后续要连接模型或其他 Runnable |
两者渲染出来的提示词内容相同,区别在于返回对象。StringPromptValue 是 LangChain 的提示词值对象,可以直接传给模型组件。
3.3 变量名必须一致
模板中写的是 {question}:
PromptTemplate.from_template("请解释:{question}")
调用时必须提供 question:
prompt.invoke({"question": "什么是变量?"})
如果误传 topic,LangChain 会报告缺少 question。需要在最终文本中保留普通大括号时,应使用双大括号:
{{question}}
4. 模板组合与本地模型调用
4.1 使用加号组合模板片段
langchain-core==1.0.2 可以把 PromptTemplate 与普通字符串使用 + 连接:
prompt = (
PromptTemplate.from_template("请写一句关于{topic}的报幕词。")
+ "要求:内容轻松幽默;"
+ "使用{language}输出。"
)
组合后,topic 和 language 都会成为必填变量。
4.2 使用管道符连接模型
代码文件为 02_prompt_chain_and_composition.py:
# 这个文件演示使用加号组合字符串模板,并通过管道符调用本地 Qwen3。
# 运行前需要先按照第二篇文章启动 mlx_lm.server。
import httpx
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# 创建最基础的字符串模板,再继续追加两个要求。
prompt = (
PromptTemplate.from_template("请写一句关于{topic}的报幕词。")
+ "要求:内容轻松幽默;"
+ "使用{language}输出。"
)
print("组合后的完整提示词:")
print(prompt.format(topic="相声", language="中文"))
# 创建本地聊天模型,地址与第二篇文章保持一致。
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
model="default_model",
temperature=0,
max_tokens=128,
http_client=httpx.Client(trust_env=False),
)
# 管道符表示先渲染提示词,再把结果交给模型。
chain = prompt | llm
response = chain.invoke({"topic": "相声", "language": "中文"})
print("\n本地 Qwen3 回答:")
print(response.content)
运行前先在另一个终端启动模型服务:
"$VIRTUAL_ENV/bin/python" -m mlx_lm server \
--model model \
--host 127.0.0.1 \
--port 18080 \
--chat-template-args '{"enable_thinking": false}'
然后运行:
python langchain/p05_prompt_templates/02_prompt_chain_and_composition.py
本次真实输出:
组合后的完整提示词:
请写一句关于相声的报幕词。要求:内容轻松幽默;使用中文输出。
本地 Qwen3 回答:
大家好,这里咱们不聊诗,不聊酒,就聊聊那让人笑出腹肌的相声!
这里的执行顺序是:
输入变量 -> PromptTemplate -> StringPromptValue -> ChatOpenAI -> AIMessage
prompt | llm 使用的是 LangChain 的管道组合能力。本文只需要理解“前一个组件的输出交给后一个组件”,LCEL 的更多内容留到后续文章。
5. ChatPromptTemplate:聊天提示词模板
5.1 为什么聊天模型更适合 ChatPromptTemplate
PromptTemplate 生成普通字符串,没有 system、human、ai 的角色结构。
ChatPromptTemplate 生成消息列表,每条内容都有明确角色:
ChatPromptTemplate.from_messages(
[
("system", "你是一个中文老师。"),
("human", "{question}"),
]
)
对于本文使用的 Qwen3 聊天模型,ChatPromptTemplate 是后续更常用的模板。
5.2 三种调用方式
代码文件为 03_chat_prompt_template_basic.py:
# 这个文件演示 ChatPromptTemplate,并比较 invoke、format、format_messages。
from langchain_core.prompts import ChatPromptTemplate
# 聊天模板由多条消息组成,每条消息都有自己的角色。
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个{role},回答要适合{audience}阅读。"),
("human", "你好,请先介绍一下你自己。"),
("ai", "你好,我会用清晰、简洁的方式回答你的问题。"),
("human", "{question}"),
]
)
values = {
"role": "中文老师",
"audience": "初学者",
"question": "请用两句话介绍春天。",
}
# invoke 返回 ChatPromptValue。
prompt_value = prompt.invoke(values)
print("invoke 返回类型:", type(prompt_value).__name__)
print(prompt_value)
# format 返回带有角色标签的普通字符串。
formatted_text = prompt.format(**values)
print("\nformat 返回类型:", type(formatted_text).__name__)
print(formatted_text)
# format_messages 返回消息对象列表。
messages = prompt.format_messages(**values)
print("\nformat_messages 返回类型:", type(messages).__name__)
print("format_messages 结果:")
for message in messages:
print(type(message).__name__, message.content)
运行:
python langchain/p05_prompt_templates/03_chat_prompt_template_basic.py
真实输出:
invoke 返回类型: ChatPromptValue
messages=[SystemMessage(content='你是一个中文老师,回答要适合初学者阅读。', additional_kwargs={}, response_metadata={}), HumanMessage(content='你好,请先介绍一下你自己。', additional_kwargs={}, response_metadata={}), AIMessage(content='你好,我会用清晰、简洁的方式回答你的问题。', additional_kwargs={}, response_metadata={}), HumanMessage(content='请用两句话介绍春天。', additional_kwargs={}, response_metadata={})]
format 返回类型: str
System: 你是一个中文老师,回答要适合初学者阅读。
Human: 你好,请先介绍一下你自己。
AI: 你好,我会用清晰、简洁的方式回答你的问题。
Human: 请用两句话介绍春天。
format_messages 返回类型: list
format_messages 结果:
SystemMessage 你是一个中文老师,回答要适合初学者阅读。
HumanMessage 你好,请先介绍一下你自己。
AIMessage 你好,我会用清晰、简洁的方式回答你的问题。
HumanMessage 请用两句话介绍春天。
三种方法的区别:
| 方法 | 返回类型 | 用途 |
|---|---|---|
| invoke() | ChatPromptValue | 直接连接聊天模型或其他 Runnable |
| format() | str | 查看带角色标签的文本表示 |
| format_messages() | list | 直接取得最终消息对象列表 |
5.3 from_messages() 支持哪些输入
from_messages() 支持多种消息表示形式。
| 输入形式 | 示例 | 说明 |
|---|---|---|
| 字符串 | “你好,{name}” | 默认当作 human 消息,不推荐大量使用 |
| 元组 | (“human”, “你好,{name}”) | 角色清楚,最常用 |
| 字典 | {“role”: “human”, “content”: “你好,{name}”} | 接近接口 JSON 格式 |
| 消息对象 | HumanMessage(“你好”) | 已经生成好的固定消息 |
| 消息模板 | HumanMessagePromptTemplate.from_template(…) | 明确创建某一角色模板 |
| 聊天模板 | 嵌套 ChatPromptTemplate | 组合已有聊天模板 |
已实例化的消息对象不是模板。下面的 {name} 不会被替换:
HumanMessage(content="你好,我是{name}")
需要变量时,应使用元组、字典或 HumanMessagePromptTemplate。
6. 变量占位符与消息占位符
6.1 两种占位符解决的问题不同
普通变量占位符替换一条消息中的某个值:
("human", "请介绍:{topic}")
topic 的值应该是字符串,例如“春天”。
消息占位符插入一条或多条完整消息:
MessagesPlaceholder(variable_name="history")
history 的值应该是消息列表,而不是普通字符串。
两种占位符的替换单位不同:

普通变量占位符只替换消息正文中的一个值;MessagesPlaceholder 会展开消息列表,并保留每条消息的类型、顺序和元数据。因此,不能把一段普通字符串直接当作 history 传入。
6.2 简写与显式写法存在默认差异
代码文件为 04_messages_placeholder.py:
# 这个文件演示消息占位符,并比较简写可选与显式默认必填的差异。
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 写法一:简写 placeholder 默认是可选变量,没有历史消息时会使用空列表。
shorthand_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个友好的数学老师。"),
("placeholder", "{history}"),
("human", "{question}"),
]
)
result1 = shorthand_prompt.invoke({"question": "2 + 3 等于多少?"})
print("简写 placeholder,不传 history:")
print("input_variables =", shorthand_prompt.input_variables)
print("optional_variables =", shorthand_prompt.optional_variables)
print(result1)
# 写法二:显式 MessagesPlaceholder 默认必填,缺少 history 会报 KeyError。
required_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个友好的数学老师。"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
print("\n显式 MessagesPlaceholder,不传 history:")
try:
required_prompt.invoke({"question": "刚才的结果再加 8 等于多少?"})
except KeyError as error:
print(type(error).__name__, str(error).splitlines()[0])
result2 = required_prompt.invoke(
{
"history": [
HumanMessage("10 - 4 等于多少?"),
AIMessage("10 - 4 = 6。"),
],
"question": "刚才的结果再加 8 等于多少?",
}
)
print("\n显式 MessagesPlaceholder,传入 history:")
print(result2)
# 写法三:显式设置 optional=True 后,也可以不传历史消息。
optional_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个友好的数学老师。"),
MessagesPlaceholder(variable_name="history", optional=True),
("human", "{question}"),
]
)
result3 = optional_prompt.invoke({"question": "5 + 6 等于多少?"})
print("\n显式 MessagesPlaceholder,optional=True:")
print(result3)
运行:
python langchain/p05_prompt_templates/04_messages_placeholder.py
真实关键输出:
简写 placeholder,不传 history:
input_variables = ['question']
optional_variables = ['history']
messages=[SystemMessage(content='你是一个友好的数学老师。', ...), HumanMessage(content='2 + 3 等于多少?', ...)]
显式 MessagesPlaceholder,不传 history:
KeyError "Input to ChatPromptTemplate is missing variables {'history'}..."
显式 MessagesPlaceholder,传入 history:
messages=[SystemMessage(...), HumanMessage(content='10 - 4 等于多少?', ...), AIMessage(content='10 - 4 = 6。', ...), HumanMessage(content='刚才的结果再加 8 等于多少?', ...)]
显式 MessagesPlaceholder,optional=True:
messages=[SystemMessage(...), HumanMessage(content='5 + 6 等于多少?', ...)]
这三种写法可以归纳为:
| 写法 | 默认是否必填 |
|---|---|
| (“placeholder”, “{history}”) | 否,缺少时使用空列表 |
| MessagesPlaceholder(“history”) | 是 |
| MessagesPlaceholder(“history”, optional=True) | 否 |
这个差异由本机 langchain-core==1.0.2 实际验证,不能只根据两种写法看起来相似就认为行为完全一样。
7. 消息模板与 partial()
7.1 三类消息模板
LangChain 为常见角色提供了对应模板类:
| 模板类 | 生成的消息 |
|---|---|
| SystemMessagePromptTemplate | SystemMessage |
| HumanMessagePromptTemplate | HumanMessage |
| AIMessagePromptTemplate | AIMessage |
7.2 使用 partial() 预填变量
partial() 用来提前固定一部分变量,返回一个新的模板。后续调用只需要提供剩余变量。
代码文件为 05_partial_and_message_templates.py:
# 这个文件演示 partial 和消息模板:预先固定一部分变量,调用时只传剩余变量。
from langchain_core.prompts import (
AIMessagePromptTemplate,
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
)
# 分别创建 system、human、ai 三类消息模板。
system_template = SystemMessagePromptTemplate.from_template(
"你是{role},回答要适合{audience}阅读。"
)
greeting_template = HumanMessagePromptTemplate.from_template("你好,请介绍一下你自己。")
ai_template = AIMessagePromptTemplate.from_template(
"你好,我会用简单、清楚的方式回答问题。"
)
question_template = HumanMessagePromptTemplate.from_template("请回答:{question}")
prompt = ChatPromptTemplate.from_messages(
[
system_template,
greeting_template,
ai_template,
question_template,
]
)
# partial 会预填固定变量,后面就不用每次重复传。
beginner_prompt = prompt.partial(role="生活常识老师", audience="初学者")
result = beginner_prompt.invoke({"question": "为什么天空看起来是蓝色的?"})
print("partial 后的聊天提示词:")
print(result)
print("剩余必填变量:", beginner_prompt.input_variables)
运行:
python langchain/p05_prompt_templates/05_partial_and_message_templates.py
真实输出:
partial 后的聊天提示词:
messages=[SystemMessage(content='你是生活常识老师,回答要适合初学者阅读。', additional_kwargs={}, response_metadata={}), HumanMessage(content='你好,请介绍一下你自己。', additional_kwargs={}, response_metadata={}), AIMessage(content='你好,我会用简单、清楚的方式回答问题。', additional_kwargs={}, response_metadata={}), HumanMessage(content='请回答:为什么天空看起来是蓝色的?', additional_kwargs={}, response_metadata={})]
剩余必填变量: ['question']
role 和 audience 已经由 partial() 固定,所以模板只剩下 question 需要在运行时传入。
8. ICL:在上下文中提供示例
8.1 ICL 不会修改模型参数
ICL 全称是 In-Context Learning,可以理解为“在当前上下文中学习”。
它的基本做法是:
- 在提示词中放入少量输入和答案示例。
- 再放入一个新的问题。
- 让模型根据示例中的格式或规律回答新问题。
ICL 不是训练,也不是微调。模型权重没有变化,示例只存在于当前输入中。下一次调用如果不再提供这些示例,模型也不会自动记住它们。
Few-shot 模板只是把示例按照指定格式渲染,再和说明、当前问题一起放入本次请求:

字符串 Few-shot 会把示例渲染成问答文本,聊天 Few-shot 会渲染成 human/ai 消息对。两种方式都只改变当前请求的上下文,不会修改模型参数。
8.2 FewShotPromptTemplate 的组成
字符串 Few-shot 模板常见组成:
| 参数 | 作用 |
|---|---|
| examples | 保存多个示例字典 |
| example_prompt | 决定单个示例怎样渲染 |
| prefix | 放在所有示例之前的说明 |
| suffix | 放在示例之后的最终问题 |
| input_variables | 运行时需要提供的变量 |
| example_separator | 示例之间的分隔符 |
代码文件为 06_few_shot_prompt_template.py:
# 这个文件演示 FewShotPromptTemplate:把少量示例拼进字符串提示词中,让模型按示例回答。
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
# examples 是示例列表,每个示例的 key 要和 example_prompt 中的变量名一致。
examples = [
{"question": "苹果 -> 红色,香蕉 -> ?", "answer": "黄色"},
{"question": "天空 -> 蓝色,草地 -> ?", "answer": "绿色"},
]
# 单个示例怎么展示,由 example_prompt 决定。
base_template = PromptTemplate.from_template("问题:{question}\n答案:{answer}")
# FewShotPromptTemplate 会把多个示例和最终问题拼成完整提示词。
final_template = FewShotPromptTemplate(
examples=examples,
example_prompt=base_template,
prefix="请根据下面的示例回答颜色:",
suffix="问题:{input}\n答案:",
input_variables=["input"],
)
result = final_template.format(input="牛奶 -> 白色,煤炭 -> ?")
print("FewShotPromptTemplate 结果:")
print(result)
运行:
python langchain/p05_prompt_templates/06_few_shot_prompt_template.py
真实输出:
FewShotPromptTemplate 结果:
请根据下面的示例回答颜色:
问题:苹果 -> 红色,香蕉 -> ?
答案:黄色
问题:天空 -> 蓝色,草地 -> ?
答案:绿色
问题:牛奶 -> 白色,煤炭 -> ?
答案:
示例字典中的 question、answer 必须与 base_template 中的变量名一致。最终用户输入使用 input,因此调用时传入 input,而不是 question。
9. 聊天提示词模板中的 ICL
字符串 Few-shot 最终得到一段文本。聊天 Few-shot 则把每个示例组织成 human/ai 消息对。
下面使用一个鹦鹉符号表示未知运算,并提供两个示例:
2 🦜 2 -> 4
2 🦜 3 -> 5
它们共同说明鹦鹉符号代表加法。
代码文件为 07_few_shot_chat_prompt_template.py:
# 这个文件演示 FewShotChatMessagePromptTemplate:渲染聊天 ICL 的完整消息列表。
from langchain_core.messages import HumanMessage
from langchain_core.prompts import (
ChatPromptTemplate,
FewShotChatMessagePromptTemplate,
MessagesPlaceholder,
)
# 每个示例都包含一个用户问题和一个助手回答。
examples = [
{"input": "2 🦜 2", "output": "4"},
{"input": "2 🦜 3", "output": "5"},
]
# 单个聊天示例由一组 human/ai 消息组成。
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{output}"),
]
)
# FewShotChatMessagePromptTemplate 会把多个聊天示例插入到主聊天模板中。
few_shot_prompt = FewShotChatMessagePromptTemplate(
examples=examples,
example_prompt=example_prompt,
)
final_prompt = ChatPromptTemplate.from_messages(
[
("system", "请根据示例找出符号规律,只回答结果。"),
few_shot_prompt,
MessagesPlaceholder(variable_name="question"),
]
)
result = final_prompt.invoke({"question": [HumanMessage("8 🦜 9")]})
print("FewShotChatMessagePromptTemplate 结果:")
print(result)
运行:
python langchain/p05_prompt_templates/07_few_shot_chat_prompt_template.py
真实输出:
FewShotChatMessagePromptTemplate 结果:
messages=[SystemMessage(content='请根据示例找出符号规律,只回答结果。', additional_kwargs={}, response_metadata={}), HumanMessage(content='2 🦜 2', additional_kwargs={}, response_metadata={}), AIMessage(content='4', additional_kwargs={}, response_metadata={}), HumanMessage(content='2 🦜 3', additional_kwargs={}, response_metadata={}), AIMessage(content='5', additional_kwargs={}, response_metadata={}), HumanMessage(content='8 🦜 9', additional_kwargs={}, response_metadata={})]
最终消息顺序是:
- 系统规则。
- 第一组 human/ai 示例。
- 第二组 human/ai 示例。
- 用户真正的问题。
这里仍然只是渲染模板,还没有证明模型真的按照示例得到了 17。
10. 使用本地 Qwen3 验证聊天 ICL
代码文件为 08_few_shot_chat_with_model.py。它和上一个脚本使用相同模板,只增加本地模型并执行链。
# 这个文件把聊天 ICL 模板真正交给本地 Qwen3,验证模型能否学会示例规律。
# 运行前需要先按照第二篇文章启动 mlx_lm.server。
import httpx
from langchain_core.messages import HumanMessage
from langchain_core.prompts import (
ChatPromptTemplate,
FewShotChatMessagePromptTemplate,
MessagesPlaceholder,
)
from langchain_openai import ChatOpenAI
# 两个示例共同说明鹦鹉符号代表加法。
examples = [
{"input": "2 🦜 2", "output": "4"},
{"input": "2 🦜 3", "output": "5"},
]
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{output}"),
]
)
few_shot_prompt = FewShotChatMessagePromptTemplate(
examples=examples,
example_prompt=example_prompt,
)
final_prompt = ChatPromptTemplate.from_messages(
[
("system", "请根据示例找出符号规律,只回答结果。"),
few_shot_prompt,
MessagesPlaceholder(variable_name="question"),
]
)
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
model="default_model",
temperature=0,
max_tokens=32,
http_client=httpx.Client(trust_env=False),
)
# 先渲染提示词,再把完整消息列表交给本地模型。
chain = final_prompt | llm
response = chain.invoke({"question": [HumanMessage("8 🦜 9")]})
print("返回消息类型:", type(response).__name__)
print("本地 Qwen3 回答:", response.content)
运行:
python langchain/p05_prompt_templates/08_few_shot_chat_with_model.py
本次真实输出:
返回消息类型: AIMessage
本地 Qwen3 回答: 17
这次结果可以确认三件事:
- Few-shot 示例被正确插入消息列表。
- 完整聊天模板能够通过管道交给本地 Qwen3。
- 模型根据示例把鹦鹉符号理解为加法,得到 8 + 9 = 17。
这仍然不是训练。把示例从下一次请求中移除后,模型不一定继续使用相同符号规则。
11. 模板语法与 langchain-core==1.0.2 类参考
11.1 三种模板语法
PromptTemplate 和 ChatPromptTemplate 支持通过 template_format 选择模板语法。
| 格式 | 变量示例 | 建议 |
|---|---|---|
| f-string | {question} | 默认格式,本文全部使用 |
| mustache | 适合已有 Mustache 模板的场景 | |
| jinja2 | 功能更丰富,但不要使用不可信模板 |
官方 PromptTemplate 参考明确建议优先使用默认 f-string,不要接受来自不可信来源的 Jinja2 模板。本文没有使用 Mustache 或 Jinja2,以免在基础阶段混淆占位符语法。
11.2 langchain-core 1.0.2 顶层导出类
以下列表来自本机 langchain_core.prompts 实际导出结果,共 14 个模板或占位符相关类。
本文重点使用
| 类 | 作用 |
|---|---|
| PromptTemplate | 生成字符串提示词 |
| ChatPromptTemplate | 生成聊天消息列表 |
| MessagesPlaceholder | 插入一组完整消息 |
| FewShotPromptTemplate | 生成字符串 Few-shot 提示词 |
| FewShotChatMessagePromptTemplate | 生成聊天消息 Few-shot 示例 |
| SystemMessagePromptTemplate | 生成系统消息 |
| HumanMessagePromptTemplate | 生成用户消息 |
| AIMessagePromptTemplate | 生成 AI 消息 |
其他具体模板
| 类 | 作用 | 本文处理方式 |
|---|---|---|
| ChatMessagePromptTemplate | 生成自定义 role 的 ChatMessage | 了解用途 |
| DictPromptTemplate | 递归渲染字典值中的变量 | 不展开代码 |
| FewShotPromptWithTemplates | prefix、suffix 也使用模板对象的 Few-shot 模板 | 不展开代码 |
抽象基础类
| 类 | 作用 |
|---|---|
| BasePromptTemplate | 所有提示词模板的基础抽象 |
| BaseChatPromptTemplate | 聊天提示词模板基础抽象 |
| StringPromptTemplate | 返回字符串的模板基础类 |
初学时不需要直接实例化抽象类,也不需要为了“全部学完”给每个类写一段代码。先掌握本文八个核心类,就已经覆盖提示词模板的主线用法。
12. 常见错误与使用边界
12.1 变量名和传参名不一致
模板使用 {question},调用时却传 input,会产生缺少变量错误。示例字典的 key 也必须和 example_prompt 中的变量一致。
12.2 把普通字符串传给 MessagesPlaceholder
MessagesPlaceholder 需要消息列表,例如:
{"history": [HumanMessage("你好"), AIMessage("你好,有什么可以帮你?")]}
它不是普通字符串占位符。
12.3 在已实例化的消息对象中写变量
下面的 {name} 只是普通文字,不会被替换:
HumanMessage(content="你好,我是{name}")
需要使用:
HumanMessagePromptTemplate.from_template("你好,我是{name}")
12.4 把模板渲染成功当成模型回答成功
prompt.invoke() 只负责生成 PromptValue,不会自动调用模型。必须继续执行:
chain = prompt | llm
response = chain.invoke(values)
本文分别保留渲染脚本和真实模型脚本,就是为了区分这两个阶段。
12.5 示例越多不代表效果一定越好
Few-shot 示例也会占用上下文 Token。示例应尽量清楚、与目标任务相关,并保持输入输出格式一致。大量无关或互相矛盾的示例反而会干扰模型。
12.6 ICL 不是训练和长期记忆
ICL 只在当前请求上下文中生效。它不会修改模型权重,也不会让模型永久记住符号规则。
12.7 不要过早建立复杂模板库
当项目中的模板数量持续增加时,可以建立专门的模板模块集中管理。但本文只有八个教学脚本,直接写在对应文件中更容易理解,因此不创建公共模板类或 common.py。
13. 小结
提示词模板的核心不是少写几个字符串,而是把固定结构、动态变量、消息历史和 Few-shot 示例组织成可复用对象。
PromptTemplate 适合普通字符串,ChatPromptTemplate 适合现代聊天模型;MessagesPlaceholder 插入完整消息列表,partial() 固定部分变量;两类 Few-shot 模板分别处理字符串示例和聊天消息示例。
本文不仅打印了模板结构,还真实验证了模板组合和聊天 ICL:本地 Qwen3 成功生成相声报幕词,并根据鹦鹉符号示例返回 17。
14. 最终知识整理
| 知识点 | 需要掌握的结论 |
|---|---|
| PromptTemplate | 使用变量生成字符串提示词 |
| format() | 返回普通字符串 |
| PromptTemplate.invoke() | 返回 StringPromptValue |
| ChatPromptTemplate | 使用角色和变量生成消息列表 |
| ChatPromptTemplate.invoke() | 返回 ChatPromptValue |
| format_messages() | 返回最终消息对象列表 |
| 变量占位符 | 替换消息或字符串中的某个值 |
| 消息占位符 | 插入一条或多条完整消息 |
| placeholder 简写 | 默认可选,缺少时使用空列表 |
| 显式 MessagesPlaceholder | 默认必填,可设置 optional=True |
| partial() | 预先固定部分变量,调用时只传剩余变量 |
| 模板组合 | + 组合片段, |
| ICL | 在当前上下文中提供示例,不修改模型参数 |
| FewShotPromptTemplate | 把多个字符串示例和最终问题拼成提示词 |
| FewShotChatMessagePromptTemplate | 把示例组织为 human/ai 消息对 |
| 本地模型实测 | Qwen3 成功生成报幕词,并根据 ICL 示例回答 17 |