Appearance
indexing相关面试题
题目1
RAG 的 indexing 阶段一般包含哪些步骤?每一步分别解决什么问题?
参考答案:
RAG 的 indexing 阶段,本质上就是把原始文档加工成后续可检索的索引数据。一般我会把它分成 6 步:
- 加载文档:先把数据拿进来,比如本地文件、数据库、CMS 或 PDF 解析结果,解决“数据从哪里来”的问题。
- 文档预处理:做清洗、去重、格式标准化,解决“原始数据能不能直接用”的问题。
- 文档切块:把长文档切成合适粒度的 chunk,解决“知识以什么单位进入向量库”的问题。
- 元数据补充:给 chunk 加上标题、来源、标签、权限这类 metadata,方便后续过滤、追踪和展示。
- 向量化:用 embedding 模型把 chunk 转成向量,解决“文本怎么变成可检索的语义表示”的问题。
- 写入索引存储:最后把向量和 metadata 写进向量库,供后续 retrieval 阶段使用。
也就是说:indexing 的核心,就是把原始文档变成结构清晰、语义可检索、可被后续检索流程直接消费的索引数据。
题目2
chunking 为什么会直接影响 RAG 的最终效果?常见的 chunking 策略有哪些?
参考答案
chunking 会直接影响 RAG 效果,核心原因是:RAG 检索回来的不是整篇文档,而是一个个 chunk。 所以 chunk 怎么切,基本就决定了模型后面能拿到什么样的上下文。
- 如果切得太大,相关信息可能被淹没,检索不够聚焦;
- 如果切得太小,上下文又容易残缺;
- 如果切得不符合语义边界,比如一句话被硬拆开,那模型拿到的内容就会不自然。
所以本质上,chunking 影响的是两件事:
- 检索粒度
- 上下文质量
常见的 chunking 策略有几种:
- 固定长度切分:最简单,适合快速落地,但容易破坏语义。
- 带 overlap 的固定长度切分:在固定长度基础上增加重叠,缓解信息被截断的问题。
- Recursive Chunking:先按标题、段落这些大结构切,不够再继续细分,比较适合结构化文档。
- Semantic Chunking:按语义边界切,更强调 chunk 的语义完整性,通常效果更好,但实现也更复杂。
因此,chunking 决定了知识进入向量库时的最小检索单位,所以它会直接影响召回质量和最终回答质量。
题目3
为什么固定长度切分往往不够?Semantic Chunking 和 Recursive Chunking 分别适合什么场景?
参考答案
固定长度切分往往不够,核心原因是它只看长度,不看语义。它不会关心一句话是不是被切到一半,也不会关心一段内容是不是属于同一个完整主题。所以它虽然简单,但很容易带来两个问题:
- chunk 语义不完整
- 检索回来的上下文不自然
尤其是技术文档、FAQ、教程这类本身有明显结构的内容,如果一律按固定长度切,其实会浪费掉原本的结构信息。
- Semantic Chunking 更强调按语义边界来切,也就是希望每个 chunk 本身就是一个相对完整的语义单元。它更适合对上下文完整性要求高的场景,比如知识文章、FAQ、概念说明这类内容。
- Recursive Chunking 更像一种工程上比较常用的折中方案。它会先按标题、段落这些大结构切,如果还太长,再继续往下细分。它比较适合 Markdown、制度文档、技术文档这类结构比较规整的内容。
如果一句话总结就是:固定长度切分适合做基础方案,Recursive Chunking 更适合大多数工程场景,Semantic Chunking 更适合追求语义完整性和检索质量的场景。
题目4
在构建索引时,metadata 为什么重要?通常会抽取哪些 metadata,它们在后续检索阶段有什么作用?
参考答案
我觉得 metadata 在 RAG 里很重要,核心原因是:向量只能表达语义相似性,但很多检索决策不能只靠语义。
比如系统还需要知道:
- 这段内容来自哪里
- 它是不是最新版本
- 当前用户有没有权限看
- 它属于哪个知识库、哪个分类
- 检索回来之后怎么展示
这些都要靠 metadata。所以可以把 metadata 理解成:给 chunk 补充结构化上下文。
它在后续检索阶段主要有三个作用:
- 做过滤:比如按知识库、语言、权限、时间范围筛选内容。
- 做展示和解释:比如告诉用户这段内容来自哪篇文档、哪个章节、标题是什么。
- 做排序和治理:比如官方文档优先、最新文档优先、某些来源降权。
通常会抽取的 metadata,大致有几类:
- 来源信息:文档 id、source 路径、URL、知识库
- 结构信息:标题、章节名、chunkIndex、sourceDocumentId
- 业务信息:标签、分类、部门、权限级别
- 时效信息:创建时间、更新时间、版本号
总结一下就是:metadata 让 chunk 不只是“一段文本”,而是一个带来源、结构和业务上下文的知识单元。 真实项目里,过滤、权限、展示、排序这些能力,基本都离不开它。
题目5
如果知识库内容会持续变化,indexing 阶段应该如何处理去重、清洗和增量更新问题?
参考答案
如果知识库内容会持续变化,那 indexing 就不能只看成一次性建库,而要看成一个持续维护的过程。这里通常要同时处理三件事:去重、清洗、增量更新。
- 去重解决的是不要把重复知识反复写进向量库,一般要考虑两个层面:
文档级去重:比如按文档 id、来源路径、内容 hash 去重
chunk 级去重:比如过滤重复页眉页脚、模板内容、重复 chunk
清洗解决的是不要把脏数据和噪音写进去,常见做法就是在 chunking 前先清洗原始文档,比如去掉空白、乱码、模板文本、OCR 噪音;chunking 后也可以再做一次轻量过滤,比如去掉空 chunk。
增量更新解决的是不要每次都全量重建索引。一般会根据更新时间、版本号、内容 hash 之类的信息判断哪些文档变了,然后分三种情况处理:
新增文档:直接新建索引
修改文档:重新切 chunk、重新 embedding,并覆盖旧索引
删除文档:把对应向量一起删掉
如果从工程上看,我会特别关注三点:
- 文档 id 要稳定
- chunk id 最好有稳定规则
- store 最好不仅支持 upsert,也支持 delete
一句话总结就是:当知识库持续变化时,indexing 本质上就不只是建索引,而是“数据治理 + 索引生命周期管理”。
题目6
如果让你设计一个 RAG SDK 的 indexing 模块,你会如何拆分核心组件?为什么这样拆?
参考答案
如果让我设计一个 RAG SDK 的 indexing 模块,我会把它拆成几层:
- Loader
- Transformer
- Chunker
- metadataBuilder
- Embedder
- VectorStore
最后再有一个 runIndexing 负责主流程编排。
这样拆的原因很简单,因为 indexing 本质上是一条数据流水线:
- 先把文档加载进来
- 做预处理
- 再切 chunk
- 补 metadata
- 做 embedding
- 最后写入向量存储
这里面每一层职责都不一样,而且都很容易变化。比如数据源会变、chunking 策略会变、embedding 模型会变、向量库也会变。所以如果不拆开,所有逻辑就会耦合在一起,后面很难扩展和替换。
我会把核心包只保留抽象和流程,把 LangChain、OpenAI、Pinecone 这类第三方能力放到 adapters 层。这样核心流程能保持稳定,外部能力也能灵活接入。
总结一下该设计,就是把 indexing 设计成“可插拔组件 + 主流程编排”的结构,因为它本质上不是一个函数,而是一条可扩展的数据处理链路。
-EOF-