源文件:chapter3/multimodal-agent/README.md 支持多种抽取技术的多模态 Agent 一个用于教学的 Agent 框架,在多个 AI 提供商(Gemini、OpenAI、豆包)之间对比不同的多模态内容抽取技术。 特性 三种抽取模式 原生多模态:直接使用模型自带的多模态能力进行处理 Gemini 2.5 Pro:文档(PDF)、图像、音频 GPT-5/GPT-4o:图像(OpenAI 多模态格式) 豆包 1.6:图像(OpenAI 多模态格式) 先抽取为文本:先把多模态内容转为文本,再处理 PDF:使用 Gemini 或 GPT-5 做 OCR 图像:使用 GPT-5 或豆包 1.
源文件:chapter3/multimodal-agent/README.md
一个用于教学的 Agent 框架,在多个 AI 提供商(Gemini、OpenAI、豆包)之间对比不同的多模态内容抽取技术。
原生多模态:直接使用模型自带的多模态能力进行处理
先抽取为文本:先把多模态内容转为文本,再处理
多模态分析工具:用于追问的附加功能
Agent 采用模块化架构,职责清晰分离:
MultimodalAgent ├── 配置 (config.py) │ ├── 模型配置 │ ├── API 密钥管理 │ └── 抽取模式设置 ├── Agent 核心 (agent.py) │ ├── 消息处理(OpenAI 格式) │ ├── 对话历史 │ ├── 按模式处理 │ └── 流式响应 └── 多模态工具 ├── 图像分析 ├── 音频分析 └── PDF 分析
cd chapter3/multimodal-agent
pip install -r requirements.txt
cp env.example .env # 编辑 .env,填入你的 API 密钥
export $(cat .env | xargs)
生成一份打包好的多模态样本——一份带图表的报告——让你能端到端跑通
实验 3-7。精确的季度数字只出现在图表的柱子里,正文文本中没有,
这正是让三种范式取舍变得可度量的关键。
# 离线:生成 test_files/sample_chart.png 和 test_files/sample_report.pdf python create_sample.py # 或:python demo.py --generate-sample
然后针对同一份文件和同一问题对比三种抽取范式
(需要视觉 API 密钥,如 OpenAI 或 Gemini):
python demo.py \ --file test_files/sample_chart.png \ --query "Which quarter had the highest revenue, and what was the exact value?" \ --model gpt-5.6-luna
所有 CLI 都提供中文 --help(python demo.py --help、python main.py --help、python create_sample.py --help)。
启动一个与 Agent 的交互式会话:
python main.py --interactive
交互模式下的可用命令:
/file <path> - 加载一个多模态文件/mode <native|extract_to_text> - 切换抽取模式/model <model_name> - 切换模型/tools <on|off> - 启用 / 禁用多模态工具/history - 显示对话历史/clear - 清空对话历史/quit - 退出用指定查询处理一个文件:
# 原生模式(默认) python main.py --file document.pdf --query "What is the main topic?" # 先抽取为文本模式 python main.py --mode extract_to_text --file image.jpg --query "Describe this image" # 启用多模态工具 python main.py --tools --mode extract_to_text --file audio.mp3 --query "What's the content?"
import asyncio from agent import MultimodalAgent, MultimodalContent from config import ExtractionMode async def example(): # 初始化 Agent agent = MultimodalAgent( model="gemini-3.5-flash", mode=ExtractionMode.NATIVE, enable_tools=True ) # 处理一份 PDF content = MultimodalContent( type="pdf", path="document.pdf" ) result = await agent.process_multimodal_content( content, "Summarize this document" ) print(result) # 流式对话 async for chunk in agent.chat("Tell me more about the key points", stream=True): print(chunk, end="", flush=True) asyncio.run(example())
运行完整的对比演示:
# 针对特定文件对比抽取模式(flag 形式) python demo.py --file document.pdf --query "What are the key findings?" --model gpt-5.6-luna # 向后兼容的位置参数形式仍然可用 python demo.py document.pdf "What are the key findings?" # 保存完整对话记录,并跳过跨模型对比 python demo.py --file test_files/sample_chart.png \ --query "Which quarter had the highest revenue?" \ --model gpt-5.6-luna --skip-model-comparison --output result.txt # 这会运行: # 1. 原生多模态模式 # 2. 先抽取为文本模式 # 3. 带工具的先抽取为文本模式 # 4. 跨不同模型对比(除非指定 --skip-model-comparison)
演示 CLI 参数:
| 参数 | 说明 |
|---|---|
--file / 位置参数 file |
要处理的多模态文件(图像 / PDF / 音频) |
--query / 位置参数 query |
针对该文件要提问的问题 |
--model |
用于原生 / 抽取模式的模型(默认:gemini-3.5-flash) |
--skip-model-comparison |
只运行三种范式的对比 |
--generate-sample |
离线:生成打包的图表样本后退出 |
--output, -o |
同时把完整对话记录写入文件 |
| 模式 | 优点 | 缺点 | 最适用于 |
|---|---|---|---|
| 原生 | - 保留完整上下文 - 视觉理解更好 - 直接处理 |
- 支持的模型有限 - token 消耗更高 |
内容混合的复杂文档 |
| 先抽取为文本 | - 适配所有模型 - token 消耗更低 - 可缓存抽取结果 |
- 丢失视觉上下文 - 两步流程 |
文本为主的文档、成本优化 |
| 带工具 | - 兼具两者优点 - 支持追问 - 选择性深入分析 |
- 配置更复杂 - 多次 API 调用 |
交互式会话、详细问答 |
| 模型 | 原生 PDF | 原生图像 | 原生音频 | 先抽取为文本 | 工具支持 |
|---|---|---|---|---|---|
| Gemini 2.5 Pro | 支持 | 支持 | 支持 | 支持 | 支持 |
| GPT-5/GPT-4o | 不支持 | 支持 | 不支持 | 支持 | 支持 |
| 豆包 1.6 | 不支持 | 支持 | 不支持 | 支持 | 支持 |
Google Gemini:PDF / 音频原生处理必需
GOOGLE_API_KEY(或 GEMINI_API_KEY——两者都会读取)OpenAI:GPT 系列模型和 Whisper 必需
OPENAI_API_KEY豆包:豆包模型必需
DOUBAO_API_KEY(或 ARK_API_KEY——两者都会读取)运行测试套件:
python test_multimodal.py
# 用原生模式获得最佳理解 agent = MultimodalAgent( model="gemini-3.5-flash", mode=ExtractionMode.NATIVE ) content = MultimodalContent(type="pdf", path="research_paper.pdf") summary = await agent.process_multimodal_content( content, "What are the main contributions and findings?" )
# 先抽取为文本并启用工具,以便追问 agent = MultimodalAgent( model="gpt-5.6-luna", mode=ExtractionMode.EXTRACT_TO_TEXT, enable_tools=True ) # 初次处理 await agent.chat("images/I have an image at photo.webp", MultimodalContent(type="image", path="photo.jpg")) # 用工具追问 await agent.chat("What objects are in the background?") await agent.chat("What's the color scheme?")
# 使用 Whisper 进行转写 agent = MultimodalAgent( model="gpt-5.6-luna", mode=ExtractionMode.EXTRACT_TO_TEXT ) content = MultimodalContent(type="audio", path="interview.mp3") transcript = await agent._extract_audio_to_text(content) print(f"Transcript: {transcript}") # 分析转写文本 analysis = await agent._answer_with_context( transcript, "What are the key points discussed?" )
模式选择:
模型选择:
性能优化:
错误处理:
API 密钥错误:
文件处理错误:
模型兼容性:
Agent 使用 OpenAI 兼容的消息格式:
{ "role": "user" | "assistant" | "system" | "tool", "content": "message text" | [{"type": "text", "text": "..."}, ...], "tool_calls": [...], # 可选 "tool_call_id": "...", # 用于工具响应 }
工具遵循 OpenAI 函数调用规范:
{ "type": "function", "function": { "name": "analyze_image", "description": "...", "parameters": { "type": "object", "properties": {...}, "required": [...] } } }
Agent 支持流式响应以改善体验:
这是一个展示多模态 AI 能力的教学项目。贡献可聚焦于:
MIT License - 详情见 LICENSE 文件
This experiment now supports a universal OpenRouter fallback for its chat LLM.
MOONSHOT_API_KEY / KIMI_API_KEY / OPENAI_API_KEY / DOUBAO_API_KEY …) is present, behavior is unchanged.OPENROUTER_API_KEY is set, the chat LLM is automatically routed through OpenRouter (https://openrouter.ai/api/v1). Model names are mapped automatically: gpt-*/o1-* → openai/…, claude-* → anthropic/claude-opus-4.8, ids already containing / are kept as-is, and other provider-native ids (e.g. kimi-k3, doubao-*) fall back to openai/gpt-5.6-luna. Set OPENROUTER_MODEL to force a specific OpenRouter model id.Add OPENROUTER_API_KEY=... to your .env (see env.example) to enable it.
Note: image analysis and text chat route through OpenRouter (vision-capable default
openai/gpt-5.6-luna). Audio transcription (Whisper) and native-PDF extraction still require a direct OpenAI/Gemini key.