在过去的一年里,随着大模型在多模态方面的能力越来越强,从文字生成图片到理解图文的门槛不断降低,多模态 Agent 的构建也逐渐变得更加日常化。
比如今年大火的 Manus、GenSpark 这样的通用 Agent,或者 Lovart、MewDesign 这类专注于设计领域的多模态 Agent,都让多模态技术更容易被产品经理和开发者所接触。
那么,当我们把这些图片和文件传递给大模型时,它究竟是如何"看"懂这些多模态信息的呢?
其实,这里的原理并不是像我们平时直接在聊天记录里贴一张图片那么简单。
大模型有专门的多模态输入通道,也就是说,在技术实现上,我们并不是把图片本身直接塞进文字消息里,而是通过一个额外的输入通道来告诉模型:"这里有一张图片"或者"这里有一个文件"。
也就是说,模型通过多模态通道来理解图片内容,并不依赖于文字上下文里的图片数据。历史消息里只需要让模型知道"有图片存在"就行,不需要反复携带图片本体。
但问题是,大多数人还在用 Base64 或者 URL 来传递图片。
年初我在设计 MewDesign 的时候发现:同样一个 10 轮对话的任务,用 URL 传递图片的 Token 消耗,是理想方案的 4-5 倍。如果用 Base64,差距更夸张——能到 20 倍以上。
更可怕的是性能:模型生成一个即梦4的签名 URL 需要 5-8 秒,而且容易出错。
那么问题来了:为什么还有那么多人在用 Base64 和 URL?
01 - 我在构建 MewDesign 时踩的三个坑
MewDesign 是我年初做的一个设计领域多模态 Agent,需要频繁调用即梦4、nano-banana、gpt-image-1 等多个文生图模型。
用户上传一张图片,Agent 需要理解它,然后调用这些模型进行编辑、生成、优化……整个流程可能需要 10 轮以上的对话。
最早的时候,我也只是像以前构建普通的 Agent 一样,选择了最"常规"的方案: 直接把图片通过 R2 的签名 URL 来传递,即使是即梦等生成的图片,也是直接(签名)URL 带回。
然后,我就踩坑了。
坑一:上下文爆炸,账单也爆炸
我拿真实场景来举个例子。
假设用户生成了一张图片,Agent 需要和模型对话 10 轮来完成优化或者多轮设计。每轮对话,历史消息都会被带回去——包括之前所有对话中的图片引用。
这就意味着:
URL 方案:一个带签名的 R2 URL 通常有 300-500 个字符(包含 token、bucket、路径、时间戳、签名参数等)。
而且每轮对话,system prompt、历史消息中的所有 URL 都会重复传递。
我实际测下来,10 轮对话的 Token 消耗,比理想方案多了 4-5 倍。
Base64 方案:更可怕。一张 500KB 的图片,Base64 编码后大约是 700KB,对应几千个 Token。
每轮对话都要把这几千个 Token 带回去。
10 轮对话下来,Token 消耗能到理想方案的 20 倍以上。
而且,这些历史消息里的 Base64 或 URL,模型根本用不到。
因为多模态输入是独立字段,跟文本上下文毫无关系。
换句话说,这些 Token 既没价值,又要钱,还影响上下文。
坑二:生成 URL 又慢又容易出错
更麻烦的是性能问题。
当 Agent 需要调用工具(比如即梦4的图片编辑接口)时,它需要把图片传递给工具。如果用 URL 方案,模型就得生成一个完整的签名 URL。
这个过程是这样的:
- 模型需要一个 Token 一个 Token 地"吐"字符
- 一个 400 字符的 URL,按照 50-100 Token/s 的生成速度,需要 4-8 秒
- 一旦错一个字符,整个 URL 就失效了
- 必须重试(再花 4-8 秒)
我在实际测试中发现,即梦4 的签名 URL 尤其长(因为包含了大量签名参数),生成一次经常要 5-8 秒。
而且,大模型在生成长字符串时,错误率非常高——可能是某个字符多了一个空格,或者某个参数拼写错了,结果就是整个调用失败。
这根本不应该让大模型去做,它也不擅长。
坑三:即梦4的 URL 会过期,24 小时后全废
还有一个更隐蔽的问题:安全性。
即梦4 生成的图片 URL,有 24 小时的时效性。
也就是说,如果用户今天生成了一张图片,明天再打开这个 Agent 对话,历史消息里的 URL 已经失效了,模型看到也没用。
而且,签名 URL 通常包含:
- token
- bucket
- 路径
- 时间戳
- 验证签名参数
一旦上下文泄露或日志未处理干净,风险极高。
Base64 更糟糕,一长串乱码,人根本看不懂,审核和追踪都麻烦。
那怎么办?
我找到了一个更优雅的方案。
02 - Key 映射——把图片的"身份"和"本体"分离
那么,什么是 Key 映射?
整体架构非常简单:
也就是说:
- Agent 层不会有任何 URL,也不需要携带 Base64
- 上下文里只传递 key
- 图片处理发生在工具或系统层,而不是模型层
这样做的核心思想是:把图片的"身份"和"本体"分离。
历史消息里只需要记录"这是一张图片,它的 key 是 img_001",而不需要关心它的具体内容、存储位置、访问方式。
当某个工具真正需要处理这张图片时,它根据 key 去存储系统里取回真实图片即可。
而且,Key 还可以语义化命名,对大模型理解更加友好。
比如:
asset_logo_001→ 这是用户上传的 logo 素材doubao_gen_output_v2→ 这是豆包第二版生成结果edited_final→ 最终编辑版本
这样的 key,大模型一看就明白,都不用在 prompt 里面再额外说明了,不容易搞混,也不容易生成错误。
改完之后,效果有多明显?
三、改造后的真实数据:成本降 50%,速度快 5 倍
成本对比
场景:用户上传一张图片,Agent 需要对话 10 轮
改造前(URL 方案):
- 带签名的 URL 在每轮对话的历史消息中重复传递
- 加上 system prompt 的重复
- Token 消耗是 Key 方案的 2倍
改造后(Key 方案):
- 只传递短短几个字符的 key
- 历史消息再长,图片部分的 Token 几乎可以忽略
- Token 消耗降低 50-70%
如果用 Base64,差距会更大——Key 方案可以节省 95% 以上的 Token。
而且,这还只是单次对话的成本。如果你的系统每天处理几千张图片,这个差距是指数级的。
性能对比
改造前:端到端响应时间平均 15 秒 改造后:端到端响应时间平均 3 秒 提升:5 倍
原因很简单:模型不再需要花 5-8 秒生成长 URL,也不会因为字符错误而重试。输出全是正常文本,这才是大模型擅长的事。
稳定性对比
改造前:URL 生成错误率约 10%,需要重试 改造后:Key 生成错误率 0%
安全性对比
改造前:
- URL 包含敏感信息(token、bucket、签名参数)
- 24 小时后自动失效
- Base64 不可读,难以审计
改造后:
- 历史消息只有抽象 key(如
img_001) - key 生存周期可控
- 工具链根据权限决定 key 是否可访问
- 不存在过期问题
而且,Key 方案还有一个意外收获。
四、跨工具协同的意外收获
用户上传一张图片,需要经过:
传统方案:每个工具都要重新上传/传递图片,或者生成新的 URL。
Key 方案:整个流程只需要传递 key,图片只上传一次。
Key 方案天然形成"统一图片引用协议",让多模态 Agent 像玩乐高一样自由拼接。
现在很多团队在用 MCP 来连接多模态模型、工具、Workflow Builder,Key 方案在这种场景下优势尤其明显。
那怎么落地?
五、三步改造,立即见效
步骤一:建立图片存储 + 生成 key
每张图片上传后生成:
{
"image_key": "img_001",
"file_path": "s3://bucket/images/...",
"metadata": {
"upload_time": "2024-02-18T10:00:00Z",
"user_id": "user_123",
"format": "png",
"size": 500000
}
}
步骤二:模型调用统一用 key
历史记录可以这样:
清爽、可读、可追踪。
而且在前端展示的时候,可以自己定义规则,提取 key 再换成真实的图片进行渲染。
比如像 MewDesign 框出来的这个,就是根据 key 渲染出来的,而不是根据 URL。
步骤三:工具根据 key 自行取图
每个工具只需要做一件事:
function resolveImage(key) {
// 根据 key 从存储系统取回图片
return storageSystem.get(key);
}
既能复用缓存,又能做权限控制。
建议:用语义化 key
建议使用有意义的 key,而不是随机字符串。
比如:
user_upload_001→ 用户上传的第一张图片gen_v1_jimeng→ 即梦生成的第一版edited_final→ 最终编辑版本
这样的 key,人和模型都容易理解。
六、总结
多模态 Agent 的正确架构是:
模型负责推理,工具负责图片处理,上下文只记录引用。
这样做的结果是:
✔ 成本:节省 20-100 倍
✔ 性能:速度提升 5-10 倍
✔ 安全:无 URL、无 Base64、无过期问题
✔ 代码更好维护:清爽可读
✔ 跨工具协作:自然兼容 MCP 与多模态生态
如果你正在构建多模态 Agent、AIGC 产品、工作流系统,赶紧试试这个新思路吧!