你告诉模型,「我叫小明」。

模型礼貌地回复,「你好,小明」。

下一次调用时,你问它「我叫什么」,它却回答不知道。

很多第一次写聊天程序的人会怀疑模型能力。其实模型没有突然变笨,它只是没有收到上一轮对话。多数聊天模型 API 是无状态的,每次调用只根据本次提交的内容生成结果。

LangChain 的 Message,就是组织这些上下文的基础数据结构。它不仅保存文本,还保存角色、工具调用、用量和服务商元数据。理解 Message 之后,多轮对话、工具调用、多模态输入和 Prompt 模板才会连成一套完整系统。

Message 不是聊天气泡

界面上的一条聊天气泡只展示文本。模型真正需要的消息通常包含三层信息。

Message

Role
谁说的

Content
说了什么

Metadata
ID、用量、工具、响应信息

Role 告诉模型这条内容来自系统、用户、助手还是工具。Content 可以是文本,也可以包含图片、音频、文件或其他内容块。Metadata 则承担运行信息和协议字段。

如果把所有内容都拼成一个长字符串,角色边界、工具结果和多轮结构就会丢失。这也是现代聊天模型更推荐消息列表的原因。

四种最常用的消息

消息类型 角色 主要职责
SystemMessage system 设定助手行为、任务边界和全局上下文
HumanMessage user/human 表示用户本轮输入
AIMessage assistant/ai 表示模型回复、工具调用和响应元数据
ToolMessage tool 把工具执行结果返回给模型
Tool Assistant User System Tool Assistant User System 设定行为和边界 提出问题 请求调用工具 返回工具结果 生成最终回答

系统消息不是管理员权限。它只是模型上下文中优先级较高的一类指令,仍然需要应用层权限、输入校验和工具边界配合。

先跑通一个最小多轮对话

下面的程序从安装、环境变量到模型调用都是完整的。它使用 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)

AIMessage

content
文本或原生内容

content_blocks
标准内容块

tool_calls
工具调用请求

usage_metadata
token 用量

response_metadata
模型名、结束原因等

usage_metadata 通常包含输入、输出和总 token,但是否完整取决于服务商。response_metadata 更具有供应商差异,读取时应使用安全访问,不要让业务逻辑依赖某一家独有字段。

tool_calls 表示模型希望执行工具,不代表工具已经执行。应用必须校验参数、权限和副作用,然后才能真正调用外部系统。

ToolMessage 为什么必须匹配 tool_call_id

工具调用是一段有严格顺序的协议。

HumanMessage

AIMessage
tool_calls

应用执行工具

ToolMessage
tool_call_id 匹配

AIMessage
最终回答

下面用手工消息说明数据关系。

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 才不会建立在一串脆弱字符串上。

延伸阅读

更多推荐