Files
Genarrative/docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md
T
kdletters f748dc72c3 收口图片生成任务超时预算
- 将四类 VectorEngine 图片任务纳入长任务预算并预留终态写回窗口
- 通过请求上下文传递绝对截止时间并约束发送重试与图片下载
- 允许显式下调单次请求超时并补齐预算回归测试
- 同步外部生成 Worker 运维文档与项目记忆
2026-07-20 20:57:34 +08:00

30 KiB
Raw Blame History

外部生成 Worker 化方案

2026-07-18 退役覆盖:旧创作模板 job 类型、玩法写回和玩法恢复链路均已退出现役 worker。当前 worker 只领取 source_module = editor-canvas 的任务;本文涉及拼图、跳一跳、拼消消、敲木鱼等玩法的内容仅作为历史设计记录,历史队列行不得被领取或改写。

更新时间:2026-07-15

背景

当前 VectorEngine gpt-image-2、音频、LLM 等外部生成链路多数由 api-server 的 HTTP handler 直接等待上游、OSS 持久化和 SpacetimeDB 回写完成。前端虽然有生成页和会话轮询,但 HTTP 进程仍承担长耗时副作用,导致接入更多玩法或大图生成时只能放大 API 进程,而不能单独扩展外部生成吞吐。

目标

  • 默认 queue 模式下,api-server 的 HTTP 角色只负责鉴权、入参校验、扣费前置/状态初始化、任务入队和返回 queued 操作结果。
  • 外部生成副作用由独立 external-generation-worker 角色执行。
  • 多个 worker 进程通过 SpacetimeDB 任务表抢占任务,依赖 lease 超时恢复,支持按进程数和单进程并发动态缩扩容。
  • 本地或小流量同步排查可显式启用 inline 模式,由 HTTP handler 复用同一 worker executor 同步执行并返回 completed;该模式不创建队列任务,也不具备 worker 横向扩容能力。
  • SpacetimeDB reducer / procedure 只做任务状态流转,不做网络、文件系统或外部 provider I/O。
  • 已接入拼图 compile_puzzle_draft、结果页 generate_puzzle_images 与结果页 generate_puzzle_ui_background,跳一跳、拼消消和敲木鱼的外部图片生成动作,以及图片画布编辑器的图片、改图、手动去背景、图标 spritesheet、UI 素材提取、角色动作、视频、音效和背景音乐生成。后续玩法和编辑器生成入口继续复用同一队列 Module,不再为每个入口发明独立队列。
  • 第一版外部生成队列粒度固定为“单个用户动作对应单个 job”。例如草稿编译、结果页单槽重生、图集重生都各自入一个 job;job 内部可以串行或并行调用 provider、OSS、SpacetimeDB 写回,但不再拆成“提示词 / 生图 / 切图 / 去背景 / 持久化 / 回写”等阶段 job。用户可见执行阶段通过现有任务行及摘要投影的轻量 phase 保存,不作为队列调度单位,也不写回大 payload。
  • 不调用外部图片 / 音频 / LLM provider 的动作继续 inline 执行,不为了统一排队而进入 external_generation_job

Module 与 Interface

新增深一点的 外部生成任务 ModuleInterface 收敛为:

  • enqueue_external_generation_job_and_return:按 dedupe_key 幂等创建或返回现有任务。
  • claim_external_generation_jobs_and_returnworker 按 worker_idlimit 和 lease 时长抢占 pending 或 lease 过期的 running 任务,返回本次 claim 的 lease_token
  • renew_external_generation_job_lease_and_returnworker 长任务执行期间按 worker_id + lease_token 续租,防止外部生成超过单次 lease 后被重复领取。
  • update_external_generation_job_phase_and_returnworker 按 job_id + worker_id + lease_token 把当前执行阶段更新为 generatingprocessing,并同步现有摘要投影;不新增阶段任务或阶段表。procedure 用结构化结果区分 LeaseFencingRejectedOtherRejected,调用方不解析错误文案;LeaseFencingRejected 立即终止,OtherRejected 以及 SDK 的 Procedure / Runtime 错误不重试,只有 Build / ConnectDropped / Timeout 在同一个 job attempt 内重试 1 次。该重试只重新上报 phase,不把任务写回 pending,也不重新调用 provider;编辑器 job 入队固定 max_attempts=1,第二次传输失败后任务进入 failed,不会回到 pending 或从 provider 生成起点重跑。
  • complete_external_generation_job_and_returnworker 成功后按 worker_id + lease_token 写入 result_payload_json,任务进入 completed
  • fail_external_generation_job_and_returnworker 失败后按 worker_id + lease_token 回写错误,并按 max_attempts 决定回到 pending 重试或进入 failed
  • list_external_generation_job_summaries_and_return:按当前账号从轻量摘要投影读取正式生成任务列表,返回 pending / running / 未确认终态数量、任务价格、执行阶段和完成提示确认状态。
  • acknowledge_external_generation_job_summaries_and_return:按当前账号确认已终态任务的完成 / 失败提示,写入摘要投影的 notification_acknowledged_at 并追加审计事件。
  • get_external_generation_queue_stats_and_returncontroller 读取队列积压、运行中任务和过期 lease 数量,用于计算 worker 目标实例数;该 procedure 只读 external_generation_job,不直接操作 systemd。
  • get_external_generation_job_summary_and_return:按 job_id 从轻量摘要投影读取单个任务状态,给 BFF 和生成页展示使用;必须只返回调用者有权读取的任务,不能暴露其它用户的 payload、错误详情或 worker 内部字段。
  • get_external_generation_job_result_and_return:仅供后端内部回填异步编辑器 Agent 工具调用;按 job_id + owner_user_id 返回 statuslast_error_message 和已持久化的 result_payload_json,不返回请求 payload、lease 或其它 worker 字段。该 procedure 不替代摘要状态读取接口,也不经 BFF 暴露给前端。

不带 summary / summaries 的旧 get / list / acknowledge_external_generation_job* procedure 只保留给受控内部兼容,不是 BFF 正式读取入口。

这个 Module 的 Seam 在 SpacetimeDB procedure + spacetime-client facadeapi-server HTTP role 和 worker role 都只依赖这个 Interface。外部 provider、OSS、计费补偿、玩法草稿回写仍留在 api-server worker implementation 内,不进入 SpacetimeDB reducer。

BFF 状态接口

队列状态对前端只通过 api-server BFF 暴露,不允许前端直接查询 SpacetimeDB private table

  • GET /api/runtime/external-generation/queue-overview:当前账号队列概览,用于兼容旧展示和轻量状态读取。返回 pending、running、未确认终态数量和更新时间。
  • GET /api/runtime/external-generation/jobs?limit=20&includeAcknowledgedTerminal=false:当前账号正式生成任务列表,用于 我的 页签任务列表和完成 / 失败提示。返回每个任务的 job id、kind、source、可展示 label、状态、进度、错误、可选 warningpriceMudPointsrefundLedgerIdnotificationAcknowledgedAt 和时间戳。默认不返回已确认的终态任务;需要拆分活跃和完成列表时可追加 statuses=running,queuedstatuses=completed,failedBFF 仍只返回当前账号任务。
  • 任务被 claim 后默认处于 generating,BFF 显示“正在生成”;真实进入 BgFilter、逐帧抠图或手动去背景时切换为 processingBFF 显示“正在处理”。旧任务 phase=Nonegenerating 兼容,前端不得按耗时或 job kind 推断阶段。
  • POST /api/runtime/external-generation/jobs/acknowledge:生成完成 / 失败提示展示后由前端后台调用,BFF 只传当前账号 job ids,后端只确认属于当前账号且已终态的任务。
  • GET /api/runtime/external-generation/jobs/{jobId}:单 job 状态,用于生成页轮询某次动作。返回 operationId(即任务 ID)、statusphaseLabelphaseDetailprogresserrorupdatedAtMicros,以及可选、可直接展示的 warning 完整文案。生成页轮询只依赖状态、阶段、进度、错误和警告;jobKind、source 和完整时间信息继续由任务列表接口或业务快照提供。attempt / maxAttempts 属于 worker 调度事实,不向该前端契约暴露;若未来需要面向用户展示,必须单独完成产品、契约和摘要投影设计。

BFF 只做鉴权、授权裁剪、字段脱敏和契约映射;worker 调度、lease、执行和计费事实仍以 external_generation_job 为准,用户可见任务列表、单任务状态、执行阶段和通知确认的正式读取事实源为 external_generation_job_summary,业务结果仍以玩法 session / work profile 为准。生成页 / 进度页只展示当前玩法业务进度;用户可见任务列表放在 我的 页签,必要时再用单 job 状态补充排障信息,并继续按原玩法 session/detail 接口收敛到 ready 或 failed。队列接口不替代玩法恢复接口,也不把 private request_payload_json 原样传给前端。终态提示的弹出与否以后端 notification_acknowledged_at 为准;前端在提示展示后后台调用 acknowledge 接口,关闭按钮只负责收起本地弹窗,不能只靠本地 dismiss 永久吞掉任务。

任务表

新增私有表 external_generation_job

字段 说明
job_id 主键,extgen- 前缀 UUID
dedupe_key 唯一键,建议为 play/action/session/scope
job_kind 执行类型,当前覆盖 puzzle_compile_draftpuzzle_generate_imagespuzzle_generate_ui_background、跳一跳 / 拼消消 / 敲木鱼生成动作,以及 editor_image_generationeditor_image_editeditor_background_removaleditor_icon_spritesheet_generationeditor_ui_design_asset_extractioneditor_character_animation_generationeditor_video_generationeditor_sound_effect_generationeditor_background_music_generation
owner_user_id 触发用户
source_module 玩法或能力名,例如 puzzle
source_entity_id session/profile/work 等作用域
request_label 排障标签
request_payload_json worker 执行入参 JSON
status pending/running/completed/failed/cancelled
attempt / max_attempts 当前尝试次数与最大尝试次数
last_error_message 最近失败原因
worker_id 当前 lease owner
lease_expires_at lease 到期时间
lease_token 本次 claim 的 fencing token,用于阻止过期 worker 回写
available_at 下次可领取时间
result_payload_json 完成摘要
created_at/started_at/completed_at/updated_at 审计时间
price_mud_points 后端计算的本任务价格,用于任务列表展示和排障
refund_ledger_id 失败退款产生的钱包退款流水 ID,便于从任务追到退款记录
notification_acknowledged_at 用户已确认完成 / 失败提示的时间,未确认终态任务下次登录继续集中弹出
phase 尾部可选字段;null / generating / processingclaim 时写 generating,进入正式后处理时写 processing

用户正式读取使用私有轻量投影 external_generation_job_summary。该表同步保存 owner、来源、状态、phase、价格、有限错误/告警摘要、通知确认和时间字段,不复制 request/result payload、worker lease 或 dedupe 内部字段;enqueue、claim、renew、phase update、complete、fail 与 acknowledge 都必须维护对应投影语义。

新增私有审计表 external_generation_job_event,记录 enqueued/claimed/lease_renewed/completed/failed/acknowledged 等事件。事件表只追加状态转换事实,不作为当前状态源;排障时先看 external_generation_job 当前状态,再按 job_idexternal_generation_job_event 时间线。

索引:

  • by_external_generation_job_status_available(status, available_at)
  • by_external_generation_job_worker_id(worker_id)
  • by_external_generation_job_source(source_module, source_entity_id)
  • by_external_generation_job_owner_user_id(owner_user_id)

状态机

pending -> running -> completed
pending -> running -> pending   (可重试失败)
pending -> running -> failed    (达到最大重试次数)
pending/running -> cancelled    (预留)

claim 只领取 pendingavailable_at <= now 的任务,或 runninglease_expires_at <= now 的任务。领取时递增 attempt、写入 worker_idstarted_at、新的 lease_expires_atlease_token。SpacetimeDB procedure 使用 ctx.timestamp 作为状态流转时间,只从 worker 入参读取“时长差值”,不信任 worker 本机绝对时间。worker 每次执行只处理自己 claim 到的任务;续租、完成或失败时必须带同一个 worker_id + lease_token,且当前 lease 尚未过期,防止过期 worker 覆盖新 lease。

玩法业务写回也必须在 SpacetimeDB 同一事务里校验 lease fencing。拼图的 compile_puzzle_agent_draft worker 调用、save_puzzle_generated_imagessave_puzzle_ui_backgroundmark_puzzle_draft_generation_failedmark_puzzle_level_generation_failedqueue 模式下会带 external_generation_job_id / worker_id / lease_token,并校验 job 仍为 running、token 未过期、job_kindowner_user_idsource_modulesource_entity_id 均匹配后才写 session / work profile。inline 模式不创建 external_generation_job,因此这三个 guard 字段必须同时为空;transaction 只把三项全空识别为 api-server 受控同步写回,三项半空仍按非法请求拒绝。worker 路径的核心业务写回失败不能返回内存快照并把 job 标为 completed;失败态业务回写成功后才允许把队列 job 标为 failed,失败态仍未写回时保留当前租约并等待后续 lease 过期重领,避免队列状态和真实 session 脱节。api-server 的资产扣费包装遇到这类 stale worker lease guard 错误时不执行补偿退款,避免旧 worker 冲掉后续合法 worker 的同一账本扣费。

执行模式与进程角色

外部生成执行模式由 GENARRATIVE_EXTERNAL_GENERATION_MODE 控制:

  • queue:默认值,HTTP handler 入队 external_generation_job,由 external-generation-worker 角色 claim lease 后执行;生产、预发和压测默认使用该模式。
  • inlineHTTP handler 直接调用同一个 worker executor,同步等待 provider、OSS 和 SpacetimeDB 写回完成后返回 operation.status = completed;只用于本地或低并发排查,不提供队列持久化、lease 重领和 worker 横向扩容。

同一个 Rust binary 通过 GENARRATIVE_PROCESS_ROLE 切换:

  • api:只启动 HTTP server。
  • external-generation-worker:只启动外部生成 worker,不监听 HTTP。
  • external-generation-controller:只启动 worker controller,不监听 HTTP,也不直接执行外部生成任务。
  • all:本地开发可同时启动 HTTP 与 worker。

worker 配置:

  • GENARRATIVE_EXTERNAL_GENERATION_WORKER_ID:实例 ID;未配置时用 hostname/pid 派生。
  • GENARRATIVE_EXTERNAL_GENERATION_WORKER_CONCURRENCY:单进程并发领取/执行数量。
  • GENARRATIVE_EXTERNAL_GENERATION_WORKER_POLL_INTERVAL_MS:空队列轮询间隔。
  • GENARRATIVE_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS:任务 lease 时长,默认 600worker 会按约三分之一 lease、最长 30 秒的间隔续租。该值应覆盖一次心跳网络抖动窗口,不需要大于完整外部生成链路耗时。
  • GENARRATIVE_EXTERNAL_GENERATION_WORKER_JOB_TIMEOUT_SECONDS:普通外部生成 job 的执行预算,默认 900。超过预算后当前 worker 停止续租并释放 worker 槽位,但不取消已启动的业务 future,也不主动写入失败 / 重试状态;在途执行交由 lease fencing 仲裁:写回在租约有效期内到达则照常完成,否则被拒绝,租约过期后任务可被重新认领,attempt 耗尽时由认领事务原子标记失败并结算退款。
  • GENARRATIVE_EXTERNAL_GENERATION_WORKER_LONG_JOB_TIMEOUT_SECONDSVectorEngine 图片生成 / 编辑、图标 spritesheet 生成、UI 素材提取以及角色动作、视频等长耗时 job 的执行预算,默认 1800。其中四类 VectorEngine 图片 job 固定为 editor_image_generationeditor_image_editeditor_icon_spritesheet_generationeditor_ui_design_asset_extraction;手动去背景等不直接调用 VectorEngine 的 job 继续使用普通预算。

worker 在单次 job 开始执行时从同一个单调时钟起点计算绝对 job deadline 和更早的 provider deadline:常规情况下为终态审计、OSS 持久化及 complete/fail 回写保留 60 秒;当整个 job 预算小于 120 秒时,保留其一半,避免 provider 预算被全部吃掉。该 deadline 只通过进程内 RequestContext 传给 VectorEngine 图片调用,不写入 HTTP DTO、队列 payload 或 SpacetimeDB;普通 HTTP / inline 上下文没有 deadline,保持原有行为。

VectorEngine 每次发送的实际 timeout 取 min(VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS, provider 剩余预算)。遇到可重试传输错误或 408 / 429 / 5xx 时,只有当剩余预算还容得下本次退避和下一次 attempt 才继续;否则立即停止重试并返回当前 provider 错误,deadline 耗尽时返回 timeout。同一绝对 deadline 同时覆盖参考图下载、provider 请求 / 响应和响应图片 URL 下载,不允许请求已返回后的图片下载越过 provider 预算。VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS 默认仍为 1000000;配置加载层允许显式值低于默认值,不再在读取环境变量时强制抬升。

controller 配置:

  • GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_MIN_WORKERS:保底 worker 实例数,生产默认 1controller 不会主动停止 @1
  • GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_MAX_WORKERS:自动扩容上限,生产模板默认 8
  • GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_TARGET_JOBS_PER_WORKER:每个 worker 实例承担的目标未完成任务数,默认 2;目标实例数按 claimable_pending + running_active + expired_running 计算后夹在 min/max 之间,避免把已包含过期 running 的 claimable_count 重复计入。
  • GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_POLL_INTERVAL_MScontroller 轮询队列统计的间隔,默认 10000
  • GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_SCALE_DOWN_IDLE_ROUNDS:连续多少轮无可领取、无运行中、无过期 running 后才允许缩容,默认 6;缩容每轮只停止最高编号的一个实例。
  • GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_SERVICE_TEMPLATEsystemd worker 模板,默认 genarrative-external-generation-worker@{}.service
  • GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_DRY_RUN:只记录决策不执行 systemctl,默认 false

动态缩扩容方式:生产默认由 deploy/systemd/genarrative-external-generation-controller.service 启动 GENARRATIVE_PROCESS_ROLE=external-generation-controllercontroller 读取 get_external_generation_queue_stats_and_return 后对 genarrative-external-generation-worker@N.service 执行精确 systemctl start/stop;无需改变 HTTP 进程数。controller 只操作 @1..@MAX 中的缺口或最高编号多余实例,保留 @1 作为保底 worker。缩容或发布重启 worker 时,进程收到 SIGINT/SIGTERM 后会停止 claim 新任务并等待当前任务完成;若进程被硬杀、机器断电或超过 systemd TimeoutStopSec,未完成任务会在 lease 过期后被其它 worker 重新领取。VectorEngine 图片链路会先于整个 job 执行预算停止 provider 发送 / 重试,以便 worker 在有效 lease 内完成终态写回;若其他业务 future 仍长时间无返回,执行预算到期后 worker 会停止续租并释放槽位,在途 future 继续运行至租约仲裁窗口;有效租约内的写回仍可完成,租约过期后才会由其它 worker 重新认领,避免客户端取消与服务端写回竞态。本次预算收口不改变 lease 续租 / fencing、迟到写回仲裁、attempt 耗尽收口和原子退款语义。容器链路已有独立 external-generation-worker compose service;扩 worker 必须扩这个 worker service,不能只扩 api-server HTTP service。

已接入的拼图纵切

拼图

compile_puzzle_draft

  1. HTTP handler 保存拼图表单草稿;queue 模式下 queued/running 的持久事实源是 external_generation_job,不把 HTTP 进程变成外部生成执行者。
  2. queue 模式下 HTTP handler 入队 puzzle_compile_draft,返回 operation.status = queued 和当前 session。拼图 dedupe key 包含本次 extgen- job id,只保证同一任务行唯一,不把同一 session 后续重新生成吞掉。inline 模式下 HTTP handler 复用同一 executor 同步执行,成功后直接返回 completed 和最新 session。
  3. 前端保持 puzzle-generating,继续轮询 getPuzzleAgentSession;首期不把 queued/running 写回 puzzle_agent_session,因此刷新或跨设备恢复生成中状态仍是后续 read model 工作。
  4. worker claim 后执行原有 compile_puzzle_draft_with_initial_covercompile_puzzle_draft_with_uploaded_cover;前置 compile_puzzle_agent_draft 也必须携带本次 job_id / worker_id / lease_token,防止过期 worker 先把草稿卡和 session 写到 ready。
  5. 成功后沿原有 SpacetimeDB 拼图会话/作品写回,前端轮询看到 progressPercent >= 94/96/100 和 ready 草稿。
  6. 失败后调用 mark_puzzle_draft_generation_failed,拼图首期业务失败直接进入 failed;只有失败态写回成功才把队列 job 标为 failed,失败态写回失败则保留租约等待重领。队列仍保留 lease 过期后的崩溃重领,避免 worker 退款后再次成功导致钱包账本漂移。前端通过现有失败草稿/弹窗机制展示来源错误。

generate_puzzle_images

  1. HTTP handler 校验本次 levelsJson 快照;queue 模式下入队 puzzle_generate_images 并返回 operation.status = queued/running/completed/failedinline 模式下同步执行原 worker executor 并在成功后返回 completed
  2. worker 执行原结果页关卡图链路:自动命名、VectorEngine / 上传图直用、关卡场景图、UI spritesheet、关卡背景资产包、OSS 持久化和 SpacetimeDB 回写。
  3. 成功后 save_puzzle_generated_images 写回目标关卡和草稿卡;失败后 mark_puzzle_level_generation_failed 只标记目标关卡 failed,不污染已 ready 的其它关卡。队列 job 只有在目标关卡失败态写回成功后才进入 failed。
  4. 前端结果页对 queued/running 操作继续轮询 getPuzzleAgentSession,目标关卡变为 ready 或 failed 后收敛。

generate_puzzle_ui_background

  1. HTTP handler 校验本次 levelsJson 快照;queue 模式下入队 puzzle_generate_ui_background 并返回 operation.status = queued/running/completed/failedinline 模式下同步执行原 worker executor 并在成功后返回 completed
  2. worker 执行原结果页 UI 背景链路:归一化提示词、VectorEngine 生成、OSS 持久化和 save_puzzle_ui_background 写回。
  3. 成功后目标关卡写入 uiBackgroundPrompt/uiBackgroundImageSrc/uiBackgroundImageObjectKey;失败后复用 mark_puzzle_level_generation_failed 标记目标关卡 failed,并在失败态写回成功后才终结队列 job,让前端轮询能收敛。

跳一跳、拼消消和敲木鱼扩展范围

以下动作按同一 worker 模式迁移。命名以现有玩法 action 为准,队列 job_kind 采用后端稳定 snake_case,不新增平行队列:

  • 跳一跳 jump-hop
    • compile-draft:草稿编译阶段需要生成地块 / 视觉资产时入队,例如 jump_hop_compile_draft
    • regenerate-tiles:结果页地块图集重生入队,例如 jump_hop_regenerate_tiles
  • 拼消消 puzzle-clear
    • compile-draft:草稿编译阶段需要生成场地底图和卡片 atlas 时入队,例如 puzzle_clear_compile_draft
    • regenerate-atlas:结果页素材 atlas 重生入队,例如 puzzle_clear_regenerate_atlas
  • 敲木鱼 wooden-fish
    • compile-draft:草稿编译阶段需要生成背景、敲击物或其它图片资产时入队,例如 wooden_fish_compile_draft
    • regenerate-hit-object:结果页敲击物图片重生入队,例如 wooden_fish_regenerate_hit_object

这些动作首版都保持“单动作单 job”:一次 compile-draft 或一次 regenerate-* 请求只创建一个 job,worker 内部负责该动作所需的 provider 调用、素材处理、OSS 持久化、失败态写回和业务成功写回。非外部图片生成动作,例如纯元信息保存、标签编辑、发布、试玩启动、运行态动作、删除和公开 read model 读取,继续 inline 执行。

每个玩法迁移时必须同时接入业务写回 lease guardworker 路径带 external_generation_job_id / worker_id / lease_token,inline 路径三项同时为空。过期 worker 不得写 session / work profile;业务失败态写回成功后才允许 job 进入 failed

图片画布编辑器

图片画布 /editor/canvas 下所有会调用外部生成 provider 的入口在 queue 模式下入 external_generation_jobHTTP handler 只返回 queueState

  • editor_image_generation:普通图片、生成规范、角色形象、UI 设计图、宣发素材和图片快速编辑。
  • editor_image_edit:图片编辑 / 修改结果。
  • editor_background_removal:手动去除任意图片背景,worker 使用 BgFilter complex 模式、首次失败后重试 1 次,并把执行阶段标记为 processing
  • editor_icon_spritesheet_generation:图标素材 spritesheet 生成和拆分。
  • editor_ui_design_asset_extractionUI 设计图红框素材提取。
  • editor_character_animation_generation:角色动作视频和帧素材生成。
  • editor_video_generation:画布视频生成和视频素材快速编辑。
  • editor_sound_effect_generation / editor_background_music_generation:画布音效与背景音乐。

画板结果的业务真相仍是 editor_project_resource、账号级 editor_asseteditor_canvas.layers_json。请求携带 projectId + canvasCompletion 时,worker 成功后读取当前项目 layout,用最新 generation dialog placeholder 或无 dialog 完成占位写入结果图层,并保存项目快照;前端轮询单 job 到 completed 后重新读取项目快照,不从队列 payload 或本地临时响应重建正式图层。生成器已被删除时,worker 只保留生成出的资源 / 素材记录,不把结果重新塞回画布。

角色形象、图标 spritesheet 和 UI 素材提取在 provider 原图已经持久化后,如果透明背景处理最终失败,只用原图完成 canvasCompletion,不创建或回填透明处理图,图标和 UI 也不继续拆分,任务保持 completed。这个 source-only 降级只包住透明背景处理的最终失败;phase 上报、provider 原图持久化、透明处理图持久化或画布写回失败仍按任务错误传播。

透明背景处理正常成功时,角色形象、图标 spritesheet 和 UI 素材提取的画布都同时放透明主结果与 provider 原图:透明主结果保持生成器 generatedLayerId 主锚点,provider 原图作为第二个图层放在其右侧;图标和 UI 实际拆分出的业务素材从 provider 原图右侧继续排列。

inline 与 external v1 成功响应继续使用结构化 warning.code/reason;图标 / UI 的透明图已经成功、只有自动拆分失败时,继续返回结构化 sliceWarning.code/reason,其中 sliceWarning.reason 保留原始诊断。queue worker 把两类告警归一为有界的 result_payload_json.warning:通用 warning 优先并原样保留完整 reason;只有不存在通用 warning 时,才给 sliceWarning.reason 添加“图集已生成,但自动拆分未完成:”前缀。任务摘要将该展示就绪的 reason 原样提取到 warning_message,单 job 状态和刷新后的任务列表 BFF 再以 warning: string 返回;Web 必须直接展示,不再补前缀或按 code 推断类型。历史任务保留写入时的 reason 快照,摘要 backfill 不按当前格式重新解释或补写前缀。该字符串语义是 worker / BFF / Web 的内部同版本契约,三者必须协调发布,不承诺滚动混部或旧 Web 缓存下的跨版本字符串兼容。

验收

基础检查:

npm run spacetime:generate
npm run check:spacetime-schema
npm run check:server-rs-ddd
cargo check -p api-server --manifest-path server-rs/Cargo.toml

定向测试:

cargo test -p spacetime-module external_generation --manifest-path server-rs/Cargo.toml
cargo test -p spacetime-module level_generation_failure --manifest-path server-rs/Cargo.toml
cargo test -p api-server external_generation_worker --manifest-path server-rs/Cargo.toml
npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx -t "keeps generation progress visible"
npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "compile_puzzle_draft"

本地 smoke

npm run dev
curl -f http://127.0.0.1:<api-port>/healthz

本地 npm run devnpm run dev:api-server 默认注入 GENARRATIVE_PROCESS_ROLE=all,同一 Rust 进程同时监听 HTTP 并消费外部生成队列;显式设置 GENARRATIVE_PROCESS_ROLE 时保留显式值。需要验证生产式拆分角色、lease 重领或扩缩容时,再分别启动 apiexternal-generation-workerexternal-generation-controller,也可以使用隔离容器 smoke。

生产 smoke 需要保持 GENARRATIVE_EXTERNAL_GENERATION_MODE=queue,并至少启动一个 api 角色、一个 external-generation-worker 角色和一个 external-generation-controller 角色;发布脚本会在默认 worker pattern 下自动启用并启动 genarrative-external-generation-worker@1.service,重启并验活 genarrative-external-generation-controller.servicegenarrative-api.service 还通过 systemd Wants=genarrative-external-generation-controller.service 弱依赖覆盖只启动 API 的现场兜底;controller 仍是独立进程,不由 HTTP 进程内执行 systemctl。若 worker 数量归零,生成任务会保持 queued/running,不会由 HTTP 进程偷偷执行。部署验证除 /healthz / /readyz 外,还要确认任务列表 BFF 可读、未确认终态任务会弹出提示、提示展示后后台 acknowledge 且刷新后不再弹出,单 job 状态能从 queued/running 收敛到业务 session/detail 的 ready 或 failed。

systemd 生产 controller 与手动兜底示例:

systemctl enable --now genarrative-external-generation-worker@1.service
systemctl enable --now genarrative-external-generation-controller.service
systemctl start genarrative-external-generation-worker@2.service
systemctl stop genarrative-external-generation-worker@2.service
systemctl status genarrative-external-generation-controller.service 'genarrative-external-generation-worker@*.service'