POD 趋势感知 Agent:缓存热点模式 + 三图合成 + 热点去重/风格去重 + review 兜底

- 缓存热点批量流程(有采集缓存不触发 Google)
- 简报不足直接从采集缓存生成(轻量补齐)
- 三图合成(模特/印花/底图)+ 底图压缩 <2MB
- 热点去重→风格去重自动切换 + 不适合类目 review 兜底
- 透明背景(background=transparent)+ 提示词清洗(敏感词/背景描述)
- 任务前 basemap 校验 + 模板国家校验 + 模特任务级分配
This commit is contained in:
2026-08-22 14:14:01 +08:00
commit f493bde8a9
98 changed files with 10280 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
"""动态策略:以 yaml 静态种子为基础,叠加动态种子(统一池 + 加权随机 + 用完全用)。
种子词机制(v49 起):
1. 全部类型放一起(统一池):静态 style + 静态 related + 月份主题 + 节日 + LLM 动态
——合并去重(跨类型同词只保留一个,权重累加 = 多来源更受重视);
2. 节日种子词提供权重:节日权重 3.0 > 月份主题 2.0 > 静态/动态 1.0,随机抽取时加权;
3. 每次随机取:从池中按权重随机抽取(不重复),limit 内数量;
4. 用完全用:池中种子数 ≤ 需要数时全部使用(不再随机限量/截断);
5. 每个国家独立配置:configs/countries/<country>.yaml 的 style.seeds / related.seed_keywords。
limit 由 seed_node 从 context 注入(max_style_seeds / max_related_seeds0 或缺失=不限)。
LLM 后端生成失败时自动回退到静态+节日主题,保证不中断。
"""
import random
from typing import Any, Dict, List
from .base import SeedStrategy
def _weighted_sample(pool: List[Dict[str, Any]], k: int) -> List[Dict[str, Any]]:
"""按权重随机不重复取 k 个;池数量 ≤ k(或用完)时全部返回(不随机限量)。"""
if k <= 0 or len(pool) <= k:
return list(pool)
out: List[Dict[str, Any]] = []
rest = list(pool)
for _ in range(k):
weights = [max(float(it["weight"]), 0.0) for it in rest]
if sum(weights) <= 0:
out.extend(rest)
break
idx = random.choices(range(len(rest)), weights=weights)[0]
out.append(rest.pop(idx))
return out
class DynamicStrategy(SeedStrategy):
name = "dynamic"
def resolve(
self,
country: str,
cc: Dict[str, Any],
context: Dict[str, Any],
llm_backend: Any = None,
) -> Dict[str, Any]:
base_style = list((cc.get("style", {}) or {}).get("seeds", []) or [])
base_related = list((cc.get("related", {}) or {}).get("seed_keywords", []) or [])
month_style = list(context.get("month_themes", []) or [])
holidays = list(context.get("upcoming_holidays", []) or [])
holiday_style = [f"{h.lower()} aesthetic" for h in holidays]
# 节日主题同时扩充 related 源(提高节日权重 + 增加 related 扩展)
holiday_related = [f"{h.lower()} tee" if not h.lower().endswith("day") else f"{h.lower()} gift"
for h in holidays]
# LLM 动态种子(失败回退,不影响静态/节日)
dyn_style: List[str] = []
dyn_related: List[str] = []
if llm_backend is not None and hasattr(llm_backend, "generate_seeds"):
try:
res = llm_backend.generate_seeds(context) or {}
dyn_style = list(res.get("style_seeds", []) or [])
dyn_related = list(res.get("related_seeds", []) or [])
except Exception as e: # noqa: BLE001
print(f"[seed] LLM 生成种子失败,仅用静态+节日主题: {e}")
# 1) 统一池:全部类型合并,跨类型去重(同词权重累加 = 多来源更受重视)
pool: Dict[str, Dict[str, Any]] = {}
def add(items: List[str], weight: float, src: str) -> None:
for it in items:
it = (it or "").strip()
if not it:
continue
key = it.lower()
if key in pool:
pool[key]["weight"] += weight
pool[key]["sources"].append(src)
else:
pool[key] = {"word": it, "weight": weight, "sources": [src]}
add(base_style, 1.0, "static")
add(base_related, 1.0, "static")
add(month_style, 2.0, "month")
add(holiday_style, 3.0, "holiday")
add(holiday_related, 3.0, "holiday")
add(dyn_style, 1.0, "dynamic")
add(dyn_related, 1.0, "dynamic")
items = list(pool.values())
limit_style = int(context.get("max_style_seeds") or 0)
limit_related = int(context.get("max_related_seeds") or 0)
# 2) 每次随机取(加权,不重复);池不足 → 全部用
style_pick = _weighted_sample(items, limit_style)
style_keys = {id(it) for it in style_pick}
remaining = [it for it in items if id(it) not in style_keys]
related_pick = _weighted_sample(remaining, limit_related)
return {
"style_seeds": [it["word"] for it in style_pick],
"related_seeds": [it["word"] for it in related_pick],
"dynamic": True,
"pool_size": len(items),
"pool": [it["word"] for it in items],
"llm_style_seeds": dyn_style,
"llm_related_seeds": dyn_related,
"holiday_style_seeds": holiday_style,
"holiday_related_seeds": holiday_related,
"static_style_seeds": base_style,
"static_related_seeds": base_related,
}