11 KiB
tags, create time
| tags | create time | |||||||
|---|---|---|---|---|---|---|---|---|
|
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 // 工程全局风格提示词
}