大模型为什么会失忆, 彻底理解 LangChain Message
你告诉模型,「我叫小明」。
模型礼貌地回复,「你好,小明」。
下一次调用时,你问它「我叫什么」,它却回答不知道。
很多第一次写聊天程序的人会怀疑模型能力。其实模型没有突然变笨,它只是没有收到上一轮对话。多数聊天模型 API 是无状态的,每次调用只根据本次提交的内容生成结果。
LangChain 的 Message,就是组织这些上下文的基础数据结构。它不仅保存文本,还保存角色、工具调用、用量和服务商元数据。理解 Message 之后,多轮对话、工具调用、多模态输入和 Prompt 模板才会连成一套完整系统。
Message 不是聊天气泡
界面上的一条聊天气泡只展示文本。模型真正需要的消息通常包含三层信息。
Role 告诉模型这条内容来自系统、用户、助手还是工具。Content 可以是文本,也可以包含图片、音频、文件或其他内容块。Metadata 则承担运行信息和协议字段。
如果把所有内容都拼成一个长字符串,角色边界、工具结果和多轮结构就会丢失。这也是现代聊天模型更推荐消息列表的原因。
四种最常用的消息
| 消息类型 | 角色 | 主要职责 |
|---|---|---|
SystemMessage |
system | 设定助手行为、任务边界和全局上下文 |
HumanMessage |
user/human | 表示用户本轮输入 |
AIMessage |
assistant/ai | 表示模型回复、工具调用和响应元数据 |
ToolMessage |
tool | 把工具执行结果返回给模型 |
系统消息不是管理员权限。它只是模型上下文中优先级较高的一类指令,仍然需要应用层权限、输入校验和工具边界配合。
先跑通一个最小多轮对话
下面的程序从安装、环境变量到模型调用都是完整的。它使用 Python 3.10+ 和 OpenAI 集成,其他供应商只需替换对应集成包与模型初始化方式。
python -m pip install -U langchain langchain-openai python-dotenv
将密钥和模型 ID 放入 .env。模型 ID 必须是当前账号可以调用的值。
OPENAI_API_KEY=replace_with_your_openai_key
OPENAI_MODEL=gpt-5.4-mini
import os
from dotenv import load_dotenv
from langchain.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
load_dotenv()
if not os.getenv("OPENAI_API_KEY"):
raise RuntimeError("请设置 OPENAI_API_KEY")
model = ChatOpenAI(
model=os.getenv("OPENAI_MODEL", "gpt-5.4-mini"),
temperature=0,
)
history = [
SystemMessage(content="你是简洁的个人助理。"),
HumanMessage(content="我叫小明。"),
]
first_response = model.invoke(history)
history.append(first_response)
history.append(HumanMessage(content="我叫什么?"))
second_response = model.invoke(history)
print(second_response.content)
第二次调用能提到小明,是因为 history 中包含第一轮的用户消息和模型原始回复。这里直接追加 first_response,而不是重新构造 AIMessage(first_response.content),可以保留模型返回的 ID、用量和工具调用等信息。
后文为了突出 Message 的结构,涉及 model 的片段都复用这里创建的实例。单独复制某个片段时,需要一并带上这段安装、环境变量和初始化代码。
字典格式与对象格式
LangChain 接受常见字典格式。
messages = [
{"role": "system", "content": "你是严谨的技术助理。"},
{"role": "user", "content": "解释什么是向量检索。"},
]
response = model.invoke(messages)
print(response.content)
也可以使用消息对象。
from langchain.messages import HumanMessage, SystemMessage
messages = [
SystemMessage("你是严谨的技术助理。"),
HumanMessage("解释什么是向量检索。"),
]
response = model.invoke(messages)
print(response.content)
字典适合 JSON、接口传输和与其他 SDK 对接。消息对象提供类型、属性和 IDE 补全,更适合在 LangChain 内部继续处理。
两种形式不是两套协议。LangChain 会把可接受的 MessageLike 输入转换为标准消息对象。
SystemMessage 应该放什么
系统消息适合放相对稳定的行为边界,例如角色、回答语言、拒答范围和输出原则。
from langchain.messages import SystemMessage
system_message = SystemMessage(
"你是订单支持助手。只能根据订单系统返回的数据回答,"
"无法确认时应明确说明,不得猜测物流状态。"
)
不要把用户输入直接拼进系统消息。用户内容属于不可信输入,一旦进入高优先级指令区域,Prompt 注入风险会更难控制。
系统消息也不适合承担数据库权限、金额阈值等确定性规则。模型能否调用退款工具,应由程序授权,不应只靠一句「未经允许不要退款」。
HumanMessage 不只是文本
纯文本可以直接写入 HumanMessage。
from langchain.messages import HumanMessage
message = HumanMessage("请总结这份会议纪要。")
在多模态场景中,HumanMessage 还可以携带标准内容块。文本、图片和文件都属于同一条用户消息的内容。关于 content_blocks 的结构,可以在多模态文章中进一步展开。
name 字段可以辅助区分多人场景中的发言者,但不同模型供应商对该字段的传递和支持并不完全一致。关键身份或权限信息不能只依赖模型是否理解 name。
AIMessage 远不止 content
模型调用通常返回 AIMessage。许多示例只读取 response.content,却忽略了调试和计费需要的其他信息。
response = model.invoke("用一句话解释机器学习。")
print(response.content)
print(response.usage_metadata)
print(response.response_metadata)
print(response.tool_calls)
usage_metadata 通常包含输入、输出和总 token,但是否完整取决于服务商。response_metadata 更具有供应商差异,读取时应使用安全访问,不要让业务逻辑依赖某一家独有字段。
tool_calls 表示模型希望执行工具,不代表工具已经执行。应用必须校验参数、权限和副作用,然后才能真正调用外部系统。
ToolMessage 为什么必须匹配 tool_call_id
工具调用是一段有严格顺序的协议。
下面用手工消息说明数据关系。
from langchain.messages import AIMessage, HumanMessage, ToolMessage
tool_call_id = "call_weather_001"
ai_message = AIMessage(
content="",
tool_calls=[
{
"name": "get_weather",
"args": {"city": "北京"},
"id": tool_call_id,
"type": "tool_call",
}
],
)
tool_message = ToolMessage(
content="北京晴,最高温度 28 摄氏度。",
tool_call_id=tool_call_id,
name="get_weather",
)
messages = [
HumanMessage("北京天气如何?"),
ai_message,
tool_message,
]
ToolMessage 必须紧跟发起它的 AIMessage,并通过相同 ID 建立对应关系。若并行调用两个工具,更不能只依赖列表顺序猜测哪个结果属于哪个请求。
删除或裁剪消息历史时,也不能留下孤立的工具结果,或者保留 AIMessage.tool_calls 却删掉对应的 ToolMessage。许多模型供应商会直接拒绝这种无效消息序列。
消息列表怎样形成多轮上下文
每一次模型调用,都需要提交它完成当前任务所需的消息。
from langchain.messages import HumanMessage, SystemMessage
conversation = [
SystemMessage("你是简洁的个人助理。"),
HumanMessage("我叫小明。"),
]
first_response = model.invoke(conversation)
conversation.append(first_response)
conversation.append(HumanMessage("我叫什么?"))
second_response = model.invoke(conversation)
print(second_response.content)
模型第二次能够回答,是因为应用把第一轮消息再次发送了过去,不是因为模型服务自动记住了小明。
真实系统还需要考虑会话隔离、上下文长度、历史摘要和持久化。Message 提供数据结构,应用负责状态所有权。
消息设计的六条规则
- 系统规则、用户输入、模型回复和工具结果保持角色分离。
- 不把用户输入提升为系统消息。
- 不依赖服务商私有元数据作为跨模型业务契约。
- 工具调用必须保留 ID 与结果配对。
- 每个用户、租户和会话使用独立历史。
- 裁剪历史时保证消息序列仍满足模型供应商要求。
Message 看起来只是一个小对象,却承载了 LLM 应用最核心的数据关系。角色混乱,模型就不知道谁在说话。工具 ID 丢失,模型就不知道结果属于哪次调用。历史所有权不清,用户之间甚至可能串话。
把消息协议理解清楚,后面的多轮对话、多模态和 Agent 才不会建立在一串脆弱字符串上。
延伸阅读
更多推荐
所有评论(0)