docs/FileProcessingPipeline-zh.md
从版本 v1.5.0 (目前在dev分支)开始,LightRAG的文件处理流水线进行了重大的升级:
LightRAG Server引入了一个文件处理的中间格式: LightRAG Document 。该格式支持表格和图片等多模态数据,同时包含文章的章节段落元数据,方便日后进行内容溯源。
本文以 LightRAG Server 的部署与使用视角组织:先给出快速开始可直接套用的配置,再展开内容抽取与分块的配置语法、存储 / 目录布局、去重、并发以及续跑规则。直接通过 Python 代码调用 LightRAG 类的开发者请翻到[第八章 Python SDK 调用](#八、Python SDK 调用)。
所有文件按旧版的文档解析和分块策略处理所有文档。不配置 LIGHTRAG_PARSER 或把它配置为如下值:
LIGHTRAG_PARSER=*:legacy-F
不依赖外部文档解析服务,不依赖VLM视觉模型。使用新版原生的 Native 解析 docx 文档,开启表格(t)和公式(e)的模态分析,搭配P分块策略;其余文档使用老版本的内容解析器,搭配效果更好的R分块策略。
LIGHTRAG_PARSER=*:native-teP,*:legacy-R
开启多模态处理能力需要依赖 MinerU 文件解析服务和 VLM 视觉识别模型。使用 Native 解释 docx 文件,使用 MinerU 解析 pdf、office 和各种图片文件。以上文件都开启图片(i)、表格(t)和公式(e)的模态分析,并并搭配P分块策略。其余文档回退到老版本的内容解析器并搭配R分块策略。
LIGHTRAG_PARSER=*:native-iteP,*:mineru-iteP,*:legacy-R
VLM_PROCESS_ENABLE=true
VLM_LLM_MODEL=kimi-k2.6
MINERU_API_MODE=local
MINERU_LOCAL_ENDPOINT=http://localhost:8000
P分块策略是LightRAG原生的分块策略,详情请参阅Paragraph Semantic 分块策略。VLM的配资请参阅基于角色的 LLM/VLM 配置指南
LightRAG 的文件处理配置由两部分合成:内容抽取引擎决定原始文件如何被解析,处理选项决定解析后是否执行多模态分析、使用哪种分块方式,以及是否构建知识图谱。通常先用环境变量 LIGHTRAG_PARSER 按文件后缀设置默认规则,再用文件名中的 [hint] 覆盖单个文件。引擎和选项可以写在同一个配置片段里,例如 docx:native-iet 或 report.[native-R!].docx。
为了向后兼容,在未修改配置的情况下,升级后的文件内容提取方式会维持原来的 legacy 行为。如需启用新的内容处理引擎,请按本节说明配置。
完整配置模型如下:
LIGHTRAG_PARSER=后缀:引擎-选项,后缀:引擎,*:legacy-R
filename.[ENGINE].ext
filename.[ENGINE-OPTIONS].ext
filename.[-OPTIONS].ext
LIGHTRAG_PARSER 是默认规则表,按文件后缀匹配,例如 pdf:mineru、docx:native-iet。[hint] 是单文件覆盖规则,例如 paper.[mineru].pdf、memo.[native-R!].docx。ENGINE 是内容抽取引擎:legacy、native、mineru 或 docling。OPTIONS 是处理选项字符组合,例如 iet、R!、P。选项最终写入 process_options,由后续流水线阶段读取。ENGINE-OPTIONS 中的连字符只用于分隔引擎和选项,不属于选项本身。[-OPTIONS],例如 [-!]。无横线的 [abc] 会被严格解释为引擎名并报错,不会回退为选项串。常见组合示例:
LIGHTRAG_PARSER=pdf:mineru-R,docx:native-ietP,*:legacy-R
MINERU_API_MODE=local
MINERU_LOCAL_ENDPOINT=http://localhost:8000
DOCLING_ENDPOINT=http://localhost:5001
my-proposal.[native-iet].docx # 使用 native 引擎,开启图、表、公式分析
my-memo.[native-R!].docx # 使用 native 引擎,递归语义分块,禁止知识图谱构建
my-proposal.[-!].docx # 使用默认引擎,仅禁止知识图谱构建
my-proposal.[mineru].docx # 使用 MinerU 引擎,处理选项全部默认
LIGHTRAG_PARSERLIGHTRAG_PARSER 用来为不同文件后缀配置默认内容抽取引擎,也可以在引擎后追加该规则的默认处理选项:
后缀:引擎,后缀:引擎,*:legacy
后缀:引擎;后缀:引擎;*:legacy
后缀:引擎-选项
pdf:mineru,不要写 *.pdf:mineru。, 或分号 ; 分隔。-选项 部分作为该规则匹配文件的默认 process_options。例如 LIGHTRAG_PARSER=docx:native-iet 表示所有 .docx 默认采用 native 引擎,并开启图像、表格、公式分析。文件名中可以使用中括号临时指定单个文件的处理方式:
paper.[mineru-R].pdf
slides.[docling].pptx
memo.[native-P].docx
notes.[-R].md
中括号内的内容支持三种形式:
[ENGINE] # 仅指定引擎,处理选项使用默认或 LIGHTRAG_PARSER 提供的默认
[ENGINE-OPTIONS] # 同时指定引擎和处理选项
[-OPTIONS] # 仅指定处理选项,引擎仍按 LIGHTRAG_PARSER / 默认规则解析
解析 hint 时,无横线内容必须整体匹配引擎名(mineru / native / docling / legacy);带横线且横线前有内容时,横线前是引擎、横线后是选项;以横线开头时表示仅指定选项。旧式 [OPTIONS] 写法不再合法,例如 [iet] 应改为 [-iet]。
| 引擎 | 说明 | 支持的文件格式(后缀) |
|---|---|---|
legacy | 旧版提取方式,在加入流水线前集中提取内容 | txt md mdx pdf docx pptx xlsx rtf odt tex epub html htm csv json xml yaml yml log conf ini properties sql bat sh c h cpp hpp py java js ts swift go rb php css scss less |
native | 内置智能结构化内容抽取器 | docx |
mineru | 外部 MinerU 内容提取引擎 | pdf doc docx ppt pptx xls xlsx png jpg jpeg jp2 webp gif bmp |
docling | 外部 Docling 内容提取引擎 | pdf docx pptx xlsx md html xhtml png jpg jpeg tiff webp bmp |
mineru 和 docling 是外部内容提取引擎,启用相关规则前必须先把服务跑起来,再在 LightRAG 配置对应 endpoint/token。
LightRAG 在本地会缓存 mineru 和 docling 引擎的解析结果。重复上传相同的文件通常不会重新调用引擎解析文档。如果需要删除解析缓存,必须在文档管理界面删除文件弹窗中点击“同时删除文件”选项。修改 mineru 和 docling 引擎的端点地址和有效提取参数也会导致缓存失效,下次上传相同文件的时候会重新调用引擎解析文件内容。
MinerU 客户端支持两种模式,二选一:
local:自建 MinerU 服务(推荐用官方 Docker Compose 部署),LightRAG 通过 HTTP 调用本地容器。official:直连 MinerU 官方精准 API v4,需要在 mineru.net 申请 token。本地化部署(Docker Compose)
从 opendatalab/MinerU 克隆官方仓库到本地,进入仓库内的 docker 部署目录后,先构建镜像:
docker compose -f compose.yaml build
然后启动 API 服务(带 --profile api 才会启用 HTTP API 容器,默认监听 8000 端口):
docker compose -f compose.yaml --profile api up -d
镜像构建细节、GPU 驱动准备、模型权重位置等请参考官方 README:https://github.com/opendatalab/MinerU。
LightRAG 侧 env 配置
Local 模式(自建 mineru-api):
MINERU_API_MODE=local
MINERU_LOCAL_ENDPOINT=http://localhost:8000
Official 模式(MinerU 云端 API):
MINERU_API_MODE=official
MINERU_API_TOKEN=<your_token>
# MINERU_OFFICIAL_ENDPOINT=https://mineru.net # 默认值,通常无需修改
其余高级开关(MINERU_MODEL_VERSION、MINERU_LANGUAGE、MINERU_ENABLE_TABLE / MINERU_ENABLE_FORMULA、MINERU_PAGE_RANGES、MINERU_LOCAL_BACKEND / MINERU_LOCAL_PARSE_METHOD、MINERU_POLL_INTERVAL_SECONDS / MINERU_MAX_POLLS、MINERU_ENGINE_VERSION、LIGHTRAG_FORCE_REPARSE_MINERU 等)请参考仓库根目录 env.example 模板的 MinerU 小节。需要特别注意 MINERU_PAGE_RANGES 在两种模式下语义不同:official 支持完整列表(如 1-3,5,7-9),local 仅支持单页(3)或简单范围(1-10),不接受逗号列表。
docling 内容提取引擎需要外部的 docling-serve 服务(v1 异步 API)。最少配置:
DOCLING_ENDPOINT=http://localhost:5001
DOCLING_ENDPOINT 只填 base URL(不带 /v1/convert/file/async)。目前LightRAG固定使用 Docling 的 standard 流水线处理文件。用户可以通过以下环境环境变量来控制 Docling 流水线的行为:
| Env | 默认 | 含义 |
|---|---|---|
DOCLING_DO_OCR | true | OCR 总开关 |
DOCLING_FORCE_OCR | true | 强制对每页 OCR(扫描件必须开,非扫描件开启通常也有助于提高版面识别质量) |
DOCLING_OCR_ENGINE | auto | OCR 引擎选择(不建议修改) |
DOCLING_OCR_PRESET | auto | OCR 引擎 preset(不建议修改) |
DOCLING_OCR_LANG | (空) | 按照OCR引擎要求设置(不建议修改) |
DOCLING_DO_FORMULA_ENRICHMENT | false | 是识别文档中的公式并按LaTex格式输出;启用前需要确保Docling后台下载了公式识别模型(见后面说明) |
未配置 DOCLING_OCR_ENGINE / DOCLING_OCR_PRESET 时等同于 auto;未配置 DOCLING_OCR_LANG 时不向 docling-serve 传递语言列表,由 OCR 引擎使用自身默认值。解析缓存按这些有效参数计算签名,因此“未配置”和“显式填写默认值”不会导致缓存失效。
轮询预算 2 个 env(docling-serve 是 server-side long-poll,客户端不再额外 sleep):
| Env | 默认 | 含义 |
|---|---|---|
DOCLING_POLL_INTERVAL_SECONDS | 5 | 等待解析结果的轮询间隔时间 |
DOCLING_MAX_POLLS | 240 | 最大轮询轮次,超过抛 TimeoutError; |
| 默认等待时间 ≈ 5 x 240(约20 分钟) |
Bundle 缓存 3 个 env:
| Env | 默认 | 含义 |
|---|---|---|
DOCLING_ENGINE_VERSION | (空) | Docling引擎版本;版本变化会导致解析缓存失效 |
LIGHTRAG_FORCE_REPARSE_DOCLING | false | 设为 true/1 时不启用解析缓存 |
DOCLING_BBOX_ATTRIBUTES | {"origin":"LEFTBOTTOM"} | Docling 版面默认坐标系 |
DOCLING_DO_FORMULA_ENRICHMENT 启用前提:docling-serve 侧需就绪 code-formula 模型权重。adapter 双轨兼容 —— 启用时 text 字段为 LaTeX,关闭或权重缺失导致 text == orig 时自动按普通文本处理,不写 equations.json。因此默认 false 是保守值,部署侧确认模型就绪后再开启。
下面以 Docker 部署 docling-serve 为例,给出从镜像下载到模型挂载的完整步骤,部署完成后将 DOCLING_DO_FORMULA_ENRICHMENT=true 写入 LightRAG 的 .env 即可启用 LaTeX 公式识别。
重要提示:以下步骤基于显卡支持 CUDA 13 的环境。如果显卡较老旧、不支持 CUDA 13,需要把命令与 compose 文件中的镜像名
docling-serve-cu130:main替换为对应 CUDA 版本的标签。可选镜像列表参见 docling-serve Packages。
1. 下载镜像
docker pull ghcr.io/docling-project/docling-serve-cu130:main
2. 下载模型
# 创建 docling 工作目录
mkdir docling
cd docling
# 创建模型挂载目录
mkdir models
# 把容器内的原有模型拷贝到 models 目录
docker run --rm -it \
-v "$(pwd)/models:/opt/app-root/src/models" \
ghcr.io/docling-project/docling-serve-cu130:main \
cp -r /opt/app-root/src/.cache/docling/models /opt/app-root/src/
# 下载公式识别模型
docker run --rm \
-v "$(pwd)/models:/opt/app-root/src/models" \
-e DOCLING_SERVE_ARTIFACTS_PATH="/opt/app-root/src/models" \
ghcr.io/docling-project/docling-serve-cu130:main \
docling-tools models download-hf-repo docling-project/CodeFormulaV2 -o models
3. 创建 docker-compose.yaml 文件
在上一步的 docling 目录下创建 docker-compose.yaml,内容如下:
services:
docling-serve:
image: ghcr.io/docling-project/docling-serve-cu130:main
container_name: docling-serve
ports:
- "5001:5001"
environment:
DOCLING_SERVE_ENABLE_UI: "true"
NVIDIA_VISIBLE_DEVICES: "all"
DOCLING_SERVE_ARTIFACTS_PATH: "/opt/app-root/src/models"
# deploy: # This section is for compatibility with Swarm
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
runtime: nvidia
restart: always
volumes:
- ./models:/opt/app-root/src/models
随后在该目录执行 docker compose up -d 启动服务。容器就绪后,在 LightRAG 的 .env 中设置:
DOCLING_ENDPOINT=http://localhost:5001
DOCLING_DO_FORMULA_ENRICHMENT=true
即可让 LightRAG 通过本地 docling-serve 识别文档中的公式并以 LaTeX 形式输出。
处理选项控制单个文件在多模态分析、知识图谱构建和文本分块上的行为。所有选项都是可选的;缺省值见下表。同一文件最多指定一种分块方式(F/R/V/P),其它选项可任意组合。
| 选项 | 类型 | 默认 | 含义 |
|---|---|---|---|
i | 多模态 | 关闭 | 启用图像分析(VLM) |
t | 多模态 | 关闭 | 启用表格分析(VLM) |
e | 多模态 | 关闭 | 启用公式分析(VLM) |
! | 流水线 | 关闭 | 禁止实体/关系抽取,不构建知识图谱(仅保留 chunks 向量索引,naive / mix 检索仍可用) |
F | 分块 | 默认 | Fix/固定长度分块:遗留方法, 按固定Token长度或按分隔符机械分割(按分隔符分割时文本块不会出现重叠) |
R | 分块 | - | Recursive/递归字符分块(RecursiveCharacterTextSplitter@LangChain):接收一个分隔符列表(默认是 ["\n\n","\n","。","!","?",";",","," ",""],按从语义最强到最弱排列)。优先按段落(双换行符)切分;如果切出的块依然超过 Token 限制,逐级降级使用单换行符 → 中文句末标点(。!?)→ 中文句中标点(;,)→ 空格 → 逐字符切分。默认 cascade 包含中文标点,使中文 / 中英混合文档能在语义边界切分。英文 .?! 故意排除(字面量匹配会误切 0.95 / e.g.)。 |
V | 分块 | - | Vector/向量语义分块(SemanticChunker@LangChain):首先按句子拆分文本(默认句子切分正则同时识别英文 .?! 与中文 。?!,使中文 / 中英混合文档能正确切句),计算相邻句子的 Embedding,然后根据指定的阈值策略(如百分位 percentile、标准差 standard_deviation 或四分位距 interquartile)寻找语义断层进行切分。SemanticChunker 本身没有 chunk size 上限——任何超过 chunk_token_size 的语义块在落库前会自动通过 R 二次切分(保留 V 的非重叠语义)。此分块策略不会出现文本块重叠的情况。 |
P | 分块 | - | Paragraph/段落语义分块(native);优先按标题分割,严格避免上一标题底部内容与下一个标题内容混合破坏语义。适合对能够准确识别标题且标题结构清晰的文档进行分块。同一标题下的超长正文 fallback 到 R 时允许按 CHUNK_P_OVERLAP_SIZE 保留重叠;相邻大表格之间的桥接文字也可按该预算重复进入前后表格块。此分块方法只能运用在保存在 sidecar 目录的 lightrag 内容。如果 lightrag 内容不存在,将退化为使用 R 方法进行文本分块。此分块方法出现文本块重叠的情况远少于 R策略 和 F策略。 |
多模态全局开关
addon_params["enable_multimodal_pipeline"]已废弃,相关行为统一由文件级i/t/e选项控制。详见附录 A。
处理选项的不同字符在流水线的不同阶段生效:
| 选项 | 作用阶段 | 说明 |
|---|---|---|
| i/t/e | Analyzing多模态分析 | 决定是否对 sidecar 中的图像 / 表格 / 公式调用 VLM 做摘要分析。抽取阶段不受影响:内容提取引擎按文档实际内容输出 drawings.json / tables.json / equations.json sidecar 文件。这样后续仅修改 i/t/e 选项触发"再分析"即可补做 VLM,无须重新解析原始文件。 |
| ! | Extraction实体关系抽取 | 跳过实体/关系抽取与图谱写入;chunks 仍写入向量库以保留 naive / mix 检索能力。 |
| F/R/V/P | Chunking文本分块 | 决定使用哪种分块策略;对解析阶段输出无影响。 |
模态可用性以"sidecar 文件是否存在"为唯一信号,内容提取引擎不需要在 meta 中声明能力。某文档若没有任何图像/表格/公式,对应 sidecar 不会写入;用户即使开启了
i/t/e,对应模态也只会被静默跳过,但analyze_multimodal会在该篇文档落一行 INFO 级日志([analyze_multimodal] sidecar e:equations empty: doc—id ...),便于排查"VLM 为何没跑"。这种情况不会报错。
LIGHTRAG_PARSER:未知内容提取引擎、错误后缀写法、显式使用不支持的后缀、外部引擎缺少 endpoint、处理选项中的非法字符都会导致启动失败。parser_routing._engine_is_usable):(a) 该引擎能力表支持此后缀;(b) 若是外部引擎(mineru / docling),对应 endpoint/token 环境变量已配置。任一检查不过,本规则跳过,继续匹配下一条规则。例如 *:mineru;html:docling 中:MinerU 不支持 html 后缀(条件 a 不过),html 继续命中 docling;如果 MINERU_API_MODE=local 但未设置 MINERU_LOCAL_ENDPOINT,所有 PDF 也会跳过 *:mineru 落到下一条规则(条件 b 不过)。这一行为对 LIGHTRAG_PARSER 规则匹配和文件名 hint 引擎选择都生效。LIGHTRAG_PARSER。如果 hint 指定的引擎不支持该后缀,系统会回退到默认规则继续选择可用引擎。LIGHTRAG_PARSER 规则中匹配项的默认选项;都没有则使用全部默认。legacy;如果 legacy 也不支持对应的文件后缀,会向系统添加一个错误条目,上传文件保留在 INPUT 目录。LIGHTRAG_PARSER 默认或全部默认;同时落日志 warning。P 仅对 native 抽取出的 LightRAG Document 结构化结果有效;对 legacy 路径或非结构化输出会自动降级到 R 并记录 warning。process_options 选用哪种分块策略(F/R/V/P),chunk_options 决定那一路分块器用哪些参数。两者职责正交:前者是单字符 selector,后者是结构化字典。
env vars (启动期一次性读取)
│
▼
addon_params["chunker"] (LightRAG 实例字段,由 env 与 legacy 兜底填入)
│
▼ resolve_chunk_options(addon_params, split_by_character=…, split_by_character_only=…)
│
full_docs[doc_id]["chunk_options"] (入队时冻结,每文件独立快照)
│
▼
chunker(tokenizer, content, chunk_token_size, **strategy_kwargs) (分块时按 selector 派发)
LightRAG.__init__ 阶段(由 default_chunker_config() 读取 strategy 特定 env,再由 _apply_chunk_size_overlay 兜底 legacy env)灌进 addon_params["chunker"]。addon_params["chunker"] 是 ObservableAddonParams 字段;Server 部署只需通过 env / 重启即可让新值生效。若需要在 Python 进程内运行时改它(不重启)以及 per-file 覆盖,请见第八章 Python SDK 调用。full_docs.chunk_options 在 apipeline_enqueue_documents 入队时冻结:默认由 resolve_chunk_options(self.addon_params, ...) 现场拼装;若调用方传入 chunk_options 参数则原样持久化(SDK 用法,见 §8.4)。full_docs.chunk_options 取对应子字典,按 process_options.chunking selector 派发到 F/R/V/P。下表所有变量在 LightRAG 实例化时一次性读入 addon_params["chunker"]:strategy 特定 env 由 default_chunker_config() 读取,legacy env (CHUNK_SIZE / CHUNK_OVERLAP_SIZE) 由 _apply_chunk_size_overlay 在 strategy env 与 legacy 构造字段都没填的槽位上兜底。修改 env 后需要重启服务(或新建 LightRAG 实例)才生效;已入队的文档持有冻结快照不受影响。
| 变量 | 默认 | 类型 | 作用域 |
|---|---|---|---|
CHUNK_SIZE | 1200 | int | legacy 顶层 chunk_token_size 兜底;优先级低于 strategy 特定 env 与 SDK 路径设置的 addon_params["chunker"]["chunk_token_size"] |
CHUNK_OVERLAP_SIZE | 100 | int | legacy overlap 兜底;当某 strategy 既无特定 env (CHUNK_F_OVERLAP_SIZE / CHUNK_R_OVERLAP_SIZE / CHUNK_P_OVERLAP_SIZE) 又无 SDK 路径的 LightRAG(chunk_overlap_token_size=…) 时填入 |
CHUNK_F_SIZE | 未设 | int | F strategy 特定 chunk_token_size;高于顶层 legacy 兜底(CHUNK_SIZE 与 SDK 路径的 LightRAG(chunk_token_size=…))。未设时 F 沿用顶层解析结果 |
CHUNK_F_OVERLAP_SIZE | 未设 | int | F strategy 特定 overlap;高于 legacy 构造字段与 CHUNK_OVERLAP_SIZE |
CHUNK_F_SPLIT_BY_CHARACTER | (未设 = null) | str? | F 预切分隔符;null / 空串 = 仅按 token 窗 |
CHUNK_F_SPLIT_BY_CHARACTER_ONLY | false | bool | F 严格模式:不二次按 token 切,超长抛错 |
CHUNK_R_SIZE | 未设 | int | R strategy 特定 chunk_token_size;高于顶层 legacy 兜底(CHUNK_SIZE 与 SDK 路径的 LightRAG(chunk_token_size=…))。未设时 R 沿用顶层解析结果 |
CHUNK_R_OVERLAP_SIZE | 未设 | int | R strategy 特定 overlap;高于 legacy 构造字段与 CHUNK_OVERLAP_SIZE |
CHUNK_R_SEPARATORS | ["\n\n","\n","。","!","?",";",","," ",""] | JSON 数组字符串 | R 分隔符级联,按从语义最强到最弱排列。默认包含中文句末(。!?)和句中(;,)标点,使中文 / 中英混合文档能在语义边界切分。英文 .?! 故意排除(字面量匹配会误切数字与缩写) |
CHUNK_V_SIZE | 未设 | int | V strategy 特定 chunk_token_size(hard cap,超过时自动通过 R 二次切分);高于顶层 legacy 兜底。未设时 V 沿用顶层解析结果 |
CHUNK_V_BREAKPOINT_THRESHOLD_TYPE | percentile | str | V 阈值类型;可选 percentile / standard_deviation / interquartile / gradient |
CHUNK_V_BREAKPOINT_THRESHOLD_AMOUNT | (未设 = null) | float? | V 阈值大小;null 让 LangChain 按类型自选默认(如 percentile=95) |
CHUNK_V_BUFFER_SIZE | 1 | int | V 句子缓冲窗,距离计算时合并的相邻句数 |
CHUNK_V_SENTENCE_SPLIT_REGEX | (?<=[.?!])\s+|(?<=[。?!]) | str | V 的句子切分正则,喂给 LangChain SemanticChunker。默认同时识别英文 .?!(要求后接空白,避免误切 0.95)和中文 。?!(不要求空白,适应中文连写)。env 值为原始正则字符串,无需 JSON 引号 |
CHUNK_P_SIZE | 2000(DEFAULT_CHUNK_P_SIZE) | int | P strategy 特定 chunk_token_size。与 R/V 不同,未设时 P 不沿用顶层 CHUNK_SIZE / LightRAG(chunk_token_size=…)——段落语义合并需要比全局默认更大的上限才能将相关段落保留在一起,因此槽位始终携带 DEFAULT_CHUNK_P_SIZE(2000) |
CHUNK_P_OVERLAP_SIZE | 未设 | int | P strategy 特定 overlap;高于 legacy 构造字段与 CHUNK_OVERLAP_SIZE。用于同一 JSONL content 行内长正文 fallback 到 R 时的文本重叠,以及相邻大表格之间桥接文字复制到前后表格块的单侧预算 |
P 的内部比例常量是算法刻度,会随 chunk_token_size 自动按比例推导。P 始终使用独立于全局链的 chunk_token_size——即使 CHUNK_P_SIZE 未设,P 也会回退到 DEFAULT_CHUNK_P_SIZE(2000)而不沿用全局 CHUNK_SIZE,因为段落语义合并需要比全局默认更大的上限才能将相关段落保留在一起。需要按部署调整时通过 CHUNK_P_SIZE 覆盖该默认。CHUNK_P_OVERLAP_SIZE 只影响 P 内部普通文本 fallback 与表格桥接上下文,不会让表格行级切片互相重叠。CHUNK_F_SIZE / CHUNK_R_SIZE / CHUNK_V_SIZE 行为不同——未设时仍会沿用顶层 chunk_token_size(F 即默认全局窗口,R 偏向较小目标利于句段切分,V 作为 advisory ceiling 通常希望放大以减少过度拆分)。
每个分块槽位的最终值按 specificity-ordered 链解析(高 → 低):
addon_params["chunker"] 显式值 —— 通过 SDK 路径运行时设置或在构造时显式写入的字段值(见 §8.3)。Server-only 部署通常不会出现这一档。最直接,赢一切。CHUNK_F_SIZE / CHUNK_R_SIZE / CHUNK_V_SIZE(各策略 chunk_token_size)、CHUNK_F_OVERLAP_SIZE / CHUNK_R_OVERLAP_SIZE / CHUNK_P_OVERLAP_SIZE(overlap)、CHUNK_P_SIZE(P 专属)。未设对应 size env 时,F/R/V 沿用顶层 chunk_token_size。仅当槽位未被 ① 显式占用时填入。LightRAG(chunk_token_size=…, chunk_overlap_token_size=…),仅 SDK 路径生效,详见 §8.2。strategy 无关,"粗粒度缺省",只填仍空的槽位。CHUNK_SIZE / CHUNK_OVERLAP_SIZE。最终回退。举例:CHUNK_R_OVERLAP_SIZE=42 + LightRAG(chunk_overlap_token_size=2) → R 子字典 chunk_overlap_token_size=42(strategy env 胜出),F / P 子字典 chunk_overlap_token_size=2(无 F / P 特定 env,legacy 构造字段填入)。
P 的 chunk_token_size 特例:P 的 chunk_token_size 槽位不走完整的四档链。当 ① 未显式提供时,直接按 CHUNK_P_SIZE env > DEFAULT_CHUNK_P_SIZE(2000)解析,跳过 ③ legacy 构造字段 LightRAG(chunk_token_size=…) 与 ④ legacy env CHUNK_SIZE。理由参见 §3.2 CHUNK_P_SIZE 行。
三层语义保证:
process_options 重做分块)读的也是 full_docs.chunk_options,避免 env 漂移破坏一致性。chunk_options(典型用法:管理 UI 单独配置某个文件的 separators 或 V 阈值)。这是 SDK 路径的入参语义,详见 §8.4。addon_params["chunker"](实例字段)保留全部四种策略的子字典作为运行时基线;full_docs[doc_id]["chunk_options"] 是精简快照——入队时只保留 process_options 选中的那一路策略子字典(缺省 F),其它策略的参数会被丢弃,因为处理阶段不会读它们。重新解析时 process_options 与 chunk_options 一同改写,避免旧策略的参数残留。
addon_params["chunker"] 全量基线(运行时可由 SDK 修改,影响后续入队):
{
"chunk_token_size": 1200, // 通用 token 上限
"fixed_token": { // F 专属
"chunk_token_size": 1200, // 可选;不写沿用顶层 chunk_token_size(可由 CHUNK_F_SIZE 种子化)
"chunk_overlap_token_size": 100,
"split_by_character": null,
"split_by_character_only": false
},
"recursive_character": { // R 专属
"chunk_token_size": 1200, // 可选;不写沿用顶层 chunk_token_size
"chunk_overlap_token_size": 100,
"separators": ["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] // 默认 cascade 含中文标点
},
"semantic_vector": { // V 专属
"chunk_token_size": 1200, // 可选 hard cap;超过时通过 R 二次切分
"breakpoint_threshold_type": "percentile", // percentile | standard_deviation | interquartile | gradient
"breakpoint_threshold_amount": null, // null = LangChain 默认
"buffer_size": 1,
"sentence_split_regex": "(?<=[.?!])\\s+|(?<=[。?!])" // 默认正则兼容中英文句末标点
},
"paragraph_semantic": { // P 专属
"chunk_token_size": 2000, // 不写则按 CHUNK_P_SIZE 或 DEFAULT_CHUNK_P_SIZE(2000)解析;
// **不**继承通用 chunk_token_size
"chunk_overlap_token_size": 100 // 不写沿用 legacy overlap 解析链
}
}
full_docs[doc_id]["chunk_options"] 精简快照(按 selector 投影;下例为 process_options="R"):
{
"chunk_token_size": 1200, // 通用 token 上限(保留为顶层 fallback)
"recursive_character": { // 唯一保留的策略子字典
"chunk_overlap_token_size": 100,
"separators": ["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
}
}
selector → 子字典映射:F → fixed_token,R → recursive_character,V → semantic_vector,P → paragraph_semantic;无 selector 默认 F。各子字典与对应分块器函数的 keyword-only 参数一一对应;新增参数时无需改 dispatcher,只在 chunker 函数添加 kwarg 即可。
老文档入队时还没有 chunk_options 字段;分块时 dispatcher 会按当前 process_options 调用 resolve_chunk_options(self.addon_params, process_options=…) 兜底拼装一份精简快照。建议在升级后通过 reprocess 一次让老文档拿到精简的 chunk_options 快照(且与当前 process_options 对齐)。
full_docs 字段文件入队和抽取结果会写入 full_docs:
| 字段 | 说明 |
|---|---|
file_path | 文件名 basename(不含目录),保留用户提供的原始名(含中括号 hint),例如 abc.[native-iet].docx 原样写入。未提供有效来源时保存为 unknown_source。文件名 hint 不会被剥离,方便管理 UI 直接展示用户原本的命名意图。 |
canonical_basename | 去掉处理提示 hint 后的规范化 basename(例如 abc.docx)。文件名查重以此字段为索引 key,保证 abc.docx 与 abc.[native-iet].docx 视为同一逻辑文档。 |
source_path | 入队时提供的原始路径(仅当含目录分隔符或绝对路径时才写入),供 native / mineru / docling 解析器定位真实文件位置。 |
parse_format | 内容格式:pending_parse, raw, lightrag。 |
content | raw 时保存抽取文本;pending_parse 时为空字符串;lightrag 时存储以 {{LRdoc}} 开头的完整合并文本(拼接 .blocks.jsonl 中所有 type=="content" 行的 body 段),分块阶段 parse_native 会剥离前缀后再交给 chunking_func,与 raw 走完全相同的代码路径。 |
content_hash | 内容 MD5,用于跨文件名查重。parse_format=raw 取 sanitize_text_for_encoding 后文本的 hash;parse_format=lightrag 取 *.blocks.jsonl 文件 hash;parse_format=pending_parse 不写入,待抽取完成后补上。 |
lightrag_document_path | parse_format=lightrag 时保存结构化 LightRAG Document 的路径;新记录优先保存为相对 INPUT_DIR 的路径,例如 __parsed__/report.docx.parsed/report.blocks.jsonl。注意路径中的子目录与 blocks 文件名都使用规范化 basename(不含 hint)。 |
parse_engine | 实际完成抽取的引擎:legacy, native, mineru, docling。对于待抽取文件,也可暂存目标引擎。 |
process_options | 入队时记录的原始处理选项串(不含引擎名和分隔 -),例如 "iet"、"R!"、""。下游各阶段以此字段为权威源,决定是否启用图像/表格/公式分析(i/t/e)、是否禁止知识图谱构建(!)以及分块方式(F/R/V/P)。空字符串等价于全部默认值。 |
chunk_options | 入队时冻结的分块器参数快照(精简字典:只保留 process_options 选中的那一路策略子字典,其它策略丢弃)。由 SDK 路径调用方传入或由 resolve_chunk_options(self.addon_params, process_options=…) 从实例字段(含 env 默认)兜底(见 §3.1)。process_options 选哪种分块策略(F/R/V/P),chunk_options 决定那一路分块器使用哪些参数。下游 process_single_document 在分块前从此字段读取专属 kwargs;持久化保证 env 变化、续跑、重启后老文档行为可复现。重新解析时与 process_options 一同改写。 |
pending_parse 表示文件已经入队,但还没有完成抽取。抽取成功后会改写为 raw 或 lightrag,并补齐 content_hash。抽取失败时保留 pending_parse 和空 content,便于后续排查和重试。
doc_status中也同步保存原始file_path(含 hint)、canonical_basename与content_hash,作为get_doc_by_file_basename/get_doc_by_content_hash的查重索引来源。get_doc_by_file_basename内部把传入参数先经canonicalize_parser_hinted_basename规范化后再与canonical_basename比对,因此abc.docx与abc.[native-iet].docx总是命中同一文档。process_options同时镜像写入doc_status.metadata["process_options"],便于管理 UI 直接展示当前文件的处理策略。
__parsed__ 目录结构__parsed__ 是输入目录旁的归档与分析结果目录。它同时保存已经处理过的原始文档,以及结构化解析产生的 LightRAG Document (lightrag格式)的文件和图片等资源。
legacy 本地抽取成功并入队后,原文件会移动到同级 __parsed__ 目录;native / mineru / docling 会先保留原文件供 pipeline 解析,解析成功并写入 full_docs 后再移动到 __parsed__。归档时保留原始文件名(含 [hint]),例如 report.[native-iet].docx 归档为 __parsed__/report.[native-iet].docx,便于追溯用户最初的命名与处理选项。[hint])加 .parsed 后缀命名的子目录,避免与归档原文件同名冲突,并保证当文件名 hint 或处理选项变化时同一逻辑文档继续指向同一目录。例如 report.docx、report.[native].docx、report.[native-iet].docx 的分析结果都写入 __parsed__/report.docx.parsed/。__parsed__/report.docx.parsed/report.blocks.jsonl;同一目录下还可能包含 report.tables.json、report.drawings.json、report.equations.json 和 report.blocks.assets/ 图片资源目录。sidecar 是否生成由文档内容决定:解析器只在文档实际包含表格/图片/公式时写出对应文件。这是模态可用性的唯一信号 —— 引擎不需要在 meta 中声明能力。i/t/e 选项只决定下一阶段是否对已存在的 sidecar 调用 VLM 做摘要分析。/documents/scan 扫描到同名且已 PROCESSED 的文件时,该输入文件会被视为已处理并移动到 __parsed__,不会作为新文档入队。/documents/scan 同一次扫描中发现多个规范化后同名的文件时,会优先保留带支持引擎 hint 的文件以尊重用户的引擎选择;如果没有任何变体带 hint,则按排序处理第一个文件。其余变体会输出 warning 并移动到 __parsed__,避免同批文件互相覆盖。例如 abc.docx 和 abc.[native].docx 同时存在时只会处理 abc.[native].docx。__parsed__;本次 doc_status 保留为 FAILED duplicate 以便追踪。_001、_002 等编号,例如 report.pdf 会依次归档为 report_001.pdf、report_002.pdf。若分析结果目录名已被普通文件占用,也会追加编号,例如 report.docx.parsed_001/。<base>.mineru_raw/mineru 引擎在解析过程中会把 MinerU 服务返回的完整产物(content_list.json + 可选的 full.md / middle.json / layout.pdf / images/ 等)落到 __parsed__/<规范文件名>.mineru_raw/ 目录下,并写入 _manifest.json 作为完整性校验文件。
设计目的:
_manifest.json,命中即跳过 MinerU 服务调用,直接从本地 content_list.json 走 adapter → SidecarWriter 流程。*.mineru_raw/ 比对原始 content_list 与图片资源。drawings.json / tables.json / equations.json 会在 self_ref 中保存 content_list.json#/N,用于回查对应的 MinerU 原始对象及其 page_idx / bbox 等定位信息。[mineru-...] / [-iet] 等处理 hint 时,调用 MinerU API 使用去 hint 后的规范文件名,避免 MinerU 返回的 raw bundle 内部文件名携带 hint。生命周期:
| 操作 | 行为 |
|---|---|
| 首次解析 | 下载所有产物 → 原子写入 _manifest.json。 |
| 重复解析(cache 命中) | 不调用 MinerU 服务;不重写产物;走 adapter+Writer 重生成 sidecar(适用于 adapter 升级场景)。 |
| 重复解析(cache miss) | 清空目录内所有文件后重新下载并写入 manifest。 |
DELETE /documents 且 delete_file=True | *.parsed/ 与 *.mineru_raw/ 与原始文件一并删除。 |
DELETE /documents 且 delete_file=False | 保留所有产物,仅删 doc_status 与 KG 数据。 |
clear_documents / __parsed__ 整体清理 | 自然一并清除。 |
| scan 周期 | 不主动 GC 孤儿 *.mineru_raw/(用户显式删除时才清,避免误删调试现场)。 |
强制重新解析(绕过 cache):设置 LIGHTRAG_FORCE_REPARSE_MINERU=true。
并发安全:LightRAG 强制要求同一 workspace 下 canonical_basename 唯一(上传/入队时返回 HTTP 409),加上流水线对单个文档的串行化处理,因此 *.mineru_raw/ 不会出现并发写入冲突,无需额外锁。
_manifest.json 失效条件(任一触发即 cache miss):
MINERU_ENGINE_VERSION 环境变量与 manifest 记录的 engine_version 都非空且不一致;MINERU_API_MODE 与 manifest 记录的 api_mode 都非空且不一致;MINERU_OFFICIAL_ENDPOINT / MINERU_LOCAL_ENDPOINT)与 manifest 记录的 endpoint_signature 都非空且不一致;content_list.json 大小或 sha256 与 manifest 不符;middle.json 等)大小与 manifest 不符。关于
engine_version/endpoint_signature的"任一侧为空即跳过"语义:当 manifest 写入时该字段为空(例如首次解析时未配置MINERU_ENGINE_VERSION),或当前环境变量未设置时,该项不参与失效判断。如果首次解析时未设置版本环境变量,事后再补上并不会自动让历史缓存失效——这类场景需要手动设置LIGHTRAG_FORCE_REPARSE_MINERU=true触发重新解析。
<base>.docling_raw/docling 引擎在解析过程中会把 docling-serve 返回的 zip 产物(DoclingDocument JSON、Markdown 和引用图片)解压到 __parsed__/<规范文件名>.docling_raw/ 目录下,并写入 _manifest.json 作为完整性校验文件。IR builder 在二次解析时会读取该目录的 .json 文件喂给 DoclingIRBuilder,不再走 docling-serve 服务。
目录布局:
__parsed__/<base>.docling_raw/
├── _manifest.json
├── <base>.json # DoclingDocument JSON(含 pages[].image base64)
├── <base>.md # Markdown 形态,供人工检查
└── artifacts/
└── image_*.png # pictures[*].image.uri 指向的图片资源
设计目的:
_manifest.json,命中即跳过对 docling-serve 的上传 / 轮询 / 下载,直接从本地 .json 走 DoclingIRBuilder → SidecarWriter 流程。*.docling_raw/ 比对原始 DoclingDocument JSON、Markdown 与 artifacts/ 图片。生命周期:
| 操作 | 行为 |
|---|---|
| 首次解析 | POST /v1/convert/file/async 上传 → 长轮询 /v1/status/poll/{task_id}?wait=N → GET /v1/result/{task_id} 下载 zip → 安全解压(拒绝绝对路径与 ..)→ 原子写入 _manifest.json。 |
| 重复解析(cache 命中) | 不调用 docling-serve;不重写产物;走 adapter+Writer 重生成 sidecar(适用于 adapter 升级场景)。 |
| 重复解析(cache miss) | 清空目录内所有文件后重新上传 / 下载 / 写入 manifest。 |
DELETE /documents 且 delete_file=True | *.parsed/ 与 *.docling_raw/ 与原始文件一并删除。 |
DELETE /documents 且 delete_file=False | 保留所有产物,仅删 doc_status 与 KG 数据。 |
clear_documents / __parsed__ 整体清理 | 自然一并清除。 |
| scan 周期 | 不主动 GC 孤儿 *.docling_raw/(用户显式删除时才清,避免误删调试现场)。 |
强制重新解析(绕过 cache):设置 LIGHTRAG_FORCE_REPARSE_DOCLING=true。
并发安全:与 MinerU 路径一致 —— LightRAG 强制要求同一 workspace 下 canonical_basename 唯一(上传 / 入队时返回 HTTP 409),加上流水线对单个文档的串行化处理,因此 *.docling_raw/ 不会出现并发写入冲突,无需额外锁。
_manifest.json 失效条件(任一触发即 cache miss):
DOCLING_ENDPOINT 与 manifest 记录的 endpoint_signature 不一致;DOCLING_ENGINE_VERSION 设置且与 manifest 记录的 engine_version 不一致;options_signature 不一致 —— 任一 OCR / 公式 / pipeline 字段变化都会触发,覆盖范围包括:
DOCLING_DO_OCR / DOCLING_FORCE_OCR / DOCLING_OCR_ENGINE / DOCLING_OCR_PRESET / DOCLING_OCR_LANG / DOCLING_DO_FORMULA_ENRICHMENT;pipeline / target_type / to_formats / image_export_mode(写入 signature 是为了防止未来值变更后老 bundle 被误复用);artifacts/ 内任一图片缺失或大小不一致;LIGHTRAG_FORCE_REPARSE_DOCLING=true。
engine_version/endpoint_signature的"任一侧为空即跳过"语义与 MinerU §4.3 一致:manifest 写入时该字段为空(首次未配置DOCLING_ENGINE_VERSION)或当前环境变量未设置时,该项不参与失效判断;事后补上版本号不会自动让历史缓存失效,需要LIGHTRAG_FORCE_REPARSE_DOCLING=true触发。
文件上传、文件解析入队和文本接口会按照「文件名 + 内容 hash」两道关卡判断是否重复,命中任一即视为重复并写入一条 FAILED 记录,不会覆盖已有的 full_docs。/documents/scan 目录扫描也使用同一套索引,但为了便于自动重试未完成文件,对文件名重复有单独的归档与重处理规则。
/data/a.pdf、inputs/a.pdf 和 a.pdf 都视为同一个文件名 a.pdf。canonical_basename 为索引:将文件名末尾的支持引擎处理提示 hint 剥离后再比对,因此 abc.docx、abc.[native].docx、abc.[native-iet].docx 之间互相视为同名;不支持的 hint 不会被剥离,例如 abc.[draft].docx 仍按原文件名处理。doc_status 中已经存在同名文件记录,无论该记录当前处于 PENDING、PARSING、ANALYZING、PROCESSING、FAILED 还是 PROCESSED,同名文件都会被视为重复。/documents/scan 目录扫描:
__parsed__ 并跳过。PROCESSED,当前扫描到的文件视为已处理文件,系统会输出 warning,将该输入文件移动到同级 __parsed__ 目录,并跳过入队。PROCESSED,扫描文件不仅因文件名相同而跳过,但也不会重新提取/覆盖既有记录。具体路径取决于既有记录的形态(与下文"为什么 scan 仍是独占写者"一节列举的分类规则一致):
full_docs 存在 → resume 路径:doc_status 现状保留,源文件留在 INPUT/,由处理循环按状态查询接走(不重新提取、不覆盖既有状态)。FAILED 且 full_docs 缺失 → 视为 apipeline_enqueue_error_documents 写下的提取错误 stub:scan 删掉这条 stub 后把当前文件按新文件重新入队。这是唯一会重新提取的子分支,目的是让"修好源文件再 scan 一次"自动生效。file_source,并按 file_source 的 basename 判断重复;缺少有效 file_source 时直接返回 400。insert / ainsert / apipeline_enqueue_documents 时不传 file_paths 是被允许的,相关行为详见 §8.4。这类无来源文档的 file_path 保存为 unknown_source。no-file-path 和 unknown_source 都会被视为未知来源;它们不会阻止新的无来源文本入队,也不会作为同名文件互相去重。存储后端通过 get_doc_by_file_basename 提供 basename 直查能力,内部按 canonical_basename 字段比对(传入参数会先经 canonicalize_parser_hinted_basename 规范化)。JsonDocStatusStorage 已经实现了内存级遍历;其它后端目前回落到默认实现(扫描全部状态后比对 canonical_basename),将在后续 PR 中补齐原生索引。
full_docs 与 doc_status 会按内容格式写入或补齐 content_hash 字段:
parse_format=raw:取经过 sanitize_text_for_encoding 之后的文本 MD5。parse_format=lightrag:取 lightrag_document_path 解析出的 *.blocks.jsonl 文件 MD5。相对路径按 INPUT_DIR 解析。parse_format=pending_parse:暂不写入 hash,等到真正完成解析后由后续步骤补上(避免按空内容误判)。legacy 路径会在本地提取文本后、入队时进行内容 hash 查重;命中重复时,本次记录写为 FAILED duplicate,不会生成新的 full_docs、chunks 或图数据。native / mineru / docling 路径会先以 pending_parse 入队;真正完成解析并补齐 content_hash 后,如果发现其它文档已有相同 hash,本次记录会在进入分析、切块、实体抽取和图写入前停止。metadata.duplicate_kind 中标记为 filename 或 content_hash,便于排查。内容 hash 重复还会记录 metadata.is_duplicate=true、metadata.original_doc_id 和 metadata.original_track_id;解析后才发现的重复会删除本次临时写入的 full_docs。PROCESSED 的同名文件时会写入日志和 pipeline status;入队阶段重复使用 LightRAG 层的 Duplicate document detected (...) 日志;解析完成后才发现的内容重复使用 Duplicate content skipped after parsing,并写入 pipeline status。扫描归档不会额外输出 [File Extraction]Duplicate skipped。get_doc_by_content_hash 进行 hash 直查;命名约定与 get_doc_by_file_basename 一致。入队批次内(同一次
apipeline_enqueue_documents调用)也会做 basename 与 content_hash 去重,命中时把后续条目直接写为FAILED并标记existing_status=batch_duplicate。其中 basename 去重只对有效文件名生效;unknown_source、no-file-path和空来源只参与内容 hash 去重。跨调用并发去重也由 workspace 级串行锁保证(详见 §6.7 enqueue 串行锁(防并发去重穿透)):两次相同内容、不同文件名的并发入队不会双双穿透
content_hash检查。
为防止 scan / upload / insert 与运行中的流水线相互覆盖 doc_status / full_docs 记录,所有写入入口在 pipeline_status 共享字典上协调。同一 workspace 下的 pipeline_status_lock 保证下表所有 transition 都在锁内原子完成。
pipeline_status 字段| 字段 | 语义 |
|---|---|
busy | 流水线繁忙的笼统标志。处理循环和破坏性作业(clear/delete)都会设它。仅有 busy=True(处理循环)不阻塞 enqueue——循环按 batch 拉取 doc_status 快照处理,每批结束后通过 request_pending 检查是否还有新工作。 |
destructive_busy | busy 的破坏性子集:/documents/clear 或 /documents/{doc_id}(删除)正在 drop 存储 / 删源文件。reservation 和 enqueue last-line guard 都会拒绝——并发 enqueue 会写入正被 drop 的存储,已接受的文档会静默丢失。处理循环不会设此字段。 |
scanning | /documents/scan 后台任务运行中(整个生命周期:分类阶段 + 处理阶段)。仅 /scan 端点用它拒绝重叠 scan,本身不阻塞 upload/insert。 |
scanning_exclusive | scanning 的独占子集:只在 scan 的分类阶段为 True——run_scanning_process 在读 doc_status 分类(已处理 / 续跑 / 删 stub / 归档),不能与并发写者交错。reservation 和 enqueue last-line guard 都会拒绝。分类完成后会立即清旗,scan 进入处理阶段后允许并发 upload。 |
pending_enqueues | 已通过 _reserve_enqueue_slot 但 bg task 未完成的 upload/insert 数。仅给 scan 端点参考——决定是否能拿独占。bg task 在 finally 里释放 slot。 |
request_pending | 让运行中的处理循环再扫一轮的信号。enqueue 在 busy=True 时写完 doc_status 后置位;处理循环每个 batch 结束后检查并重新拉快照。 |
| 入口 | 条件 | 行为 |
|---|---|---|
/documents/upload / /documents/text / /documents/texts | scanning_exclusive=True 或 destructive_busy=True | 抛 HTTP 409,不写文件、不调入队 |
| 同上 | 否则(含纯 busy=True、scan 处理阶段 scanning=True 但 scanning_exclusive=False) | 锁内 pending_enqueues++ 预留 slot → 严格名字预检 → 保存文件 → schedule bg task;bg task 在 finally 释放 slot |
/documents/scan | busy=True 或 scanning=True 或 pending_enqueues>0 | 落 warning 后立即返回 scanning_skipped_pipeline_busy,不 schedule 后台任务 |
| 同上 | 全部 idle | 锁内设 scanning=True 后 schedule,task 结束在 finally 清旗 |
/documents/clear / /documents/delete_document | busy=True 或 scanning=True 或 pending_enqueues>0 | 端点同步返回 status="busy",不 schedule 后台任务 |
| 同上 | 全部 idle | 端点同步在锁内设 busy=True + destructive_busy=True(delete_document 在返回 deletion_started 之前),bg task 的 finally 一并清旗 |
apipeline_enqueue_documents 内部 (last-line guard) | scanning_exclusive=True 且 from_scan=False,或 destructive_busy=True | 抛 RuntimeError("Cannot enqueue while scan is classifying / clearing or deleting") |
| 同上 | 任何其它情况(含纯 busy=True、scan 处理阶段) | 正常入队;写完 doc_status 后若 busy=True 自动 nudge request_pending=True |
from_scan=True 是 scan 后台任务自身入队时的旁路:scan 已持有 scanning 旗标,必须允许它把扫到的文件入队。
busy 不再阻塞 enqueue旧版本里 busy=True 一律拒绝任何新入队,理由是"修改 doc_status 会与流水线工作线程交错"。但实际上:
apipeline_enqueue_documents 总是先 upsert full_docs、再 upsert doc_status。处理循环开头的 consistency check 仅删除"doc_status 行没有对应 full_docs"的孤儿——这种状态在并发 enqueue 中不可能出现。get_docs_by_statuses 快照,新写入的 PENDING 行不会破坏当前 batch;下一轮通过 request_pending 重拉快照即可看到新工作。request_pending 设计本就为此:旧版同时存在 request_pending 字段——它就是为"运行中又有新工作"设计的,但被 busy 守护堵死了。新契约把这个机制启用起来后,用户在长批次处理过程中仍可继续上传新文档,bg task 写完 doc_status 后由运行中的循环自动接管。
scan 不仅 enqueue 自己扫到的新文件,还会读 doc_status 决定每个文件去向:
PROCESSED 行 → 归档源文件、跳过入队。full_docs 存在 → resume 路径,源文件保留在 INPUT/,不归档(pending-parse 解析器仍可能需要它),由处理循环按状态查询接走。FAILED 且 full_docs 缺失 → 识别为之前 apipeline_enqueue_error_documents 写下的提取错误 stub(一致性检查会保留这种行供人工 review),scan 自动删除该 stub 并把当前文件按新文件重新入队,让用户"修好源文件再 scan 一次"能直接生效。这些"读—决策—写"组合不能与其它写者交错,否则分类决策会基于过期视图。所以 scan 必须独占,且 scan 端点会在 busy / scanning / pending_enqueues>0 任一存在时拒绝。
upload 通过 reservation 后、保存文件前必须双道检查:
canonicalize_parser_hinted_basename 规范化,遍历 INPUT 目录里现有任何同 canonical 变体(含 hint / 不含 hint),命中即 409。get_existing_doc_by_file_basename,命中即 409。两道都过 → 保存文件 → schedule bg task → bg task 调 apipeline_enqueue_documents 写库 + 调 apipeline_process_enqueue_documents 触发处理。
旧版本曾允许 upload 在已有同名记录时悄悄写入 FAILED 重复条目;新规则改为 fail-fast,不在 doc_status 留下任何重复痕迹。如需替换同名文档,请先调用
/documents/{doc_id}的删除接口。
两个 upload 同时进来时(scan 此时拿不到独占):
_reserve_enqueue_slot → pending_enqueues=1,写文件,schedule bg task A,返回 success。_reserve_enqueue_slot → pending_enqueues=2,写文件,schedule bg task B,返回 success。apipeline_enqueue_documents → 写 doc_status → 调 apipeline_process_enqueue_documents → 设 busy=True 处理 A 的文档。apipeline_enqueue_documents → 看到 scanning=False,正常写入;写完后看到 busy=True,自动设 request_pending=True。apipeline_process_enqueue_documents → 看到 busy=True,设 request_pending=True 立即返回。request_pending=True,重拉快照,把 B 的 PENDING 行接上处理。busy=False、pending_enqueues=0。任何一个 bg task 都不会因为 busy 被误拒——因为 enqueue 不再检查 busy;处理循环也不会重复处理同一份 batch——request_pending 只在 batch 间生效,且每次重拉前清零。
apipeline_enqueue_documents 内部"读 doc_status 做去重 → 写 full_docs / doc_status"这一段在 workspace 级 enqueue_serialize 锁内串行执行。原因:放开 busy/scan-processing 阶段允许并发 enqueue 之后,两次相同内容、不同文件名的入队(典型场景:scan 处理阶段的 enqueue 与 upload 同时进来)若在没有锁的情况下并发执行——
doc_status 查 content_hash:未命中。doc_status 查 content_hash:仍未命中(A 还没 upsert)。full_docs + doc_status。full_docs + doc_status。结果:同 content_hash 的两条 PENDING 都进入流水线后续处理,原本应当被识别为 duplicate_kind=content_hash 的那条没被识别。
加上串行锁后第二次 enqueue 一定能在去重读时看到第一次已 upsert 的行,正常走"无新唯一文档"的早返回路径并把本次记为 duplicate_kind=content_hash 的 FAILED 行。锁的作用范围只覆盖:
filter_keys(按 doc_id 排除已存在)full_docs.upsert + doc_status.upsert锁不覆盖 request_pending nudge(在锁外,只取一下 pipeline_status_lock),也不阻塞处理循环的 get_docs_by_statuses 读(处理循环走的是 doc_status 自身的并发读,与 enqueue 写是 KV 级原子,不抢同一把锁)。锁顺序:enqueue_serialize → pipeline_status_lock,无死锁路径。
pipeline_status 相关的锁解决的是"谁能写"的正确性问题,本节这一组参数解决的是"同时跑几个 worker"的吞吐量问题。流水线分为 3 个阶段,每个阶段的 worker 池数量独立可调:
┌─ q_native ──► [native parser × N1] ─┐
PENDING ─►├─ q_mineru ──► [mineru parser × N2] ���┼─► q_analyze ─►[analyzer × N4] ─► q_process ─►[processor × N5]
└─ q_docling ──► [docling parser × N3] ─┘
入队时 resolve_stored_document_parser_engine 根据每个文档的 parser_engine(来自 LIGHTRAG_PARSER 默认值或文件 hint)把它放入对应解析队列;3 个解析队列完全互不阻塞——mineru 占满不会拖慢 docling 或 native。解析完成后统一进入 q_analyze(多模态分析),再进入 q_process(实体/关系抽取 + 入库)。
| 环境变量 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
MAX_PARALLEL_PARSE_NATIVE | 5 | N1: native 解析(docx / pdf / txt 等纯本地处理)并发 worker 数 | 纯 CPU、内存占用低,可按 CPU 核数提高 |
MAX_PARALLEL_PARSE_MINERU | 2 | N2: MinerU 解析并发 worker 数 | MinerU 占用 GPU/CPU 显著,默认 2 为适度并发。资源紧张时可降到 1;本地部署且显存充足时可设 2-3;走 MinerU 官方云端服务时可适当提高(受云端配额限制) |
MAX_PARALLEL_PARSE_DOCLING | 2 | N3: Docling 解析并发 worker 数 | Docling 同样资源敏感,默认 2 为适度并发。资源紧张时可降到 1;本地部署且 CPU/GPU 充足时可设 2-3 |
MAX_PARALLEL_ANALYZE | 5 | N4: 多模态分析(VLM 图片 / 表格描述)并发 worker 数 | 直接消耗 VLM 配额。建议 ≤ VLM 服务并发上限 |
MAX_PARALLEL_INSERT | 3 | N5: 实体 / 关系抽取 + 入库阶段并发文档数 | 推荐 MAX_ASYNC / 3,区间 2~10。该阶段每个文档会触发多次 LLM 调用,过高会撞 LLM 限流。同时该值还作为 asyncio.Semaphore 用于二次约束(worker 数和信号量值一致) |
QUEUE_SIZE_PARSE | 20 | parse(native/MinerU/Docling)输入队列长度 | 一般无需调整。队列内仅为轻量 doc_id(大文档体在进入 analyze 前已剥离),仅限制 pipeline 一次预派发给 parse worker 的待处理文档数,调整影响很小 |
QUEUE_SIZE_ANALYZE | 100 | analyze 队列(parse → analyze 阶段)的有界容量 | 一般无需调整。极少量大批量任务(成千上万)可适当提高,避免 enqueue 端反压;内存紧张时可调低 |
QUEUE_SIZE_INSERT | 4 | analyze → process 阶段间的队列容量 | process 是流水线中最慢、最耗内存的阶段,队列特意做小,给上游提供反压防止内存堆积 |
几个要点:
MAX_PARALLEL_INSERT 兼任 worker 池大小和信号量上限:流水线创建 Semaphore(max_parallel_insert),每个 process worker 在抽取入库前还要拿一次信号量。所以哪怕你把 worker 数手动改大,实际并发上限仍由这个值决定——直接调它就够了。QUEUE_SIZE_INSERT=4 这个偏小的默认值是有意为之——process 阶段慢且占内存,让 analyze 阶段在队列写满时阻塞、再反压到 parse 阶段,避免一次性把成千上万份解析结果堆在内存里。.env(或环境变量)传入,仅在 LightRAG 实例构造时读取一次;改完需要重启服务。典型调优场景:
MAX_PARALLEL_PARSE_MINERU=2、MAX_PARALLEL_ANALYZE=5、MAX_PARALLEL_INSERT=3(默认即可;显存紧张时把 MINERU 降到 1)。MAX_PARALLEL_PARSE_MINERU=3~5(视云端配额),其它保持默认。MAX_PARALLEL_PARSE_NATIVE=10、MAX_PARALLEL_INSERT 按 MAX_ASYNC/3 推算。MAX_PARALLEL_INSERT(process 阶段每文档多次 LLM 调用),再降 MAX_PARALLEL_ANALYZE(VLM 是独立配额)。每次 apipeline_process_enqueue_documents 起步时,会拉取所有处于 PARSING / ANALYZING / PROCESSING / PENDING / FAILED 状态的文档继续处理。续跑路径根据"内容是否已抽取"分流,保证同一个文档无论之前进度如何,按当前 process_options 续跑都有幂等结果。
续跑规则只对 doc_id 已经存在于 doc_status 的文档生效。新文件入队需要"并发与重入约束"中的文件查重逻辑,避免新文件挤掉旧的已经成功提取内容的文件记录。
读 full_docs[doc_id]:
parse_format | 判定 |
|---|---|
lightrag 且 lightrag_document_path 文件存在 | ✅ 已抽取 |
raw 且 content 非空 | ✅ 已抽取 |
其它(含 pending_parse、记录缺失) | ❌ 未抽取 |
走完整流水线(parse_native / parse_mineru / parse_docling → analyze_multimodal → 分块 → 实体抽取),按 full_docs.process_options 决定每一阶段的行为。这是"首次入队"的常规流。
一律跳过解析(不重新调 parse_*),从 ANALYZING 阶段重启,并清光旧 chunks / entities 后按当前 process_options 重做:
| 子步骤 | 行为 |
|---|---|
| 引擎对比 | 若 process_options 隐含的引擎 ≠ full_docs.parse_engine,仅 warn,不重新解析。已抽取的内容是不可变事实,重新跑不同引擎会产生不一致。要切换引擎请先 delete 整个文档再重传。 |
| 旧 chunks / 实体 / 关系清理 | 读 status_doc.chunks_list 收集旧 chunk id 集,调 _purge_doc_chunks_and_kg(doc_id, chunk_ids):从 chunks_vdb / text_chunks 删除 chunk 行;按 entity_chunks / relation_chunks 反查受影响的实体 / 关系,对失去全部源的条目直接从图谱与向量库删除,对仍有其它文档贡献的条目调 rebuild_knowledge_from_chunks 用剩余 chunks 重建;最后删除 full_entities / full_relations 中本 doc 的索引行。purge 完成后 status_doc.chunks_list = [] / chunks_count = 0 重置,避免后续 state-machine upsert 写回旧 ID。 |
analyze_multimodal | 对已启用模态,每次运行都会重新计算 sidecar item 分析并覆盖已有的 llm_analyze_result。由于 LLM cache 的存在重复计算通常会保持语义字段不变,只会重写 analyze_time 等运行时字段;cache miss,例如更换模型和提示词等,保存内容才可能与上次不同。 |
| 重新分块 | 按新 process_options.chunking 选策略,参数从 full_docs.chunk_options 读取(入队快照,不会因续跑被覆盖;env 改动后老文档仍按入队那一刻的参数分块)。LightRAG Document path 在 process_options=P 时走 paragraph_semantic,否则按 selector 分发到 F/R/V。 |
| 实体抽取 / KG-skip | 按新 process_options.skip_kg 决定 |
这条规则保证:用户改
i/t/e重传同名文档(先删旧 doc 再上传带新 hint 的文件)时,多模态分析能增量补齐;改F/R/V/P时 chunks 与图谱重建;改!时停掉或恢复 KG 构建。引擎变更被视为"重大变更",统一由 delete + 重传完成,不在续跑路径里隐式发生。
本章针对直接 import LightRAG 类进行集成的开发者,覆盖 Server 部署不会用到的运行时 API、构造期参数和已移除的旧接口。Server 用户通常无须阅读本章。
from lightrag import LightRAG
rag = LightRAG(working_dir="./rag_storage", ...)
await rag.initialize_storages()
await rag.ainsert("text", file_paths="doc.pdf")
这种调用方式以下行为与 Server 路径不同:可在不重启进程的情况下改 addon_params["chunker"],可向 apipeline_enqueue_documents 传入 per-file chunk_options,可在 ainsert 调用时动态覆盖 F 策略的预切分参数。
LightRAG(chunk_token_size=…, chunk_overlap_token_size=…) 是 §3.3 优先级链中的第 3 档:"legacy 构造字段"。strategy 无关、粗粒度缺省,只填仍空的槽位:
addon_params["chunker"] 显式值(§8.3)和 strategy 特定 env(§3.2)。CHUNK_SIZE / CHUNK_OVERLAP_SIZE。self.chunk_token_size / self.chunk_overlap_token_size 在 __post_init__ 之后总会被回填为 int,方便仍读这两个字段的旧路径(如 pipeline.py 中 chunk_opts.get("chunk_token_size") or self.chunk_token_size 兜底)继续工作。addon_params["chunker"]addon_params["chunker"] 是 ObservableAddonParams 字段,可以运行时改:
rag.addon_params["chunker"]["recursive_character"]["separators"] = ["##", "\n", " "]
改完后,后续入队的文档拿到新默认;已入队文档保留入队时的快照不变(参见 §3.3 三层语义保证)。这是 §3.3 优先级链的第 1 档:"addon_params["chunker"] 显式值",赢一切。
Server 部署没有这个能力 —— 改 env 后必须重启服务才生效。
apipeline_enqueue_documents(chunk_options=…)apipeline_enqueue_documents 接受可选的 chunk_options 参数,调用方传入 dict / list[dict] 会按当前文档的 process_options 投影为精简快照(只保留对应策略子字典 + 顶层 chunk_token_size)后持久化到 full_docs[doc_id]["chunk_options"];不传则由 resolve_chunk_options(self.addon_params, process_options=…) 现场拼装一份。调用方可以放心传入全量字典——其它策略子字典会被 dispatcher 丢弃,不会污染存储。
典型用法:
await rag.apipeline_enqueue_documents(
input=["text A", "text B"],
file_paths=["a.[native-R].txt", "b.txt"],
process_options=["R", ""],
chunk_options=[
{"chunk_token_size": 800, "recursive_character": {"separators": ["\n\n", "\n"]}},
{"chunk_token_size": 1500},
],
)
per-file 个性化的典型场景:管理 UI 单独配置某个文件的 separators 或 V 阈值;将来上传 API 也可在 form / hint 中接收覆盖。
不传 file_paths 的兼容:核心 API insert / ainsert / apipeline_enqueue_documents 仍兼容未传 file_paths 的调用;这类文档的 file_path 会保存为 unknown_source,不会参与文件名查重,文档 ID 继续按文本内容生成。
apipeline_enqueue_documents 自身的并发约束(last-line guard、from_scan=True 旁路)见 §6.2 入口行为表。
ainsert(split_by_character=…, split_by_character_only=…)LightRAG.ainsert(split_by_character=…, split_by_character_only=…) 的运行时参数在入队时由 resolve_chunk_options 覆写到 chunk_options.fixed_token:
split_by_character 非 None 即覆盖 env 默认;split_by_character_only=True 即覆盖(False 是签名默认值,与"未指定"无法区分,所以 env 默认胜出)。仅对 F 策略生效;其它策略的子字典不受影响。
reprocess_existing_non_processed旧 apipeline_enqueue_documents 的 reprocess_existing_non_processed=True 行为会在 scan 时直接删除非 PROCESSED 的旧记录并重建,与 §五 / §六 的规则相冲突,已整段移除。替代路径:
/documents/{doc_id} 删旧文档,再上传同名新文件。