RAG工程化实战:从玩具Demo进化成企业可用知识库

文章目录

    • 前言
    • 一、今天到底要搞懂啥
    • 二、为啥之前那套东西上不了班
    • [三、Document Loader:先把文件读成字](#三、Document Loader:先把文件读成字)
    • [四、切 Chunk 那点事](#四、切 Chunk 那点事)
    • [五、为什么 Chunk 之后还得给它贴标签](#五、为什么 Chunk 之后还得给它贴标签)
    • [六、动手:给 Chroma 加 Metadata](#六、动手:给 Chroma 加 Metadata)
    • [七、Metadata Filter:先筛一遍再搜](#七、Metadata Filter:先筛一遍再搜)
    • [八、为啥 Filter 要放在向量搜索前面](#八、为啥 Filter 要放在向量搜索前面)
    • [九、Filter + Vector + Rerank 三件套](#九、Filter + Vector + Rerank 三件套)
    • 十、踩坑现场:标签明明写了,为啥查不到
    • 十一、学习阶段的流氓解法:删库重建
    • 十二、这次排错让我悟了一件事
    • [十三、Incremental Update:别动不动就全量重建](#十三、Incremental Update:别动不动就全量重建)
    • [十四、现在的 RAG 长啥样](#十四、现在的 RAG 长啥样)
    • 十五、今天想明白的几件事
      • [15.1 RAG 不等于 Vector Search](#15.1 RAG 不等于 Vector Search)
      • [15.2 Metadata 是企业库的灵魂](#15.2 Metadata 是企业库的灵魂)
      • [15.3 Vector DB 不只是存向量](#15.3 Vector DB 不只是存向量)
      • [15.4 RAG 是一条 Pipeline](#15.4 RAG 是一条 Pipeline)
      • [15.5 Demo 和生产系统最大的差别,在数据更新](#15.5 Demo 和生产系统最大的差别,在数据更新)
    • 十六、收尾

P.S. 推荐一个大神的教程给想要了解或者学习人工智能知识的读者,这个教程里内容讲解通俗易懂且风趣幽默,对我帮助很大。我想与大家分享这个宝藏教程,请点击下方链接查看, 传送门https://blog.csdn.net/qq_74013365

前言

前几天我吭哧吭哧手写了个 RAG,跑通的那一刻我甚至给自己泡了杯咖啡,觉得自己马上能创业了。

流程大概是这样:用户提问 → Embedding → 向量搜索 → TopK → Rerank → 大模型吐答案。

后来我又补了 Chunk、Recall、Precision、Hybrid Search 这些概念,笔记写了厚厚一沓。

直到今天我才反应过来一个扎心的事实:我那个知识库,一共就 5 条数据,还都是我自己敲在 knowledge.txt 里的。这玩意儿拿去跟老板演示,老板问一句"那我们公司 3000 份合同怎么办",我大概只能现场表演一个离职。

所以今天的主题很朴素:怎么从"能跑的玩具",走向"能上班的系统"。

一、今天到底要搞懂啥

核心就这几个词:

Document Loader、Document、Chunk、Metadata、Metadata Filter、Incremental Update、RAG Pipeline。

其中最要命的一条链路是:

复制代码
Metadata Filter  →  Vector Search  →  Rerank  →  LLM

先靠元数据把范围砍下来,再去做向量搜索。不然你在 10 万条数据里捞那 100 条相关的,电费都能把你家空调干停机。

二、为啥之前那套东西上不了班

我之前的代码长这样:

复制代码
documents = [
    { "text": "公司工资于每月15日发放。" },
    { "text": "公司工作时间为周一至周五,每天上午9点到下午6点。" },
    { "text": "员工入职满一年后可以享受带薪年假。" }
]

学习用,没毛病。但真实企业的目录大概长这样:

复制代码
knowledge/
├── 薪资制度.pdf
├── 员工手册.pdf
├── 上海项目合同.pdf
├── 产品说明书.docx
├── 技术规范.txt
└── 项目资料/
    ├── 项目A.pdf
    ├── 项目B.pdf
    └── 项目C.pdf

这时候问题就全来了:

PDF 怎么读?Word 怎么转文本?切完 Chunk 它知不知道自己来自哪个文件?用户问"上海项目付款条件是什么",你总不能把所有合同都扫一遍吧?新增一个文件,难道要把 10 万条数据重新 Embedding 一遍,然后让服务器在机房里热到烤鸡蛋?

说白了,之前我那套就像在家里用小炖锅煮了一碗螺蛳粉,还挺香。但真要开馆子,你得考虑后厨排烟、食材进货、菜单管理、客人们还会要求"少辣不加香菜"------这些破事,才叫工程化。

三、Document Loader:先把文件读成字

大模型和 Embedding 模型,本质上只认文本。

所以 PDF、Word、TXT 进来之前,得先过一道"提取"工序:

复制代码
PDF / Word / TXT
      ↓
  Document Loader
      ↓
     Text
      ↓
     Chunk
      ↓
   Embedding
      ↓
   Vector DB

举个例子:

复制代码
薪资制度.pdf
      ↓
  PDF Loader
      ↓
公司工资于每月15日发放。
公司年度奖金根据绩效进行发放。
员工入职满一年后可以享受带薪年假。

记住一句话:RAG 不是真的"看懂"PDF,它只是先把 PDF 扒成了纯文本。

以后你用 LangChain、LlamaIndex 看到 PDFLoader(...) 这种东西,本质就是干这事儿的,别被名字唬住。

就像你点外卖,商家后台其实不知道你长啥样,他只知道你的收货地址、电话和备注"多放辣"。Loader 就是那个把 PDF 翻译成"字"的快递小哥,他自己不读内容,他只负责把东西从 A 运到 B。

四、切 Chunk 那点事

拿到完整文本之后,下一步还是切 Chunk。我之前的切法非常暴烈:

复制代码
chunks = [
    chunk.strip()
    for chunk in text.split("\n\n")
    if chunk.strip()
]

用双换行一切,完事。对于那种整整齐齐的示例文本,确实好使:

复制代码
Chunk 1:公司工资于每月15日发放。
Chunk 2:公司工作时间为周一至周五,每天上午9点到下午6点。
Chunk 3:员工入职满一年后可以享受带薪年假。

但真实的 PDF 里,段落是乱的、页眉页脚会混进来、图片说明可能突然插在中间。你要是还靠 split("\n\n"),切出来的东西大概跟我老妈切的土豆块一样------大小随缘,形状看心情。

所以后面要正经学:Chunk Size、Chunk Overlap、Token Chunking、文本清洗。

切 Chunk 这事儿特别像包饺子:皮太大,一口塞不下;皮太小,一煮就破;还不能把馅漏了。我现在的水平大概就是"能包熟",离"好吃"还有距离。

五、为什么 Chunk 之后还得给它贴标签

这是今天最关键的一个概念。

之前我的 Vector DB 里存的东西就三样:ID、文本、Embedding。

但在企业知识库里,光知道"这句话长这样"根本不够。比如你存了一句:

复制代码
公司的办公地点位于上海。

你还得知道:

它来自哪个文件?属于薪资类还是行政类?是哪个项目的?第几页?哪个部门发的?

所以我们给每个 Chunk 加一个叫 Metadata 的东西:

复制代码
{
    "category": "office",
    "source": "company.txt",
    "file_type": "txt"
}

再真实一点:

复制代码
{
    "source": "上海项目合同.pdf",
    "file_type": "pdf",
    "page": 12,
    "category": "contract",
    "project_id": "SH-2026-001"
}

最后一条知识大概长这样:

复制代码
┌──────────────────────────────┐
│             ID               │
│            Text             │
│          Embedding          │
│          Metadata           │
│   ├── source                │
│   ├── file_type             │
│   ├── category              │
│   ├── page                  │
│   └── project_id            │
└──────────────────────────────┘

Metadata 就像每个人的身份证。你走在街上,光看脸只能认出"这是个人",但你查他身份证才知道他叫啥、住哪、哪个公司的、有没有前科。向量搜索是"看脸找人",Metadata 是"查身份证",俩一起用,警察叔叔效率才高。

六、动手:给 Chroma 加 Metadata

我的知识库还是那个 knowledge.txt,5 条数据:

复制代码
公司工资于每月15日发放。
公司工作时间为周一至周五,每天上午9点到下午6点。
员工入职满一年后可以享受带薪年假。
公司每周五下午提供下午茶。
公司的办公地点位于上海。

切完 5 个 Chunk,我给它们贴标签:

复制代码
metadatas = [
    { "category": "salary" },
    { "category": "work_time" },
    { "category": "vacation" },
    { "category": "afternoon_tea" },
    { "category": "office" }
]

然后写进 Chroma:

复制代码
collection.add(
    ids=[f"doc_{index}" for index in range(len(chunks))],
    documents=chunks,
    embeddings=embeddings,
    metadatas=metadatas
)

写完之后,Chroma 里就变成了:

复制代码
doc_0  工资           category = salary
doc_1  工作时间       category = work_time
doc_2  年假           category = vacation
doc_3  下午茶         category = afternoon_tea
doc_4  办公地点       category = office

七、Metadata Filter:先筛一遍再搜

有了标签之后,搜索之前就能先过滤。

比如用户问"公司在哪办公",这明显是 office 类的问题,那就直接:

复制代码
where={
    "category": "office"
}

完整查询:

复制代码
results = collection.query(
    query_embeddings=[query_vector],
    n_results=5,
    where={ "category": "office" }
)

原来 5 条数据:

复制代码
5 条
  ↓
category = office
  ↓
1 条

最后就剩一句"公司的办公地点位于上海",交给后面处理。

这就像你去海底捞找人,你朋友说"我在靠窗那桌",你直接走过去就行,不用把每桌都扫一遍问"哎哥们你是不是来吃火锅的"。服务员要是真这么干,海底捞能被你问到停业整顿。

八、为啥 Filter 要放在向量搜索前面

这个顺序非常关键。

正确姿势:

复制代码
Metadata Filter
      ↓
   Vector Search
      ↓
     Rerank
      ↓
      LLM

错误姿势:

复制代码
Vector Search
      ↓
     Rerank
      ↓
 Metadata Filter

为啥?假设库里有 10000 条知识,其中真正属于"上海项目"的就 100 条。

先 Filter:

复制代码
10000 条
   ↓
Metadata Filter
   ↓
100 条
   ↓
Vector Search → TopK → Rerank

先 Vector Search:

复制代码
10000 条
   ↓
Vector Search → TopK → Rerank
   ↓
再过滤

显然后者是有病。

说白了就是:先用结构化条件把范围砍小,再用语义搜索去精搜。

这跟 SQL 里写 WHERE project_id = 'SH-2026-001' 是一个味儿。

你找对象,肯定是先按"同城、单身、年龄合适"筛,再去看照片聊性格,而不是先把全宇宙的人都聊一遍,最后才问"哎你是上海的吗"。后者那叫牛郎织女,不叫相亲。

九、Filter + Vector + Rerank 三件套

到这儿,前几天学的 Rerank 也能串进来了。完整长链路:

复制代码
用户提问
   ↓
Query Embedding
   ↓
Metadata Filter
   ↓
Vector Search
   ↓
TopK
   ↓
Rerank
   ↓
TopN
   ↓
Context
   ↓
DeepSeek
   ↓
Answer

每个环节干啥活:

复制代码
Metadata Filter   负责缩小搜索空间
Vector Search      负责语义召回
Rerank             负责精确排序
LLM                负责读 Context 并写答案

一句话:Filter 砍范围,Vector 粗捞,Rerank 精选,LLM 动笔。

十、踩坑现场:标签明明写了,为啥查不到

今天我真被这个问题卡了半小时。

我把代码改成这样:

复制代码
metadatas = [
    {"category": "salary"},
    ...
]

然后查询:

复制代码
where={
    "category": "office"
}

结果返回:

复制代码
[None, None, None, None, None]

我人傻了。

最后发现原因特别朴素:你改建库代码,不会自动修改已经存在的 Vector DB。

之前的 Chroma 数据早就持久化到 ./chroma_db 里了。我改 collection.add(...),只是改了"以后怎么写",它不会回头把旧数据重写一遍。

这就像你谈了个对象,分手之后你改了自己的择偶标准,结果你前任还是那个前任------你不能指望她自动升级成你想要的样子。你得重新找,或者......把记忆格式化。

十一、学习阶段的流氓解法:删库重建

最简单的办法就是直接干掉:

复制代码
chroma_db/

然后重新跑:

复制代码
python file_rag_demo.py

重新走一遍:读文件 → Chunk → Embedding → Metadata → 写入 Chroma。

重建完检查一下:

复制代码
data = collection.get()
print(data["metadatas"])

输出:

复制代码
[
  {'category': 'salary'},
  {'category': 'work_time'},
  {'category': 'vacation'},
  {'category': 'afternoon_tea'},
  {'category': 'office'}
]

再查:

复制代码
where={ "category": "office" }

向量搜索 TopK:

复制代码
0.9698 - 公司的办公地点位于上海。

Rerank:

复制代码
0.9749 - 公司的办公地点位于上海。

最后交给 DeepSeek,它回我:

复制代码
根据知识库中的信息,公司的办公地点位于上海。

整条链路,通了。

十二、这次排错让我悟了一件事

RAG 不是一个黑盒,它是一条流水线:

复制代码
Document Loader
   ↓
Chunk
   ↓
Embedding
   ↓
Vector DB
   ↓
Metadata Filter
   ↓
Vector Search
   ↓
Rerank
   ↓
Context
   ↓
LLM

出问题的时候,别光盯着最后那句答案发呆,得一层层往下查:

复制代码
① 文件读对了吗?
   ↓
② Chunk 切对了吗?
   ↓
③ Embedding 生成了吗?
   ↓
④ Vector DB 写进去了吗?
   ↓
⑤ Metadata 写进去了吗?
   ↓
⑥ Filter 生效了吗?
   ↓
⑦ 向量搜索找到正确内容了吗?
   ↓
⑧ Rerank 排对了吗?
   ↓
⑨ Context 拼对了吗?
   ↓
⑩ LLM 是基于 Context 答的吗?

这感觉就像你肚子疼,医生不会直接让你开刀,他得先问你今天吃啥了、拉了没、发不发烧。RAG 排错也一样,别一上来就怪大模型"你怎么这么笨",它可能只是吃到了一份被切坏的外卖。

十三、Incremental Update:别动不动就全量重建

今天还学了个词,叫增量更新。

假设知识库已经有 10000 个 Chunk,现在你新增一个 PDF。

如果你每次都:

复制代码
删掉整个 Vector DB
   ↓
10000 个 Chunk 重新 Embedding
   ↓
重新写入

这不是写程序,这是在给服务器做电疗。

正确姿势:

复制代码
原来的 10000 个 Chunk
       +
     新增 PDF
       ↓
只处理新增的 PDF
       ↓
  Chunk → Embedding
       ↓
   追加写入 Vector DB

当然,今天这个 Demo 为了方便验证 Metadata,我还是用了删库重建。但真实项目里你敢这么干,运维能顺着网线过来捶你。

这就像你家冰箱里已经放了 100 瓶可乐,今天又买了 1 瓶。你不会把冰箱清空、所有可乐重新摆一遍位置,你就是把新那瓶塞进去而已。RAG 也一样,别把"全量重建"当万能钥匙,那玩意儿只适合学习阶段,不适合上班。

十四、现在的 RAG 长啥样

最开始是这样:

复制代码
Question
   ↓
Embedding
   ↓
Vector Search
   ↓
   LLM

现在进化成这样:

复制代码
Knowledge Build
  Documents
     ↓
   Chunk
     ↓
  Metadata
     ↓
SiliconFlow Embedding
     ↓
   Chroma
     ↓
User Question → Query Embedding
     ↓
Metadata Filter
     ↓
Vector Search
     ↓
   TopK
     ↓
  Rerank
     ↓
   TopN
     ↓
  Context
     ↓
  DeepSeek
     ↓
  Answer

说真的,看到这张图我有点小激动------这玩意儿终于有点企业知识库那味儿了,不再是我笔记本上跑着玩的小玩具。

十五、今天想明白的几件事

真实的 RAG 是好几步拼起来的:

复制代码
Filter → Vector Search → Rerank

而不是一句 Question → Vector DB → TopK 就完事。

15.2 Metadata 是企业库的灵魂

Vector 负责语义检索,Metadata 负责结构化过滤。俩配合,才能同时用上"意思相近"和"条件匹配"。

15.3 Vector DB 不只是存向量

一条有用的知识至少得有:

复制代码
ID / Text / Embedding / Metadata

向量负责找,原文负责答,Metadata 负责圈范围。三位一体,缺一个都不好使。

15.4 RAG 是一条 Pipeline

出问题别先骂 LLM,按段排查:Loader → Chunk → Embedding → Vector DB → Filter → Search → Rerank → Context → LLM。

15.5 Demo 和生产系统最大的差别,在数据更新

Demo 可以删库重建。生产环境要面对新增、修改、删除、增量 Embedding、Metadata 同步、Vector DB 持久化。这才是真正的工程问题。

写 Demo 就像在你家厨房煮泡面,水开了下调料,三分钟搞定。上生产就像开连锁餐厅,你得考虑食材保质期、后厨排班、食材涨价、顾客拉肚子要投诉------泡面那套完全不顶用。

十六、收尾

今天没引入啥新框架,但它让我换了个角度看 RAG:

RAG 不是让大模型"查个资料",它是一整套数据处理、检索、排序、生成的流水线。

今天最核心的一条链路:

复制代码
Document
   ↓
Chunk
   ↓
Metadata
   ↓
Embedding
   ↓
Vector DB
   ↓
Metadata Filter
   ↓
Vector Search
   ↓
Rerank
   ↓
LLM

四个角色:Filter 砍范围、Vector 粗召回、Rerank 精选、LLM 动笔。

到这儿,我那个 RAG 终于从"向量搜索 Demo",开始有点真实企业知识库的样子了。

下一步要干的事,就是把手写的 knowledge.txt 换成真正能读 PDF、Word、TXT 的 Loader------

毕竟用户上传一份 PDF,你总不能跟他说"麻烦你把内容自己敲成 txt 我再处理"吧。

P.S. 推荐一个大神的教程给想要了解或者学习人工智能知识的读者,这个教程里内容讲解通俗易懂且风趣幽默,对我帮助很大。我想与大家分享这个宝藏教程,请点击下方链接查看,传送门https://blog.csdn.net/qq_74013365

相关推荐
吴佳浩1 小时前
“优化 KV Cache”到底在优化什么?vLLM、SGLang、Strata 优化的是三个不同的维度
人工智能
a努力。1 小时前
MCP只连工具,不直接连技能
人工智能
neoooo1 小时前
🚀Claude Code 小技巧(一):一键进入危险模式
前端·人工智能·后端
阿里云大数据AI技术2 小时前
PAI 模型蒸馏全新发布「场景化蒸馏」:把最佳实践装进模板,让大模型能力"轻装上阵"
人工智能
亚川楼宇自控系统数据中心厂家2 小时前
机房 3D 可视化技术:从平面监控到立体化管理的升级
大数据·数据库·人工智能
LaughingZhu2 小时前
任务在飞书创建,开发在 Orca 继续:我把飞书项目接到了 Linear
大数据·人工智能·深度学习·神经网络·飞书
一水鉴天2 小时前
UML/MOF 元层级与 Model & Notation:整体设计核心术语的定稿探讨 20261012(豆包“查看聊天内容”继续)
人工智能·机器学习
陈嘿萌2 小时前
TIP 2026|没有分割标签,也能做任务导向图像融合:SSVIF的自监督语义注入
图像处理·人工智能·深度学习·计算机视觉·图像融合·学术研究与理论基础
森山冶仁2 小时前
把专家抽检变成分钟级全量初审:科学数据审核智能体的召回率与证据链怎么设计
人工智能·数据治理·数据质量·元数据