什么时候值得调用 Local RAG,什么时候直接读源码更快

我缺的是事实,还是入口?这一个问题就能决定要不要检索。

本页目录 · 9 节

RAG 里有两个经常被混在一起的「要不要取」:数据侧决定要不要重新抓 Source Body,开发侧决定要不要为这次问题调用检索。前者是 Raw Data Control Plane,后者是 Query Routing。两者共同遵守一个原则:先判断缺口,再执行有成本、有权限或可能产生过期风险的动作。

本篇知识地图

flowchart TB
  M["Metadata Discovery"] --> F["Freshness / Currentness"]
  F --> D{"Fetch Decision"}
  D -->|changed / new / uncertain| B["Fetch Full Body"]
  D -->|old and unchanged| S["Skip Full Fetch"]
  B --> R["Raw Snapshot + Source Event"]
  S --> L["Keep Existing Raw + Decision Lineage"]

  Q["Developer Query"] --> G{"Already has exact entry?"}
  G -->|yes| C["Read Current Source"]
  G -->|no / cross-source| X["Call Local RAG"]

需要掌握的知识点包括 Metadata-first Ingestion、Freshness 与 Currentness 的区别、Change Override、Source Event Identity、权限与成本 Gate,以及查询侧的 Direct Search Fallback。

Control Plane 不处理正文,它决定正文是否值得处理

如果每次定时任务都重新下载所有 Confluence 页面、附件和代码,再全量解析、切块、Embedding,系统会把大量资源浪费在没有变化的数据上;如果只看更新时间又直接跳过,错误 Timestamp 或旧页面更新会永久漏掉。

当前 Confluence 策略先拉轻量 Metadata,再计算:

age_days = max(0, as_of - effective_updated_at)
freshness_score = exp(-ln(10) * age_days / 1096)

1096 天时 Score 为 0.1。只有同时满足以下条件才跳过 Full Fetch:

  1. 不是 Force 模式;
  2. 已存在 Previous Snapshot;
  3. Revision、Effective Updated At 和 Metadata Fingerprint 都没变化;
  4. Timestamp 可正常评分;
  5. score < 0.1,注意是严格小于。

新页面、Revision 变化、Metadata Fingerprint 变化都会触发 change_override。Timestamp 缺失、非法或超出未来容忍范围时不会为了省流量而跳过;它们以 Fail-open Fetch 的方式保留内容,以 Fail-closed Claim 的方式记录异常状态。

flowchart LR
  A["metadata snapshot"] --> B{"complete and unchanged?"}
  B -->|no| F["fetch_full"]
  B -->|yes| C{"score < 0.1?"}
  C -->|yes| S["skip_full_fetch"]
  C -->|no| F
  F --> P["persist only after complete fetch"]
  P --> E["SourceChangeEvent"]

在 Metadata-first Freshness Control Plane 内,只有单个 Full Fetch 完整成功后才调用 Persister。一次 Skip 不覆盖已有 Raw,单个页面半失败也不会被该控制面当成完整 Snapshot 发布。

但项目仍保留 Legacy Full Refresh:它会先删除 Jira/Confluence 的旧 Raw,再重新抓取;网络中途失败时,整个 Mutable Raw Mirror 仍可能处于部分完成状态。更稳妥的目标是先写 Staging Snapshot,再整体切换 Pointer。把单页面原子覆盖写成「整个 Full Refresh 原子」会高估当前保证。

Freshness 不能套在代码上

Confluence 的时间衰减表达「较旧设计可能更需要谨慎」。代码的有效性却不由年龄决定:一个多年前未改动的核心函数仍可能是当前实现,最近的 Feature Branch 也可能根本没有部署。

因此 source_temporal_policy 对 Confluence 返回 freshness,对 Code 返回 currentness。Code Currentness 依赖 Repo、Branch、Pinned Commit 和同步状态;查询 Payload 只有在 Source Sync 为 Fresh 时才设置 currentness_valid=true。把代码按 updated_at 做指数衰减,会错误地压低稳定实现,并抬高未经发布的新代码。

Source Event:目标是变化被接受后才进入下游

Raw Control Plane 的输出不是「一个目录改了」,而是结构化 SourceChangeEvent

source_type / entity_type / entity_id
operation = upsert | delete
revision / content_hash / payload_hash
occurred_at / detected_at / authoritative
run_id / job_id

source_entity_state 用 Revision、Content Hash、Payload Hash 和 Deleted Flag 抑制跨 Run 的未变化实体。Event ID 保留 Run/Source Revision 语义,因此合法的 A → B → A 变化不会因为内容回到旧值而被错误吞掉。

增量来源的「没有看见」不是 Delete。Jira/Confluence 的增量查询不是权威全量 Inventory,只有成功完成的 Reconciliation Sweep 才能发 Authoritative Tombstone。否则一次分页失败就可能被误读成成百上千条删除。

当前 Scheduled Jira 路径还有一个明确缺口:调度先执行不带 --fast-metadata 的完整 Collect,随后 Enrich 只挑 Placeholder/Incomplete Issue。完整 Collect 后 Enrich 可能没有候选,因此 Jira Issue Source Event Handoff 可能为空;Attachment Event 也不能替代 Issue Event。Raw Mirror 更新成功与 V3 Journal 已收到事件,必须分别验收。

调度默认只拉 Source

当前 Docker 调度把来源拆成独立 Job:Atlassian 与 Code 均按日调度但频次不同,DDB Weekly 默认关闭。默认 Job 只更新 Source,不自动运行 Embedding 或 Store Write。除此之外还并存 Legacy Monolithic Refresh、V3 SQLite Journal 和 System RAG Immutable Manifest Generation;它们是迁移中的三套控制路径,不能描述成已经统一的一个 Runtime。

DDB 要启用需要 Weekly Flag、Read-only Confirmation 和当前 ISO Week 三个值同时成立。周范围确认自动过期,因为「上周允许读」不能推导出「本周仍允许读」。即使命令固定 --read-only,它仍然可能读取生产数据并产生 AWS 成本,所以代码开关不能替代人工安全确认。

查询侧:我缺的是事实,还是入口

数据侧完成后,开发任务也不应无条件调用 Local RAG。

如果用户给了文件、错误栈、Class、GraphQL Field 或具体 Diff,我已经拥有精确入口。此时 rg、类型定义、测试和当前调用方比向量检索更直接,因为它们读取当前 Working Tree,不需要再判断索引 Commit。

Local RAG 更适合四类缺口:

  • 业务语言和代码命名不一致;
  • 问题横跨 Jira、Confluence 和多个仓库;
  • 只知道领域概念,不知道 Module 或 Symbol;
  • 需要找一项历史决定的原始页面,再回当前代码验证。

所以「什么时候值得调用」不是由任务看起来复杂与否决定,而是由检索能否显著缩小无方向搜索决定。Local RAG 找入口,当前源码和 Provider 原文完成证明。

失败模式与取舍

失败模式 当前处理 Trade-off
Confluence Timestamp 缺失或非法 Fetch Full,并记录状态 多一次抓取,换取不永久漏页
老页面 Metadata 变化 Change Override,忽略低 Freshness 解析成本增加,但变更不会被年龄遮蔽
定时任务 Source 无变化 不生成 Derived Work Journal/State 增加实现复杂度
增量结果缺少一个对象 不发 Delete 删除收敛较慢,避免误删整个投影
Legacy Full Refresh 抓取中断 Mutable Raw 可能只剩部分新快照 需要 Staging + Pointer 才能获得批次级原子切换
Jira Raw 更新但 Issue Event 未交接 V3 下游可能看不到该次变化 分别核对 Raw、Journal 和 Consumer Cursor
Local RAG/Qdrant 不可用 回到有边界的 Direct Search 跨来源发现能力下降,但开发不被隐藏 Runtime 阻塞
精确 Symbol 已知仍先检索 视为不必要路径 统一流程更简单,但延迟和 Staleness 风险更高,因此不采用

可复核的项目证据

  • src/mt_rag/freshness/policy.py:1096 天指数评分和 Source Temporal Policy;
  • src/mt_rag/freshness/confluence.py:Metadata Fingerprint、严格阈值、Change Override 与 Decision Reason;
  • src/mt_rag/freshness/control_plane.py:Metadata-first Sweep、完整 Fetch 后持久化和 Source Event;
  • src/mt_rag/refresh/contracts.py:Source/Vector/Graph Event Contract;
  • src/mt_rag/refresh/journal.py:Unchanged Suppression、Entity State 与 Consumer Cursor;
  • src/mt_rag/service/service_refresh.py:Source-aware Schedule 和默认 Source-only 行为;
  • docs/03-reference/incremental-refresh-v3-event-design.md:Source Job、确认 Gate 和删除语义。

参考资料

一个可靠的 Control Plane 不追求「每次都做更多」,而是让抓取、检索和降级都由可观察的缺口触发,并把没有执行的原因同样记录为证据。

相关文章

在做类似的事情?

很乐意就分布式系统、交付流程和应用 AI 交换意见。

[email protected]