从 Agent 到 Skill:AI 图像工作流设计
本文记录我在 Picuro(一个 AI 图像处理产品)中搭建 AI 工作流编排层的设计思路:为什么需要编排、核心概念怎么分层、一次修图任务如何端到端跑通,以及在可靠性和演进上踩过哪些坑。
一、为什么需要"编排"
Picuro 表面上只是一个"上传图片、输入需求、拿到结果"的产品,但背后的 AI 场景其实不少:智能修图、图片生成、多图融合、风格转换、卡通创作、AI 选片。每一个场景都不是一次模型调用能完成的,而是由"理解需求 → 分析原图 → 生成/编辑 → 质检 → 回写结果"这样一串步骤组成。
早期实现里,这些步骤是散落在业务 Service 代码里的:修图逻辑靠一串方法调用和 if 状态判断拼起来,哪个场景走哪几步、用哪个模型、Prompt 是什么,全都硬编码在 Go 代码里。随着场景变多,问题开始集中暴露:
- 业务与模型耦合:业务 Service 直接认识具体的 Provider 客户端和模型参数,换一个模型要改业务代码。
- 流程隐含在代码里:没法直观地查看、发布、追踪一个场景到底由哪些步骤组成。
- 接口语义不统一:文本流式、视觉分析、同步图片、异步图片任务,各自的输入输出和错误格式都不一样。
- 日志链路不完整:只能看到单次模型调用日志,无法从一次业务任务展开到全部节点、模型、成本和结果。
- 路由逻辑固化:场景与能力的匹配靠
switch写死,无法平滑配置主模型、降级模型和参数覆盖。 - 进度体验有限:用户端只能看到任务级的
stage/progress,分析阶段多为同步等待,节点进度和可恢复性都不足。
一句话概括:当"一个场景 = 一个模型调用"变成"一个场景 = 一条有依赖关系的处理流水线"时,就需要一个专门的编排层来承接复杂度。
二、先划清边界:业务是业务,编排是编排
设计编排层最容易犯的错误,是把它做成"又一个业务系统"——既管 AI 流程,又顺手去改工程状态、扣积分、决定任务是否完成。这样只会让耦合更深。
所以第一步是明确一个铁律:AI 编排层不直接修改任何业务表,业务层通过应用服务与它交互,依赖方向严格单向。
两侧各司其职:
- 业务层(
photo_studio_*):工程、素材、对话、方案、执行批次、处理项和结果;用户归属、权限、额度、幂等和业务状态机;负责把业务对象转换成 AI 输入,再把 AI 输出转换成业务事实。 - AI 编排层(
ai_*):根据入口解析启用的 Workflow,校验输入,按依赖关系执行,调用 Skill/Capability/Model/Provider,处理重试、降级、超时和输出校验,保存执行快照、节点状态和最终输出。
AI 层不负责:改工程标题、创建业务结果、扣退款用户额度、决定工程是否完成、覆盖用户手动编辑的内容。这些只能由业务层在消费 AI 输出后自己完成。
这个边界带来两个直接好处:一是业务与模型解耦,二是让编排层可以独立演进、独立测试。
三、一层一层的抽象
编排层的核心,是把"一次 AI 任务"拆成一组职责清晰、各归其位的概念。它们的关系如下:
每一层回答一个不同的问题:
| 概念 | 回答的问题 | 是否可复用 | 示例 |
|---|---|---|---|
| Agent | 业务是谁、以什么身份进入 AI | 否 | photo_editor |
| Workflow | 一条任务由哪些步骤、什么依赖组成 | 是 | 智能修图工作流 |
| Node | 某个步骤在流程中的位置、依赖谁、数据怎么映射 | 否 | analyze_source_image |
| Skill | 用哪套能力配置、Prompt、实现路由和默认参数 | 是 | photo_image_analyzer |
| Capability | 输入输出的稳定协议是什么 | 是 | image.analyze |
| Capability Implementation | 哪套代码把标准协议适配到具体 Provider API | 是 | jimeng_seedream_46_edit |
| Model | Provider 下可调用的具体模型或请求键 | 是 | qwen-vl-max |
| Provider | 服务商账号与连接配置 | 是 | 阿里云百炼、火山即梦 |
有两个容易混淆的点,值得单独说清楚:
1. Node 与 Skill 不重复。 Node 只负责"编排"——在哪个位置、依赖谁、输入输出如何映射,它自己不实现任何模型调用,只引用一个 skill_id。Skill 才负责"执行配置"——绑定 Capability、Prompt、实现候选策略和默认参数。这样同一个 image.analyze Skill 可以被修图、选片、商品图多个 Workflow 复用,而不同 Node 可以给它不同的输入映射和超时。
2. Capability 与 Implementation 要同时存在。 Capability 是平台内部的标准契约(JSON Schema、同步/异步模式、标准错误),业务和 Workflow 只认识它;Implementation 是第三方接口的适配层,一份接口文档对应一套代码实现,负责把标准输入翻译成 Provider 请求,再把 Provider 响应翻译回标准输出。这样换模型、换服务商,只动 Implementation 这一层,Workflow 和业务代码完全无感。
一个能落地的纪律:别再增加 Tool、Action、Component 这类同义概念。 概念每多一层,理解和维护成本就翻一倍。
四、Workflow 是怎么执行的
Workflow 是"可发布、可复用"的执行定义。我们刻意把它做成了一个受约束的 DAG,而不是图灵完备的脚本引擎——不做循环、不做动态建节点、不做任意脚本,只保留图像处理真正需要的那些能力:
- 节点依赖:依赖必须存在,禁止自依赖和环;执行前先做环检测。
- 拓扑分层并发:同一层无依赖的节点并发执行,受
max_concurrency限制。 - 输入/输出映射:用简化的
$.a.b路径把 Context 里的值喂给节点,再把节点输出写回 Context。 - 条件分支:支持
==、!=、&&,不满足时节点标记为skipped。修图首版只需要"需求不明确时提前结束"这一个分支。 - 超时与重试:Workflow 有总超时,每个节点有独立超时、最大尝试次数和退避时间。
- 失败策略:
on_failure=fail终止执行,on_failure=skip继续往下走。
一个 Skill 节点被真正执行时,顺序是这样的:
- 校验 Capability 的 Input Schema;
- 合并 Skill 默认参数、节点参数和运行参数;
- 渲染 Prompt,并合并 Prompt 里的模型参数;
- 按实现策略的候选列表加载启用的 Implementation;
- 过滤掉能力不匹配、或超出模型限制的实现;
- 调用代码里注册的 Provider Adapter;
- 校验 Capability 的 Output Schema 和 Skill 的输出校验;
- 若错误码落在
fallback_on里,则切换到下一个候选实现继续尝试。
这套"先校验、再路由、再降级"的顺序,把"用什么模型、失败换谁"从硬编码变成了可配置的策略。
五、一次"智能修图"是怎么跑完的
把上面这些概念串起来,一次智能修图的完整链路大致是这样:
几个关键设计点:
提交是"同步返回、异步执行"。 SubmitAgentExecution 立即返回 execution_no(状态通常是 queued),AI 路由和执行在后台进行。业务层拿到执行编号后就把它关联到自己的工程记录上,然后通过事件流等待结果,而不是傻等 HTTP 响应。
Agent 是稳定入口,路由由配置驱动。 业务层用 namespace + code 定位 Agent,Agent 再根据 execution_role 在候选 Workflow 里做路由——只有一个候选就直接选,多个候选就调用 Planner Skill 来决定。选中的 Workflow、选中的原图/历史结果,都会写进执行快照,供后续重试、审计和解释使用。
事件落库是事实来源。 每个节点状态变化都会持久化成一条 ai_event,进程内 Channel / Redis Pub/Sub 只负责唤醒订阅者。SSE 用递增的 Sequence 作为 id,客户端断线重连时带上 Last-Event-ID,服务端先补发数据库里漏掉的历史事件,再订阅新事件。这样用户刷新页面或断网重连,都不会丢进度。
回写是业务层的事。 AI 完成后,业务层消费统一输出协议,把结果落成 photo_studio_result,再更新 Plan/Run/Project 的状态,最后追加工程事件通知前端。AI 层从头到尾没有碰过任何业务表。
六、可靠性的几个抓手
异步长任务最怕"跑了半天结果丢了"或者"点重试结果重复扣费"。这里有几个针对性的设计:
- 幂等:唯一键是
agent_id + idempotency_key。同一次业务动作重复提交,直接返回原来的 Execution,不会重复调度。 - 快照:Execution 保存 Agent、Workflow、Node、Skill、Prompt 和路由策略的完整快照。配置发布后修改,不影响已经在跑的任务——历史执行永远可解释、可重放。
- 状态机:Execution 走
queued → running → succeeded/failed/canceled,重试会新建一条 Execution 并记录parent_execution_id。 - 断点恢复:服务启动时以及每 5 秒扫描,恢复
queued/running的执行;succeeded/skipped的节点直接从数据库恢复,不重复调用 Provider;异步 Provider 的waiting_provider节点根据 Implementation ID 和 Provider Job ID 继续轮询。 - 错误分类与降级:限流、超时、Provider 不可用、输出无效这类错误自动重试;
invalid_input、content_policy、canceled永不重试也不降级,避免拿错误输入反复烧钱。 - 连续修图与增量执行:用户"继续、再、基于刚才"的增量修改,通过变更分类和依赖失效计算,尽量复用上一轮的图片分析、需求理解和方案,只重跑真正受影响的部分。
七、当前边界与下一步
这套设计已经落地,但它有意保持克制,也留下了一些明确的"没做":
- 一次 Execution 目前只选择一个 Workflow(多 Workflow 的数据结构已就绪,执行尚未接入);
- 尚不支持从指定节点重试,只能整体重试;
- 取消能停掉本地 Executor,但远端任务的真正取消还没完全打通;
- SSE 目前用 500ms 数据库轮询、异步 Provider 用固定 5 秒轮询,后续可换成更高效的通知机制;
- JSON Schema 校验和 Condition 表达式都是轻量实现,够用但不算完备。
这些边界是刻意的。编排层第一阶段的目标不是做一个 Dify 式的低代码平台,而是把"业务与模型解耦、流程可配置、执行可追踪、失败可恢复"这几件最实在的事做扎实。
八、写在最后
回头看,做 AI 工作流编排最值钱的不是那些花哨的 DAG 或插件机制,而是两件朴素的事:把"流程"从代码里显式地拎出来,以及把"业务"和"模型"之间画一条清晰的边界。前者让一个场景能看、能配、能追踪;后者让模型、服务商的任何变化都停留在 Implementation 那一层,不再向上污染业务。
这套编排目前已经在 Picuro 里实际跑起来了。如果你想直观感受一个"会编排、可追踪、可恢复"的 AI 图像处理流程长什么样,可以到 picuro.fengwenyi.com 上手试试——从上传一张图、写一句需求开始,看它是如何一步步分析、生成、质检,再把结果回写到你眼前的。
