它的核心不是“做视频”,是“怎么编排 52 个工具做复杂流程”
前阵子系统里有个痛点:订单处理流程改得太频繁了。产品今天说要加个 VIP 校验,明天说要把风控提前,每次都得改代码、测接口、重新发布。烦是真的烦。
周末刷 GitHub 的时候,看到一个叫 OpenMontage 的项目,8600+ star。简介说是“AI 视频制作系统”,Python 写的。我第一反应是“跟我有什么关系”,但鬼使神差点进去看了它的架构文档,结果一口气读完了,还顺手翻了不少源码。
它确实是个视频生成工具,但更准确地说,它是一套 Agent 编排 52 个工具来完成复杂流程 的框架。视频只是它的业务场景,真正值钱的是它怎么组织步骤、怎么选工具、怎么保证每一步的输出质量。
我今天不聊视频,就聊聊我从它身上学到的三个设计思想,以及怎么用 Java 落地。
一、把流程写在 YAML 里,而不是写在代码里
OpenMontage 把一个视频生成过程拆成 12 个 stage:研究、写脚本、生成图片、配音、合成……每个 stage 用哪个工具、依赖什么规则文件,全写在 YAML 配置文件里。
比如它大概长这样(我简化过):
pipeline: name: video_generation stages: - research: tool: web_search - script: tool: llm_writer skill: script_writing.md - compose: tool: video_compiler quality_gates: [duration_check, resolution_check]
代码里只有一个通用的执行引擎,负责按顺序跑每个 stage,至于跑什么、怎么跑,全看 YAML 怎么配。
这对我最大的启发是:我们是不是也可以把订单处理、数据清洗这类长流程,从 Java 代码里抽出来?
我之前写的订单处理代码,大概是这样的:
public void processOrder(Order order) { validate(order); checkInventory(order); calculatePrice(order); riskCheck(order); pay(order); notify(order);}
看似清晰,但一旦要调整顺序或插入新步骤,就得改这个方法,重新打包部署。要是把流程定义移到 YAML 里,执行引擎不变,改流程只需要改配置文件,连重启都不需要(配合动态加载)。
在 Java 里实现这个并不复杂,用 Spring 的 ApplicationContext 获取对应 tool 的 bean,按 YAML 定义的顺序执行就行。核心逻辑就几十行:
for (Stage stage : pipeline.getStages()) { BaseTool tool = applicationContext.getBean(stage.getToolName(), BaseTool.class); ToolResult result = tool.execute(context); // 质量关卡校验 if (!passesQualityGates(result, stage.getQualityGates())) { throw new PipelineException("Stage failed: " + stage.getName()); } // 保存结果,传给下一个 stage}
现在产品再提“加个 VIP 校验”,我只需要在 YAML 里插一行,提交配置,应用自动刷新,完事。
二、不写 if-else 选工具,让“评分”来决策
第二个让我拍大腿的设计,是它的 Provider Selector。
OpenMontage 要调用视频生成、图像生成、语音合成等外部服务,每个类型都有十几个提供商可选。它没有在代码里写 if (provider == "openai") 这种硬逻辑,而是给每个工具打了一组评分维度:
任务匹配度(30%)
输出质量(20%)
可靠性(15%)
成本效率(10%)
延迟(5%)
每次执行时,根据当前任务的侧重(比如高峰期更看重可靠性,闲时更看重成本),动态计算每个工具的加权得分,选最高的那个。
这恰好解决了我们一直头疼的“多供应商切换”问题。
我们系统里短信发送也是三个渠道:阿里云(便宜但时不时限流)、AWS(贵但稳)、腾讯云(居中)。以前代码里到处都是 if (channel == "aliyun"),要切换权重还得改配置,非常笨。
受 OpenMontage 启发,我重构了一个短信发送选择器:
@Servicepublic class SmsSelector { public SmsTool select(SmsContext ctx) { // 根据时段调整权重 Map<String, Double> weights = ctx.isPeak() ? Map.of("reliability", 0.4, "latency", 0.3, "cost", 0.2) : Map.of("cost", 0.4, "reliability", 0.3, "latency", 0.2); return allTools.stream() .map(t -> new Score(t, computeScore(t, weights))) .max(Comparator.comparing(Score::getValue)) .map(Score::getTool) .orElse(aliyunTool); // fallback }}
这样一来,新增一个短信渠道,只要写一个新的 @Component 类实现 SmsTool 接口,选择器自动把它加入候选列表,完全不需要修改选择逻辑。而且每次选择都有评分记录,出了问题可以直接翻日志看“为什么选了 AWS,因为可靠性得分最高”。
三、每一个决策都要有“日志”,不通过的坚决不交付
第三个让我印象深刻的是 Quality Gate 和 Decision Trail。
OpenMontage 每个 stage 执行完,必须先过质量关卡——比如检查生成的视频时长对不对、分辨率是否达标——不通过就报错,不会把残次品传到下一步。同时,每一步的选择理由、评分细节、执行时长、成本,全都被记录成结构化的日志,随时可查。
这在我们日常开发里太容易被忽略了。我们习惯用 try-catch 包一下,失败了打个 error 日志就算了,但很少有人去记录“为什么走这条路,为什么选这个参数”。
后来我在数据同步管道里借鉴了这套做法。我们的同步流程有 5 个 stage,每个 stage 执行后把结果(包括输入快照、输出摘要、耗时)存到一张 checkpoint 表里。如果某个 stage 挂了,修复问题后重新跑,自动跳过前面已经成功的 stage,直接从失败点继续。
这其实就是个简易版的 Saga 模式,但加上 Decision Trail 之后,出了故障能直接定位到“第 3 个 stage 因为成本超预算被选择器跳过了”,而不是对着日志瞎猜。
当然,踩坑也是有的
说几点实际用起来不太舒服的地方:
第一,YAML 和 Markdown 文件多了之后,管理是个大问题。OpenMontage 自己有 500 多个 Markdown 规则文件。我哪怕只定义了 10 个 stage,配了 20 个 tool,配置文件也快上百行了。后来我加了自动校验的单元测试,每次提交检查所有 YAML 语法、引用的 tool 是否存在,不然改配置比改代码还容易出错。
第二,调试长流程真的很痛苦。第 8 个 stage 挂了,前面的日志被冲掉大半。后来我加了个“调试模式”,可以在指定 stage 前暂停,打印当前上下文,甚至允许人工修改输入后再继续。虽然有点麻烦,但比反复跑全流程省事多了。
第三,不要什么都参数化。我一开始兴奋过头,把业务规则都塞到 Markdown 里,结果配置变成了“Lisp 风格”,没人看得懂。后来收敛了一下,只把经常变的步骤和规则外置,核心逻辑还是留在代码里。适度抽象,别过度设计。
说回 Java 开发者的视角
OpenMontage 如果是一个 Java 项目,可能很多人会说“这不就是个 Spring Batch + 自定义编排吗”。但恰恰因为它用了 Python + Agent 的组合,它逼着你跳出框架思维,重新思考几个本质问题:
这些问题跟语言无关。OpenMontage 给出的答案,Java 完全可以照搬,而且 Spring 生态里很多现成的组件(比如 ApplicationContext、@Conditional、Spring Batch)能让我们实现得更顺手。
如果你也遇到过流程频繁变更、多供应商切换混乱、长流程故障恢复困难这类问题,我真心建议花半小时看看 OpenMontage 的架构文档,不用管视频那一层,只看它怎么组织 Agent 和工具。保证有收获。