《AI 知识卡片》第 10 期 · 一堆"块"还是一张"网"?存法决定捞法
聊 RAG 时,大家的注意力几乎都在"检索"上,但这件事发生在用户提问之后 。在那之前,还有一整套离线 的活儿:把你的文档读进来、处理好、存进知识库。把 RAG 拆开看,核心的两条时间线:
Line 1:离线(提问之前) :加载 → 切块 → 变成向量 → 存入知识库。做一次,之后一直复用 ,称为索引构建。
Line 2:在线(提问之后) :把问题也变成向量 → 去库里检索 → 把捞到的资料拼进提示词 → 交给模型生成答案。每次提问都走一遍。
两条线之间有个硬约束:必须用同一个 Embedding 模型 。存的时候和查的时候得用同一把尺子,否则没法比较相关性。这一期主要来聊聊离线 部分。简单来说存进去的是什么样,检索就只能捞出什么样。
第一步:把各种格式读成"统一的文本"
现实里的资料格式很杂,如:PDF、Word、Markdown、HTML、网页、表格等,而后面所有环节只认一种东西------纯文本 + 元数据。
首先要加载 ,这里要用到不同文件格式的Loader**,**不管来源是什么格式,都解析成统一的结构。正文 ------抽出来的纯文本;元数据------这段文字来自哪个文件、哪一页、哪个章节、什么日期等。
这一步有个残酷的现实:垃圾进,垃圾出 。纯文本和 Markdown 好解析,但复杂 PDF 很容易解析得错行乱序,甚至只剩一堆乱码。这时候普通解析器不够用,得上专门的版面分析或 OCR 工具。一旦这里读错了,后面 Embedding 再准、模型再强,也救不回来。

第二步:切块,再变成向量
读进来的文档往往很长,不能整篇存。所以要先切块 ------把长文档切成一个个小块(chunk)。那这一刀往哪儿下?------常见的切法有四类,从"完全不看内容"到"看懂了内容再切":
| 切法 | 怎么切 | 代价 |
|---|---|---|
| 按字数硬切 | 数到 N 个字符就一刀,再让相邻两块重叠 一小段(chunk_overlap),把切断的语义接回来 |
最快,但很容易切在句子中间 |
| 递归切分 | 按分隔符层层退让:先按段落切,还太长就按句号、逗号、空格切,最后才硬切字符 | 通用默认选择,兼顾块大小和句子完整 |
| 按文档结构切 | 顺着 Markdown 标题、HTML 标签、代码里的函数边界切,还能把标题路径一并写进元数据 | 只适用于结构规整的文档;某一节特别长时还得再切一刀 |
| 语义切分 | 用 Embedding 算相邻句子的距离,在语义跳变最大的地方断开(比如把所有距离里最大的 5% 当断点) | 最贴合语义,但建库时要多跑一遍模型,慢且贵 |
实际项目里用得最多的是中间两类。按 Markdown 标题切 ,每个小标题下正好是一件事。但不管用哪种,都要记住一个结论:切出来的块,是后面检索和生成的最小单位。切太大,重点会被稀释;切太碎,上下文会残缺。
然后是关键一步:把每个块交给 Embedding 模型 ,变成一串定长的数字,也就是向量 。上一期「RAG 向量检索」讲过。
真实项目里资料会不断新增、修订,这时的做法是增量入库 :只把新增或改动的那部分文档走一遍上面的流程,追加进索引即可。这也正是 RAG 最实用的地方------知识要更新,动的是知识库,模型一个参数都不用改。
向量库里,到底存了什么?
很多人以为向量库里只有一堆数字。其实它至少存了三样东西,而且分工明确:
| 存的东西 | 干什么用 |
|---|---|
| 向量索引 | 只存向量,负责算距离、找最近邻 |
| 原文 | 存每个块的文本和元数据(向量是没法还原成文字的) |
| 两者的映射 | 把"第 123 号向量"连回"第 123 块的原文" |
为什么要分开存?因为检索命中时,向量索引只会告诉你"第 123 号最像 "。要把这段内容真正交给模型,还得凭这个编号去把原文 取回来。向量负责找,原文负责还原,缺一样都不行。
还有一个绕不开的问题:几百万、上亿条向量,怎么在几十毫秒内找到最近的几个? 靠的是专门的近似最近邻(ANN)索引------它不保证百分百找到最准的那几条,而是牺牲一点点精度,换来几个数量级的速度。这也是"向量数据库"存在的意义:它不是拿传统数据库改一改,而是为"找相似"这件事从头设计的。
常见的两类数据库:
- 嵌入式:FAISS、Chroma、LanceDB。装上就能用,跑在自己的进程里,索引就是磁盘上那几个文件。原型和中小规模的首选。
- 服务化:Milvus、Qdrant、Weaviate(自建)或 Pinecone(托管)。数据量过了百万级,或者要高并发、实时更新、复杂的元数据过滤,就选这一类。
另一种存法:把资料存成一张"关系网"
上面说的资料都是一堆互相独立的小块 。这种存法对付"这个东西是什么、怎么做 "很在行,但碰上另一类问题就吃力了,比如:"A 和 B 之间有什么关联?""换成 C 行不行?"
因为这类问题的答案不在任何单独一块里 ,而藏在块与块的关系里。而向量库只懂"像不像",压根没有"关系"这个概念。
于是有了第二种知识图谱存法,它不存文本块,存两样东西:
- 节点:实体(可以是一个产品、一种材料、一个人、一个步骤);
- 边 :实体之间的关系,而且关系本身是可查询的(谁需要谁、谁属于谁、谁替代谁);

怎么把文档变成这样一张网?------现在常见的做法是让 LLM 去读文档、抽取出实体和关系 (知识抽取 ),再存进图数据库。
图数据库同样分这两类:Neo4j 要单独起服务,Kuzu 是嵌入式的、整个库就是本地一个目录。查询语言都是 Cypher ,写起来很像"画路径"------MATCH (r:Recipe)-[:REQUIRES]->(i:Ingredient)。
经过本地实测,踩到两类问题:
- 抽取会出错。LLM 会把分类判错,也会"过度抽取"------把文中一句"可选的、可以考虑加的东西"当成正式条目抽出来。
- 同名不同物、同物不同名 。文档里反复出现的同一个实体,抽出来常常变成好几个节点,不同东西又可能撞上同名。这些都得靠后续的对齐、去重、校验来收拾。
所以建图谱的成本,远高于建向量库 ------不是代码难写,而是这些"数据治理"的脏活儿绕不过去。也正因如此,实践里很常见的选择是两种存法并用:向量库管"找相似",图谱管"查关系",各干各最擅长的那件事。
一句话总结
存进去的是什么样,检索就只能捞出什么样。至于存成一堆"块"还是一张"网",取决于你要回答的是"这是什么",还是"它们之间有什么关系"。