文章目录零项目位置一整体功能介绍二程序入口与参数三向量数据库初始化四文档 node 构建流程五为什么 debug 模式非常重要六metadata 统计分析七观察最大和最小 node八最终索引构建九整个代码的核心思路文章索引《企业知识库RAG到底有多难理论知识部分》《企业知识库RAG到底有多难实战1原始文档处理》《企业知识库RAG到底有多难实战2数据内容分块》《企业知识库RAG到底有多难实战3向量化与存储》零项目位置我的项目Repo https://github.com/ShionWakanae/Llamarkdown。现在讲的是./src/index_cli.py。这篇比较水因为没什么技术含量。一整体功能介绍这段代码本质上是一个“文档索引构建工具”作用是把 Markdown 文档读取进来处理成适合向量检索的 node然后生成 embedding最后写入向量数据库供后续 RAG 检索使用。代码并没有和某一个具体的向量数据库强绑定而是通过 LlamaIndex 的VectorStore抽象层来接入数据库。也就是说业务层其实只需要面对VectorStoreIndex至于底层到底是ChromaQdrantMilvusPGVectorWeaviate其实都可以替换。比如这里当前使用的是:ChromaVectorStore(chroma_collectionchroma_collection)然后交给:StorageContext.from_defaults(vector_storevector_store)最后统一进入VectorStoreIndex(...)。整个索引流程就建立完成了。这种结构有个很实际的好处就是后续迁移数据库成本会低很多。开发阶段可以先使用本地 Chroma甚至只用LlamaIndex的默认存储。部署阶段再切换 Milvus 或 Qdrant而大部分业务代码其实都不用改。所以这里的重点其实不是“用了 Chroma”而是“LlamaIndex 已经帮我们把向量数据库抽象统一了。”二程序入口与参数程序入口部分比较简单两个参数文档目录调试标识--debug。调试实际上非常照耀因为真实项目里大量 RAG 问题并不是 embedding 模型的问题也不是向量数据库的问题而是最前面的数据处理阶段已经出了问题。比如markdown 结构异常node 长度失控metadata 丢失标题层级错乱某些 section 没有被正确解析文档格式特殊导致 parser 崩溃这些问题如果不提前发现数据一旦入库后面检索质量会非常差而且问题会变得很难定位。所以这里 debug 模式的设计目标其实是“先人工确认 node 正常再允许入库。”是的是的没有前面辛苦的人工就无法带来后面的智能。三向量数据库初始化这里初始化的是 Chromachromadb.PersistentClient(path./storage/chroma_db)这是一个本地持久化数据库。也就是说即使程序退出向量数据仍然会保存在./storage/chroma_db目录里。接着get_or_create_collection(docs)创建 collection可以理解成“向量表”。后面metadata{hnsw:space: cosine}表示使用 cosine 相似度。随后通过ChromaVectorStore(...)包装成 LlamaIndex 的统一 vector store 接口。再交给StorageContext.from_defaults(...)这样后续VectorStoreIndex(...)就不需要知道底层数据库细节了。这里正体现了 LlamaIndex 是一个“统一的数据接入层”。后续如果你想切换MilvusQdrantPGVector通常只需要替换vector_store xxxVectorStore(...)这一层。而 node 构建、embedding、索引流程基本都不用改。四文档 node 构建流程真正的数据处理部分在这里builder IndexBuilder()然后final_nodes builder.build_nodes(doc_path, debug_mode)会把 markdown 文档转换成 node上篇文章的内容。node 可以理解成“最终进入向量数据库的最小检索单元”每个 node 通常包含textmetadatametadata 里可能包含标题路径topicblock_type是否包含 SQL是否包含 API是否包含错误码后续检索时其实大量能力都依赖 metadata。比如过滤某类文档限制某种 block_type优先召回 API 文档按 topic 检索所以 metadata 的质量其实非常重要。这里还有一个小处理meta[merged_headers] .join(meta[merged_headers])因为有些数据库不喜欢直接存 list 类型 metadata会报错。所以这里会提前转成一级标题 二级标题 三级标题这样的字符串结构。五为什么 debug 模式非常重要因为我发现数据问题会特别多就算是自己精心编写的文档到了RAG系统里也会有结构不合理的问题。再说我一直是做运营商软件的怀疑一切是我的习惯。就算是正式文档写了我也会怀疑它不生效。同时用友商文档和中国移动标准帮助工程上的同事回怼友商也是工作的一部分。呃总之这部分其实是我自己现在经常用到的因为你根本不知道最终生成了多少 nodenode 长度是否合理metadata 是否完整有没有大量空 chunk有没有超长 chunkmarkdown 是否解析失败所以这里我专门写了Show_debug_info_and_exit(final_nodes)在 debug 模式下只分析 node。不索引不入库。最后打印日志后直接中断程序。这样做的目的就是把问题尽量提前暴露。最近才调试发现一个问题node合并跨文档了离谱……如果这些 node 一旦进入向量数据库会出现神奇的bug再去定位问题会非常麻烦。六metadata 统计分析debug 模式下首先会执行print_metadata_stats(final_nodes)这里会统计 metadata 信息。例如topicblock_typehas_sqlhas_apihas_error_code比如stats[has_sql][true] 1最后会打印总 node 数量每种 metadata 的数量占比百分比这个步骤其实也容易发现问题如果你足够了解你的文档。例如“为什么 SQL 文档数量为 0”或者“为什么 API 类型异常少”这通常意味着parser 有问题metadata enrich 失败markdown 结构异常某类 block 没识别出来这些问题如果提前发现修复成本很低。但如果已经完成 embedding 并入库再去查问题会困难很多。七观察最大和最小 node另一个我经常使用的调试方式是直接观察最长和最短 node。代码会遍历for i, node in enumerate(final_nodes):然后记录最大 node最小 node最后输出print([min node], final_nodes[node_min_index])以及print([max node], final_nodes[node_max_index])这个步骤其实非常有效。因为很多格式问题一眼就能看出来。例如最短 node 如果只有-或者通常说明 markdown 被错误切碎了。而最长 node 如果突然达到几万字符通常意味着标题层级失效section 没切开parser 出错merge 逻辑异常这些问题会直接影响 embedding 质量。因为向量模型并不擅长处理极短垃圾文本超长混乱文本所以我现在基本已经形成习惯先 debug 看 node确认node 数量合理node 长度正常metadata 正确文档结构正常之后才允许真正入库。八最终索引构建最后才是真正建立索引VectorStoreIndex(...)这里会传入final_nodesstorage_contextembed_model也就是说node - embedding - vector store 整个流程由 LlamaIndex 自动完成。这里show_progressTrue我一般都会打开。因为真实项目里文档量可能非常大。不开进度条时很容易误以为程序卡死。并且这里其实是默认用的CUDA如果没有N卡暂时只能改代码换成CPU但是会慢10多倍……九整个代码的核心思路这份代码其实没有特别复杂的算法。重点更多在于使用 LlamaIndex 解耦向量数据库在正式入库前先观察数据质量提前发现 markdown 与 metadata 问题尽量避免低质量 node 进入向量数据库因为实际 RAG 项目里很多召回质量问题本质上都来自“数据本身已经坏了”。而 debug 阶段其实是最容易发现这些问题的时候。