什么时候值得调用 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:
- 不是 Force 模式;
- 已存在 Previous Snapshot;
- Revision、Effective Updated At 和 Metadata Fingerprint 都没变化;
- Timestamp 可正常评分;
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 和删除语义。
参考资料
- Atlassian Confluence Content API 官方文档;
- Bitbucket Cloud Webhooks 官方文档;
- AWS DynamoDB 安全最佳实践;
- Debezium Architecture 官方文档;
- Git
rev-parse官方文档。
一个可靠的 Control Plane 不追求「每次都做更多」,而是让抓取、检索和降级都由可观察的缺口触发,并把没有执行的原因同样记录为证据。
相关文章
RAG 不是一条必经流水线,而是一种按需取证的能力
先理解目标、先看当前源码,只有跨来源信息真的能改善判断时,才调用 Local RAG 去找证据候选。
一次代码命中为什么还不算答案
工程检索必须把结果绑到仓库、commit 和 symbol 上;缺失、过期或来自错误仓库的命中,只能当定位线索。