LightRAG 解析引擎调试 CLI 实战:单文件驱动生产同源 Parser Registry,快速定位解析阶段问题
LightRAG 把「文件解析」抽象为可插拔的 Parser 引擎注册表:native / legacy / mineru / docling 四种内置引擎,加上通过 lightrag.parsers entry point 注册的第三方引擎。当文档入库时 IR 构建、sidecar 落盘或外部服务下载出现问题,直接在服务端流水线里复现成本很高。LightRAG 为此提供了统一的解析调试 CLI(lightrag/parser/cli.py):对单个文件走与流水线 worker 完全相同的 get_parser(engine).parse(...) 分发路径,把 sidecar 与 raw 缓存输出到扁平目录,并打印块级预览。读完本文,你将掌握该 CLI 的完整参数、输出目录布局、五类典型排障场景、MinerU/Docling 环境变量配置,以及它与生产解析路径逐字节等价的底层实现依据。
与生产目录结构的三个刻意差异
CLI 的设计目标是「排障时看得方便」,因此它相对生产入库目录只有三处差异,其余流程(IR 构建、sidecar 写入、full_docs 同步)与生产完全一致:
| 差异点 | 生产入库 | 调试 CLI |
|---|---|---|
| sidecar 位置 | <INPUT_DIR>/__parsed__/<name>.parsed/(多一层 __parsed__/) |
直接落在指定父目录下(无 __parsed__/ 中间层) |
| 源文件 | 解析完成后归档移动到 <INPUT_DIR>/__parsed__/ |
不归档,源文件留在原地 |
| raw 缓存有效性判定 | 严格校验(_manifest.json 等) |
只看 raw 目录是否存在且非空,任何非空 mineru / docling raw 目录都视为有效 |
生产布局的定义在 lightrag/utils_pipeline.py:parsed_artifact_dir_for(file_path, *, parent_hint) 在有 parent_hint 时于其下拼 PARSED_DIR_NAME(即 __parsed__,见 lightrag/constants.py)再拼 <name>.parsed。CLI 恰好把这三处行为替换掉,而替换手段是 monkey-patch,不修改任何生产代码(详见后文「等价性实现」一节)。
命令格式与参数
python -m lightrag.parser.cli <input_file> \
--engine <engine> \
[-o <sidecar_parent_dir>] \
[--doc-id <doc-id>] \
[--force-reparse] \
[--preview N]
参数逐项说明(取自 lightrag/parser/cli.py 的 argparse 定义):
| 参数 | 说明 |
|---|---|
input_file |
待解析源文件路径(位置参数,必填)。文件必须真实存在,CLI 会先做 source.is_file() 检查,否则报错 error: input file does not exist: ... 并以退出码 1 终止 |
--engine |
必填,取值动态来自注册表:内置 native(.docx / .md / textpack,本地解析)/ legacy(纯文本提取,无 sidecar,支持 txt、md、pdf、docx 等约 40 种后缀)/ mineru(PDF/Office/图片,调用 MinerU 服务)/ docling(PDF/Office,调用 docling-serve),以及任何已注册的第三方引擎 |
-o / --sidecar-parent-dir |
sidecar 与 raw 目录的父目录。默认是源文件所在目录 |
--doc-id |
自定义文档 ID。默认 doc-<md5(源文件绝对路径)>,同一文件多次运行稳定不变(由 lightrag/utils.py 的 compute_mdhash_id 计算) |
--force-reparse |
仅对外部服务引擎(mineru / docling 及继承 ExternalParserBase 的第三方引擎)生效:清空 raw 目录并强制重新下载、重新解析。默认策略是非空 raw 目录直接复用 |
--preview N |
解析完成后打印前 N 个 block 的预览(heading + 内容片段,片段超 80 字符截断),默认 5,0 禁用。对无 sidecar 的引擎(如 legacy),改为打印提取文本的前 400 字符 |
两个值得注意的前置校验都发生在真正解析之前:
- 后缀/引擎错配直接拦截。CLI 用注册表的
suffix_capabilities(engine)提前比对后缀(lightrag/parser/cli.py),报错形如error: engine 'native' does not support .pdf files (supported: docx, md, textpack)——避免错误深埋在 IR builder 里才暴露。 - 第三方引擎自动可选。
main()入口先调用load_third_party_parsers()发现lightrag.parsersentry point 引擎(lightrag/parser/cli.py),与 API server 在create_app时加载插件的方式一致,因此通过 docs/ThirdPartyParser-zh.md 注册的引擎在 CLI 中无需任何修改即可作为--engine选项出现。
输出目录布局
以输入 ./inputs/workspace/sample.pdf + 默认 sidecar 父目录(即 ./inputs/workspace/)为例:
./inputs/workspace/
├── sample.pdf # 原始文件,原封不动
├── sample.pdf.parsed/ # ← sidecar 输出
│ ├── sample.blocks.jsonl # JSONL:首行是 meta,后续每行一个 block
│ ├── sample.blocks.assets/ # native 引擎提取的图片/媒体资源(如有)
│ ├── sample.tables.json # 表格 sidecar(IR 含表格时)
│ ├── sample.drawings.json # 图形/图片 sidecar(IR 含 drawings 时)
│ └── sample.equations.json # 公式 sidecar(IR 含公式时)
└── sample.pdf.<engine>_raw/ # ← mineru / docling 的 raw 缓存(native 没有)
├── _manifest.json # 引擎下载流程写入;CLI 缓存校验不读取它
└── <bundle files> # 引擎特定原始产物(content_list.json / *.json / assets 等)
布局规则在源码中有对应实现:
- raw 目录与 sidecar 目录互为兄弟:
foo.parsed/+ 引擎后缀 →foo.<engine>_raw/,见 lightrag/parser/external/mineru/cache.py 与 lightrag/parser/external/docling/init.py。CLI 中 raw 目录名由引擎自身的raw_dir_suffix派生(lightrag/parser/cli.py),所以任何外部引擎都自动适配。 native引擎是纯本地解析,不产生 raw 目录;legacy引擎更简单——只返回纯文本,连 sidecar 都不写。
运行结束时的标准输出也值得对照检查(_print_summary / _print_raw_summary,lightrag/parser/cli.py):
parsed dir : .../sample.pdf.parsed (exists=True)
raw dir : .../sample.pdf.docling_raw (exists=True)
document : sample.pdf
doc_id : doc-xxxx
engine : docling
blocks : 42
sidecars : tables=... drawings=... equations=... asset_dir=...
--- preview (first 5 of 42 blocks) ---
[a1b2c3d4] heading='1. 概述' :: 本文档介绍……(截断至 80 字符)
blocks.jsonl 首行 meta 里的 parse_engine、document_name、table_file 等字段与生产 sidecar 完全同构,可直接作为产物正确性的判据。
典型使用场景
A. 本地解析 .docx(零网络依赖)
python -m lightrag.parser.cli ./inputs/workspace/sample.docx --engine native
# 输出: ./inputs/workspace/sample.docx.parsed/ (含 blocks.jsonl + assets)
native 引擎按后缀分派 docx 与 markdown 处理器(lightrag/parser/native_dispatch.py),全程离线。若 docx 触发内容超限,CLI 会捕获 DocxContentError 并打印格式化错误、以非零码退出(无 traceback,见 lightrag/parser/cli.py)。
B. 用 MinerU 解析 PDF(首次运行会下载 raw)
# 第一次运行: 下载 raw bundle + 生成 sidecar
python -m lightrag.parser.cli ./inputs/workspace/sample.pdf --engine mineru
# 第二次运行(无变化): raw 目录非空 → 直接复用 → 只重新生成 sidecar, 很快
python -m lightrag.parser.cli ./inputs/workspace/sample.pdf --engine mineru
# 日志会显示: [mineru] raw cache hit doc_id=...
缓存命中日志 [mineru] raw cache hit doc_id=... 来自 lightrag/parser/external/_base.py 的 logger.info。注意命中后完全走本地:即使外部端点不可用也能重建 sidecar。
C. 用 Docling 解析 PDF + 复用已存在的 raw 目录
# 已存在 ./inputs/workspace/sample.pdf.docling_raw/(含 docling 的 JSON 输出等)
python -m lightrag.parser.cli ./inputs/workspace/sample.pdf --engine docling
# CLI 不检查 manifest; 只要 raw 目录非空, 就跳过 docling-serve 调用
这是 legacy python -m lightrag.parser.external.docling 调试入口中「从既有 raw 目录重建 sidecar」场景的等价替代——只需把 raw 目录放到约定位置(<sidecar_parent>/<source>.docling_raw/)即可触发缓存命中分支。仓库测试 tests/parser/test_parser_cli.py 的 test_cli_writes_sidecar_from_existing_raw_dir 正是用一份静态 docling JSON fixture 种子化 raw 目录后驱动 CLI,验证「零外部服务、零真实 PDF 内容」下 sidecar 仍能正确生成。
D. 输出到自定义目录
python -m lightrag.parser.cli ./inputs/workspace/sample.docx \
--engine native -o /tmp/debug_sidecar
# 输出: /tmp/debug_sidecar/sample.docx.parsed/
# 源文件 ./inputs/workspace/sample.docx 不会被移动
-o 指向的父目录不存在时会自动 mkdir(parents=True)(lightrag/parser/cli.py)。
E. 强制重新解析(清空 raw 重新下载)
python -m lightrag.parser.cli ./inputs/workspace/sample.pdf \
--engine docling --force-reparse
# raw 目录被清空 → 再次调用 docling-serve 下载 → 重新生成 sidecar
CLI 实现上这是把实例的 is_bundle_valid 换成恒返回 False 的桩函数,从而使模板走进「mkdir → clear → download」分支(lightrag/parser/cli.py)。
环境变量:仅缓存未命中时需要
mineru / docling 在缓存未命中(首次解析或 --force-reparse)时才调用外部服务,所需环境变量与生产入库完全相同(完整定义与默认值见 env.example):
- MinerU:
MINERU_API_MODE(local/official)。official模式需MINERU_API_TOKEN(可选MINERU_OFFICIAL_ENDPOINT,默认https://mineru.net,MINERU_MODEL_VERSION等);local模式需MINERU_LOCAL_ENDPOINT(默认http://127.0.0.1:8000)。可选:MINERU_ENGINE_VERSION/MINERU_MODEL_VERSION/MINERU_POLL_INTERVAL_SECONDS/MINERU_MAX_POLLS。端点是否配置好由注册表中的endpoint_configured闭包实时判定(lightrag/parser/registry.py)。 - Docling:
DOCLING_ENDPOINT(只填 base URL,客户端自行拼接/v1/convert/file/async、/v1/status/poll/{task_id}、/v1/result/{task_id})。可选:DOCLING_ENGINE_VERSION/DOCLING_DO_OCR/DOCLING_FORCE_OCR/DOCLING_OCR_ENGINE/DOCLING_OCR_PRESET/DOCLING_OCR_LANG/DOCLING_DO_FORMULA_ENRICHMENT/DOCLING_POLL_INTERVAL_SECONDS/DOCLING_MAX_POLLS。
更详细的参数语义(轮询预算、OCR 调优、*_ADDITIONAL_SUFFIXES 与路由规则的配合等)参见 docs/FileProcessingPipeline.md 与 env.example 中的注释。
关键结论:缓存命中时(raw 目录已存在且非空,且未传 --force-reparse),完全不需要任何外部服务环境变量——这意味着你可以把 raw 目录拷到任意离线机器上复现 sidecar 生成,这是排查「服务端下载正常但 IR 构建异常」这类问题的利器。
与生产解析路径的等价性:源码级拆解
文档宣称「CLI 驱动与流水线 parse worker 相同的注册表分发路径」,这一承诺在源码里可以逐行验证。
调用入口:get_parser(engine).parse(ParseContext(rag, ...))
CLI 的 _run() 中核心调用是(lightrag/parser/cli.py):
result = (
await parser.parse(
ParseContext(
rag,
doc_id,
str(source),
{"parse_format": FULL_DOCS_FORMAT_PENDING_PARSE, "content": ""},
)
)
).to_dict()
其中 rag 是 lightrag/parser/debug.py 的 build_debug_rag() 返回的轻量替身:它复用了真实的 LightRAG._persist_parsed_full_docs 方法,把 full_docs 换成内存版 DebugFullDocs、doc_status 换成 no-op 的 DebugDocStatus,并实现了 ParseContext 读取所需的全部方法面(_resolve_source_file_for_parser、_build_global_config 等)。模块 docstring 明确约定:任何引擎都通过 get_parser(engine).parse(ParseContext(rag, ...)) 驱动,解析器新增对 rag 的依赖时应扩展这个替身,而不是在调用点复制并行桩。该替身同时被 golden 测试与重放脚本(scripts/regen_native_docx_golden.py)共用,保证「CLI 输出 == 测试输出 == 生产调用链上的解析器行为」这一闭环。
三处差异全靠 monkey-patch 实现(零生产代码修改)
CLI 在 ExitStack 中注册三个补丁(lightrag/parser/cli.py):
parsed_artifact_dir_for→ 扁平路径。生产实现会把 sidecar 放进__parsed__/(见前文),补丁版直接返回<sidecar_parent>/<source.name>.parsed/。由于该函数在模块加载期被from导入,CLI 同时打补丁到lightrag.utils_pipeline和lightrag.pipeline两个命名空间。- 解析器实例的
is_bundle_valid→ 「非空即有效」。ExternalParserBase.parse调用的是self.is_bundle_valid(...),所以补丁直接打在 get_parser 返回的实例上——不关心引擎来自哪个模块,任何外部引擎通吃。默认策略是_lenient_bundle(raw_dir.exists() and any(raw_dir.iterdir())),传--force-reparse则换成恒False的_force_miss。 archive_docx_source_after_full_docs_sync→ no-op。所有引擎归档源文件都经由ctx.archive_source转到该函数,打补丁后源文件原地保留。
除这三点外,模板流程与生产逐段一致——外部引擎模板 lightrag/parser/external/_base.py 的固定顺序为:resolve → raw_dir → force-reparse check → cache-hit skip else (mkdir + clear + download_into) → build_ir → write_sidecar → _persist_parsed_full_docs → archive source。因此:
- sidecar 的字段、命名与内容格式与生产入库完全相同;
- IR builder、
write_sidecar调用与_persist_parsed_full_docs行为一致; - 逐文件引擎参数(如
mineru(page_range=1-3))的解码与缓存签名参与逻辑(decode_parse_engine,lightrag/parser/external/_base.py)同样生效。
一处需要说明的差别:生产环境用 LIGHTRAG_FORCE_REPARSE_MINERU / LIGHTRAG_FORCE_REPARSE_DOCLING 环境变量强制重解析,而 CLI 用 --force-reparse 标志(两者最终都落在「缓存恒未命中」这一行为上)。
测试与 golden 佐证
- tests/parser/test_parser_cli.py:引擎无关地覆盖 CLI 行为——扁平 sidecar 布局(无
__parsed__/)、非空 raw 目录的宽松缓存策略、源文件不归档保证、doc_id跨运行稳定性(test_cli_doc_id_default_is_stable_across_runs对比两次运行的 meta 与 blockid)。 - tests/parser/docx/golden/native_docx/:8 组 golden 场景(
all_modalities、tables_mixed、equations_block_and_inline、drawings_with_assets、text_only_hierarchy等),CLI 输出可与之交叉校验。CLI 不冻结时间戳,比对时只需排除created_at之类的时间字段(冻结时间的FrozenDateTime只用于 golden 测试侧,见 lightrag/parser/debug.py)。
常见问题排查
| 症状 | 处理 |
|---|---|
error: input file does not exist: ... |
检查 input_file 路径;必须是真实存在的文件(不是 raw 目录) |
| raw 目录存在但 sidecar 内容仍是旧的 | 默认行为就是复用 raw、重新生成 sidecar;若 raw 本身已过期或被替换过,加 --force-reparse 清空重下 |
MinerU 报缺 MINERU_API_TOKEN / Docling 连不上 DOCLING_ENDPOINT |
说明缓存未命中、触发了外部服务调用——核对对应环境变量;或确认 raw 目录是否非空(缓存命中则不需要服务) |
| 源文件被意外移动 | 理论上不应发生:CLI 已 mock 掉归档函数。若可复现请提 issue(可能是流水线新增了归档调用点) |
docling 报 produced zero blocks |
docling raw 中的主 JSON 内容不可解析或为空;检查 raw 目录里 *.json 文件是否有效 |
报 engine 'X' does not support .pdf files (supported: ...) |
后缀与引擎能力不匹配;按括号内实际支持列表换引擎,或先做后缀/引擎路由配置 |
小结
Parser 调试 CLI 的价值在于「最小复现面」:单文件、扁平输出、宽松缓存、无归档、零生产代码改动,却完整走注册表分发与 sidecar 写入主链。排查思路可以固化为三步:先用 native/既有 raw 目录把问题离线复现(排除外部服务因素),再用 --preview 与 golden 产物核对 IR/sidecar 正确性,最后用 --force-reparse 区分「raw 下载问题」与「raw→sidecar 构建问题」。所有结论均可回溯到 lightrag/parser/cli.py、lightrag/parser/external/_base.py 与 tests/parser/test_parser_cli.py 中的具体实现,读者可据此继续深入。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00