RAGFlow Intro Part 1 — 全景架構 — 從一份 PDF 到一句帶引用的答案

大多數人讀 RAG 開源專案的方式,是打開 README,跑 docker compose up,上傳一份 PDF,看到答案出來就說「我懂了」。 真正的答案是:一個生產級 RAG 引擎有 80% 的複雜度不在「呼叫 LLM」那一行,而在資料怎麼被解析、怎麼被切、怎麼被編碼、怎麼被存、怎麼被取回、以及取回之後怎麼證明它沒有胡說。 Demo 只需要 200 行。引擎需要一整套子系統。 這個系列拆解的是後者。


前言:這個系列要做什麼

RAGFlow (InfiniFlow,Apache-2.0,2023-12-12 開源)是目前最受關注的開源 RAG 引擎之一:GitHub 上約 9.0 萬顆星、1.06 萬 fork,官方定位是「a leading open-source RAG engine that fuses cutting-edge RAG with Agent capabilities to create a superior context layer for LLMs」。

它值得逐層讀完,理由不是星星數,而是:它把 RAG 每一個環節都實作成可替換的元件,而且每個選擇背後都有明確的理由。 讀它等於讀一份「RAG 系統設計的參考答案」。

這個系列分成五篇:

Part主題對應原始碼
Part 1(本篇)全景架構、資料的兩條路徑、部署形態docker/conf/、整體
Part 2資料進場:DeepDoc 解析與 Chunking 策略deepdoc/rag/app/rag/nlp/
Part 3Encode 與 Save:向量化、索引 Schema、雙引擎抽象conf/mapping.jsonrag/utils/*_conn.py
Part 4Decode 與檢索:混合搜尋、Rerank、GraphRAG/RAPTOR、引用rag/nlp/search.pyrag/nlp/query.py
Part 5系統與程式碼結構:服務分層、Task Executor、Go 遷移api/rag/svr/internal/

本篇的目標很單純:讀完之後,你能在腦中畫出 RAGFlow 的方塊圖,並且知道每個方塊的名字對應到哪個目錄。


一、核心問題:naive RAG 在哪裡壞掉

先講清楚 RAGFlow 存在的理由。一個「教學版 RAG」長這樣:

PDF ──pdfminer──▶ 純文字 ──每 512 token 切一刀──▶ embedding ──▶ 向量庫
                                                                    │
問題 ──embedding──▶ top-5 餘弦相似 ◀──────────────────────────────────┘
                        │
                        ▼
                   丟給 LLM 生成

這條管線在 demo 資料上可以跑,在真實企業資料上會在四個地方同時壞掉:

壞點一:解析階段就已經失去資訊。 掃描版 PDF 出來是空字串;有跨頁表格的財報,欄位會全部混成一行;雙欄排版的論文,左右兩欄的句子會交錯黏在一起。後面所有環節都在處理一份已經壞掉的文字。

壞點二:固定長度切分無視語意邊界。 512 token 一刀切,會把「問題」和「答案」切開,把表頭和資料列切開,把條款編號和條款內容切開。切壞的 chunk 無論用多好的 embedding 都救不回來。

壞點三:單一向量召回沒有精確匹配能力。 使用者查「型號 RTX-4090D 的功耗」,向量模型可能覺得「RTX-4080」也很像。專有名詞、料號、法條編號這類查詢,需要的是關鍵字精確匹配,不是語意近似。

壞點四:無法證明答案的來源。 使用者問「這個數字哪來的?」,系統答不出來。在合規、法務、財務場景,不能溯源等於不能用。

RAGFlow 的四個核心主張,正好一一對應:

RAGFlow 主張對應壞點實作位置
Deep document understanding — 用視覺模型理解版面,而不是只抽文字壞點一deepdoc/vision/
Template-based chunking — 依文件類型選切分模板,且切分結果可視化、可人工介入壞點二rag/app/(14 種模板)
Multiple recall + fused re-ranking — 關鍵字與向量雙路召回後融合重排壞點三rag/nlp/search.py
Grounded citations — 答案的每一句話回頭對齊到來源 chunk壞點四Dealer.insert_citations()

README 把前兩點總結成一句話:“Quality in, quality out.” 這句話是整個專案的設計主軸——與其在檢索端補救,不如在入場端就不要弄壞資料。


二、全景圖:七個子系統

以下是 RAGFlow 的方塊圖。每個方塊我都標了對應的原始碼目錄,之後四篇會逐個拆開。

                          ┌──────────────────────────────┐
                          │  Web UI (React + TS + Vite)  │
                          │            web/              │
                          └───────────────┬──────────────┘
                                          │ HTTP (nginx :80/:443)
        ┌─────────────────────────────────┼─────────────────────────────────┐
        │                                 ▼                                 │
        │  ┌────────────────┐   ┌──────────────────┐   ┌─────────────────┐  │
        │  │  API Server    │   │  Admin Server    │   │  MCP Server     │  │
        │  │  api/  :9380   │   │  admin/  :9381   │   │  mcp/    :9382  │  │
        │  └───────┬────────┘   └──────────────────┘   └─────────────────┘  │
        │          │  ① 寫 task 到佇列                                       │
        │          ▼                                                        │
        │  ┌─────────────────────────────────────────────────────────────┐  │
        │  │  Redis Stream 佇列 "te" / group "rag_flow_svr_task_broker"  │  │
        │  └───────┬─────────────────────────────────────────────────────┘  │
        │          │  ② N 個 consumer 搶任務                                 │
        │          ▼                                                        │
        │  ┌─────────────────────────────────────────────────────────────┐  │
        │  │  Task Executor  ×N     rag/svr/task_executor.py             │  │
        │  │  ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────┐ ┌─────────┐  │  │
        │  │  │ DeepDoc │▶│ Chunker │▶│ Embedding│▶│ Save │ │ GraphRAG│  │  │
        │  │  │deepdoc/ │ │rag/app/ │ │ rag/llm/ │ │      │ │ RAPTOR  │  │  │
        │  │  └─────────┘ └─────────┘ └──────────┘ └──────┘ └─────────┘  │  │
        │  └───────┬──────────────────────┬──────────────────────────────┘  │
        └──────────┼──────────────────────┼─────────────────────────────────┘
                   │                      │
      ┌────────────┼──────────────┬───────┴────────┬───────────────────┐
      ▼            ▼              ▼                ▼                   ▼
┌───────────┐ ┌─────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐
│  MySQL    │ │  MinIO  │ │  Doc Engine  │ │  Redis /     │ │  外部 LLM 供應商│
│ 中介資料  │ │ 原始檔  │ │ ES / Infinity│ │  Valkey      │ │ chat/embedding │
│           │ │ + 圖片  │ │ chunk + 向量 │ │ 快取 + 佇列  │ │ rerank/ocr/tts │
└───────────┘ └─────────┘ └──────────────┘ └──────────────┘ └────────────────┘

七個子系統,一句話各自的職責:

  1. API Server(api/ — Quart(async Flask)寫的 REST API,處理登入、知識庫 CRUD、上傳、對話。它不做重活:所有解析與索引都是丟到佇列。
  2. Task Executor(rag/svr/task_executor.py — 真正幹活的 worker,93 KB 的單一檔案(是的,很大),從 Redis Stream 拉任務,跑完整條 ingestion 管線。可以水平開 N 個。
  3. DeepDoc(deepdoc/ — 文件理解層,分 vision/(OCR、版面辨識、表格結構辨識)與 parser/(各格式解析器)。Part 2 主角。
  4. Chunking + Retrieval NLP(rag/app/rag/nlp/ — 14 種切分模板、分詞器、查詢編譯器、混合檢索與重排。Part 2 與 Part 4 主角。
  5. Doc Engine(可插拔) — chunk 與向量的家。預設 Elasticsearch 8.11.3,可換 Infinity、OpenSearch、OceanBase、SereneDB、GaussDB。Part 3 主角。
  6. Agent / Pipeline(agent/rag/flow/ — 兩套 DAG 執行引擎:agent/canvas.py 跑對話型 agent 工作流,rag/flow/pipeline.py 跑可編排的 ingestion 管線。Part 5 主角。
  7. Model Layer(rag/llm/ — 把 chat / embedding / rerank / vision / OCR / TTS / ASR 七類模型抽象成統一介面,背後接數十家供應商。

三、資料的兩條路徑

RAGFlow 只有兩條主要資料流。看懂這兩條,整個系統就通了。

3.1 Ingestion 路徑(寫入)

使用者上傳 report.pdf
        │
        ▼
① API Server:檔案存進 MinIO,MySQL 寫 document 列(status=UNSTART)
        │
        ▼
② API Server:往 Redis Stream "te" 推一則訊息 {id, doc_id, task_type:"dataflow", ...}
        │
        ▼
③ Task Executor:consumer group 搶到訊息,用 msg["id"] 回 MySQL 撈完整 task 列
        │
        ▼
④ DeepDoc 解析:PDF → OCR → 版面辨識(10 類)→ 表格結構辨識 → 帶版面座標的 sections
        │
        ▼
⑤ Chunking:依 parser_id 選模板(naive / paper / manual / laws / qa / table …)
   切分契約:delimiter "\n!?;。;!?" + chunk_token_num + MergeStrategy
        │
        ▼
⑥ Chunk 加值(可選、每項都是一次 LLM 呼叫):
   keyword_extraction / question_proposal / content_tagging / auto-metadata / TOC
        │
        ▼
⑦ Embedding:vec = 0.1 × embed(檔名) + 0.9 × embed(內容),寫進 q_{dim}_vec 欄位
        │
        ▼
⑧ 寫入 Doc Engine:索引 ragflow_{tenant_id},圖片另存 MinIO,進度回寫 MySQL
        │
        ▼
⑨ 選配的第二階段任務(各自是獨立 task type):
   RAPTOR 摘要樹 / GraphRAG 知識圖 / Mindmap / Wiki / Skill …

三個值得先記住的細節:

  • 佇列裡只放 ID,不放內容。 訊息體極小,任務狀態的唯一真實來源是 MySQL 的 task 表。Executor 拿到訊息第一件事是 TaskService.get_task(msg["id"]),並檢查 has_canceled()
  • 進度是一個 0.0~1.0 的浮點數 + 一段訊息字串,由 set_progress() 一路回寫。Embedding 階段的進度公式寫死在程式裡:0.7 + 0.2 × (i+1)/len(cnts)——也就是說,UI 上看到 70%~90% 就是在做向量化。
  • RAPTOR、GraphRAG 不是 ingestion 的一部分,它們是 KB 層級的 fan-out 任務,用一個假的 doc_id sentinel 標記,參與的文件放在 task["doc_ids"]

3.2 Query 路徑(讀取)

使用者問:「Q3 的毛利率是多少,跟去年比?」
        │
        ▼
① 問句改寫(可選):
   refine_multiturn → full_question()  把多輪對話壓成一個獨立問句
   cross_languages  → 跨語言擴寫
   keyword          → LLM 抽關鍵字加進查詢
        │
        ▼
② 查詢編譯:FulltextQueryer.question()
   分詞 → 停用詞移除 → term weighting → 同義詞擴展 → bigram 短語
   產出加權布林查詢,欄位 boost:important_kwd^30 > question_tks^20 > title_tks^10 > content_ltks^2
        │
        ▼
③ 雙路召回(一次請求打進 Doc Engine)
   ┌──────────────────┐        ┌──────────────────┐
   │ 全文檢索(BM25 變體)│       │ 向量 KNN         │
   │ 自訂 scripted_sim │        │ top_k 1024       │
   │                  │        │ candidates 2048  │
   └────────┬─────────┘        └─────────┬────────┘
            └──────────┬─────────────────┘
                       ▼ weighted_sum 融合
        │
        ▼
④ 本地重排:sim = 0.3 × token相似度 + 0.7 × 向量相似度 + rank_feature
   (rank_feature = tag 特徵 cosine × 10 + PageRank)
        │
        ▼
⑤ Cross-encoder rerank(可選):對前 64 個候選做精排
        │
        ▼
⑥ 進階召回(可選):GraphRAG 實體/關係/社群、RAPTOR 摘要層、TOC 增強、metadata 過濾
        │
        ▼
⑦ 組 prompt:kb_prompt() 用 max_tokens × 0.97 當預算,塞不下就截斷並記警告
        │
        ▼
⑧ LLM 生成(串流)
        │
        ▼
⑨ 事後引用對齊:insert_citations() 把答案切句,每句與 chunk 做混合相似度比對
   閾值從 0.63 開始,每輪 ×0.8 降到 0.3,每句最多掛 4 個 [ID:n]

第 ⑨ 步是 RAGFlow 最有辨識度的設計。 多數 RAG 系統的做法是在 prompt 裡拜託 LLM「請標註引用」,然後祈禱它照做。RAGFlow 反過來:讓 LLM 自由生成,生成完之後用向量比對「反查」每一句話出自哪個 chunk。 引用因此不依賴 LLM 的服從度。Part 4 會細講這個機制的代價(要對答案的每一句再做一次 embedding)。


3.3 誰存什麼:四個儲存的分工

兩條路徑會反覆碰到四個儲存。它們的分工是理解整個系統的關鍵,而且分工非常乾淨:

儲存存什麼是真實來源嗎掉了會怎樣
MySQL使用者、租戶、知識庫、文件、任務、對話、模型設定✅ 是系統無法運作
MinIO / S3使用者上傳的原始檔、DeepDoc 切出的圖片✅ 是無法重新解析、UI 看不到原文
Doc Enginechunk 文字、向量、排序特徵、知識圖節點❌ 可重建檢索失效,但可從上面兩者重跑
Redis / Valkey任務佇列、LLM 回應快取、同義詞表、分散式鎖、session⚠️ 混合系統整體停擺(不只是快取失效)

兩個常被誤解的點

第一,doc engine 不是真實來源。 它裡面的一切都可以從 MySQL(知道有哪些文件、用什麼參數)+ MinIO(原始檔)重新產生。這就是為什麼預設 number_of_replicas: 0 不算太瘋狂——代價是「重新解析一次」,不是「資料永久遺失」。備份的重點是 MySQL 和 MinIO。

第二,Redis 不只是快取。 它同時是佇列、快取、鎖和 session store。很多人以為 Redis 掛了只是「變慢」,但在 RAGFlow 裡,Redis 掛了等於任務系統死亡 + 登入態失效。Phase 3 一定要做 Redis 叢集。

四、三個演進階段

RAGFlow 的部署形態可以直接對應到三個規模階段。以下每一階段都是 docker/.env 裡幾個變數的組合。

╔══ Phase 1:POC(單機 / < 1 萬 chunk / 1~5 人) ══╗

┌──────────────────────────────────────────────────┐
│  單一 Docker 主機(4 core / 16 GB / 50 GB)        │
│                                                  │
│  ┌────────────────────────────────────────────┐  │
│  │ ragflow 容器                                │  │
│  │  nginx + api_server + admin + 1×executor   │  │
│  └────────────────────────────────────────────┘  │
│                                                  │
│  ┌──────────┐ ┌────────┐ ┌───────┐ ┌──────────┐ │
│  │ ES 8.11.3│ │ MySQL  │ │ MinIO │ │ Valkey 8 │ │
│  │  8 GB    │ │  8.0   │ │       │ │          │ │
│  └──────────┘ └────────┘ └───────┘ └──────────┘ │
└──────────────────────────────────────────────────┘
  • .env 設定:DOC_ENGINE=elasticsearchDEVICE=cpuMETADATA_DB_PROFILE=mysql
  • Embedding 用外部 API(智譜、OpenAI、SiliconFlow…),不佔本機 GPU
  • 可接受的捷徑:單 executor(一次只解析一份文件)、ES 單節點 0 replica、MinIO 單盤
  • 解決的問題:能跑通、能上傳、能問答、能看到 chunk 長什麼樣
  • 剩下的問題:一份 300 頁掃描 PDF 會塞住整個佇列;ES 掉了就全掉;vm.max_map_count 沒調就啟動失敗

實務數字:ES 容器 MEM_LIMIT 預設 8 GB(.env 裡是 8073741824),所以 16 GB 是硬底線,不是建議值。

╔══ Phase 2:MVP(10 萬~百萬 chunk / 一個團隊) ══╗

┌───────────────┐   ┌──────────────────────────────────────┐
│ nginx / LB    │──▶│ api_server × 2(無狀態,可直接複製)  │
└───────────────┘   └───────────────────┬──────────────────┘
                                        │ Redis Stream "te"
        ┌───────────────────────────────┼───────────────────────────────┐
        ▼                               ▼                               ▼
┌────────────────┐            ┌────────────────┐            ┌────────────────┐
│ executor host A│            │ executor host B│            │ executor host C│
│ WORKERS=4      │            │ WORKERS=4      │            │ GPU: DEVICE=gpu│
│ (一般任務)      │            │ (一般任務)      │            │ (OCR/版面辨識) │
└────────────────┘            └────────────────┘            └────────────────┘
        └───────────────────────────────┼───────────────────────────────┘
                                        ▼
              ┌──────────────────────────────────────────────┐
              │ ES 3 節點 / MySQL 主從 / MinIO 分散式         │
              └──────────────────────────────────────────────┘
  • 新增元件 vs Phase 1:多台 executor 主機--host-id + --consumer-no-beg/end 切開 consumer 編號)、GPU executor 專跑 DeepDoc、ES 叢集
  • 關鍵設定:entrypoint.shWORKERS(每台幾個 executor 進程)、CONSUMER_NO_BEG/END(避免多台機器的 consumer 名字撞號)
  • 解決的問題:解析吞吐可線性擴充;重文件不再堵住輕文件;ES 單點消失
  • 剩下的問題:GPU 機器成本;LLM 加值步驟(關鍵字、問題生成、標籤)的 token 帳單開始有感;ES 的 BM25 與向量融合只能用「近似」權重(Part 4 詳談)

成本感受:假設 5 萬份文件、平均 40 chunk/份 = 200 萬 chunk。若開啟 question_proposal + keyword_extraction + content_tagging,那是 600 萬次 LLM 呼叫。 這是 Phase 2 最容易失控的一筆帳,也是為什麼這三個開關預設都是關的。

╔══ Phase 3:Scale(多租戶 / 千萬 chunk 以上) ══╗

┌──────────────────────────────────────────────────────────────────┐
│                    Kubernetes(repo 內含 helm/)                  │
│                                                                  │
│  ┌────────────┐  ┌───────────────┐  ┌──────────────────────────┐ │
│  │ api (HPA)  │  │ go api server │  │ executor pool(分池)     │ │
│  │            │  │ :9384         │  │ common / gpu / graphrag   │ │
│  └────────────┘  └───────────────┘  └──────────────────────────┘ │
│         │                │                       │               │
│         └────────────────┴───────────────────────┘               │
│                          ▼                                       │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │ Doc Engine:Infinity(原生多路融合)或 ES 叢集              │  │
│  │ 索引隔離:每個 tenant 一個索引 ragflow_{tenant_id}          │  │
│  │ 過濾隔離:kb_id 作 low-cardinality secondary index          │  │
│  └────────────────────────────────────────────────────────────┘  │
│  ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌───────────┐ ┌────────┐ │
│  │  MySQL   │ │ MinIO/S3 │ │ Valkey  │ │ Jaeger    │ │Langfuse│ │
│  │ 或 Gauss │ │ /OSS/GCS │ │ 叢集    │ │ 分散式追蹤│ │LLM 觀測│ │
│  └──────────┘ └──────────┘ └─────────┘ └───────────┘ └────────┘ │
└──────────────────────────────────────────────────────────────────┘
  • 新增元件 vs Phase 2:Go serverAPI_PROXY_SCHEME=go|hybrid,把熱路徑換成 Go 實作,Part 5 詳談)、Infinity 取代 ES(原生 weighted-sum 融合,不需要第二次 KNN 回查)、executor 分池-t 參數指定 task type,讓 GraphRAG 這種吃 LLM 的任務不擠壓一般解析)、Jaeger + Langfuse 觀測
  • 解決的問題:多租戶隔離、融合精度、尾延遲、成本歸屬
  • 剩下的問題:Go/Python 雙實作的一致性維護(repo 裡 AGENTS.md 明文要求「converge to one path」);Infinity 的生態成熟度低於 ES

三階段對照

維度Phase 1Phase 2Phase 3
chunk 量級< 1 萬10 萬~100 萬1000 萬+
進程數1 容器全包api×2 + executor×12K8s,分池 HPA
Doc EngineES 單節點ES 3 節點Infinity 或 ES 叢集
Metadata DBMySQL 8.0 單機MySQL 主從MySQL / GaussDB
部署複雜度docker compose upcompose + 多主機參數Helm chart
硬體底線4c / 16 GB3 台 + 1 GPU依租戶數線性
主要瓶頸解析吞吐LLM token 成本融合精度與尾延遲

五、關鍵設計哲學:三個「不一樣」的選擇

讀 RAGFlow 時,有三個決定會反覆出現,值得先講。

5.1 索引以 tenant 為單位,不是以知識庫為單位

1def index_name(uid):
2    return f"ragflow_{uid}"   # uid = tenant_id

一個租戶一個索引,知識庫用 kb_id 欄位過濾(在 Infinity 裡明確標成 {"type": "secondary", "cardinality": "low"})。

為什麼不是一個 KB 一個索引? 因為 ES 每個索引都有固定開銷(預設 2 shard),使用者建 500 個知識庫就會生出 1000 個 shard,叢集直接跪。以 tenant 切、以欄位濾,是在「隔離強度」與「叢集健康」之間選了後者。代價是跨 KB 查詢很便宜,但單一 KB 的刪除是一次大範圍 delete-by-query。

5.2 欄位名稱本身就是 Schema

RAGFlow 的 ES mapping(conf/mapping.json)幾乎沒有寫死任何欄位,全靠 19 條 dynamic template 用後綴決定型別:

後綴型別用途
*_tks / *_ltkstext(whitespace analyzer)已分好詞的 token 串
*_kwd / *_id / *_idskeyword(boolean similarity)精確匹配欄位
*_int / *_flt / *_long數值頁碼、座標、排序
*_512_vec*_1536_vecdense_vector(cosine)向量,維度寫在名字裡
*_fea / *_feasrank_feature(s)PageRank、標籤特徵
*_with_weighttext, index=false只存不索引的原文

所以 embedding 完之後那行 d["q_%d_vec" % len(v)] = v——維度是 768 就自動變成 q_768_vec,命中 *_768_vec 模板,自動成為 768 維 cosine 向量欄位。 換 embedding 模型不需要改 mapping。Part 3 會完整拆這張表。

5.3 Chunk 是給人看的,不只是給機器用的

README 的 “Visualization of text chunking to allow human intervention” 不是行銷詞。chunk_api.py 提供 chunk 的 CRUD,UI 可以逐一檢視、編輯、停用(available_int)、加關鍵字(important_kwd)、加問句(questions)。而這些人工欄位在檢索時有極高權重important_kwd^30question_tks^20,本地重排時 token 還要再乘(important_kwd × 5question_tks × 6)。

這是一個明確的產品判斷:與其追求全自動,不如讓人的一次修正產生 30 倍槓桿。


5.4 每一個貴的功能都預設關閉

RAGFlow 有一長串「打開之後品質會變好」的功能:keyword_extractionquestion_proposalcontent_tagginggen_metadatabuild_TOC、RAPTOR、GraphRAG、refine_multiturncross_languagestoc_enhance、rerank 模型、web search。

它們預設全部關閉。

這不是保守,是一個關於「誰該決定付錢」的立場。這些功能的成本結構是:

一次性成本(ingestion 時)             每次查詢成本
──────────────────────────────────    ──────────────────────────────
keyword_extraction   每 chunk 一次     refine_multiturn   +1 次 LLM
question_proposal    每 chunk 一次     cross_languages    +1 次 LLM
content_tagging      每 chunk 一次     keyword            +1 次 LLM
gen_metadata         每文件一次        toc_enhance        +1 次 LLM
build_TOC            每文件一次        rerank 模型        +150~400ms
RAPTOR               分群 + 每群摘要   web search         +外部 API
GraphRAG             每 chunk 1~3 次
                     + 實體消解 + 社群報告

以 200 萬 chunk 的知識庫為例,開三個 chunk 級加值 = 600 萬次 LLM 呼叫。這不是「一個優化選項」,這是一筆需要被批准的預算。

設計原則:預設值應該是「能跑、便宜、可預測」,而不是「效果最好」。 想要更好的效果,使用者必須明確打開開關——那時他就知道自己在買什麼。

這也是我認為 RAGFlow 值得學的一個產品判斷。很多 RAG 框架的預設值是「把所有能提升品質的東西都打開」,結果新使用者第一次上傳文件就收到一張看不懂的帳單。

六、為什麼選 X 不選 Y

以下六個決定會貫穿整個系列。

決定                選 X 的理由                          不選 Y 的理由 / Y 更好的時機
──────────────────────────────────────────────────────────────────────────────────
Elasticsearch      生態成熟、運維人力好找、dynamic         Infinity:原生 tensor + 多路融合、
(預設)            template 讓 schema 免維護、             延遲更低,但生態新、除錯資料少
vs Infinity        highlight/aggregation 現成              ▶ 翻轉點:融合精度成為瓶頸、
                                                            或 QPS 高到 ES 成本不可接受時換 Infinity

Redis Stream       已經有 Redis(快取/分散式鎖都用它),    Celery:多一組 broker + backend 要維運;
vs Celery/MQ       consumer group 天生支援多 worker 搶單、  RabbitMQ:吞吐更好但運維面積更大
                   unacked 訊息可重新投遞、訊息體只有 ID    ▶ 翻轉點:需要延遲任務、複雜路由、
                                                            或跨資料中心投遞時

MinIO / S3         chunk 要能回頭取原始檔與圖片切片,       本機磁碟:executor 一旦水平擴充就
vs 本機磁碟         物件儲存讓 executor 完全無狀態          必須共享檔案系統,NFS 又是新單點
                                                          ▶ 翻轉點:永遠單機部署時可省掉

模板化 chunking     法條、論文、履歷、表格的結構假設         純遞迴切分:實作 50 行、零設定,
vs 純遞迴切分       完全不同,一套規則不可能都對;          但在結構化文件上召回率差一截
                   14 種模板讓使用者選對假設                ▶ 翻轉點:文件同質性極高(例如全是
                                                            部落格文章)時,遞迴切分夠用

自建 DeepDoc       ONNX 模型跑在 CPU 上,成本可預測、       純 VLM 解析:品質上限更高,但每頁
(ONNX)           不依賴外部 API、單頁成本 ≈ 0;          都是一次多模態呼叫,1 萬頁的帳單
vs 全部丟 VLM       版面/表格結構是結構化輸出,不是自由文字   會嚇到人
                                                          ▶ 翻轉點:文件量小但版面極端複雜
                                                            (手寫、圖表為主)時開 VLM 增強

事後引用對齊        不依賴 LLM 服從指令、換模型不會壞、     要求 LLM 自己標引用:零額外成本,
vs prompt 要求      引用是可驗證的相似度分數                但小模型經常亂標或不標
LLM 標引用                                                ▶ 翻轉點:只用頂級模型且延遲敏感時,
                                                            prompt 方案更省一次 embedding

七、實際跑起來:你會用到的埠與 profile

這一段是給打算動手的人。RAGFlow 的 compose 用 profile 控制要起哪些服務,.env 裡這行是總開關:

1COMPOSE_PROFILES=${DOC_ENGINE},${DEVICE},metadata-${METADATA_DB_PROFILE}
2# 預設展開成:elasticsearch,cpu,metadata-mysql

換 doc engine 不用改 compose 檔,只要改 DOC_ENGINE=infinity,profile 就會起 Infinity 而不起 ES。這是一個很乾淨的多後端切換設計——同一份 compose 支援 6 種 doc engine、3 種 metadata DB、CPU/GPU 兩種 device。

埠位一覽(容器內埠):

服務說明
80 / 443nginxWeb UI 與 API 入口
9380Python API serverapi/ragflow_server.py
9381Python admin serveradmin/server/admin_server.py
9382MCP server--enable-mcpserver 才啟動
9383Go admin serverbin/ragflow_server --admin
9384Go API serverbin/ragflow_server --api
1200 / 9200Elasticsearch主機埠 1200 → 容器 9200
1201OpenSearch替代方案
23817 / 23820InfinityThrift / HTTP
3306MySQLmetadata
9000MinIO物件儲存

啟動前的兩個必做動作(漏掉就啟動失敗,這是 issue 區最常見的問題):

1sysctl vm.max_map_count            # 必須 >= 262144,否則 ES 起不來
2sudo sysctl -w vm.max_map_count=262144

環境需求:CPU ≥ 4 核、RAM ≥ 16 GB、磁碟 ≥ 50 GB、Docker ≥ 24.0.0、Docker Compose ≥ v2.26.1、Python ≥ 3.13(從原始碼跑時)。要用 code executor(agent 的沙箱)還需要 gVisor


7.1 最小可跑流程

 1git clone https://github.com/infiniflow/ragflow.git
 2cd ragflow/docker
 3
 4# 確認 vm.max_map_count(ES 需要)
 5sysctl vm.max_map_count || sudo sysctl -w vm.max_map_count=262144
 6
 7# 起服務(profile 由 .env 的 DOC_ENGINE/DEVICE/METADATA_DB_PROFILE 決定)
 8docker compose up -d
 9
10# 看 log 直到 API server ready
11docker logs -f ragflow-cpu

然後在 UI 上的順序是固定的,漏一步就會卡住:

① 設定模型:至少要一個 chat 模型 + 一個 embedding 模型
   (沒有 embedding 模型,建知識庫時會失敗)
        │
        ▼
② 建知識庫:選 embedding 模型(★ 建了就不能換)+ 選 chunk 模板
        │
        ▼
③ 上傳文件 → 按「解析」→ 看進度條
        │
        ▼
④ 檢查 chunk(★ 這一步不要跳過)
        │
        ▼
⑤ 建對話助理:綁知識庫 + 設 prompt + 調檢索參數
        │
        ▼
⑥ 提問,看引用

第 ② 步的「embedding 模型不能換」是最容易後悔的決定(原因見 Part 3:向量維度寫在欄位名裡)。第 ④ 步是最容易被跳過但最重要的一步——chunk 壞了,後面所有調參都是在補救。

7.2 從原始碼跑(開發用)

1uv sync --python 3.13 --all-extras          # 依賴
2uv run python3 ragflow_deps/download_deps.py # 下載 DeepDoc 的 ONNX 權重
3docker compose -f docker/docker-compose-base.yml up -d   # 只起依賴服務
4source .venv/bin/activate
5export PYTHONPATH=$(pwd)
6bash docker/launch_backend_service.sh        # 起 api + executor
7
8cd web && npm install && npm run dev         # 前端

注意 download_deps.py:DeepDoc 的模型權重不在 git repo 裡,要另外下載。如果跳過這步,解析 PDF 時會失敗。中國大陸環境可以設 export HF_ENDPOINT=https://hf-mirror.com

7.3 五個最常見的啟動問題

症狀原因處理
ES 容器反覆重啟vm.max_map_count < 262144sysctl -w vm.max_map_count=262144
建知識庫失敗沒設 embedding 模型先在模型設定裡加一個
解析卡在 0%executor 沒起來 / Redis 連不上看 executor 的 log
解析卡在 70~90%embedding provider 有問題換 provider 或檢查 API key
解析後檢索不到embedding 維度不是 512/768/1024/1536換模型並重建知識庫(見 Part 3)

最後一個特別陰險:寫入成功、無錯誤日誌、但向量檢索永遠回傳空。 原因是 ES 的動態 mapping 只認 512/768/1024/1536 四種維度,用 384 維模型(如 all-MiniLM-L6-v2bge-small)時向量欄位不會被建成 KNN 索引。Part 3 會完整解釋這個機制。

八、程式碼地圖:從哪裡開始讀

如果你要 clone 下來自己讀,建議的閱讀順序(也就是這個系列的順序):

ragflow/
├── docker/                 ← ① 從這裡開始:compose + .env + entrypoint.sh
│   ├── docker-compose.yml      看清楚有幾個進程
│   ├── docker-compose-base.yml 看清楚有幾個依賴服務
│   └── entrypoint.sh           看清楚進程怎麼被拉起來(Part 5)
├── conf/                   ← ② schema 與設定的真實來源
│   ├── mapping.json            ES dynamic template(Part 3 主角)
│   ├── infinity_mapping.json   Infinity 表結構,欄位名一目了然
│   └── service_conf.yaml       各服務連線設定
├── deepdoc/                ← ③ Part 2:文件理解
│   ├── vision/                 OCR / layout / TSR(ONNX 模型)
│   └── parser/                 pdf / docx / excel / html / markdown / mineru / docling…
├── rag/                    ← ④ Part 2/3/4:引擎核心
│   ├── app/                    14 種 chunk 模板
│   ├── nlp/                    分詞、查詢編譯、混合檢索(search.py 45 KB)
│   ├── llm/                    七類模型的統一介面
│   ├── flow/                   可編排 ingestion pipeline
│   ├── graphrag/               GraphRAG(general / light / ner 三種抽取策略)
│   ├── utils/                  各種 *_conn.py:doc engine 與物件儲存的 driver
│   └── svr/task_executor.py    ⑤ Part 5:整條 ingestion 管線的排程中心
├── api/                    ← ⑥ Part 5:服務分層
│   ├── apps/restful_apis/      29 個 API 藍圖
│   └── db/services/            33 個 service 類別
├── agent/                  ← ⑦ Part 5:Agent canvas + 20 個元件 + 27 個工具
├── internal/ + cmd/        ← ⑧ Part 5:Go 實作(新增中)
├── web/                    React + TypeScript + Vite 前端
├── mcp/  sdk/  admin/  memory/  helm/
└── AGENTS.md               專案自己給 AI coding agent 的指引,讀它可以快速了解慣例

一個實用技巧AGENTS.mdinternal/development.md 是這個 repo 裡資訊密度最高的兩份文件,比 docs/ 更貼近當前程式碼。前者列出完整目錄職責與測試分層,後者是 Go 實作的開發指南(含一套 SQL 風格的 CLI,可以直接對系統下 RETRIEVE 'AI' ON DATASETS 'test' WITH top_k 50 use_kg true;)。


九、系統效應:RAGFlow 相對 naive RAG 改了什麼

把本篇提到的設計整理成一張對照表——這也是後面四篇要逐一驗證的清單。

環節naive RAG 做法RAGFlow 做法效果
解析pdfminer 抽純文字OCR + 版面辨識(10 類)+ 表格結構辨識(5 類)+ 自動旋轉掃描件與表格從「不可用」變「可用」
切分固定 512 token14 種模板 + 分隔符優先 + 一次邊界溢出容忍語意邊界不被硬切
人工介入chunk 可視化 / 可編輯 / important_kwd(^30)/ questions(^20)一次修正 = 30 倍檢索槓桿
編碼只編碼內容0.1×檔名 + 0.9×內容;有 questions 時優先編碼問句檔名資訊不遺失
儲存單一向量欄位後綴驅動的動態 schema,向量維度寫在欄位名換模型不改 mapping
召回單路向量 top-k全文 + 向量雙路,weighted_sum 融合,空結果自動退避專名與語意查詢都能命中
重排本地 0.3/0.7 混合 + rank_feature,選配 cross-encoder(前 64)精度與成本可調
引用靠 prompt 拜託 LLM事後逐句反查,閾值 0.63→0.3 遞降,每句最多 4 個引用可驗證、不依賴模型服從度
進階RAPTOR 摘要樹 / GraphRAG 圖檢索 / TOC 增強全域性問題也能回答

十、系列導航

本篇建立了地圖,接下來四篇逐層下鑽:

  • Part 2 — 資料進場:DeepDoc 的 OCR / 版面 / 表格三層視覺管線,14 種 chunk 模板各自的結構假設,以及 naive_merge 的切分契約(分隔符、token 上限、溢出策略、重疊比例)。
  • Part 3 — Encode 與 Save:混合向量的權重從哪來、批次與截斷怎麼做、後綴驅動的動態 mapping 全表、那個把 TF 壓成 min(freq,1) 的自訂相似度為什麼存在。
  • Part 4 — Decode 與檢索:查詢編譯器如何把一句人話變成加權布林查詢、ES 與 Infinity 兩種融合路徑的差異、rerank 三部曲、GraphRAG 與 RAPTOR 的成本與時機、引用對齊演算法。
  • Part 5 — 系統與程式碼結構:進程拓撲、服務分層、Task Executor 的 13 種任務型別、Agent canvas 與 ingestion pipeline 兩套 DAG 引擎、以及正在進行的 Go 遷移。

RAGFlow Intro Part 2 — 資料進場 — DeepDoc 解析、Chunking 策略與 14 種模板


本文基於 RAGFlow main 分支(2026 年 9 月)原始碼撰寫,版本參考 v0.27.1。所有程式碼路徑、參數預設值與埠位皆從原始碼與設定檔實際核對。RAGFlow 仍在快速演進,細節請以你 clone 的版本為準。

Yen

Yen

Yen