一、认识消息
大模型没有记忆,它的输出只和输入模型的内容有关(上下文)。很多大模型 API 服务也没有在服务端维护会话历史,是"无状态"的。因此,如果应用需要"记住"对话历史,就必须在程序中维护消息列表。
在 LangChain 中,Message(消息)是模型交互的最基本单元,既代表模型接收到的输入(Input),也代表模型生成的输出(Output)。
每一轮与大模型的对话,都由一条或多条 Message 构成;
每个 Message 不仅包含文字内容,还携带描述上下文状态的元信息(metadata),用于保持对话的一致性和可追踪性(比如理解"谁在说话"、"说了什么"、"属于哪一轮对话")。
LangChain 在 1.0 中提供了跨模型统一的 Message 标准。无论你使用 OpenAI、Anthropic、Gemini 还是本地模型,这一标准都能保持一致的行为,带来三大好处:
兼容性强:不同模型的消息格式自动对齐;
可扩展性高:方便添加多模态内容或自定义字段;
可追踪性好:为 LangSmith 等调试工具提供一致的上下文数据结构。
二、消息的内部结构
LangChain 的消息(Message)对象包含三种字段:
三、消息的类型
LangChain 定义了很多消息类型,通过 role 区分。常用的有四种:
1. 系统消息(System)
也称为系统提示词,用于在对话开始时为模型设定角色、行为准则和上下文背景。它像是一份"工作说明书",决定了 AI 回答的风格、领域和专业范围。
{"role": "system", "content": "你是个精通编程的软件架构师"} 2. 用户消息(User)
在多轮对话中表示用户的一次输入。可以包含简单的文本问题,也可以是复杂的多模态内容(图片、音频、文档等)。
{"role": "user", "content": "你好啊~"}3. 助手(AI)消息(Assistant)
代表模型的回复,包括生成的文本、工具调用、元数据等。
{"role": "assistant", "content": "我也很高兴认识你"}带工具调用的 AI 消息:
{
"role": "assistant",
"content": "",
"tool_calls": [{
"name": "get_weather",
"args": {"location": "北京"},
"id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"
}]
}4. 工具调用消息(Tool)
工具调用结果匹配的消息类型。将此消息返回给模型,让模型基于结果继续生成回复。
{"role": "tool", "content": "今天天气很好", "tool_call_id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"}为什么使用不同的消息类型?
明确角色:清晰区分系统提示、用户输入和 AI 回复;
控制行为:通过 SystemMessage 精确控制 AI 的行为;
对话历史:构建完整的多轮对话上下文;
调试友好:更容易追踪和调试对话流程。
四、消息格式
LangChain 支持两种消息格式:JSON 格式和对象格式。
格式 1:JSON 格式
{"role": "system", "content": "你是个善解人意的助手"}
{"role": "user", "content": "你好啊~"}
{"role": "assistant", "content": "我也很高兴认识你"}
{"role": "tool", "content": "<工具输出>", "tool_call_id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"}格式 2:对象格式
from langchain_core.messages import (
HumanMessage, # 用户消息
AIMessage, # AI 消息
SystemMessage, # 系统消息
ToolMessage # 工具返回消息
)
SystemMessage(content="你是个善解人意的助手")
HumanMessage(content="你好啊~")
AIMessage("我也很高兴认识你")
ToolMessage(
content="<工具输出>",
tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s" # 一定要和 AI 消息中的调用 ID 匹配
)消息角色对照小结
五、消息对象字段说明
SystemMessage / HumanMessage 参数
content:消息内容,字段名可省略。
HumanMessage("你好啊~") # 等价于
HumanMessage(content="你好啊~")HumanMessage 还支持 name 和 id 元数据字段(用于多人对话区分不同发言者,但并非所有模型都支持):
HumanMessage(
content="Hello!",
name="alice", # 可选,用户名
id="msg_123", # 可选,message 的 ID
)OpenAI 的 API 支持
name字段(多人对话场景很有用),而 DeepSeek 官方文档虽声明支持,实测却无法识别。使用前需查阅各模型供应商的手册。
AIMessage 参数
tool_calls 结构示例:
tool_calls=[
{
'name': 'get_weather', # 应调用的工具名
'args': {'city': '杭州'}, # 调用工具的参数
'id': 'call_xxx', # 工具调用的唯一标识 ID
'type': 'tool_call'
}
]ToolMessage 参数
ToolMessage(
content="<工具输出>",
name="get_weather",
tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s" # 一定要和 AI 消息中的调用 ID 匹配
)六、实战:对话历史管理
关键规则:每次调用必须传递完整的对话历史
# 第 1 轮:[system, user] → AI 回复 → 保存回复
# 第 2 轮:[system, user, assistant, user] → AI 回复 → 保存回复
# 第 3 轮:[system, user, assistant, user, assistant, user] → AI 回复注意:每次对话都要在原有的消息列表中添加新消息,不可重新创建新列表。
三类常见错误 ❌
# 错误 1:没传历史
response1 = model.invoke("我叫张三")
response2 = model.invoke("我叫什么?") # AI 不记得!
# 错误 2:重新创建列表
conversation = [{"role": "user", "content": "问题1"}]
response1 = model.invoke(conversation)
conversation = [{"role": "user", "content": "问题2"}] # 丢失了历史
response2 = model.invoke(conversation)
# 错误 3:忘记保存 AI 回复
conversation.append({"role": "user", "content": "问题1"})
response1 = model.invoke(conversation) # 忘记保存 response1.content!
conversation.append({"role": "user", "content": "问题2"})
response2 = model.invoke(conversation) # AI 不知道之前的回答正确做法 ✅
conversation = []
# 第一次
conversation.append({"role": "user", "content": "我叫张三"})
response1 = model.invoke(conversation)
# 关键:保存 AI 回复
conversation.append({"role": "assistant", "content": response1.content})
# 第二次(传递完整历史)
conversation.append({"role": "user", "content": "我叫什么?"})
response2 = model.invoke(conversation) # AI 记得!七、实战:对话历史优化
问题:对话历史会越来越长,消耗大量 tokens 和成本。
方案:只保留最近 N 轮对话——始终保留 system 消息,只保留最近 N 轮对话,丢弃更早的历史。
def keep_recent_messages(messages, max_pairs=3):
"""保留最近的 N 轮对话(每轮 = user + assistant)"""
system_msgs = [m for m in messages if m.get("role") == "system"]
conversation_msgs = [m for m in messages if m.get("role") != "system"]
recent_msgs = conversation_msgs[-(max_pairs * 2):]
return system_msgs + recent_msgs八、拓展:消息属性 content 与 content_blocks
content
消息的 content 是弱类型字段,支持字符串和字典列表两种形式。
纯文本:直接传字符串即可;
多模态内容(图片、音频等):使用字典列表形式,内容遵循模型供应商的 API 规范。
# 纯文本
HumanMessage(content="你好啊")
# 多模态(图片 + 文本)
HumanMessage(
content=[
{'type': 'text', 'text': '这张图里有什么?'},
{'type': 'image_url', 'image_url': base64_image},
]
)content_blocks(LangChain 1.x 重大升级)
content_blocks 提供一种跨模型供应商、标准化的多模态数据结构,终结了不同厂商 API 格式各异导致的适配混乱。
数据结构:
list[TypedDict],每个 block 都有一个type字段区分内容类型;支持类型:
text(文本)、image(图片)、audio(音频)、video(视频)、tool_call(工具调用)、reasoning(推理/思维链);懒加载:调用时才会解析。
在 LangChain 1.2 中,content 属性仍保留(向前兼容),但推荐使用 content_blocks 构建复杂的 HumanMessage 或 AIMessage:
HumanMessage(
content_blocks=[
{'type': 'text', 'text': '这张图里有什么?'},
{'type': 'image', 'base64': base64_image},
]
)借助 content_blocks,我们能用一套标准代码无缝地在不同厂商模型之间切换。特别提示:优先检查 response.content_blocks 而非 response.content,尤其是需要获取"思维链(reasoning)"或"引用(citations)"信息时。
评论区