做多模态 AI Agent,我差点被"接线"逼到退坑

12 阅读6分钟

不是 Agent 不行,是你在替 AI 打工。


先说说我当时有多想放弃

为了做一个"自动写文案 + 配图 + 发推"的小 Agent,我真的动过退意。

不是因为 AI 能力不够,也不是架构想不清楚——是因为每次以为快跑通了,就会冒出新的 API 兼容性问题、新的轮询超时、新的莫名 credits 飞掉。你花在"接线、对账、重试兜底"上的时间,比花在"让 Agent 真正工作"上的时间多得多。

后来我把媒体侧统一了,才终于把精力还给了正经事。这篇文章就是把这段踩坑过程复盘一遍,给同样在做多模态 Agent 的人参考。


一、原本的设计挺清晰的,然后就开始地狱模式

最初的架构思路很简单,两层:

  • 文本/规划层: 用熟悉的 LLM(OpenAI / Anthropic 等),负责文案生成、内容规划
  • 媒体生成层: 挑几个效果好、价格合适的图片/视频模型跑素材

听起来合理,实际开始接 API 之后:

SDK 各写各的,没有统一规范,每家模型都要单独适配一套初始化逻辑。

参数命名各叫各的,同样是"生成时长"这个概念,有的叫 duration,有的叫 length,有的是嵌在 config 里的子字段。

最骚的是"长任务"处理方式不统一——有的是同步等结果,有的是异步给你一个 job_id 让你自己轮询,有的支持 callback,有的不支持。

你本来是在写 Agent 业务逻辑,最后变成了**"多平台媒体 API 适配工程师"**,还是那种随时会因为某家 API 改动就要重新返工的版本。


二、那个 429 / 超时 / 失败重试,真能把人写成 try...except 仙人

多模态任务最恶心的不是"失败"本身,是失败之后你要处理的一整条链路后果。

轮询频率的坑: 异步任务需要你主动去查状态,但轮询太频繁就触发限流。有些平台文档直接写明"建议轮询间隔 15–30 秒,生产环境推荐用 callback",但你第一次集成根本不会注意到这句话,然后就被 rate limiting 打回来了。

重试策略的坑: 某步失败了,要不要重试?重试几次?要不要换模型降级?每个问题都需要你主动做决策,没人帮你兜底。

最恐怖的现象: 你没把"任务状态"做清楚,Agent 就会进入一种诡异的状态——用户界面上显示"正在生成",你的 credits 余额在悄悄掉血,但你根本不知道它在重试哪一步、已经跑了多少钱。

# 你以为你在写 Agent 逻辑
async def generate_content(prompt):
    try:
        result = await model_a.generate(prompt)
        return result
    except RateLimitError:
        await asyncio.sleep(30)
        try:
            result = await model_a.generate(prompt)  # 重试一次
            return result
        except Exception:
            try:
                result = await model_b.generate(prompt)  # 换模型
                return result
            except Exception as e:
                # 此时你已经被扣了三次钱
                raise AgentFailed(f"全崩了: {e}")

上面这段不是夸张,是我真实写过的东西。它能跑,但它不可维护、不可复盘、不可控制成本。


三、月底对账才发现:钱不是花在"生成",是花在"反复生成"

我踩过最蠢的一次成本事故:

为了选出"最好看的 1 张图",我让 Agent 同时对撞了 3 个模型。每个模型因为超时或失败又各自触发了 2–3 轮重试。最终只用了一张图,但成本已经是正常的将近 10 倍。

更隐蔽的坑: 大多数平台生成的 media_url 不是永久的。很多平台会在 14 天左右清理任务结果,如果你没有及时把素材下载落盘,过两周回头想复盘"哪个模型风格效果更好"——文件直接没了,连对比的机会都没有。

没有统一的任务状态和费用日志,你基本上是在靠猜来"优化"你的 Agent。


四、后来怎么改:媒体侧统一托管,脑子和工厂分开

悟了之后,我做了一个很明确的切分:

  • 文本/规划: 继续用习惯的 LLM,这一侧不动
  • 图片/视频/音频生成: 统一交给 crun.ai 的任务接口跑

选择把媒体侧统一托管,不是因为"它的模型质量最好",而是因为它解决的是工程问题,刚好是我最头疼的三件事:

1. 统一任务模型:CreateTask → TaskInfo

所有模型的调用方式统一成一套接口,不再需要为每家模型维护单独的 SDK 适配层。TaskInfo 对 Models 类下所有模型提供统一查询接口,任务状态追踪是一致的。

# 以前:每个模型一套写法
flux_result = flux_client.images.generate(prompt=prompt, ...)
kling_task = kling_client.create_video_task(input={"prompt": prompt}, ...)
veo_job = veo_client.submit(content=prompt, ...)

# 现在:统一接口
task = crun.create_task(model="flux-1.1-pro", prompt=prompt)
status = crun.get_task(task_id=task.id)

2. 异步处理终于像个人

状态清楚、结果清楚、callback 机制完整,文档里甚至直接写明"别疯狂轮询,用 callback 能省成本降延迟"。这种"把工程建议写进文档"的习惯,是我觉得靠谱的信号。

3. 用量可对账

Pricing / Usage / Logs 是给开发者用的,不是摆设。按每次生成消耗多少 credits、折算多少钱,列得很细,还有模型间的价格对比。

这正是做 Agent 最需要的数据:我到底在哪个场景烧钱、哪条链路重试最多、哪个模型性价比最高。


五、分层之后,才能把精力花回到正经事

媒体侧统一之后,整个 Agent 架构变得清晰很多:

LLM(脑子层)
├── 内容规划
├── 文案生成
└── 发布包蓝图输出(prompt / 风格描述 / 禁用项)
        ↓
媒体生成服务(工厂层)
├── 并发跑 2–3 个候选素材
├── 统一任务状态管理
├── 超时 / 失败自动处理
└── 结果落盘归档
        ↓
人工点选 → 发布

脑子干脑子的事,工厂干工厂的事。你终于可以把时间花在真正的 Agent 逻辑上:它会不会规划内容结构?会不会根据平台风格调整语气?会不会把素材打包成可发布的结果?

而不是继续当那个替 AI 接线、重试、对账的人。


写在最后

我最大的感受是:多模态 Agent 失控的根源,往往不是 AI 能力不够,而是工程层没有边界。

文本侧和媒体侧职责不分、任务状态不透明、成本不可追踪——这三个问题不解决,Agent 永远是一个跑起来让你提心吊胆、跑完让你看不懂账单的黑盒。

分清"脑子"和"工厂",比追求一步到位的全自动,要实用得多。


如果你也在做多模态 Agent,欢迎评论区聊聊你遇到的坑,互相避雷。