LangChain - 消息(Messages)
一、消息是什么?
▎ 消息是 LangChain 中模型上下文的基本单元。它们代表模型的输入和输出,携带与 LLM 交互时表示对话状态所需的内容和元数据。
翻译成大白话:你和模型聊天时,每一句话、模型每一句回复,在 LangChain 里都是一个"消息对象"。 模型自己没有记忆,所谓"多轮对话",本质上就是把一堆消息排成列表喂给它。
一个消息对象包含三样东西:
- 角色(role):这条消息是谁说的(system / user / assistant / tool)
- 内容(content):实际内容(文本、图像、音频、文档等)
- 元数据(metadata):可选的附加信息(消息 ID、token 用量、响应信息等)
LangChain 提供了适用于所有厂商的标准消息类型,换模型不用改消息代码。
二、怎么把消息传给模型?
页面给了三种方式,从简单到完整。
方式 1:文本提示(直接传字符串)
适合一次性、不需要对话历史的任务:
response = model.invoke("Write a haiku about spring")
方式 2:消息对象列表
适合多轮对话、多模态、含系统指令的场景:
from langchain.messages import SystemMessage, HumanMessage, AIMessage
messages = [
SystemMessage("You are a poetry expert"),
HumanMessage("Write a haiku about spring"),
AIMessage("Cherry blossoms bloom...")
]
response = model.invoke(messages)
方式 3:字典格式(OpenAI 风格)
直接用 role / content 字典:
messages = [
{"role": "system", "content": "You are a poetry expert"},
{"role": "user", "content": "Write a haiku about spring"},
{"role": "assistant", "content": "Cherry blossoms bloom..."}
]
response = model.invoke(messages)
▎ 三种方式结果一样,选哪种看你喜好和场景。最完整的是方式 2(消息对象)。
三、四种消息类型(核心!)
LangChain 有四种标准消息类型,每种"身份"不同:
| 类型 | 谁说的 | 作用 |
|---|---|---|
| SystemMessage | 系统设定 | 告诉模型“你是谁、该怎么表现” |
| HumanMessage | 用户 | 用户的输入 |
| AIMessage | 模型 | 模型的回复(含文本、工具调用、元数据) |
| ToolMessage | 工具 | 工具执行后返回的结果 |
1. SystemMessage —— 设定模型角色
from langchain.messages import SystemMessage, HumanMessage
system_msg = SystemMessage("""
You are a senior Python developer with expertise in web frameworks.
Always provide code examples and explain your reasoning.
Be concise but thorough in your explanations.
""")
messages = [
system_msg,
HumanMessage("How do I create a REST API?")
]
response = model.invoke(messages)
作用:定调子、定角色、定回复准则。
2. HumanMessage —— 用户输入
可以包含文本,也可以包含图像、音频、文件等多模态内容。还能带元数据:
human_msg = HumanMessage(
content="Hello!",
name="alice", # 可选:标识不同用户
id="msg_123", # 可选:用于追踪的唯一 ID
)
▎ 页面提醒:name 字段各厂商处理方式不同,有的用来识别用户,有的直接忽略。
3. AIMessage —— 模型输出
调用模型后返回的就是它:
response = model.invoke("Explain AI")
print(type(response)) # <class 'langchain.messages.AIMessage'>
实用技巧:你可以手动创建 AIMessage 插入对话历史(假装它是模型之前说的),这样能给模型提供上下文:
from langchain.messages import AIMessage, SystemMessage, HumanMessage
ai_msg = AIMessage("I'd be happy to help you with that question!")
messages = [
SystemMessage("You are a helpful assistant"),
HumanMessage("Can you help me?"),
ai_msg, # 当作模型之前的回复插进去
HumanMessage("Great! What's 2+2?")
]
response = model.invoke(messages)
AIMessage 有一堆属性(见下表),其中 tool_calls 和 usage_metadata 最常用:
| 属性 | 含义 |
|---|---|
| text | 消息的文本内容 |
| content | 原始内容(字符串或字典数组) |
| content_blocks | 标准化的内容块(跨厂商统一,见后文) |
| tool_calls | 模型做的工具调用,没有则为空 |
| id | 消息唯一标识符 |
| usage_metadata | token 用量等使用元数据 |
| response_metadata | 响应元数据 |
4. ToolMessage —— 工具执行结果
这是承接上一讲"工具调用"的关键。模型说"我要调 get_weather",你执行完工具后,要把结果用 ToolMessage 包起来传回模型。
from langchain.messages import AIMessage, ToolMessage, HumanMessage
# 模型发起的工具调用(这里手动构造示意)
ai_message = AIMessage(
content=[],
tool_calls=[{
"name": "get_weather",
"args": {"location": "San Francisco"},
"id": "call_123"
}]
)
# 执行工具,把结果包成 ToolMessage
weather_result = "Sunny, 72°F"
tool_message = ToolMessage(
content=weather_result,
tool_call_id="call_123" # 必须和上面的 id 对上号!
)
# 继续对话
messages = [
HumanMessage("What's the weather in San Francisco?"),
ai_message, # 模型的工具调用
tool_message, # 工具执行结果
]
response = model.invoke(messages) # 模型据此生成最终回复
关键点:tool_call_id 必须匹配——这样模型才知道这个结果是哪次工具调用的回复。
ToolMessage 还有个特别实用的字段 artifact:
| 属性 | 说明 |
|---|---|
| content | 必填,发给模型的字符串输出 |
| tool_call_id | 必填,对应工具调用 ID |
| name | 必填,被调用的工具名 |
| artifact | 不发给模型、但程序可访问的附加数据 |
artifact 的妙用:比如检索工具把一段文字放进 content 给模型看,但同时把"文档 ID、页码"放进 artifact 给你的程序用——既不让模型上下文变乱,又能让程序拿到元数据:
tool_message = ToolMessage(
content="It was the best of times, it was the worst of times.",
tool_call_id="call_123",
name="search_books",
artifact={"document_id": "doc_123", "page": 0}, # 程序用,模型看不到
)
四、工具调用(tool_calls)与 Token 用量
工具调用存在 AIMessage 里
model_with_tools = model.bind_tools([get_weather])
response = model_with_tools.invoke("What's the weather in Paris?")
for tool_call in response.tool_calls:
print(f"Tool: {tool_call['name']}")
print(f"Args: {tool_call['args']}")
print(f"ID: {tool_call['id']}")
Token 用量在 usage_metadata 里
response = model.invoke("Hello!")
response.usage_metadata
# {'input_tokens': 8,
# 'output_tokens': 304,
# 'total_tokens': 312,
# 'input_token_details': {'audio': 0, 'cache_read': 0},
# 'output_token_details': {'audio': 0, 'reasoning': 256}}
能拿到输入/输出/总 token 数,甚至音频、缓存、推理 token 的细分——算成本、做监控很有用。
五、流式传输与分块(AIMessageChunk)
流式输出时,你收到的不是 AIMessage,而是一块块 AIMessageChunk,它们可以相加拼成完整消息:
chunks = []
full_message = None
for chunk in model.stream("Hi"):
chunks.append(chunk)
print(chunk.text)
full_message = chunk if full_message is None else full_message + chunk
▎ 跟上一讲"模型"讲的流式一致:每个 chunk 是部分输出,累加后得到完整 AIMessage。
六、消息内容(content)的三种写法
content 属性是"松散类型",可以放三种东西:
from langchain.messages import HumanMessage
# 1. 纯字符串
human_message = HumanMessage("Hello, how are you?")
# 2. 厂商原生格式(如 OpenAI 风格)
human_message = HumanMessage(content=[
{"type": "text", "text": "Hello, how are you?"},
{"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}
])
# 3. LangChain 标准内容块(跨厂商通用)
human_message = HumanMessage(content_blocks=[
{"type": "text", "text": "Hello, how are you?"},
{"type": "image", "url": "https://example.com/image.jpg"},
])
七、标准内容块(content_blocks)—— 跨厂商统一
问题背景:不同厂商对同一种内容的格式不一样(比如 Anthropic 叫 thinking,OpenAI 叫 reasoning)。content_blocks 属性会把这些惰性解析成统一格式:
from langchain.messages import AIMessage
# Anthropic 风格的"思考"块
message = AIMessage(
content=[
{"type": "thinking", "thinking": "...", "signature": "WaUjzkyp..."},
{"type": "text", "text": "..."},
],
response_metadata={"model_provider": "anthropic"}
)
message.content_blocks
# [{'type': 'reasoning', 'reasoning': '...', 'extras': {'signature': 'WaUjzkyp...'}},
# {'type': 'text', 'text': '...'}]
不管原始是 Anthropic 的 thinking 还是 OpenAI 的 reasoning,解析出来都是统一的 {‘type’: ‘reasoning’, …}。
澄清一个重要点:
▎ 内容块不是 content 的替代品,而是一个新属性,用于以标准化格式访问消息内容,同时保持向后兼容。
如果外部应用需要标准格式,可以设环境变量 LC_OUTPUT_VERSION=v1,或初始化模型时传 output_version=“v1”。
八、多模态内容
消息能装的不止文字,还有图像、PDF、音频、视频。以图像为例,有三种来源:
# 从 URL
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this image."},
{"type": "image", "url": "https://example.com/path/to/image.jpg"},
]
}
# 从 base64 数据
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this image."},
{"type": "image", "base64": "AAAAIGZ0eXBtcDQy...", "mime_type": "image/jpeg"},
]
}
# 从厂商托管的 File ID
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this image."},
{"type": "image", "file_id": "file-abc123"},
]
}
▎ 提醒:不是所有模型都支持所有文件类型,要查厂商文档了解支持的格式和大小限制。此外像 OpenAI 和 AWS Bedrock Converse 还需要 PDF 文件名等额外键。
九、内容块类型速查表
核心块:
| 类型 | type 值 | 用途 |
|---|---|---|
| TextContentBlock | “text” | 标准文本 |
| ReasoningContentBlock | “reasoning” | 模型推理步骤 |
| PlainTextContentBlock | “text-plain” | 文档文本(.txt/.md) |
多模态块:
| 类型 | type 值 | 必填字段 |
|---|---|---|
| ImageContentBlock | “image” | url 或 base64(+ mime_type) |
核心块:
| 类型 | type 值 | 用途 |
|---|---|---|
| TextContentBlock | “text” | 标准文本 |
| ReasoningContentBlock | “reasoning” | 模型推理步骤 |
| PlainTextContentBlock | “text-plain” | 文档文本(.txt/.md) |
多模态块:
| 类型 | type 值 | 必填字段 |
|---|---|---|
| ImageContentBlock | “image” | url 或 base64(+ mime_type) |
| AudioContentBlock | “audio” | url 或 base64(+ mime_type) |
| VideoContentBlock | “video” | url 或 base64(+ mime_type) |
| FileContentBlock | “file” | url 或 base64(+ mime_type,如 PDF) |
工具相关块:
| 类型 | type 值 | 用途 |
|---|---|---|
| ToolCall | “tool_call” | 函数调用(name/args/id) |
| ToolCallChunk | “tool_call_chunk” | 流式工具调用片段 |
| InvalidToolCall | “invalid_tool_call” | 格式错误的调用(捕获 JSON 解析错误) |
| ServerToolCall | “server_tool_call” | 服务器端执行的工具调用 |
| ServerToolCallChunk | “server_tool_call_chunk” | 流式服务器端工具调用片段 |
| ServerToolResult | “server_tool_result” | 搜索结果 |
厂商特定块:
| 类型 | type 值 | 用途 |
|---|---|---|
| NonStandardContentBlock | “non_standard” | 厂商独有功能的"逃生舱" |
十、与聊天模型配合 —— 无状态循环
聊天模型接受消息列表作输入、返回 AIMessage 作输出,交互通常是无状态的。所谓多轮对话,就是用一个不断增长的消息列表反复调用模型:
# 伪代码:最简单的对话循环
messages = [SystemMessage("You are a helpful assistant")]
while True:
user_input = input("You: ")
messages.append(HumanMessage(user_input))
response = model.invoke(messages) # 把全部历史喂进去
messages.append(response) # 把回复加入历史
print("AI:", response.text)
两个相关方向:
- 持久化和管理对话历史的内置功能
- 管理上下文窗口的策略(消息裁剪、总结)
一句话总结 + 与前两讲的关系
▎ 消息 = 模型输入输出的基本单元,靠"角色"区分谁说的、靠"content"装内容、靠"元数据"带附加信息。 四种类型 SystemMessage / HumanMessage / AIMessage / ToolMessage 串起来就是一次完整对话:系统定调 → 用户提问 →
模型回复(或发起工具调用)→ 工具返回结果 → 模型再回复。
三讲连起来看就通透了:
- 模型:会思考的大脑,吃消息、吐消息(AIMessage)
- 消息:大脑吃的"话",四种角色构成对话
- 代理:把"模型 + 工具 + 消息循环"自动化套起来——代理循环里发生的,本质上就是 HumanMessage → AIMessage(tool_calls) → ToolMessage → AIMessage(最终答案) 这套消息流转
更多推荐



所有评论(0)