第十周完整学习笔记:继续升级为“检索更准、权限安全、索引可维护、失败可恢复、长期可运行”的企业级 RAG 系统

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
栏目授权

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 必须持续同步。


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 复制代码
没有输出。

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

一句话:

第十周解决"知识怎么安全可靠地进来和被搜到",第十一周开始解决"搜到以后,到底给模型看什么、看多少、记多久"。

相关推荐
毒爪的小新1 小时前
最新 Dify 1.14.0+ 内网离线插件打包、部署完整踩坑方案(解决 uv.lock 依赖校验失败)
运维·ai·uv·dify
七牛云行业应用1 小时前
DeepSeek Harness 插件架构 vs MCP:Cordis 的「Everything is a Plugin」到底解决什么
ai·github·ai编程
sg_knight2 小时前
Bug 定位实战:把报错丢给 Codex,它是怎么修的
chatgpt·llm·bug·openai·agent·ai编程·codex
Ivanqhz2 小时前
MoE + LLM 应用
数据库·人工智能·pytorch·python·深度学习·算法·1024程序员节
Mr. zhihao2 小时前
Deep Agent 架构深度解析:主Agent与子Agent完整交互机制与核心参数作用
python·ai·langgraph·deepagent
l1m0_2 小时前
AI设计工具有哪些?Paico、Pixso AI、墨刀AI、Lovable、Codex怎么选
前端·人工智能·ai·设计·墨刀·codex·pixso
狗凯之家源码网2 小时前
iOS C2 V2 与 Python FastAPI 全链路远控系统深度评测
python·ios·fastapi
超级架构师2 小时前
让 Fortran 模型自己调用 Agent:原生 Fortran 2008 实现深度剖析
人工智能·python·架构·机器人·开源·fortran
小蒜学长2 小时前
基于Django+Vue喵汪联盟宠物领养系统的设计与实现(代码+数据库+LW)
后端·python·django·宠物领养系统·流程规范化