fix:更新算法
This commit is contained in:
287
spec_kit/Personalized Reco/spec.md
Normal file
287
spec_kit/Personalized Reco/spec.md
Normal 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 用户画像 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 集成(请求入参、响应、任务封装)
|
||||
|
||||
Reference in New Issue
Block a user