11 KiB
11 KiB
Content Repository(候选查询与数据访问层)|Plan
对应规范:
spec_kit/Personalized Reco/modules/content-repository/spec.md依赖对齐:
- DB 设计:
spec_kit/Personalized Reco/modules/db-design/plan.md- 回退梯度与场景口径:
spec_kit/Personalized Reco/spec.md(Fallback Ladder + 场景默认参数)
1. 目标与交付物
1.1 目标
- 为推荐引擎提供与 ORM/SQL 解耦的数据访问接口:候选召回与按 ID 批量获取。
- 将 DB 内部存储形态(JSON、关联表、NULL 语义等)统一“规范化”为上层稳定的
ContentProfile结构。 - 在候选不足时支持按
fallback_level (L0~L3)进行可控降级(降个性化/回退通用池/安全池),并且不产生 N+1 查询。
1.2 交付物
modules/content-repository/plan.md:本技术计划(本文件)。- 代码实现(后续 tasks 阶段落地)建议位置:
server/app/features/personalized_reco/content_repository/(或等价目录)- 需要包含:
- 抽象接口
ContentRepository(Protocol 或 ABC) - SQLAlchemy 实现
SqlAlchemyContentRepository ContentProfile(DTO/数据结构)与规范化工具函数
- 抽象接口
- 单元测试与最小集成测试(后续 tasks 阶段落地):
- risk_flags 映射与去重
- suitability 缺失兜底
- personalization_power 映射
- 查询不出现按
content_id循环查 flags(避免 N+1)
2. 关键技术决策(V1)
2.1 存储形态(来自 DB Design 的已对齐结论)
content_id:MySQL 自增主键(int)。context_suitability_json / need_suitability_json:JSON 存储。risk_flags:关联表content_risk_flags(content_id, flag)(同一 content 下uniq_content_flag去重)。emotion_score:NULL表示 general。review_confidence:DB 可为NULL;读取层输出时按0.7兜底(对齐规则口径)。personalization_power:DB 推荐存0/5/10;读取层输出稳定为0.0/0.5/1.0。
2.2 职责边界(避免耦合)
Content Repository不负责 Hard Filter/Soft Scoring/Rerank/Freqcap(这些由引擎编排与打分子模块完成)。Content Repository负责:- 按
fallback_level对候选池做“降级约束”(例如限制personalization_power、回退通用池/安全池) - 输出稳定结构(JSON 解析、默认值兜底、旧 flag 映射)
- 按
3. 接口与数据结构(V1)
3.1 接口定义(与 spec 对齐)
fetch_candidates(scene, user_profile, fallback_level, limit, locale, exclude_content_ids=None) -> List[ContentProfile]fetch_contents_by_ids(content_ids: List[int], locale) -> List[ContentProfile]
3.2 ContentProfile(输出契约的推荐形态)
稳定字段(必须输出):
content_id: inttext: strstage: "general" | "expecting" | "parenting" | "unknown"emotion_score: float | None(None表示 general)context_suitability: Dict[str, float]need_suitability: Dict[str, float]personalization_power: float(0/0.5/1)risk_flags: List[str]
可选字段(尽量输出):
author_id: str | Nonetemplate_id: str | Nonereview_confidence: float(缺失/NULL 按0.7输出)
4. 读取层规范化(Normalization)
4.1 text 选文案与多语言策略(不允许回退)
数据来源(当前 DB/ORM 约定):
contents.text_en:英文contents.text_tc:繁体中文
规则(不允许语言回退):
locale=en*:仅允许返回存在text_en的内容;输出text = text_enlocale=tc/zh-TW/zh-HK:仅允许返回存在text_tc的内容;输出text = text_tc
若内容缺少目标语言文本(例如 locale=en* 但 text_en 为空):该内容视为不可用,必须在候选/按 ID 获取时过滤掉。
4.1 suitability JSON 解析与缺失兜底
固定 key 集合(对齐 DB Plan 的最小入库契约):
context_suitability:family/work/relationship/friends/healthneed_suitability:emotional_support/parenting_pressure/self_worth/anxiety_relief/rest_balance
规则:
- 若 DB 字段缺失/为
NULL/解析失败:补齐为全 0.5(以上所有 key 均为0.5)。 - 若 DB JSON 存在但缺少部分 key:对缺少 key 补
0.5,其余按原值。 - 值域约束:期望值为
0/0.5/1;若出现其他值(例如字符串、越界浮点),按0.5兜底并记录告警日志(V1 可先打 debug,后续接入可观测模块)。
4.2 review_confidence 兜底
- DB
review_confidence为NULL或缺失:输出0.7。
4.3 personalization_power 映射
若 DB 存 0/5/10:
0 -> 0.05 -> 0.510 -> 1.0
若读到其他值:按 0.0 兜底并记录告警日志。
4.4 risk_flags 旧→新映射与输出约束
映射表(对齐 spec):
block_stage_unknown→unsafe_for_stage_unknownblock_stage_parenting→unsafe_for_stage_parentingblock_emotion_low→unsafe_for_emotion_lowblock_health_sensitive→block_health_medical(V1 保守硬拦截)
输出约束:
- 输出
risk_flags必须去重。 - 输出不得包含旧命名。
- 输出建议稳定排序(便于测试与可观测):按字典序排序或按严重等级排序(V1 可先字典序)。
5. 查询策略(V1)
原则:DB 层先做“粗过滤”,应用层再做“精过滤/打分”。避免在 V1 过早依赖 JSON 路径查询索引。
5.1 fetch_contents_by_ids(按 ID 批量获取)
目标:
- 输入任意
content_id列表,返回无重复的ContentProfile列表。 - 避免 N+1:不得按
content_id循环查content_risk_flags。 - 返回顺序:必须与输入
content_ids一致(对“缺记录/缺语言文本”的 id 采取跳过策略,见下)。
缺记录/缺语言文本的处理(V1 约定):
- 若某个
content_id在 DB 中不存在,或按locale规则无法产出text:该 id 在返回列表中跳过(不返回占位对象)。
推荐实现形态(两段式,避免 JOIN 导致重复行):
- 批量拉主体与画像(
contentsJOINcontent_profiles),限制content_id IN (...)。 - 批量拉 risk_flags:
SELECT content_id, flag FROM content_risk_flags WHERE content_id IN (...),在应用层按content_id聚合为集合,再做旧→新映射与去重。
备注:
- 由于
content_risk_flags是 1:N,直接三表 JOIN 容易导致行膨胀;两段式更便于组装与去重。
5.2 fetch_candidates(候选召回,支持 L0~L3)
输入:
scene: feed | push | widgetuser_profile(允许字段缺失)fallback_level: 0|1|2|3limitexclude_content_ids(可选)
5.2.1 fallback_level 约束(对齐大规范 Fallback Ladder)
从 spec_kit/Personalized Reco/spec.md 对齐:
- L0:正常召回配比(不在读取层实现复杂配比,读取层只保证候选池足够大且不过度放宽)
- L1:放宽匹配 + 降个性化:限制
personalization_power ≤ 0.5 - L2:回退通用池 + 进一步降个性化:限制
personalization_power = 0,且优先stage=general - L3:兜底安全池:限制
is_safe_pool = true(安全池字段来自 DB Design)
缺失字段的最小处理(对齐大规范 Candidate Generation 口径):
- 若
user_profile缺失明显(need/context/emotion 任一缺失):读取层按至少 L1 的约束执行(即使入参 fallback_level=0)。
5.2.2 stage 粗过滤策略(V1)
读取层可做的最小粗过滤(不引入复杂业务判断):
- L2/L3:只取
stage=general(L3 额外is_safe_pool=true)。 - L0/L1:
- 优先取
stage=用户匹配阶段+stage=general - 若无法从
user_profile明确阶段,则仅取stage=general(避免误推)
- 优先取
说明:更细粒度的阶段/跨维度规则(例如 unknown+parenting_pressure 的禁推)由 Hard Filter 子模块实现;读取层仅做粗过滤以减少扫描与传输。
5.2.3 查询形态(避免 JOIN 行膨胀 + 保证 limit)
推荐采用“两段式候选召回”:
- 先只查候选 ID 列表(
contentsJOINcontent_profiles),应用粗过滤(stage / personalization_power / is_safe_pool / exclude_content_ids),并增加 locale 文本存在性过滤(不允许语言回退),再用LIMIT limit * multiplier拉一批候选 ID(multiplier例如 3~5,避免后续去重/过滤后不足)。 - 再用
fetch_contents_by_ids批量补全字段(主体+画像+risk_flags),最终在应用层去重并截断到limit。
排序(V1):
- 若没有更明确的排序字段:使用
updated_at DESC或随机抽样(需谨慎,MySQLORDER BY RAND()在大表会慢)。 - 推荐:V1 先用
content_profiles.updated_at DESC或contents.created_at DESC,后续由打分模块决定最终排序。
6. 性能与可观测(V1)
6.1 性能约束
- 单次调用不得出现按
content_id循环查库(避免 N+1)。 fetch_candidates必须在 DB 层支持limit,并尽量通过粗过滤减少扫描。
6.2 建议打点/日志(为 observability 子模块预留)
在 Repository 层建议输出 debug 级日志(或埋点字段,供上层汇总):
scenefallback_level(入参)与effective_fallback_level(考虑缺失字段自动至少 L1 后的实际约束级别)limit、exclude_content_ids_countcandidate_ids_size_raw(第 1 段查到的候选 ID 数)candidate_size_returned(最终返回数量)
7. 测试计划(V1)
7.1 单元测试(纯函数)
- risk_flags 映射:
- 输入包含旧 flag,输出只包含新命名
- 去重与稳定排序
- suitability 兜底:
- DB 字段缺失/NULL/解析失败 → 全 0.5
- 部分 key 缺失 → 补齐 0.5
- personalization_power 映射:
- 0/5/10 → 0.0/0.5/1.0
- 异常值 → 0.0 兜底
- review_confidence:
- NULL/缺失 → 0.7
7.2 最小集成测试(含数据库)
fetch_contents_by_ids:- 输入多个 id 返回无重复
- flags 聚合正确(同一 content 多条 flag 行能聚合成 list)
- 查询次数断言(避免 N+1):
fetch_contents_by_ids:固定 2 次查询(主体+画像一次,flags 一次)fetch_candidates:固定 3 次查询(候选 id 一次 +fetch_contents_by_ids两次),或实现允许的常数级次数
8. 风险与后续演进
8.1 已知风险
- V1 不做 JSON 路径索引:候选量变大后,粗过滤不足可能导致候选池拉取过多、应用层过滤成本上升。
ORDER BY RAND()的性能风险:候选大表下不可用,需要替代策略(时间窗口抽样/预生成候选池)。
8.2 V1.1 优化方向(与 DB Plan 对齐)
- 为常用 need/context key 增加生成列/函数索引(从 JSON_EXTRACT 提取到 TINYINT)以加速召回。
- 为强规则风险(如
block_health_medical)增加派生布尔列或缓存表,减少 JOIN 成本。