跳到主要内容

数据截至 (上游 commit 8c51b8dc5408)

Morphik Core — 架构与原理

30 秒导读: Morphik Core 是一个开源的多模态检索引擎。它和传统 RAG 最大的不同是:处理 PDF/PPT/Word 这类"看的比读的重要"的文档时,它不把文档抽成纯文本,而是把每一页渲染成图片,用视觉模型 ColPali 建索引;命中后把那一页的原图送给大模型看图作答。图表、版式、手绘标注因此不会在"抽文本"这步丢掉。


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

一句话定义

一个可自托管的文档检索后端:你把文件丢进去,它负责解析、切块、向量化、存储;你提问,它负责找出相关内容并调大模型给答案。

它想解决的问题

先看传统 RAG 的做法:PDF → 抽文本 → 切块 → 文本向量 → 相似度检索。

这套流程在视觉密集的文档上会塌:

文档里的东西抽成文本后变成什么
柱状图 / 折线图几个孤立的数字,失去坐标轴含义
工程图纸的尺寸标注散落的字符串,失去空间关系
跨页表格一串没有行列结构的文字
扫描件 / 手写批注OCR 噪声,或干脆是空

Morphik 的回答是:别抽了,直接看。把每一页原样渲染成图片交给视觉模型建索引。

给谁用

  • 要在合同、图纸、财报、说明书上做问答的开发者。
  • 需要自托管(数据不出内网)且要能换掉每个组件(嵌入模型、向量库、对象存储)的团队。

用起来什么样

下面是官方 Python SDK 的最小用法(源自 README.mdsdks/python/morphik/sync.py:1125retrieve_chunks 签名):

from morphik import Morphik

morphik = Morphik("<your-morphik-uri>")

# 摄取:异步排队,函数很快返回,后台 worker 慢慢处理
morphik.ingest_file("chair-assembly-manual.pdf")

# 问答:内部会先检索、再把命中页交给大模型
answer = morphik.query("14-A 号螺丝的长度是多少?")

# 只要检索结果、不要生成:padding=1 表示连命中页的前后各一页一起返回
chunks = morphik.retrieve_chunks("扭矩规格表", k=5, use_colpali=True, padding=1)

注意 use_colpali=True 这个开关——它决定了走"看图"这条路还是走传统文本向量那条路。这是整个项目最重要的一个参数。

一句话直觉

把每一页文档当成一张截图,把检索当成"在一堆截图里找出最像回答这个问题的那几张"。 找到之后不做转述,直接把截图递给会看图的模型。


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

两个面:写入是异步的,读取是同步的

这张图从上到下读。上半部分是写入面(摄取),下半部分是读取面(查询)。两个面之间只通过数据库和向量库耦合。

========================= 写入面(异步,可能几分钟) =========================

上传文件
|
v
FastAPI /ingest/file ---> 原文件落对象存储 (S3 / 本地盘)
| 文档行落 Postgres,status = processing
v
Redis 队列 (arq) <-- 立刻返回 Document 给调用方
|
v
ingestion worker ---> 解析 -> 切块 -> 向量化 -> 写向量库
最后把 status 改成 completed

========================= 读取面(同步,亚秒到数秒) =========================

提问
|
v
FastAPI /query ---> ① 先查"这个租户能看哪些文档"(拿到 doc_ids 白名单)
| ② 同时把问题编码成查询向量
v
向量检索(限定在白名单内)---> 命中若干 chunk(可能是页面图片)
|
v
组装上下文:文本原样,图片转成 image_url
|
v
视觉大模型 ---> 答案 + 出处(文件名 + 页码)

怎么读这张图: 写入面和读取面唯一的交汇点是"向量库 + Postgres 文档表"。写入没完成(status 不是 completed)的文档,读取面根本查不到——过滤条件写死在 core/services/document_service.py:340status_filter=["completed"]

部件一句话职责

部件干什么在哪个文件
FastAPI app对外 HTTP 接口,/ingest/*/retrieve/*/querycore/api.py:179core/routes/
服务单例装配按配置把数据库、存储、解析器、模型、向量库拼起来core/services_init.py
IngestionService落库 + 上传 + 入队,不做重活core/services/ingestion_service.py:60
ingestion workerarq 后台进程,干解析/向量化/写库的重活core/workers/ingestion_worker.py:448
MorphikParser文件 → 文本(Docling / openpyxl / 视频 / XML 多路)core/parser/morphik_parser.py:196
ColpaliEmbeddingModel页面图片 / 文本 → 多向量(每 token 一个 128 维向量)core/embedding/colpali_embedding_model.py:25
MultiVectorStore多向量的 Postgres 实现(位串 + max_sim SQL 函数)core/vector_store/multi_vector_store.py:39
FastMultiVectorStore多向量的快路实现(定长编码 + Turbopuffer ANN + 精确重排)core/vector_store/fast_multivector_store.py:305
PGVectorStore传统单向量 pgvector 存储(非 ColPali 路径用)core/vector_store/pgvector_store.py
DocumentService检索与问答的主逻辑core/services/document_service.py:43
LiteLLMCompletionModel把上下文(含图片)喂给任意厂商的大模型core/completion/litellm_completion.py:217

主线走一遍(高层,不进代码)

摄取一份 PDF:

  1. HTTP 收到文件 → 存进对象存储 → Postgres 建一行 status=processing → 往 Redis 塞一个任务 → 立即返回。
  2. worker 取出任务 → 从对象存储下载文件。
  3. 判断是不是 ColPali 原生格式(PDF/图片/Word/PPT)。是的话跳过文本解析,直接把每页渲染成 PNG。
  4. 每页图片过 ColPali 模型,得到一组多向量。
  5. 按批(默认 16 页)写进多向量库,页面图片本体写对象存储。
  6. status 改成 completed,文档可被检索。

回答一个问题:

  1. 鉴权,得到 app_id(云上)或 user_id(自托管)。
  2. 并行做两件事:把问题编码成查询多向量;查 Postgres 拿到"当前租户 + 过滤条件下可见且已完成"的文档 ID 列表。
  3. 在这个 ID 列表范围内做多向量检索,拿回 top-k 个页面。
  4. 可选:padding(把命中页的前后几页也捞出来当上下文)。
  5. 页面图片转成 data URI,连同文本块一起塞进 LLM 请求,图片走 image_url 通道。
  6. 返回答案 + sources(文档 ID、chunk 号、分数)。

3. 一眼看懂的关键设计选择

这几条是理解全项目的钥匙,细节在各章展开:

选择具体是什么后果
页即块ColPali 路径下,PDF 的第 N 页就是 chunk_number = N引用可以精确到页;渲染失败也要放一张白页占位以免页号错位
二选一,不混合一个文档要么进 ColPali 多向量库,要么进 pgvector,不会两边都进省一半算力和存储;代价是同一批文档无法真正混合检索
内容不进数据库chunk 的真实内容(页面 PNG、文本)存对象存储,库里只存 key数据库轻;代价是检索后要多一轮下载
摄取全异步HTTP 只负责排队,重活全在 arq worker上传大文件不会超时;代价是要自己轮询 status
先粗后精快路先用一个定长向量做 ANN 粗筛,再用完整多向量精确打分兼顾召回与延迟,是快路的核心把戏

4. 阅读地图

建议顺序就是编号顺序;只想解决具体问题的话按下表跳。

章节讲什么什么时候读它
01-ingestion-pipeline.md摄取六步、解析降级阶梯、批处理与重试想知道"我的文件为什么卡在 processing"
02-colpali-visual-retrieval.mdColPali 是什么、MaxSim 在算什么、模型怎么调想理解这个项目的核心创新
03-multivector-stores.md两套多向量存储的原理和取舍要选型,或觉得检索太慢
04-retrieval-and-answering.md检索四种配置、权限过滤、padding、多模态提示词想调检索效果
05-storage-tenancy-ops.md存储分层、多租户、进度与指标要部署或排障
06-design-notes.md值得借鉴的技巧、已知边界、横向对比、总代码地图读完想带走点什么

5. 代码地图(入口级)

主题文件路径符号名
进程入口start_server.py(脚本主体)
FastAPI 应用与生命周期core/api.pycore/app_factory.pyapplifespan
全部单例的装配处core/services_init.pydocument_serviceingestion_servicecolpali_vector_store
配置加载(toml → Settings)core/config.pySettingsget_settings
中央配置文件morphik.toml[morphik][multivector_store][parser]
摄取入口core/routes/ingest.pyingest_fileingest_text
后台任务与 worker 配置core/workers/ingestion_worker.pyprocess_ingestion_jobWorkerSettings
检索与问答主逻辑core/services/document_service.pyretrieve_chunksquery
ColPali 嵌入模型core/embedding/colpali_embedding_model.pyColpaliEmbeddingModel
多向量存储(慢路 / 快路)core/vector_store/multi_vector_store.pycore/vector_store/fast_multivector_store.pyMultiVectorStoreFastMultiVectorStore