📝 提示词模板
手写一长串消息既繁琐又难复用。提示词模板(Prompt Template)让你声明式地定义消息结构,用变量动态填充——这是构建可维护 LLM 应用的基础工程实践。
本章目标
- 理解
BasePromptTemplate的"模板 + 变量"思想 - 熟练使用
ChatPromptTemplate.from_messages()构建消息序列 - 掌握
MessagesPlaceholder注入历史消息(多轮对话的关键) - 认识到 Prompt 本身是一个 Runnable(为 LC5 LCEL 铺垫)
为什么需要模板
看一段不用模板的代码,感受痛点:
# 每次调用都要手写一遍,变量靠 f-string 拼接,容易出错
def ask(name, question, history):
msgs = [SystemMessage(content=f"你是 {name} 的助手")]
msgs += history # 历史消息
msgs.append(HumanMessage(content=question))
return model.invoke(msgs)
问题很明显:消息结构散落在代码里、变量类型不明确、无法复用、无法序列化。模板解决这一切:把"消息骨架"和"变量填充"分离。
ChatPromptTemplate.from_messages
这是日常最常用的构造方式。它接受一个"消息表示序列",每条可以是元组 (role, content),也可以是现成的 Message 对象:
class ChatPromptTemplate(BaseChatPromptTemplate):
@classmethod
def from_messages(cls, messages, template_format="f-string"):
"""从多种消息格式创建聊天提示模板。
每条消息可以是: 元组 (role, content)、BaseMessage、或模板对象。
"""
...
# 用法示例
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个翻译助手,把中文翻译成{target_lang}。"),
("human", "请翻译这句话:{text}"),
])
# 填充变量,得到消息列表
messages = prompt.invoke({"target_lang": "英文", "text": "你好世界"})
print(messages)
# → ChatPromptValue(messages=[
# SystemMessage(content="你是一个翻译助手,把中文翻译成英文。"),
# HumanMessage(content="请翻译这句话:你好世界"),
# ])
元组的第一个元素(role)支持简写:"system" / "human" / "ai" / "user" / "assistant" 等,会自动映射到对应 Message 类。这是最省事的写法。
MessagesPlaceholder:注入历史
多轮对话场景下,你需要把"历史消息列表"作为一个整体插进模板。直接用 {history} 变量不行——因为历史是消息对象的列表,不是字符串。这时要用 MessagesPlaceholder:
class MessagesPlaceholder(BaseMessagePromptTemplate):
"""一个占位符,假设变量本身已经是消息列表。"""
# 经典用法:system + 历史 + 当前问题
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有帮助的助手。"),
MessagesPlaceholder("history"), # ← 历史消息整个插在这里
("human", "{question}"),
])
# 调用时传入历史
messages = prompt.invoke({
"history": [
HumanMessage(content="我叫小明"),
AIMessage(content="你好小明!"),
],
"question": "我叫什么名字?",
})
# 得到: [SystemMessage, HumanMessage("我叫小明"), AIMessage("你好小明!"), HumanMessage("我叫什么名字?")]
回顾 F3 章的 Agent 循环:每一步都要把历史思考 + 行动 + 观察拼成输入。这些"历史"就是一串消息。MessagesPlaceholder("agent_scratchpad") 正是用来插入这部分动态历史的标准做法——LC8 章 create_tool_calling_agent 就会用到它。
其他模板类型(了解)
| 类 | 源码 | 用途 |
|---|---|---|
PromptTemplate | prompts/prompt.py:24 | 纯字符串模板(传统 LLM 补全用),不是聊天场景 |
FewShotPromptTemplate | prompts/few_shot.py | few-shot 示例模板,自动插入若干示例 |
HumanMessagePromptTemplate | prompts/chat.py:668 | 单条 Human 消息的模板,from_messages 内部会构造它 |
现代应用 95% 的场景用 ChatPromptTemplate 就够了,其他类型按需了解。
可运行代码
模板是纯本地逻辑,pip install langchain 即可跑通,不消耗 token。
# pip install langchain
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
# ① 基础模板:用元组简写
prompt = ChatPromptTemplate.from_messages([
("system", "你是{role},用{style}的语气回答。"),
("human", "{question}"),
])
result = prompt.invoke({"role": "诗人", "style": "浪漫", "question": "什么是爱?"})
for m in result.to_messages():
print(f"[{m.type}] {m.content}")
# [system] 你是诗人,用浪漫的语气回答。
# [human] 什么是爱?
# ② 多轮对话模板:MessagesPlaceholder 注入历史
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是有帮助的助手,会记住用户告诉你的信息。"),
MessagesPlaceholder("history"),
("human", "{question}"),
])
# 模拟两轮对话
history = []
q1 = "我叫张三,我喜欢Python"
# 假设模型回答了,加入历史
history.append(HumanMessage(content=q1))
history.append(AIMessage(content="你好张三!记住了你喜欢Python。"))
q2 = "我叫什么?我喜欢什么?"
messages = chat_prompt.invoke({"history": history, "question": q2})
print("\n第二轮的完整输入消息:")
for m in messages.to_messages():
print(f" [{m.type}] {m.content}")
# ③ MessagesPlaceholder 的 optional 参数
optional_prompt = MessagesPlaceholder("history", optional=True)
# 当 history 未提供时,返回空列表而不报错(适合首轮对话)
print(optional_prompt.format_messages()) # → []
重要伏笔:Prompt 本身是 Runnable
注意上面我们用的是 prompt.invoke(...)——没错,模板也是一个 Runnable(继承自 RunnableSerializable)。这意味着它可以和模型、解析器用 | 管道串联。这是 LC5 章的核心,先留个印象:
# 模板 | 模型 | 解析器 —— 这就是 LCEL 的精髓,LC5 详解
chain = prompt | model | StrOutputParser()
result = chain.invoke({"question": "你好"})
# prompt 把字典 → 消息;model 把消息 → AIMessage;parser 取出文本
与生产实践对照
| LangChain 概念 | OpenCode 对应 | |
|---|---|---|
| SystemMessage / 系统提示 | → | session/system.ts 动态生成(按模型选 anthropic.txt/gpt.txt) |
| ChatPromptTemplate 模板 | → | 字符串拼接 + 环境变量注入(工作目录/git/平台) |
| MessagesPlaceholder | → | 从 Session 数据库读取历史消息列表注入 |
| few-shot 示例 | → | 工具的 .txt 描述文件(read.txt/edit.txt 等) |
小结
ChatPromptTemplate.from_messages()是构造消息序列的标准方式,元组(role, content)最省事。MessagesPlaceholder用于注入消息列表(对话历史、Agent 草稿区),是多轮对话和 Agent 的关键。- 模板本身是 Runnable,可以参与管道组合(LC5 详解)。
下一章 LC3 · 语言模型抽象:消息和提示都准备好了,该让真正的 LLM "接收消息、吐出回答"了。我们看 BaseChatModel 如何统一所有模型,以及 bind_tools 怎么把工具绑到模型上。