fix:更新算法

This commit is contained in:
吕新雨
2026-02-02 11:22:35 +08:00
parent 814b96edb6
commit 58d17fc39f
27 changed files with 3286 additions and 62 deletions

View File

@@ -0,0 +1,287 @@
# 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 用户画像 UUserProfile
用户画像结构以客户端问卷输出为准V1.2),详见:
- `spec_kit/User Profile Scoring/spec.md`(字段与缺失约定)
- `modules/reco-engine/spec.md`(本模块如何消费画像与缺失判定)
### 4.3 文案画像 CContentProfile
模块需能从 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`: intfeed 默认 30push/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 FeedExploration-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 PushPrecision & 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 WidgetConsistency & 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 的 MMRPush/Widget 冷却)
- `modules/observability/spec.md`可观测事件结构与指标candidate_pool_size_* 等)
- `modules/integration-api-worker/spec.md`API 与 Celery 集成(请求入参、响应、任务封装)