Skip to content

从 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业务是谁、以什么身份进入 AIphoto_editor
Workflow一条任务由哪些步骤、什么依赖组成智能修图工作流
Node某个步骤在流程中的位置、依赖谁、数据怎么映射analyze_source_image
Skill用哪套能力配置、Prompt、实现路由和默认参数photo_image_analyzer
Capability输入输出的稳定协议是什么image.analyze
Capability Implementation哪套代码把标准协议适配到具体 Provider APIjimeng_seedream_46_edit
ModelProvider 下可调用的具体模型或请求键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 节点被真正执行时,顺序是这样的:

  1. 校验 Capability 的 Input Schema;
  2. 合并 Skill 默认参数、节点参数和运行参数;
  3. 渲染 Prompt,并合并 Prompt 里的模型参数;
  4. 按实现策略的候选列表加载启用的 Implementation;
  5. 过滤掉能力不匹配、或超出模型限制的实现;
  6. 调用代码里注册的 Provider Adapter;
  7. 校验 Capability 的 Output Schema 和 Skill 的输出校验;
  8. 若错误码落在 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_inputcontent_policycanceled 永不重试也不降级,避免拿错误输入反复烧钱。
  • 连续修图与增量执行:用户"继续、再、基于刚才"的增量修改,通过变更分类和依赖失效计算,尽量复用上一轮的图片分析、需求理解和方案,只重跑真正受影响的部分。

七、当前边界与下一步

这套设计已经落地,但它有意保持克制,也留下了一些明确的"没做":

  • 一次 Execution 目前只选择一个 Workflow(多 Workflow 的数据结构已就绪,执行尚未接入);
  • 尚不支持从指定节点重试,只能整体重试;
  • 取消能停掉本地 Executor,但远端任务的真正取消还没完全打通;
  • SSE 目前用 500ms 数据库轮询、异步 Provider 用固定 5 秒轮询,后续可换成更高效的通知机制;
  • JSON Schema 校验和 Condition 表达式都是轻量实现,够用但不算完备。

这些边界是刻意的。编排层第一阶段的目标不是做一个 Dify 式的低代码平台,而是把"业务与模型解耦、流程可配置、执行可追踪、失败可恢复"这几件最实在的事做扎实。

八、写在最后

回头看,做 AI 工作流编排最值钱的不是那些花哨的 DAG 或插件机制,而是两件朴素的事:把"流程"从代码里显式地拎出来,以及把"业务"和"模型"之间画一条清晰的边界。前者让一个场景能看、能配、能追踪;后者让模型、服务商的任何变化都停留在 Implementation 那一层,不再向上污染业务。

这套编排目前已经在 Picuro 里实际跑起来了。如果你想直观感受一个"会编排、可追踪、可恢复"的 AI 图像处理流程长什么样,可以到 picuro.fengwenyi.com 上手试试——从上传一张图、写一句需求开始,看它是如何一步步分析、生成、质检,再把结果回写到你眼前的。

Released under the MIT License.