MessagesPlaceholder 是什么?

MessagesPlaceholder 是 LangChain 中专门用于 ChatPromptTemplate 的一个特殊占位符组件。它与普通字符串占位符(如 {input})最大的不同在于:它用来接收并注入一整个“消息列表(List[BaseMessage])”,而不是一个普通的字符串值。

在代码中,它的写法是:

MessagesPlaceholder("变量名")

其中 "变量名" 是运行时传入的字典中对应的键名。

一句话定义MessagesPlaceholder 是 LangChain 在提示模板中预留的“插槽”,用于在运行时把一整套带有角色(System/Human/AI/Tool)的对话消息无损地插入到模板中。


为什么需要它?(普通占位符 vs MessagesPlaceholder)

如果我们只用普通字符串占位符 {chat_history},我们需要手动把历史消息列表拼接成一个长字符串(例如:"用户:你好\nAI:你好,有什么可以帮您?\n用户:...")。这样做有两个致命问题:

问题 说明
丢失角色信息 拼接成的字符串失去了 HumanMessageAIMessage 这类结构化标签,模型无法区分哪句是谁说的(除非你在字符串里强行加“用户:”前缀,但这是纯文本,不是原生结构)。
无法传输工具消息(ToolMessage) RAG 或 Agent 中涉及工具调用时,历史里可能包含 ToolMessage(工具返回结果)。纯文本拼接无法保留其特殊结构。

MessagesPlaceholder 恰恰解决了这两个问题:它直接传递 BaseMessage 对象列表,保持每条消息的完整角色、内容和元数据,让聊天模型(ChatModel)可以原生解析,不用再手动去做字符串拼接。


在代码中如何使用?

1. 定义模板
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的助手。"),
    MessagesPlaceholder("chat_history"),   # ① 预留历史消息插槽
    ("human", "{input}")                   # ② 当前用户输入
])
  • 运行时,chat_history 会替换成一整段历史消息列表。
  • input 仍是一个普通的字符串占位符。
2. 运行时注入
from langchain_core.messages import HumanMessage, AIMessage

# 模拟历史对话
history_msgs = [
    HumanMessage(content="我叫小明"),
    AIMessage(content="你好小明!很高兴认识你。")
]

# 注入数据
final_messages = prompt.invoke({
    "chat_history": history_msgs,
    "input": "你还记得我叫什么吗?"
}).to_messages()

# 此时 final_messages 包含:
# [SystemMessage, HumanMessage("我叫小明"), AIMessage("..."), HumanMessage("你还记得我叫什么吗?")]

最终生成的 final_messages 会保留每一条消息的角色,直接传给模型。


RunnableWithMessageHistory 的关系

MessagesPlaceholder 负责“占位”,RunnableWithMessageHistory 负责“填充”

在实战中,MessagesPlaceholder 最常见的搭配就是 RunnableWithMessageHistory(如你之前写的代码)。它的工作流程是:

  1. RunnableWithMessageHistory 根据 session_idchat_history_store 里取出一整个消息列表。
  2. 在调用 base_chain 时,它自动把这个列表塞进 invoke 字典的 chat_history 键里。
  3. prompt 里的 MessagesPlaceholder("chat_history") 收到这个列表,完成注入。
  4. 模型生成回复。
  5. 框架自动把新的 UserMessageAIMessage 追加回存储中。

没有 MessagesPlaceholderRunnableWithMessageHistory 就无处安放历史消息;没有 RunnableWithMessageHistoryMessagesPlaceholder 就只能手动传参。


面试高频追问与回答

Q1:如果我用普通字符串占位符 {chat_history},然后传一个 JSON 字符串,效果一样吗?
A:不一样。ChatModel(如通义千问、GPT-4)的 API 接收的是结构化的消息数组([{"role": "user", "content": "..."}])。如果传 JSON 字符串,模型会把它当成一段普通的 content,而无法识别其中的角色信息。这会导致系统提示和用户消息界限模糊,严重影响模型的指令遵循能力。

Q2:MessagesPlaceholder 能传 ToolMessage 吗?
A:可以。只要是 BaseMessage 的子类(HumanMessage, AIMessage, SystemMessage, ToolMessage 等),MessagesPlaceholder 都能无损传递。这在 Agent 场景中非常关键,因为工具调用结果必须原样送回模型,模型才能据此做下一步推理。

Q3:MessagesPlaceholderStringPromptTemplate 能混用吗?
A:不能。StringPromptTemplate 生成的是纯文本提示,不区分角色。MessagesPlaceholder 是专为 ChatPromptTemplate 设计的。二者不能混用。

Q4:如何限制 MessagesPlaceholder 里消息的数量?
AMessagesPlaceholder 本身不提供限制功能。你需要在使用 RunnableWithMessageHistory 时,配合 trim_messages 工具,或者在自定义的 ChatMessageHistoryadd_messages 方法里做截断。官方推荐的做法是在传给 MessagesPlaceholder 之前,用 trim_messages 对历史列表进行裁剪。


总结

MessagesPlaceholder 是 LangChain 实现结构化多轮对话的基石。它让历史消息得以“原汁原味”地传入模型,让 Agent 和 RAG 系统能精准理解上下文。在面试中,能清晰区分 MessagesPlaceholder 和普通字符串占位符,并能解释其在 RunnableWithMessageHistory 中的协作关系,是体现你对 LangChain 理解深度的标志性知识点之一。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐