# Personalized Reco|后端个性化推荐算法模块(解耦版)|Spec(索引版) > 阶段:高层规范(spec) > > 目标:把「个性化推荐算法」做成后端**独立模块**,输入用户画像与曝光历史,模块内部从数据库拉取文案候选并排序,输出推荐句子(支持 Feed / Push / Widget 三种场景的可调参策略)。 > > 依据: > > - `设计说明文档/個性化推薦算法規則.md` > - `设计说明文档/句子文案打分規則.md`(risk_flags 命名与语义必须严格对齐) > - `spec_kit/User Profile Scoring/spec.md`(用户画像输入契约,V1.2) --- ## 1. 背景与动机(摘要) 需要在后端实现一套跨场景一致、可调参、可观测、可回退的推荐流程,并将其做成独立模块以便复用(API / Celery 均可调用),降低 Push 等高风险场景的翻车概率。 已确认约束: - 数据库当前未设计且为空:本需求内新增 DB 设计与迁移子模块。 - risk_flags 等内容画像语义:严格按 `句子文案打分规则` 执行。 - 推荐请求:由客户端在请求时传入 `user_profile` 与历史集合。 - Widget 情绪区间(0.4~0.8):采用软降权。 - Push/Widget:需要频控与冷却;且 API 与 Celery 两种调用方式都需要。 --- ## 2. 目标(Goals) - **模块解耦**:推荐逻辑以独立包/子模块方式存在,暴露稳定接口,不与 FastAPI 路由/ Celery 任务强绑定。 - **统一 Pipeline**:Hard Filter → Soft Scoring → Rerank(多样性/新鲜度/疲劳),并支持候选不足的回退梯度(L0~L3)。 - **输入明确**:输入用户画像(允许字段缺失)、已推荐的文案 ID、已触达/浏览的文案 ID(用于去重/疲劳/频控)。 - **从 DB 取候选**:模块内部负责根据画像与场景召回候选,并从数据库读取文案画像/风险标记/元信息。 - **输出可直接下发**:输出推荐句子(含 content_id、文本、排序分与必要的解释字段用于打点/调参)。 - **可观测**:对候选规模、过滤原因、回退层级、 served_k 等关键指标提供统一事件结构,便于落点与报表。 --- ## 3. 非目标(Non-goals) - **不在本阶段定义**:内容生产(生成句子)、人工审核工作流、embedding/向量检索(V2 扩展)。 - **不绑定具体表结构**:仅规定“必须能读到的字段契约”,具体表名/索引/迁移由后续 plan 细化。 - **不负责推送/Widget 排程**:模块只负责“给定场景与约束,返回推荐结果”,调度由上层系统决定。 --- ## 4. 术语与数据对象(Definitions,摘要) ### 4.1 场景(Scene) - **feed**:探索优先(Exploration-first),返回序列 TopK(例如 30 条)。 - **push**:精确与安全优先(Precision-first),通常返回 Top1(或 TopN 供上层挑选)。 - **widget**:稳定与品牌一致性优先(Consistency-first),返回 Top1(每日一句)。 ### 4.2 用户画像 U(UserProfile) 用户画像结构以客户端问卷输出为准(V1.2),详见: - `spec_kit/User Profile Scoring/spec.md`(字段与缺失约定) - `modules/reco-engine/spec.md`(本模块如何消费画像与缺失判定) ### 4.3 文案画像 C(ContentProfile) 模块需能从 DB 读到(或通过视图/派生字段得到): - **content_id**(稳定主键) - **text**(句子文本) - **stage**(或可推导的 stage 标签) - **emotion_score**(0~1 或标记为 general) - **need_suitability / context_suitability**(离散适配表/JSON) - **personalization_power**(0 / 0.5 / 1) - **risk_flags**(集合/数组/位图均可,语义一致即可) - (可选)**author_id / template_id / review_confidence** --- ## 5. 模块边界与接口(API Contract) ### 5.1 入口函数(推荐引擎接口) 推荐模块对外暴露一个主入口,建议形式(不限定语言结构,但需表达同等信息): - **输入**: - `scene`: `"feed" | "push" | "widget"` - `user_profile`: `UserProfile` - `already_recommended_ids`: `list[int|str]` - `touched_or_viewed_ids`: `list[int|str]`(触达/浏览/点击等均归一为“已曝光”集合) - `k`: int(feed 默认 30,push/widget 默认 1) - `now`: 时间戳(用于时间衰减/冷却窗口) - `constraints`(可选):如黑名单 author/template、最大风险等级、频控窗口天数等 - **输出**: - `items`: 推荐结果数组(长度 ≤ k) - `meta`: 本次推荐的统计与解释字段(用于打点/调参) ### 5.2 输出结果项(RecommendedItem) 每条推荐至少包含: - `content_id` - `text` - `final_score` - `fallback_level_final` - `explanations`(轻量可选):如命中 need/context/stage、是否 general、被降个性化的原因(缺失/低置信度/回退) ### 5.3 数据访问抽象(Repository / Gateway) 为实现解耦,推荐模块**不得**直接依赖 FastAPI 的 `Depends`;应通过注入的 `ContentRepository` 读取数据: - `fetch_candidates(scene, user_profile, limit, fallback_level) -> list[ContentProfile]` - `fetch_contents_by_ids(ids) -> list[ContentProfile]`(用于补全曝光历史的画像信息时可用) 实现层可使用 `AsyncSession`(SQLAlchemy)完成具体查询,但算法层不感知 ORM/SQL。 --- ## 6. 推荐流程(统一 Pipeline) 本节是工程必须遵守的“骨架”,参数可按场景配置。 ### 6.1 Candidate Generation(候选集生成) 核心要求: - 依据 `U` 召回一个候选池:匹配 need/context/stage 的内容 + 一部分通用内容(general)。 - 若问卷允许跳过导致字段缺失(need/context/emotion_score 缺失),候选池需自动提高通用安全内容占比,并视为至少进入 **L1**(限制 `personalization_power ≤ 0.5`)。 ### 6.2 Hard Filter(硬性过滤) 基于 `risk_flags` 与产品规则做剔除(说明文档 3.x): - `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=1` 且 `need_suitability[parenting_pressure]=1` 且 `personalization_power=1` → 禁推。 Hard Filter 后需输出 `candidate_pool_size_after_hard_filter` 以及若清空的 `empty_reason`。 ### 6.3 Soft Scoring(软性打分) 采用说明文档的线性加权结构: 最终分: \[ final\_score(U,C_i)=\mathbb{I}[pass]\times\Big(S_{core}+S_{personal}+S_{fresh}-P_{fatigue}-P_{repeat}-P_{risk}-P_{uncertainty}\Big) \] 其中 \[ S_{core}=w_{need}S_{need}+w_{emotion}S_{emotion}+w_{stage}S_{stage}+w_{context}S_{context} \] 缺失字段的保守计算(说明文档 V1.2,必须实现): - `U.need` 缺失 → `S_need = 0.5` - `U.context` 缺失 → `S_context = 0.5` - `U.emotion_score` 缺失 → `S_emotion = 0.8`(general 仍为 0.8) 个性化加成(必须受回退梯度约束): \[ S_{personal}=\alpha \cdot personalization\_power \cdot \max(S_{need}, S_{context}) \] 不确定性惩罚(Push 建议默认开启,模块需支持开关): \[ P_{uncertainty}=\beta \cdot (1-conf_U)\cdot(1-conf_{C_i})\cdot personalization\_power \] ### 6.4 Rerank(重排:多样性/新鲜度/疲劳) 最小要求: - **去重**:排除 `already_recommended_ids` 与 `touched_or_viewed_ids`(同一句不重复)。 - **多样性**:作者/模板/标签多样性;Feed 场景建议使用 MMR(说明文档 4.5)。 - **频控与冷却**(Push/Widget 必做):同句/同作者/同模板在窗口 X 天内不重复(具体 X 由 plan 定参数)。 --- ## 7. 回退策略(Fallback Ladder) 候选不足或过滤/频控导致 served_k 过少时,必须按梯度回退,并输出最终 `fallback_level_final`: - **L0(正常)**:按场景默认召回配比。 - **L1(放宽匹配)**:need/context 命中阈值放宽;`personalization_power ≤ 0.5`。 - **L2(回退通用池)**:增加 general 低风险句;`personalization_power = 0`。 - **L3(兜底安全池)**:仅从通用安全句库/白名单池抽取。 规则: - 回退时必须“降个性化与降风险”,尤其 Push。 - 每次回退都要重新统计候选规模,并记录触发原因(如 `hard_filter_all` / `freqcap_all` / `pool_empty`)。 --- ## 8. 场景默认参数(V1 建议) ### 8.1 Feed(Exploration-first) - 候选配比:匹配 60% + 通用 30% + 探索 10% - 权重建议:`w_need=0.35, w_emotion=0.20, w_stage=0.15, w_context=0.30` - 序列:Top1 最高分,后续使用 MMR(λ=0.7) ### 8.2 Push(Precision & Safety-first) - 候选配比:need 强命中 70% + emotion 命中 20% + 通用安全 10% - 权重建议:`w_need=0.45, w_emotion=0.35, w_stage=0.15, w_context=0.05` - 默认启用 `P_uncertainty`;当 `fallback_level_final>0` 或画像缺失/低置信度时自动降个性化 ### 8.3 Widget(Consistency & Brand-first) - 候选池:以 general + `personalization_power≤0.5` 为主 - 权重建议:`w_need=0.25, w_emotion=0.25, w_stage=0.30, w_context=0.20` - 情绪区间约束:建议 `emotion_score` 限制在 0.4~0.8(过低/过高降权或过滤,具体由 plan 定) --- ## 9. 数据库与存储契约(DB Contract) 模块需要 DB 提供以下能力(不限定表名实现方式): - **按标签召回**:能按 need/context/stage/general、personalization_power、risk_flags 等条件筛选候选。 - **按 ID 批量拉取**:用于补全曝光历史或在 rerank 时补字段。 - **安全池支持**:L3 兜底必须能拉到一批“通用安全句”(白名单/低风险集合)。 性能要求(V1): - 单次推荐(k<=30)应避免 N+1 查询;候选批量拉取 + 内存打分。 - 查询需可加索引:`stage`、`general`、`personalization_power`、(以及用于召回的标签字段)。 --- ## 10. 可观测性(事件与指标) 每次推荐(Feed session / 每次 Push / 每日 Widget)必须能产出以下字段(由上层统一打点即可): - `candidate_pool_size_raw` - `candidate_pool_size_after_hard_filter` - `candidate_pool_size_after_dedup` - `candidate_pool_size_after_freqcap` - `fallback_level_final` - `served_k` - `empty_reason`(served_k=0 时必填) 建议额外输出(用于调参/排查): - `scene` - `conf_U` - `missing_fields`(need/context/emotion 的缺失情况) - `risk_filtered_count_by_flag`(可选聚合) --- ## 11. 安全与合规 - 严禁返回 Hard Filter 禁推内容。 - 对 Push 场景,默认倾向“低风险 + 低个性化强度”;当画像不确定或字段缺失时必须进一步降个性化。 - 不在仓库写入真实数据库账号密码;连接串统一从 `DATABASE_URL` 环境变量读取(已由 `server/app/core/config.py` 支持)。 --- ## 12. 验收标准(Acceptance Criteria) - 能以统一接口在三种场景运行:输入画像 + 曝光集合 → 输出 TopK 句子。 - 画像字段缺失时仍能产出排序,并触发降个性化策略(至少 L1),不会报错。 - Hard Filter 规则生效:命中 `block_health_medical` 等风险标记的内容绝不出现在输出中。 - 候选不足时会逐级回退并输出 `fallback_level_final`,不会出现无输出且无原因字段的情况。 - 输出包含用于上层打点的 meta 统计字段,便于观测候选规模与回退率。 --- ## 13. 子模块规范索引(modules/) > 子模块规范用于实现拆分;本文件保留大需求高层约束与对外契约摘要。 - `modules/db-design/spec.md`:数据库设计与迁移(当前库为空,本需求内补齐) - `modules/content-repository/spec.md`:数据访问层(按画像与场景拉取候选、按 ID 批量查) - `modules/reco-engine/spec.md`:推荐引擎编排(候选→过滤→打分→重排→回退) - `modules/scoring/spec.md`:打分与惩罚项(严格对齐规则文档;含缺失字段保守策略) - `modules/rerank-freqcap/spec.md`:重排/去重/频控(Feed 的 MMR;Push/Widget 冷却) - `modules/observability/spec.md`:可观测事件结构与指标(candidate_pool_size_* 等) - `modules/integration-api-worker/spec.md`:API 与 Celery 集成(请求入参、响应、任务封装)