跳到主要内容

Morphik Core — 架构与原理

30 秒导读: Morphik 是一套「视觉密集文档」的开源 RAG 工具链。它最狠的一招是把 PDF/PPT/图片的每一页当成一张图片,用 ColPali 这类多向量视觉模型直接编码,检索时用「晚交互(late interaction)+ MaxSim」打分——从而绕开传统 RAG 里 OCR、表格拉直、图表变文字碎片的信息损失。你 ingest_file("a.pdf")query("..."),中间的解析、切分、向量化、存储、检索、生成全给你包了。

1. 这是什么(零基础也能懂)

  • 一句话定义: Morphik Core 是一个 FastAPI 后端,把「文档进 → 语义检索 → LLM 答」这条 RAG 流水线端到端做好,并专门为图表、扫描件、幻灯片这种「看得懂才答得对」的文档优化。

  • 解决什么问题 / 给谁用: 假设你在做一个 AI 助手,要回答「螺丝 14-A 在椅子装配图里的高度是多少」。传统 RAG 会先把 PDF 抽成纯文本——装配图变成一堆无意义字符,答案直接丢了。给需要在真实生产里查复杂文档的开发者用。

  • 传统 RAG 为什么在这类文档上翻车:

    环节传统做法后果
    抽取OCR/文本抽取图表变碎片、表格拉成一行乱串
    切分按字符切 chunk空间/版式关系全丢
    拼装一堆独立工具粘一起脆、难维护、每个组件各有坑
  • 它能做什么(功能):

    • 多模态检索:文本、PDF、图片、视频用同一个入口检索(README 主打 ColPali)。
    • 快速可扩展的元数据抽取、规则处理、分类打标。
    • 与 Google Suite / Slack / Confluence 等集成。
  • 用起来什么样: 最小示例(来自 README.md,Python SDK):

    from morphik import Morphik

    morphik = Morphik("<your-morphik-uri>")
    morphik.ingest_file("path/to/your/super/complex/file.pdf") # 摄入
    morphik.query("What's the height of screw 14-A in the chair assembly?") # 提问
  • 一句话直觉/类比: 把它想成「给文档拍照片再问的搜索引擎」。传统 RAG 是把书抄成盲文再检索(视觉信息全没了);Morphik 是把书每页拍成照片,让一个既懂图又懂字的模型直接看图找答案。

本节不谈代码。记住一件事:Morphik 的核心赌注是「整页当图片 + 多向量晚交互」。

2. 顶层全景(它大概怎么转)

Morphik 分两条主干:摄入(ingestion,离线、异步)检索+问答(query,在线、同步)。两条干道共用同一套向量存储。

2.1 顶层结构图

怎么读这张图:上半是「摄入」(文件进来变成向量落库),下半是「问答」(问题进来捞向量再让 LLM 答)。中间的双向量库是两条干道的交汇点。

=== 摄入(异步,arq/Redis 队列)===
上传文件 ──▶ FastAPI /ingest ──▶ 存原文件到 storage ──▶ enqueue 到 Redis

arq worker 取任务

解析(Docling/openpyxl/PyMuPDF) ─▶ 切 chunk ─▶ 两路 embedding

┌───────────────────────────────┴────────────┐
▼ ▼
普通文本 embedding ColPali 多向量(整页当图)
│ │
▼ ▼
pgvector 单向量库 多向量库(pgvector BIT / TurboPuffer)
└───────────────────┬─────────────────────────┘

============================ 双向量库(交汇点) ============================

=== 问答(同步)=== ▼
问题/图片 ──▶ FastAPI /query ──▶ retrieve_chunks(捞候选 chunk)

(可选)reranker 重排 ──▶ padding 补上下页


chunk 拼成 context(图片→data URI)──▶ LiteLLM 多模态补全


答案 + 引用来源

2.2 部件一句话职责

部件干什么在哪个文件
API 层FastAPI 路由,收请求、鉴权、性能埋点core/api.pycore/routes/*.py
DocumentService检索与问答的总编排(retrieve/query)core/services/document_service.py
IngestionService摄入编排:解析→切分→embedding→存储core/services/ingestion_service.py
异步 workerarq 后台任务,真正跑摄入core/workers/ingestion_worker.py
Parser各种文件 → markdown 文本 + 切 chunkcore/parser/morphik_parser.py
普通 embedding文本 → 单向量(走 LiteLLM)core/embedding/litellm_embedding.py
ColPali embedding图/文 → 多向量(ColQwen2.5)core/embedding/colpali_embedding_model.py
单向量库pgvector 常规相似度检索core/vector_store/pgvector_store.py
多向量库(慢)pgvector BIT[] + max_sim SQL 函数core/vector_store/multi_vector_store.py
多向量库(快)TurboPuffer + MUVERA FDE 单向量 ANNcore/vector_store/fast_multivector_store.py
Reranker交叉编码器重排(可选)core/reranker/flag_reranker.py
Completion拼 context、图片喂给 LLM、生成答案core/completion/litellm_completion.py

2.3 主线走一遍(高层)

摄入: 文件上传到 /ingest/file(core/routes/ingest.py:107)→ 原文件存进 storage → redis.enqueue_job("process_ingestion_job", ...) 丢进队列(core/routes/ingest.py:353)。worker 里 process_ingestion_job(core/workers/ingestion_worker.py:373)慢慢跑:解析、切 chunk、算两路 embedding、写两个向量库。摄入是异步的——API 立刻返回一个 processing 状态的文档。

问答: 问题打到 /queryDocumentService.query(core/services/document_service.py:1032)先 retrieve_chunks 捞候选 chunk,再把 chunk(文本原样、图片转成 data URI)拼成 context,交给 completion_model.complete(core/completion/litellm_completion.py:620)让多模态 LLM 生成答案,附上来源引用。

3. 阅读地图(建议顺序)

这个项目大而全,按下面顺序读,由浅入深:

  1. 01-ingestion.md — 摄入流水线。 先搞懂「一份文件怎么变成向量落库」:异步队列为什么必要、Docling/openpyxl/PyMuPDF 各管什么、切分策略、双路 embedding。
  2. 02-colpali-multivector.md — ColPali 多向量晚交互(核心中的核心)。 Morphik 的招牌:整页当图片、每个 patch 一个向量、MaxSim 打分、二值化压缩。看懂这章就看懂了 Morphik 和普通 RAG 的本质区别。
  3. 03-retrieval-and-answer.md — 检索到答案。 retrieve_chunks 的四种配置矩阵、padding 补页、reranker、把图片喂给 LLM 的多模态拼装。
  4. 04-deep-dive.md — 深入与精华。 MUVERA FDE 把多向量塌成单向量、二值 MaxSim 的 SQL、双写迁移、边界与局限、完整代码地图。

只想抓精华:直接看 0204 的「巧妙之处」