HumanMessage 的 content 不只是字符串——它可以是一个包含文本、图片、音频的列表,让 Agent 理解多种媒体类型的输入。
在前面的课程里,HumanMessage(content="...") 的 content 是一个字符串。但 LangChain 的消息系统支持一种更强大的格式:content 列表——把多种媒体类型组合在同一条消息里。
content 列表的每个元素是一个字典,包含 type 字段,用于区分内容类型:
| type 值 | 内容类型 | 关键字段 |
|---|---|---|
"text" |
纯文本 | text: 文本内容字符串 |
"image_url" |
图片(URL 或 base64) | image_url.url: 图片 URL 或 data URI |
"input_audio" |
音频数据 | data: base64 音频,format: 格式 |
同一条消息里可以同时包含文字问题和图片,例如:"这张图片里有什么?(附图)"。列表格式允许混合多种内容类型,比单一字符串更灵活。字符串格式是列表格式的简写——LangChain 内部会把字符串自动转换为 [{"type": "text", "text": "..."}]。
即使是纯文本消息,也可以用显式的列表格式写——这在需要与图片或音频组合时非常有用:
from langchain.messages import HumanMessage
from langchain.agents import create_agent
agent = create_agent(
model=deepseek_model(),
system_prompt="You are a science fiction writer, create a capital city at the users request.",
)
# 显式的 content 列表格式(等价于 content="What is the capital of The Moon?")
question = HumanMessage(content=[
{"type": "text", "text": "What is the capital of The Moon?"}
])
response = agent.invoke({"messages": [question]})
print(response['messages'][-1].content)
文本列表格式和字符串格式效果完全相同,只是写法不同。当需要在同一条消息里加入图片时,就需要在这个列表里追加图片元素。
把图片发送给多模态 LLM 的标准方式是:将图片转为 Base64 编码,然后用 Data URI 格式嵌入消息:
import base64
# 假设 img_bytes 是图片的 bytes 数据
img_b64 = base64.b64encode(img_bytes).decode("utf-8")
question = HumanMessage(content=[
{
"type": "text",
"text": "What is in this image?" # 文字问题
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{img_b64}" # Data URI 格式
}
}
])
data:image/png;base64,{img_b64} 这个字符串的结构是:
| 部分 | 含义 | 示例 |
|---|---|---|
data: |
Data URI 协议前缀 | 固定写法 |
image/png |
MIME 类型(图片格式) | image/jpeg、image/webp |
;base64, |
编码方式声明 | 固定写法 |
{img_b64} |
实际的 base64 编码内容 | 很长的字符串 |
如果图片可以公开访问,可以直接传图片 URL:
{"type": "image_url", "image_url": {"url": "https://example.com/image.png"}}
这种方式无需 base64 编码,但要求图片必须能被 API 服务器访问到(公开 URL)。
在 Jupyter Notebook 里,可以用 ipywidgets.FileUpload 提供图片上传 UI,然后读取上传的文件数据:
from ipywidgets import FileUpload
from IPython.display import display
import base64
# 显示上传按钮
uploader = FileUpload(accept='.png', multiple=False)
display(uploader)
# 上传完成后(用户选择文件后):
uploaded_file = uploader.value[0] # 第一个上传的文件
# 关键:uploaded_file["content"] 是 memoryview,不是 bytes
content_mv = uploaded_file["content"]
img_bytes = bytes(content_mv) # memoryview → bytes
# Base64 编码
img_b64 = base64.b64encode(img_bytes).decode("utf-8")
FileUpload 返回的 content 字段是 memoryview 对象,不是 bytes。必须先用 bytes(content_mv) 或 content_mv.tobytes() 转换,再进行 base64 编码。直接对 memoryview 调用 base64.b64encode() 会报错。
print(uploader.value[0])
同样的思路——录制麦克风音频,转为 base64,发送给支持音频输入的 LLM:
import sounddevice as sd
from scipy.io.wavfile import write
import base64
import io
# 录制 5 秒音频
duration = 5
sample_rate = 44100
print("Recording...")
audio = sd.rec(int(duration * sample_rate), samplerate=sample_rate, channels=1)
sd.wait() # 等待录音完成
print("Done.")
# 写入内存 WAV 缓冲区
buf = io.BytesIO()
write(buf, sample_rate, audio) # scipy 写 WAV 格式
wav_bytes = buf.getvalue()
# Base64 编码
aud_b64 = base64.b64encode(wav_bytes).decode("utf-8")
音频消息格式(针对支持音频的模型):
question = HumanMessage(content=[
{
"type": "input_audio",
"data": aud_b64,
"format": "wav"
}
])
并非所有模型都支持图片或音频输入。notebook 里明确提醒了这一点:
DeepSeek 的公开聊天 API 是文本/工具调用导向的,官方 API 文档中没有暴露图片输入或音频输入的模型接口。
如果需要图片理解或音频理解,需要使用专用的多模态提供商(如 GPT-4V、Claude 3、Gemini 等)。
| 模型/提供商 | 文本 | 图片 | 音频 |
|---|---|---|---|
| DeepSeek(当前公开 API) | ✓ | ✗ | ✗ |
| GPT-4o / GPT-4V | ✓ | ✓ | ✓ |
| Claude 3.x (Sonnet/Opus) | ✓ | ✓ | ✗ |
| Gemini 1.5 / 2.0 | ✓ | ✓ | ✓ |
虽然不同模型的多模态能力不同,但 LangChain 的消息格式(content 列表)是统一的。你用同一套代码结构,只需切换 model_provider 和模型名称,就能在不同的多模态 LLM 之间切换,不需要改消息构造逻辑。这就是 LangChain 抽象层的价值所在。
HumanMessage(content=[
{"type": "text", "text": "你的问题"}
])
# 简写等价:HumanMessage(content="你的问题")
HumanMessage(content=[
{"type": "text", "text": "这张图里有什么?"},
{
"type": "image_url",
"image_url": {
# 方式 1:base64 Data URI
"url": f"data:image/png;base64,{img_b64}"
# 方式 2:公开 URL
# "url": "https://example.com/image.png"
}
}
])
HumanMessage(content=[
{
"type": "input_audio",
"data": aud_b64, # base64 编码的音频
"format": "wav" # 音频格式:wav / mp3
}
])
# 注意:需要使用支持音频输入的模型(如 GPT-4o-audio)
{"type": ..., ...} 字典memoryview 需先转 bytes