💬 消息体系

LangChain 系统学习的第一站。消息(Message)是整个框架最底层的"原子"——所有对话、所有 Agent 循环,本质上都是消息的流动。掌握五种消息类型,就掌握了与 LLM 交流的基本语法。

本章目标

  • 理解 BaseMessage 抽象基类及其核心字段
  • 掌握五种消息类型(Human / AI / System / Tool / Function)的用途与区别
  • 看懂 AIMessagetool_calls 字段——工具调用的载体
  • 能用消息构造一次完整的工具调用往返
📚 依赖链提示

LangChain 八章按依赖顺序:消息 → 提示 → 模型 → 解析 → LCEL → 工具 → Schema → Agent消息是地基,没有它后面什么都搭不起来。

为什么需要"消息"抽象

回顾 F2:LLM 的输入是带 role 的消息序列。但不同厂商 API 的格式五花八门——OpenAI 用 {"role": "user", "content": "..."},Anthropic 用 {"type": "text", "text": "..."},还有的多模态要塞图片……

LangChain 用一套统一的 Python 类屏蔽这些差异。无论底层接哪家模型,你写的代码都是 HumanMessage("你好")。这就是 langchain_core.messages 模块的存在意义。

你的代码 HumanMessage(...) langchain_core .messages 统一的 消息抽象 屏蔽厂商差异 OpenAI 格式 Anthropic 格式 Gemini / 其他格式
图 LC1.1 · messages 模块是厂商差异的"适配层"

BaseMessage:所有消息的根

所有消息类型都继承自 BaseMessage。先看它的核心字段:

📄 langchain_core/messages/base.py:93 · BaseMessage 基类 python
class BaseMessage(Serializable):
    """所有消息的抽象基类。消息是 chat model 的输入和输出。"""

    content: str | list[str | dict[Any, Any]]
    """消息内容。可以是纯字符串,也可以是多模态内容列表(文本/图片/工具结果)"""

    additional_kwargs: dict[Any, Any] = Field(default_factory=dict)
    """附加载荷。例如模型厂商特有的原始 tool_calls 编码"""

    response_metadata: dict[Any, Any] = Field(default_factory=dict)
    """响应元数据。例如 token 计数、模型名、响应头"""

    type: str
    """消息类型标识,用于反序列化时识别(如 "human"/"ai"/"system")"""

    name: str | None = None
    """可选的可读名称"""

    id: str | None = Field(default=None, coerce_numbers_to_str=True)
    """消息唯一 ID,用于追踪和消息去重"""
💡 重点字段
  • content:可以是 str(纯文本)或 list(多模态,如图片+文本)。多模态场景下用 list。
  • type:子类会固定这个值(如 AIMessagetype="ai"),是判断消息角色的关键。

五种消息类型

围绕 BaseMessage,LangChain 定义了五种具体消息,对应 F2 章讲的角色:

type角色源码位置典型用途
HumanMessage human 用户 messages/human.py 用户的提问/指令
AIMessage ai 模型 messages/ai.py:160 模型回复,带 tool_calls
SystemMessage system 系统 messages/system.py 人设/规则/工具说明
ToolMessage tool 工具 messages/tool.py 工具执行结果回传
FunctionMessage function (已废弃) messages/function.py 旧版 function 调用,已被 ToolMessage 取代

AIMessage:工具调用的载体

AIMessage 是 Agent 场景下最重要的消息类型,因为它承载了工具调用指令。仔细看它的三个特有字段:

📄 langchain_core/messages/ai.py:160 · AIMessage 的三个关键字段 python
class AIMessage(BaseMessage):
    """AI 返回的消息。代表模型的输出。"""

    tool_calls: list[ToolCall] = Field(default_factory=list)
    """工具调用列表 —— Agent 的"行动指令"就在这里。每个 ToolCall 含 name/args/id"""

    invalid_tool_calls: list[InvalidToolCall] = Field(default_factory=list)
    """解析失败的工具调用(如模型输出了格式错误的 JSON)"""

    usage_metadata: UsageMetadata | None = None
    """token 用量元数据(输入/输出/缓存 token 数),用于计费和监控"""

    type: Literal["ai"] = "ai"
    """类型固定为 "ai",用于反序列化识别"""
⚠️ tool_calls 是 Agent 的核心

判断一条 AIMessage 是"要继续行动"还是"已结束",就看 tool_calls 是否为空:

  • tool_calls == [] → 模型给出最终文本回答 → 循环结束(对应 F3 章的 Finish)。
  • tool_calls 非空 → 模型要调工具 → 循环继续(对应 F3 章的 Action)。

这正是 LangGraph 的 tools_conditionLG8)和 OpenCode runLoop(OC2)的判断逻辑。

ToolMessage:结果的回执

当应用层执行完一个工具调用,需要用 ToolMessage 把结果喂回给模型。它必须携带 tool_call_id,用来和当初的 tool_call 对应(一个 AIMessage 可能带多个 tool_calls,模型要能区分哪个结果对应哪次调用)。

AIMessage tool_calls: [{ id: "call_abc" 执行工具 拿到结果 result ToolMessage tool_call_id: "call_abc"(对应回去)
图 LC1.2 · tool_call_id 是连接 AIMessage 和 ToolMessage 的"回执单号"

可运行代码

🚀 准备环境

pip install langchain langchain-openai。下面的示例不调真实模型(用伪数据),让你专注理解消息结构。后面 LC3 章会接真实模型。

📄 lc1_messages.py · 五种消息类型实操 python
# pip install langchain
from langchain_core.messages import (
    HumanMessage, AIMessage, SystemMessage, ToolMessage,
)

# ① SystemMessage —— 设定人设/规则
system = SystemMessage(content="你是一个天气助手,可以使用工具查天气。")

# ② HumanMessage —— 用户提问
user = HumanMessage(content="北京今天天气怎么样?")

# ③ AIMessage —— 模型回复,【带 tool_calls】表示要调工具
ai = AIMessage(
    content="",  # 可能没有文字内容
    tool_calls=[{
        "name": "search_weather",      # 工具名
        "args": {"location": "北京"},   # 参数
        "id": "call_abc123",           # 这次调用的唯一 ID
    }],
)

# ④ 应用层执行工具(这里假装执行)
result = '{"temp": 25, "weather": "晴"}'

# ⑤ ToolMessage —— 把结果喂回去,必须带 tool_call_id
tool_msg = ToolMessage(
    content=result,
    tool_call_id="call_abc123",   # 和上面 ai.tool_calls[0]["id"] 对应!
)

# ⑥ AIMessage —— 模型基于工具结果,给出最终回答(无 tool_calls = 循环结束)
final = AIMessage(content="北京今天 25 度,晴天。")

# 完整的一次 Agent 往返消息序列:
messages = [system, user, ai, tool_msg, final]
for m in messages:
    print(f"[{m.type:8}] {m.content[:40] if m.content else '(空)'}"
          + (f"  → tool_calls={m.tool_calls}" if hasattr(m, 'tool_calls') and m.tool_calls else ""))

# 输出:
# [system  ] 你是一个天气助手,可以使用工具查天气。
# [human   ] 北京今天天气怎么样?
# [ai      ] (空)  → tool_calls=[{'name': 'search_weather', ...}]
# [tool    ] {"temp": 25, "weather": "晴"}
# [ai      ] 北京今天 25 度,晴天。

多模态 content(进阶)

当需要发送图片时,content 用 list 形式,每项带 type

📄 多模态消息示例 python
from langchain_core.messages import HumanMessage

# content 是 list,混合文本和图片
msg = HumanMessage(content=[
    {"type": "text", "text": "这张图里是什么?"},
    {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}},
])
# 大多数主流 chat model 的 .invoke([msg]) 都能处理

与生产实践对照

LangChain 概念OpenCode 对应
消息类型 (Human/AI/...)MessageV2 的 Part 体系(TextPart/ToolPart/ReasoningPart)
AIMessage.tool_callsToolPart(流式聚合而成)
ToolMessageToolPart 的 output 字段
消息序列流转Session 数据库持久化的消息列表

OpenCode 把"一条消息"拆得更细——一条 AIMessage 在它内部是一个 Message,内含多个 Part(文本 Part、工具 Part、推理 Part),更适合流式处理。详见 OC2

小结

下一章

下一章 LC2 · 提示词模板:消息是"原子",但手写一长串消息很繁琐。我们学习 ChatPromptTemplate,用模板批量、动态地生成消息序列。