games-development-ai/cheap-worker/cheap_genre_route.py
lili d2b916f112 feat(cheap): T4 生产 create 路 genre 最小路由(brief→五类确定性词表·先验序=特异性降序·无命中回退通用) (M3 cutover 放行检查单增补)
- cheap_genre_route:纯词表路由(零 LLM/零 IO/确定性),规则表数据化单源(_GENRE_RULES 顺序即先验序:
  非遗>TRPG>经营>解谜>剧情,特异词前泛词后);映射对齐 GENRE_BY_TEMPLATE 五类模板
- cheap_service_driver:取代硬编码 scaffold_template=None——命中选 per-genre 黄金骨架+genre 透传丰富度评分
  (生产 create 路与 S5 bake_off 复验同轨);无命中 (None,None) 走通用 _template(路由只加不减)
- tests/test_genre_route:五类命中/无命中回退/多类共现先验序/GENRE_BY_TEMPLATE 一致性/确定性/脏输入 14 用例

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:17:54 -07:00

66 lines
4.0 KiB
Python

"""cheap_genre_route.py — brief → genre 确定性关键词路由(生产 create 路最小品类接线)。
背景(M3 cutover plan「放行检查单增补 2026-07-04」):live Service 创建路 cheap_service_driver 此前硬编码
scaffold_template=None,不选品类骨架、不透传 genre——W-GENRE 四件套(品类 skill/骨架/few-shot/评分尺)在
生产 create 路不通电,而 S5 复验(bake_off)是显式传品类的,复验口径与放量后生产口径不同轨。本模块补最小
路由:纯词表匹配、零 LLM、确定性(同 brief 恒同结果),词表映射到既有 GENRE_BY_TEMPLATE 的五类模板;
无命中回退 None(走通用 _template,与旧行为一致——路由只加不减,失配面=原行为)。
规则表数据化:_GENRE_RULES 是唯一规则源(顺序即先验序),改词表/调先验只动这一张表,不动逻辑。
"""
from typing import Optional, Tuple
# 品类路由规则表(唯一规则源;列表顺序 = 先验序)。
# 先验序依据:按【词面特异性】降序——特异词品类在前(非遗/TRPG 的词几乎不出现在别的品类 brief 里,
# 误伤率最低),泛词品类垫后(「剧情/故事」在各品类 brief 里都常见,最易过泛匹配)。多类词共现时取
# 序前者:如「经营一家店铺,顾客的故事各不相同」同时命中经营与剧情 → 判经营(特异性高者定品类,
# 泛词只是修饰)。每条:(genre 键, scaffold 模板名, 关键词元组)——genre/模板对齐 cheap_verify.GENRE_BY_TEMPLATE。
_GENRE_RULES: Tuple[Tuple[str, str, Tuple[str, ...]], ...] = (
# ① 非遗(heritage):非遗工艺词特异性最高,与其他品类零重叠。
("heritage", "_template-feiyi", (
"非遗", "剪纸", "皮影", "刺绣", "年画", "扎染", "泥塑", "糖画", "风筝制作",
"传统工艺", "传统手艺", "民俗", "戏曲", "京剧", "昆曲", "陶艺", "木雕", "漆器",
)),
# ② TRPG:跑团/骰子/地下城词特异性高。
("trpg", "_template-trpg", (
"trpg", "跑团", "骰子", "地下城", "龙与地下城", "dnd", "角色扮演冒险", "冒险者小队",
"桌面角色扮演", "属性检定", "掷骰",
)),
# ③ 经营(sim-business):开店/客人/生意词较特异(「服务」单字面泛,组合词收窄)。
("sim-business", "_template-shop", (
"经营", "开店", "店铺", "商店", "餐厅", "饭店", "奶茶店", "咖啡店", "咖啡馆", "面馆",
"小卖部", "杂货店", "顾客", "客人", "接客", "点单", "上菜", "生意", "收银", "摆摊",
)),
# ④ 解谜(puzzle):谜题/推理词。
("puzzle", "_template-puzzle", (
"解谜", "谜题", "谜语", "推理", "密室", "华容道", "数独", "拼图", "解密", "机关",
"找不同", "找茬",
)),
# ⑤ 剧情(narrative):故事/分支词最泛,垫底(仅当没有更特异品类命中时才路由到剧情)。
("narrative", "_template-story", (
"剧情", "故事", "文字冒险", "互动小说", "视觉小说", "对话选择", "分支", "多结局",
"章节", "叙事",
)),
)
def route_genre(brief: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
"""把用户 brief 确定性路由到 (scaffold_template, genre);无命中 → (None, None) 走通用模板(原行为)。
匹配口径:大小写不敏感的子串包含(brief 是自然语言短句,词表词都是低歧义中文词/固定拼写,子串够用、
不引分词依赖);按 _GENRE_RULES 先验序扫,首个命中即定(多类词共现取先验序前者)。空/非字符串 brief
→ 无命中回退。纯函数、零 IO、零 LLM——同 brief 恒同结果(生产路由必须可复验)。
Returns:
(scaffold_template, genre):如 ("_template-shop", "sim-business");无命中 (None, None)。
"""
if not brief or not isinstance(brief, str):
return None, None
text = brief.lower()
for genre, template, keywords in _GENRE_RULES:
for kw in keywords:
if kw.lower() in text:
return template, genre
return None, None