存量积累:生图素人感约束+评图分+幂等防重跑+审核回路

- 015-017迁移:image_candidate 文案复审/AI视觉分/重生标记
- constants C7素人感约束(反电商摆拍对齐真实笔记)+C3叠字口子
- celery visibility_timeout=2h 防长任务被误判重投重复烧钱(task75教训)
- image_scorer 评图分(只筛选+展示,真实信号才进飞轮权重)
- storyboard/image_gen/pipeline_io 生图存量
- task_actions/tasks/task_service/flywheel 审核回路+飞轮存量

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-18 11:16:42 +08:00
parent cefdbaabdc
commit 7f419f4c8b
16 changed files with 346 additions and 30 deletions

View File

@@ -29,6 +29,11 @@ celery_app.conf.update(
task_track_started=True,
task_acks_late=True, # 任务处理完才 ACK防丢失
worker_prefetch_multiplier=1, # 一次只取1条防长任务堆积
# 🔴 visibility_timeout 必须 > 单任务最长耗时。生图(gpt-image-2)单张可达 270s+
# 整条流水线(多图+评图重试)可能十几分钟。默认仅 1h 时,长任务超时会被 Redis broker
# 误判丢失而重新投递给另一 worker → 同一 task 被反复重跑、重复烧钱(task75 教训)。
# 设 2h 留足余量;配合 pipeline 入口幂等检查双保险。
broker_transport_options={"visibility_timeout": 7200},
task_routes={
"app.workers.tasks.run_generation_pipeline": {"queue": "generation"},
"app.workers.tasks.build_delivery_package": {"queue": "packaging"},

View File

@@ -145,10 +145,16 @@ def run_text_generation(db, clients, task, product_dict: dict, flywheel_fragment
def run_image_generation(db, clients, task, product_dict: dict,
push_fn, workspace_id: int, seq_start: int,
first_copy: dict, upload_base_path: str) -> int:
first_copy: dict, upload_base_path: str,
regen_strategy: str | None = None,
regen_role: str | None = None,
custom_prompt: str | None = None) -> int:
"""
Step6+7+8(image): 调 generate_storyboard_images → 后处理 → 存 ImageCandidate → 推 SSE。
返回 next_seq。
R2 局部重生(均 None=全量A/B/C)regen_strategy 限定只跑该套regen_role 配合限定该套该张;
custom_prompt 人工追加提示词。重生产出 is_regen=True 新增不删旧。
"""
import time
from app.services.ai_engine.image_gen import generate_storyboard_images
@@ -197,8 +203,17 @@ def run_image_generation(db, clients, task, product_dict: dict,
)
seq = seq_start
# 3套正交叙事 A/B/C每套各 image_count 张独立生图
for strategy in ("A", "B", "C"):
# R2: 限定重生套别(regen_strategy)则只跑该套,否则全量 A/B/C 三套正交叙事
_strategies = (regen_strategy,) if regen_strategy else ("A", "B", "C")
_is_regen = bool(regen_strategy or regen_role)
# 进度总数:单张重生=1单套=image_count全量=image_count×3
if regen_role:
_img_total = 1
elif regen_strategy:
_img_total = task.image_count
else:
_img_total = task.image_count * 3
for si, strategy in enumerate(_strategies):
t0 = time.monotonic()
img_success = True
img_error_code = None
@@ -210,6 +225,8 @@ def run_image_generation(db, clients, task, product_dict: dict,
image_count=task.image_count,
reference_images=reference_images or None,
strategy=strategy,
target_role=regen_role,
custom_prompt=custom_prompt,
))
except Exception as exc:
img_success = False
@@ -261,17 +278,25 @@ def run_image_generation(db, clients, task, product_dict: dict,
url=img_url,
seq=i + 1,
strategy=strategy, # 写入 A/B/C非 hardcode
is_regen=_is_regen, # R2 重生标记新增不删旧前端同strategy+role默认展示最新
# E12 AI评图分只落展示分,绝不碰 eval_score(留 NULL)AI 分不进飞轮权重
ai_visual_score=img_result.get("ai_visual_score"),
ai_visual_note=img_result.get("ai_visual_note"),
)
db.add(ic)
db.flush()
seq += 1
push_fn(task.id, workspace_id, "image_candidate", {
"candidate_id": ic.id, "strategy": strategy,
"candidate_id": ic.id, "strategy": strategy, "seq": i + 1,
"url": img_url, "role": img_result["role"],
"is_regen": _is_regen,
"ai_visual_score": img_result.get("ai_visual_score"),
"ai_visual_note": img_result.get("ai_visual_note"),
}, seq)
seq += 1
push_fn(task.id, workspace_id, "image_progress", {
"done": i + 1, "total": task.image_count, "strategy": strategy,
"done": si * task.image_count + (i + 1),
"total": _img_total, "strategy": strategy,
}, seq)
# 写 ai_call_logs每套一条失败不阻断