💬 消息体系
LangChain 系统学习的第一站。消息(Message)是整个框架最底层的"原子"——所有对话、所有 Agent 循环,本质上都是消息的流动。掌握五种消息类型,就掌握了与 LLM 交流的基本语法。
本章目标
- 理解
BaseMessage抽象基类及其核心字段 - 掌握五种消息类型(Human / AI / System / Tool / Function)的用途与区别
- 看懂
AIMessage的tool_calls字段——工具调用的载体 - 能用消息构造一次完整的工具调用往返
LangChain 八章按依赖顺序:消息 → 提示 → 模型 → 解析 → LCEL → 工具 → Schema → Agent。消息是地基,没有它后面什么都搭不起来。
为什么需要"消息"抽象
回顾 F2:LLM 的输入是带 role 的消息序列。但不同厂商 API 的格式五花八门——OpenAI 用 {"role": "user", "content": "..."},Anthropic 用 {"type": "text", "text": "..."},还有的多模态要塞图片……
LangChain 用一套统一的 Python 类屏蔽这些差异。无论底层接哪家模型,你写的代码都是 HumanMessage("你好")。这就是 langchain_core.messages 模块的存在意义。
BaseMessage:所有消息的根
所有消息类型都继承自 BaseMessage。先看它的核心字段:
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:子类会固定这个值(如AIMessage的type="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 场景下最重要的消息类型,因为它承载了工具调用指令。仔细看它的三个特有字段:
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",用于反序列化识别"""
判断一条 AIMessage 是"要继续行动"还是"已结束",就看 tool_calls 是否为空:
tool_calls == []→ 模型给出最终文本回答 → 循环结束(对应 F3 章的 Finish)。tool_calls非空 → 模型要调工具 → 循环继续(对应 F3 章的 Action)。
这正是 LangGraph 的 tools_condition(LG8)和 OpenCode runLoop(OC2)的判断逻辑。
ToolMessage:结果的回执
当应用层执行完一个工具调用,需要用 ToolMessage 把结果喂回给模型。它必须携带 tool_call_id,用来和当初的 tool_call 对应(一个 AIMessage 可能带多个 tool_calls,模型要能区分哪个结果对应哪次调用)。
可运行代码
pip install langchain langchain-openai。下面的示例不调真实模型(用伪数据),让你专注理解消息结构。后面 LC3 章会接真实模型。
# 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:
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_calls | → | ToolPart(流式聚合而成) |
| ToolMessage | → | ToolPart 的 output 字段 |
| 消息序列流转 | → | Session 数据库持久化的消息列表 |
OpenCode 把"一条消息"拆得更细——一条 AIMessage 在它内部是一个 Message,内含多个 Part(文本 Part、工具 Part、推理 Part),更适合流式处理。详见 OC2。
小结
BaseMessage是所有消息的根,核心字段content/type/additional_kwargs。- 五种类型:Human(用户)、AI(模型,带 tool_calls)、System(系统)、Tool(工具结果,带 tool_call_id)。
AIMessage.tool_calls是否为空 = Agent 循环"继续 vs 结束"的判断依据。
下一章 LC2 · 提示词模板:消息是"原子",但手写一长串消息很繁琐。我们学习 ChatPromptTemplate,用模板批量、动态地生成消息序列。