0
0

RAG 文档解析失败时,我会先建隔离队列和复核台

最近给一个内部知识库补导入能力时,我发现最容易拖慢交付的环节出在文档解析失败之后。PDF、Word、表格、扫描件混在一起,只要有几份格式异常,后台任务就会反复重试、进度卡住,用户也看不到原因。最后我把失败文件收进隔离队列,再加一张人工复核台,让解析失败变成可管理的状态。

RAG 文档解析失败从上传、解析、失败分类、隔离、人工复核到重新入库的流程图

原创示意图:解析失败先进入隔离队列,复核后再重试、跳过或重新入库。 来源:Codex image generation

问题背景

原来的 RAG 导入流程很直:上传文件后进入解析 worker,成功就切 chunk、抽 metadata、写入向量库,失败就记录 error 并重试。早期文件少,这条链路能跑通;文件来源一多,扫描 PDF 返回空文本,表格产生超长 chunk,带密码的文件持续失败,部分 Office 文档在不同解析器之间结果不一致。

LlamaIndex 的 IngestionPipeline 文档把导入拆成 transformations、cache、vector store 等阶段。Unstructured 的 partition 文档也把文件拆分描述成结构化元素产出。它们给我的提醒是:解析过程会产生很多中间状态,失败需要被建模,不能只躲在日志里。

踩坑和关键难点

第一个坑是无限重试。临时依赖故障值得重试,密码文件、损坏文件、空文本文件继续重试只会浪费队列。Temporal 的 Retry Policy 文档把 transient、intermittent、permanent failure 分开讨论,放到 RAG 导入里,我需要先判定失败类型,再决定是否进入下一次尝试。

第二个坑是上下文缺失。用户只看到“导入失败”,运营同学要翻日志才能判断。日志里又混着文件名、解析器版本、异常堆栈和 chunk 统计,复核成本很高。第三个坑是向量库污染。失败文件如果已经写入部分 chunk,重新导入时可能出现重复内容、脏 metadata,甚至同一文档多个版本同时可检索。

解决思路

我把导入任务改成五个状态:received、parsing、quarantined、approved、indexed。worker 不直接决定失败文件的最终命运,只产出一张 failure card。卡片里保存 file_id、parser_version、content_hash、error_type、sample_text、chunk_count、retry_count 和 trace_id。错误类型先收敛成几类:临时依赖失败、空文本、格式不支持、疑似加密、结构异常、人工判断。

隔离队列只保存解析结果、失败摘要和必要样本,原始文件仍在对象存储里。复核台提供三个动作:换解析器重跑、标记跳过、人工补摘要后入库。Qdrant 的 payload 文档说明向量点可以携带结构化字段并支持过滤,所以我给 chunk 保留 document_id、source_hash、ingest_run_id、parser_version 和 review_status。重新入库前先按这些字段找到旧数据,再做替换或回滚。

关键步骤

落地时我先加了一张 ingest_runs 表,记录每次导入的版本、发起人、文件数量和状态。每个文件进入 worker 前生成 content_hash,同一文件重复上传时复用已有结果。解析 worker 统一输出 ParseResult,成功时给 normalized_text 和 elements,失败时给 failure card。前端任务面板只展示失败类型、样本片段、建议动作和剩余重试次数。

复核台的人工动作也要写成事件。approve_retry 会新建解析任务,并把上一次 failure card 挂到 parent_failure_id;approve_skip 会让文档进入 skipped 状态;approve_manual 要求复核人填写摘要、来源说明和适用范围,再进入低优先级索引。所有动作写入 audit log,后续能追到谁把哪份文件从隔离区放回了索引。

可复用经验

RAG 导入链路要尽早承认解析失败是业务状态。只要文件来自用户,就会遇到空文本、乱码、密码、扫描件、超大表格和解析器差异。隔离队列让 worker 保持简单,让前端给出清楚反馈,也让向量库远离半成品数据。下一次接新解析器时,只需要新增 error_type 映射和复核动作,整条导入流程可以继续复用。更重要的是,复核数据会反过来沉淀解析器规则,下一轮导入的失败率也更容易被量化。

主要来源

LlamaIndex Ingestion Pipeline: https://developers.llamaindex.ai/python/framework/module_guides/loading/ingestion_pipeline/

Unstructured Partitioning: https://docs.unstructured.io/open-source/core-functionality/partitioning

Qdrant Payload: https://qdrant.tech/documentation/concepts/payload/

Temporal Retry Policies: https://docs.temporal.io/encyclopedia/retry-policies

评论