# Observability(可观测性与打点载荷)|Plan > 对应规范:`spec_kit/Personalized Reco/modules/observability/spec.md` > > 规则来源(必须对齐): > > - `设计说明文档/個性化推薦算法規則.md`(candidate_pool_size_*、fallback_level_final、empty_reason 等必打点字段) > - `spec_kit/Personalized Reco/overview.md`(模块边界:本模块被 `reco-engine` 与 `integration-api-worker` 共同使用) --- ## 1. 目标与交付物 ### 1.1 目标 - 定义推荐模块统一的 `RecoMeta`(meta/event 载荷),**随推荐结果一起返回**,由调用方负责上报/落库/打点。 - 确保每次推荐调用都能产出可用于监控与排障的关键字段: - 覆盖率、回退率、候选规模分布、过滤原因分布 - `served_k=0` 时必须给出准确的 `empty_reason`(定位清空阶段) - 与现有子模块接口对齐: - 候选生成(content-repository) - Hard Filter(引擎阶段) - Soft Scoring(scoring) - Rerank/Freqcap(rerank-freqcap) ### 1.2 交付物 - `modules/observability/plan.md`:本技术计划(本文件)。 - 代码实现(后续 tasks 阶段落地)建议位置: - `server/app/features/personalized_reco/observability/` - 包含: - `types.py`:`RecoMeta`(Pydantic BaseModel) - `builder.py`:`RecoMetaBuilder`(在 pipeline 中逐步填充) - `utils.py`:`empty_reason` 判定工具函数 - 单元测试(后续 tasks 阶段落地): - `served_k=0` 时 empty_reason 必填且阶段一致 - 计数口径一致性(after_* 的单调性与非负) --- ## 2. 模块职责边界(V1 约定) ### 2.1 本模块负责 - 提供统一的数据结构 `RecoMeta`(返回给调用方)与构建方式(builder)。 - 在推荐 pipeline 中收集各阶段统计,避免“散落日志/散落字段”: - 候选生成规模(raw) - Hard Filter 后规模 - Dedup 后规模 - Freqcap 后规模 - 回退层级与触发原因(由引擎提供) - served_k 与 empty_reason - `conf_U` 与 `missing_fields` - (可选)risk flag 命中统计 ### 2.2 不在本模块实现 - 不负责真正的上报实现(埋点 SDK / 日志落库 / 指标上报)。 - 不负责决定回退策略与过滤规则,只负责“把发生了什么”记录成统一载荷。 --- ## 3. `RecoMeta` 字段定义(V1) > 以 `modules/observability/spec.md` 为准,本 plan 补充“口径/生成时机/默认值”。 ### 3.1 必须字段(每次推荐都要产出) - `scene: "feed" | "push" | "widget"` - `candidate_pool_size_raw: int` - `candidate_pool_size_after_hard_filter: int` - `candidate_pool_size_after_dedup: int` - `candidate_pool_size_after_freqcap: int` - `fallback_level_final: int` - `served_k: int` - `empty_reason: str | None` - `served_k=0` 时必填 - `served_k>0` 时可为 `None`(或输出 `"unknown"`,但建议为 None 更干净) - `conf_U: float` - `missing_fields: { need: boolean; context: boolean; emotion: boolean }` ### 3.2 可选字段(建议支持,便于排障/调参) - `risk_filtered_count_by_flag: { flag: count }` - 其他调参辅助字段(V1 可先不返回给客户端,仅在内部日志/事件中使用): - `config_snapshot`(权重、alpha/beta、mmr_lambda、cooldown 等) - `fallback_trigger_reason`(例如 pool_empty/hard_filter_all/freqcap_all) --- ## 4. 口径与生成时机(V1 必须写死) ### 4.1 数量统计口径(强约束) - 计数必须满足: - 全部为非负整数 - 单调不增: - `raw >= after_hard_filter >= after_dedup >= after_freqcap >= served_k` - 每个阶段计数的来源: - `candidate_pool_size_raw`:候选生成阶段拿到的候选数(从 `content-repository` 返回的候选列表长度) - `candidate_pool_size_after_hard_filter`:Hard Filter 过滤后的候选数 - `candidate_pool_size_after_dedup`:去重(基于历史集合)后的候选数 - `candidate_pool_size_after_freqcap`:频控/冷却后的候选数(包含作者/模板维度若执行) - `served_k`:最终输出 items 的长度(≤ k) ### 4.2 empty_reason(served_k=0 时必填) 枚举建议(对齐 spec): - `hard_filter_all` - `freqcap_all` - `pool_empty` - `unknown` 判定逻辑(V1 推荐写死,保证阶段一致): - 若 `candidate_pool_size_raw == 0` → `pool_empty` - 否则若 `candidate_pool_size_after_hard_filter == 0` → `hard_filter_all` - 否则若 `candidate_pool_size_after_freqcap == 0` → `freqcap_all` - 否则 → `unknown` > 说明:dedup 导致清空通常也会表现为 `after_freqcap==0`(若 dedup 发生在 freqcap 前),V1 先统一归入 `freqcap_all`,并建议在可选字段中输出更细分的 `empty_stage`(例如 `dedup`),后续迭代细化。 ### 4.3 `conf_U` 与 `missing_fields` 口径 - `conf_U`:直接取 `user_profile.profile_confidence` - `missing_fields`: - `need`: `user_profile.need` 为空对象 `{}` 或不存在 - `context`: `user_profile.context` 为空对象 `{}` 或不存在 - `emotion`: `user_profile.emotion_score` 为 `null`/不存在 --- ## 5. 工程落地方式(V1) ### 5.1 Builder 模式(避免散落) 推荐在 `reco-engine` 内使用 `RecoMetaBuilder`: - 初始化:`builder = RecoMetaBuilder(scene, user_profile, k, now)` - 各阶段更新: - `builder.set_candidate_pool_size_raw(n)` - `builder.set_after_hard_filter(n, risk_filtered_count_by_flag=...)` - `builder.set_after_dedup(n)` - `builder.set_after_freqcap(n, freqcap_filtered_counts=...)` - `builder.set_fallback_level_final(level, reason=...)` - `builder.set_served_k(len(items))` - 最终:`meta = builder.build()`(内部负责 empty_reason 判定与默认值填充) ### 5.2 API/Celery 的返回策略(边界清晰) - `integration-api-worker` 对外返回: - `items` - `meta`(RecoMeta) - 是否对客户端透出所有 meta 字段: - V1 建议:对客户端返回最小必要字段;但服务端事件中保留完整 meta(含可选字段) - 具体裁剪由 API 层决定,Observability 模块只负责提供完整结构 --- ## 6. 测试计划(V1) ### 6.1 单元测试(pure) - `empty_reason` 判定: - raw=0 → pool_empty - raw>0 且 after_hard_filter=0 → hard_filter_all - after_freqcap=0 → freqcap_all - 单调性断言(若输入不满足单调性,builder 应做防御式 clamp 或记录告警,V1 可选择“以最后写入为准”并在测试中覆盖) ### 6.2 最小集成验证(与 reco-engine 串联) - 构造一次推荐调用: - items 长度与 `served_k` 一致 - `candidate_pool_size_*` 与实际阶段产物一致 - `served_k=0` 时 `empty_reason` 与清空阶段一致 --- ## 7. 风险与后续演进 ### 7.1 已知风险 - V1 可能缺少细分 empty_stage(例如 dedup_all 与 freqcap_all 的区分),导致排障粒度不足。 ### 7.2 演进方向 - 增加 `empty_stage: "candidate" | "hard_filter" | "dedup" | "freqcap" | "unknown"`,保持与 `empty_reason` 并存。 - 增加 `freqcap_filtered_counts`、`risk_filtered_count_by_flag` 的统一结构与上报策略,便于报表按维度聚合。