一、如何实现「语义切分」(而不是死板按字数截断)
1. 主流实现方案
1)基于标点/段落分割(最简单实用,RAG项目首选) 先按换行、句号、分号、段落分隔把文档切成自然句子,再把连续句子拼接起来,直到接近token上限再截断。 不会把一句话从中间劈断,保证每一块语义完整。
工具:LangChain 的 RecursiveCharacterTextSplitter
分割顺序:段落→换行→句号→逗号,逐级切分,优先保证句子完整。
2)基于大模型语义分割(精度更高,成本更高) 调用LLM把整篇文档拆成逻辑章节,保证每一块是完整知识点(一条佣金规则、一条违约条款)。适合合同、规章这类结构化文档。
3)基于句向量聚类(进阶方案) 先把每句话向量化,再把语义相近的连续句子自动合并成一块,自动识别章节边界。
2. 生产落地首选方案(你这个租赁知识库直接用)
用递归字符分割器:
- 优先在段落、句号处断开;
- 块之间设置 overlap 重叠文本;
- 达到token上限才切,不会腰斩完整条款。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=400, # 单块token上限
chunk_overlap=60, # 块之间保留重叠,防止句子被切断
separators=["\n\n", "\n", "。", ";", ","] # 优先分割顺序
)
二、拆分片段的原始数据如何保存?
结论:不要把整块原文塞进payload,只存索引+文件路径是最优方案。
方案对比
方案A:payload存放完整文本块
- 缺点:向量库体积膨胀;重复占用存储空间;更新原文时很难同步清理旧向量。
- 适用:极小知识库。
方案B(推荐,生产标准做法)
payload只保存三类信息:
file_path:文档本地/对象存储路径chunk_idx:当前是这篇文档的第几块- 业务标签:分类、创建时间、房源类型等用于过滤
向量库只存:向量 + 精简元数据,不存大块原文。
完整工作流
- 文档本地/MinIO存原文;
- 文档语义分块,给每一块打上
文件路径+分片序号; - 把向量 + {path, chunk_idx, category, time} 写入Qdrant;
- 检索命中后,根据 path + chunk_idx 去原文文件读取对应片段;
- 再把真实文本送入Rerank+LLM。
好处
- 向量库轻量化,读写更快;
- 原文单独维护,修改文档后可以全量重建向量,互不干扰;
- 避免Qdrant存储大量长文本造成存储冗余。
三、补充一条面试踩坑点
如果把完整文本都塞进向量库payload: 当知识库达到几十万分片,Qdrant的索引体积会成倍变大,查询延迟上升。 行业最佳实践都是:向量库只存索引与元数据,原文独立存放,检索命中后再二次加载文本内容。
如果你需要,我可以把「文件分块→记录路径+分片号→命中后读取对应片段」写成一整套连贯代码。