面包屑图标 当前位置: 首页
AI资讯
热点详情

RAGFlow架构详解与性能优化方案

AI热点日报
AI热点日报时间:2026-07-11
热点解读

RagFlow数据处理模块通过线程池并行解析多格式文档,利用解析器工厂动态选择解析器,支持中文分词;图片单独存储以减轻向量库负担。潜在瓶颈包括大文件内存占用高、缺乏解析缓存与增量更新、嵌入未异步化,可通过流式处理、增量更新及异步嵌入优化。

# RagFlow 核心架构深度解析:数据处理、检索与嵌入模块 RagFlow 作为一款领先的 RAG(检索增强生成)系统,其架构设计在数据处理、检索和嵌入方面具有高度的灵活性和可扩展性。本文旨在深入解析这些核心模块的职能、关键实现、潜在瓶颈及优化策略,帮助开发者更好地理解和运用 RagFlow,为性能优化和二次开发打下坚实基础。

数据处理模块

### 模块职能与流程 RagFlow 的数据处理模块负责将原始资料(文档、图片、音频等)加载并预处理成适合检索的文本“切片”。其核心流程包括四个主要阶段:
  • 数据加载:读取文件内容,支持多种格式的输入。
  • 解析分块:根据文件类型提取文本和相关信息,并按既定策略将内容切分成多个片段。
  • 向量化嵌入:对每个文本片段生成语义向量,使其具备可检索性。
  • 持久化存储:将片段及其对应的向量写入向量索引或数据库中。
这一完整流程主要由 DocumentService 等服务类配合各类型文件的解析器协同完成。

### 关键类与方法 DocumentService(位于 api/db/services/document_service.py)是数据处理流程的核心调度者。它利用解析器工厂根据文档类型动态选择合适的解析器,并对每个上传的文档进行并行处理。 DocumentService 维护一个解析器映射表(如 FACTORY),将文件的 parser_id 映射到具体解析器模块。例如,Word/PDF 使用通用文本解析,PPT 使用 presentation 解析,图片使用 picture 解析,音频使用 audio 解析等。
    FACTORY = {
            ParserType.PRESENTATION.value: presentation,
            ParserType.PICTURE.value: picture,
            ParserType.AUDIO.value: audio,
            ParserType.EMAIL.value: email
        }
        parser_config = {“chunk_token_num”: 4096, “delimiter”: “n!?;。;!?”, “layout_recognize”: “Plain Text”}
        exe = ThreadPoolExecutor(max_workers=12)
        threads = []
    ...
    for d, blob in files:
            kwargs = {
                “callback”: dummy,
                “parser_config”: parser_config,
                “from_page”: 0,
                “to_page”: 100000,
                “tenant_id”: kb.tenant_id,
                “lang”: kb.language
            }
            threads.append(exe.submit(FACTORY.get(d[“parser_id”], naive).chunk, d["name"], blob, **kwargs))
    每种解析器都需实现一个统一的 chunk 接口,负责将输入文件解析成若干带有元数据的文本块。例如,PPT 解析器会提取每页幻灯片的文本和缩略图,PDF 解析器则按页码拆分文本并可选地结合 OCR 提取扫描图片中的文字。
      if re.search(r“.pptx?$”, filename, re.IGNORECASE):
              ppt_parser = Ppt()
              for pn, (txt, img) in enumerate(ppt_parser(
                      filename if not binary else binary, from_page, 1000000, callback)):
                  d = copy.deepcopy(doc)
                  pn += from_page
                  d["image"] = img
                  d["page_num_int"] = [pn + 1]
                  d["top_int"] = [0]
                  d["position_int"] = [(pn + 1, 0, img.size[0], 0, img.size[1])]
                  tokenize(d, txt, eng)
                  res.append(d)
              return res
      elif re.search(r".pdf$", filename, re.IGNORECASE):
              pdf_parser = Pdf()
              if kwargs.get("layout_recognize", "DeepDOC") == "Plain Text":
                  pdf_parser = PlainParser()
              for pn, (txt, img) in enumerate(pdf_parser(filename, binary,
                                                             from_page=from_page, to_page=to_page, callback=callback)):
                  d = copy.deepcopy(doc)
                  pn += from_page
                  if img:
                      d["image"] = img
                  d["page_num_int"] = [pn + 1]
                  d["top_int"] = [0]
                  d["position_int"] = [(pn + 1, 0, img.size[0] if img else 0, 0, img.size[1] if img else 0)]
                  tokenize(d, txt, eng)
                  res.append(d)
              return res
      
      小提示: 在调用解析器前,DocumentService 会启动一个线程池(默认最多12线程)来并发执行多个文档的解析。每个线程调用相应解析器的 chunk 方法将文档切分成片段列表,并返回包含文本内容(以及可能的权重、图像等)的字典结构。对于文本型文档,解析器还会调用 rag_tokenizer.tokenize 进行中文分词细粒度标记,这些标记的元数据会随结果一起返回。 下列代码展示了解析结果的组装过程,其中 img_id 被用来引用存储于外部的图片二进制数据,以减少向量库的负担:
        for (docinfo, _), th in zip(files, threads):
                docs = []
                doc = {
                    "doc_id": docinfo["id"],
                    "kb_id": [kb.id]
                }
                for ck in th.result():
                    d = deepcopy(doc)
                    d.update(ck)
                    d["id"] = xxhash.xxh64((ck["content_with_weight"] + str(d["doc_id"])).encode("utf-8")).hexdigest()
                    d["create_time"] = str(datetime.now()).replace("T", " ")[:19]
                    d["create_timestamp_flt"] = datetime.now().timestamp()
                    if not d.get("image"):
                        docs.append(d)
                        continue
                    output_buffer = BytesIO()
                    if isinstance(d["image"], bytes):
                        output_buffer = BytesIO(d["image"])
                    else:
                        d["image"].sa ve(output_buffer, format='JPEG')
                    STORAGE_IMPL.put(kb.id, d["id"], output_buffer.getvalue())
                    d["img_id"] = "{}-{}".format(kb.id, d["id"])
                    d.pop("image", None)
                    docs.append(d)
          class StorageFactory:
              storage_mapping = {
                  Storage.MINIO: RAGFlowMinio,
                  Storage.AZURE_SPN: RAGFlowAzureSpnBlob,
                  Storage.AZURE_SAS: RAGFlowAzureSasBlob,
                  Storage.AWS_S3: RAGFlowS3,
                  Storage.OSS: RAGFlowOSS,
              }
              @classmethod
              def create(cls, storage: Storage):
                  return cls.storage_mapping[storage]()
          
          STORAGE_IMPL_TYPE = os.getenv('STORAGE_IMPL', 'MINIO')
          STORAGE_IMPL = StorageFactory.create(Storage[STORAGE_IMPL_TYPE])
          常见问题:
          • 问:如何理解解析输出的内容? 答:解析器的 chunk 方法输出一个列表,列表中的每个元素都是一个字典,代表一个文本片段。这个字典包含了文本内容(content_with_weight)、所属文档 ID、知识库 ID、自身片段 ID、页面号、位置信息等元数据。图片数据会被单独存储(如 MINIO),只在字典中保留 img_id 作为引用,从而优化向量库的存储。
          • 问:为什么要使用线程池并行解析文档? 答:由于不同文档的解析过程相互独立,使用线程池可以同时处理多个文件,显著缩短批量处理时间。默认的 12 个线程设置是一个平衡点,但可以根据服务器的 CPU 核心数量进行调整。
          ### 潜在瓶颈与优化建议 当前数据处理流程虽然通过线程池并行解析多个文档,但仍存在潜在的性能瓶颈和可改进空间。
          • 大文件处理开销高: 对于超大文件(如含成千上万页的 PDF),单个解析线程可能长时间占用,并一次性产生大量片段数据存入内存。代码中会等待解析完成后再批量处理嵌入和存储,这可能导致内存占用峰值较高。

            • 优化建议: 可引入流式处理或分段加载机制,分批解析和处理文档。例如,将超长文档按章节分页解析,每生成一定数量片段就立刻进行嵌入和写入,减少单批内存占用。也可考虑在解析阶段对文本进行摘要或过滤无关信息,减小后续处理量。
          • 缺乏解析缓存和增量更新: 每次新增或修改文档都会重新解析全文。DocumentService 目前通过在入库前删除原有片段实现更新,没有细粒度的增量更新机制。

            • 优化建议: 对于未变化的文档内容可跳过重复解析和嵌入。可以维护文件内容的哈希或指纹,如果文档再次上传且内容未变则复用已有向量。对于部分更新的文档,可对比新旧内容的差异,仅解析变化部分并更新对应片段。
            @classmethod
            @DB.connection_context()
            def remove_document(cls, doc, tenant_id):
                    cls.clear_chunk_num(doc.id)
                    try:
                        settings.docStoreConn.delete({"doc_id": doc.id}, search.index_name(tenant_id), doc.kb_id)
                        graph_source = settings.docStoreConn.getFields(
                            settings.docStoreConn.search(["source_id"], [], {"kb_id": doc.kb_id, "knowledge_graph_kwd": ["graph"]}, [], OrderByExpr(), 0, 1, search.index_name(tenant_id), [doc.kb_id]), ["source_id"]
                        )
                        if len(graph_source) > 0 and doc.id in list(graph_source.values())[0]["source_id"]:
                            settings.docStoreConn.update({"kb_id": doc.kb_id, "knowledge_graph_kwd": ["entity", "relation", "graph", "subgraph", "community_report"], "source_id": doc.id},
                                                        {"remove": {"source_id": doc.id}},
                                                        search.index_name(tenant_id), doc.kb_id)
                            settings.docStoreConn.update({"kb_id": doc.kb_id, "knowledge_graph_kwd": ["graph"]},
                                                        {"removed_kwd": "Y"},
                                                        search.index_name(tenant_id), doc.kb_id)
                            settings.docStoreConn.delete({"kb_id": doc.kb_id, "knowledge_graph_kwd": ["entity", "relation", "graph", "subgraph", "community_report"], "must_not": {"exists": "source_id"}},
                                                        search.index_name(tenant_id), doc.kb_id)
                    except Exception:
                        pass
                    return cls.delete_by_id(doc.id)
          • 并行度与异步处理: 代码使用线程池并行解析,但嵌入计算仍在主线程同步执行:线程池收集所有文档片段后,依次对每个文档调用嵌入模型编码。若解析速度远快于嵌入,则 CPU 可能空闲等待 GPU 或外部 API 响应。

            • 优化建议: 引入异步任务队列多线程/多进程并发嵌入。例如,可在解析线程完成一个文档的切片后立即异步提交嵌入任务,而不是等所有解析结束;或者利用多个 GPU/进程并行处理不同文档的向量生成,从而提高整体吞吐。还可以考虑批量嵌入的异步 I/O,例如使用 asyncio 批发调用 OpenAI 等服务接口,加速远程嵌入 API 调用。
            def embedding(doc_id, cnts, batch_size=16):
                    nonlocal embd_mdl, chunk_counts, token_counts
                    vects = []
                    for i in range(0, len(cnts), batch_size):
                        vts, c = embd_mdl.encode(cnts[i: i + batch_size])
                        vects.extend(vts.tolist())
                        chunk_counts[doc_id] += len(cnts[i:i + batch_size])
                        token_counts[doc_id] += c
                    return vects
          • 解析器性能与定制: RagFlow 支持丰富格式,但解析依赖第三方库(如 Aspose 用于 PPT, PyPDF 用于 PDF, OCR 库用于图片)可能成为瓶颈。例如,大 PDF 的 OCR 耗时很长。

            • 优化建议: 针对常用格式引入更高效的解析工具或并行 OCR 方案;提供解析参数调优(如是否提取图片、OCR 精度等)以在性能和精度间平衡。对于企业场景,可允许用户预先转换好部分数据格式,减少在线解析工作量。高精度场景可以提供多种方法及模型等配置选项,如 MinerU 等。
          完成解析和嵌入后,DocumentService 通过 docStoreConn 接口将片段数据持久化到向量存储。它先检查或创建索引结构,然后按批次(默认每批 64 条)插入片段。每个片段包含文本、元数据以及生成的向量(字段名形如 q__vec)。同时,DocumentService 会更新关系型数据库中的文档记录(如总切片数、总 Token 数等)以供监控。整个数据入库流程确保了解析 -> 嵌入 -> 存储的顺序执行,并在出现错误时回滚或记录异常。
            for i, d in enumerate(cks):
                v = vects[i]
                d["q_%d_vec" % len(v)] = v
            for b in range(0, len(cks), es_bulk_size=64):
                if try_create_idx:
                    if not settings.docStoreConn.indexExist(idxnm, kb_id):
                        settings.docStoreConn.createIdx(idxnm, kb_id, len(vects[0]))
                    try_create_idx = False
                settings.docStoreConn.insert(cks[b:b + es_bulk_size], idxnm, kb_id)

            Retriever 模块

            ### 结构设计 RagFlow 的检索模块采用面向接口的设计,屏蔽了不同向量库实现的差异。其核心抽象为 DocStoreConnection(位于 rag/utils/doc_store_conn.py),以及基于它构建的 search.Dealer 检索器(位于 rag/nlp/search.py)。系统在初始化时根据配置的后端向量引擎类型(DOC_ENGINE)创建相应的 DocStoreConnection 实例,并赋给全局的 settings.docStoreConn
              global DOC_ENGINE, docStoreConn, retrievaler, kg_retrievaler
              DOC_ENGINE = os.environ.get("DOC_ENGINE", "elasticsearch")
              
              lower_case_doc_engine = DOC_ENGINE.lower()
              if lower_case_doc_engine == "elasticsearch":
                      docStoreConn = rag.utils.es_conn.ESConnection()
              elif lower_case_doc_engine == "infinity":
                      docStoreConn = rag.utils.infinity_conn.InfinityConnection()
              elif lower_case_doc_engine == "opensearch":
                      docStoreConn = rag.utils.opensearch_coon.OSConnection()
              else:
                      raise Exception(f"Not supported doc engine: {DOC_ENGINE}")
              
              retrievaler = search.Dealer(docStoreConn)
              kg_retrievaler = kg_search.KGSearch(docStoreConn)
              支持的引擎包括:Elasticsearch(利用其内置稠密向量索引)、OpenSearch(偏传统搜索引擎的向量支持),以及 InfiniFlow 团队自研的“Infinity”引擎。每种引擎在 rag/utils 目录下有对应实现类(如 ESConnectionOSConnectionInfinityConnection),它们都提供统一的方法,例如:createIdx 创建索引、insert 插入文档、search 执行搜索、getTotal 获取结果总数等。这种设计的优势在于,更换底层向量库(如从 Elasticsearch 换成 Milvus 或 FAISS)对上层检索流程的影响很小,只需实现新的 DocStoreConnection 子类并配置启用即可。 常见问题:
              • 问:如何增加新的向量库类型? 答:尽管社区中还有 FAISS、Wea viate、Milvus 等向量库,当前源码主要直接支持上述三种后端。但 RagFlow 架构上具备扩展能力:开发者可以参考现有 DocStoreConnection 接口封装新的后端(例如利用 FAISS 在本地内存建立索引,或通过 Wea viate/Milvus 的 Python 客户端发送查询)。引入这些向量库有望在纯向量检索性能和分布式扩展上提供优势。
              ### 检索策略 RagFlow 的 Retriever 模块采用多路召回融合的策略,即同时执行稠密向量检索和稀疏关键字检索,并融合结果进行重排序。在 search.Dealer.search 方法中,这一逻辑清晰可见:首先,对用户查询进行处理,生成语义向量查询文本关键词查询
                def get_vector(self, txt, emb_mdl, topk=10, similarity=0.1):
                        qv, _ = emb_mdl.encode_queries(txt)
                        shape = np.array(qv).shape
                        if len(shape) > 1:
                            raise Exception(
                                f"Dealer.get_vector returned array's shape {shape} doesn't match expectation(exact one dimension).")
                        embedding_data = [get_float(v) for v in qv]
                        vector_column_name = f"q_{len(embedding_data)}_vec"
                        return MatchDenseExpr(vector_column_name, embedding_data, 'float', 'cosine', topk, {"similarity": similarity})
                语义向量查询通过 Embedding 模块将用户问题编码成向量(使用 emb_mdl.encode_queries 得到查询向量);关键词查询则使用 FulltextQueryer 将问题转换为搜索式(提取关键词并设定匹配度)。接着,Dealer 构造一个融合查询,将稠密向量匹配(MatchDenseExpr)和稀疏文本匹配(MatchTextExpr)组合(加权融合则通过 FusionExpr 实现)。最终通过 docStoreConn.search 接口提交给底层引擎执行。
                  matchText, keywords = self.qryr.question(qst, min_match=0.3)
                  if emb_mdl is None:
                      matchExprs = [matchText]
                      res = self.dataStore.search(src, highlightFields, filters, matchExprs, orderBy, offset, limit,
                                                  idx_names, kb_ids, rank_feature=rank_feature)
                      total = self.dataStore.getTotal(res)
                      logging.debug("Dealer.search TOTAL: {}".format(total))
                  else:
                      matchDense = self.get_vector(qst, emb_mdl, topk, req.get("similarity", 0.1))
                      q_vec = matchDense.embedding_data
                      src.append(f"q_{len(q_vec)}_vec")
                      fusionExpr = FusionExpr("weighted_sum", topk, {"weights": "0.05, 0.95"})
                      matchExprs = [matchText, matchDense, fusionExpr]
                      res = self.dataStore.search(src, highlightFields, filters, matchExprs, orderBy, offset, limit,
                                                  idx_names, kb_ids, rank_feature=rank_feature)
                      total = self.dataStore.getTotal(res)
                      logging.debug("Dealer.search TOTAL: {}".format(total))
                      # If result is empty, try again with lower min_match
                      if total == 0:
                          if filters.get("doc_id"):
                              res = self.dataStore.search(src, [], filters, [], orderBy, offset, limit, idx_names, kb_ids)
                              total = self.dataStore.getTotal(res)
                          else:
                              matchText, _ = self.qryr.question(qst, min_match=0.1)
                              filters.pop("doc_id", None)
                              matchDense.extra_options["similarity"] = 0.17
                              res = self.dataStore.search(src, highlightFields, filters, [matchText, matchDense, fusionExpr],
                                                          orderBy, offset, limit, idx_names, kb_ids, rank_feature=rank_feature)
                              total = self.dataStore.getTotal(res)
                              logging.debug("Dealer.search 2 TOTAL: {}".format(total))
                  小提示: 当初次检索结果为空或不足时,系统会降低文本匹配阈值(min_match)并再次发起结合关键词匹配matchText)+ 向量匹配matchDense)+ 融合表达式fusionExpr)的检索,以最大限度召回相关片段。这种设计保证了高召回率。 除了 dense/sparse 双检索外,RagFlow 还支持知识图谱检索(KGSearch)等多模态场景,代码中 KGSearch(docStoreConn) 表明针对结构化知识使用知识图谱检索器。但就文本 QA 场景而言,当前主要是稠密与稀疏信息的互补。这样设计的好处是:召回率更高且结果有解释性。稠密向量确保语义相关但可能引入模糊匹配,稀疏关键词确保精确匹配但可能漏掉语义改述,通过融合可以综合两者优点。同时,Dealer 在返回结果时也提供了高亮和聚合等信息用于结果呈现。
                    class SearchResult:
                            total: int
                            ids: list[str]
                            query_vector: list[float] | None = None
                            field: dict | None = None
                            highlight: dict | None = None
                            aggregation: list | dict | None = None
                            keywords: list[str] | None = None
                            group_docs: list[list] | None = None
                    检索结果返回后,RagFlow 还可以执行重排序(rerank)操作。代码中预留了 RERANK_MDL 配置以及 rag/llm/rerank_model.py 模块用于加载例如 BCE、BGE 这样的 cross-encoder 重排序模型。在问答流程中,当 Retriever 找回 N 条候选片段后,调用 similarity(query, texts) 对片段与原问句打分,再重新排序以提升相关性。这种两阶段检索在文档中被称为“融合重排序”,可有效提升最终答案依据的准确性。 ### 潜在问题与优化建议
                    • 召回率不稳定: 稠密检索依赖向量质量,若嵌入模型未涵盖领域知识,可能漏掉相关内容;稀疏检索则受限于文本关键字匹配。融合后效果一般较单一路径好,但仍需调优参数(如向量与文本匹配的相对权重、min_match 阈值等)。目前代码里 similarity 下限等参数硬编码(如再次检索时将 matchDense 阈值设 0.17),这些可能需要根据实际数据调整。

                      • 改进建议: 引入基于训练的数据驱动方法优化融合策略,例如学习一个小模型来判断向量匹配结果是否可靠,从而动态调整 dense vs. sparse 权重。此外可以增加对查询意图的判断:当用户问句包含具体实体/关键词时,加强 BM25 权重;当问句偏概念模糊时,更多依赖向量召回。
                    • 检索速度较慢: 使用 ElasticSearch/OpenSearch 进行大规模向量检索,在向量维度高和数据量大时性能可能不如专门的向量数据库(如 Milvus、FAISS)。Infinity 引擎采用 HNSW 提升了稠密检索速度,但如果知识库规模达到千万级,单机 HNSW 内存和查询延迟都会增长。

                      • 优化方案: 引入更高性能的向量索引:例如将 Faiss 纳入后端,通过 GPU 加速近似最近邻搜索;或集成 Milvus 等支持分布式分片,以水平扩展大数据量下的查询。另外,可对现有 HNSW 参数调优(如 M、ef 值)或启用压缩降低内存占用。对于稀疏检索部分,可提前对文本做关键词过滤索引精简(如移除停用词)提升匹配效率。
                    • 多 GPU/分布式支持不足: 当前检索流程主要在单实例完成,并未充分利用多 GPU 并行。Infinity 等向量库如果部署在多节点,需要协调多服务器的检索结果合并,这部分框架暂未体现。另外,在本地调用 Faiss 时,可以利用多 GPU 进行 IndexShard 或数据并行搜索,但目前架构未涵盖。

                      • 改进建议: 提供分布式检索模式的支持,例如通过 gRPC 调用远程向量引擎集群;在应用层对大索引做分片并行检索,然后融合结果。此外,如上所述允许 embedding 环节利用多 GPU 生成查询向量,也能稍微减轻单卡压力。值得注意的是,多 GPU 更多地会用在嵌入阶段,检索阶段更多是 CPU/内存瓶颈,因此提升检索并发能力和索引优化更为重要。
                    常见问题:
                    • 问:如何调整稠密向量与稀疏文本匹配的融合权重? 答:在 FusionExpr 中,weights 参数控制了融合比例。例如 {"weights": "0.05, 0.95"} 表示文本匹配权重为 0.05,向量匹配权重为 0.95。你可以根据知识库特性和业务需求调整这两个值,以达到最优的检索效果。通常建议在测试集上进行 A/B 测试来确定最佳权重。
                    • 问:检索结果为空时,系统会如何处理? 答:如上文代码所示,当首次检索结果为空时,系统会尝试降低 min_match 阈值(从 0.3 降到 0.1),并放宽向量相似度门槛(从 0.1 降到 0.17),然后使用更低的标准重新发起检索,尽力找到相关片段。如果仍无结果,则可能是知识库中确实缺乏相关内容。
                    Retriever 模块通过接口封装实现对不同向量存储的支持,采用稠密+稀疏融合提升召回和准确率,搭配可选的重排序模型以进一步优化结果排序。为让检索模块更为健壮,后续可以:扩展支持更丰富的后端(Faiss/Milvus 等)以提高性能和部署灵活性;引入Agent 式检索调优(例如动态调整查询或多跳检索);加强并行与分布式能力,确保在大规模知识库下也能快速稳定地返回结果。

                    嵌入(Embedding)模块

                    ### 支持的嵌入模型 RagFlow 的嵌入模块设计非常灵活,集成了多种来源的 Embedding 模型,包括本地和云端模型。例如:
                    • 本地向量模型: 默认使用开源的通用文本嵌入模型,代码中 DefaultEmbedding 会通过 FlagEmbedding 库加载指定名称的模型权重(默认 BAAI/bge-large-zh-v1.5),并将其缓存在本地。此外还有 FastEmbed 实现,使用轻量模型(如 bge-small-en-v1.5)及优化推理库以提升嵌入计算速度。另一个本地模型是 YoudaoEmbed,集成了网易有道的中英双语 Embedding 模型。这些本地模型在初始化时如果未缓存,会自动从 HuggingFace Hub 下载权重。
                    • OpenAI 等云嵌入服务: 模块提供 OpenAIEmbed 类封装 OpenAI 的文本嵌入 API(默认模型 text-embedding-ada-002)。AzureEmbed 支持 Azure OpenAI 服务。国内云服务也有:如 BaiChuanEmbed 对接百川智能的 Embedding API,QWenEmbed 通过阿里达摩院提供的 DashScope 接口获取其 Embedding 模型(如通义千问 embedding),ZhipuEmbed 对接智谱 AI 的 Embedding 接口。还有 JinaEmbed 用于调用 Jina AI 提供的向量服务接口。这些云服务类通常封装 HTTP 请求调用,并解析返回的 embedding 向量。例如 JinaEmbed 中直接以 requests.post 发送 JSON 请求并获取结果。
                    • 本地部署的 Embedding 服务: 模块也考虑了用户自托管的大模型服务,如 LocalAIEmbed 用于对接本地的开源 Embedding API(OpenAI 兼容接口),OllamaEmbed 用于调用本地的 Ollama 引擎生成 embedding(支持一些本地 LLM),XinferenceEmbed 对接 XInference 服务等。这些类通常复用 OpenAI 接口逻辑但将 base_url 指向本地服务器地址,从而利用本地算力生成向量。
                    Embedding 模块通过不同子类囊括了主流的嵌入方案:既支持开源模型离线推理,也支持各大厂商在线服务。配置上,系统允许用户设置 EMBEDDING_MDL,选择默认的 embedding 模型。例如默认 embedding 模型列表里包括 BGE-largeYoudao BCE。根据部署模式(LIGHTEN 配置)可以决定是否加载大模型权重还是调用轻量服务。
                      BUILTIN_EMBEDDING_MODELS = ["BAAI/bge-large-zh-v1.5@BAAI", "maidalun1020/bce-embedding-base_v1@Youdao"]
                      ### Token限制与批处理策略 由于不同模型对输入文本长度有限制,代码对长文本都做了截断处理,避免超过模型支持的 token 数。比如 OpenAI 的 ada-002 模型最多支持 8191 个 token,OpenAIEmbed.encode 对每段文本截断至 8191 长度;智谱 embedding-3 模型上限 3072 token,代码中据此截断输入。本地 FlagEmbedding 模型也将文本裁剪在 2048 字符左右以控制计算量。这种截断保证了对任意长文段都能安全编码,但也可能丢失部分信息。优化上,未来可考虑对超长文本分段嵌入(如对长段落进一步切分取均值)或者引入长上下文 embedding 模型。
                        class ZhipuEmbed(Base):
                            def __init__(self, key, model_name="embedding-2", **kwargs):
                                self.client = ZhipuAI(api_key=key)
                                self.model_name = model_name
                        
                            def encode(self, texts: list):
                                arr = []
                                tks_num = 0
                                MAX_LEN = -1
                                if self.model_name.lower() == "embedding-2":
                                    MAX_LEN = 512
                                if self.model_name.lower() == "embedding-3":
                                    MAX_LEN = 3072
                                if MAX_LEN > 0:
                                    texts = [truncate(t, MAX_LEN) for t in texts]
                        
                                for txt in texts:
                                    res = self.client.embeddings.create(input=txt,
                                                                        model=self.model_name)
                                    arr.append(res.data[0].embedding)
                                    tks_num += self.total_token_count(res)
                                return np.array(arr), tks_num
                        
                            def encode_queries(self, text):
                                res = self.client.embeddings.create(input=text,
                                                                    model=self.model_name)
                                return np.array(res.data[0].embedding), self.total_token_count(res)
                        在批处理方面,多数实现将请求按批大小 16 拆分,以兼顾效率和 API 限制。例如 OpenAI 每次请求最多 16 条输入,代码中显式设置 batch_size=16;本地模型也通常 16 条一批以充分利用矩阵并行。也有特殊情况,如 Qwen 的 DashScope 接口批量能力较弱,仅用 batch_size=4。批处理逻辑多采用简单的 for 循环累积结果。例如对一组文本列表,按 16 条一组调用底层模型获取 embedding,并将结果扩展成一个 numpy 数组返回。每批次调用后,有的实现如 OpenAI 会累加已处理的 token 数方便后续记录。另外,有些会提供 encode_queries 专门处理单条查询的情况,有的则会直接调用 encode([text]) 简化实现。总的来看,当前批处理策略较为保守,没有动态调整批大小或并行发出多个批次请求。
                          class LocalAIEmbed(Base):
                              def __init__(self, key, model_name, base_url):
                                  if not base_url:
                                      raise ValueError("Local embedding model url cannot be None")
                                  if base_url.split("/")[-1] != "v1":
                                      base_url = os.path.join(base_url, "v1")
                                  self.client = OpenAI(api_key="empty", base_url=base_url)
                                  self.model_name = model_name.split("___")[0]
                          
                              def encode(self, texts: list):
                                  batch_size = 16
                                  ress = []
                                  for i in range(0, len(texts), batch_size):
                                      res = self.client.embeddings.create(input=texts[i:i + batch_size], model=self.model_name)
                                      ress.extend([d.embedding for d in res.data])
                                  # local embedding for LmStudio donot count tokens
                                  return np.array(ress), 1024
                          
                              def encode_queries(self, text):
                                  embds, cnt = self.encode([text])
                                  return np.array(embds[0]), cnt
                          小提示: 在批处理方面,多数实现将请求按批大小 16 拆分,以兼顾效率和 API 限制。例如 OpenAI 每次请求最多 16 条输入,代码中显式设置 batch_size=16;本地模型也通常 16 条一批以充分利用矩阵并行。 ### 多线程/并发实现 在 Embedding 模块内部,各模型的 encode 实现大多是同步的,并未显式使用多线程。但 RagFlow 利用上层流程并发处理不同文档的嵌入来提高吞吐:DocumentService 中对不同文档的 chunk 是并行的,多个文档的向量生成可以借助线程池同时进行(受限于 GPU 算力)。不过对于单个大文档,其所有 chunk 向量还是在单线程顺序计算。目前也没有看到使用 GPU 多卡并行计算同一批次 embedding 的机制。而且 DefaultEmbedding 类初始化时将环境变量 CUDA_VISIBLE_DEVICES 强制设为 0锁定使用第一块 GPU,这在多 GPU 机器上可能造成闲置。 改进建议: 考虑引入多线程/异步的编码方式。例如,对于需要调用外部 API(OpenAI 等),可以使用 asyncio.gather 同时发出多个请求批次,充分利用网络带宽;对于本地模型,如 PyTorch 的 embedding 模型,可采用多线程批次推理或 OpenMP 并行(前提是模型计算瓶颈在 CPU)。此外,可以让用户配置使用哪些 GPU 设备,或在初始化时检测多 GPU 并对模型和数据进行切分,做到多卡并行嵌入(例如 FlagEmbedding 模型实例化多份各占一卡,分摊批次)。这些措施对大规模文档批量入库时的嵌入吞吐将有明显提升。 ### 效率和可维护性评估 RagFlow 的 Embedding 模块代码虽长但结构清晰:使用抽象基类 Base 定义接口,然后为不同服务商/模型各建一个子类实现。这种方式在增加新模型支持时需要修改代码增添类,但也提供了高度的控制力。目前来看,可维护性方面,每个子类基本独立,新增其它平台支持(例如 TogetherAI、PerfX 等,在代码中已经有一些雏形)很方便。如果后续模型种类继续增加,采用插件机制或配置驱动会更灵活,例如通过配置文件列出可用的 embedding 后端及参数,运行时反射加载对应实现,避免主代码不断膨胀。 在大规模文档嵌入情境下,当前结构可能暴露一些效率瓶颈:如所有文档 embedding 共用单一线程顺序执行,难以充分利用多核;本地大型模型(BGE large)占用显存大且推理速度相对慢,批量处理时 GPU 成为瓶颈。如果要提升吞吐,可考虑以下建议:
                          • 批量并行: 对于上万段文本的嵌入,可将其划分给多个进程/线程分别处理,比如启动多个 EmbeddingWorker 进程,各自加载模型处理一部分数据,然后合并结果。这在多核 CPU 或多 GPU 场景下尤为有效。也可使用向量化工具(如 NumPy 并行、PyTorch DataLoader)对 CPU 计算部分提速。
                          • 模型压缩和加速: 针对大模型嵌入的性能问题,可引入蒸馏、小模型(如使用 small 版 BGE 或其他轻量模型)在对性能要求高时使用;或者使用 OpenVINO、TensorRT 等对 embedding 模型做推理加速。FastEmbed 类就是一个采用优化库的例子,其调用的 fastembed.TextEmbedding 内部可能利用 C++ 实现提升计算速度。进一步的优化可以考虑量化 embedding 模型的权重,用 FP16 甚至 INT8 以换取更高速度和更低内存占用。
                          • 流水线并行: 将文档解析->嵌入->存储三步解耦成流水线,不必串行等待。比如在一个文档解析出部分 chunk 后,就立刻交给嵌入线程处理,同时解析线程继续处理下一个部分,从而解析和嵌入两个阶段重叠执行,提高硬件利用率。需要在架构上实现任务调度和缓冲队列,防止某一步过慢拖累整体。
                          常见问题:
                          • 问:如何选择合适的嵌入模型? 答:选择嵌入模型通常取决于几个关键因素:是否支持你的语言(如中文、英文或双语)、对向量维度的要求(高精度场景可能需要高维向量)、推理速度与成本(本地推理 vs. 云端 API 调用)。RagFlow 提供了丰富的选项,建议先用默认的 BGE-large-zh-v1.5 进行测试,如果性能和准确性满足要求则无需更换。如果需要更快的处理速度或更低的资源消耗,可以尝试 FastEmbed 或小模型。
                          • 问:遇到 Token 长度限制怎么办? 答:代码已经对长文本做了截断处理,确保不会超出模型支持的最大长度。但如果截断导致关键信息丢失,可以考虑:1) 在解析阶段将文档切分成更短的片段;2) 使用支持更长上下文的嵌入模型(如 OpenAI 的 text-embedding-3-large 支持 8191 tokens);3) 对超长段落进行分段嵌入后取均值作为该段落的最终向量。
                          • 问:如何加速本地嵌入模型的推理? 答:可以通过多种方式加速:使用 FastEmbed 类(它内置了优化);量化模型(如使用 FP16 或 INT8);启用更大的批处理大小(batch_size);如果有多张 GPU,可以考虑将模型复制到多卡上并行处理。另外,使用更小的模型(如 bge-small 而不是 bge-large)也能显著提升推理速度。
                          RagFlow 的 Embedding 模块具有灵活易扩展的优点。在后续扩展中,可以引入配置化和插件化降低新增模型的代码改动,并通过并行优化模型加速方案,使大规模文档嵌入过程更高效。随着开源社区新的 embedding 模型出现(如多模态 embedding,将图片、音频也编码到同一向量空间),RagFlow 也可以通过类似架构无缝接入,从而增强系统在多模态检索上的能力。 --- **模块依赖关系概览:** 数据处理模块负责将原始数据转化为向量库可用的结构,内部依赖解析器(如 DeepDoc 等库)提取文本,并调用 Embedding 模块完成向量生成;Embedding 模块对接外部模型或服务,将文本转为语义向量;生成的文本片段和向量通过 DocStore 接口持久化到检索引擎。检索查询发生时,Retriever 模块调用 Embedding 模块将用户查询转为向量,并结合关键词匹配通过 DocStore 接口检索相关片段;Retriever 返回的结果可再送入重排序模型(二次调用 Embedding 或独立的 Rerank 模型)以优化顺序,最终将结果提供给上层问答流程使用。因此,数据处理 -> 向量存储 <- 检索查询这一闭环确保了知识库内容与用户问题在同一向量空间匹配。 各模块解耦明确:解析与嵌入模块以配置和接口衔接,检索模块通过统一接口适配不同存储,实现了较好的模块化和可扩展性。今后在此架构上进行二次开发和性能优化,只需针对瓶颈模块局部改进,而不影响整体流程。各改进方案如上所述,将有助于 RagFlow 更高效地处理海量异构数据,服务大规模问答应用。
                          热点追踪提示词
                          你是一名 AI 行业编辑,请围绕下面这条热点输出一份资讯解读:
                          热点:RAGFlow架构详解与性能优化方案要求:
                          1. 先用一句话解释这条热点在讲什么
                          2. 再总结它为什么重要
                          3. 说明会影响哪些 AI 产品或内容方向
                          4. 最后给出 3 个适合资讯站使用的标题
                          来源:https://www.53ai.com/news/RAG/2025052042651.html
                          ai 人工智能

                          游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

                          相关热点
                          AI热点2026-07-11 20:22
                          Quickie AI 智能快捷方式,快速完成任务

                          QuickieAI是一款轻量级Chrome插件,核心逻辑是快速准确。在写作、社交媒体运营、营销活动场景中,一键完成内容研究与文案生成,支持侧边栏即时响应。可辅助写作、生成多平台文案及营销话术,擅长冷启动,最终质量需人工微调。

                          AI热点2026-07-11 20:22
                          快手开源模型可图Kolors 支持图像内生成文字

                          快手开源图像生成模型可图Kolors,采用GLM文本编码器支持中英文双语理解,可处理256token长文本。基于数十亿文本图像对训练,专门优化中国文化元素,能稳定生成中文文字,英文文字偶有缺漏。

                          AI热点2026-07-11 20:22
                          Hebbia AI智能搜索引擎 金融法律政府制药行业知识工作助手

                          Hebbia是一款专为金融、法律、政府和制药等行业设计的AI搜索引擎,其Matrix产品通过拆解复杂文档实现自动化信息提取与分析,已获3000万美元融资。它适用于研究人员、商业人士及学生,提供全面、精准的检索与洞察。

                          AI热点2026-07-11 20:21
                          Penf1 AI驱动的博客写作工具

                          Penf1是一款AI驱动的博客创作工具,专注于简化从构思到发布的全流程。它支持即时内容生成、创意破题和SEO优化,帮助创作者快速获得高质量初稿,从而将精力集中于打磨观点和注入个人风格。

                          延伸阅读