Skip to content

LangChain 提示词模板

概要

ChatPromptTemplate 解决一个具体问题:把用户输入和固定约束组装成结构化的消息列表。直接在代码里拼接字符串,提示词散落各处无法统一管理,变量硬编码换场景就要改代码。from_messages() 定义消息骨架——{变量} 是占位符,invoke() 填入具体值,生成 ChatModel 直接消费的 消息列表。提示词从此可以像函数一样被调用、复用、组合。

一、基础使用

模板的核心思想就一个:骨架和内容分离。system 消息定角色和约束,user 消息放每次不同的业务输入,占位符 {变量} 标记变化的部分。

from_messages() 支持三种消息格式(元组、字典、消息对象),与消息系统中介绍的完全一致。

python
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个{role},请用{style}的风格回答问题。"),
    ("user", "{input}"),
])

messages = prompt.invoke({
    "role": "Python 技术顾问",
    "style": "简洁",
    "input": "装饰器的原理是什么?",
})
# messages 是 List[BaseMessage],可直接传给 ChatModel
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的助手。"),
    MessagesPlaceholder(variable_name="history"),
    ("user", "{input}"),
])

# 历史消息从外部传入——数据库、缓存、前端会话皆可
history = [
    ("user", "我叫小明"),
    ("assistant", "你好小明,有什么可以帮你的?"),
]

messages = prompt.invoke({"history": history, "input": "我叫什么名字?"})

两个模板对应两种场景:基础模板所有变量都由 invoke 传入,适合单轮问答;MessagesPlaceholder 在模板中留一个"消息列表插槽",适合多轮对话——历史由外部管理,模板只管结构。

二、初始化参数类型

from_messages() 接受 6 种参数,按是否需要变量替换归为两类。日常只用 tuple,其余按场景查。

python
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个{role}。"),  # 2-元组:(role, content)
    ("user", "{input}"),
])
# ("tool", "content", tool_call_id)  # tool 消息需 3-元组
python
prompt = ChatPromptTemplate.from_messages([
    {"role": "system", "content": "你是一个{role}。"},
    {"role": "user", "content": "{input}"},
])
# 与 OpenAI Chat API 的 messages 格式一致,序列化友好
python
prompt = ChatPromptTemplate.from_messages([
    "你是一个乐于助人的助手。",      # 等价于 ("human", "...")
    ("human", "{input}"),
])
# 纯字符串自动视为 human 消息
python
from langchain_core.messages import SystemMessage

prompt = ChatPromptTemplate.from_messages([
    SystemMessage(content="你是一个助手。"),  # 固定内容,不含 {变量}
    ("user", "{input}"),
])
# BaseMessage 是具体消息实例,不会做变量替换
python
# xxxMessagePromptTemplate
from langchain_core.prompts import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template("你是一个{role}。"),
    HumanMessagePromptTemplate.from_template("{input}"),
])
# 需要显式指定消息类型时使用
python
# 子模板:可独立测试、跨 prompt 复用
role_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个{role}。"),
])

# 父模板:组合子模板 + 业务变量
prompt = ChatPromptTemplate.from_messages([
    role_prompt,
    ("user", "{input}"),
])
# 多个 prompt 共享同一套 system 约束时最有用
类型变量替换使用场景
tuple ("role", "content")日常首选,最简洁
dict {"role": ..., "content": ...}对接 OpenAI API,需序列化存储时
str快速测试,仅 human 消息
MessagePromptTemplate需显式控制消息类型时
BaseChatPromptTemplate模板组合复用
BaseMessage固定不变的 system 指令

表中按推荐度排序——从上到下是日常使用频率。

三、与 ChatModel 配合

模板只生成消息列表,不调用模型。连接模型只需 LCEL 管道 |

python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

model = ChatOpenAI(model="gpt-4o-mini")
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个{role}。"),
    ("user", "{input}"),
])

chain = prompt | model
response = chain.invoke({"role": "代码审查专家", "input": "解释 SOLID 原则"})
# response 是 AIMessage,需通过 .content 拿文本
print(response.content)
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = ChatOpenAI(model="gpt-4o-mini")
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个{role}。"),
    ("user", "{input}"),
])

chain = prompt | model | StrOutputParser()
result = chain.invoke({"role": "代码审查专家", "input": "解释 SOLID 原则"})
# result 直接是字符串,无需 .content
print(result)

两种链的区别只有一点:是否需要 AIMessage 的元数据StrOutputParser 把 AIMessage 提取为纯字符串,适合直接展示、入库、API 返回。需要访问 tool_callsresponse_metadata 或做消息级操作时,保留 AIMessage,不加 parser。

TIP

prompt | model | parser 是 LangChain 中最常见的三件套。掌握了这个模式,后续学 Tools(prompt | model.bind_tools(...) | ...)和 Agent 就是在此基础上的自然扩展。

四、高级特性

模板定义好骨架之后,变量从三条路径进入:invoke() 传参(每次变化的业务数据)、partial() 固化(定义时就确定的全局配置)、MessagesPlaceholder 插入(外部维护的消息列表)。第一条路径在第一章已演示,这里展开后两条。

1. partial — 固化不变变量

语气风格、输出格式这类配置在应用启动时就确定了,每次 invoke 都传一遍既啰嗦又容易漏。partial 把它们固化在模板里:

python
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个{role},请用{style}的风格回答。"),
    ("user", "{input}"),
])

# 固化全局配置,invoke 只需传业务变量
prompt = prompt.partial(style="简洁专业")

messages = prompt.invoke({
    "role": "Python 顾问",
    "input": "装饰器原理?",
})

partial_variables 构造参数与此等价——选哪个只是风格偏好。

变量名冲突

partial 固化的变量不能与 invoke 传入的变量重名——LangChain 优先使用 partial 值,invoke 中的同名变量被静默忽略。从命名上彻底区分:styletoneformat 用 partial;inputtopicquestion 用 invoke。

2. MessagesPlaceholder — 消息列表插槽

第一章用 tuple 列表快速演示了基础用法。生产环境中的对话历史通常以 HumanMessage / AIMessage 对象形式存在——它们携带 content_blocks(多模态内容)、tool_calls(工具调用记录)等元数据,tuple 无法表达这些信息。

python
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的助手。"),
    MessagesPlaceholder(variable_name="history"),
    ("user", "{input}"),
])

history = [
    HumanMessage(content="我叫小明"),
    AIMessage(content="你好小明!"),
]

messages = prompt.invoke({"history": history, "input": "我叫什么名字?"})
python
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个助手。"),
    MessagesPlaceholder(variable_name="history", optional=True),
    ("user", "{input}"),
])

messages = prompt.invoke({"input": "你好"})
# 不传 history 不报错,占位符位置为空
参数类型默认值说明
variable_namestrinvoke 时用此键名传入消息列表
optionalboolFalse为 True 时可不传,占位处为空
n_messagesintNone限制恰好 N 条消息,超出抛 ValueError

TIP

n_messages 用于硬限制历史轮数(如设为 10 保留最近 5 轮对话),防止 token 消耗随对话增长失控。optional=True 适合首次对话没有历史的场景,但注意不传变量不报错,占位处为空——如果后续链逻辑依赖该位置有内容,模型行为可能异常。

小结

提示词模板解决的本质问题是消息结构的复用——骨架和内容分离,同一套 prompt 适配不同输入、不同对话历史、不同全局配置。在 LangChain 工作流中,prompt | model | parser 是最基础的组装单元:Tools 在此之上给模型绑定函数调用能力,Agent 在此基础上加入自主决策循环。返回概念总览可回顾整体架构。

📖 相关资源

CC BY-NC-SA 4.0 协议