第七章:Multimodal
Deep Agents 的内置 readfile 不只读文本。对受支持的图片、PDF、音视频文件,它会返回标准多模态 content blocks,让支持对应模态的模型直接检查文件内容,而不是把二进制转成乱码塞进 prompt。
Deep Agents 的内置 read_file 不只读文本。对受支持的图片、PDF、音视频文件,它会返回标准多模态 content blocks,让支持对应模态的模型直接检查文件内容,而不是把二进制转成乱码塞进 prompt。
最小代码
代码在 deepagent_src/advanced_teach/07_multimodal.py。
本例复用仓库既有的一张真实机场登机照片,并把它复制进临时目录。它会用同一模型走两条路径:直接通过 HumanMessage 传图,以及让 Agent 调用 read_file("/airport.png")。这样能区分“模型根本不能识图”和“provider 不支持多模态工具结果”这两类问题。
agent = create_deep_agent(
model=get_gpt_model(disable_tool_streaming=True),
backend=LocalShellBackend(root_dir=tmp, virtual_mode=True),
subagents=[],
)
direct = model.invoke(
[HumanMessage(content=[text_block, image_block])]
)
state = agent.invoke(
{"messages": [HumanMessage(content="Inspect the scene in /airport.png.")]}
)
这里的关键不是复制代码,而是 Agent 的 read_file 工具。backend 返回 PNG 的数据;Deep Agents 的 filesystem middleware 根据扩展名把它变成 image content block,再交给模型。示例将 filesystem tool allowlist 限为 read_file,避免模型在这个单一验证中绕去 ls 或 execute。
运行命令
uv run python -m deepagent_src.advanced_teach.07_multimodal
这会将仓库里的实拍图复制到临时目录,触发一次直接视觉调用和一次 Agent 调用;文件只存在临时目录,运行结束自动删除。
预期现象
direct_image: The aircraft is Thai AirAsia, its dominant color is red, and the people are boarding in a line up the stairs.
tool_calls: read_file
read_file_blocks: image
final: ...
tool_result_vision_supported: False
multimodal capability probe complete
直接路径必须识别 AirAsia、red 与登机/排队行为,否则脚本会失败。Agent 路径则先断言真的调用 read_file,并且返回了 image content block,再打印 tool_result_vision_supported。
本项目在 2026-07-29 的真实结果是:gpt-5.5 经当前 ChatOpenAI gateway 能正确处理直接图片输入,但 read_file 的图片 ToolMessage 没有被正确理解,探针输出 False。这不是 Deep Agents 把文件当成文本,也不证明模型不支持视觉;它说明这个 provider 链路不支持或没有正确实现多模态工具结果。升级或切换模型前,必须重新运行探针,不要凭模型宣传页瞎猜。
支持文件
官方当前文档列出的扩展名:
| 类型 | 扩展名 |
|---|---|
| 图片 | .png、.jpg、.jpeg、.gif、.webp、.heic、.heif |
| 文档 | .pdf、.ppt、.pptx |
| 音频 | .wav、.mp3、.aiff、.aac、.ogg、.flac |
| 视频 | .mp4、.mpeg、.mov、.avi、.flv、.mpg、.webm、.wmv、.3gpp |
是否真正能看、听、读,仍取决于所选模型和 provider 支持的 MIME type。文件扩展名受支持,不等于每个模型都支持该模态。
常见误区
不要把 base64 字符串拼进普通文本 prompt。本例的直接探针使用标准 {"type": "image", "base64": ..., "mime_type": ...} content block,这是合法的模型输入。长对话或大媒体仍应优先传文件路径或 URL;如果走 read_file,还必须验证 provider 支持多模态 tool result。
不要无上限读取大媒体文件。图片、PDF 页面、音视频都会占模型上下文和成本;生产环境要限制大小、页数、时长和允许的 MIME type,并对用户上传文件做恶意内容和权限检查。
验证
- 临时目录复制到有效 PNG。
- Agent 的 tool call 列表包含
read_file。 - 直接图片输入必须识别
AirAsia、red 与登机/排队行为。 - Agent 路径必须有
read_filetool call,且该 tool result 包含imagecontent block。 tool_result_vision_supported才表示当前 provider 是否真的能让 Agent 看见工具返回的图片。
官方依据:/oss/python/deepagents/multimodal 说明 read_file 对支持的非文本文件返回多模态 content blocks,前提是所选模型支持对应模态和 tool result;/oss/python/langchain/messages 说明图片可以使用标准 image content block;/oss/python/deepagents/tools 说明自定义工具也可以返回同类 content blocks。
相关资源
查看示例代码:deepagent_src/advanced_teach/07_multimodal.py
from __future__ import annotations import base64 import os from pathlib import Path from tempfile import TemporaryDirectory from typing import Any from deepagents import create_deep_agent from deepagents.backends import LocalShellBackend from deepagents.middleware import FilesystemMiddleware from langchain.messages import HumanMessage from deepagent_src.llms import get_gpt_model SOURCE_IMAGE = ( Path(__file__).resolve().parents[2] / "anything_chat_rag/parser_output/旅行日记2/docling/images/image_0.png" ) def copy_airport_image(path: Path) -> None: assert SOURCE_IMAGE.is_file(), SOURCE_IMAGE path.write_bytes(SOURCE_IMAGE.read_bytes()) def tool_call_names(messages: list[Any]) -> list[str]: names: list[str] = [] for message in messages: for call in getattr(message, "tool_calls", None) or []: names.append(call.get("name", "")) return names def matches_airport_scene(text: str) -> bool: normalized = text.lower() return ( "airasia" in normalized and "red" in normalized and any(word in normalized for word in ("board", "queue", "line")) ) def direct_image_answer() -> str: image_b64 = base64.b64encode(SOURCE_IMAGE.read_bytes()).decode() response = get_gpt_model().invoke( [ HumanMessage( content=[ { "type": "text", "text": ( "Inspect this image in one English sentence: name the airline " "printed on the aircraft, state the aircraft's dominant color, " "and describe what the people are doing." ), }, {"type": "image", "base64": image_b64, "mime_type": "image/png"}, ] ) ] ) return response.text def tool_image_block_types(messages: list[Any]) -> list[str]: return [ block.get("type", "") for message in messages if getattr(message, "type", "") == "tool" and getattr(message, "name", "") == "read_file" for block in message.content_blocks ] def main() -> None: os.environ["LANGSMITH_TRACING"] = "false" os.environ["LANGCHAIN_TRACING_V2"] = "false" direct_text = direct_image_answer() assert matches_airport_scene(direct_text), direct_text print("direct_image:", direct_text) with TemporaryDirectory(prefix="deepagents-multimodal-") as tmp: image_path = Path(tmp, "airport.png") copy_airport_image(image_path) backend = LocalShellBackend(root_dir=tmp, virtual_mode=True) agent = create_deep_agent( model=get_gpt_model(disable_tool_streaming=True), backend=backend, subagents=[], middleware=[FilesystemMiddleware(backend=backend, tools=["read_file"])], system_prompt=( "You are a multimodal teaching assistant. " "You must call read_file on /airport.png before answering. " "Inspect the image and answer in one English sentence: name the airline " "printed on the aircraft, state the aircraft's dominant color, and " "describe what the people are doing." ), ) state = agent.invoke( { "messages": [ HumanMessage(content="Inspect the scene in /airport.png.") ] } ) final_text = state["messages"][-1].text calls = tool_call_names(state["messages"]) image_blocks = tool_image_block_types(state["messages"]) print("tool_calls:", ", ".join(calls)) print("read_file_blocks:", ", ".join(image_blocks)) print("final:", final_text) assert "read_file" in calls, calls assert "image" in image_blocks, image_blocks print("tool_result_vision_supported:", matches_airport_scene(final_text)) print("multimodal capability probe complete") if __name__ == "__main__": main()