Civitai Orchestrator 工作流查询统一化:从双端点走向类型感知的单一路由
2026/9/19 7:14:44 网站建设 项目流程

Civitai Orchestrator 工作流查询统一化:从双端点走向类型感知的单一路由

【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai

导读

本文围绕 Civitai 主站(当前仓库src/目录下的 Next.js 应用)中的一份架构演进文档 docs/unified-workflow-query.md 展开,剖析其提出的"统一工作流查询端点"(Unified Workflow Query Endpoint)方案:将图片生成查询与提示词增强查询合并为单一 tRPC 端点,并让服务端格式化层具备类型感知能力。读完本文,你将理解当前双端点架构的分歧点、统一方案的四个实施步骤、其中涉及的四个关键挑战,以及源码中已有的部分统一化进展(queryWorkflowsByTags),可以直接将该方案应用于自己的 orchestrator 集成或同类多类型查询场景。

一、现状:两条并行的查询路径

文档首先指出,当前存在两个彼此独立的查询端点,它们调用的是同一个底层 orchestrator API,分歧完全发生在服务端格式化层

端点Tag 过滤服务端处理客户端消费方
queryGeneratedImagesgenerationformatGenerationResponse2(约 150~200 行:资源富化、遗留元数据归一化、图片 blob 格式化)Queue.tsxFeed.tsx,经useGetTextToImageRequests
queryPromptEnhancementsprompt-enhancement无(queryWorkflows原始透传)HistoryTab.tsx,经useGetPromptEnhancementHistory

在源码中可以完整印证这张表。路由定义位于 src/server/routers/orchestrator.router.ts:

  • queryGeneratedImages(第 331 行起)通过queryGeneratedImageWorkflows2处理,并在 green 域名下追加'green'tag、开启cache: true
  • 通用按 tag 查询queryWorkflowsByTags(第 211 行起)是提示词增强历史的实际落点,其注释明确写着"Generic workflow query by tags — used for prompt enhancement history, future text workflows, etc.",即该端点从设计上就是类型无关的。

客户端两侧的消费方式也各不相同:

  • 图片侧useGetTextToImageRequests(src/components/ImageGeneration/utils/generationRequestHooks.ts)会registerSignalGroup('generation'),把WORKFLOW_TAGS.GENERATION(实际常量值为'gen',见 src/shared/constants/generation.constants.ts)与筛选器 tag 拼接后调用useInfiniteQuery,随后把每条 workflow 包成new WorkflowData(workflow, { domain, nsfwEnabled })
  • 提示词侧useGetPromptEnhancementHistory(src/components/Generation/PromptEnhance/promptEnhanceHooks.ts)直接以{ tags: ['prompt-enhancement'] }调用queryWorkflowsByTags.useInfiniteQuery,再经mapWorkflowToRecord映射为PromptEnhancementRecord(包含 originalPrompt、enhancedPrompt、issues、recommendations、temperature 等字段),供 HistoryTab.tsx 渲染。

二、共享底座:类型无关的queryWorkflows

两条路径之所以能统一,关键在于它们最终都汇入同一个底层函数queryWorkflows。该函数位于 src/server/services/orchestrator/workflows.ts,本身不关心 workflow 的具体类型,它承担了三件公共职责:

  1. 强制注入'civitai'tagtags: ['civitai', ...(query.tags ?? [])],确保查询始终限定在 civitai 域内;
  2. 读取超时兜底AbortSignal.timeout(ORCHESTRATOR_QUERY_TIMEOUT_MS),常量值为 20 秒(workflows.ts),防止 orchestrator 挂起时无限阻塞并占死 api 连接池;触发后会被识别为可重试的 503(统一文案ORCHESTRATOR_UNAVAILABLE_MESSAGE);
  3. 错误分类映射:400 → BadRequest、401 → Authorization、403 → InsufficientFunds、429 → RateLimit、404 → NotFound,其余 4xx 归为 BadRequest,上游 5xx/网络故障归为可重试 503,未知错误原样抛出(保持真实 bug 可见)。

同时,单条读取getWorkflow也有独立的 20 秒兜底(ORCHESTRATOR_GET_TIMEOUT_MS),服务于statusUpdate轮询热路径。可以推断:既然底层查询天然类型无关,统一化的工作重心确实如文档所说,全部集中在"服务端格式化层 + 客户端消费层"。

三、分歧点:formatGenerationResponse2的图片假设

queryGeneratedImages之所以需要专门的服务端处理,是因为formatGenerationResponse2(src/server/services/orchestrator/orchestration-new.service.ts)假定每个 workflow 都包含图片/视频步骤。它的处理流水线包括:

  • 资源富化:从workflow.metadata.resources和每个 step 的getResourceRefsFromStep中收集资源 ID,按(id, epoch)去重(保证同一版本的不同 epoch 不被合并),然后批量调用getResourceData命中数据库,把模型名、版本、epoch 号等信息补全到响应中(Raw-AIR 负 ID 资源跳过富化);
  • 新格式归一化workflow.metadata.params+workflow.metadata.resources,其中normalizedWfMeta显式白名单而非展开——每个暴露给调用方的 key 都必须被命名,例如remixOfIdisPrivateGenerationmodelSubstitutions都是逐项挑选的;
  • 遗留格式回退:无 workflow 级 metadata 时回退到steps[0].metadata(标准生成),或step.metadata.transformations[last](遗留增强格式);
  • 步骤格式化formatStep再对每个 step 做输出归一化。formatStepOutputs内部已经是一个按step.$type分发的 switch(orchestration-new.service.ts),覆盖comfyimageGen/textToImagemodel3DPreviewimageUpscalerpreprocessImagevideoGen(含 LTX 2.3 的additionalVideos多视频批处理)、videoUpscaler/videoEnhancement/videoInterpolationpreprocessVideoaceStepAudiominiMaxMusic3polyGen(合并 rigged/animated 变体)等类型,default返回空数组。

问题在于:提示词增强 workflow 的 input/output 是自包含的,没有资源、没有图片 blob,这套流水线对它要么空转、要么产生垃圾数据。这正是文档强调"必须让formatGenerationResponse2类型感知"的根本原因。

四、统一方案:四个实施步骤

文档给出的方案分四步,以下是完整设计及与源码的对应关系。

1. 单一 tRPC 端点

queryGeneratedImages重命名/替换为统一的queryWorkflows端点,去掉硬编码的generationtag 过滤,让客户端按需传 tag。从源码看,这一思路的雏形其实已经落地为queryWorkflowsByTags——它接收的正是workflowQuerySchema,且没有任何类型假设。

2. 类型感知的服务端格式化器

formatGenerationResponse2从"假定全图/视频"改为按步骤类型分发:

switch (step.$type) { case 'textToImage': case 'videoGen': // 现有 image/video 格式化(资源富化、blob 处理、遗留兼容) break; case 'promptEnhancement': // 透传或轻量归一化(input/output 自包含) break; default: // 未知类型的通用透传 break; }

这里的 switch 结构与源码中formatStepOutputsswitch (step.$type)完全同构,差异在于前者目前是"输出归一化"层,而统一方案要求把"资源富化 + 元数据归一化"也纳入类型分派。

3. 判别联合(Discriminated Union)响应

每个 workflow 携带一个type判别字段,让客户端无需猜测 payload 形状:

type NormalizedWorkflow = | { type: 'generation'; /* 现有 image/video 字段 */ } | { type: 'promptEnhancement'; /* input/output/issues/recommendations */ } | { type: 'unknown'; /* 原始 step 数据 */ };

可以推断,promptEnhancement分支的字段应与PromptEnhancementRecord(promptEnhanceHooks.ts)对齐,即 workflowId、createdAt、originalPrompt、enhancedPrompt、issues、recommendations、instruction、preserveTriggerWords、temperature、status。

4. 客户端过滤

  • Queue/Feed:过滤type === 'generation'
  • 历史页:过滤type === 'promptEnhancement'
  • 或者干脆渲染混合内容,为不同类型配各自的卡片组件。

对应的客户端改造面包括:useGetTextToImageRequests目前直接对全部条目执行new WorkflowData(...)(generationRequestHooks.ts),统一后需要先按type分流;useGetPromptEnhancementHistorymapWorkflowToRecord(按$type === 'promptEnhancement'name === 'prompt-enhancement'查找 step)则可以退化为简单的类型断言。

五、关键挑战详解

5.1 遗留格式处理

formatGenerationResponse2同时处理三种元数据格式,统一时必须保证:

  • 新格式:workflow.metadata.params+workflow.metadata.resources(orchestration-new.service.ts);
  • 遗留格式:step.metadata.params+step.metadata.resources(第 3115-3124 行);
  • 遗留增强格式:step.metadata.transformations[last](第 3098-3114 行)。

这些逻辑必须对图片 workflow 保留,但绝不能对提示词增强 workflow 执行——否则会失败或产生垃圾数据。文档给出的约束是:类型分发发生在格式化入口,遗留兼容逻辑只挂在generation分支下。

5.2 资源富化的条件化

图片 workflow 需要把资源 ID 富化为模型名、版本、epoch 号,这一步要命中数据库(getResourceData)。提示词增强 workflow 没有资源,富化步骤必须条件化,否则每次历史查询都会产生无谓的数据库开销。文档同时点出一个细节:formatGenerationResponse2的资源收集横跨 workflow 级与 step 级两处(第 3010-3026 行),条件化时要同时覆盖这两处入口。

5.3 Signal 更新通道

useTextToImageSignalUpdate(src/components/ImageGeneration/utils/useGenerationSignalUpdate.ts)订阅SignalMessages.TextToImageUpdate(常量值'orchestrator:text-to-image-update',见 src/server/common/enums.ts),经 100ms 防抖后把 statusUpdate 结果 patch 进trpc.orchestrator.queryGeneratedImages的 React Query 缓存(updateWorkflowsStatusgetQueryKey(trpc.orchestrator.queryGeneratedImages)定位缓存)。而提示词侧走的是另一条通道:useWorkflowUpdateSignal订阅SignalMessages.WorkflowUpdate'orchestrator:workflow-update'),通过onWorkflowSignal监听器注册表把事件分发给useGetPromptEnhancementHistory,后者再以updateWorkflowInTagCache(workflowItem, PROMPT_ENHANCEMENT_TAGS)更新queryWorkflowsByTags的缓存(src/components/Orchestrator/workflowHooks.ts)。

统一后需要二选一:要么建立统一的 Signal 通道,要么维护多个订阅共同更新同一个缓存。注意updateWorkflowInTagCache/addWorkflowToTagCache已经是按 tag 匹配查询键的通用实现(matchesTagQuery检查 query key 的 input.tags 是否包含目标 tag),可以推断这套机制天然适合迁移到统一端点。

5.4WorkflowData/BlobData包装器

图片侧把 workflow 包进WorkflowData(src/shared/orchestrator/workflow-data.ts),它会把原始输出包装成BlobData子类(ImageBlobVideoBlobAudioBlobModel3DBlob,抽象基类见 workflow-data.ts),承担 NSFW 拦截与父引用(BlobData.stepStepData.workflow)接线。这套机制与图片输出强耦合:统一查询需要多态包装器,或对非图片类型跳过包装。从源码看,WorkflowData的构造选项(domainnsfwEnabledallowMatureContent)都与图片的 NSFW 策略相关,提示词增强记录不需要也不应走这套包装。

六、统一化不可忽视的缓存维度

文档未展开、但源码中必须纳入统一化评估的是queryGeneratedImages专属的短 TTL 缓存体系(orchestration-new.service.ts):

  • QUERIED_WORKFLOWS_CACHE_TTL = 3秒,用于折叠 SignalR 重连风暴——每次重连所有标签页会同时 invalidatequeryGeneratedImages,导致毫秒级的大量相同首页查询;
  • 缓存键getQueriedWorkflowsCacheKey必须覆盖所有影响响应的参数(take、cursor、tags、ascending、fromDate、toDate、excludeFailed、hideMatureContent),并刻意排除 token以允许同一用户的多标签页共享条目;
  • 失效采用每用户索引集合(SMEMBERSDEL),deleteWorkflowcancelWorkflowupdateWorkflowpatch四个变更端点都会调用bustQueriedWorkflowsCache主动清缓存(orchestrator.router.ts),且只缓存第一页(无 cursor),深翻页直接穿透。

queryWorkflowsByTags(提示词增强历史)目前没有这层缓存。统一端点后,"3 秒短 TTL + 变更即失效"的语义是否要覆盖全部类型,需要单独决策:如果对提示词增强历史也启用,会让历史记录的增删改(如删除一条增强记录)同样依赖失效链路;如果维持现状,则统一端点内部将出现"同端点不同缓存策略"的分叉,这本身也是一种复杂度。

七、工作量评估与落地建议

文档给出的评估为2~3 天,拆解如下:

  1. formatGenerationResponse2类型感知且不破坏现有图片 workflow(约 1 天);
  2. 更新客户端 hooks 与组件以处理判别联合(约 1 天);
  3. 回归验证遗留 workflow 格式仍能正确渲染(约 0.5 天)。

建议是作为后续项推进,而不是提示词增强功能的一部分——当前双端点方案已可用且足够清晰;当出现第三种 orchestrator workflow 类型时,统一化才真正值得做,因为届时"每新增一种类型就新增一个端点"的模式会开始显得冗余。

从当前仓库的实际状态可以印证这一演进方向已经部分发生:queryWorkflowsByTags的存在本身就是"通用端点先行"的落地(orchestrator.router.ts),提示词增强已不再需要专属端点;而图片侧的queryGeneratedImages仍保留着强图片假设与专属缓存。可以推断,后续真正需要做的,是让formatGenerationResponse2的类型分发与queryWorkflowsByTags的通用查询能力合流,再统一客户端判别与 Signal 通道,即可完成文档描述的整体收敛。相关查询类型定义可在 src/types/router.ts 的QueryGeneratedImages中追根溯源。

参考路径速查

  • 设计文档:docs/unified-workflow-query.md
  • 路由层:src/server/routers/orchestrator.router.ts(queryGeneratedImagesL331、queryWorkflowsByTagsL211)
  • 底层查询:src/server/services/orchestrator/workflows.ts(queryWorkflowsL127、超时兜底 L64-L88)
  • 格式化与缓存:src/server/services/orchestrator/orchestration-new.service.ts(formatGenerationResponse2L3003、queryGeneratedImageWorkflows2L3277、缓存 TTL L3177)
  • 查询入参 schema:src/server/schema/orchestrator/workflows.schema.ts
  • 图片侧 hooks:src/components/ImageGeneration/utils/generationRequestHooks.ts、Signal 更新:useGenerationSignalUpdate.ts
  • 提示词侧 hooks:src/components/Generation/PromptEnhance/promptEnhanceHooks.ts
  • 通用缓存辅助:src/components/Orchestrator/workflowHooks.ts
  • 客户端包装器:src/shared/orchestrator/workflow-data.ts
  • Tag 与 Signal 常量:src/shared/constants/generation.constants.ts、src/server/common/enums.ts

【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询