Files
2026-02-02 16:47:37 +08:00

10 KiB
Raw Permalink Blame History

Reco Engine推荐引擎编排Tasks

对应计划:spec_kit/Personalized Reco/modules/reco-engine/plan.md

执行规则:

  • 本任务清单可执行、可验证;每项完成后在“状态”处标记为 已完成 并补充必要的证据(测试用例/日志/截图/输出)。
  • 禁止破坏性数据库操作(如需必须先征得同意并回复“允许操作数据库”)。
  • 本模块默认约定:
    • locale 主要来自客户端 API 入参;若未传,默认 en
    • Feedfeed_allow_partial=truefeed_fill_with_fallback=true(允许不足,但会尝试回退补齐)
    • Hard Filter仅实现硬规则集合(不实现 UserProfileV1_2_Extended.hard_rules 扩展)
    • explanations:默认开启(但建议输出“轻量 explanations”避免载荷过大

0. 准备与对齐(不改代码)

  • 确认依赖模块接口未变更(状态:已完成)
    • 检查点
      • ContentRepository.fetch_candidates(...) 入参含 locale/fallback_level/exclude_content_ids
      • scoring.score_content(...) 可用且 Push 默认启用 P_uncertainty
      • rerank_freqcap.rerank_and_freqcap(...) 可用且会在缺失 recent_* 时跳过该维度过滤
      • observability.RecoMetaBuilder 的字段口径与 empty_reason 规则不变
    • 证据
      • server/app/features/personalized_reco/content_repository/interface.pyfetch_candidates(..., locale, fallback_level, exclude_content_ids)
      • server/app/features/personalized_reco/scoring/score.pyscore_content(...)
      • server/app/features/personalized_reco/rerank_freqcap/rerank.pyrerank_and_freqcap(..., recent_author_ids=None, recent_template_ids=None)
      • server/app/features/personalized_reco/observability/builder.pyRecoMetaBuilder.build()compute_empty_reason

1. 代码骨架与类型(新增 reco_engine 模块)

  • 创建目录与初始化文件(状态:已完成)

    • 目标路径server/app/features/personalized_reco/reco_engine/
    • 文件
      • __init__.py
      • types.py
      • defaults.py
      • utils.py
      • hard_filter.py
      • orchestrator.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
      • RecoEngineConfigFeed 补齐策略、候选倍率等)
      • RecommendedItemcontent_id/text/final_score/fallback_level_final/explanations
      • RecoEngineResultitems/meta
      • HardFilterResultkept_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 10Push/Widget 30(可配置)
      • Feed 策略默认:allow_partial=truefill_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_unknown
      • U.stage.parenting=1:过滤 unsafe_for_stage_parenting
      • U.emotion_score <= 0.2:过滤 unsafe_for_emotion_low
      • 跨维度规则:U.stage.unknown=1C.need_suitability[parenting_pressure]=1C.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 都要跑一遍):
      • Candidaterepo.fetch_candidates(...)
      • Hard Filterhard_filter(...)
      • Soft Scoringscore_content(...)
      • Rerank/Freqcaprerank_and_freqcap(...)
      • Serve截断到 k 并构造 RecommendedItem
      • MetaRecoMetaBuilder 逐阶段填充并 build()
    • 验收
      • 任意 k(含 0不报错
      • 输出结构稳定:itemsmeta 永远存在
    • 证据:已实现于 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_used
      • hard_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
    • 验收
      • 传入不支持的 localejp)时不会导致接口崩溃
    • 证据normalize_locale 失败时返回空 itemsmeta.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(默认为 Nonererank_freqcap 自身会记录缺失维度
  • 对齐 RecoMetaBuilder 阶段字段写入点(状态:已完成)

    • 必须写入
      • raw / after_hard_filter / after_dedup / after_freqcap / served_k / fallback_level_final
      • risk_filtered_count_by_flagfreqcap_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 返回空 itemsmeta.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 -q19 passed

6. 文档与总览标记(仅在全部任务完成后做)

  • 更新本子模块执行状态(状态:已完成)

    • 文件spec_kit/Personalized Reco/modules/reco-engine/tasks.md
    • 要求:本文件所有任务项标记为 已完成,并补齐证据
    • 证据:本文件已全部打勾并补充证据
  • 更新大需求总览 overview.md(状态:未开始)

    • 文件spec_kit/Personalized Reco/overview.md
    • 要求:当 reco-engine 全部任务完成后,将第 6 项 “已实施/已完成” 并补充变更记录(日期 + 简述)