Files

121 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""动态策略:以 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_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_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,
}