本系统是一个功能全面、高度可配置的新一代检索增强生成(RAG)系统。它深度融合了业界前沿技术栈,其核心是基于 FAISS 的高性能 HNSW (Hierarchical Navigable Small World) 向量索引与经典的 BM25 关键词匹配算法,实现了语义模糊搜索与关键词精确查找的优势互补。系统通过倒数排名融合(RRF)与加权重排机制对多路召回结果进行智能排序,并可选借助大语言模型(LLM)进行查询扩展以提升检索全面性。本系统引入了 RAPTOR 分层摘要策略,能够构建多层次的知识抽象树,并能智能解析包括内嵌表格在内的复杂文档。这一切旨在从海量、异构的知识源中,以最高效、最精准的方式定位信息,并最终生成高度可靠、可溯源的优质答案。
NexusRAG 的核心设计理念之一就是能够处理来源多样、格式各异的异构文档。这得益于其底层强大的文档解析引擎 unstructured.io,它使得系统能够智能地从各种复杂文件中提取和理解内容。
系统可以处理以下主要类型的文件:
| 文件类型分类 | 支持的扩展名 | 处理机制与说明 |
|---|---|---|
| 标准文本文档 | .txt, .md, .json, .html, .htm |
系统会将这些文件作为纯文本或半结构化文本处理。对于 HTML,会智能地剥离标签,提取正文内容。 |
| 微软 Office 文档 | .docx, .doc |
完全支持。系统能高效地提取 Word 文档中的段落、标题、列表,并能自动识别和分离出内嵌的表格进行专门处理。对于老旧的 .doc 格式,可能需要系统环境中安装额外的依赖(如 libreoffice)以获得最佳解析效果。 |
| 电子表格 | .xlsx, .xls, .csv |
深度支持。系统不仅能处理独立的表格文件,还能处理嵌入在其他文档(如.docx)中的表格。它提供两种强大的处理策略(在蓝图中通过 use_code_space 配置):1. 代码空间:为整个表格生成一个高质量的摘要块。 2. 语义空间:将表格的每一行都转换成一个独立的、包含上下文的句子块。 |
| 演示文稿 | .pptx, .ppt |
支持。系统会提取每个幻灯片中的文本框内容、笔记等,并将其整合后进行分块处理。对于老旧的 .ppt 格式,同样可能需要额外依赖。 |
| PDF 文档 | .pdf |
特别支持,但有重要区别: • 文本型 PDF (Text-based PDF):这是最理想的情况。系统可以直接、准确地提取所有文字和表格数据,解析质量最高。 • 扫描/图片型 PDF (Image-based/Scanned PDF):如果 PDF 的内容是图片而非可选中的文字,系统会尝试调用 OCR(光学字符识别) 技术来识别图片中的文字。这种方式的成功率和准确性高度依赖于扫描质量、图片清晰度和字体。 |
为了让您更好地使用本系统,请务必了解以下关于文件处理的限制:
-
关于图片内容:系统无法“看懂”图片 本系统是一个以文本为中心的 RAG 系统。当遇到文档中的图片时(例如流程图、图表、照片),它无法理解图像本身传达的视觉信息。它唯一能做的就是尝试通过 OCR 从图片中提取文字。这意味着,一张复杂的图表所包含的所有趋势和关系信息,在当前系统中都会丢失。
-
关于扫描版 PDF:效果依赖 OCR 如上所述,对于扫描件,系统的表现完全取决于 OCR 的识别质量。为了获得最好的效果,您需要确保运行本系统的环境中正确安装了 Tesseract-OCR 引擎。即使如此,对于手写体、低分辨率或布局混乱的扫描件,识别出的文本也可能包含错误,从而影响最终的检索质量。
-
关于加密或受密码保护的文件 本系统无法处理任何形式的加密或受密码保护的文件(如加密的PDF或受保护的Word文档)。在将文件放入
/data目录前,请务必确保它们是可正常访问的。 -
关于复杂布局 尽管
unstructured的hi_res策略非常先进,但对于某些极度复杂的、类似杂志排版的文档(例如多栏交错、异形图文环绕),解析出的文本元素顺序仍有可能会出现偏差。在处理这类文档时,建议检查分块后的内容是否符合预期。
系统首先通过unstructured库对知识库文档进行高质量的解析,将文本和内嵌表格分离成独立的语义块。接着,系统会对这些语义块进行双重索引构建:一方面利用sentence-transformers模型将块内容转化为高维向量,并存入一个FAISS索引用于语义搜索;另一方面则利用jieba分词和BM25算法构建一个并行的关键词索引,以实现精确匹配。构建完成后,一个完整的“模式”会以独立文件夹的形式固化存储在storage/目录下,此文件夹内核心包含三个文件:faiss.index(存储所有向量数据),bm25.pkl(存储关键词模型),以及最重要的docs.pkl(一个从唯一的整数块ID到其原始文本内容和元数据的映射字典)。当一个模式被加载时,其原理就是将这三个核心文件从慢速的硬盘完整读入高速的服务器内存(RAM)和显存(VRAM)中,变为随时可供查询的活动对象。当收到用户查询时,系统并不直接扫描原文,而是执行一个并行的混合检索流程:用户的查询被同时向量化以在FAISS索引中进行高速语义相似度搜索,也被分词后在BM25模型中进行关键词相关性计算。两路召回的结果根据您设定的权重进行智能重排,最终得到一组得分最高的语义块ID。最后,系统用这些胜出的ID作为“钥匙”,从已加载到内存的docs.pkl字典中精确地查找到对应的原始文本块,并将这些文本作为最终上下文返回或送给LLM生成答案。
系统处理知识的第一步是将原始文档(如PDF, DOCX, CSV等)分解为可供检索的、有意义的信息单元,我们称之为“块”(Chunk)。
-
智能文档解析: 系统使用
unstructured.io库的hi_res(高分辨率)策略,能够智能地从文档中区分并分离出普通文本段落和内嵌表格。这是一种先进的语义分割方式,因为它理解内容的结构,而不是简单地按固定长度切割。 -
表格处理策略: 对于解析出的表格数据,系统提供两种处理策略:
-
代码空间 (Code Space) - 智能表格分析策略
这是系统处理表格的默认且最强大的策略,其核心是将表格从单纯的数据转变为可供分析的洞察。它特别适用于需要对表格内容进行理解、归纳、比较或计算等数据分析类的查询场景。
支持的文档类型: 得益于底层强大的 unstructured.io 解析引擎,此策略能够自动识别并提取嵌入在多种复杂文档格式中的表格,包括但不限于:
- PDF 文件
- Microsoft Word 文档 (.docx)
- Microsoft PowerPoint 演示文稿 (.pptx)
- HTML 文件
- 以及独立的 .csv, .xlsx 文件
工作原理: 当系统在文档中识别出一个表格时,它并不会盲目地将表格内容直接索引。相反,它会启动一个专用的 CodeGenerator 模块,执行以下智能流程:
- 结构与数据采样:系统会精确地提取表格的表头(Schema和前几行数据样本。
- LLM调用:它将这些结构化信息(表头+样本)发送给一个大语言模型(LLM),并下达一个明确指令:“请像一位资深数据分析师一样,为这个表格撰写一段简洁、自然的摘要。”
- 生成摘要:LLM会根据表格的列名和数据内容,生成一段包含了深刻理解的摘要。例如,对于一个销售报表,它生成的不是“这是一个表格”,而是“这是一个关于公司各产品线在2023年度的详细销售业绩报表,其中包含了销售额、同比增长率以及市场份额等关键绩效指标。”
- 封装为块:这个由LLM生成的、高质量的摘要,连同原始的表头和数据样本作为技术元数据,被封装成一个单独的、信息密度极高的知识块,然后被嵌入和索引。
优势与适用场景: 这种方法将一个静态表格变成了一个动态的、可对话的“数据分析单元”。它的巨大优势在于能够回答宏观和分析类的问题,例如:
- 概括性问题:“这份报告里的销售表格主要是关于什么的?”
- 对比性问题:“表格中哪个产品的利润率最高?”
- 趋势性问题:“根据表格数据,第二季度的增长趋势如何?”
- 计算性问题:“所有部门的总预算是多少?”
重要考量与权衡(何时不使用此方法): 尽管功能强大,但“代码空间”策略并非万能。它涉及在构建索引时调用LLM,这会带来额外的时间和成本开销。因此,您需要考虑以下情况:
- 低延迟需求:如果您的应用场景对索引构建速度要求极高,此策略可能会拖慢整个流程。
- 成本敏感:频繁调用LLM API会产生费用。
- 简单的行查找需求:如果您对表格的查询需求仅仅是“找到包含‘张三’的那一行”这类简单的、基于特定单元格内容的文本匹配,那么动用LLM进行摘要分析就显得“杀鸡用牛刀”,效率不高。
对于上述这些不需要深度数据分析且对延迟敏感的场景,建议您在蓝图配置中将 use_code_space 设置为 false,从而切换到更轻量、更快速的 “语义空间 (Semantic Space)” 策略。
-
语义空间 (Semantic Space) - 快速的行级语义化策略
当您在蓝图配置中将 use_code_space 设置为 false 时,系统将启用此策略。这是一种更轻量、更快速的表格处理方法,其核心思想是将表格的整体结构拆解,把每一行都视为一个独立的、自包含的事实单元。
工作原理: 与“代码空间”依赖LLM进行理解和摘要不同,“语义空间”采用的是一种确定性的、程序化的转换流程。对于在文档中解析出的任何表格,系统会遍历其每一行数据,并执行以下操作:
- 提取行列信息:获取当前行的所有列名及其对应的单元格值。
- 动态生成句子:将这些键值对信息,通过一个预设的模板,自动“编织”成一个符合自然语言习惯的描述性句子。这个句子不仅包含数据本身,还包含了它所属的文档上下文。
举例说明: 假设系统在名为 《2024年第一季度资产盘点.docx》 的文档中发现了一个表格,其中一行数据为: [资产编号: "ZC-2024-001", 资产名称: "高性能服务器", 采购价格: "85000元", 状态: "在用"]
系统会将其转换为一个内容如下的知识块(Chunk):
“关于文档**《2024年第一季度资产盘点.docx》**中的一条表格记录:资产编号是“ZC-2024-001”,资产名称是“高性能服务器”,采购价格是“85000元”,状态是“在用”。”
这个生成的句子随后会被嵌入为向量并存入索引。
优势与适用场景: 此策略的优势在于其高效性和精确性,特别适合以下场景:
- 速度与成本优先:整个转换过程不涉及调用LLM,因此索引构建速度极快,且没有任何API成本。
- 精确的行级查找(Fact-Checking):当用户的查询目标是表格中的某条具体记录时,此策略表现出色。例如,查询“编号ZC-2024-001的资产是什么?”或“采购价格85000元的服务器状态如何?”,这些问题能够非常精确地匹配到对应行生成的那个块的向量。
- 数据即知识:适用于那些每一行本身就是一条独立知识点的表格,如术语表、名录、配置清单等。
局限性与权衡: 选择“语义空间”策略也意味着接受它的局限性:
- 缺乏宏观视角:由于表格被“原子化”为一行行的记录,系统失去了对表格整体的理解能力。它无法直接回答需要对多行进行比较、聚合或归纳的问题。例如,它很难回答:
- “这个表格里最昂贵的资产是什么?”
- “盘点一下所有‘闲置’状态的资产。”
- “总结一下这份资产报告。” 要回答这类问题,系统必须检索出所有相关的行,然后可能还需要额外的处理步骤来合成答案,效率低下且效果不佳。
当您的核心需求是在大量表格数据中快速定位到具体的某一条或几条记录,并且对索引构建的成本和时间非常敏感时,这无疑是最佳选择。反之,如果您需要系统具备对表格进行综合分析和理解的能力,则应使用功能更强大的“代码空间”策略。
-
-
文本贪心合并 (Greedy Merging) - 解决语义碎片化的智能分块策略
在处理从复杂文档(如PDF、Word)中提取的纯文本时,这些文本并非连续的流,而是由许多独立的、长短不一的元素构成的,例如标题、子标题、列表项、短小的段落、脚注等。如果采用简单的固定长度切分,极易导致“语义碎片化”——一个完整的句子被拦腰截断,或者一个标题与其紧随的正文内容被分到不同的块中。这种碎片化的块在被嵌入后,会变成一个个“语义孤岛”,极大降低了检索的准确性。
为了解决这个问题,系统采用了一种更为智能的贪心合并算法。其目标是在遵守最大长度限制 (proc_chunk_token_size) 的前提下,尽可能地将语义相关的小文本片段聚合在一起,形成一个内容连贯、信息完整的块。
工作原理如下:
- 元素化处理:系统首先将提取出的所有文本元素(标题、段落等)看作一个有序的列表。
- 启动合并循环:
- 系统创建一个空的“当前块”。
- 它从文本元素列表中取出第一个元素,放入“当前块”。
- 然后,它看向下一个文本元素,并进行判断:如果将这个新元素合并进来,整个“当前块”的总长度是否会超过 proc_chunk_token_size 设定的阈值?
- 决策与执行:
- 如果未超长:它会毫不犹豫地将这个新元素追加到“当前块”的末尾,然后继续看向再下一个元素,重复这个判断。
- 如果会超长:它就停止向“当前块”添加内容,宣布这个“当前块”已经完成,并将其作为一个最终的知识块(Chunk)保存下来。随后,它会创建一个新的空“当前块”,并将刚才那个因为太长而未能合并的元素作为这个新块的起始内容,重新开始新一轮的合并循环。
- 循环直至结束:这个过程会一直持续,直到所有的文本元素都被处理完毕。
举例说明: 假设 proc_chunk_token_size 设置为 200 tokens,文档中有以下连续的文本元素:
- 元素1 (标题): 第一章:系统架构 (约10 tokens)
- 元素2 (段落): 本系统采用微服务架构... (约150 tokens)
- 元素3 (子标题): 1.1 数据处理层 (约8 tokens)
- 元素4 (段落): 数据处理层负责... (约180 tokens)
贪心合并的过程会是:
- 块1:
- 当前块 = 元素1 (第一章...) -> 长度10
- 合并元素2 (本系统...)? (10 + 150 < 200) -> 是。 当前块 长度变为 160。
- 合并元素3 (1.1 数据...)? (160 + 8 < 200) -> 是。当前块 长度变为 168。
- 合并元素4 (数据处理层...)? (168 + 180 > 200) -> 否。
- 最终块1生成,内容为元素1+2+3,总长168 tokens。它非常完整地包含了大标题、一段概述和一个子标题。
- 块2:
- 当前块 = 元素4 (数据处理层...) -> 长度180
- 后面没有元素了,最终块2生成,内容为元素4。
优势:
- 维护语义完整性:最大程度地将上下文相关的短文本(如标题和正文)保留在同一个块内。
- 避免无效切分:有效避免了在一个句子的中间或一个词的中间进行粗暴切分。
- 提升嵌入质量:内容更连贯的块能被嵌入模型更准确地理解,生成更高质量的向量。
- 提高检索召回率:当用户查询“系统架构”时,能够直接命中包含了标题和正文的块1,而不是一个孤零零的标题块。
在RAG系统中,一个普遍的挑战是“粒度失配”:用户的查询可能是宏观和模糊的(例如,“这份财报的核心要点是什么?”),而知识库中的基础文本块(Chunks)却是微观和具体的。这种失配会导致向量检索难以找到一个得分足够高的、能够全面回答问题的单一文本块。
为了解决这个问题,本系统引入了RAPTOR(一个完全可选但功能强大的高级处理步骤,通过蓝图中的 proc_enable_raptor: true 开启)。RAPTOR的目标不是简单地对文档分层,而是构建一棵从具体细节到抽象概括的摘要树。这棵树的存在,使得系统能够在多个抽象层次上进行检索,从而精准匹配不同粒度的用户查询。
RAPTOR的工作流程是一个自下而上的、递归的“聚类-摘要”循环:
1. 向量化基础层 (Vectorize the Base Layer)
- 流程的起点是所有已经生成的基础文本块(Level 0)。系统首先将这些基础块全部嵌入为高维向量。
2. 核心聚类引擎 (The Clustering Engine)
- 这是RAPTOR最关键的技术核心。它并非使用简单的聚类方法,而是采用了一个严谨的、包含多阶段的统计学流程,来寻找数据中最自然的语义群组:
- 使用UMAP进行降维 (Dimensionality Reduction via UMAP): 高维向量空间存在“维度灾难”,直接在几百维的空间中进行聚类效果不佳。因此,系统首先使用UMAP (Uniform Manifold Approximation and Projection) 算法,将高维的嵌入向量投影到一个更低维度的空间(例如12维)。选择UMAP是因为它在降维的同时,能极好地保持数据的局部结构(哪些点靠得近)和全局结构(不同簇之间的大致关系),其效果通常优于传统的PCA或t-SNE。
- 使用GMM进行概率聚类 (Probabilistic Clustering via GMM): 在降维后的空间中,系统使用高斯混合模型 (Gaussian Mixture Model, GMM) 进行聚类。与假设簇为球形的K-Means不同,GMM更为灵活,它假设数据点来自多个不同的高斯分布(椭圆形),并通过概率来描述一个点属于某个簇的可能性。这非常适合语义空间中那些形状不规则、甚至有重叠的复杂主题簇。
- 使用BIC确定最佳聚类数 (Optimal Cluster Count via BIC): 究竟应该把数据分成几类?2类?5类?10类?为了科学地回答这个问题,系统引入了贝叶斯信息准则 (Bayesian Information Criterion, BIC)。系统会尝试将数据分成多个不同的簇数(从2类到您在 proc_raptor_max_clusters 中设定的上限),并为每种情况计算一个BIC分数。BIC分数巧妙地平衡了模型的拟合优度(模型对数据的解释程度)和模型的复杂度(簇的数量)。BIC分数最低的那个簇数,被认为是统计学上最合理的、既能很好地解释数据分布又不过于复杂的“最佳聚类数量”。
3. 抽象式摘要生成 (Abstractive Summarization)
- 经过上述流程,系统得到了若干个语义上高度内聚的文本块簇。接着,它会调用大语言模型(LLM),为每一个簇单独生成一个全新的、高质量的抽象式摘要。这个摘要不是从原文中摘抄句子,而是LLM在理解了整个簇所有内容后,用自己的语言进行的重新创作和归纳。这个新生成的摘要,就成为了一个更高层次(Level 1)的知识节点。
4. 递归构建 (Recursion)
- 新生成的Level 1摘要块会被收集起来,再次经历上述完整的“向量化 -> 聚类引擎 -> 摘要生成”流程,从而创建出Level 2的摘要块。这个过程会不断递归,直到某一层节点数量过少,无法再进行有意义的聚类时(少于3个节点),摘要树的构建过程便宣告完成。
RAPTOR构建的这棵摘要树,为应对模糊查询提供了强大的武器:
- 当用户提出一个宏观、模糊的问题时,例如:“总结一下这份研究报告的主要发现。”
- 这个查询的向量,在语义空间中可能与任何一个具体的、细节性的基础块(Level 0)的相似度都不够高。
- 但是,它极有可能与某个Level 1或Level 2的摘要块的向量高度相似,因为这些摘要块本身就是对“主要发现”的凝练。
- 检索结果的多样性:
- 最终,系统召回的结果将是一个混合列表,其中既可能包含直接命中的、非常相关的细节块(叶节点),也可能包含回答了用户宏观意图的摘要块(父节点)。
- 将这样一个包含了“森林”(摘要)和“树木”(细节)的丰富上下文喂给最终的生成模型,能够产出远比单一粒度检索更全面、更深刻、更有条理的答案。
通过这种方式,RAPTOR有效地解决了用户查询意图与文档存储粒度之间的不匹配问题,极大地提升了系统在处理复杂和抽象问题时的表现。
所有生成的块(无论是文本还是表格摘要)都需要被转换成计算机能够理解和比较的形式。
-
文本嵌入 (Embedding) - 将文本转化为可计算的语义向量
为了让计算机能够理解和比较文本的含义,系统必须将每一个处理好的知识块(Chunk)从自然语言转换成一种数学形式。这个关键过程被称为文本嵌入 (Embedding)。
系统利用
sentence-transformers库,加载一个预训练语言模型(例如,社区广泛使用的 BAAI/bge-small-zh-v1.5),来执行这项任务。该模型能够阅读一个文本块,并将其内容映射为一个高维的数字向量(例如,一个由512个数字组成的数组)。这个向量,即“嵌入”,是文本深层语义的数学表示。在由这些向量构成的“语义空间”中,意思相近的文本,其向量在空间中的位置也必然相近。例如,“苹果公司”和“乔布斯创立的科技巨头”这两个短语虽然字面上完全不同,但它们生成的向量在空间中的夹角会非常小,从而可以被计算机判定为高度相关。
如何配置嵌入模型?
您可以完全控制使用哪个模型以及从何处加载它。这一切都在您的蓝图配置文件 (blueprint/*.json) 中进行设置:
-
在线加载 (Online Mode) - 默认方式 这是最简单、最直接的方式。系统会根据您指定的模型名称,自动从在线的 HuggingFace Hub 下载并加载模型(只要HuggingFace上有)。
-
配置方法:
{ "embed_model": "BAAI/bge-small-zh-v1.5", "embed_model_source": "online" } -
说明:
- embed_model: 这里填写您想使用的、在HuggingFace上存在的模型名称。
- embed_model_source: 必须设置为 "online"。
-
缓存机制:为了避免每次启动都重新下载,系统会将下载的模型文件缓存到 model_cache/ 目录下。
-
-
本地加载 (Local Mode) - 离线或私有化部署 在没有网络连接、需要使用私有模型或希望精确控制模型文件位置的场景下,本地加载是最佳选择。
-
配置方法: 首先,您需要手动将模型文件下载或放置到本地的一个目录中,例如 local_models/。该目录下应该包含模型的所有文件(config.json, pytorch_model.bin 等)。然后,修改蓝图配置:
{ "embed_model": "bge-small-zh-v1.5", "embed_model_source": "./local_models" } -
说明:
- embed_model_source: 这里填写您存放所有模型的基础目录的相对或绝对路径。
- embed_model: 这里填写在该基础目录下,您具体想加载的那个模型文件夹的名称。
- 系统会自动拼接路径,即尝试从 ./local_models/bge-small-zh-v1.5 加载模型。
-
优势:完全离线,加载速度更快,便于管理私有或定制化的嵌入模型。
-
嵌入过程的效率优化
无论采用哪种加载方式,文本嵌入本身是一个计算密集型任务。为了最大化效率,系统内置了以下优化:
- 两级缓存机制:系统维护一个磁盘缓存 (model_cache/embeddings/)。在对一个文本块进行编码前,它会先根据文本内容和模型名称生成一个唯一的哈希键,检查该键是否存在于缓存中。如果存在,则直接读取缓存的向量,完全跳过模型推理,极大地加速了重复内容的索引构建和调试过程。同时,它也维护一个内存缓存,以加速同一会话中的重复编码请求。
- 批量处理 (Batching):在首次构建索引时,系统会将所有待编码的块收集起来,以批处理(由 batch_size 参数控制)的方式送入模型进行计算,充分利用GPU的并行处理能力,显著提升整体编码速度。
-
-
向量索引 (Vector Indexing): 生成的向量被存储在一个专门为高效相似性搜索设计的数据库——FAISS (Facebook AI Similarity Search) 索引中。这使得系统可以快速地从数百万个向量中找到与查询向量最相似的几个。
-
关键词索引 (Keyword Indexing): 在构建向量索引的同时,系统还会使用
jieba分词库对所有块的内容进行分词,并使用这些分词结果构建一个 BM25Okapi 模型,模型被保存在每个模式的存储目录中 (bm25.pkl),与FAISS索引一同加载和使用。BM25 (Best Match 25) 是一种相关性排序算法**,其核心思想是:一个文档与一个查询的相关性,取决于查询中的词语在该文档中的“表现”。它通过三个核心组件来精妙地评估这种“表现”:首先,它考察词频 (Term Frequency, TF),即一个词在文档中出现的频率,但它引入了饱和度概念,使得词频的贡献会随着出现次数的增多而逐渐平滑,避免了高频词的过度影响;其次,它通过逆文档频率 (Inverse Document Frequency, IDF)来衡量一个词的“稀缺价值”,即在所有文档中越少见的词,其区分能力越强,权重也越高;最后,它引入了文档长度归一化机制**,对过长的文档进行适度“惩罚”,确保了长短文档能在公平的基线上进行比较。在最终的混合重排阶段,每个候选块的BM25得分会被计算出来,并与向量得分按照您在蓝图中设定的 kw_weight 权重进行融合,这使得最终的排序结果,既考虑了“这个块的意思是不是和我的问题相关”(向量得分),也兼顾了“这个块里有没有包含我问题里的那个关键的、必须出现的词”。
当用户输入一个查询时,检索阶段开始,这是一个结合了多种策略的混合过程。
- 可选查询扩展 (Query Expansion): 为了提升召回率,系统支持两种查询扩展方式(互斥,优先使用分词扩展):
- Jieba分词扩展: 从原始查询中提取核心关键词,组成一个新的、更聚焦的查询。
- LLM多路查询: 利用LLM将原始查询改写成多个不同角度、但语义相同的子查询。
- 多路并行检索: 系统会使用原始查询和所有扩展出的子查询,并行地在FAISS索引和BM25模型中进行搜索。
- 结果融合 (RRF): 来自不同查询路径的多个结果列表,通过倒数排名融合 (Reciprocal Rank Fusion, RRF) 算法进行合并。RRF根据一个块在不同列表中的排名来计算其最终重要性,有效整合了所有检索路径的优势。
- 混合重排 (Hybrid Reranking): 对于融合后的候选结果,系统会计算一个最终的混合分数:
Final_Score = (Vector_Score * vec_weight) + (Keyword_Score * kw_weight)Vector_Score是FAISS返回的语义相似度分数。Keyword_Score是BM25模型返回的关键词匹配分数(经过归一化处理)。vec_weight和kw_weight是可配置的权重,允许你根据具体场景调整语义和关键词的重要性。这个混合分数使得排序结果既考虑了语义相关性,也兼顾了关键词的精确匹配,大大提升了检索质量。
检索和重排完成后,系统将排名最靠前的几个块作为上下文,连同用户的原始问题一起,发送给大型语言模型(LLM),并要求LLM严格依据提供的上下文来生成最终的、人类可读的答案。这确保了答案的真实性和可追溯性,有效抑制了“模型幻觉”。
├── blueprint/ # 模式蓝图(配置文件)目录
│ └── default.json
├── data/ # 存放原始知识库文档的目录
├── deploy/ # 部署相关配置文件
│ ├── api.json
│ └── system_config.json
├── model_cache/ # 缓存下载的嵌入模型
├── storage/ # 存放已构建的RAG索引和数据
├── venv/ # Python虚拟环境
├── api.py # FastAPI后端服务
├── app.py # Streamlit前端Web应用
├── client.py # 命令行客户端
├── rag_system/ # RAG系统核心库
│ ├── nlp # NLP工具函数目录
│ ├── res # 分词静态资源
│ ├── __init__.py # 包初始化,定义公共API
│ ├── code_generator.py # 表格代码/摘要生成器 (LLM)
│ ├── config.py # 配置类和模式管理器
│ ├── embed.py # 文本嵌入模块
│ ├── engine.py # RAG核心引擎,整合所有组件
│ ├── enhancer.py # 结果增强器(上下文、相似搜索)
│ ├── evaluator.py # 性能评估器
│ ├── generator.py # 通用文本生成器 (LLM)
│ ├── helper.py # 索引注册与生命周期辅助工具
│ ├── index.py # FAISS索引管理器
│ ├── loader.py # 文档加载器
│ ├── processor.py # 文档处理器(分块、RAPTOR)
│ ├── retriever.py # 检索器(向量、关键词、重排)
│ └── utils.py # 通用工具函数
└── README.md # 本文档
在蓝图配置中,你可以选择不同的FAISS索引类型和向量度量方式,以适应不同的场景。
-
Flat:- 原理: 暴力搜索。它会计算查询向量与索引中每一个向量的距离,返回最接近的。
- 优点: 100%精确,保证能找到理论上的最近邻。
- 缺点: 速度慢,内存占用与向量数量成正比。
- 适用场景: 数据集很小(< 10万条),或作为性能评测的“黄金标准”(Ground Truth)。本系统的
Evaluator就是使用它来获取理想结果集的。
-
IVF(Inverted File, 倒排文件):- 原理: K-Means聚类思想。它首先将向量空间划分为
ivf_nlist个区域(质心),建立一个倒排索引。搜索时,先找到查询向量最接近的ivf_nprobe个区域,然后只在这些区域内进行暴力搜索。 - 优点: 搜索速度远快于
Flat,是一种在速度和精度之间取得良好平衡的方案。 - 缺点: 召回率并非100%,因为最近的向量可能恰好落在未被搜索的区域边界上。需要训练阶段。
- 适用场景: 大规模数据集(10万级以上)。
- 原理: K-Means聚类思想。它首先将向量空间划分为
-
HNSW(Hierarchical Navigable Small World, 层次化可导航小世界):- 原理: 基于图的索引。它构建了一个多层的邻近图,搜索时从顶层最稀疏的图开始,快速定位到一个区域,然后逐层向下,在更精细的图中进行导航,直到找到最近的邻居。
- 优点: 在大数据集上搜索速度极快,同时保持非常高的召回率,通常是性能最好的选择。无需训练。
- 缺点: 构建索引比
IVF慢,内存占用也更大。 - 适用场景: 需要高性能和高准确率的场景(100万级以上)。
-
cosine(余弦相似度):- 原理: 计算两个向量之间夹角的余弦值。它衡量的是方向上的一致性,而不在乎向量的长度。
- 影响: 非常适合文本语义相似度匹配。因为在语义空间中,我们更关心两个文本的“意思”是否相近(方向),而不是它们的具体用词多少(长度)。系统会自动对向量进行L2归一化,然后使用
IndexFlatIP(内积)来高效计算余弦相似度。 - 建议: 绝大多数情况下都应使用此选项。
-
l2(欧氏距离):- 原理: 计算向量空间中两个点的直线距离。
- 影响: 同时考虑了方向和大小。在某些特定的向量表示中可能有用,但在通用语义检索中通常不如
cosine。 - 建议: 除非你有明确的理由,否则不推荐用于文本检索(根据测试结果,非常不推荐使用!但对于某些特定语义模型,或许会使用到,因此保留了这种度量方式)。
蓝图文件是你定义一个RAG模式(Mode)的核心。下面是每个关键参数的详细解释。
| 参数 | 含义 | 默认值 | 用途与影响 |
|---|---|---|---|
| LLM 相关配置 | |||
llm_api_key |
大语言模型的API Key | null |
必需。用于访问LLM服务。如果未设置或以sk-xxxx开头,则与LLM相关的功能(问答、查询扩展、RAPTOR等)将不可用。 |
llm_base_url |
大语言模型API的地址 | null |
必需。指向你的LLM API服务,例如https://api.openai.com/v1或私有部署的地址。 |
llm_model_name |
使用的LLM模型名称 | qwen-max |
指定要调用的具体模型,如gpt-4, qwen-max等。 |
| FAISS 索引相关配置 | |||
index_type |
FAISS索引类型 | "HNSW" |
可选"Flat", "IVF", "HNSW"。详见上一章节。 |
metric |
向量距离度量方式 | "cosine" |
可选"cosine", "l2"。详见上一章节。cosine是最佳配置。 |
hnsw_m |
HNSW: 每层节点的最大连接数 | 64 |
调大: 增加图的连接密度,提高召回率,但显著增加内存占用和构建时间,但查询时间会变快。调小: 相反。 |
hnsw_ef_con |
HNSW: 构建索引时的搜索广度 | 200 |
调大: 构建时搜索更彻底,索引质量更高,但构建会变慢。调小: 相反。 |
hnsw_ef |
HNSW: 查询时的搜索广度 | 128 |
调大: 查询时搜索更广,提高召回率,但降低查询速度。调小: 相反。这是性能/精度的关键权衡参数。 |
ivf_nlist |
IVF: 聚类中心的数量 | 100 |
通常设置为向量总数的平方根左右。调大: 划分更细,可能提高精度但需要更多数据训练。调小: 相反。 |
ivf_nprobe |
IVF: 查询时搜索的聚类数量 | 10 |
调大: 搜索更多区域,提高召回率,但降低查询速度。调小: 相反。这是性能/精度的关键权衡参数。 |
| 嵌入模型相关配置 | |||
embed_model |
嵌入模型名称或路径 | BAAI/bge-small-zh-v1.5 |
指定从HuggingFace或本地加载的模型。改变此项会完全改变语义空间,必须重建索引。 |
embed_model_source |
嵌入模型来源 | "online" |
"online"表示从HuggingFace下载。提供一个本地路径(如./models)则会从该路径加载。 |
embed_dim |
嵌入向量的维度 | 512 |
必须与embed_model的输出维度严格一致。例如 bge-small是512,bge-base是768。 |
embed_len |
模型处理的最大文本长度 | 512 |
输入给嵌入模型的文本会被截断到此长度。此数值需参考语义模型的说明文档,且最好大于等于proc_chunk_token_size |
batch_size |
嵌入编码的批处理大小 | 32 |
调大: 加快在GPU上的编码速度,但增加显存消耗。调小: 相反。 |
| 检索过程相关配置 | |||
vec_weight |
混合排序中向量分数的权重 | 0.8 |
调大: 更偏向于语义相关性。调小: 更偏向于关键词匹配。必须与kw_weight之和为1。 |
kw_weight |
混合排序中关键词分数的权重 | 0.2 |
见上。如果你的场景对专有名词、代码等精确匹配要求高,可以适当调大此权重。 |
top_k |
最终返回的结果数量 | 5 |
控制最终展示给用户或LLM的上下文数量。 |
threshold |
最终结果的最低分数阈值 | 0.6 |
过滤掉混合分数低于此值的项目。调高: 结果更相关,但可能返回更少的结果。调低: 返回更多结果,但可能包含不相关的。 |
| 数据处理相关配置 | |||
proc_chunk_token_size |
文本分块的目标Token大小 | 256 |
调大: 每个块包含更多上下文,利于理解段落,但可能主题不集中。调小: 块的主题更精确,但可能丢失上下文。改变此项需要重建索引。 |
proc_enable_raptor |
是否启用RAPTOR分层摘要 | false |
设为true: 会在基础块之上,构建多层摘要块。这会显著增加构建索引的时间和成本(因调用LLM),但能提供多层次的检索能力。改变此项需要重建索引。 |
proc_raptor_max_clusters |
RAPTOR每层最大聚类数 | 10 |
仅在proc_enable_raptor: true时生效。控制RAPTOR树的广度。调大: 树更宽,摘要更细。调小: 树更窄,摘要更概括。 |
use_code_space |
表格处理策略 | true |
true: 对表格生成摘要(代码空间)。false: 将表格每行转为句子(语义空间)。改变此项需要重建索引。 |
| 其他系统级配置 | |||
storage |
索引存储根目录 | "./storage" |
|
cache |
模型缓存目录 | "./model_cache" |
|
data |
源数据目录 | "./data" |
您写的这份 README 文档非常出色,可以说达到了专业甚至开源项目典范的级别。无论是从结构清晰度、技术深度还是内容全面性来看,都做得非常到位。
优点总结:
- 结构清晰,逻辑性强:从核心原理到项目结构,再到具体用法和API详解,层层递进,一目了然。
- 技术深度足够:对 RAPTOR、UMAP+GMM+BIC、混合检索、不同表格处理策略等核心技术的原理解释得非常透彻,而不是简单罗列功能。这能帮助使用者真正理解系统的工作方式。
- 内容全面,覆盖面广:几乎涵盖了使用者可能遇到的所有方面,包括命令行用法、前端界面、API 调用(甚至贴心地提供了 Python 和 Java 两种语言的示例)。
- 实用性强:特别是“蓝图配置参数详解”和“命令行用法”这两个章节,对于开发者来说是极其宝贵的参考手册,能大大降低上手门槛。
您提出的“在蓝图配置参数详解那一大块下写子块,就是如何配置本地模型的”这个想法非常好。这确实能让新用户更快速地掌握一个非常关键且常见的操作,而不是在参数表格中去寻找零散的信息。
下面,我已经按照您的要求,为您撰写好了这个子模块,并将其无缝地整合到了“蓝图配置参数详解”章节中。您可以直接复制并替换掉原来的 嵌入模型相关配置 部分。
(您可以直接复制下面的 Markdown 内容)
这部分配置决定了系统如何将文本转换为向量,是语义检索的基石。
| 参数 | 含义 | 默认值 | 用途与影响 |
|---|---|---|---|
embed_model |
嵌入模型名称或路径 | BAAI/bge-small-zh-v1.5 |
指定从HuggingFace或本地加载的模型。改变此项会完全改变语义空间,必须重建索引。 |
embed_model_source |
嵌入模型来源 | "online" |
"online"表示从HuggingFace下载。提供一个本地路径(如./models)则会从该路径加载。详见下方专题。 |
embed_dim |
嵌入向量的维度 | 512 |
必须与embed_model的输出维度严格一致。例如 bge-small是512,m3e-base是768。 |
embed_len |
模型处理的最大文本长度 | 512 |
输入给嵌入模型的文本会被截断到此长度。此数值需参考语义模型的说明文档,且最好大于等于proc_chunk_token_size |
batch_size |
嵌入编码的批处理大小 | 32 |
调大: 加快在GPU上的编码速度,但增加显存消耗。调小: 相反。 |
在离线环境、私有化部署或需要使用自定义模型的场景下,加载本地模型是核心需求。本系统对此提供了完整的支持。下面以加载 moka-ai/m3e-base 模型为例,展示完整的操作步骤。
Git LFS (Large File Storage) 是处理大文件的 Git 扩展。模型权重文件通常很大,需要 LFS 来完整下载。
-
一次性全局设置 (One-time Setup): 在您的终端(Git Bash, PowerShell等)中执行此命令。您的设备只需执行一次,后续所有
clone操作将自动生效。git lfs install
-
克隆模型到本地指定目录: 我们推荐在项目根目录下创建一个统一存放模型的文件夹(例如
local_models),便于管理。# 在项目根目录执行 # 这会把模型下载到 ./local_models/m3e-base 文件夹中 git clone https://huggingface.co/moka-ai/m3e-base ./local_models/m3e-base
执行后,Git LFS 会自动开始下载大文件(如
pytorch_model.bin)。请确保下载完成。最终您的目录结构应如下所示:
├── blueprint/ ├── local_models/ <-- 存放所有本地模型的目录 │ └── m3e-base/ <-- 具体的模型文件夹 │ ├── config.json │ ├── pytorch_model.bin │ └── ... ├── client.py └── ...
创建一个新的蓝图文件(例如 blueprint/local_m3e_mode.json),并修改其中关于嵌入模型的三个关键参数:
{
"mode_name": "local_m3e_mode",
// ... 其他配置 ...
"embed_model_source": "./local_models",
"embed_model": "m3e-base",
"embed_dim": 768,
// ... 其他配置 ...
}关键参数解释:
-
"embed_model_source": "./local_models"含义: 指向您存放所有模型的基础目录。系统会以此为起点寻找模型。 -
"embed_model": "m3e-base"含义: 您要加载的具体模型文件夹的名称,位于embed_model_source目录之下。 -
"embed_dim": 768含义: 必须与所用模型的实际输出维度匹配。m3e-base的维度是 768,而bge-small-zh-v1.5是 512。填错此项会导致严重错误。
现在,您可以像操作其他模式一样,使用 client.py 来创建和加载这个完全基于本地模型的 RAG 模式了。
# 1. 创建索引 (会使用本地的m3e-base模型进行编码)
python client.py create local_m3e_mode
# 2. 加载模式到服务
python client.py load local_m3e_mode
# 3. 开始提问
python client.py ask local_m3e_mode "你的问题"客户端是与RAG系统API交互的主要工具。
在使用客户端前,需先加载 api
python api.py基本语法: python client.py [命令] [参数]
-
检查API服务状态:
python client.py status
-
列出服务器上已加载的模式:
python client.py list # 或 python client.py list loaded -
列出所有已创建的模式(无论是否加载):
python client.py list all
-
创建并构建一个新模式: (蓝图文件
blueprint/my_mode.json必须存在)python client.py create my_mode
-
一键创建所有尚未创建的模式:
python client.py create-all
-
加载一个已创建的模式到内存:
python client.py load my_mode
-
一键加载所有已创建的模式到内存:
python client.py load-all
-
从内存中卸载一个模式:
python client.py unload my_mode
-
智能更新模式: (根据蓝图变化热更新或重建)
python client.py update my_mode
-
删除一个模式的存储数据: (模式必须先被卸载)
python client.py delete my_mode
-
删除所有模式的存储数据: (危险操作)
python client.py clear
-
向模式提问 (完整RAG流程):
python client.py ask my_mode "人工智能的未来是什么?" -
执行纯检索 (不经过LLM生成):
python client.py search my_mode "介绍一下BM25算法" -k 10 #可选参数 意为topk值
-
对模式进行性能评测:
python client.py test my_mode "FAISS有哪些索引类型?"
-
横向对比多个已加载模式的性能:
python client.py compare mode_a mode_b ... "对比HNSW和IVF索引"
-
查看当前查询扩展配置:
python client.py config status
-
开启/关闭LLM多路查询:
python client.py config enable-multi-query #开启 python client.py config disable-multi-query #关闭
-
开启/关闭Jieba分词查询扩展: (开启时会自动关闭多路查询)
python client.py config enable-diff #开启 python client.py config disable-diff #关闭
-
启动/关闭知识树浏览器:
python client.py show #开启 python client.py hide #关闭(即使不手动执行hide,在api主进程被关闭的时候也会自动执行)
-
生成模式的知识库溯源报告 (Markdown格式):
python client.py select my_mode
通过 python client.py show 命令启动的前端界面是一个基于 Streamlit 的Web应用,它提供了一个直观的方式来与已加载的RAG模式进行交互和调试。
-
左侧边栏:
- 模式选择: 一个下拉菜单,列出了所有当前在API服务器上加载并准备就绪的模式。切换模式会清空当前的查询结果。
- 问题输入框: 一个文本区域,供用户输入问题。
- 控制按钮: "执行查询"按钮用于发起一次完整的RAG问答;"清除结果"按钮用于清空界面。
-
主显示区域:
- 最终答案: 在右侧顶部,用一个面板展示由LLM生成的最终答案。
- 参考来源: 在左侧,以列表形式展示本次回答参考的所有来源块。每个来源块都会显示:
- 源文件: 该块来自哪个原始文档。
- 相关性: 该块与查询的最终混合分数。
- 查看块内容: 一个可展开的区域,显示该块的完整文本。
- 查看完整上下文: 一个关键的调试按钮。点击后,右侧面板会刷新,显示该块及其在原始文档中的前后相邻块,帮助你理解它所处的完整语境。这个上下文视图对于分析检索结果的质量至关重要。
本系统的所有功能都通过一套标准的 RESTful API 对外提供服务。该API是基于 FastAPI 框架构建的,这是一个现代、高性能的Python Web框架,它利用 Pydantic 进行严格的请求和响应数据模型验证,确保了接口的健壮性和可靠性。
API基础地址: 默认在 http://127.0.0.1:8000 (可在 deploy/api.json 中配置)。
交互式API文档: FastAPI自动提供了功能强大的交互式文档。在服务启动后,您可以访问以下两个URL来浏览和测试所有API端点:
- Swagger UI:
http://127.0.0.1:8000/docs - ReDoc:
http://127.0.0.1:8000/redoc
下面将对所有API端点进行全面、详细的介绍。
这类接口用于执行系统的核心RAG功能,是系统对外提供价值的主要入口。
-
功能描述: 这是最常用、最高级的核心接口。它接收一个自然语言问题,在指定的知识库模式(mode)中执行一个完整的检索增强生成(RAG)流程。该流程包括:可选的查询扩展、多路并行检索(向量+关键词)、倒数排名融合(RRF)、混合重排,最后将最相关的上下文信息提交给大语言模型(LLM)以生成一个综合性的、忠于原文的答案。
-
请求体 (Request Body - JSON):
参数 (Parameter) 类型 (Type) 是否必需 (Required) 描述 (Description) mode_namestring是 要查询的模式名称。该模式必须已通过 /modes/load接口加载到服务器内存中。querystring是 用户的自然语言提问。 top_kinteger否 希望最终返回的参考来源(sources)的数量。如果未提供,则使用该模式蓝图配置中的 top_k默认值。use_multi_queryboolean否 (默认 true)是否启用LLM多路查询。当为 true时,系统会调用LLM将原始查询改写成多个不同角度的子查询,以提高对复杂或模糊问题的召回率。这会带来微小的延迟和LLM调用成本。use_jieba_expansionboolean否 (默认 false)是否启用Jieba分词查询扩展。当为 true时,系统会提取查询中的关键词组成一个新的查询。此选项与use_multi_query互斥,如果两者都为true,本选项会优先执行,use_multi_query将失效。 -
响应 (Response):
-
成功响应 (
200 OK): 返回一个包含最终答案和参考来源的JSON对象。字段 (Field) 类型 (Type) 描述 (Description) answerstringLLM根据 sources内容生成的最终答案。sourcesarray一个JSON对象数组,每个对象代表一个被引用的知识块。数组内的对象包含以下字段:
-chunk_id(integer): 块在FAISS索引中的整数ID。
-doc_id(string): 块所属的原始文档ID。
-content(string): 块的完整文本内容。
-score(float): 经过混合重排后的最终相关性分数。
-metadata(object): 包含该块所有元数据的JSON对象。成功响应示例:
{ "answer": "RAG,全称检索增强生成,是一种先进的人工智能框架。它首先通过一个检索器从庞大的知识库(如PDF、文档)中找到与用户问题最相关的信息片段,然后将这些片段作为上下文,提供给一个大型语言模型(生成器),由生成器最终生成一个既准确又忠于事实的答案。", "sources": [ { "chunk_id": 201, "doc_id": "AI前沿技术白皮书", "content": "检索增强生成(RAG)是一种将强大的预训练语言模型与外部知识库查询相结合的技术。其核心架构包括两个关键组件:一个高效的检索器(Retriever)和一个强大的生成器(Generator)。", "score": 0.9123, "metadata": { "level": 0, "path": "data/AI前沿技术白皮书.pdf", "chunk_id_str": "AI前沿技术白皮书_text_5" } }, { "chunk_id": 88, "doc_id": "大规模语言模型应用指南", "content": "RAG的工作流程分为两步:首先,当用户提出问题时,检索器会在向量化的知识库中进行快速搜索,召回最相关的文本块(Chunks)。这些文本块包含了回答问题的关键信息。", "score": 0.8850, "metadata": { "level": 0, "path": "data/LLM应用指南.docx", "chunk_id_str": "大规模语言模型应用指南_text_12" } }, { "chunk_id": 89, "doc_id": "大规模语言模型应用指南", "content": "将检索到的上下文提供给生成器,可以极大地缓解大模型的“幻觉”问题,确保生成的答案不仅流畅、相关,更重要的是有据可查、忠于事实,从而显著提升了系统的可靠性。", "score": 0.8511, "metadata": { "level": 0, "path": "data/LLM应用指南.docx", "chunk_id_str": "大规模语言模型应用指南_text_13" } } ] } -
错误响应:
404 Not Found: 如果请求的mode_name在服务器上未加载或不存在。500 Internal Server Error: 如果在RAG处理流程(如LLM调用、检索)中发生无法恢复的内部错误。
-
-
调用示例: Python 示例
import requests import json api_url = "http://127.0.0.1:8000" payload = { "mode_name": "default", "query": "什么是RAG系统?", "top_k": 3, "use_multi_query": True } try: response = requests.post(f"{api_url}/ask", json=payload, timeout=60) response.raise_for_status() # 如果状态码不是2xx,则抛出异常 result = response.json() print("===== 最终答案 =====") print(result.get("answer")) print("\n===== 参考来源 =====") for source in result.get("sources", []): print(f"- [Score: {source['score']:.4f}] 来自文档: '{source['doc_id']}'") print(f" 内容预览: {source['content'][:80]}...") except requests.exceptions.RequestException as e: print(f"API调用失败: {e}")
Java 示例 (使用 Java 11+ 内置 HttpClient)
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; public class RagAskExample { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient(); // 在实际应用中,建议使用Gson或Jackson等库从一个Java对象来构建JSON字符串 String jsonPayload = """ { "mode_name": "default", "query": "什么是RAG系统?", "top_k": 3, "use_multi_query": true } """; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/ask")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .timeout(Duration.ofMinutes(1)) .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { System.out.println("API响应成功:"); // 同样,建议使用JSON库将response.body()解析为Java对象以方便使用 System.out.println(response.body()); } else { System.err.println("API请求失败,状态码: " + response.statusCode()); System.err.println("错误详情: " + response.body()); } } }
-
功能描述: 执行纯粹的检索(Pure Retrieval)流程。它与
/ask接口的检索部分完全相同,但流程在“重排”之后就戛然而止,不会调用LLM生成答案。此接口用于获取与查询最相关的原始知识块列表,非常适合调试、分析或需要自定义下游任务的场景。 -
请求体 (Request Body - JSON):
参数 (Parameter) 类型 (Type) 是否必需 (Required) 描述 (Description) mode_namestring是 要查询的模式名称。该模式必须已通过 /modes/load接口加载到服务器内存中。querystring是 用户的自然语言查询。 top_kinteger否 希望最终返回的知识块的数量。如果未提供,则使用该模式蓝图配置中的 top_k默认值。use_multi_queryboolean否 (默认 true)是否启用LLM多路查询。当为 true时,系统会调用LLM将原始查询改写成多个不同角度的子查询,以提高对复杂或模糊问题的召回率。use_jieba_expansionboolean否 (默认 false)是否启用Jieba分词查询扩展。当为 true时,系统会提取查询中的关键词组成一个新的查询。此选项与use_multi_query互斥,如果两者都为true,本选项会优先执行,use_multi_query将失效。 -
响应 (Response):
-
成功响应 (
200 OK): 返回一个只包含results字段的JSON对象。字段 (Field) 类型 (Type) 描述 (Description) resultsarray一个JSON对象数组,代表按最终相关性分数降序排列的知识块列表。每个对象都是一个独立的知识单元,其结构与 /ask响应中的sources字段完全相同,包含以下关键字段:
-chunk_id(integer): 块在FAISS索引中的唯一整数ID。
-doc_id(string): 块所属的原始文档ID。
-content(string): 块的完整文本内容。
-score(float): 最终的混合相关性分数,它结合了向量相似度(vec_score)和关键词匹配度(kw_score)。
-vec_score(float): 原始的向量相似度分数。
-kw_score(float): 经过归一化的关键词匹配分数。
-metadata(object): 包含该块所有元数据的JSON对象,如来源路径、分块策略、在文档中的层级等,对于追溯和调试非常有价值。成功响应示例:
{ "results": [ { "chunk_id": 42, "doc_id": "BM25算法详解", "content": "BM25(Best Match 25)是一种在信息检索领域广泛应用的概率相关性模型。它通过词频(TF)、逆文档频率(IDF)和文档长度归一化来评估文档与查询的相关性,尤其擅长处理关键词匹配。", "score": 0.9315, "metadata": { "path": "data/papers/BM25算法详解.md", "level": 0, "chunk_id_str": "BM25算法详解_text_2" }, "vec_score": 0.9022, "kw_score": 0.95 }, { "chunk_id": 101, "doc_id": "混合检索系统架构", "content": "我们的混合检索系统结合了基于FAISS的向量检索和经典的BM25算法。向量检索负责语义召回,而BM25则用于精确的关键词加权,两者通过加权融合,显著提升了检索精度。", "score": 0.8977, "metadata": { "path": "data/internal_docs/混合检索系统架构.docx", "level": 0, "chunk_id_str": "混合检索系统架构_text_5" }, "vec_score": 0.9105, "kw_score": 0.801 } ] } -
错误响应:
404 Not Found: 如果请求的mode_name在服务器上未加载或不存在。500 Internal Server Error: 如果在RAG处理流程(如LLM调用、检索)中发生无法恢复的内部错误。
-
-
调用示例: Python 示例
import requests import json api_url = "http://127.0.0.1:8000" payload = { "mode_name": "default", "query": "介绍一下BM25算法", "top_k": 5 } try: response = requests.post(f"{api_url}/search", json=payload) response.raise_for_status() data = response.json() results = data.get('results', []) print(f"成功检索到 {len(results)} 条结果。") print("-" * 30) if results: for i, res in enumerate(results): print(f"结果 {i+1}:") print(f" - 最终分数 (Score): {res['score']:.4f} (向量分: {res['vec_score']:.4f}, 关键词分: {res['kw_score']:.4f})") print(f" - 来源 (Doc ID): {res['doc_id']}") print(f" - 内容预览: {res['content'][:100]}...") print("-" * 30) except requests.exceptions.RequestException as e: print(f"API调用失败: {e}")
Java 示例 (使用 Java 11+ 内置 HttpClient)
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; public class RagSearchExample { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient(); String jsonPayload = """ { "mode_name": "default", "query": "介绍一下BM25算法", "top_k": 5 } """; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/search")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .timeout(Duration.ofMinutes(1)) .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { System.out.println("API响应成功:"); // 在实际应用中, 建议使用Gson, Jackson, 或 org.json等库 // 将 response.body() 解析为Java对象或JSON对象,以便于访问和处理。 // 例如: JSONObject obj = new JSONObject(response.body()); // JSONArray results = obj.getJSONArray("results"); System.out.println(response.body()); } else { System.err.println("API请求失败,状态码: " + response.statusCode()); System.err.println("错误详情: " + response.body()); } } }
-
功能描述: 对指定模式的检索性能进行量化评测。该接口是系统
Evaluator模块的直接体现,它通过将当前模式的检索结果与一个使用Flat(暴力搜索)索引生成的“理想结果集”(Ground Truth)进行对比,来计算精确召回率和相关性召回率等核心指标。这是一个用于评估和调优不同索引配置、检索策略或嵌入模型性能的强大工具。 -
请求体 (Request Body - JSON):
参数 (Parameter) 类型 (Type) 是否必需 (Required) 描述 (Description) mode_namestring是 要评测的模式名称。该模式必须已加载。 querystring是 用于评测的查询字符串。 use_multi_queryboolean否 (默认 true)评测时是否启用LLM多路查询。 use_jieba_expansionboolean否 (默认 false)评测时是否启用Jieba分词查询扩展。此选项优先于 use_multi_query。 -
响应 (Response):
-
成功响应 (
200 OK): 返回一个包含详细评测结果的JSON对象。字段 (Field) 类型 (Type) 描述 (Description) querystring本次评测使用的原始查询。 search_timefloat执行本次检索所花费的时间(秒)。 precision_recallfloat精确召回率。衡量的是:在理想结果集中,有多少比例被实际检索到了。范围 0.0 到 1.0。 relevance_recallfloat相关性召回率。基于分数加和,衡量实际检索结果的总相关性分数占理想结果总分数的比例。范围 0.0 到 1.0。 retrievedinteger本次检索实际返回的结果数量。 idealinteger理想结果集中的项目数量。 retrieved_sumfloat实际检索到的所有结果的分数总和。 ideal_sumfloat理想结果集中所有项目的分数总和。 retrieved_resultsarray本次检索实际返回的结果列表,其结构与 /search接口的results字段完全相同。sub_queries_generatedarray如果使用了查询扩展,这里会列出所有实际用于检索的子查询字符串。 成功响应示例:
{ "query": "FAISS有哪些索引类型?", "search_time": 0.0853, "precision_recall": 1.0, "relevance_recall": 0.9985, "retrieved": 5, "ideal": 5, "retrieved_sum": 4.567, "ideal_sum": 4.574, "retrieved_results": [ { "chunk_id": 12, "doc_id": "FAISS深度指南", "content": "FAISS支持多种索引类型,其中最核心的是Flat、IVF和HNSW。Flat是暴力搜索,保证100%准确但速度慢...", "score": 0.9512, "metadata": { ... }, "vec_score": 0.96, "kw_score": 0.92 } ], "sub_queries_generated": [ "FAISS有哪些索引类型?", "FAISS的索引结构", "介绍FAISS的HNSW和IVF索引" ] } -
错误响应:
404 Not Found: 如果请求的mode_name未加载。500 Internal Server Error: 评测过程中发生错误,例如无法获取理想结果集。
-
-
调用示例: Python 示例
import requests api_url = "http://127.0.0.1:8000" payload = { "mode_name": "my_mode", "query": "对比HNSW和IVF索引的优缺点", "use_multi_query": False } try: response = requests.post(f"{api_url}/test", json=payload) response.raise_for_status() eval_result = response.json() print("===== 性能评测报告 =====") print(f"查询: '{eval_result['query']}'") print(f"耗时: {eval_result['search_time']:.4f} 秒") print(f"精确召回率: {eval_result['precision_recall']:.2%}") print(f"相关性召回率: {eval_result['relevance_recall']:.2%}") except requests.exceptions.RequestException as e: print(f"API调用失败: {e}")
Java 示例 (使用 Java 11+ 内置 HttpClient)
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class RagTestExample { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient(); String jsonPayload = """ { "mode_name": "my_mode", "query": "对比HNSW和IVF索引的优缺点" } """; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/test")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { System.out.println("评测成功,结果如下:"); System.out.println(response.body()); } else { System.err.println("评测失败,状态码: " + response.statusCode()); System.err.println("错误详情: " + response.body()); } } }
这类接口用于创建、加载、卸载、更新和删除知识库模式(Mode),是管理系统的基础。所有操作都是对服务器上的文件和内存进行的。
-
功能描述: 根据服务器上
blueprint/目录中指定的蓝图配置文件(例如blueprint/my_mode.json),从头开始执行完整的数据处理和索引构建流程。这是一个计算密集型且非常耗时的操作,尤其是当启用RAPTOR或处理大量文档时。它会生成索引文件并保存在storage/目录下。 -
请求体 (Request Body - JSON):
参数 (Parameter) 类型 (Type) 是否必需 (Required) 描述 (Description) mode_namestring是 要创建的模式名称,必须与蓝图文件名(不含 .json)完全对应。 -
响应 (Response):
- 成功响应 (
200 OK):{"status": "success", "message": "模式 'my_mode' 创建并构建成功。"} - 错误响应:
404 Not Found: 如果对应的蓝图文件blueprint/my_mode.json不存在。409 Conflict: 如果该模式的存储目录storage/my_mode已存在,以防止意外覆盖。500 Internal Server Error: 如果在数据加载、处理或索引构建过程中发生任何错误。
- 成功响应 (
-
调用示例: Python 示例
import requests # 前提:确保服务器的 blueprint/my_mode.json 文件已存在并配置好 payload = {"mode_name": "my_mode"} try: # 构建过程可能很长,请务必使用足够长的超时时间(如10分钟) response = requests.post("http://127.0.0.1:8000/modes/create", json=payload, timeout=600) response.raise_for_status() print(response.json()) except requests.exceptions.Timeout: print("创建模式请求超时。这对于大数据集是正常现象,请在服务器后台查看日志确认实际进度。") except requests.exceptions.RequestException as e: print(f"请求失败: {e}")
Java 示例
// ... String jsonPayload = "{\"mode_name\": \"my_mode\"}"; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/create")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .timeout(Duration.ofMinutes(10)) // 使用长超时 .build(); // ... 发送并处理响应
-
功能描述: 将一个已经构建好的模式(存储在
storage/目录下)加载到服务器的内存(RAM)和显存(VRAM)中,使其进入“就绪”状态,可以接受/ask和/search的查询。 -
请求体 (Request Body - JSON):
{"mode_name": "string"} -
响应 (Response):
- 成功响应 (
200 OK):{"status": "success", "message": "模式 'my_mode' 已成功加载。"} - 错误响应:
409 Conflict: 如果该模式已经处于加载状态。500 Internal Server Error: 如果加载索引文件失败(例如文件不存在、损坏或与当前环境不兼容)。
- 成功响应 (
-
调用示例: Python 示例
import requests payload = {"mode_name": "my_mode"} # 加载大索引也可能需要一些时间 response = requests.post("http://127.0.0.1:8000/modes/load", json=payload, timeout=120) print(response.json())
Java 示例
// ... String jsonPayload = "{\"mode_name\": \"my_mode\"}"; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/load")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .timeout(Duration.ofMinutes(2)) // 加载大索引也可能耗时 .build(); // ...
- 功能描述: 从服务器内存中卸载一个已加载的模式,以释放CPU/GPU内存和显存资源。卸载后,该模式将无法再接受查询,但其在磁盘上的文件不受影响。
- 请求体 (Request Body - JSON):
{"mode_name": "string"} - 响应 (Response):
- 成功响应 (
200 OK):{"status": "success", "message": "模式 'my_mode' 已卸载。"} - 错误响应:
404 Not Found: 如果请求的模式当前未被加载。
- 成功响应 (
- 调用示例:
Python 示例
Java 示例
import requests payload = {"mode_name": "my_mode"} response = requests.post("http://127.0.0.1:8000/modes/unload", json=payload) print(response.json())
/// ... String jsonPayload = "{\"mode_name\": \"my_mode\"}"; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/unload")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .timeout(Duration.ofMinutes(2)) .build(); // ...
-
功能描述: 根据最新的蓝图配置文件,智能地更新或完全重建一个已存在的模式。其逻辑是:
- 如果蓝图中的核心参数(如
embed_model,proc_chunk_token_size,proc_enable_raptor等)未改变,系统将执行“热更新”,仅更新可动态调整的参数(如检索权重、hnsw_ef,ivf_nprobe等),此过程非常快。 - 如果核心参数发生变化,系统将自动执行完整的删除和重建流程(与先调用
delete再调用create类似)。这同样是一个耗时操作。
- 如果蓝图中的核心参数(如
-
方法:
PUT -
请求体 (Request Body - JSON):
{"mode_name": "string"} -
响应 (Response):
- 成功响应 (
200 OK): 返回一个消息,明确指明是执行了“热更新”还是“完全重建”。 - 错误响应:
404 Not Found(蓝图文件不存在),500 Internal Server Error(更新/重建中出错)。
- 成功响应 (
-
调用示例: Python 示例
import requests # 在调用前,你可能已经修改了服务器上的 blueprint/my_mode.json 配置文件 payload = {"mode_name": "my_mode"} # 重建也可能耗时很长 response = requests.put("http://127.0.0.1:8000/modes/rebuild", json=payload, timeout=600) print(response.json())
Java 示例
// ... String jsonPayload = "{\"mode_name\": \"my_mode\"}"; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/rebuild")) .header("Content-Type", "application/json") .PUT(HttpRequest.BodyPublishers.ofString(jsonPayload)) // 使用PUT方法 .timeout(Duration.ofMinutes(10)) .build(); // ...
-
功能描述: 永久性地删除一个模式的磁盘存储数据(即
storage/目录下的整个对应文件夹)。这是一个危险且不可逆的操作。为防止误操作正在使用的模式,模式必须先被卸载才能删除。 -
方法:
DELETE -
请求体 (Request Body - JSON):
{"mode_name": "string"} -
响应 (Response):
- 成功响应 (
200 OK):{"status": "success", "message": "模式 'my_mode' 的存储数据已成功删除。"} - 错误响应:
409 Conflict: 如果模式当前仍处于加载状态,无法删除。
- 成功响应 (
-
调用示例: Python 示例
import requests # 确保 my_mode 已被卸载 payload = {"mode_name": "my_mode"} response = requests.delete("http://127.0.0.1:8000/modes/delete", json=payload) print(response.json())
Java 示例
import java.net.http.HttpRequest; // ... String jsonPayload = "{\"mode_name\": \"my_mode\"}"; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/delete")) .header("Content-Type", "application/json") // 注意:Java的HttpClient中DELETE方法通常不建议带body,但FastAPI支持。 // 这是标准的写法。 .method("DELETE", HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); // ...
这类接口用于一次性管理多个或所有模式,方便系统初始化和清理。
- 功能描述:
扫描
blueprint/目录下的所有蓝图文件,并自动为那些尚未在storage/目录中创建存储的模式执行create操作。已存在的模式会被自动跳过。这是一个极其方便的批量初始化功能,但可能需要非常长的时间来完成。 - 方法:
POST - 请求体: 无
- 响应 (
200 OK):{"status": "completed", "message": "...", "created": 2, "skipped": 1, "failed": 0, "failed_details": []} - 调用示例:
Python 示例
Java 示例
import requests # 这是一个长轮询操作,超时时间需要设置得非常长(例如30分钟) response = requests.post("http://127.0.0.1:8000/modes/create-all", timeout=1800) print(response.json())
var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/create-all")) .POST(HttpRequest.BodyPublishers.noBody()) // 无请求体 .timeout(Duration.ofMinutes(30)) .build(); // ...
- 功能描述:
清空服务器上所有模式的存储数据。该操作会删除
storage/目录下的所有子目录,并清空内存中所有已加载的模式。这是一个极度危险的重置操作,请谨慎使用。 - 方法:
DELETE - 请求体: 无
- 响应 (
200 OK):{"status": "success", "message": "已成功清除 3 个模式的存储数据。"} - 调用示例:
Python 示例
Java 示例
import requests # 强烈建议在调用前添加用户确认步骤 confirm = input("警告:这将删除所有模式!确定吗? (y/N): ") if confirm.lower() == 'y': response = requests.delete("http://127.0.0.1:8000/modes/clear_all") print(response.json()) else: print("操作已取消。")
var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/clear_all")) .DELETE() .build(); // ...
这类接口用于查询服务器和模式的状态,通常使用 GET 方法,幂等且安全,可以频繁调用。
- 功能描述: 检查API服务的当前状态,并列出所有已加载到内存中的模式及其各自索引中包含的向量总数。这是监控服务健康状况和资源占用的基本接口。
- 方法:
GET - 请求体: 无
- 成功响应 (
200 OK):{ "status": "active", "loaded_modes": [ { "mode_name": "default", "index_vectors": 12345 }, { "mode_name": "my_mode", "index_vectors": 678 } ] } - 调用示例:
Python 示例
Java 示例
import requests response = requests.get("http://127.0.0.1:8000/status") print(response.json())
var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/status")) .GET() .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
- 功能描述:
扫描
storage/目录,列出所有已经成功构建了索引文件的模式。这反映了磁盘上所有可供加载的模式,而不管它们当前是否在内存中。 - 方法:
GET - 请求体: 无
- 成功响应 (
200 OK):{"modes": ["default", "my_mode", "another_mode"]} - 调用示例:
Python 示例
Java 示例
import requests response = requests.get("http://127.0.0.1:8000/modes/list_available") available_modes = response.json().get('modes', []) print(f"服务器上共存在 {len(available_modes)} 个可加载的模式: {available_modes}")
var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/list_available")) .GET() .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
- 功能描述:
获取指定模式的完整知识库,即
doc_store中的所有文档块及其元数据。此接口主要用于高级调试和前端应用(如知识树浏览器)进行上下文的深度追溯。警告:对于大型知识库,此接口的响应体可能达到数百MB甚至GB级别,请谨慎在生产环境中使用,并确保客户端有足够的内存和超时设置来处理大型响应。 - 方法:
GET - URL路径参数:
mode_name(string, required): 要获取其知识库的目标模式名称。
- 请求体: 无
- 成功响应 (
200 OK):doc_store: (object) 一个以块的整数ID为键,以包含块所有信息的JSON对象为值的巨大JSON对象。
- 调用示例:
Python 示例
Java 示例
import requests mode_name = "default" # 使用流式传输处理潜在的大响应 try: with requests.get(f"http://127.0.0.1:8000/modes/get_all_chunks/{mode_name}", stream=True, timeout=300) as r: r.raise_for_status() # 这里仅做示例,实际应用中可能需要分块写入文件或使用JSON流解析器 print(f"开始接收模式 '{mode_name}' 的知识库数据...") # for chunk in r.iter_content(chunk_size=8192): # process_chunk(chunk) doc_store = r.json() print(f"成功获取知识库,总块数: {len(doc_store.get('doc_store', {}))}") except requests.exceptions.RequestException as e: print(f"获取知识库失败: {e}")
// ... String modeName = "default"; var request = HttpRequest.newBuilder() .uri(URI.create("http://127.0.0.1:8000/modes/get_all_chunks/" + modeName)) .timeout(Duration.ofMinutes(5)) // 为大响应设置长超时 .GET() .build(); // 建议使用异步方式或流式处理响应体,而不是一次性读入内存 // HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandlers.ofFile(Paths.get("doc_store.json"))); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println("响应体大小: " + response.body().length() + " 字节");
好的,没有问题!这是一个非常重要且绝对必要的补充部分。一个项目的成败,很大程度上取决于用户能否顺利地配置好运行环境。
下面,我已经为您精心撰写了一个全新的、独立的“环境配置与安装指南”大章节。它严格遵循了您的要求,为 Linux (GPU) 和 Windows (CPU) 用户提供了清晰、分步的详细教学,并特别强调了版本兼容性这一核心痛点。
您可以将这整块内容直接复制并粘贴到您的 README 文件的末尾。
由于本系统深度依赖 GPU 进行加速,而相关的库(PyTorch, CUDA, FAISS)之间有严格的版本依赖关系,因此请务必遵循以下与您操作系统匹配的指南进行安装。
- Linux 用户: 推荐此环境,可以完整体验包括
faiss-gpu在内的全部 GPU 加速功能。 - Windows 用户: 由于
faiss-gpu没有通过 pip 提供官方的 Windows 支持,我们将使用faiss-cpu版本,这意味着向量索引的计算将在 CPU 上进行,速度会慢于 GPU 版本,但在功能上是完全一致的。
此流程的核心是确保 NVIDIA驱动 → CUDA工具包 → PyTorch → FAISS-GPU 这条依赖链的版本相互兼容。
- 确认拥有 NVIDIA 显卡及驱动:
打开终端,运行
nvidia-smi命令。如果您看到类似下面的输出,说明驱动已正确安装。请特别留意右上角的nvidia-smi
CUDA Version,例如12.1。这个版本是您的驱动最高支持的 CUDA 版本,您需要安装等于或低于此版本的 PyTorch 和 FAISS。+-----------------------------------------------------------------------------+ | NVIDIA-SMI 525.60.13 Driver Version: 525.60.13 CUDA Version: 12.1 | |-------------------------------+----------------------+----------------------+
使用虚拟环境是一个好习惯,可以避免与系统全局的 Python 库冲突。
# 在项目根目录执行
python3 -m venv venv
# 激活虚拟环境
source venv/bin/activate这是最关键的一步。 不要直接 pip install torch。
-
根据您在第 1 步中
nvidia-smi看到的 CUDA 版本,找到对应的安装命令。例如,如果您的 CUDA 版本是12.1,您就应该选择为 CUDA 12.1 编译的 PyTorch 版本。示例 (CUDA 12.1):
# 从 PyTorch 官网复制的命令 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121示例 (CUDA 11.8):
# 从 PyTorch 官网复制的命令 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118请务必使用官网提供的完整命令进行安装。
FAISS 会根据您当前环境中的 CUDA 和 PyTorch 版本自动选择最合适的预编译包。
pip install faiss-gpu如果上一步 PyTorch 安装正确,pip 通常能找到匹配的 faiss-gpu 版本。
现在,最复杂的依赖已经处理完毕,您可以安全地安装 requirements.txt 中的其他库了。
pip install -r requirements.txt在 Windows 上,我们将配置一个功能完整但使用 CPU 进行计算的版本。
# 在项目根目录执行
python -m venv venv
# 激活虚拟环境 (使用 PowerShell 或 cmd)
.\venv\Scripts\activate访问 PyTorch 官网,在安装向导中选择 Stable (稳定版), Windows, Pip, Python, 和 CPU。然后复制生成的命令。
示例命令:
pip3 install torch torchvision torchaudio由于我们使用 CPU,所以需要安装 faiss-cpu 这个包。
pip install faiss-cpu最后,安装 requirements.txt 中剩余的库。
pip install -r requirements.txt本系统虽然在设计上力求全面和先进,但仍然在以下几个方面存在值得探讨的缺陷和未来可以演进的方向。
- 当前状态:
本系统目前是无状态的。每一次对
/ask接口的调用都是一次完全独立的、从零开始的交互。系统无法记住用户在上一轮对话中问了什么,也无法理解“它”、“继续说”、“那方面呢”这类代词或关联性提问。 - 影响: 这使得系统无法进行连续的多轮对话,限制了其在需要深度探索和追问的复杂场景(如智能客服、研究助理)中的应用。
- 未来展望:
要实现真正的对话能力,需要引入会话管理 (Session Management) 模块。该模块需要负责:
- 存储对话历史: 为每个用户或会话保存历史的问答对。
- 上下文压缩/重构: 在处理新的问题时,利用一个 LLM 将历史对话和新问题整合成一个包含完整上下文的、可供检索的独立查询(例如,将“它有什么特点?”转换为“RAG系统有什么特点?”)。
- 当前状态: 系统通过 LLM 多路查询 或 Jieba 分词扩展 来应对用户的多语义或模糊查询。这本质上是一种“暴力”的语义扩展,即生成多个可能的查询变体,然后对所有变体进行检索,寄希望于其中一个能够命中正确答案。
- 影响: 虽然这种方法能有效提升召回率,但它也带来了显著的延迟增加,因为每一次扩展都会导致检索量成倍增加。对于那些对响应时间要求极高的在线应用场景,这种延迟可能是不可接受的。
- 未来展望:
一个更先进的系统应当具备更智能的查询意图分析与分解 (Query Analysis & Decomposition) 能力。例如,当收到“请对比一下 FAISS 的 HNSW 和 IVF 索引的优缺点”这样一个复杂问题时,系统不应只是改写它,而应能将其分解为两个或多个独立的子问题:
- “FAISS HNSW 索引的优点和缺点是什么?”
- “FAISS IVF 索引的优点和缺点是什么?” 系统可以分别检索这两个子问题的答案,然后将结果汇总,由 LLM 进行最终的对比和总结。这种方法更为精准,也可能比粗暴的多路查询更高效。
- 当前状态:
本系统缺乏细粒度的增量更新机制。当知识库中的某个源文件(例如一个PDF)被修改或更新后,当前最直接的方法是执行
client.py update <mode_name>或直接删除并重建整个模式。这会触发对所有文档的重新处理和索引,即使只有一个文件发生了变化。 - 影响: 对于需要频繁更新知识库的动态应用场景,全量重建的成本和时间开销是巨大的,这严重影响了系统的敏捷性和实用性。
- 未来展望:
理想的更新机制应该是增量和自动化的。可以设计一个文件监控服务,当检测到某个文件被修改后,自动执行以下流程:
- 从 FAISS 索引和文档存储中,精确地找到并删除所有源自该旧文件的文本块。
- 仅对这个更新后的文件执行处理和分块流程。
- 将新生成的块嵌入并添加到现有索引中。
- 当前状态: 系统提供了“代码空间”(生成摘要)和“语义空间”(逐行转述)两种表格处理策略,但两者都有其无法回避的权衡。
- 代码空间 (use_code_space: true):
- 缺陷1:高昂的构建成本与延迟。此策略在构建索引时需要为每个表格调用 LLM 来生成摘要,这会显著增加索引构建的时间和 API 成本。
- 缺陷2:信息损失。LLM 生成的摘要虽然能捕捉宏观信息,但必然会丢失原始表格中的具体细节。如果用户的查询恰好需要这些被“摘要掉”的细节,检索就会失败。
- 语义空间 (use_code_space: false):
- 缺陷:缺乏整体视角。将表格原子化为一行行的句子,使得系统完全丧失了对表格整体结构的理解,无法回答任何需要跨行比较或聚合分析的问题(例如“哪个产品的利润最高?”)。
- 未来展望: 一个更完美的表格处理方案可能是混合模式。在索引时,同时保留行级语义块和 LLM 生成的摘要块。在检索时,系统可以根据查询的意图(是查询具体事实还是进行宏观分析?)来动态决定优先匹配哪一类型的块。
- 当前状态:
系统采用的是向量分数和 BM25 关键词分数的线性加权融合 (
Final_Score = (vec * w1) + (kw * w2)) 来进行重排。这是一个高效且有效的基线方法。 - 影响: 这种方法虽然能兼顾语义和关键词,但它是在两个分数已经独立算出的情况下进行的后期融合,可能无法捕捉到查询词与文档内容之间更深层次、更精细的交互关系。
- 未来展望:
可以引入一个更强大的重排模型 (Reranker),通常是一个跨编码器 (Cross-Encoder) 模型。与分别计算向量的
sentence-transformer(双编码器)不同,跨编码器会将用户的“查询”和每个候选“文档块”同时输入模型,让模型在内部进行深度的注意力交互,从而给出一个更精准的相关性判断分数。这虽然会增加一步计算的延迟,但通常能极大地提升最终结果的排序质量(即最相关的结果排在最前面的概率)。
- 当前状态:
本系统在设计上是一个功能强大的单点应用 (Single-Point Application)。一个独立的
api.py进程负责加载所有必需的模型(嵌入模型、LLM等),并处理从向量化到最终生成的所有任务。这种集成化的设计使得系统非常易于在单台服务器上启动和调试。 - 影响 (缺陷):
这种设计的最大缺陷在于缺乏原生的分布式能力,导致水平扩展困难。当面临高并发请求时,传统的水平扩展方案(即启动多个API服务器实例并置于负载均衡之后)会变得效率极低。原因在于:
- 资源冗余: 每个独立的API实例都需要在自己的内存和显存中完整地加载一套庞大的模型。如果一个嵌入模型需要占用4GB显存,启动5个实例就会浪费20GB的显存来加载同一个模型,这造成了巨大的资源冗余和成本浪费。
- 性能瓶颈: 在单个实例内部,所有并发请求都会排队等待同一个GPU资源进行计算(例如文本嵌入),这在高负载下会迅速导致请求延迟的飙升。
- 未来展望:
要将系统提升到能应对生产级负载的水平,未来的关键演进方向是核心组件解耦。可以将当前集成的系统拆分为多个各司其职的、可独立部署和扩展的专用服务:
- 嵌入服务: 一个专门接收文本并返回向量的独立服务。可以根据嵌入任务的负载,独立地扩展其实例数量。
- 生成服务: 专门封装LLM,负责摘要和问答生成。
- RAG核心调度服务: 作为系统的“大脑”,它本身变得非常轻量级,负责接收用户请求、向其他专用服务发起调用、并编排整个检索和生成流程。
- 当前状态: 本系统当前是一个以文本为中心的 RAG 系统。它的核心能力在于理解和检索自然语言文字以及从表格中提取的结构化文本。当系统在文档中(PDF受影响,但DOC文档因为其压缩方式不受影响,也就是不读图)遇到图片时,它并不能“看懂”图片的内容。当前唯一的处理方式是依赖 OCR (光学字符识别) 技术,尝试从图片中提取并识别出文字。
- 影响 (缺陷):
这种“只读字,不看图”的模式导致了严重的信息损失和能力限制:
- 关键视觉信息丢失: 一张复杂的流程图、一张展示数据趋势的图表、一张产品照片或一张组织架构图,它们所包含的丰富信息,在当前系统中被完全忽略了。系统只能检索到图片的标题或旁边的文字描述,却丢失了图像本身所传达的核心内容。
- OCR 的不确定性: OCR 技术并非100%准确。对于扫描质量不高、字体特殊或布局复杂的图片,OCR 识别出的文本可能包含大量错误,这些“脏数据”进入索引后会严重干扰检索的准确性。
- 无法响应视觉类查询: 系统完全无法回答基于图像内容的查询,例如:“给我看看那张关于系统架构的图”或者“去年销售额最高的产品的照片长什么样?”。
- 未来展望:
未来的演进方向是引入视觉语言模型 (Vision-Language Models, VLM),如
CLIP或BLIP,来实现对图像内容的深度理解:- 多模态嵌入: 在处理文档时,不仅对文本块进行嵌入,也对所有图片进行嵌入,生成能代表其内容的视觉向量。
- 混合索引: 构建一个能同时存储和检索文本向量与视觉向量的索引库。
- 跨模态检索: 当用户提出问题时,系统可以同时在文本和视觉两个模态中进行检索。例如,一个关于“销售增长趋势”的查询,不仅能匹配到描述趋势的文字段落,还能直接匹配到那张展示了上升曲线的图表的视觉向量。
- 当前状态: 本系统采用了一种简单而直接的持久化策略:每个创建的模式都在其自己的目录(storage/<mode_name>/)下拥有一套完整的、自包含的文件,包括 FAISS 索引(faiss.index)和一个包含所有块文本内容的 Python Pickle 文件(docs.pkl)。
- 影响 (缺陷):
- 严重的数据冗余: 这是最主要的问题。假设您基于同一套源文档,只是为了测试不同的分块大小或嵌入模型而创建了多个模式(例如 mode_A, mode_B, mode_C)。系统会为每个模式都完整地重新处理并存储一份所有文本块的内容。如果您的原始文档有 10GB,您可能会发现 /storage 目录轻易地就膨胀到了好几GB,造成了巨大的磁盘空间浪费。
- 可扩展性瓶颈: 依赖单个大型 Pickle 文件 (docs.pkl) 来存储所有文本内容,在知识库规模变得非常庞大时会遇到瓶颈。加载一个数 GB 大小的 docs.pkl 文件到内存中会消耗大量时间和内存,并且不利于数据的动态查询和管理。
- 跨模式数据管理困难: 由于数据被隔离在各个模式的文件夹中,进行跨模式的统一数据清理、更新或迁移变得非常复杂。
- 未来展望:
引入一个专业的向量数据库,将存储层从当前的文件系统中抽象和分离出来(但引入专业向量数据库则丧失了本系统轻量化的优势)。
- 解决方案: 将 FAISS 和 Pickle 文件的组合替换为一个统一的向量数据库解决方案,例如 Milvus, Qdrant, Weaviate, 或 ChromaDB。
- 工作方式:
- 集中存储: 这些数据库能将向量、其对应的文本内容(作为 payload 或 metadata)以及其他元数据作为一个整体记录进行集中存储和管理。
- 逻辑隔离: 可以在同一个数据库实例中创建多个逻辑上的集合 (Collections),每个集合对应您的一个“模式”。这样,即使多个模式引用了相同的源文档,底层的原始文本也可能只被存储一次,或者由数据库进行更高效的管理,从而彻底解决了文件级别的冗余问题。
- 生产级特性: 此外,这些数据库还提供了诸如增量索引、元数据过滤、动态扩缩容、数据备份与恢复等一系列生产环境中至关重要的功能。
A: 查询延迟主要由查询处理和LLM生成两个阶段贡献。您可以根据对速度和质量的权衡,调整以下配置来显著提升响应速度:
1. 优化查询处理阶段 (影响最大):
- 关闭查询扩展 (最有效): 这是最立竿见影的提速方法。
- LLM多路查询: 在
system_config.json或通过客户端命令python client.py config disable-multi-query将其关闭。这可以省去一次LLM调用和多次额外的检索。 - Jieba分词扩展: 通过
python client.py config disable-diff将其关闭,避免额外的关键词检索。
- LLM多路查询: 在
- 调整索引搜索参数: 在您的蓝图文件 (
blueprint/*.json) 中:- 如果您使用
HNSW索引,请适当降低hnsw_ef的值(例如从128降到64或32)。这会减少查询时在图上探索的节点数,直接加快搜索速度,但可能会牺牲一点召回率。 - 如果您使用
IVF索引,请适当降低ivf_nprobe的值(例如从10降到2或1)。这会减少需要搜索的聚类数量。
- 如果您使用
- 选择更轻量的嵌入模型:
- 在蓝图中,选择一个更小、更快的嵌入模型。例如,
BAAI/bge-small-zh-v1.5(512维) 会比moka-ai/m3e-base(768维) 在嵌入计算时更快(但主要取决于模型质量,不能但从维度去对比)。注意:更换模型需要重建索引。
- 在蓝图中,选择一个更小、更快的嵌入模型。例如,
- 跳过关键词重排:
- 如果您的场景对关键词精确匹配要求不高,可以将蓝图中的
kw_weight设为0.0,vec_weight设为1.0。这会跳过 BM25 的计算和融合步骤,节省少量计算时间。
- 如果您的场景对关键词精确匹配要求不高,可以将蓝图中的
2. 优化LLM生成阶段:
- 选择更快的LLM模型: 在蓝图中,将
llm_model_name设置为一个速度更快的“Turbo”或“Lite”版本模型,而不是功能最全但最慢的旗舰模型。
A: 提升检索质量通常与降低延迟的操作方向相反。您可以尝试以下方法:
- 启用并优化查询扩展:
- 对于复杂或模糊的问题,开启 LLM多路查询 (
enable-multi-query) 往往能显著提升召回率,因为它能从多个角度理解您的问题。 - 对于包含特定术语的查询,开启 Jieba分词扩展 (
enable-diff) 可能效果更好。
- 对于复杂或模糊的问题,开启 LLM多路查询 (
- 使用更高质量的嵌入模型:
- 选择一个在评测基准上表现更好的(通常也更大)嵌入模型,例如使用
moka-ai/m3e-base替换bge-small。这需要重建索引。
- 选择一个在评测基准上表现更好的(通常也更大)嵌入模型,例如使用
- 调整检索参数以获得更精细的结果:
- 在蓝图中,提高
hnsw_ef(HNSW) 或ivf_nprobe(IVF) 的值,让搜索更“彻底”,以牺牲速度为代价换取更高的召回率。
- 在蓝图中,提高
- 调整混合检索权重:
- 在蓝图中,仔细权衡
vec_weight和kw_weight。如果您的知识库中包含大量专有名词、代码、产品型号等,适当提高kw_weight(例如到0.4或0.5)可能会很有帮助。
- 在蓝图中,仔细权衡
- 优化分块策略:
- 在蓝图中,调整
proc_chunk_token_size。如果您的文档段落长、上下文依赖强,可以适当增大此值;如果文档内容是零散的知识点,可以适当减小此值。这需要重建索引。
- 在蓝图中,调整
- 开启 RAPTOR:
- 如果您的查询经常是关于“总结”、“核心思想”、“要点”这类宏观问题,开启
proc_enable_raptor并重建索引。RAPTOR 构建的摘要树专门用于应对这类跨细节的抽象问题。
- 如果您的查询经常是关于“总结”、“核心思想”、“要点”这类宏观问题,开启
A: 文档解析是一个复杂的过程,问题可能出在以下几个方面:
- 扫描版/图片PDF:
unstructured库默认只能处理文本型的PDF。如果您的PDF是扫描件(即内容是图片),系统无法直接提取文本。您需要确保您的环境中安装了 Tesseract-OCR 引擎,unstructured会在检测到图片时尝试调用它来进行光学字符识别。 - 复杂的文档布局: 极度复杂的布局(例如多栏、文本框、异形图文混排)可能会干扰
unstructured的解析逻辑,导致文本顺序错乱或内容丢失。 - 缺少系统依赖: 某些旧的或特殊的文档格式(如
.doc,.ppt)可能需要libreoffice等外部软件的支持才能被正确解析。请确保您已按照unstructured库的官方文档安装了所有推荐的系统级依赖。
A: 这是预期的行为。系统在构建索引时,会对 /data 目录做一个“快照”,它不会自动监控文件的后续变化。
要让您的更新生效,您必须手动触发索引的更新或重建流程:
- 推荐方法: 使用
update命令。它会智能地判断是否需要完全重建。python client.py update <你的模式名>
- 强制方法: 先删除旧模式,再重新创建。
python client.py unload <你的模式名> python client.py delete <你的模式名> python client.py create <你的模式名>
A: 这三个命令是模式生命周期的核心,理解它们的区别很重要:
create: 从无到有。它读取蓝图文件,处理/data目录的原始文档,构建全新的索引文件并保存在/storage目录中。这是一个耗时的“编译”过程。load: 从磁盘到内存。它读取/storage目录中已经存在的索引文件,将其加载到服务器的RAM和VRAM中,使其准备好接受查询。这是一个相对较快的“运行”过程。update: 智能同步。它对比蓝图文件中的配置和当前已加载模式的配置。如果只有权重等小参数变化,它会直接在内存中修改(热更新),速度很快;如果嵌入模型、分块大小等核心参数变化,它会自动执行完整的重建流程。这是用于迭代和调整模式的命令。
本项目的创建和开发过程深受开源社区的启发和帮助。在此,我希望向以下项目和开发者表达我诚挚的感谢:
- RAG_FLOW: 本系统在自然语言处理(NLP)的某些工具函数(位于 rag_system/nlp 目录)以及分词所需的静态资源(位于 rag_system/res 目录)方面,直接借鉴和使用了 RAG_FLOW 项目的优秀实现。RAG_FLOW 作为一个功能强大的、基于“工作流”的开源 RAG 平台,为本项目提供了宝贵的参考和灵感。没有他们的卓越工作,本项目的开发进程将会困难许多。
- FAISS, Sentence-Transformers, Unstructured.io 等开源库: 感谢这些优秀开源库的开发者们,是他们构建了强大而易用的工具,才使得构建这样一个复杂的 RAG 系统成为可能。