精读 LangChain 官方文档(七)Messages 篇:把对话上下文讲清楚

精读 LangChain 官方文档(七)Messages 篇:把对话上下文讲清楚
本文基于 LangChain Python 官方文档整理:
Messages:https://docs.langchain.com/oss/python/langchain/messages
Markdown 版本:https://docs.langchain.com/oss/python/langchain/messages.md
对应开源文档源码:
https://github.com/langchain-ai/docs/blob/main/src/oss/langchain/messages.mdx
刚学 LangChain 时,会把模型调用理解成“给模型一段字符串,模型返回一段字符串”。这个理解能跑通第一个 demo,但很快就会在真实业务里卡住:系统提示词放哪?多轮历史怎么保存?模型想调用工具时怎么表达?工具执行结果怎么回传?图片、PDF、音频这些多模态内容又怎么进入上下文?
LangChain 的 Messages 页面讲的就是这件事。
这篇文档的核心主线可以概括成一句话:
Message = role + content + metadata。消息不是普通文本,而是 LangChain 统一模型上下文、工具调用、多模态输入、流式输出和可观测数据的最小工程单元。
如果说上一篇 Agents 篇讲的是“智能体怎么组织行动”,Models 篇讲的是“推理引擎怎么接入”,那 Messages 篇讲的就是:模型到底是靠什么看懂一段对话的。
1. Messages(消息)到底解决什么问题
它解决的问题:
Messages 把一次模型调用里的上下文,从一段普通字符串升级成一组可解释、可追踪、可组合的消息对象。
官方文档开头强调,消息是 LangChain 中模型上下文的基本单位。每条消息通常包含三类信息:
role:消息角色,例如系统、用户、助手、工具。content:消息内容,可以是文本、图片、音频、文件,也可以是内容块列表。metadata:元数据,例如消息 ID、响应信息、token 用量。
示例:
import os
from langchain_openai import ChatOpenAI
from langchain.messages import SystemMessage, HumanMessage
model = ChatOpenAI(
model="qwen3.7-max",
api_key=os.environ["QWEN_API_KEY"],
base_url=os.environ["QWEN_BASE_URL"],
)
messages = [
SystemMessage("你是一名耐心的烘焙客服助手,只回答和订单、商品、售后相关的问题。"),
HumanMessage("我昨天买的蛋糕什么时候能发货?"),
]
response = model.invoke(messages)
print(response.text())
这里:
ChatOpenAI:LangChain 的 OpenAI-compatible 聊天模型封装,这里用来接入qwen3.7-max。QWEN_API_KEY:环境变量名,保存模型 API 密钥,不能把真实密钥写进代码。QWEN_BASE_URL:环境变量名,保存 OpenAI-compatible 服务地址。messages:消息列表,按顺序描述模型本次调用能看到的上下文。SystemMessage:系统消息,用来设置模型行为边界。HumanMessage:用户消息,用来表示真实用户输入。response:模型返回的AIMessage,不是普通字符串。
业务场景:
在智能客服系统里,你不希望每次都把“你是客服助手”拼进用户问题,也不希望把工具调用结果伪装成用户说的话。消息对象让不同来源的信息各归其位。
最简记法:
字符串只能表达“说了什么”,Message 还能表达“谁说的、带着什么结构、后续怎么追踪”。

2. Text Prompt、Message Prompt、Dictionary Format 怎么选
它解决的问题:
LangChain 支持多种输入形式,让简单任务保持简单,让复杂对话保留结构。
官方文档把基础用法分成三类:
- Text prompts:直接传字符串。
- Message prompts:传消息对象列表。
- Dictionary format:传 OpenAI chat completions 风格的字典列表。
示例:
# 方式一:直接字符串,适合一次性任务
summary = model.invoke("请用三句话解释 LangChain Messages 的作用")
# 方式二:消息对象,适合多轮对话和系统约束
reply = model.invoke([
SystemMessage("你是一名技术讲解助手,回答要简洁。"),
HumanMessage("Messages 和普通字符串有什么区别?"),
])
# 方式三:字典格式,适合和 OpenAI 风格协议对齐
reply_from_dict = model.invoke([
{"role": "system", "content": "你是一名技术讲解助手,回答要简洁。"},
{"role": "user", "content": "Messages 和普通字符串有什么区别?"},
])
这里:
Text prompts:文本提示词,适合不需要历史、不需要角色区分的单次生成。Message prompts:消息提示词,适合系统提示词、多轮历史、多模态内容和工具调用。Dictionary format:字典格式,role和content字段与 OpenAI chat completions 协议接近。role:消息角色字段,告诉模型这条消息属于系统、用户、助手还是工具。content:消息正文,可以是字符串,也可以是结构化内容列表。
业务场景:
如果只是“帮我写一句促销文案”,字符串就够了。如果是“用户问订单状态,系统先查库,再结合售后规则回答”,就应该使用消息对象或字典格式。
最简记法:
一次性任务用字符串,多轮和工程化任务用消息列表。

3. SystemMessage(系统消息):给模型划定行为边界
它解决的问题:SystemMessage 用来告诉模型应该扮演什么角色、遵守什么规则、采用什么回答风格。
系统消息通常放在消息列表最前面。它不代表用户的问题,而代表应用开发者给模型设置的行为协议。
示例:
from langchain.messages import SystemMessage, HumanMessage
messages = [
SystemMessage(
"你是一名资深 Python 后端工程师。"
"回答时先给结论,再给最小可运行示例,最后解释关键参数。"
),
HumanMessage("如何用 FastAPI 写一个健康检查接口?"),
]
response = model.invoke(messages)
print(response.text())
这里:
SystemMessage:系统级指令,不是用户消息,主要控制模型行为。HumanMessage:用户输入,表示用户真正想问的问题。资深 Python 后端工程师:角色设定,用来影响模型回答视角。先给结论、最小可运行示例、解释关键参数:回答格式约束。
业务场景:
企业内部知识库助手可以用系统消息固定边界:只回答公司制度,不编造链接,不回答员工隐私,不执行高风险操作。
最简记法:SystemMessage 是应用给模型的“岗位说明书”。
4. HumanMessage(用户消息):承载真实输入
它解决的问题:HumanMessage 表示用户输入,它可以是普通文本,也可以承载图片、音频、文件等多模态内容。
官方文档还提到,用户消息可以携带 name 和 id 这样的元数据。
示例:
from langchain.messages import HumanMessage
human_msg = HumanMessage(
content="请帮我判断这条用户反馈属于物流问题还是商品质量问题:蛋糕到货时已经变形。",
name="customer_service_user",
id="msg_20260614_001",
)
response = model.invoke([human_msg])
这里:
content:用户消息正文。name:可选发送者名称,不同模型供应商对它的支持不完全一致。id:消息唯一标识,常用于日志追踪、问题复现和链路排查。human_msg:变量名,表示一条用户消息对象。
业务场景:
客服系统可以把每次用户输入都保存成 HumanMessage,再用 id 对齐数据库里的会话记录。出现误答时,就能从日志里找到完整上下文。
最简记法:HumanMessage 不是“用户说的一句话”,而是“用户输入这件事”的结构化记录。
5. AIMessage(模型消息):模型输出也不是普通字符串
它解决的问题:AIMessage 表示模型输出,它不仅包含文本,还可能包含工具调用、内容块、token 用量和响应元数据。
很多初学者会习惯把模型返回值当字符串处理。但在 LangChain 里,模型返回的通常是 AIMessage,它常见属性包括:
text:模型输出的文本内容。content:原始内容,可能是字符串,也可能是内容块列表。content_blocks:LangChain 标准化后的内容块列表。tool_calls:模型请求调用的工具。id:消息唯一标识。usage_metadata:token 用量。response_metadata:模型供应商返回的响应信息。
示例:
response = model.invoke("请用一句话解释 AIMessage 的作用")
print(type(response))
print(response.text())
print(response.usage_metadata)
print(response.response_metadata)
这里:
response.text():读取模型文本输出的便捷方法。usage_metadata:记录输入 token、输出 token、总 token 等用量信息,适合做成本统计。response_metadata:记录供应商侧响应信息,例如模型名、结束原因、请求标识等。type(response):用于确认返回值是消息对象,而不是普通字符串。
业务场景:
企业 AI 平台需要统计每个用户、每条业务线、每个模型的调用成本。只保存最终文本不够,还要保存 usage_metadata 这样的用量字段。
最简记法:AIMessage 是模型输出的“完整回执”,文本只是其中一部分。


6. tool_calls(工具调用):模型想做事时怎么表达
它解决的问题:tool_calls 用结构化方式表示“模型想调用哪个工具、传什么参数、这次调用编号是什么”。
当模型绑定工具后,模型可以不直接回答,而是在 AIMessage.tool_calls 里提出工具调用请求。程序再执行工具,把结果返回给模型。
示例:
from langchain_openai import ChatOpenAI
# 查询订单状态,真实项目中可以替换为数据库查询或物流系统 API 调用。
def query_order_status(order_id: str) -> str:
return f"订单 {order_id} 已出库,预计明天送达。"
model_with_tools = model.bind_tools([query_order_status])
response = model_with_tools.invoke("请帮我查一下订单 DD20260614001 的物流状态")
for tool_call in response.tool_calls:
print(tool_call["name"])
print(tool_call["args"])
print(tool_call["id"])
这里:
bind_tools:把可调用工具绑定到模型,让模型知道有哪些外部能力可以使用。query_order_status:函数名,表示查询订单物流状态的工具。order_id:函数参数名,表示订单编号。tool_calls:模型生成的工具调用列表。name:工具名称,通常对应函数名。args:工具参数,通常是一个字典。id:本次工具调用的唯一编号,后续ToolMessage必须用它对齐。
业务场景:
用户问“我的订单在哪”,模型不应该编造答案,而应该生成工具调用,让后端系统去查询真实订单数据。
最简记法:tool_calls 是模型递给程序的“我要调用工具”申请单。

7. ToolMessage(工具消息):把工具结果交还给模型
它解决的问题:ToolMessage 用来把工具执行结果放回对话上下文,并且和前面的工具调用精确对齐。
工具调用不是“模型说一句,程序随便回一句”。它必须通过 tool_call_id 对应到前一个 AIMessage.tool_calls 里的 id。
示例:
from langchain.messages import AIMessage, HumanMessage, ToolMessage
ai_message = AIMessage(
content=[],
tool_calls=[
{
"name": "query_order_status",
"args": {"order_id": "DD20260614001"},
"id": "call_order_001",
}
],
)
tool_message = ToolMessage(
content="订单 DD20260614001 已出库,预计明天送达。",
tool_call_id="call_order_001",
name="query_order_status",
)
messages = [
HumanMessage("我的订单 DD20260614001 到哪里了?"),
ai_message,
tool_message,
]
final_response = model.invoke(messages)
这里:
ToolMessage:工具结果消息,表示程序已经执行了工具并拿到结果。tool_call_id:工具调用编号,必须匹配前面AIMessage.tool_calls中的id。name:工具名称,便于追踪是哪个工具返回了结果。content:会交给模型看的工具结果文本。
业务场景:
订单助手、CRM 助手、数据分析助手都需要这个闭环:模型提出查询,后端执行查询,工具结果回到模型,模型再组织成用户能看懂的回答。
最简记法:ToolMessage 是工具执行后的“回执单”,必须贴上原工具调用编号。
8. artifact(附加产物):有些数据要给程序看,不必给模型看
它解决的问题:artifact 用来存放程序后续需要、但不适合塞进模型上下文的附加数据。
官方文档给出的典型场景是检索工具。模型只需要看到检索片段,但应用可能还需要文档 ID、页码、相似度分数,用来渲染引用来源或做调试。
示例:
from langchain.messages import ToolMessage
tool_message = ToolMessage(
content="《配送规则》第 3 条说明:冷链商品发货后通常 24 小时内送达。",
tool_call_id="call_search_policy_001",
name="search_policy",
artifact={
"document_id": "policy_delivery_2026",
"page": 3,
"score": 0.92,
},
)
这里:
artifact:附加产物字段,不一定发送给模型,但程序可以读取。document_id:文档编号,用来定位原始资料。page:页码,用来做引用跳转。score:检索相似度分数,用来辅助排序或调试。search_policy:工具名称,表示检索公司规则文档。
业务场景:
知识库问答需要在答案下方展示“引用自哪篇文档第几页”。这些引用信息没必要全部塞给模型,但前端展示时必须能拿到。
最简记法:content 给模型看,artifact 给程序用。
9. content 与 content_blocks:原始内容和标准内容块
它解决的问题:content 保留消息原始内容,content_blocks 提供跨供应商更一致、更类型安全的访问方式。
官方文档指出,消息的 content 可以是三种形态:
- 字符串。
- 供应商原生内容块列表。
- LangChain 标准内容块列表。
为了让不同供应商的结构更好统一,LangChain v1 引入了 content_blocks 属性。它不是要替代 content,而是提供标准化视图。
示例:
from langchain.messages import HumanMessage
message = HumanMessage(
content_blocks=[
{"type": "text", "text": "请识别这张商品图里的包装是否破损。"},
{"type": "image", "url": "https://example.com/order-package.jpg"},
]
)
for block in message.content_blocks:
print(block["type"])
这里:
content:消息原始内容,可能带有供应商格式差异。content_blocks:LangChain 标准内容块列表。type:内容块类型,例如text、image、file、reasoning。url:图片或文件地址。
业务场景:
同一个应用可能先接 OpenAI-compatible 模型,后接 Anthropic 或其他供应商。如果业务代码都依赖供应商原生字段,迁移成本会很高。content_blocks 可以降低这类耦合。
最简记法:content 是原始载荷,content_blocks 是 LangChain 帮你整理后的标准视图。

10. Standard Content Blocks(标准内容块):把复杂内容分门别类
它解决的问题:
标准内容块让文本、推理、多模态、工具调用和供应商特有数据都能被统一描述。
官方文档列了很多内容块类型。对于工程落地,可以先记住这几组:
| 类型 | 中文解释 | 常见用途 |
|---|---|---|
TextContentBlock |
文本内容块 | 普通回答、提示词、摘要 |
ReasoningContentBlock |
推理内容块 | 模型推理摘要或思考过程 |
ImageContentBlock |
图片内容块 | 图片理解、商品识别、截图分析 |
AudioContentBlock |
音频内容块 | 语音输入、录音理解 |
VideoContentBlock |
视频内容块 | 视频片段理解 |
FileContentBlock |
文件内容块 | PDF、文档、附件 |
ToolCall |
工具调用块 | 模型请求调用函数 |
ToolCallChunk |
工具调用流式片段 | 流式生成工具参数 |
InvalidToolCall |
无效工具调用 | 捕获 JSON 解析失败等异常 |
ServerToolCall |
服务端工具调用 | 供应商侧执行的工具请求 |
ServerToolResult |
服务端工具结果 | 供应商侧工具执行结果 |
NonStandardContentBlock |
非标准内容块 | 供应商特有结构兜底 |
示例:
message = {
"role": "user",
"content": [
{"type": "text", "text": "请总结这份售后说明文档的核心规则。"},
{
"type": "file",
"url": "https://example.com/after-sale-policy.pdf",
"mime_type": "application/pdf",
},
],
}
这里:
file:文件内容块类型。mime_type:文件媒体类型,告诉模型或供应商这是 PDF、图片、音频还是其他格式。application/pdf:PDF 文件的媒体类型。NonStandardContentBlock:当供应商提供了 LangChain 标准之外的结构时,用来保留原始数据。
业务场景:
一个售后助手既要看用户文字,也要看用户上传的破损照片,还要分析 PDF 规则文档。标准内容块让这些不同形态的数据可以出现在同一条消息里。
最简记法:
标准内容块就是 LangChain 给消息内容做的“类型目录”。
11. Multimodal(多模态):图片、文件、音频和视频怎么进入消息
它解决的问题:
多模态消息让模型上下文不再只有文本,而可以接收图片、PDF、音频和视频等不同数据。
官方文档示例里,多模态内容可以来自:
url:外部资源地址。base64:base64 编码数据。file_id:供应商托管文件 ID。
示例:
message = {
"role": "user",
"content": [
{"type": "text", "text": "请判断这张蛋糕照片是否存在明显破损。"},
{
"type": "image",
"url": "https://example.com/cake-damaged.jpg",
},
],
}
response = model.invoke([message])
这里:
image:图片内容块类型。url:图片地址。base64:把二进制文件编码成文本后放入消息,适合没有公网 URL 的文件。file_id:供应商托管文件编号,适合先上传文件再引用。mime_type:base64 文件通常需要这个字段说明数据类型。
业务场景:
生鲜、烘焙、服装售后经常需要用户上传图片。多模态消息能让模型直接结合图片和文字判断问题类型,而不是只靠用户描述。
最简记法:
多模态消息把“看图、读文件、听音频”都纳入同一套消息协议。

12. Streaming and Chunks(流式与消息片段):为什么 chunk 能拼回完整消息
它解决的问题:
流式输出时,模型不会一次返回完整 AIMessage,而是持续返回可以累加的 AIMessageChunk。
官方文档里的关键点是:流式 chunk 可以组合成完整消息对象。也就是说,流式不是“随便吐字”,而是仍然沿用消息协议。
示例:
chunks = []
full_message = None
for chunk in model.stream("请用三句话解释 LangChain 的消息机制"):
chunks.append(chunk)
print(chunk.text(), end="")
full_message = chunk if full_message is None else full_message + chunk
print("\n完整消息:")
print(full_message.text())
这里:
stream:流式调用方法,适合前端实时展示。chunk:流式返回的消息片段。AIMessageChunk:AI 消息片段类型,可以累加。full_message:把多个片段合并后的完整消息。
业务场景:
长答案生成、报告撰写、代码解释都适合流式输出。前端可以实时展示 token,后端最终仍然保存完整消息,便于审计和复盘。
最简记法:
流式输出是“分片返回”,不是“脱离消息协议”。
13. usage_metadata 与 response_metadata:消息里的成本和供应商信息
它解决的问题:
元数据让模型调用可以被计费、监控、排查和治理。
官方文档提到,AIMessage 可以通过 usage_metadata 保存 token 用量,通过 response_metadata 保存响应信息。
示例:
response = model.invoke("请给出三条提升客服回复质量的建议")
usage = response.usage_metadata
provider_info = response.response_metadata
print("输入 token:", usage.get("input_tokens"))
print("输出 token:", usage.get("output_tokens"))
print("总 token:", usage.get("total_tokens"))
print("供应商响应信息:", provider_info)
这里:
input_tokens:输入消耗 token 数。output_tokens:输出消耗 token 数。total_tokens:本次调用总 token 数。provider_info:变量名,表示模型供应商返回的响应信息。response_metadata:常用于查看模型名、结束原因、请求 ID 等。
业务场景:
如果一个团队有多个 AI 功能,例如客服、文案、知识库、数据分析,就需要按功能统计成本。usage_metadata 是成本看板的基础字段。
最简记法:
没有元数据,就很难把 AI 能力从 demo 管成平台。
14. Message History(消息历史):从单次调用走向可恢复对话
它解决的问题:
聊天模型通常是无状态的,应用需要自己管理不断增长的消息列表,并在必要时裁剪、总结或持久化。
官方文档最后提醒,Chat model 接收消息序列作为输入,返回 AIMessage。多轮应用会维护一组越来越长的消息历史,并结合记忆管理策略处理上下文窗口。
示例:
from langchain.messages import AIMessage, HumanMessage, SystemMessage
messages = [
SystemMessage("你是一名电商客服助手。"),
HumanMessage("我想买低糖蛋糕,有推荐吗?"),
]
first_reply = model.invoke(messages)
messages.append(first_reply)
messages.append(HumanMessage("如果明天生日宴用,配送时间怎么安排?"))
second_reply = model.invoke(messages)
messages.append(second_reply)
这里:
messages.append(first_reply):把模型回复加入历史,下一轮模型才能看到之前说过什么。Message History:消息历史,表示同一会话里的上下文集合。context window:上下文窗口,表示模型一次调用能容纳的最大上下文长度。trimming:裁剪消息,删除或压缩不重要的历史。summarizing:总结消息,把长历史压缩成更短摘要。
业务场景:
用户先问“推荐低糖蛋糕”,再问“明天生日宴用怎么安排”,第二个问题里的“它”依赖第一轮上下文。没有消息历史,模型就无法理解指代关系。
最简记法:
多轮对话不是模型天生记得,而是应用把消息历史带回去了。

总结:把 Messages 当成工程协议,而不是聊天文本
读完 LangChain 的 Messages 文档,最重要的不是记住每个类名,而是换一个心智模型:
| 层次 | 你看到的表面 | 工程里真正要管理的东西 |
|---|---|---|
| 普通字符串 | 用户说了一句话 | 没有角色、历史和元数据,适合简单任务 |
| Message 对象 | 一条带角色的消息 | 可以区分系统、用户、助手和工具 |
| AIMessage | 模型回答 | 文本、工具调用、token 用量、供应商元数据 |
| ToolMessage | 工具结果 | 和工具调用 ID 对齐的执行回执 |
| content_blocks | 内容块 | 文本、图片、文件、推理、工具调用的标准表示 |
| Message History | 聊天记录 | 可恢复、可裁剪、可总结、可审计的上下文链路 |
所以,Messages 的最简记法是:
字符串负责表达内容,Messages 负责表达上下文协议。
下一篇如果继续沿着 Agent 核心循环往下读,就可以进入 Runtime 篇:看看 LangChain 为什么要站在 LangGraph 之上,以及运行时上下文如何影响 Agent 的执行过程。
更多推荐

所有评论(0)