AI Agent 第十周完整学习笔记
第十周主题:RAG ACL + Metadata Filtering + Document Lifecycle + Incremental Indexing + Reliability + Production Architecture
核心目标:把第九周"检索更准"继续升级为"检索更准、权限安全、索引可维护、失败可恢复、长期可运行"的企业级 RAG 系统。
一、第十周总体路线
text
Day46
RAG ACL
↓
谁能看什么?
Day47
Metadata Filtering
↓
权限和业务条件怎么真正进入检索?
Day48
Document Lifecycle
+
Incremental Indexing
↓
新增 / 修改 / 删除 / 版本 / 旧Chunk清理
Day49
Indexing Reliability
↓
Retry / Idempotency / Failure Isolation / Dead Letter
Day50
Production RAG Architecture
↓
把Indexing和Serving完整串联
二、第十周最终完整架构
text
================================================
INDEXING PIPELINE
================================================
Business Source
↓
Stable Source ID
↓
Index Text + Metadata
↓
Content Hash / Metadata Hash
↓
Change Detection
↓
Index Task
↓
Idempotency Check
↓
Chunk
↓
┌───────────────┬───────────────┐
↓ ↓
Dense Index BM25 Index
↓ ↓
└───────────────┬───────────────┘
↓
Validation
↓
Activate Version
↓
Cleanup Old
↓
Update Registry
失败:
Retry
↓
Backoff
↓
Dead Letter
================================================
SERVING PIPELINE
================================================
Authenticated User
+
Question
↓
Permission Service
↓
Permission Scope
↓
Filter Builder
↓
Security + Lifecycle + Business Filters
↓
┌─────────────────┬──────────────────┐
↓ ↓
Authorized Dense Authorized BM25
↓ ↓
└─────────────────┬──────────────────┘
↓
RRF
↓
Candidate TopK
↓
Reranker
↓
Final ACL Check
↓
Final TopK
↓
Context
↓
Qwen
↓
Answer + Sources
Day46|RAG ACL + Permission-aware Retrieval
1. 为什么 RAG 必须有 ACL
企业知识库中的 Document 可能包含普通产品资料、高端产品内部资料、客户信息、管理层会议纪要等敏感内容。
如果 Retriever 只判断:
text
相关不相关
而不判断:
text
当前用户有没有权限
那么无权限资料可能进入:
text
Candidate
Reranker
Context
LLM
日志
缓存
Trace
所以第一条原则:
Prompt 不是权限安全边界。
2. ACL 是什么
text
ACL
=
Access Control List
可以理解为:
谁可以访问什么资源。
例如:
text
用户A
→ PUBLIC_FUND
用户B
→ HIGH_END
用户C
→ PUBLIC_FUND + PORTFOLIO
RAG 中就是:
text
Current User
↓
Permission Scope
↓
只检索Authorized Documents
3. Pre-filter vs Post-filter
Post-filter
text
Query
↓
全库Retriever
↓
TopK
↓
Permission Check
↓
删除Unauthorized Documents
问题:假设 Top5 里 4 个无权限,过滤后只剩 1 个;而 Rank6~Rank10 可能还有很多"有权限 + 高相关"的文档,但已经被 TopK 截断。
所以 Post-filter 可能导致:
text
Authorized Recall下降
Pre-filter
text
Current User
↓
Permission Scope
↓
Authorized Scope
↓
Retriever只在Authorized Documents中搜索
↓
TopK
推荐:
text
Pre-filter
=
主权限机制
Post-filter / Final ACL Check
=
Defense in Depth
4. Metadata 是权限基础
python
from langchain_core.documents import Document
doc = Document(
page_content="高端产品栏目中的内部产品资料。",
metadata={
"document_id": "FILE-10001",
"chunk_id": "FILE-10001#1",
"column_code": "HIGH_END",
"visibility": "INTERNAL"
}
)
预期输出 / 你应该看到的结果
text
没有输出。
这里只创建Document。
5. 当前用户权限
python
user_permission_scope = {
"user_id": "USER-001",
"viewable_columns": {
"PUBLIC_FUND",
"PORTFOLIO"
}
}
预期输出 / 你应该看到的结果
text
没有输出。
6. Permission Check
python
def can_view_document(doc, permission_scope):
column_code = doc.metadata.get("column_code")
return (
column_code
in permission_scope["viewable_columns"]
)
预期输出 / 你应该看到的结果
text
没有输出。
7. View / Edit / Manage 如何进入 RAG
复杂权限逻辑:
text
VIEW
EDIT
MANAGE
应该先由:
text
Permission Service
统一计算成:
text
这个用户到底能不能查看这个栏目
Retriever 最终只需要:
text
viewable_columns
而不是自己理解 Edit / Manage 是否包含 View。
8. 为什么不建议每用户一套 Vector Store
不推荐:
text
USER-A
→ Vector Store A
USER-B
→ Vector Store B
问题:
text
存储重复
权限变更难同步
索引维护复杂
更合理:
text
Shared Index
+
Query-time ACL Filter
9. Dense 和 BM25 都必须带 ACL
text
Permission Scope
↓
┌───────┴────────┐
↓ ↓
Dense BM25
↓ ↓
Authorized Authorized
Results Results
不能:
text
Dense有ACL
BM25没ACL
否则 BM25 仍可能把无权限 Document 送进 RRF。
10. Context 前最终 ACL Check
python
def validate_context_documents(documents, permission_scope):
unauthorized = [
doc
for doc in documents
if not can_view_document(
doc,
permission_scope
)
]
if unauthorized:
raise PermissionError(
"发现无权限Document,禁止进入LLM Context"
)
return documents
预期输出 / 你应该看到的结果
text
没有输出。
11. 为什么 Prompt 不能做权限
错误:
text
Prompt:
"用户没有权限时,请不要泄露资料。"
问题:
text
LLM不是Authorization Engine
Prompt可能被Injection影响
模型行为不是完全确定性
无权限数据已经进入Context
最安全原则:
模型根本看不到无权限数据。
Day46 核心结论
RAG 权限不是"模型能不能说",而是"无权限数据从一开始就不能进入模型可见的 Retrieval 链路"。
Day46 复习题
1. ACL 是什么?
答案: Access Control List,用于控制谁可以访问什么资源。
2. 什么是 Pre-filter?
答案: 先计算 Authorized Scope,再只在 Authorized Documents 中检索。
3. 什么是 Post-filter?
答案: 先全库检索,再删除无权限 Document。
4. 为什么权限场景优先 Pre-filter?
答案: 可以避免无权限文档进入 Candidate、Reranker、Context,并避免 TopK 后过滤造成 Authorized Recall 下降。
5. Prompt 可以承担 ACL 吗?
答案: 不能。
6. ACL 变化需要重新 Embedding 吗?
答案: 通常不需要,只需要更新 Metadata / Permission Scope。
Day47|Metadata Filtering + 多维权限检索设计
1. Metadata 在企业 RAG 中的三类职责
text
Security Metadata
→ tenant_id / column_code / visibility
Business Metadata
→ material_type / product_code / status
Trace Metadata
→ source / document_id / chunk_id / version
2. Security Filter 和 Business Filter 必须分开
用户问:
text
160422最近的月报
Security:
text
tenant_id = TENANT-001
column_code IN (...)
status = ACTIVE
Business:
text
product_code = 160422
material_type = MONTHLY_REPORT
最终:
text
Security Filter
AND
Business Filter
AND
Retrieval Query
3. 为什么 Security Filter 不能放宽
Business Filter:
text
月报
日期
产品代码
可以根据业务规则做搜索降级。
但:
text
tenant
ACL
visibility
属于:
text
Hard Constraint
永远不能为了搜不到而删除。
4. PermissionScope
python
permission_scope = {
"user_id": "USER-001",
"tenant_id": "TENANT-001",
"viewable_columns": {
"PUBLIC_FUND",
"PORTFOLIO"
}
}
预期输出 / 你应该看到的结果
text
没有输出。
5. Security Filter
python
def security_filter(doc, permission_scope):
metadata = doc.metadata
tenant_ok = (
metadata.get("tenant_id")
== permission_scope["tenant_id"]
)
column_ok = (
metadata.get("column_code")
in permission_scope["viewable_columns"]
)
status_ok = (
metadata.get("status")
== "ACTIVE"
)
return (
tenant_ok
and column_ok
and status_ok
)
预期输出 / 你应该看到的结果
text
没有输出。
6. Business Filter
python
def business_filter(
doc,
product_code=None,
material_type=None
):
metadata = doc.metadata
if (
product_code is not None
and metadata.get("product_code") != product_code
):
return False
if (
material_type is not None
and metadata.get("material_type") != material_type
):
return False
return True
预期输出 / 你应该看到的结果
text
没有输出。
7. AND / OR 怎么理解
用户可访问:
text
PUBLIC_FUND
PORTFOLIO
所以栏目条件:
text
column_code = PUBLIC_FUND
OR
column_code = PORTFOLIO
也就是:
text
column_code IN (...)
但 Tenant 与 Column 是:
text
tenant匹配
AND
栏目授权
8. Search Query 和 Filter 是两回事
Query:
text
上传失败以后文件还在吗?
负责:
text
相关性 Ranking
Filter:
text
tenant_id=T001
column_code=PUBLIC_FUND
负责:
text
有没有资格参与检索
一句话:
Filter 决定资格,Retriever 决定排名。
9. Exact Filter Fields
适合:
text
tenant_id
column_code
status
material_type
product_code
document_id
chunk_id
典型逻辑:
text
=
IN
term
terms
全文字段:
text
content
title
summary
适合:
text
BM25
match
Embedding
Dense Search
10. Metadata Schema 为什么重要
如果索引里只有:
text
source
content
后面要求:
text
Tenant隔离
栏目权限
有效版本
产品筛选
材料类型
会发现没有字段可 Filter。
所以:
Metadata Schema 本身就是索引设计的一部分。
11. 推荐 Metadata 基线
text
document_id
chunk_id
source
tenant_id
column_code
directory_id
material_type
product_code
status
version
created_at
effective_at
不是越多越好,只保存真正用于 Retrieval / Filter / ACL / Trace 的字段。
12. 栏目权限 + 单文档特批
text
tenant必须匹配
AND
status必须ACTIVE
AND
(
column_code IN viewable_columns
OR
document_id IN explicitly_granted_docs
)
13. FilterBuilder
推荐:
text
PermissionService
↓
PermissionScope
↓
FilterBuilder
↓
Security + Business + Lifecycle Filters
避免 Dense、BM25 各写一套权限规则,导致规则漂移。
Day47 核心结论
Filter 决定"这个 Document 有没有资格参加比赛",Dense/BM25/RRF/Reranker 决定"有资格的 Document 中谁排得更靠前"。
Day47 复习题
1. Security Filter 和 Business Filter 有什么区别?
答案: Security 是安全边界,不能放宽;Business 是搜索条件,可以按业务规则降级。
2. 为什么多个栏目权限是 OR / IN?
答案: 同一字段命中任意一个授权栏目即可。
3. 为什么 tenant 和 column_code 通常是 AND?
答案: 既要属于正确租户,又要拥有目标栏目权限。
4. ACL Filter 应该影响 relevance score 吗?
答案: 不应该,ACL 是资格判断,不是相关性判断。
5. 为什么 status=ACTIVE 适合 Metadata Filter?
答案: 因为版本是否有效是确定性规则,应在 Retrieval 阶段处理。
6. LLM 可以自己决定用户权限吗?
答案: 不能。
Day48|Document Lifecycle + Incremental Indexing
1. 为什么知识库不能"一次建完就不管"
真实系统会发生:
text
新增文件
修改内容
改标题
移动栏目
权限变化
版本失效
删除文件
重新Chunk
更换Embedding模型
所以 Search Index 必须持续同步。
2. Source of Truth vs Search Index
Source of Truth:
text
业务数据库
原始文件
对象存储
文件平台
Search Index:
text
Vector Index
BM25 Index
Search Metadata
原则:
业务 Source 是权威数据,Search Index 是可重建的检索副本。
3. Document Lifecycle 四类变化
text
CREATE
UPDATE
DELETE
METADATA_CHANGE
4. stable source_id
不要使用文件名或路径作为长期业务身份。
推荐:
text
source_id = 业务表主键
例如:
text
FILE-1001
5. 为什么需要 version
text
source_id
=
它是谁
version
=
这是第几代内容
例如:
text
FILE-1001 v1
FILE-1001 v2
6. Chunk ID 建议
text
FILE-1001:v1:chunk:0001
FILE-1001:v1:chunk:0002
新版本:
text
FILE-1001:v2:chunk:0001
这样能定位 Source、Version、Chunk Position。
7. Chunk ID 函数
python
def build_chunk_id(
source_id,
version,
chunk_index
):
return (
f"{source_id}"
f":v{version}"
f":chunk:"
f"{chunk_index:04d}"
)
预期输出 / 你应该看到的结果
text
没有输出。
测试:
python
print(
build_chunk_id(
"FILE-1001",
2,
3
)
)
预期输出 / 你应该看到的结果
text
FILE-1001:v2:chunk:0003
8. content_hash
作用:
text
判断实际Embedding Input有没有变化
python
import hashlib
def calculate_content_hash(text: str):
return hashlib.sha256(
text.encode("utf-8")
).hexdigest()
预期输出 / 你应该看到的结果
text
没有输出。
9. metadata_hash
正文没变,但这些可能变化:
text
column_code
status
ACL
所以建议区分:
text
content_hash
metadata_hash
10. Metadata Hash
python
import json
def calculate_metadata_hash(metadata: dict):
canonical_json = json.dumps(
metadata,
ensure_ascii=False,
sort_keys=True
)
return hashlib.sha256(
canonical_json.encode("utf-8")
).hexdigest()
预期输出 / 你应该看到的结果
text
没有输出。
11. ACL 变化需要重新 Embedding 吗?
通常不需要。
因为:
text
正文没变
→ Vector语义没变
只需要更新 Metadata。
但底层 Vector Store 如果不支持 Metadata-only Update,可能需要重新 Upsert 索引记录。
12. pipeline_version
即使业务内容没变,只要:
text
chunk_size变化
overlap变化
Embedding模型变化
Tokenizer变化
Index Text构造变化
都可能需要重新索引。
所以:
text
pipeline_version = rag-v1
升级:
text
rag-v1 → rag-v2
触发:
text
REINDEX
13. Change Detection
python
def detect_change(
old_record,
new_content_hash,
new_metadata_hash,
pipeline_version
):
if old_record is None:
return "CREATE"
if old_record["pipeline_version"] != pipeline_version:
return "REINDEX"
if old_record["content_hash"] != new_content_hash:
return "CONTENT_UPDATE"
if old_record["metadata_hash"] != new_metadata_hash:
return "METADATA_UPDATE"
return "SKIP"
预期输出 / 你应该看到的结果
text
没有输出。
14. 五种 Change Type
text
CREATE
→ 新建Index
CONTENT_UPDATE
→ 重新Chunk + Embedding
METADATA_UPDATE
→ 更新Metadata
REINDEX
→ Pipeline升级,整套重建
SKIP
→ 什么都不做
15. UPDATE 推荐策略
不推荐:
text
先删v1
↓
再建v2
更安全:
text
Build v2
↓
Validate v2
↓
Switch Active v1 → v2
↓
Cleanup v1
即:
text
Build New
→ Switch
→ Cleanup Old
16. DELETE
正确流程:
text
Source Delete
↓
根据source_id查Registry
↓
找到chunk_ids
↓
先禁止Serving
↓
Dense Index删除
↓
BM25 Index删除
↓
Registry更新
↓
Cache失效
17. Soft Delete
可以先:
text
status = DELETED
Retriever 强制只查:
text
status = ACTIVE
然后异步做 Physical Cleanup。
18. Upsert 不是完整同步
旧版本:
text
Chunk1
Chunk2
Chunk3
Chunk4
新版本:
text
Chunk1
Chunk2
Chunk3
如果只 Upsert 新的三个:
text
旧Chunk4仍然残留
所以还需要:
text
Stale Chunk Cleanup
19. Incremental Indexing
只处理发生变化的 Source,而不是每次重新构建整个知识库。
例如:
text
100000个文件
新增10
修改20
删除5
其余99965不变
只处理 35 个变化。
20. Incremental vs Full Reconciliation
text
Incremental
=
处理明确变化的数据
速度快
Full Reconciliation
=
对比Source全集和Registry
发现漏同步和孤儿索引
成本更高
推荐:
text
平时Incremental
+
定期Reconciliation
Day48 核心结论
RAG Index 不是"一次建完就结束",而是由业务 Source 驱动、可重建、可增量维护、可版本切换的搜索副本。
Day48 复习题
1. 为什么 Vector Store 不能当 Source of Truth?
答案: 因为它是检索派生数据,真正权威的是业务数据库和原始文件。
2. 为什么需要 stable source_id?
答案: 为了在文件名、路径变化后仍然识别同一个业务资源。
3. content_hash 解决什么?
答案: 判断实际索引文本是否变化,从而决定是否重新 Chunk / Embedding。
4. metadata_hash 解决什么?
答案: 判断 ACL、栏目、状态等 Metadata 是否变化。
5. ACL 变化需要重新 Embedding 吗?
答案: 通常不需要。
6. 什么是 Stale Chunk?
答案: Source 已更新或删除,但旧 Chunk 仍残留在 Search Index 中。
7. 为什么 Upsert 不等于完整同步?
答案: 新版本不存在的旧 Chunk 不会自动消失。
Day49|Indexing Reliability
1. 为什么 Index Job 一定会失败
Indexing 可能依赖:
text
Database
File Server
Embedding API
Vector DB
OpenSearch
Redis
MQ
任何一个都有可能出现 Timeout、Rate Limit、Network Error、Service Restart、Data Error。
生产目标不是:
text
永远不失败
而是:
失败后可以恢复,而且不会越重试越乱。
2. 四个核心概念
text
Retry
=
失败后重新执行
Idempotency
=
重复执行最终结果仍一致
Failure Isolation
=
一个Source失败不影响其他Source
Dead Letter
=
多次失败后进入专门失败区
3. Failure Isolation
正确目标:
text
FILE-001 SUCCESS
...
FILE-037 FAILED
FILE-038 SUCCESS
...
FILE-100 SUCCESS
而不是第37个失败后后面全部停止。
4. Retryable vs Permanent
Retryable:
text
Timeout
HTTP 429
HTTP 502
HTTP 503
Connection Error
临时服务不可用
Permanent:
text
PDF损坏
格式不支持
文件不存在
必填字段缺失
数据格式非法
Permanent 不应该无限自动 Retry。
5. 异常分类
python
class RetryableIndexError(Exception):
pass
class PermanentIndexError(Exception):
pass
预期输出 / 你应该看到的结果
text
没有输出。
6. Exponential Backoff
text
第1次失败 → 1秒
第2次失败 → 2秒
第3次失败 → 4秒
第4次失败 → 8秒
python
def calculate_backoff(retry_count):
return 2 ** retry_count
预期输出 / 你应该看到的结果
text
没有输出。
7. Jitter
大量任务同时失败后,如果都按完全相同的时间重试,会再次产生流量尖峰。
所以:
text
Backoff
+
Random Jitter
让请求散开。
8. Retry 必须有限
例如:
text
max_retry_count = 5
超过后:
text
DEAD
不能无限 Retry。
9. Dead Letter
可以是 MQ Dead Letter Topic,也可以是:
text
rag_index_task.status = DEAD
然后由人工修复数据、修复代码或手动重试。
10. Idempotency
定义:
同一个任务执行一次或执行多次,最终结果应该一致。
例如:
text
FILE-1001:v2
执行1次或3次,最终 Search Index 里仍然只有同一套 v2 Chunk。
11. 稳定 Chunk ID 是幂等基础
text
FILE-1001:v2:chunk:0001
FILE-1001:v2:chunk:0002
任务重跑时仍生成相同 ID,可以 Upsert,而不是重复 Insert。
12. task_key
建议:
text
source_id
+
source_version
+
task_type
+
pipeline_version
例如:
text
FILE-1001:v2:UPDATE:rag-v3
13. PROCESSING 可能假死
Worker 宕机后任务可能永远停在 PROCESSING。
所以可以设计:
text
heartbeat_time
lock_expire_time
locked_by
超时后重新接管。
14. Dense 成功、BM25 失败怎么办
text
v1继续ACTIVE
v2保持FAILED / INDEXING
不要让半成品 v2 进入线上。
15. 为什么不一定需要回滚 Dense
如果 v2 尚未 ACTIVE:
text
Serving看不到v2
即使 Dense v2 已经写入,也不会污染线上,可以直接重试 v2。
这就是 Version Isolation。
16. Validation
不能只看 API 没报错。
应该检查:
text
Expected Chunk IDs
=
Dense Chunk IDs
=
Sparse Chunk IDs
python
def validate_chunk_ids(
expected_ids,
dense_ids,
sparse_ids
):
return (
set(expected_ids)
== set(dense_ids)
== set(sparse_ids)
)
预期输出 / 你应该看到的结果
text
没有输出。
17. SUCCESS 什么时候才能写
应该满足:
text
Chunk成功
+
Dense成功
+
BM25成功
+
Metadata成功
+
Validation成功
+
Activate成功
之后才能:
text
SUCCESS
18. Failed 记录为什么要独立保存
如果失败记录和单 Source 主事务一起回滚:
text
主事务失败
↓
FAILED记录也消失
系统就不知道它失败过。
因此失败状态、失败原因、任务记录需要能够独立持久化。
19. Index Lag
例如:
text
10:00 Source更新
10:20 Index ACTIVE
则:
text
Index Lag = 20分钟
它反映知识新鲜度。
Day49 核心结论
text
第一:
没有幂等,就不敢安全Retry。
第二:
一个Source失败,不应该拖垮整个Batch。
第三:
多系统一致性可以通过Version Isolation + State Machine + Retry实现。
Day49 复习题
1. Retry 是什么?
答案: 对临时失败的同一个确定性操作再次执行。
2. Idempotency 是什么?
答案: 重复执行一次或多次,最终业务结果仍然一致。
3. Failure Isolation 是什么?
答案: 让一个 Source 的失败只影响自己。
4. Dead Letter 是什么?
答案: 多次失败仍不能自动恢复的任务进入专门失败区等待人工处理。
5. 为什么需要 Backoff?
答案: 避免 Retry Storm,给下游服务恢复时间。
6. 为什么需要 Jitter?
答案: 避免大量任务在同一时间一起重试。
7. Dense成功、BM25失败,新版本能ACTIVE吗?
答案: 不能。
8. 为什么 Activate 前要 Validation?
答案: API成功不代表索引完整,需要检查 Chunk Count / Chunk ID 一致性。
Day50|Production RAG Knowledge Base Architecture
1. 企业 RAG 至少有两条 Pipeline
text
Indexing Pipeline
=
知识怎么进入RAG
Serving Pipeline
=
用户提问后知识怎么安全地被找到
2. 三个平面
text
Data Plane
=
Document / Chunk / Vector / BM25数据
Control Plane
=
Task / Version / Registry / Permission / Metadata
Serving Plane
=
Retriever / RRF / Reranker / Context / LLM
3. Source → Index Mapping
不要直接把业务表所有字段塞进 Search Index。
应该:
text
Business Entity
↓
Index Mapping
↓
Index Text + Search Metadata
4. Index Text
python
def build_index_text(source):
return (
f"标题:{source['title']}\n"
f"正文:{source['content']}"
)
预期输出 / 你应该看到的结果
text
没有输出。
5. Search Metadata
python
def build_metadata(source):
return {
"source_id": source["source_id"],
"source_version": source["source_version"],
"tenant_id": source["tenant_id"],
"column_code": source["column_code"],
"status": source["status"],
"material_type": source["material_type"]
}
预期输出 / 你应该看到的结果
text
没有输出。
6. Task 和 Registry 的区别
text
Registry
=
当前Index世界是什么状态
Task
=
准备做什么
正在做什么
做到哪一步
例如:
text
Registry:
FILE-1001 ACTIVE=v1
Task:
FILE-1001 正在构建v2
完全合理。
7. Indexing Pipeline
text
Source
↓
Build Index Text
↓
Build Metadata
↓
Content Hash
↓
Metadata Hash
↓
Detect Change
↓
Index Task
↓
Idempotency Check
↓
Chunk
↓
Dense + BM25
↓
Validate
↓
Activate
↓
Registry Update
↓
Cleanup Old
8. Serving Pipeline
text
Authenticated User
+
Question
↓
Permission Service
↓
Permission Scope
↓
FilterBuilder
↓
Security + Lifecycle + Business Filters
↓
Authorized Dense + BM25
↓
RRF
↓
Candidate TopK
↓
Reranker
↓
Final ACL Check
↓
Final TopK
↓
Context
↓
Qwen
↓
Answer + Sources
9. Security Filter 永远不能放宽
text
tenant
ACL
visibility
是 Hard Constraint。
Business Filter 可以降级:
text
产品 + 月报 + 日期
↓
产品 + 月报
↓
产品
但每一步必须保留 Security Filter。
10. ACTIVE 是 Indexing 和 Serving 的边界
text
PENDING
PROCESSING
FAILED
INDEXING
Serving 不应该看到。
Serving 只消费:
text
ACTIVE
所以:
text
ACTIVE
=
这批知识已经完整准备好,可以对外提供检索服务
11. 删除也要走 Lifecycle
text
Source Delete
↓
Registry
↓
找到Chunk IDs
↓
先禁止Serving
↓
Dense Cleanup
↓
Sparse Cleanup
↓
Cache Invalidate
↓
Registry Update
12. Cache 也是 Lifecycle 一部分
知识更新后可能需要失效:
text
Retrieval Cache
Context Cache
Answer Cache
权限变化后:
text
Permission Cache
也必须及时失效。
13. 企业 RAG 的四种一致性
text
Source
↔
Dense Index
Source
↔
BM25 Index
Permission
↔
Search Filter
Active Version
↔
Serving Result
14. Monitoring
| 维度 | 指标 |
|---|---|
| 索引健康 | SUCCESS / FAILED / DEAD |
| 新鲜度 | Index Lag |
| 完整性 | Dense/BM25 Chunk一致性 |
| 权限安全 | Unauthorized Retrieval Rate |
| 检索质量 | Recall@K / MRR |
| 性能 | Retrieval / Rerank Latency |
| 稳定性 | Retry Count / Failure Code |
| 服务质量 | No-result Rate |
15. RAG Debug 应该逐层排查
用户:
text
"我刚上传文件,为什么搜不到?"
不要先改 Prompt。
应该:
text
Source存在吗?
Index Task创建了吗?
Task成功了吗?
Registry ACTIVE了吗?
Dense有吗?
BM25有吗?
Permission Filter把它过滤了吗?
Candidate里有吗?
RRF排名多少?
Reranker后还在吗?
Context里有吗?
Qwen使用了吗?
16. RAG Error 分类
text
Source Error
Indexing Error
Version Error
Permission Error
Filter Error
Retrieval Error
Fusion Error
Reranking Error
Context Error
Generation Error
这比一句"RAG答错了"更有工程意义。
17. Java 侧建议拆分
Query Side
text
RagQueryService
PermissionService
SearchFilterBuilder
HybridRetriever
DenseRetriever
Bm25Retriever
RrfFusionService
RerankService
AclValidator
ContextBuilder
LlmClient
Index Side
text
KnowledgeIndexService
SourceLoader
IndexTextBuilder
ChunkService
EmbeddingService
DenseIndexRepository
SparseIndexRepository
IndexRegistryService
IndexTaskService
IndexVersionService
Day50 核心结论
第十周已经把 RAG 从"检索算法"扩展成了一套真正需要考虑权限、生命周期、增量同步、多系统一致性、重试、幂等和线上 Serving 的长期运行系统。
三、第十周最重要的十句话
1. Prompt 不是权限安全边界。
2. 无权限 Document 最好从 Retrieval 一开始就不能参加检索。
3. Filter 决定资格,Retriever / Reranker 决定排名。
4. Security Filter 是 Hard Constraint,不能为了 Recall 自动放宽。
5. Vector/BM25 Index 是业务 Source 的可重建搜索副本,不是 Source of Truth。
6. content_hash 决定正文是否需要重新索引,metadata_hash 决定 Metadata 是否变化。
7. Upsert 不等于完整同步,旧 Stale Chunk 仍然需要 Cleanup。
8. 新版本应该 Build → Validate → Activate → Cleanup Old。
9. 没有幂等,就不敢安全 Retry。
10. 企业 RAG 的核心已经不只是 LLM,而是 Search + Data Engineering + Distributed Systems + Security。
四、第十周综合面试回答
如果面试官问:
"请你设计一个企业级 RAG 知识库。"
可以回答:
数据侧我会把业务数据库和原始文件作为 Source of Truth,使用稳定 source_id 标识业务资源,通过 content hash 判断索引文本是否变化,通过 metadata hash 判断 ACL、栏目、状态等 Search Metadata 是否变化,并使用 pipeline version 判断 Chunk 策略、Embedding 模型或 Tokenizer 是否升级。新增或更新后生成 Chunk,并分别写入 Dense 和 BM25 索引,新版本只有在两路索引都成功且通过 Chunk 完整性校验后才切换为 ACTIVE,旧版本再异步清理。
索引任务侧使用稳定 task key、source/version/chunk ID 和 upsert 保证幂等;对网络超时、限流等 transient failure 使用有限 Retry、Backoff 和 Jitter,对不可自动恢复的问题进入 Dead Letter;批处理以单 Source 作为 Failure Isolation 粒度,避免一个文件失败拖垮整个批次。
查询侧从认证上下文获取当前用户,由 Permission Service 计算 PermissionScope,FilterBuilder 生成 tenant、ACL、status 等 Security Filter 以及 product_code、material_type 等 Business Filter。Dense 和 BM25 必须使用完全一致的 Security Scope,再通过 RRF 做融合、Cross-Encoder 做精排,进入 Context 前再进行最终 ACL Validation,最终只把 Authorized Final TopK 交给 LLM 生成答案和来源。
线上还会监控 Index Lag、FAILED/DEAD Task、Unauthorized Retrieval Rate、Recall@K、MRR、Retrieval Latency 等指标,并通过分层 Debug 区分 Source、Indexing、Permission、Retrieval、Rerank、Context 和 Generation 问题。
五、第十周最终验收清单
学完本周后,应能解释:
text
为什么企业RAG必须有ACL?
为什么Prompt不能承担权限?
Pre-filter和Post-filter区别?
Security Filter和Business Filter区别?
为什么Metadata Schema很重要?
为什么多个Column适合IN / terms?
为什么Tenant和Column通常是AND?
什么是stable source_id?
为什么需要version?
content_hash和metadata_hash区别?
ACL变化要不要重新Embedding?
为什么需要pipeline_version?
什么是Incremental Indexing?
什么是Stale Chunk?
为什么Upsert不等于完整同步?
为什么更新最好Build New再Switch?
为什么Dense和BM25必须版本一致?
什么是Index Registry?
什么是Index Task?
什么是Idempotency?
什么是Failure Isolation?
什么错误应该Retry?
什么错误不应该无限Retry?
为什么需要Backoff和Jitter?
什么是Dead Letter?
Dense成功、BM25失败怎么办?
为什么Activate必须放最后?
为什么需要Validation?
什么是Index Lag?
RAG答错时应该从哪些层Debug?
Indexing Pipeline和Serving Pipeline有什么区别?
Data Plane / Control Plane / Serving Plane分别是什么?
六、下周预告|第十一周:Context Engineering + Memory
第十周解决的是:
text
资料能安全、可靠地进入索引
+
用户只能搜索自己有权限的资料
第十一周开始解决:
Retriever 找到的 Document,不等于最终要给 LLM 的 Context
核心问题:
text
Retriever找到了20个Chunk
哪些应该留下?
重复Chunk怎么去重?
顺序怎么排?
Context太长怎么办?
Token Budget怎么控制?
历史聊天是不是全部放进去?
State、Memory、Context到底有什么区别?
短期Memory和长期Memory怎么设计?
一个用户为什么不能直接thread_id=user_id?
最终会形成:
text
Retrieved Documents
↓
Deduplicate
↓
Select
↓
Order
↓
Compress
↓
Token Budget
↓
Conversation / Memory Context
↓
Final Context
↓
LLM
第十一周核心主题:
text
Context Engineering
+
Conversation State
+
Short-term Memory
+
Long-term Memory
+
Context Window Management
一句话:
第十周解决"知识怎么安全可靠地进来和被搜到",第十一周开始解决"搜到以后,到底给模型看什么、看多少、记多久"。