- 缓存热点批量流程(有采集缓存不触发 Google) - 简报不足直接从采集缓存生成(轻量补齐) - 三图合成(模特/印花/底图)+ 底图压缩 <2MB - 热点去重→风格去重自动切换 + 不适合类目 review 兜底 - 透明背景(background=transparent)+ 提示词清洗(敏感词/背景描述) - 任务前 basemap 校验 + 模板国家校验 + 模特任务级分配
144 lines
6.4 KiB
Python
144 lines
6.4 KiB
Python
"""Mock LLM 后端:启发式兜底(无 key 也能端到端跑通)。
|
|
|
|
逻辑:黑名单硬拦 -> 常识风险词标 review -> 动态风格/配色推导 -> 分类。
|
|
这是生产环境 LLM 不可用时的安全降级路径,保证流水线永远能产出可用结果。
|
|
"""
|
|
from typing import Any, Dict, List
|
|
|
|
from ..classify import classify, prompt_suggestion
|
|
from ..style_rules import derive_style_palette, derive_composition
|
|
|
|
|
|
def _dedup_limit(items: List[str], limit: int) -> List[str]:
|
|
"""去重(大小写不敏感)并限量,保留首次出现顺序。"""
|
|
seen = set()
|
|
out: List[str] = []
|
|
for it in items:
|
|
it = (it or "").strip()
|
|
if not it:
|
|
continue
|
|
low = it.lower()
|
|
if low in seen:
|
|
continue
|
|
seen.add(low)
|
|
out.append(it)
|
|
if len(out) >= limit:
|
|
break
|
|
return out
|
|
|
|
|
|
# 常识风险词(兜底用;真实判定交给 LLM)。同时被 seed_node 复用为「种子护栏」,
|
|
# 避免真人/IP/平台词作为 Google Trends 相关查询种子浪费抓取。
|
|
COMMON_RISK_WORDS = [
|
|
"disney", "marvel", "nike", "adidas", "apple", "iphone", "mcdonalds",
|
|
"mcdonald", "starbucks", "coca", "pepsi", "pokemon", "mario", "hello kitty",
|
|
"sanrio", "sonic", "minions", "barbie", "harry potter", "batman", "spiderman",
|
|
"star wars", "fortnite", "roblox", "minecraft", "tiktok", "netflix", "pearl jam",
|
|
"nirvana", "taylor swift", "trump", "biden", "kardashian", "lebron", "kanye",
|
|
"kick", "gta", "ufc", "westmeath", "lottery", "prison break", "margot robbie",
|
|
"dana white", "euro", "spotify", "youtube", "instagram", "xbox", "playstation",
|
|
"noah kahan", "gina carano", "camry", "hurricanes", "eras tour",
|
|
"springsteen", "reiner", "eliza lopes", "camilla", "h&m", "truck accident attorney",
|
|
]
|
|
|
|
# 原创短标语池(mock 模式的 slogan;纯原创、无版权无品牌)
|
|
# 原创短标语池(mock 模式的 slogan;纯原创、无版权无品牌)
|
|
# 英语通用 + 各国语言(JP=日语短标语),按国家动态注入
|
|
_STABLE_SLOGANS_EN = [
|
|
"good vibes", "stay cozy", "happy place", "be kind", "dream big",
|
|
"keep smiling", "sunshine", "peace love", "stay wild", "pet the cat",
|
|
"coffee first", "tiny paws", "warm hugs", "soft life", "grow slowly",
|
|
"lucky charm", "sweet dreams", "go outside", "mindful", "lazy days",
|
|
]
|
|
_STABLE_SLOGANS_JP = [
|
|
"ゆめいっぱい", "やさしい気持ち", "ずっと元気", "おだやかな日", "きょうもハッピー",
|
|
"ねこが好き", "いっしょにね", "ぽかぽか", "はるの風", "なつのおもいで",
|
|
"きらきら", "わくわく", "のんびり", "しあわせ", "えがお",
|
|
]
|
|
|
|
|
|
def _stable_slogan(topic: str, country: str = "") -> str:
|
|
"""按主题哈希稳定选一条原创标语(同一主题缓存一致;mock 兜底用)。
|
|
country=JP → 日语短标语;其他国家 → 英语。"""
|
|
import hashlib
|
|
pool = _STABLE_SLOGANS_JP if str(country).upper() == "JP" else _STABLE_SLOGANS_EN
|
|
h = int(hashlib.md5((topic or "").encode("utf-8")).hexdigest(), 16)
|
|
return pool[h % len(pool)]
|
|
|
|
|
|
class MockBackend:
|
|
name = "mock"
|
|
|
|
def screen(
|
|
self,
|
|
topics: List[str],
|
|
country: str,
|
|
aesthetic_hint: str,
|
|
system_prompt: str,
|
|
blacklist: List[str],
|
|
batch_size: int = 12,
|
|
) -> List[Dict[str, Any]]:
|
|
bl = [b.lower() for b in (blacklist or [])]
|
|
out: List[Dict[str, Any]] = []
|
|
for t in topics:
|
|
tl = t.lower()
|
|
hits = [b for b in bl if b and b in tl]
|
|
blocked = bool(hits)
|
|
soft_hits = [w for w in COMMON_RISK_WORDS if w in tl]
|
|
if blocked:
|
|
risk_level = "blocked"
|
|
elif soft_hits:
|
|
risk_level = "review"
|
|
else:
|
|
risk_level = "safe"
|
|
cat = classify(t)
|
|
art_style, palette = derive_style_palette(t, country, category=cat)
|
|
# motif:从分类模板取核心描述,去掉配色/白底尾巴,保持干净可复用
|
|
motif = prompt_suggestion(t, cat).split(" --no ")[0].split(",")[0].strip()
|
|
composition = derive_composition(t, cat)
|
|
negative = ("no real people, no likeness of any person, no copyrighted characters, "
|
|
"no brand logos, no trademarks, no celebrity, no readable text unless safe")
|
|
slogan = _stable_slogan(t, country) # 按国家语言(JP→日语短标语,其余英语)
|
|
out.append({
|
|
"topic": t,
|
|
"safe_for_print": not blocked,
|
|
"risk_level": risk_level,
|
|
"risk_reasons": [f"命中黑名单: {hits}"] if hits
|
|
else (["疑似受保护实体,需人工复核"] if soft_hits else []),
|
|
"suitable_for_print": not blocked,
|
|
"design_category": cat,
|
|
"concept": f"(启发式兜底)围绕「{t}」做原创{art_style}风格印花",
|
|
"motif": motif,
|
|
"art_style": art_style,
|
|
"color_palette": palette,
|
|
"composition": composition,
|
|
"slogan": slogan,
|
|
"negative_prompt": negative,
|
|
"confidence": 0.55 if risk_level == "safe" else 0.4,
|
|
})
|
|
return out
|
|
|
|
def generate_seeds(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""规则生成种子词(零 API 成本):借用月份主题、临近节日、trending 派生、历史热点。"""
|
|
month_themes = context.get("month_themes", []) or []
|
|
upcoming = context.get("upcoming_holidays", []) or []
|
|
trending = context.get("trending_seeds", []) or []
|
|
history = context.get("history_hotspots", []) or []
|
|
|
|
style: List[str] = []
|
|
related: List[str] = []
|
|
# 月份主题 + 临近节日 → 风格种子(带美学倾向)
|
|
style += list(month_themes)
|
|
style += [f"{h.lower()} aesthetic" for h in upcoming]
|
|
style += trending[:4]
|
|
# related:历史 safe 热点 + 剩余 trending(行业交叉验证)
|
|
related += history[:6]
|
|
related += trending[4:8]
|
|
|
|
max_style = int(context.get("max_style_seeds", 10) or 10)
|
|
max_related = int(context.get("max_related_seeds", 10) or 10)
|
|
return {
|
|
"style_seeds": _dedup_limit(style, max_style),
|
|
"related_seeds": _dedup_limit(related, max_related),
|
|
}
|