LangChain 系列 5:提示词模板从基础到 ICL


上一篇介绍了 LangChain 中的消息。我们已经知道,聊天模型接收的通常不是一段孤立文字,而是由 SystemMessage、HumanMessage、AIMessage 等对象组成的消息列表。

接下来会遇到另一个问题:消息结构可以固定,但其中的角色、问题、语言、历史消息和示例会不断变化。如果每次调用都重新拼接字符串和消息列表,代码很快就会变得重复并且难以维护。

提示词模板就是用来解决这个问题的。它把固定结构保留下来,把变化内容声明为变量,运行时再填入具体值。

本文从最简单的 PromptTemplate 开始,逐步学习 ChatPromptTemplate、变量占位符、消息占位符、partial()、Few-shot 和 ICL,最后把模板真实交给本地 Qwen3 验证结果。

先从整体上区分字符串模板和聊天模板的输入、输出:

PromptTemplate 与 ChatPromptTemplate 的输入输出

PromptTemplate 最终组织一段字符串;ChatPromptTemplate 会保留消息角色、顺序和历史结构。两者都可以继续连接聊天模型,但模板渲染完成并不等于模型已经生成回答。

1. 为什么不只使用 Python 字符串

1.1 字符串拼接可以使用,但容易失控

最简单的提示词可以用 f-string:

language = "中文"
question = "什么是提示词模板?"
prompt = f"请用{language}解释:{question}"

只有一两个变量时,这样写没有问题。但当提示词中包含角色、输出格式、历史消息和多个示例时,会逐渐出现这些问题:

  1. 固定文字和变量混在一起,可读性下降。
  2. 相同结构分散在多个文件中,修改容易遗漏。
  3. 变量名写错时,很难快速发现。
  4. system、human、ai 等角色需要手动组织。
  5. 历史消息和 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,可以理解为“在当前上下文中学习”。

它的基本做法是:

  1. 在提示词中放入少量输入和答案示例。
  2. 再放入一个新的问题。
  3. 让模型根据示例中的格式或规律回答新问题。

ICL 不是训练,也不是微调。模型权重没有变化,示例只存在于当前输入中。下一次调用如果不再提供这些示例,模型也不会自动记住它们。

Few-shot 模板只是把示例按照指定格式渲染,再和说明、当前问题一起放入本次请求:

Few-shot 与 ICL 的提示词组装流程

字符串 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={})]

最终消息顺序是:

  1. 系统规则。
  2. 第一组 human/ai 示例。
  3. 第二组 human/ai 示例。
  4. 用户真正的问题。

这里仍然只是渲染模板,还没有证明模型真的按照示例得到了 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

这次结果可以确认三件事:

  1. Few-shot 示例被正确插入消息列表。
  2. 完整聊天模板能够通过管道交给本地 Qwen3。
  3. 模型根据示例把鹦鹉符号理解为加法,得到 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

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录