Files
cs-note/hzh/Gen2D/05-生成管线.md
T

11 KiB
Raw Blame History

tags, create time
tags create time
pipeline
eino
graph-pattern
quality-check
fallback
state-machine
go
2026-06-03 10:20

05. 生成管线 (Generation Pipeline)

概述

基于 CloudWeGo Eino 的 4 阶段生成管线,带质量回退和降级,确保任务不阻塞。


正文

管线拓扑

graph TB
    START(["START"]) --> PO["PromptOptimizer<br/>提示词优化"]
    PO --> AG["AssetGenerator<br/>素材生成"]
    AG --> QS["QualitySupervisor<br/>质量检查"]

    QS -->|pass| FA["FormatAdapter<br/>格式适配"]
    QS -->|fail + retry < 3| PO
    QS -->|fail + retry >= 3| FA

    FA --> END(["END"])

    style QS fill:#fff3cd,stroke:#ffc107
    style PO fill:#d1ecf1,stroke:#17a2b8
    style AG fill:#d4edda,stroke:#28a745
    style FA fill:#d4edda,stroke:#28a745

四阶段详解:

阶段 节点名 职责 耗时特征
1 PromptOptimizer 合并风格、注入重试原因、追加技术参数 < 1ms(纯 CPU)
2 AssetGenerator 调用 AI 推理 API 出图 10-120s(IO 密集)
3 QualitySupervisor 质检,决定路由目标 1-5s(可含 LLM 调用)
4 FormatAdapter 精灵表切割 + GIF 预览 1-3s(CPU 密集)

PipelineState 全局状态

Eino compose.Graph 通过 WithGenLocalState 注入全局状态,各节点通过 StatePreHandler / StatePostHandler 读写:

type PipelineState struct {
    Input        PipelineInput    // 原始输入(首次运行时保存)
    FinalPrompt  string           // PromptOptimizer 输出
    RawImages    []GeneratedImage // AssetGenerator 输出
    PassQuality  bool             // QualitySupervisor 质检结果
    RejectReason string           // 质检不通过原因
    RetryCount   int              // 重试次数(最多 3 次)
    NextNode     string           // QualitySupervisor 设置的路由目标
}

状态流转:

RetryCount=0, Input=原始输入
    │
    ▼ PromptOptimizer
FinalPrompt = "风格约束:...。用户描述:...。技术参数:..."
    │
    ▼ AssetGenerator
RawImages = [GeneratedImage, ...]
    │
    ▼ QualitySupervisor
    ├── pass=true  → NextNode="format_adapter"
    ├── pass=false, RetryCount<3 → RetryCount++, NextNode="prompt_optimizer"
    └── pass=false, RetryCount>=3 → NextNode="format_adapter"(降级)

节点详解

1. PromptOptimizer — 提示词优化

职责:合并多源提示词,输出最终提示词(纯字符串拼接,无 LLM 调用)。

输入:PipelineInput
输出:string(最终提示词)

拼接顺序:
1. 工程全局风格提示词 (GlobalStylePrompt)
2. 用户原始提示词 (Prompt)
3. 风格描述 (ProjectStyle + TaskStyle → "风格约束:色调:暖色;线条:粗线条")
4. 重试拒绝原因 (RejectReason → "注意修正以下问题:风格不一致")
5. 技术参数段 ("素材类型: sprite;分辨率: 64;输出格式: spritesheet")

StatePreHandler:

场景 行为
首次运行 (RetryCount=0) 保存原始输入到 state.Input
重试运行 注入 RejectReason 到输入

StatePostHandler:将最终提示词写入 state.FinalPrompt。

2. AssetGenerator — 素材生成

职责:调用 AI 推理 API 生成图片,支持三级图片来源回退。

输入:string(提示词)
输出:[]GeneratedImage(原始图片列表)

图片来源优先级:
1. 任务级参考图 (ReferenceImageData) → 图生图 (/images/edits)
2. 工程级参考图 (ProjectReferenceImage URL) → 下载后图生图
3. 无参考图 → 纯文生图 (/images/generations)

三级回退逻辑:

if len(refData) > 0 {
    // 1. 任务级参考图:直接使用 base64 解码后的数据
    return GenerateImagesFromRef(ctx, prompt, refData, params)
}
if projectRefURL != "" {
    // 2. 工程级参考图:下载后使用
    refData, err = downloadImage(ctx, projectRefURL)
    if err == nil {
        return GenerateImagesFromRef(ctx, prompt, refData, params)
    }
    // 下载失败,回退到纯文生图
}
// 3. 纯文生图
return GenerateImages(ctx, prompt, params)

外部 API 弹性:

HTTP 状态 行为 说明
200 OK 解析响应 成功
5xx 自动重试(默认 2 次,间隔 5s) 服务端临时故障
4xx 立即失败,不重试 客户端错误(参数错误等)
超时 10 分钟(由 WorkerPool context 控制) 长任务保护

Mock 降级:

当 APIKey 为空时,生成彩色占位图(纯色 PNG),便于开发和演示:

if imgCfg.APIKey == "" {
    l.Warn("image API key not configured, using mock")
    return generateMockImages(size, count)
}

StatePostHandler:将原始图片写入 state.RawImages。

3. QualitySupervisor — 质量检查

职责:检查生成图片的质量和风格一致性,决定路由目标。

输入:[]GeneratedImage
输出:PipelineInput(用于路由分支读取)

决策逻辑:
1. 调用 CheckQuality(ctx, images, style)
2. pass=true → NextNode = "format_adapter"
3. pass=false + RetryCount < 3 → RetryCount++, NextNode = "prompt_optimizer"
4. pass=false + RetryCount >= 3 → NextNode = "format_adapter"(降级)

[!tip] 重试策略思考

为什么是 3 次?

  • 第 1 次失败:LLM API 波动或偶发噪声,重试大概率通过
  • 第 2 次失败:提示词可能不够精确,重新优化后改善
  • 第 3 次仍失败:当前参数组合确实无法生成合格图片,继续重试只会浪费资源

超过 3 次后 降级到 FormatAdapter——即使结果不完美,也比永远阻塞管线要好。这就是「宁可降级,不可阻塞」的原则。

路由分支(Eino AddBranch):

g.AddBranch(nodeQualitySupervisor, compose.NewGraphBranch(
    func(ctx context.Context, _ PipelineInput) (string, error) {
        var next string
        compose.ProcessState[*PipelineState](ctx, func(_ context.Context, state *PipelineState) error {
            next = state.NextNode
            return nil
        })
        return next, nil
    },
    map[string]bool{nodePromptOptimizer: true, nodeFormatAdapter: true},
))

质量检查器:

检查器 行为 用途
defaultCheckQuality 始终返回 true 生产环境(当前默认)
NewCountedQualityChecker(n) 第 n 次调用后通过 测试重试逻辑
AlwaysFailQualityChecker 始终返回 false 测试降级路径

[!tip] 可扩展性:QualityChecker 是一个可替换的函数变量,未来可接入 LLM 视觉模型进行真正的质量评估。

4. FormatAdapter — 格式适配

职责:将原始图片转换为游戏引擎友好的格式。

输入:PipelineInput
输出:PipelineOutput

处理模式:
├── 精灵表模式 (Format="spritesheet", 单张图)
│   ├── PNG 解码
│   ├── splitsprite.Process() 切割为独立帧
│   ├── gifmaker.Encode() 生成 GIF 预览
│   └── 输出:原始精灵表 + 帧列表 + GIF 预览
│
└── 普通模式(多图或非精灵表)
    └── 原样透传

精灵表处理流程:

单张精灵表 PNG
    │
    ▼ splitsprite.Process()
独立帧列表 [frame_0, frame_1, ..., frame_N]
    │
    ├── 保留原始精灵表
    ├── 编码每帧为独立 PNG
    └── gifmaker.Encode() → 预览 GIF
    │
    ▼ PipelineOutput{
        Assets: [spritesheet.png, frame_000.png, ..., preview.gif]
        Metadata: {FrameWidth, FrameHeight, FrameCount, Directions, GIFURL}
    }

降级策略:

失败点 降级行为
splitsprite.Process() 失败 整张图作为单帧输出
gifmaker.Encode() 失败 跳过 GIF 预览,不阻塞管线
单帧编码失败 返回错误(无法降级)

进度上报

管线通过 context 注入 ProgressReporter 回调,实时上报进度:

type ProgressReporter func(stage string, progress int)

进度点:

阶段 进度值 触发时机
prompt_builder 10 首次进入 PromptOptimizer
prompt_builder 30 + retry*10 重试进入 PromptOptimizer
asset_generator 35 PromptOptimizer 完成
quality_supervisor 60 AssetGenerator 完成
quality_supervisor 50 + retry*10 质检不通过(重试)
format_adapter 85 质检通过或降级

进度通过 EventBus.Publish() 推送给 SSE 客户端。

阶段耗时监控

WithStageTimer 注入阶段计时器到 context,reportProgress 在每次进度上报时计算上一阶段的耗时并上报 Prometheus:

metrics.PipelineStageDuration.WithLabelValues(prevStage).Observe(elapsed.Seconds())

监控指标:

Prometheus 指标 类型 Label 说明
gen2d_pipeline_total Counter status 管线执行总量 (success/fail/timeout)
gen2d_pipeline_duration_seconds Histogram status 端到端耗时
gen2d_pipeline_stage_duration_seconds Histogram stage 各阶段耗时
gen2d_pipeline_retries_total Counter stage 重试次数
gen2d_pipeline_tasks_active Gauge — 当前活跃管线数

Eino Graph 编译与执行

func RunPipeline(ctx context.Context, in PipelineInput) (*PipelineOutput, error) {
    // 1. 创建 Graph
    g, err := NewGenerateGraph()

    // 2. 编译(maxRunSteps=20 防止无限循环)
    r, err := g.Compile(ctx, compose.WithMaxRunSteps(20))

    // 3. 执行
    output, err := r.Invoke(WithStageTimer(ctx), in)

    return &output, nil
}

安全机制:

机制 说明
WithMaxRunSteps(20) 最大执行步数,防止无限循环(3 次重试 * 4 节点 + 余量)
context.WithTimeout(10min) WorkerPool 层面的任务超时
context.Canceled 优雅关闭时取消正在执行的管线

PipelineInput 完整结构

type PipelineInput struct {
    ProjectID             string            // 工程 ID
    TaskID                string            // 任务 ID
    Prompt                string            // 用户原始文本
    AssetType             string            // sprite / background / ui / animation
    ProjectStyle          map[string]string // 工程风格键值对
    TaskStyle             map[string]string // 任务风格覆盖
    Params                AssetParams       // 技术参数
    RejectReason          string            // 重试时由 state 注入
    Tags                  []string          // 用户选择的标签
    UserNote              string            // 用户额外描述
    ReferenceImageData    []byte            // 任务级参考图(base64 解码后)
    ProjectReferenceImage string            // 工程级参考图 CDN URL
    GlobalStylePrompt     string            // 工程全局风格提示词
}

关联文档