10 KiB
10 KiB
Reco Engine(推荐引擎编排)|Tasks
对应计划:
spec_kit/Personalized Reco/modules/reco-engine/plan.md执行规则:
- 本任务清单可执行、可验证;每项完成后在“状态”处标记为
已完成并补充必要的证据(测试用例/日志/截图/输出)。- 禁止破坏性数据库操作(如需必须先征得同意并回复“允许操作数据库”)。
- 本模块默认约定:
locale主要来自客户端 API 入参;若未传,默认en- Feed:
feed_allow_partial=true且feed_fill_with_fallback=true(允许不足,但会尝试回退补齐)- Hard Filter:仅实现硬规则集合(不实现
UserProfileV1_2_Extended.hard_rules扩展)explanations:默认开启(但建议输出“轻量 explanations”,避免载荷过大)
0. 准备与对齐(不改代码)
- 确认依赖模块接口未变更(状态:已完成)
- 检查点:
ContentRepository.fetch_candidates(...)入参含locale/fallback_level/exclude_content_idsscoring.score_content(...)可用且 Push 默认启用P_uncertaintyrerank_freqcap.rerank_and_freqcap(...)可用且会在缺失recent_*时跳过该维度过滤observability.RecoMetaBuilder的字段口径与empty_reason规则不变
- 证据:
server/app/features/personalized_reco/content_repository/interface.py:fetch_candidates(..., locale, fallback_level, exclude_content_ids)server/app/features/personalized_reco/scoring/score.py:score_content(...)server/app/features/personalized_reco/rerank_freqcap/rerank.py:rerank_and_freqcap(..., recent_author_ids=None, recent_template_ids=None)server/app/features/personalized_reco/observability/builder.py:RecoMetaBuilder.build()与compute_empty_reason
- 检查点:
1. 代码骨架与类型(新增 reco_engine 模块)
-
创建目录与初始化文件(状态:已完成)
- 目标路径:
server/app/features/personalized_reco/reco_engine/ - 文件:
__init__.pytypes.pydefaults.pyutils.pyhard_filter.pyorchestrator.py
- 验收:可被
from app.features.personalized_reco.reco_engine import ...导入 - 证据:
- 已新增:
server/app/features/personalized_reco/reco_engine/__init__.py - 导出入口:
from app.features.personalized_reco.reco_engine import recommend
- 已新增:
- 目标路径:
-
定义核心类型(状态:已完成)
types.py建议包含:RecoConstraints(可选过滤:exclude_content_ids/exclude_author_ids/exclude_template_ids/max_candidates_limit/recent_author_ids/recent_template_ids)RecoEngineConfig(Feed 补齐策略、候选倍率等)RecommendedItem(content_id/text/final_score/fallback_level_final/explanations)RecoEngineResult(items/meta)HardFilterResult(kept_items/risk_filtered_count_by_flag/removed_count/optional_hits)
- 验收:类型可在单测中直接构造与序列化(若用 pydantic)
- 证据:已实现于
server/app/features/personalized_reco/reco_engine/types.py
-
默认配置落地(状态:已完成)
defaults.py建议:get_default_engine_config(scene)或统一RecoEngineConfig()- 候选倍率:Feed
10,Push/Widget30(可配置) - Feed 策略默认:
allow_partial=true、fill_with_fallback=true
- 验收:不传 config 时引擎可稳定运行
- 证据:已实现于
server/app/features/personalized_reco/reco_engine/defaults.py
-
工具函数:ID 与 locale 的防御式处理(状态:已完成)
utils.py建议:normalize_int_id_list(mixed_ids) -> list[int]:解析str|int,无效值忽略merge_exclude_ids(already, touched, extra) -> list[int]normalize_or_default_locale(locale) -> "en"|"tc":缺失默认en;非法时抛出/返回错误由 orchestrator 捕获
- 验收:输入包含
"1" / 1 / "abc" / None不报错 - 证据:已实现于
server/app/features/personalized_reco/reco_engine/utils.py
2. Hard Filter(硬过滤)实现
- 实现硬规则集合(状态:已完成)
- 文件:
hard_filter.py - 必须实现规则:
- 全场景:
block_health_medical一律过滤 U.stage.unknown=1:过滤unsafe_for_stage_unknownU.stage.parenting=1:过滤unsafe_for_stage_parentingU.emotion_score <= 0.2:过滤unsafe_for_emotion_low- 跨维度规则:
U.stage.unknown=1且C.need_suitability[parenting_pressure]=1且C.personalization_power=1→ 过滤
- 全场景:
- 输出统计:
risk_filtered_count_by_flag: dict[str,int](按命中的 risk_flag 计数;跨维度规则可用固定 key 如rule:unknown_stage_parenting_pressure_power1)
- 验收:
- 传入 3 条候选,命中规则的被剔除
risk_filtered_count_by_flag的数值与剔除条数一致
- 证据:已实现于
server/app/features/personalized_reco/reco_engine/hard_filter.py
- 文件:
3. Orchestrator(编排器)实现
-
实现
recommend(...)主入口(状态:已完成)- 文件:
orchestrator.py - 函数形态建议:
async def recommend(*, repo: ContentRepository, scene, user_profile, already_recommended_ids, touched_or_viewed_ids, k, now, locale=None, constraints=None, config=None) -> RecoEngineResult
- 关键编排步骤(每次 fallback level 都要跑一遍):
- Candidate:
repo.fetch_candidates(...) - Hard Filter:
hard_filter(...) - Soft Scoring:
score_content(...) - Rerank/Freqcap:
rerank_and_freqcap(...) - Serve:截断到
k并构造RecommendedItem - Meta:用
RecoMetaBuilder逐阶段填充并build()
- Candidate:
- 验收:
- 任意
k(含 0)不报错 - 输出结构稳定:
items与meta永远存在
- 任意
- 证据:已实现于
server/app/features/personalized_reco/reco_engine/orchestrator.py
- 文件:
-
Fallback Ladder 回退循环(状态:已完成)
- 行为:
- 依次尝试
fallback_level in [0,1,2,3] - 每层更新
meta_builder.set_fallback_level_final(level, reason=...) - 每层记录
fallback_trace并写入meta.config_snapshot
- 依次尝试
- Feed 策略(默认):
served_k < k时继续回退补齐,直到k或 L3- 若最终仍不足,允许返回不足,但
served_k/fallback_level_final必须正确
- 验收:
- 构造一个“强过滤 + 频控后为空”的场景能触发逐级回退
- 证据:
meta.config_snapshot.fallback_trace会记录每层的 raw/after_hard/after_dedup/after_freqcap/served_total
- 行为:
-
explanations 默认开启但保持轻量(状态:已完成)
- 建议默认包含:
fallback_level_usedhard_filter_hits(命中的 risk_flags/规则 id)score_summary(可选:只保留少量关键字段,如S_core/S_personal/P_uncertainty/P_risk,不输出全量 breakdown)
- 验收:
- 返回载荷可控(Feed 30 条不会过大)
- 证据:explanations 仅包含
fallback_level_used/hard_filter_hits/score_summary
- 建议默认包含:
-
异常兜底与 meta 记录(状态:已完成)
- 要求:
- 捕获
normalize_locale抛错、repo 查询异常、单条内容打分异常等 - 返回
items=[],并在meta.config_snapshot写入{"error": "...", "stage": "..."}(避免 500)
- 捕获
- 验收:
- 传入不支持的 locale(如
jp)时不会导致接口崩溃
- 传入不支持的 locale(如
- 证据:
normalize_locale失败时返回空 items,且meta.config_snapshot.stage="normalize_locale"
- 要求:
4. 与现有模块的对齐与集成
-
对齐
rerank_freqcap的作者/模板冷却输入含义(状态:已完成)- 含义说明:
recent_author_ids/recent_template_ids表示“冷却窗口内已触达的作者/模板集合”- 本模块不负责计算窗口裁剪;调用方需按
cooldown_*_days裁剪后再传
- 默认策略:
- 若调用方不提供,则传
None,由rerank_freqcap记录缺失并跳过该维度过滤(句子级去重仍有效)
- 若调用方不提供,则传
- 验收:
- 不提供
recent_*时不报错,且 meta 中freqcap_filtered_counts仍有 sentence 维度计数
- 不提供
- 证据:引擎透传
recent_author_ids/recent_template_ids(默认为 None);rerank_freqcap自身会记录缺失维度
- 含义说明:
-
对齐
RecoMetaBuilder阶段字段写入点(状态:已完成)- 必须写入:
- raw / after_hard_filter / after_dedup / after_freqcap / served_k / fallback_level_final
risk_filtered_count_by_flag、freqcap_filtered_counts
- 验收:
empty_reason可区分pool_empty / hard_filter_all / freqcap_all
- 证据:单测覆盖
pool_empty / hard_filter_all / freqcap_all
- 必须写入:
5. 单元测试(必做)
- 新增
server/tests/test_reco_engine.py(状态:已完成)- 测试用例建议:
k=0返回空 items,meta.served_k=0- raw=0 → empty_reason=
pool_empty - raw>0 且 after_hard=0 → empty_reason=
hard_filter_all - raw>0 且 after_freqcap=0 且 after_hard>0 → empty_reason=
freqcap_all - 去重生效:输出不包含 already/touched ids
block_health_medical必挡- Push 缺失画像字段时仍稳定(repository 会至少 L1;引擎 meta 与 fallback_trace 正确)
- 验收:
pytest全绿(只跑相关 tests 也可) - 证据:
- 新增文件:
server/tests/test_reco_engine.py - 在本机 venv 下执行:
server/.venv/bin/python -m pytest -q→19 passed
- 新增文件:
- 测试用例建议:
6. 文档与总览标记(仅在全部任务完成后做)
-
更新本子模块执行状态(状态:已完成)
- 文件:
spec_kit/Personalized Reco/modules/reco-engine/tasks.md - 要求:本文件所有任务项标记为
已完成,并补齐证据 - 证据:本文件已全部打勾并补充证据
- 文件:
-
更新大需求总览
overview.md(状态:未开始)- 文件:
spec_kit/Personalized Reco/overview.md - 要求:当
reco-engine全部任务完成后,将第 6 项 “已实施/已完成” 并补充变更记录(日期 + 简述)
- 文件: