跳转至

知识库参考文件解析技术方案

  • 版本:v1.0
  • 日期:2026-04-12
  • 作者:Codex
  • 适用项目:Tender_Documents

1. 背景

当前知识库文件链路已经支持:

  1. 前端通过 presigned-url 直传 OSS。
  2. 上传完成后调用 confirm-upload(file_key)
  3. 服务端校验对象存在并创建 KnowledgeFile 记录。
  4. 列表接口基于 knowledge_files 表返回文件列表。

当前知识库链路还没有做的是:

  1. 上传确认后异步解析文件。
  2. 解析基础信息并回写状态。
  3. 生成 Markdown、切块、索引等后续处理。

现状里 KnowledgeFile 已经有 status 字段,但尚未形成真正的任务驱动状态流转;而标书解析已经具备完整的 API -> 入队 -> ARQ worker -> 状态回写 -> 结果落库 模式。


2. 目标与非目标

2.1 目标

  1. 知识库文件上传确认后自动触发异步解析。
  2. 一期先产出“基础信息”,不在本期生成 Markdown。
  3. 后续可平滑扩展到 Markdown、切块、索引、检索准备。
  4. 复用现有 ARQ/Redis/Worker 基础设施,尽量避免重复造轮子。
  5. 保持 confirm-upload 快速返回,不把重活放在同步接口里。

2.2 非目标

  1. 本期不直接复用 Tender 的业务落库模型。
  2. 本期不做向量化、召回、知识问答。
  3. 本期不做文件内容去重与跨库复用。
  4. 本期不做前端实时逐页 SSE 展示。
  5. 更新(2026-05-21):已补充文件级任务进度 SSE 接口 GET /v1/knowledges/{kb_id}/files/{file_id}/parse-job/events,采用 Redis Pub/Sub 推送阶段事件,并用 DB 快照兜底重连。

3. 方案结论

结论:知识库文件解析应采用“确认上传后立即入队,异步 worker 解析”的模式,但不直接复用现有 Tender 解析服务的业务实现。

推荐原则:

  1. 复用现有 ARQ 队列、Redis 连接、worker 部署方式。
  2. 不直接复用当前 DocumentIngestionService 的 Tender 业务收口逻辑。
  3. 知识库单独设计“文件解析任务”和“文件解析结果回写”。
  4. confirm-upload 只做“校验对象 + 落库 + 入队”,不要同步解析。

原因:

  1. 当前 ingestion_tasks 明显偏标书场景,和 Tender 关联较深。
  2. 知识库文件未来扩展方向与标书解析不同,强行复用会让耦合越来越重。
  3. 即便一期只做基础信息,后面大概率也会扩到 Markdown、切块和索引,异步任务模型更稳妥。

4. 当前代码落点

与本方案直接相关的现有代码:

  1. 知识库上传与确认:
  2. app/routes/v1/knowledges/file.py
  3. app/services/knowledge/file_service.py
  4. 知识库文件模型:
  5. app/models/knowledge.py
  6. OSS 访问能力:
  7. app/services/oss_service.py
  8. 队列与 worker 基础设施:
  9. app/services/arq_queue_service.py
  10. app/worker/arq_jobs.py
  11. 标书解析现有参考实现:
  12. app/services/ingestion/index.py
  13. app/services/task_service.py
  14. app/models/ingestion_tasks.py

5. 一期范围

一期只做知识库文件“异步基础信息解析”,不生成 Markdown。

建议产出以下基础信息:

  1. file_key
  2. content_type
  3. ext
  4. size
  5. page_count
  6. parse_error
  7. parsed_at
  8. parser_version

说明:

  1. sizecontent_type 可以直接来自 HeadObject
  2. page_count 对 PDF 可直接解析;对 DOCX 可尽量提取,若成本过高可先留空。
  3. parser_version 用于后续重跑与兼容性治理。

6. 总体架构

sequenceDiagram
    participant U as Browser
    participant API as FastAPI
    participant OSS as ObjectStorage
    participant DB as Postgres
    participant Q as ARQQueue
    participant W as KnowledgeWorker

    U->>API: 1) 获取 presigned-url
    API-->>U: 2) 返回 upload_url + file_key
    U->>OSS: 3) PUT 文件
    U->>API: 4) confirm-upload(file_key)
    API->>OSS: 5) HeadObject 校验
    API->>DB: 6) 创建 KnowledgeFile(status=pending)
    API->>DB: 7) 创建解析任务
    API->>Q: 8) enqueue knowledge_file_parse
    API-->>U: 9) 返回文件记录
    Q->>W: 10) 消费任务
    W->>OSS: 11) 下载文件
    W->>W: 12) 提取基础信息
    W->>DB: 13) 更新 KnowledgeFile(status=success/failed)
    W->>DB: 14) 更新任务状态

7. 数据模型设计

7.1 KnowledgeFile 扩展建议

当前 KnowledgeFile 只有:

  1. name
  2. file_url
  3. size
  4. directory_id
  5. status
  6. remark
  7. is_deleted

建议补充字段:

  1. file_key: str
  2. content_type: Optional[str]
  3. ext: Optional[str]
  4. page_count: Optional[int]
  5. parse_error: Optional[str]
  6. parsed_at: Optional[datetime]
  7. parser_version: Optional[str]
  8. task_id: Optional[UUID]

推荐原因:

  1. file_key 是对象存储里的真实主键,应显式存库,不要每次从 file_url 反推。
  2. status 只表示文件当前解析状态,细节错误要落到 parse_error
  3. 后续重跑、版本升级、增量修复都需要 parser_versionparsed_at

7.2 独立任务表建议

推荐新增独立表,例如:knowledge_file_parse_tasks

建议字段:

  1. id
  2. knowledge_file_id
  3. file_key
  4. status
  5. stage
  6. error_message
  7. retry_count
  8. started_at
  9. finished_at
  10. result_meta
  11. parser_version

不建议直接复用 ingestion_tasks 的主要原因:

  1. ingestion_tasks 当前命名和语义都偏“文档摄入到 Tender”。
  2. result_data 是按页进度设计,知识库一期并不需要逐页流式结果。
  3. 模型已经带 Tender 关系,后面继续复用会越来越绕。
  4. 知识库未来很可能有“重新解析”“重新切块”“重新建索引”等任务类型,独立任务表更清晰。

8. 状态机设计

8.1 KnowledgeFile.status

建议统一为:

  1. pending
  2. processing
  3. success
  4. failed

含义:

  1. pending:已确认上传,待 worker 消费。
  2. processing:worker 正在解析。
  3. success:基础信息解析完成。
  4. failed:解析失败,需要人工重试或系统补偿。

8.2 任务状态

任务表建议保留更细粒度:

  1. PENDING
  2. PROCESSING
  3. COMPLETED
  4. FAILED
  5. CANCELLED

以及阶段:

  1. QUEUED
  2. DOWNLOADING
  3. EXTRACTING_META
  4. COMPLETED
  5. FAILED
  6. CANCELLED

9. 接口设计建议

9.1 confirm-upload

现有接口保留:

  • POST /v1/knowledges/{kb_id}/files/confirm-upload

请求体:

{
  "file_key": "knowledges/{kb_id}/xxx.pdf"
}

服务端流程调整为:

  1. 校验 file_key 前缀。
  2. HeadObject 校验对象存在。
  3. 读取真实 size/content_type
  4. 创建 KnowledgeFile
  5. 创建解析任务。
  6. 入队 knowledge_file_parse
  7. 返回文件记录,状态为 pending

9.2 文件列表

保留:

  • GET /v1/knowledges/{kb_id}/files

建议返回补充字段:

  1. content_type
  2. ext
  3. page_count
  4. parse_error
  5. parsed_at

这样前端不需要额外查任务详情,也能展示解析状态。

9.3 重试接口

建议预留:

  • POST /v1/knowledges/{kb_id}/files/{file_id}/reparse

用途:

  1. 失败文件重新入队。
  2. 解析器升级后批量重跑。
  3. 后续切块和索引切换时做补偿。

10. Worker 设计

建议新增知识库专用 worker handler,例如:

  1. knowledge_file_parse

其职责:

  1. 根据 task_id 加载任务与 KnowledgeFile
  2. 下载 OSS 文件
  3. 根据扩展名选择解析器
  4. 仅提取基础信息
  5. 更新 KnowledgeFile.status
  6. 更新任务状态与错误信息

建议不要直接调用当前 DocumentIngestionService 的原因:

  1. 其内部会校验任务是否仍属于 Tender
  2. 会在结束时回写 TenderContent
  3. 会推进按页 result_data
  4. 业务收口与知识库文件并不一致

推荐做法:

  1. 复用 OSSService.download_bytes
  2. 复用现有 ARQ worker 部署方式
  3. 复用通用 parser 能力时,只抽取底层文档解析器
  4. 知识库单独实现自己的 service 和结果回写逻辑

11. 基础信息提取策略

11.1 PDF

建议优先提取:

  1. 页数
  2. 文档标题元数据
  3. 文件大小
  4. MIME
  5. 扩展名

工具建议:

  1. fitz 或现有 PDF 能力

11.2 DOCX

建议优先提取:

  1. 文件大小
  2. MIME
  3. 扩展名
  4. 文档核心属性
  5. 页数如果实现成本高,可暂缓

11.3 DOC

建议一期保守处理:

  1. 只提取对象元数据
  2. 页数可留空
  3. 后续统一通过转格式或专门解析器增强

12. 幂等与一致性

需要重点处理以下场景:

  1. 前端重复调用 confirm-upload
  2. 同一文件重复点击上传确认
  3. worker 重试导致重复回写
  4. 文件被删除时任务仍在运行

建议策略:

  1. KnowledgeFile.file_key 加唯一约束,或在同目录下做唯一校验。
  2. 若已存在相同 file_key 的未删除记录,confirm-upload 直接返回已有记录。
  3. 若同一 KnowledgeFile 已有 pending/processing 任务,则禁止重复入队。
  4. worker 写库前先检查文件是否已删除,已删除则取消任务。

13. 删除与补偿策略

当前知识库/图库删除是软删除,因此解析链路也应遵守软删除语义。

建议:

  1. 文件软删除时,不立即删 OSS 对象。
  2. 若文件被软删除且任务未完成,worker 应停止后续写入。
  3. 真正的 OSS 物理删除交给后续“清理任务”或“彻底删除”接口。

这样可以避免:

  1. 刚删除就无法恢复。
  2. 正在解析时源文件被提前删掉。
  3. 文件记录还在但 OSS 已丢失的脏状态。

14. 后续扩展路径

Phase 1:基础信息解析

  1. confirm-upload 后创建解析任务
  2. worker 提取基础信息
  3. 文件列表展示解析状态

Phase 2:Markdown 生成

  1. 在任务完成基础信息后增加 EXTRACTING_MARKDOWN 阶段
  2. 落库 source_markdown
  3. 支持失败重跑

Phase 3:切块与索引

  1. 基于 Markdown 或原文生成 chunk
  2. 记录 chunk 版本
  3. 建向量索引或全文索引

Phase 4:知识库引用与检索增强

  1. 支持按文件、按段落引用
  2. 支持检索召回
  3. 支持重新建索引与增量更新

15. 分阶段落地建议

第一步

完成数据模型补充:

  1. KnowledgeFile 增加 file_key 等解析相关字段
  2. 新增 knowledge_file_parse_tasks

第二步

完成上传确认与入队:

  1. confirm-upload 创建文件记录
  2. 同事务或补偿方式创建任务记录
  3. 提交 ARQ 队列

第三步

完成知识库专用 worker:

  1. 新增 knowledge_file_parse handler
  2. 失败时回写 parse_error
  3. 成功时回写基础信息

第四步

补查询与重试能力:

  1. 列表返回完整解析状态
  2. 提供 reparse 接口
  3. 视需要增加任务查询接口

16. 风险与注意事项

  1. DOC 解析能力通常弱于 PDF/DOCX,一期不要承诺太高。
  2. 若未来 Markdown 生成很重,不应继续复用“基础信息任务”,而应拆分阶段。
  3. 已提供文件级任务 SSE:startedparse_startedparse_donechunk_doneembedding_startedembedding_doneindex_startedindex_donecompletederror。当前粒度到解析/切块/向量化/索引阶段,逐页解析事件仍未实现。
  4. 如果未来有多种解析器版本,必须从一开始记录 parser_version

17. 最终建议

建议采用以下路线:

  1. 知识库文件确认上传后立即入队异步解析。
  2. 一期只做基础信息,不做 Markdown。
  3. 新增知识库专用任务表,不直接复用当前 ingestion_tasks
  4. 复用现有 ARQ/Redis/OSS 基础设施,但知识库独立实现自己的解析 service。

这样做的收益是:

  1. 同步接口足够轻。
  2. 状态机清晰。
  3. 后续扩展到 Markdown、切块、索引时不需要推翻重做。
  4. 不会把 Tender 业务耦合带进知识库模块。