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

- 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

@@ -15,6 +15,7 @@ import os
from typing import Any, Protocol
from .constants import IMAGE_RETRY_ATTEMPTS, IMAGE_RETRY_BACKOFF_BASE, IMAGE_SIZE_DEFAULT
from .image_scorer import score_image
from .storyboard import plan_image_set, sanitize_text
logger = logging.getLogger(__name__)
@@ -126,15 +127,24 @@ async def generate_storyboard_images(
reference_images: list[bytes] | None = None,
analysis: dict | None = None,
strategy: str | None = None,
target_role: str | None = None,
custom_prompt: str | None = None,
) -> list[dict]:
"""
按 storyboard 逐张生图asyncio.gather 并发),返回每张结果列表。
strategy: None=默认叙事,'A'/'B'/'C'=三套正交叙事策略
target_role: 非空时只生成该 role 那一张R2 单张重生)
custom_prompt: 非空时追加到每张 per_prompt 末尾R2 人工提示词)
每项:{role, name, image_bytes, error}
"""
plan = plan_image_set(note, product, image_count, analysis, strategy=strategy)
storyboard = plan["storyboard"]
base_prompt = plan["base_prompt"]
# R2 单张重生:只保留目标 role 的分镜(匹配不到则原样全跑,避免空结果)
if target_role:
_filtered = [it for it in storyboard if it.get("role") == target_role]
if _filtered:
storyboard = _filtered
async def _gen_one(item: dict) -> dict:
# 逐图 prompt 9 字段(扒 promptFromStoryboard:323-334每张差异化
@@ -156,12 +166,31 @@ async def generate_storyboard_images(
"中文文字少而清晰,主标题+最多3个短点位可自然用✅✨🌿💧🪞🧴📦🔍种草符号但不堆砌"
"不要生成App截图或笔记详情页界面。"
)
# R2 人工提示词:追加到末尾权重最高,但不覆盖前面合规/真实约束
if custom_prompt:
per_prompt += f"\n运营补充要求(在不违反上述合规与真实约束前提下尽量满足):{sanitize_text(custom_prompt, 200)}"
try:
img_bytes = await generate_one_image(client, per_prompt, reference_images)
return {"role": item["role"], "name": item["name"], "image_bytes": img_bytes, "error": None}
# 注gpt-image-2 渲染中文偶发错别字(约1/6)。vision/OCR 文字校验闸门实测
# 不可靠(漏报形近字+幻觉误伤品牌词),倩倩姐2026-06-16拍板先撤,纯生图,
# 错别字作已知问题记录,后续迭代再处理。详见记忆 clover-image-text-check-shelved。
#
# C3 canvas 叠字口子(倩倩姐2026-06-12拍板"只留口子不实现")
# 当 OVERLAY_TEXT_RENDER_ENABLED=True 时,此处由 PIL 在模型出的干净底图上
# 叠 item['overlay_text']/brand_keyword(字体资源+排版坐标后续补),彻底解决中文乱码。
# TODO(C3-overlay): from .constants import OVERLAY_TEXT_RENDER_ENABLED
# if OVERLAY_TEXT_RENDER_ENABLED: img_bytes = overlay_text_on_image(img_bytes, item)
# E12 AI评图分只做展示+排序,绝不进飞轮权重,失败返 None 不阻断(倩倩姐2026-06-16)。
visual = await score_image(client, img_bytes)
return {"role": item["role"], "name": item["name"], "image_bytes": img_bytes,
"error": None, "text_review": None,
"ai_visual_score": (visual or {}).get("score"),
"ai_visual_note": (visual or {}).get("note")}
except Exception as exc:
logger.error("分镜 %s 生图失败: %s", item["role"], exc)
return {"role": item["role"], "name": item["name"], "image_bytes": None, "error": str(exc)}
return {"role": item["role"], "name": item["name"], "image_bytes": None,
"error": str(exc), "text_review": None,
"ai_visual_score": None, "ai_visual_note": None}
# 限并发apiports 图片接口有 QPS 限制6 张全并发会撞 429/503
concurrency = int(os.environ.get("IMAGE_CONCURRENCY", "2"))