LangChain之消息体系深度解析
消息(Message)是 LangChain 构建大模型交互、对话管理、工具调用与多模态能力的最小数据单元。BaseMessage作为所有消息类型的抽象基类,定义了标准化结构、序列化规范与扩展机制,直接决定对话流程的稳定性、可观测性与跨模型兼容性。本文从底层设计、属性体系、消息家族、序列化与反序列化、工具调用场景、工程最佳实践六个维度,对 LangChain Message 机制进行系统性解析,为复杂 LLM 应用开发提供理论与实践支撑。
1 Message 在 LangChain 架构中的核心地位
LangChain 的核心能力围绕大模型交互展开:
- 对话历史管理
- 系统提示与用户指令分离
- 多轮工具调用(函数调用)
- 多模态输入输出
- 日志、追踪、重试与异常处理
这些能力均依赖统一、可扩展的消息结构。BaseMessage承担三个关键使命:
- 统一抽象:屏蔽不同模型(OpenAI、Anthropic、通义千问等)的消息格式差异。
- 可序列化:支持对话持久化、分布式传输与断点恢复。
- 可扩展:支持元数据、扩展字段、内容块、工具调用等复杂场景。
脱离 Message 体系,LangChain 的 Chain、Agent、Memory、Callback 均无法稳定工作。
2 BaseMessage 底层设计与核心契约
BaseMessage位于langchain_core.messages,是抽象基类(ABC),基于 Pydantic 实现强类型校验。
2.1 设计原则
- 最小必需原则:只定义所有消息必须具备的属性与方法。
- 不可变优先:内容与结构尽量不可变,避免副作用。
- 双向可序列化:支持对象 ↔ 字典双向无损转换。
- 向前兼容:新增字段不破坏历史消息解析。
2.2 核心抽象接口
所有子类必须实现:
content:消息主体type:唯一类型标识(用于反序列化)additional_kwargs:扩展载荷
提供通用能力:
to_dict()/from_dict():序列化pretty_print():格式化输出copy():浅拷贝- 支持
text、content_blocks等派生属性
3 Message 完整属性体系(官方标准定义)
LangChain Core 最新版定义的标准属性如下:
| 属性 | 类型 | 含义与用途 |
|---|---|---|
| content | str | list[str | dict] | 消息内容:文本/多模态/结构化块 |
| type | str | 唯一消息类型标识:human/ai/system/tool/function |
| additional_kwargs | dict | 模型原生扩展字段:tool_calls、function_call 等 |
| response_metadata | dict | 响应元数据:token、模型名、请求ID、日志概率 |
| name | str | None | 角色名/函数名/发送者标识 |
| id | str | None | 消息唯一ID(通常由模型返回) |
| model_config | ConfigDict | Pydantic 配置(内部使用) |
| content_blocks | list[ContentBlock] | 标准化内容块(自动解析) |
| text | TextAccessor | 纯文本提取器,兼容 str/list 格式 |
3.1 关键属性边界
content ≠ text
content可包含图片、文件、工具结构、富文本。text只提取纯文本,用于日志、检索、摘要。
additional_kwargs vs response_metadata
additional_kwargs:输入侧扩展,发给模型。response_metadata:输出侧元数据,来自模型返回。
type 是反序列化的唯一依据
从字典恢复消息对象时,只依赖 type,不依赖类名或路径。
4 LangChain 标准消息家族与适用场景
| 消息类 | type | 典型场景 |
|---|---|---|
| HumanMessage | human | 用户输入/提问 |
| AIMessage | ai | 模型回复/工具调用决策 |
| SystemMessage | system | 系统提示、角色定义 |
| ToolMessage | tool | 工具执行结果(官方推荐) |
| FunctionMessage | function | 兼容旧版函数调用 |
| ChatMessage | chat | 自定义角色消息 |
4.1 工具调用场景的消息流(最复杂且最关键)
标准 Agent 执行流程:
HumanMessage:用户需求AIMessage:content=None,additional_kwargs["tool_calls"]存在ToolMessage:工具执行结果(成功/失败/返回数据)AIMessage:模型总结最终答案
这也是你实际遇到[AI]: None的根本原因:
工具调用模式下,AI 回复的文本内容为空,决策存在于 tool_calls 中。
5 序列化与反序列化机制(生产环境必备)
5.1 标准序列化
msg_dict=msg.to_dict()输出结构固定包含:
role/typecontentadditional_kwargsresponse_metadataname
5.2 反序列化(关键:只认 type)
fromlangchain_core.messagesimportmessages_from_dict msgs=messages_from_dict(msg_list)底层根据type自动映射到对应类,保证跨版本、跨服务兼容。
5.3 空 content 场景的序列化安全
content=""、content=None、content=[]均合法。- 反序列化不会崩溃,但业务逻辑必须判断空内容。
6 工程实践:空内容消息与工具调用的正确处理
6.1 核心判断模式
# 是否为纯工具调用消息is_tool_call=(msg.type=="ai"andnotmsg.content.strip()and"tool_calls"inmsg.additional_kwargs)# 是否有有效文本has_valid_text=msg.textandmsg.text.strip()6.2 稳定打印方法(适配生产日志)
defformat_msg(msg:BaseMessage)->dict:return{"type":msg.type,"text":msg.textor"","has_tool_calls":"tool_calls"inmsg.additional_kwargs,"tool_count":len(msg.additional_kwargs.get("tool_calls",[])),"name":msg.name,"token_usage":msg.response_metadata.get("token_usage",{})}6.3 对话历史清洗规则
- 连续空 AI 消息需合并或过滤。
- ToolMessage 必须与 AIMessage 一一对应。
- 超长 content 截断日志,避免存储爆炸。
7 最佳实践与避坑指南
永远不要直接判断 isinstance(msg, AIMessage)
反序列化后可能是动态类,应使用msg.type == "ai"。不要修改 content 与 additional_kwargs 原值
使用model_copy、copy生成新对象。日志只记录 text,不记录原始 content
避免多模态结构污染日志。工具调用优先使用 ToolMessage,不使用 FunctionMessage
FunctionMessage 已逐步被弃用。所有对话历史必须可序列化
禁止放入无法序列化的对象(如文件句柄、数据库连接)。
8 总结
BaseMessage是 LangChain 架构中最基础、最稳定、最通用的抽象。
- 它统一了大模型交互的数据格式;
- 支撑对话、工具、多模态、记忆、追踪全链路;
- 空 content 是工具调用的正常形态,而非异常;
- 工程化必须依赖
type、text、tool_calls进行稳健判断。
深入理解 Message 体系,是开发高可靠、可观测、可维护LLM 应用的前提。
import asyncio import logging import pprint from app.agent import create_agent logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s" ) logger = logging.getLogger(__name__) def print_result(result): if "messages" in result: for msg in result["messages"]: msg_type = msg.type print(f"\n[{msg_type.upper()}]:\n{msg.pretty_print()}\n") else: pprint.pprint(result) async def main(): agent = create_agent() logger.info("Writing memory to Redis...") result = await agent.ainvoke( { "messages": [ { "role": "user", "content": "Write 'hello redis memory' to /memories/test.txt" } ] } ) logger.info("Write file result:") print_result(result) logger.info("Reading memory...") result = await agent.ainvoke( { "messages": [ { "role": "user", "content": "Read the file /memories/test.txt" } ] } ) logger.info("Read file result:") print_result(result) if __name__ == "__main__": asyncio.run(main()) # 2026-03-12 13:24:59,797 [INFO] Writing memory to Redis... # 2026-03-12 13:25:03,826 [INFO] HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" # 2026-03-12 13:25:10,038 [INFO] HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" # 2026-03-12 13:25:13,003 [INFO] HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" # 2026-03-12 13:25:17,014 [INFO] HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" # 2026-03-12 13:25:20,814 [INFO] HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" # 2026-03-12 13:25:20,823 [INFO] Write file result: # ================================ Human Message ================================= # Write 'hello redis memory' to /memories/test.txt # [HUMAN]: # None # ================================== Ai Message ================================== # Tool Calls: # write_file (call_6ZbYGfEqkShaEQfXGokDfBpA) # Call ID: call_6ZbYGfEqkShaEQfXGokDfBpA # Args: # file_path: /memories/test.txt # content: hello redis memory # [AI]: # None # ================================= Tool Message ================================= # Name: write_file # Cannot write to /memories/test.txt because it already exists. Read and then make an edit, or write to a new path. # [TOOL]: # None # ================================== Ai Message ================================== # Tool Calls: # edit_file (call_rwMCTlI6JkWoObRelbd5QBJG) # Call ID: call_rwMCTlI6JkWoObRelbd5QBJG # Args: # file_path: /memories/test.txt # old_string: # new_string: hello redis memory # [AI]: # None # ================================= Tool Message ================================= # Name: edit_file # Error: String '' appears 19 times in file. Use replace_all=True to replace all instances, or provide a more specific string with surrounding context. # [TOOL]: # None # ================================== Ai Message ================================== # Tool Calls: # read_file (call_aw5SJ4rzbczVOSkqoMbP3r9U) # Call ID: call_aw5SJ4rzbczVOSkqoMbP3r9U # Args: # file_path: /memories/test.txt # [AI]: # None # ================================= Tool Message ================================= # Name: read_file # 1 hello redis memory # [TOOL]: # None # ================================== Ai Message ================================== # Tool Calls: # edit_file (call_UVDaDo2e8cRdnfpflTevBL7s) # Call ID: call_UVDaDo2e8cRdnfpflTevBL7s # Args: # file_path: /memories/test.txt # old_string: hello redis memory # new_string: hello redis memory # replace_all: True # [AI]: # None # ================================= Tool Message ================================= # Name: edit_file # Successfully replaced 1 instance(s) of the string in '/memories/test.txt' # [TOOL]: # None # ================================== Ai Message ================================== # The content "hello redis memory" has been successfully written to `/memories/test.txt`. # [AI]: # None # 2026-03-12 13:25:20,826 [INFO] Reading memory... # 2026-03-12 13:25:25,682 [INFO] HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" # 2026-03-12 13:25:31,458 [INFO] HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK" # 2026-03-12 13:25:31,466 [INFO] Read file result: # ================================ Human Message ================================= # Read the file /memories/test.txt # [HUMAN]: # None # ================================== Ai Message ================================== # Tool Calls: # read_file (call_eiNxY5q08sPhoLuwP3gRwf38) # Call ID: call_eiNxY5q08sPhoLuwP3gRwf38 # Args: # file_path: /memories/test.txt # [AI]: # None # ================================= Tool Message ================================= # Name: read_file # 1 hello redis memory # [TOOL]: # None # ================================== Ai Message ================================== # The file `/memories/test.txt` contains the text: "hello redis memory". # [AI]: # None