Skip to content

核心代码导读

这是一份代码阅读地图:从哪里开始读、每个文件看什么、文件之间怎么衔接。目标是让你读一遍源码就能把整个项目串起来。

1. 推荐阅读顺序

text
第一遍(30 分钟,建立全貌):
  领域层 → 装配 → 服务 → 一个 Parser → Worker

第二遍(按链路精读):
  摄入链路:submit → process → repository → 各 adapter
  问答链路:route → rag → retrieval → context → llm

第三遍(深入细节):
  Parser 全家 / Chunker 算法 / Milvus 一致性 / 状态机边界

2. 第一站:领域层 —— 项目的「词汇表」

先读 src/ultimate_rag/domain/models.py,这是所有模块共享的领域对象:

看什么为什么重要
DocumentStatus 枚举文档状态机(PENDING…READY…FAILED)
IngestionJobStatus 枚举任务状态机
SourceLocator跨格式来源定位(page/bbox/sheet/slide)
Block / ParsedDocumentParser 的统一输出
Chunk / EmbeddedChunk切块与向量化的连接
RetrievalResult / Citation检索与问答的输出

接着看 domain/ports.py(8 个 Protocol 端口)和 domain/exceptions.py(业务异常体系)。先理解端口,再看实现,因为所有模块都围绕端口协作。

3. 第二站:配置与装配

src/ultimate_rag/config.py —— 所有环境变量默认值(看一眼有哪些配置组即可)。

src/ultimate_rag/runtime.py —— Composition Root。这里能看到完整依赖图:

python
create_processing_runtime(settings):
    create_database(...)      # 数据库 Engine + Repository
    MinioObjectStorage(...)   # 对象存储
    BailianEmbedder(...)      # 向量化
    MilvusVectorStore(...)    # 向量库
    BailianOCRClient(...)     # OCR
    BailianVisionClient(...)  # 视觉理解
    ParserRegistry([...])     # 7 个 Parser,顺序即优先级
    StructureAwareChunker(...)
    IngestionService(...)     # 上传入队
    DocumentProcessingService(...)  # 后台处理管线

💡 从 runtime.py 开始读,是理解「谁依赖谁」最快的入口。

4. 摄入链路代码导读

4.1 入口:apps/api/routes.pyupload_document

python
content = await _read_bounded_upload(file, dependencies.max_upload_bytes)  # 有界读取
value = await dependencies.ingestion.submit(...)   # 入队,不等待
return DocumentResponse.from_domain(value)          # 202 + PENDING

4.2 入队:application/services.pyIngestionService.submit

关注顺序:校验 → 系统对象键 → 预校验 Parser → 先存 MinIODocument+Job 同事务 → 失败补偿删除。

4.3 领取:infrastructure/database/repository.pyclaim_ingestion_job

这段 SQL 是任务系统的核心,重点看:

python
# 先收敛租约过期且尝试耗尽的任务
# 再领取:attempts < max AND (PENDING 且到时间 OR RUNNING 且租约过期)
.with_for_update(skip_locked=True)   # 多 Worker 不冲突

4.4 处理:application/services.pyDocumentProcessingService.process

五个阶段(Parse → Chunk/Asset → Snapshot → Embed → Index),每个公开阶段先更新状态再干活。 图片 Asset 和最终 Chunk JSON 必须先于 Embedding 完成持久化,READY 仍在最后。

4.5 数据落库:repository.pyreplace_chunks / update_document_status

  • replace_chunks:事务内「先删后插」,重试不产生重复
  • update_document_status:状态 + error_message 同事务,推进时清空旧错误

4.6 Worker:src/ultimate_rag/worker.pyIngestionWorker

  • run_once():领取 → 启动心跳 Task → 处理 → 提交/失败
  • _heartbeat():续租,租约丢失立即置 lease_lost
  • _handle_processing_failure():清理半成品 + 有限重试决策

5. 解析器代码导读

src/ultimate_rag/parsers/

文件看点
registry.py如何按 supports() 顺序选 Parser
_shared.py输入安全(ZIP Bomb 防护、扩展名/MIME 校验)、表格转 Markdown
markdown.py标题路径如何维护:heading_path = heading_path[:level-1]; append(content)
pdf.py最复杂:PDFium 文字/栅格探测 + Docling 布局 + 扫描页 OCR/Vision
office.pyWord/Excel/PPT 各自的结构映射
image.pyPillow 验证 → 百炼 OCR/Vision 融合;模型噪声由 _model_output.py 清理

读懂一个 Parser(推荐 Markdown),就懂了全部:都是「原始格式 → Block[]」的映射。

6. 切块器代码导读

src/ultimate_rag/chunkers/markdown.pyStructureAwareChunker

方法看什么
_build_sections怎么把 Block 聚合为「同来源 + 同策略」的 Section
_split_section按内容类型选择切分策略(正文/表格/代码/图片)
_split_natural自然单元贪心装箱 + 尾部 overlap 处理
uuid5 稳定 ID幂等重试的基础
Token 硬预算超限显式报错,不静默

7. 向量化与向量库

src/ultimate_rag/embeddings/bailian.py

python
# 按 batch 分批 → index 排序恢复顺序 → 数量/维度校验
ordered = sorted(response.data, key=lambda item: item.index)
self._validate(embeddings, len(texts))   # 阻止错误向量进入 Milvus

src/ultimate_rag/vectorstores/milvus.py

  • ensure_collection:幂等准备 Dense 与 BM25 两个 Collection
  • upsert:稳定 Chunk ID 同步写入两种派生索引,Flush 后才返回
  • search/search_sparse:知识库与可选文档白名单在 Milvus 层过滤
  • 同步 SDK 全部 asyncio.to_thread

8. 问答链路代码导读

文件看什么
routes.pystream_chat检索先于响应开始;SSE 事件顺序(start→data-retrieval→text-delta→finish)
services.pyRAGService._prepare_generation无证据降级、XML 标签隔离、Citation 构造
application/retrieval.pyRetrievalService.retrieveREADY/过滤、改写、多路召回、RRF、重排、Small2Big 与降级
retrieval/fusion.py不依赖模型/数据库的确定性 RRF
context.pyContextBuilder字符预算内拼接 [来源 N],顺序不重排
generation/bailian.py完整生成 vs 原生增量流;空答案拒绝
app.py → 异常映射404/400/409/502 的业务异常对应

9. 状态一致性代码清单

这些位置共同保证「未 READY 不可检索」:

text
repository.py  →  create_document_with_job   (文档+任务同事务)
repository.py  →  update_document_status      (状态+错误同事务)
repository.py  →  replace_chunks              (先删后插,重试不重复)
services.py    →  process                     (READY 放最后)
retrieval.py   →  retrieve                    (READY 事实交集 + Hit 二次过滤)
worker.py      →  _handle_processing_failure  (清理半成品向量)

10. 如果你想测试自己的理解

跟着下面这些问题读代码:

  1. 上传后为什么能立刻返回 202?Document 和 IngestionJob 怎么保证同时存在?
  2. 两个 Worker 同时领任务会发生什么?SKIP LOCKED 怎么避免冲突?
  3. Worker 处理到一半崩溃,任务和文档分别停在什么状态?谁来自动恢复?
  4. Chunk ID 为什么稳定?它怎么让重试幂等?
  5. 检索为什么多取 3 倍候选再过滤?READY 过滤解决什么问题?
  6. 流式问答中,为什么检索和 Citation 在响应建立前完成?
  7. 如果删除文档时 PostgreSQL 删成功了但 MinIO 失败,会怎样?(答:见 delete_document 的顺序)
  8. Milvus 丢失了怎么重建?(答:PostgreSQL Chunk 事实 + Embedder 重新向量化)

下一步

UltimateRAG · 从最小可用 RAG 演进为企业级知识平台