在这里插入图片描述

精读 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:字典格式,rolecontent 字段与 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 表示用户输入,它可以是普通文本,也可以承载图片、音频、文件等多模态内容。

官方文档还提到,用户消息可以携带 nameid 这样的元数据。

示例:

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 是模型输出的“完整回执”,文本只是其中一部分。

角色分工图

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:内容块类型,例如 textimagefilereasoning
  • 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 的执行过程。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐