一、认识消息

大模型没有记忆,它的输出只和输入模型的内容有关(上下文)。很多大模型 API 服务也没有在服务端维护会话历史,是"无状态"的。因此,如果应用需要"记住"对话历史,就必须在程序中维护消息列表。

在 LangChain 中,Message(消息)是模型交互的最基本单元,既代表模型接收到的输入(Input),也代表模型生成的输出(Output)。

  • 每一轮与大模型的对话,都由一条或多条 Message 构成;

  • 每个 Message 不仅包含文字内容,还携带描述上下文状态的元信息(metadata),用于保持对话的一致性和可追踪性(比如理解"谁在说话"、"说了什么"、"属于哪一轮对话")。

LangChain 在 1.0 中提供了跨模型统一的 Message 标准。无论你使用 OpenAI、Anthropic、Gemini 还是本地模型,这一标准都能保持一致的行为,带来三大好处:

  • 兼容性强:不同模型的消息格式自动对齐;

  • 可扩展性高:方便添加多模态内容或自定义字段;

  • 可追踪性好:为 LangSmith 等调试工具提供一致的上下文数据结构。

二、消息的内部结构

LangChain 的消息(Message)对象包含三种字段:

字段

说明

Role

消息所属的角色或类型,如 system、user、assistant

Content

消息内容

Metadata

(可选)元数据,存储额外信息,如消息 ID、响应时间、token 消耗量、消息标签等

三、消息的类型

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 匹配
)

消息角色对照小结

角色

字典格式

对象格式

用途

示例

System

{"role": "system", ...}

SystemMessage(...)

设定 AI 的行为、角色、规则

"你是一个专业的数学老师"

User

{"role": "user", ...}

HumanMessage(...)

用户输入

"什么是微积分?"

Assistant

{"role": "assistant", ...}

AIMessage(...)

AI 的回复

"微积分是研究变化率的数学分支..."

Tool

{"role": "tool", ...}

ToolMessage(...)

工具执行的结果

"今天北京天气晴朗"

五、消息对象字段说明

SystemMessage / HumanMessage 参数

content:消息内容,字段名可省略。

HumanMessage("你好啊~")               # 等价于
HumanMessage(content="你好啊~")

HumanMessage 还支持 name 和 id 元数据字段(用于多人对话区分不同发言者,但并非所有模型都支持):

HumanMessage(
    content="Hello!",
    name="alice",    # 可选,用户名
    id="msg_123",    # 可选,message 的 ID
)

OpenAI 的 API 支持 name 字段(多人对话场景很有用),而 DeepSeek 官方文档虽声明支持,实测却无法识别。使用前需查阅各模型供应商的手册。

AIMessage 参数

字段

说明

content

模型输出的原始内容,字段名可省略

response_metadata

LLM 响应中附加的元数据(如 token 使用量,随模型而异)

tool_calls

工具调用信息。有工具调用时包含,否则为空

usage_metadata

用量信息

tool_calls 结构示例:

tool_calls=[
    {
        'name': 'get_weather',     # 应调用的工具名
        'args': {'city': '杭州'},  # 调用工具的参数
        'id': 'call_xxx',          # 工具调用的唯一标识 ID
        'type': 'tool_call'
    }
]

ToolMessage 参数

字段

说明

content

文件/工具输出内容

name

工具名称

tool_call_id

工具调用唯一 ID,必须与匹配的 AIMessage 中 tool_calls 的 id 一致

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)"信息时。