Skip to content

第 9 章 · 多模态 Agent

本章目标:学会用 Image / Audio / Video / File 内容对象给 Agent 传入多模态输入,了解图像与音频输出的实现路径,并完成一个"发票图片 → 结构化数据"的实战案例。

9.1 多模态的内容对象体系

Agno 用统一的内容对象抽象四种模态,全部从 agno.media 导入。官方文档给出的结构如下:

支持的来源常用元数据
Imageurlfilepathcontentformatmime_typedetail
Audiourlfilepathcontentformatmime_typesample_ratechannels
Videourlfilepathcontentformatmime_typeduration
Fileurlfilepathcontentexternalfilenameformatmime_type

同一个类既支持 URL 也支持本地文件和原始字节,这让"线上图片直连"和"本地文件处理"可以无缝切换。传入方式是 run() / print_response() 的关键字参数:images=[...]audios=[...]videos=[...]files=[...]

模型能力决定一切

框架只是搬运工,真正决定能不能看图/听音的是模型。接入视觉模型时请确认三方服务商的对应模型支持该模态(如 DeepSeek 的 chat 系列当前不接收图像输入,做视觉任务应选择支持视觉的三方模型)。视频输入目前仅 Gemini 与 AWS Bedrock 部分模型支持。

9.2 图像理解:URL 与本地文件

python
# 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 对象传入:

python
# 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 并指定图像模型;音频输出则由模型直接返回:

python
# 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")
python
# 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_datawrite_audio_to_file 两个工具函数。

9.5 综合案例:发票图片结构化提取

把本章内容与前一章的工具、第 5 章的结构化输出串起来——这是企业里最高频的多模态场景之一:

python
# 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",最合适的组合是?

🛠️ 动手实践

  1. 找两张不同场景的照片,让 Agent 分别"单图描述"与"双图对比",观察提示词相同时回答结构的差异。
  2. 把 9.5 节的发票提取改造成合同关键信息提取(甲乙方、金额、期限),设计你自己的 Pydantic 模型。
  3. 给 9.4 的生图 Agent 加一个 save_last_image() 自定义工具,让用户能用一句"把刚才的图存下来"完成落盘。

完成动手实践后,进入第 10 章:会话管理 session state 与 chat history