课程0基础Agent开发课 / Agent基础 / 多模态Agent-处理图片音频与文本的Agent
— 22 min read

多模态Agent-处理图片音频与文本的Agent

*多模态 Agent 架构——文本/图片/音频输入经多模态 LLM 统一处理,调用不同类型工具*

多模态 Agent:处理图片、音频与文本的 Agent

1.1 多模态的技术基础:如何让模型理解非文字信息

多模态Agent架构图
多模态 Agent 架构——文本/图片/音频输入经多模态 LLM 统一处理,调用不同类型工具

在深入实现之前,需要理解多模态能力的技术原理——不是为了自己实现它,而是为了理解它的能力边界和限制来自哪里。

LLM 只能处理 token,但图像不是 token

LLM 的基本工作单元是 token(文本片段)。图像、音频、视频本质上是不同维度的数值矩阵,和 token 序列在形式上完全不同。要让 LLM "看懂"图像,必须先把图像转换成 LLM 能理解的格式。

视觉编码器(Vision Encoder)的作用

多模态模型(如 GPT-4o、Claude 的视觉版本)内置了视觉编码器——一个专门处理图像的神经网络组件(通常基于 CLIP 或 ViT 架构)。它的工作是把图像切分成固定大小的"图块"(patch),对每个图块提取特征向量,然后通过一个映射层把这些特征向量转换成 LLM 能直接处理的 token 序列。

这就是为什么图像会消耗大量 token——一张 1024x1024 的图片被切成几十上百个 patch,每个 patch 对应几个 token,合计可能消耗上千个 token。这也解释了为什么图像的 detail 参数(高分辨率 vs 低分辨率)会影响 token 消耗和识别精度。

音频处理的不同路径

大多数主流 LLM 的对话接口不直接接受音频(部分例外如 GPT-4o Realtime API)。通常的处理路径是先用语音识别(ASR,Automatic Speech Recognition)模型把音频转成文字,再让 LLM 处理文字。OpenAI 的 Whisper 是目前最常用的 ASR 模型,它本质上是一个 Transformer,专门训练用来做语音转文字。

跨模态对齐的挑战

"让模型同时理解图像和文字"比听起来难得多。图像和文字在向量空间里的分布完全不同,训练时需要大量图文对(图像 + 描述文字的配对数据)来让模型学会"对齐"——把同一个概念在不同模态里的表示拉近。这个对齐质量,决定了模型能否准确回答"图里说的 XX 和文本中提到的 XX 是不是同一件事"。

理解了这些原理,后面代码里的各种参数(detail 等级、token 消耗、识别精度限制)就有了合理的解释依据。


早期的 AI Agent 只能处理文字。用户上传一张报表截图,Agent 看不懂;录制一段语音描述,Agent 无从处理。多模态 LLM 的普及改变了这个局面——现在的 Agent 可以直接"看图说话"、"听音转文",并在此基础上推理和决策。

本文介绍多模态 Agent 的核心能力和实现方式,涵盖图像输入、音频处理、视频理解,并给出一个能分析图表的数据分析 Agent 完整示例。

1.2 主流多模态 LLM 能力对比

模型 图像理解 音频输入 视频 图像生成 备注
GPT-5.4 支持 支持 支持 不支持 综合能力最强,Computer Use GA
Claude Opus 4.6 支持 不支持 不直接支持 不支持 代码和指令遵从最强
Gemini 3 Pro 支持 支持 支持 支持 多模态最强,1M上下文
Gemini 2.0 Flash 支持 支持 支持 支持 速度快,性价比高
DeepSeek V3 支持 不支持 不支持 不支持 开源,成本极低

图像理解是目前最成熟的多模态能力,主流模型都支持。音频原生输入(直接传入音频文件而非转录文字)仍是少数模型的能力,通用做法是先用 Whisper(OpenAI 开源的语音转文字模型)转文字再处理。

1.3 图像输入的两种方式

向 LLM 传入图像有两种方式:URL 直链和 base64 编码。

URL 方式:图片已在公网可访问,直接传 URL,LLM 在处理时会自动抓取图片。适合图片来自网络、CDN(Content Delivery Network,内容分发网络,将静态资源缓存到全球各地的服务器以加速访问)或公开存储桶的场景。

python
from openai import OpenAI

client = OpenAI()

def analyze_image_url(image_url: str, question: str) -> str:
    """通过 URL 传入图片,让 LLM 分析。"""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": image_url,
                            # detail 控制图像处理精度:
                            # "low"  - 固定 512x512,低 Token 消耗(约 85 tokens)
                            # "high" - 按原图分辨率处理,高精度(每 512x512 区块约 170 tokens)
                            # "auto" - 由模型自动选择(默认)
                            "detail": "high",
                        },
                    },
                    {"type": "text", "text": question},
                ],
            }
        ],
        max_tokens=1024,
    )
    return response.choices[0].message.content


# 示例:分析公网图表
result = analyze_image_url(
    image_url="https://example.com/sales_chart.png",
    question="这张图表显示了什么趋势?找出增长最快的季度。",
)
print(result)

base64 方式:图片在本地或私有存储中,无法通过 URL 访问。先读取文件转换为 base64 字符串,再传入 API。适合处理用户上传的文件、内网系统的截图等场景。

python
import base64
from pathlib import Path


def image_to_base64(image_path: str) -> tuple[str, str]:
    """
    将本地图片转换为 base64 字符串。
    返回 (base64_string, media_type) 元组。
    媒体类型从文件扩展名推断,LLM 需要此信息才能正确解码。
    """
    ext_to_type = {
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".png": "image/png",
        ".gif": "image/gif",
        ".webp": "image/webp",
    }
    path = Path(image_path)
    media_type = ext_to_type.get(path.suffix.lower(), "image/jpeg")
    image_data = base64.standard_b64encode(path.read_bytes()).decode("utf-8")
    return image_data, media_type


def analyze_local_image(image_path: str, question: str) -> str:
    """分析本地图片文件。"""
    image_data, media_type = image_to_base64(image_path)

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "image_url",
                        "image_url": {
                            # base64 格式:data:{media_type};base64,{data}
                            "url": f"data:{media_type};base64,{image_data}",
                            "detail": "high",
                        },
                    },
                    {"type": "text", "text": question},
                ],
            }
        ],
    )
    return response.choices[0].message.content

1.4 音频处理:Whisper + LLM

主流 LLM 的对话接口不直接接受音频文件(GPT-4o 的实时 API 例外)。通用做法是两步走:用 Whisper 将音频转录为文字,再将文字交给 LLM 处理。

python
from openai import OpenAI
from pathlib import Path

client = OpenAI()


def transcribe_audio(audio_path: str, language: str = "zh") -> str:
    """
    使用 Whisper 将音频转录为文字。
    支持格式:mp3, mp4, mpeg, mpga, m4a, wav, webm
    单文件最大 25MB。
    language 参数可以提升准确率,zh 表示中文。
    """
    with open(audio_path, "rb") as audio_file:
        transcript = client.audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
            language=language,
            # response_format="verbose_json" 可以获取时间戳信息
            response_format="text",
        )
    return transcript


def process_audio_query(audio_path: str, system_prompt: str = None) -> dict:
    """
    完整的音频问答流程:转录 → LLM 处理 → 返回答案。
    适用于语音助手、会议录音分析等场景。
    """
    # 第一步:转录
    transcription = transcribe_audio(audio_path)
    print(f"转录结果:{transcription}")

    # 第二步:LLM 处理
    messages = []
    if system_prompt:
        messages.append({"role": "system", "content": system_prompt})
    messages.append({"role": "user", "content": transcription})

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
    )

    return {
        "transcription": transcription,
        "response": response.choices[0].message.content,
    }


# 示例:处理语音问题
result = process_audio_query(
    audio_path="question.mp3",
    system_prompt="你是一个客服助手,根据用户的语音问题提供帮助。",
)

1.5 视频理解:抽帧 + 多图分析

主流 LLM 不直接接受视频文件(Gemini 除外)。处理视频的通用方案是:按固定间隔抽取关键帧,将多张图片一起传入 LLM,让模型综合理解视频内容。

python
import cv2
import base64
from pathlib import Path


def extract_frames(video_path: str, interval_seconds: float = 5.0, max_frames: int = 20) -> list[str]:
    """
    从视频中按时间间隔抽取帧,返回 base64 编码的图片列表。

    interval_seconds:抽帧间隔(秒)
    max_frames:最多抽取的帧数,防止 Token 超限
    注意:帧数越多,Token 消耗越大。每帧在 high 模式下约 1000-2000 tokens。
    """
    cap = cv2.VideoCapture(video_path)
    fps = cap.get(cv2.CAP_PROP_FPS)
    frame_interval = int(fps * interval_seconds)

    frames = []
    frame_count = 0

    while len(frames) < max_frames:
        cap.set(cv2.CAP_PROP_POS_FRAMES, frame_count)
        ret, frame = cap.read()
        if not ret:
            break

        # 将 OpenCV BGR 图像编码为 JPEG base64
        _, buffer = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 85])
        frame_b64 = base64.standard_b64encode(buffer).decode("utf-8")
        frames.append(frame_b64)

        frame_count += frame_interval

    cap.release()
    return frames


def analyze_video(video_path: str, question: str, interval_seconds: float = 5.0) -> str:
    """
    分析视频内容,将多帧图像一起传入 LLM。
    """
    frames = extract_frames(video_path, interval_seconds=interval_seconds)
    print(f"共抽取 {len(frames)} 帧")

    # 构造多图消息
    content = []
    for i, frame_b64 in enumerate(frames):
        content.append({
            "type": "text",
            "text": f"第 {i + 1} 帧(时间约 {i * interval_seconds:.0f}s):",
        })
        content.append({
            "type": "image_url",
            "image_url": {
                "url": f"data:image/jpeg;base64,{frame_b64}",
                "detail": "low",  # 视频帧多,用 low 降低 Token 消耗
            },
        })
    content.append({"type": "text", "text": question})

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": content}],
        max_tokens=2048,
    )
    return response.choices[0].message.content

1.6 多模态 Agent 处理流程

纯文本

图片文件

音频文件

视频文件

URL

是图片

不是图片

用户输入

输入类型检测

直接处理

图片预处理
调整尺寸/格式

Whisper 转录

抽帧
每N秒1帧

检查是否为图片URL

base64 编码
或直接传URL

转录文本

多帧 base64 列表

抓取网页文本

构造多模态消息

调用多模态 LLM
gpt-4o / claude-opus-4-6

需要工具调用?

执行工具
查询/计算/搜索

返回最终答案

1.7 完整示例:图表分析 Agent

以下是一个可以分析图表的数据分析 Agent,支持用户上传图表截图,Agent 自动识别图表类型、提取数据、给出分析结论。

python
import json
import base64
from pathlib import Path
from openai import OpenAI

client = OpenAI()


def extract_chart_data(image_source: str) -> dict:
    """
    从图表图片中提取结构化数据。
    image_source 可以是本地文件路径或 URL。
    """
    # 判断输入类型:URL 还是本地文件
    if image_source.startswith("http://") or image_source.startswith("https://"):
        image_content = {
            "type": "image_url",
            "image_url": {"url": image_source, "detail": "high"},
        }
    else:
        path = Path(image_source)
        if not path.exists():
            return {"success": False, "error": f"文件不存在:{image_source}"}
        image_data = base64.standard_b64encode(path.read_bytes()).decode("utf-8")
        ext = path.suffix.lower()
        media_type = {"png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg"}.get(ext, "image/png")
        image_content = {
            "type": "image_url",
            "image_url": {"url": f"data:{media_type};base64,{image_data}", "detail": "high"},
        }

    # 让 LLM 提取结构化数据
    extraction_prompt = """请仔细分析这张图表,提取以下信息:
1. 图表类型(折线图、柱状图、饼图、散点图等)
2. 标题(如果有)
3. X 轴标签和单位
4. Y 轴标签和单位
5. 所有数据系列的名称和数值

请以 JSON 格式返回,格式如下:
{
  "chart_type": "图表类型",
  "title": "图表标题",
  "x_axis": {"label": "标签", "unit": "单位"},
  "y_axis": {"label": "标签", "unit": "单位"},
  "data_series": [
    {"name": "系列名", "data": [{"x": "...", "y": ...}]}
  ]
}
如果某个字段无法识别,设为 null。"""

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "user",
                "content": [
                    image_content,
                    {"type": "text", "text": extraction_prompt},
                ],
            }
        ],
        response_format={"type": "json_object"},  # 强制 JSON 输出,避免格式混乱
        max_tokens=2048,
        temperature=0,
    )

    try:
        data = json.loads(response.choices[0].message.content)
        return {"success": True, "data": data}
    except json.JSONDecodeError:
        return {"success": False, "error": "JSON 解析失败", "raw": response.choices[0].message.content}


def analyze_chart_trends(chart_data: dict) -> str:
    """
    基于提取的图表数据进行趋势分析。
    将结构化数据转换为分析文字。
    """
    if not chart_data.get("success"):
        return f"数据提取失败:{chart_data.get('error')}"

    data = chart_data["data"]
    analysis_prompt = f"""基于以下图表数据,提供专业的数据分析报告:

{json.dumps(data, ensure_ascii=False, indent=2)}

请分析:
1. 主要趋势(上升/下降/波动)
2. 最高点和最低点
3. 显著的变化节点
4. 数据背后可能的原因和业务含义
5. 对未来走势的预判

请用简洁专业的语言表达,约 200-300 字。"""

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": analysis_prompt}],
        temperature=0.3,
    )
    return response.choices[0].message.content


class ChartAnalysisAgent:
    """
    图表分析 Agent。
    支持用户持续上传图表并提问,维护多轮对话历史。
    """

    def __init__(self):
        self.conversation_history = []
        self.analyzed_charts = []  # 已分析的图表数据缓存

    def analyze(self, image_source: str, question: str = None) -> str:
        """
        分析图表并回答用户问题。
        如果没有具体问题,自动给出全面分析。
        """
        print(f"正在分析图表:{image_source}")

        # 步骤一:提取图表数据
        chart_data = extract_chart_data(image_source)
        if not chart_data["success"]:
            return f"图表识别失败:{chart_data['error']}"

        self.analyzed_charts.append(chart_data["data"])

        # 步骤二:根据问题类型选择分析策略
        if question is None:
            # 无具体问题:给出全面分析
            result = analyze_chart_trends(chart_data)
        else:
            # 有具体问题:带上图表数据和问题一起分析
            self.conversation_history.append({
                "role": "user",
                "content": [
                    self._build_image_content(image_source),
                    {
                        "type": "text",
                        "text": f"图表数据:\n{json.dumps(chart_data['data'], ensure_ascii=False)}\n\n问题:{question}",
                    },
                ],
            })

            response = client.chat.completions.create(
                model="gpt-4o",
                messages=[
                    {
                        "role": "system",
                        "content": "你是一个专业的数据分析师,擅长从图表中提取洞察。用简洁专业的语言回答问题。",
                    }
                ] + self.conversation_history,
                max_tokens=1024,
            )
            result = response.choices[0].message.content
            self.conversation_history.append({
                "role": "assistant",
                "content": result,
            })

        return result

    def _build_image_content(self, image_source: str) -> dict:
        """构造图片输入内容。"""
        if image_source.startswith("http"):
            return {"type": "image_url", "image_url": {"url": image_source, "detail": "high"}}
        image_data = base64.standard_b64encode(Path(image_source).read_bytes()).decode("utf-8")
        return {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_data}", "detail": "high"}}

    def compare_charts(self, question: str) -> str:
        """
        基于已分析的多个图表进行比较分析。
        需要先分析至少两张图表。
        """
        if len(self.analyzed_charts) < 2:
            return "需要至少两张图表才能进行比较分析"

        prompt = f"""请比较以下 {len(self.analyzed_charts)} 张图表:

{json.dumps(self.analyzed_charts, ensure_ascii=False, indent=2)}

用户问题:{question}"""

        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
        )
        return response.choices[0].message.content


# 使用示例
if __name__ == "__main__":
    agent = ChartAnalysisAgent()

    # 分析单张图表
    analysis = agent.analyze(
        image_source="sales_chart.png",
        question="2024 年哪个季度的增长最显著?",
    )
    print("分析结果:", analysis)

    # 继续追问(多轮对话)
    followup = agent.analyze(
        image_source="sales_chart.png",
        question="与去年同期相比,整体表现如何?",
    )
    print("追问回答:", followup)

1.8 限制与注意事项

Token 消耗:图像会消耗大量 Token。一张 1024x1024 的图片在 high 模式下约消耗 1000+ tokens,是纯文字请求的数倍。批量处理图片时需要估算成本。

分辨率限制:各模型对最大图片尺寸有限制,通常为 2048x2048 或 4096x4096。超过限制的图片会被自动压缩,可能影响细节识别精度(如图表中的小字、精细数字)。

识别精度:LLM 的图像理解不是 OCR,复杂图表(多数据系列、密集文字、特殊图表类型)的识别精度会下降。精度要求高的场景建议结合专用 OCR 工具(如 Azure Document Intelligence)做二次验证。

隐私安全:图片传输到第三方 LLM API 时需考虑数据合规问题。包含敏感信息(人脸、证件、财务报告)的图片,使用前需评估数据安全风险。本地部署的多模态模型(如 Qwen-VL、LLaVA)是数据不出域的替代方案。


小结: 多模态 Agent 的核心在于将不同类型的输入(图片、音频、视频)统一转换为 LLM 可理解的格式,再利用 LLM 的理解能力完成分析和推理。图像通过 URL 或 base64 直接传入,音频经 Whisper 转录后处理,视频通过抽帧分解为多图。随着多模态模型能力的持续提升,Agent 处理非结构化媒体数据的准确率还在快速提高,这是一个值得持续关注的方向。

本页目录