121 lines
5.4 KiB
Python
121 lines
5.4 KiB
Python
"""动态策略:以 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_seeds(单一总量,不再按类型分,从统一池随机抽后均分
|
||
style/related);兼容旧参数 max_style_seeds / max_related_seeds(0 或缺失=不限)。
|
||
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_total = int(context.get("max_seeds") or 0)
|
||
limit_style = int(context.get("max_style_seeds") or 0)
|
||
limit_related = int(context.get("max_related_seeds") or 0)
|
||
|
||
if limit_total > 0:
|
||
# 不再按类型分:从统一池随机抽 max_seeds 个,均分到 style/related(各约一半)
|
||
pick = _weighted_sample(items, limit_total)
|
||
half = (len(pick) + 1) // 2
|
||
style_pick = pick[:half]
|
||
related_pick = pick[half:]
|
||
else:
|
||
# 兼容旧参数(max_style_seeds / max_related_seeds 分别限量)
|
||
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,
|
||
}
|