1. 本章目标 #
上一章用字符串或 dict 列表调用模型。本章把对话状态的基本单位讲清楚:
用 System / Human / AI / Tool 消息正确表示对话,并了解多模态内容怎么写。
学完你应能:
- 区分三种传参方式:纯文本、消息对象、dict
- 正确使用四种消息类型
- 手写多轮对话与「模型 ↔ 工具」消息闭环
- 理解
content与content_blocks - 按标准块格式构造多模态输入
参考文档:
2. 消息是什么 #
消息是模型上下文的基本单位,通常包含:
| 部分 | 含义 |
|---|---|
| Role(角色) | 谁说的:system / user / assistant / tool |
| Content(内容) | 文本、图片、文件等载荷 |
| Metadata(元数据) | id、token 用量、tool_calls 等 |
Agent、多轮客服、工具循环,本质上都是在维护一份不断增长的 messages 列表。
[SystemMessage] 人设与约束
[HumanMessage] 用户第 1 问
[AIMessage] 模型回答(或带 tool_calls)
[ToolMessage] 工具结果(若有)
[AIMessage] 最终回答
[HumanMessage] 用户第 2 问
...3. 三种传参方式 #
3.1. 纯文本(单轮、最简) #
适合一次性生成,不需要历史:
# 从 langchain.chat_models 模块导入初始化聊天模型的函数
from langchain.chat_models import init_chat_model
# 从 dotenv 模块导入加载环境变量的函数
from dotenv import load_dotenv
# 加载 .env 文件中的环境变量,override=True 表示覆盖已存在的同名环境变量
load_dotenv(override=True)
# 初始化聊天模型,使用 deepseek:deepseek-v4-flash,并将 temperature 设为 0
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# 调用模型生成一句关于春天的俳句,并将返回结果赋值给 response
response = model.invoke("写一首关于春天的诗歌")
# 打印模型响应中的文本内容
print(response.content)
等价于只发一条用户消息。
3.2. 消息对象(推荐,类型清晰) #
3.2.1. message_objects.py #
# 从 langchain.chat_models 模块导入初始化聊天模型的函数
from langchain.chat_models import init_chat_model
# 从 langchain.messages 模块导入系统消息类和人类消息类
from langchain.messages import SystemMessage, HumanMessage
# 从 dotenv 模块导入用于加载环境变量的函数
from dotenv import load_dotenv
# 加载 .env 文件中的环境变量,override=True 表示覆盖已存在的同名环境变量
load_dotenv(override=True)
# 初始化 DeepSeek 聊天模型,并将温度设为 0 以使输出更稳定、更确定
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# 创建用于本次对话的消息列表
messages = [
# 添加系统消息,设定助手为简洁的中文助手且回答不超过两句话
SystemMessage("你是简洁的中文助手,回答不超过两句话。"),
# 添加人类消息,向模型提问 langchain 中 Messages 的含义
HumanMessage("什么是 langchain 里的 Messages?"),
# 结束消息列表的定义
]
# 调用模型处理消息列表,并获取 AI 的回复结果
ai = model.invoke(messages)
# 打印返回对象的类型名称,预期结果为 AIMessage
print(type(ai).__name__) # AIMessage
# 打印 AI 回复消息中的文本内容
print(ai.content)
3.3. dict 格式(OpenAI 风格) #
与 HTTP API / 前端协议对齐时很方便;LangChain 会帮你转成消息对象:
3.3.1. message_dicts.py #
# 从 langchain.chat_models 模块导入初始化聊天模型的函数
from langchain.chat_models import init_chat_model
# 从 dotenv 模块导入加载环境变量的函数
from dotenv import load_dotenv
# 加载 .env 文件中的环境变量,override=True 表示覆盖已存在的同名环境变量
load_dotenv(override=True)
# 初始化聊天模型,使用 deepseek:deepseek-v4-flash,并将 temperature 设为 0
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# 定义要发送给模型的消息列表
messages = [
# 系统角色消息:设定助手为简洁的中文助手
{"role": "system", "content": "你是简洁的中文助手。"},
# 用户角色消息:询问 langchain 里 Messages 的含义
{"role": "user", "content": "什么是 langchain 里的 Messages?"},
# 结束消息列表
]
# 调用模型处理消息,并打印返回结果中的 content 内容
print(model.invoke(messages).content)3.4. 怎么选 #
| 方式 | 适合 |
|---|---|
| 字符串 | 单轮、无历史 |
| 消息对象 | 多轮、工具、多模态、要类型提示 |
| dict | 与外部协议对接、快速原型 |
本文轮场景优先用 消息对象。
4. 四种消息类型 #
4.1. SystemMessage:人设与全局约束 #
告诉模型「你是谁、怎么答、有哪些红线」。一般放在列表最前面。
from langchain.messages import SystemMessage
system = SystemMessage(
"你是资深旅游助手。"
"先给结论,再给最短行程建议。"
"不要编造不存在的景点或交通信息。"
)create_agent(..., system_prompt="...") 本质上也是在 harness 里注入系统级约束;单独调模型时用 SystemMessage 更直观。
4.2. HumanMessage:用户输入 #
可以是纯文本,也可以带多模态块。可选 name / id 便于追踪:
from langchain.messages import HumanMessage
human = HumanMessage(
content="请解释 tool_calls 是什么",
name="alice", # 部分供应商会用;有的会忽略
id="msg_001",
)4.3. AIMessage:模型输出 #
model.invoke(...) 的返回值就是 AIMessage。常见属性:
| 属性 | 含义 |
|---|---|
content |
文本或内容块列表 |
content_blocks |
标准化后的内容块(懒解析) |
tool_calls |
工具调用请求列表(无则为空) |
usage_metadata |
token 用量(若有) |
id |
消息 id |
response_metadata |
供应商原始响应元数据 |
也可以手动构造 AIMessage,塞进历史(例如回放、测试、注入固定助手回复):
4.3.1. 3.manual_ai_message.py #
# 从 langchain.chat_models 导入初始化聊天模型的函数
from langchain.chat_models import init_chat_model
# 从 langchain.messages 导入系统消息、人类消息和 AI 消息类
from langchain.messages import SystemMessage, HumanMessage, AIMessage
# 从 dotenv 导入加载环境变量的函数
from dotenv import load_dotenv
# 加载 .env 文件中的环境变量,override=True 表示覆盖已存在的环境变量
load_dotenv(override=True)
# 初始化 DeepSeek 聊天模型,温度设为 0 以保证输出更确定
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# 构建对话消息列表,包含系统提示与多轮对话历史
messages = [
# 系统消息:设定助手角色为简洁助手
SystemMessage("你是简洁助手。"),
# 人类消息:用户自我介绍叫张三
HumanMessage("我叫张三。"),
# AI 消息:假装模型之前说过的回复,表示已记住名字
AIMessage("好的,张三,已记住。"),
# 人类消息:用户询问自己叫什么名字
HumanMessage("我叫什么名字?"),
# 结束消息列表的定义
]
# 调用模型进行推理,并打印返回的文本内容
print(model.invoke(messages).content)
4.4. ToolMessage:工具结果回传 #
当 AIMessage 带有 tool_calls 时,你需要执行工具,并把结果用 ToolMessage 塞回对话。tool_call_id 必须与对应调用的 id 一致。
4.4.1. tool_message_loop.py #
# 从 langchain.chat_models 导入初始化聊天模型的函数
from langchain.chat_models import init_chat_model
# 从 langchain.messages 导入人类消息类和工具消息类
from langchain.messages import HumanMessage, ToolMessage
# 从 langchain.tools 导入 tool 装饰器,用于将函数注册为工具
from langchain.tools import tool
# 从 dotenv 导入加载环境变量的函数
from dotenv import load_dotenv
# 加载 .env 文件中的环境变量,override=True 表示覆盖已存在的同名环境变量
load_dotenv(override=True)
# 使用 @tool 装饰器将下方函数注册为可被大模型调用的工具
@tool
# 定义获取天气信息的工具函数,接收城市名参数,返回字符串结果
def get_weather(city: str) -> str:
# 工具的功能说明文档字符串,供模型理解该工具用途
"""获取指定城市的天气信息。"""
# 返回模拟的天气信息字符串
return f"{city} 今天晴,气温 25°C。"
# 初始化 DeepSeek 聊天模型,温度设为 0 以保证输出更稳定可复现
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# 将 get_weather 工具绑定到模型上,使模型能够发起对该工具的调用
model_with_tools = model.bind_tools([get_weather])
# 创建初始消息列表,包含一条询问北京天气的人类消息
messages = [HumanMessage("北京今天天气怎么样?")]
# 第 1 步:调用绑定了工具的模型,模型可能返回 tool_calls
ai = model_with_tools.invoke(messages)
# 将模型的回复(可能包含工具调用请求)追加到消息列表中
messages.append(ai)
# 打印模型发起的工具调用信息,便于观察调用细节
print("tool_calls:", ai.tool_calls)
# 第 2 步:遍历模型返回的每一个工具调用请求
for call in ai.tool_calls:
# 使用工具调用中携带的参数执行 get_weather 工具
result = get_weather.invoke(call["args"])
# 将工具执行结果封装为 ToolMessage 并追加到消息列表
messages.append(
# 创建工具消息对象,用于把工具执行结果回传给模型
ToolMessage(
# 工具返回的内容,转换为字符串形式
content=str(result),
# 对应本次工具调用的唯一标识 ID
tool_call_id=call["id"],
# 被调用的工具名称
name=call["name"],
# 结束 ToolMessage 对象的构造
)
# 结束 messages.append 调用
)
# 第 3 步:把包含工具结果的完整消息再次交给模型,生成最终自然语言回答
final = model_with_tools.invoke(messages)
# 打印模型给出的最终回答文本内容
print(final.content)这就是 Agent 内部循环的「手工版」。create_agent 帮你自动完成第 2、3 步;
5. 多轮对话:维护消息列表 #
关键不在 API,而在你如何累积历史:
5.1. multi_turn.py #
# 从 langchain.chat_models 导入 init_chat_model,用于初始化聊天模型
from langchain.chat_models import init_chat_model
# 从 langchain.messages 导入系统消息、人类消息和 AI 消息类型
from langchain.messages import SystemMessage, HumanMessage, AIMessage
# 从 dotenv 导入 load_dotenv,用于加载环境变量
from dotenv import load_dotenv
# 加载 .env 文件中的环境变量,override=True 表示覆盖已存在的变量
load_dotenv(override=True)
# 初始化 DeepSeek 聊天模型,temperature=0 使输出更确定
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# 创建对话历史列表,并加入系统提示
history = [
# 系统消息:设定助手角色为简洁助手
SystemMessage("你是简洁助手。用尽量短的句子回答。"),
]
# 定义多轮用户输入列表
turns = [
# 第一轮用户消息
"我喜欢猫。",
# 第二轮用户消息,用于测试上下文记忆
"根据刚才的信息,我喜欢什么动物?",
]
# 遍历每一轮用户输入
for user_text in turns:
# 将当前用户消息追加到对话历史
history.append(HumanMessage(user_text))
# 调用模型,传入完整对话历史进行推理
ai = model.invoke(history)
# 将模型回复追加到对话历史
history.append(ai)
# 打印当前用户输入
print(f"用户: {user_text}")
# 打印助手回复内容
print(f"助手: {ai.content}\n")
# 查看完整轨迹:遍历对话历史中的每条消息
for i, msg in enumerate(history):
# 打印消息索引、类型名称和内容
print(f"[{i}] {type(msg).__name__}: {msg.content}")
注意:
- 每轮都要把 本轮 Human + 本轮 AI 追加进列表,再问下一轮
- 历史无限涨会撞上下文窗口;
6. 在 Agent 里看消息 #
6.1. 6.agent_message_types.py #
# 从 langchain.agents 模块导入 create_agent 函数,用于创建智能体
from langchain.agents import create_agent
# 从 dotenv 模块导入 load_dotenv 函数,用于加载环境变量
from dotenv import load_dotenv
# 加载 .env 文件中的环境变量,override=True 表示覆盖已存在的同名环境变量
load_dotenv(override=True)
# 定义获取指定城市天气信息的工具函数,参数 city 为城市名称,返回值为字符串
def get_weather(city: str) -> str:
# 函数文档字符串,说明该函数的作用是获取指定城市的天气信息
"""获取指定城市的天气信息。"""
# 返回模拟的天气信息,包含城市名、晴天描述和气温
return f"{city} 今天晴,气温 25°C。"
# 创建智能体实例,并配置模型、可用工具以及系统提示词
agent = create_agent(
# 指定智能体使用的大语言模型为 deepseek:deepseek-v4-flash
model="deepseek:deepseek-v4-flash",
# 将 get_weather 函数注册为智能体可调用的工具列表
tools=[get_weather],
# 设置系统提示词,指示智能体在需要天气信息时调用工具
system_prompt="需要天气时请调用工具。",
)
# 调用智能体执行推理,传入包含用户问题“上海天气如何?”的消息列表
result = agent.invoke({"messages": [{"role": "user", "content": "上海天气如何?"}]})
# 遍历智能体返回结果中的 messages 列表,同时获取索引 i 和消息对象 msg
for i, msg in enumerate(result["messages"]):
# 打印当前消息的索引编号以及消息对象的类型名称
print(f"[{i}] {type(msg).__name__}")
# 安全获取消息的 tool_calls 属性,若存在工具调用则进入分支
if getattr(msg, "tool_calls", None):
# 打印该消息中包含的工具调用详细信息
print(" tool_calls:", msg.tool_calls)
# 安全获取消息的 content 属性,若不存在则使用空字符串作为默认值
content = getattr(msg, "content", "")
# 判断消息内容是否非空,非空时才进行打印
if content:
# 打印该消息的文本内容
print(" content:", content)
典型顺序:HumanMessage → AIMessage(tool_calls) → ToolMessage → AIMessage(最终回答)。
7. 内容载荷:content 与 content_blocks #
7.1. content 可以是什么 #
- 字符串:最常见
- LangChain 标准内容块:跨供应商更一致
from langchain.messages import HumanMessage
# 字符串
HumanMessage("你好")
# 标准块(推荐写多模态时使用)
HumanMessage(content_blocks=[
{"type": "text", "text": "描述这张图"},
{"type": "image", "url": "https://example.com/demo.jpg"},
])读回复时可用 ai.content_blocks 拿到标准化块(例如把不同厂商的 reasoning / thinking 统一成 type: "reasoning")。
# 从 langchain 导入初始化聊天模型的函数
from langchain.chat_models import init_chat_model
# 从 dotenv 导入加载环境变量的函数
from dotenv import load_dotenv
# 从 rich 库导入增强版打印函数,便于美化输出
from rich import print
# 加载 .env 文件中的环境变量,override=True 表示覆盖已存在的环境变量
load_dotenv(override=True)
# deepseek-v4 默认开启 thinking,便于演示 content_blocks 中的 reasoning
# 初始化 DeepSeek 聊天模型,设置温度为 0 使输出更稳定
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# 调用模型,请求写一首关于春天的短诗
ai = model.invoke("写一首关于春天的短诗")
# content 是原始字符串(或厂商原始块列表)
print("content:", ai.content)
# content_blocks 是标准化块:不同厂商的 reasoning / thinking 会统一成 type="reasoning"
# 打印 content_blocks 标题
print("content_blocks:")
# 遍历模型返回的标准化内容块列表
for block in ai.content_blocks:
# 打印当前内容块的完整信息
print(block)
# 判断当前块是否为推理(reasoning)类型
if block["type"] == "reasoning":
# 打印推理内容
print(" reasoning:", block.get("reasoning"))
# 判断当前块是否为文本(text)类型
elif block["type"] == "text":
# 打印文本内容
print(" text:", block.get("text"))
7.2. 多模态入门 #
多模态 = 一条消息里混合文本、图片、文件、音频等。是否支持取决于模型,不是写了块就一定能跑。
7.2.1. multimodal_image_message.py(结构示意) #
# 从 langchain.messages 导入 HumanMessage 类,用于构建人类输入消息
from langchain.messages import HumanMessage
# 从 langchain_qwq 导入 ChatQwen,用于调用通义千问多模态模型
from langchain_qwq import ChatQwen
# 从 dotenv 导入 load_dotenv,用于从 .env 文件加载环境变量
from dotenv import load_dotenv
# 加载 .env 中的环境变量;override=True 表示覆盖已存在的同名环境变量
load_dotenv(override=True)
# 阿里云百炼 / DashScope 多模态视觉模型(需 DASHSCOPE_API_KEY)
# 国内默认可在 .env 设置:
# DASHSCOPE_API_BASE=https://dashscope.aliyuncs.com/compatible-mode/v1
# 创建 ChatQwen 模型实例,指定使用 qwen-vl-plus 视觉语言模型
model = ChatQwen(model="qwen-vl-plus")
# 方式 A:图片 URL
# 构建一条包含文本提示和图片 URL 的 HumanMessage
msg_url = HumanMessage(
# 消息的内容块列表,可混合文本、图片等多种类型
content_blocks=[
# 文本内容块:要求模型用一句话描述图片内容
{"type": "text", "text": "用一句话描述图片内容。"},
# 图片内容块:通过远程 URL 传入图片
{
# 声明该内容块的类型为图片
"type": "image",
# 指定图片的远程访问地址
"url": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg",
# 结束图片内容块字典
},
# 结束内容块列表
]
# 结束 HumanMessage 构造
)
# 调用模型,传入消息列表并获取 AI 回复
ai = model.invoke([msg_url])
# 打印 AI 回复的纯文本内容
print(ai.content)
# 打印 AI 回复的结构化内容块
print(ai.content_blocks)
# 方式 B:base64(本地文件常见)
# 导入 base64 模块,用于将本地图片编码为 base64 字符串
import base64
# 从 pathlib 导入 Path,用于方便地读写本地文件路径
from pathlib import Path
# 读取本地图片文件 dog_and_girl.jpeg 的原始二进制数据
raw = Path("dog_and_girl.jpeg").read_bytes()
# 构建一条包含文本提示和 base64 图片的 HumanMessage
msg_b64 = HumanMessage(
# 消息的内容块列表
content_blocks=[
# 文本内容块:要求模型用一句话描述图片内容
{"type": "text", "text": "用一句话描述图片内容。"},
# 图片内容块:通过 base64 编码传入本地图片
{
# 声明该内容块的类型为图片
"type": "image",
# 将图片二进制数据编码为 base64 字符串并解码为普通字符串
"base64": base64.b64encode(raw).decode(),
# 指定图片的 MIME 类型为 image/jpeg
"mime_type": "image/jpeg",
# 结束图片内容块字典
},
# 结束内容块列表
]
# 结束 HumanMessage 构造
)
# 调用模型,传入消息列表并获取 AI 回复
ai = model.invoke([msg_url])
# 打印 AI 回复的纯文本内容
print(ai.content)
# 打印 AI 回复的结构化内容块
print(ai.content_blocks).env
DEEPSEEK_API_KEY="sk-0b24393e7dbb4b7d854f59c6e8be527e"
OPENAI_API_BASE="https://api.deepseek.com"
OPENAI_API_KEY=sk-0b24393e7dbb4b7d854f59c6e8be527e
# 阿里云百炼 DashScope(多模态 Qwen-VL)
DASHSCOPE_API_KEY=sk-ws-H.ERHHILP.EwfH.MEUCIG4MABNcWTX6Ab0hxo2mH0h_LZhK54LD5mh-Ud_Fy9byAiEA_Ke0gA9kPvpxK57xEEXtEijinbGbJZGUCNsknqSDnuA
DASHSCOPE_API_BASE=https://dashscope.aliyuncs.com/compatible-mode/v1
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=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
LANGSMITH_PROJECT="langchain"PDF / 音频等类似,把块类型换成 file / audio,并带上 url 或 base64 + mime_type。细节以各 供应商文档 为准。
8. 实用约定与坑 #
| 约定 | 说明 |
|---|---|
| 系统消息靠前 | SystemMessage 一般放列表开头 |
| 工具结果必须对齐 id | ToolMessage.tool_call_id ↔ tool_calls[].id |
| 历史要成对追加 | 漏掉某轮 AI/Tool,下一轮上下文会错乱 |
| 字符串 ≠ 永远够用 | 一上多轮 / 工具 / 多模态,就改用消息列表 |
| 别把密钥写进消息 | 用户内容可能进日志与 LangSmith |
9. 练习 #
- 改人设:同一句用户问题,换两条不同的
SystemMessage,对比回答风格。 - 多轮记忆:先告诉模型一个秘密数字,再新开一轮(清空 history)提问,确认「无历史就无法回答」。
- 手工工具环:不使用
create_agent,只靠bind_tools+ToolMessage完成一次天气问答。 - 打印 content_blocks:对任意
AIMessage打印content与content_blocks,观察差异。
10. 本章小结 #
- 消息 = 角色 + 内容 + 元数据,是对话状态的标准表示。
- 四种类型:System / Human / AI / Tool,工具环靠
tool_call_id对齐。 - 传参可用字符串、消息对象或 dict;复杂场景用消息对象。
content_blocks是跨供应商的内容标准视图;多模态按块拼装,且依赖模型能力。- Agent 的
result["messages"]就是这些类型的轨迹。
下一章:Prompts 提示词工程——模板、变量填充、少样本与可维护的 prompt 层。