02 · 消息类数据字典:模型输入输出的”统一信封”
本片目标:彻底搞懂 LangChain 的”消息”体系——为什么对话不是”字符串进字符串出”,而是”消息对象进、AIMessage 出”。这是全系列最重要的一篇数据字典。
新增规定性:2(对话的原子单位是消息对象,有角色之分、有 9 个字段)
数据字典:BaseMessage / SystemMessage / HumanMessage / AIMessage / ToolCall / UsageMetadata。
进程线程模型:无变化(仍是同步阻塞)。
网络模型:invoke(messages) 把消息对象序列化成各家协议的 JSON 结构。
1. 上集回顾
第 01 篇我们只用了model.invoke("你好")——传字符串。但你有两个问题:
- 多轮对话怎么传?我需要同时告诉模型”你是助手”(system)+ 用户历史问题(user)+ 之前回答(assistant)。一个字符串表达不了这种结构。
- 模型返回的 AIMessage 到底有什么?第 01 篇看到它有
content、response_metadata、usage_metadata、tool_calls…… 为什么一个回答要带这么多字段?
2. 数据字典:消息类体系(关键类的数据)
2.1 类层次
2.2 BaseMessage 六字段(所有消息的公共部分)
序列化后的形态(给模型的 JSON)——这是网络层真正发送的:
2.3 AIMessage:9 字段(模型输出的完整信封)
实测 AIMessage.model_dump() 完整结构(智谱 glm-4.7,第 01 篇运行输出,脱敏):
2.4 UsageMetadata(token 用量,三家协议统一成这三键)
三家的原始 usage 结构完全不同(Anthropic 是input_tokens/output_tokens,OpenAI 是prompt_tokens/completion_tokens),LangChain 统一成一套——这就是”统一信封”在元数据层的体现。
2.5 ToolCall(工具调用结构,第 11 篇深入)
3. 关键方法(本片主角:怎么构造和检查消息)
4. 三协议差异(为什么需要”统一信封”的实证)
这就是第 01 篇说的”换厂商只改 import + 构造参数”的底层原因:LangChain 的消息对象把上表右边的全部差异吸收掉了。你写的消息列表,对三家是同一份 Python 代码。
5. 验证:跑起来
配套代码code/02_messages.py:
- 构造 SystemMessage + HumanMessage,
invoke传给模型; - 打印 AIMessage 的
model_dump()全字段 JSON; - 打印
usage_metadata和model_provider,验证协议统一。
6. 边界
- AIMessage 的
id不是模型返回的 id——它是 LangChain 给 run 生成的 id(lc_run--xxx)。模型原始 id 在response_metadata["id"](msg_xxx)。 tool_calls平时是空列表——只有你让模型”可以调工具”时它才有内容(第 11 篇工具篇用bind_tools激活它)。- 不要把消息对象和普通 dict 混用——
model.invoke()里要么全是消息对象,要么是符合协议的 dict 列表(第 01 篇那种)。混用会报错。 - content 可能是列表——Anthropic 协议在思考/工具场景下 content 是
[{"type":"text","text":...}, ...]。判断答案要遍历找type=="text"的块。
推荐资料(延伸阅读)
- LangChain 官方文档 · 消息 —— 消息类完整数据字典(官方维护)
- langchain-core API 参考 · messages —— 全部消息类的签名与字段
7. 未完待续
我们已经能手工构造消息列表并调用模型。但问题来了:每次都要手写SystemMessage(...) + HumanMessage(...) 列表,还穿插历史消息,代码会变得很啰嗦、很容易写错(比如漏了 system、历史消息位置不对)。有没有一种方式,把”消息怎么拼”变成”模板”?
→ 03 · ChatPromptTemplate:模板与历史槽位