一、消息是什么?

▎ 消息是 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(最终答案) 这套消息流转
Logo

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

更多推荐