1. 本章目标 #

上一章用字符串或 dict 列表调用模型。本章把对话状态的基本单位讲清楚:

用 System / Human / AI / Tool 消息正确表示对话,并了解多模态内容怎么写。

学完你应能:

参考文档:

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}")

注意:

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)

典型顺序:HumanMessageAIMessage(tool_calls)ToolMessageAIMessage(最终回答)

7. 内容载荷:content 与 content_blocks #

7.1. content 可以是什么 #

  1. 字符串:最常见
  2. 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,并带上 urlbase64 + mime_type。细节以各 供应商文档 为准。

8. 实用约定与坑 #

约定 说明
系统消息靠前 SystemMessage 一般放列表开头
工具结果必须对齐 id ToolMessage.tool_call_idtool_calls[].id
历史要成对追加 漏掉某轮 AI/Tool,下一轮上下文会错乱
字符串 ≠ 永远够用 一上多轮 / 工具 / 多模态,就改用消息列表
别把密钥写进消息 用户内容可能进日志与 LangSmith

9. 练习 #

  1. 改人设:同一句用户问题,换两条不同的 SystemMessage,对比回答风格。
  2. 多轮记忆:先告诉模型一个秘密数字,再新开一轮(清空 history)提问,确认「无历史就无法回答」。
  3. 手工工具环:不使用 create_agent,只靠 bind_tools + ToolMessage 完成一次天气问答。
  4. 打印 content_blocks:对任意 AIMessage 打印 contentcontent_blocks,观察差异。

10. 本章小结 #

  1. 消息 = 角色 + 内容 + 元数据,是对话状态的标准表示。
  2. 四种类型:System / Human / AI / Tool,工具环靠 tool_call_id 对齐。
  3. 传参可用字符串、消息对象或 dict;复杂场景用消息对象。
  4. content_blocks 是跨供应商的内容标准视图;多模态按块拼装,且依赖模型能力。
  5. Agent 的 result["messages"] 就是这些类型的轨迹。

下一章:Prompts 提示词工程——模板、变量填充、少样本与可维护的 prompt 层。