第 9 章 · 多模态 Agent
本章目标:学会用
Image/Audio/Video/File内容对象给 Agent 传入多模态输入,了解图像与音频输出的实现路径,并完成一个"发票图片 → 结构化数据"的实战案例。
9.1 多模态的内容对象体系
Agno 用统一的内容对象抽象四种模态,全部从 agno.media 导入。官方文档给出的结构如下:
| 类 | 支持的来源 | 常用元数据 |
|---|---|---|
Image | url、filepath、content | format、mime_type、detail |
Audio | url、filepath、content | format、mime_type、sample_rate、channels |
Video | url、filepath、content | format、mime_type、duration |
File | url、filepath、content、external | filename、format、mime_type |
同一个类既支持 URL 也支持本地文件和原始字节,这让"线上图片直连"和"本地文件处理"可以无缝切换。传入方式是 run() / print_response() 的关键字参数:images=[...]、audios=[...]、videos=[...]、files=[...]。
模型能力决定一切
框架只是搬运工,真正决定能不能看图/听音的是模型。接入视觉模型时请确认三方服务商的对应模型支持该模态(如 DeepSeek 的 chat 系列当前不接收图像输入,做视觉任务应选择支持视觉的三方模型)。视频输入目前仅 Gemini 与 AWS Bedrock 部分模型支持。
9.2 图像理解:URL 与本地文件
# vision_agent.py —— 图像理解(需换用支持视觉的三方/兼容模型)
import os
from agno.agent import Agent
from agno.models.openai import OpenAIChat # 或 OpenAILike 指向任意视觉模型端点
from agno.media import Image
agent = Agent(
model=OpenAIChat(
id="qwen-vl-plus", # 示例:三方 OpenAI 兼容的视觉模型 id
api_key=os.getenv("VISION_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
),
instructions=["描述要具体,先说主体再说细节"],
markdown=True,
)
# 方式一:URL 直传
agent.print_response(
"这张图片里有什么?用一句话概括。",
images=[Image(url="https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/640px-Cat03.jpg")],
)
# 方式二:本地文件
agent.print_response(
"描述这张图片",
images=[Image(filepath="./photo.jpg")],
)
# 方式三:多图对比
agent.print_response(
"对比这两张图的风格差异",
images=[Image(filepath="./a.jpg"), Image(filepath="./b.jpg")],
)三个细节值得注意:
- 多张图就是列表多一项,顺序即模型看到的顺序;
Image(detail=...)可控制视觉细节级别(影响 token 消耗);- 字节流场景(如刚从网络下载、还没落盘)用
Image(content=raw_bytes)。
9.3 文档理解与音频转写
PDF 等文档通过 File 对象传入;音频通过 Audio 对象传入:
# doc_audio.py —— PDF 摘要与语音转写
import os
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.media import Audio, File
pdf_agent = Agent(
model=OpenAIChat(
id="deepseek-chat", # 文本模型即可处理解析后的 PDF 文本
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
),
)
pdf_agent.print_response(
"总结这份报告的三个核心结论",
files=[File(filepath="./annual_report.pdf")],
)
audio_agent = Agent(
# 音频输入需要选择支持音频的模型端点
model=OpenAIChat(
id="qwen-audio-asr",
api_key=os.getenv("AUDIO_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
),
)
with open("./meeting.wav", "rb") as f:
audio_bytes = f.read()
audio_agent.print_response(
"把这段录音转成文字,并列出待办事项",
audios=[Audio(content=audio_bytes, format="wav")],
)9.4 图像与音频输出
除了"读",Agno 也支持"写"。图像生成走工具路径——给 Agent 配上 OpenAITools 并指定图像模型;音频输出则由模型直接返回:
# gen_image.py —— 让 Agent 生成图片并保存到本地
import base64
import os
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.openai import OpenAITools # 内置 OpenAI 能力工具包
from agno.utils.media import save_base64_data # 官方提供的保存工具函数
agent = Agent(
model=OpenAIChat(
id="gpt-image-hosting-model", # 示例:承载图像生成的对话模型
api_key=os.getenv("IMAGE_API_KEY"),
base_url="https://your-openai-compatible-endpoint/v1",
),
tools=[OpenAITools(image_model="gpt-image-2")], # 指定实际生图模型
)
response = agent.run("画一张雪山日落的插画")
if response.images and response.images[0].content:
image_base64 = base64.b64encode(response.images[0].content).decode("utf-8")
save_base64_data(image_base64, "sunset.png") # 落盘
print("已保存 sunset.png")# gen_audio.py —— 语音回复
import os
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.utils.audio import write_audio_to_file
agent = Agent(
model=OpenAIChat(
id="gpt-audio",
api_key=os.getenv("AUDIO_API_KEY"),
base_url="https://your-openai-compatible-endpoint/v1",
modalities=["text", "audio"], # 声明输出模态
audio={"voice": "alloy", "format": "wav"}, # 音色与格式
),
)
response = agent.run("用一个寓言故事解释什么是复利")
if response.response_audio:
write_audio_to_file(audio=response.response_audio.content, filename="story.wav")生成的媒体去哪了
图像生成结果在 response.images 列表中(字节内容在 .content);语音结果在 response.response_audio。它们默认只存在于内存中,需要你自己编码落盘——官方提供了 save_base64_data 与 write_audio_to_file 两个工具函数。
9.5 综合案例:发票图片结构化提取
把本章内容与前一章的工具、第 5 章的结构化输出串起来——这是企业里最高频的多模态场景之一:
# invoice_ocr.py —— 发票图片 -> 结构化 JSON
import os
from pydantic import BaseModel, Field
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.media import Image
class Invoice(BaseModel):
vendor: str = Field(..., description="开票方名称")
amount: float = Field(..., description="价税合计金额")
currency: str = Field(default="CNY", description="币种代码")
invoice_no: str = Field(..., description="发票号码")
agent = Agent(
model=OpenAIChat(
id="qwen-vl-plus", # 视觉模型负责"看"
api_key=os.getenv("VISION_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
),
output_schema=Invoice, # Pydantic 负责"定形"
instructions=[
"从发票图片中提取字段,金额保留两位小数",
"识别不清的字段置为空字符串并在备注说明",
],
)
result: Invoice = agent.run(
"提取这张发票的信息",
images=[Image(filepath="./invoice_scan.png")],
).content
print(result.vendor, result.amount, result.invoice_no)这个模式的扩展性极强:换 output_schema 就能适配合同、身份证、报表截图等任何"图片进、结构出"的需求。
本章小结
- 四种模态对应
agno.media的四个内容对象,均支持url/filepath/content三种来源; - 输入挂在
run()的images/audios/videos/files参数上;能否使用取决于所选模型的能力,视频输入目前仅 Gemini 和 Bedrock 支持; - 输出方向:图像经
OpenAITools工具生成(结果在response.images),音频由模型直出(response.response_audio); - 多模态 +
output_schema组合是 OCR/票据提取等场景的标准解法。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 向 Agent 传入一张本地图片的正确写法是?
2. Agent 配好了 Image 输入但模型报错不支持图像,问题出在哪?
3. 调用生图 Agent 后,生成的图片字节位于哪里?
4. 要做"扫描件 PDF -> 提取字段 JSON",最合适的组合是?
🛠️ 动手实践
- 找两张不同场景的照片,让 Agent 分别"单图描述"与"双图对比",观察提示词相同时回答结构的差异。
- 把 9.5 节的发票提取改造成合同关键信息提取(甲乙方、金额、期限),设计你自己的 Pydantic 模型。
- 给 9.4 的生图 Agent 加一个
save_last_image()自定义工具,让用户能用一句"把刚才的图存下来"完成落盘。
完成动手实践后,进入第 10 章:会话管理 session state 与 chat history。