跳到主要内容

数据截至 (上游 commit 8c51b8dc5408)

巧妙之处、边界与横向对比

本章讲什么: 读完前五章之后,值得抄走的技巧、必须知道的坑、以及它在同类项目里的位置。


1. 值得借鉴的技巧

1.1 用占位符维护"索引守恒"

妙在哪:chunk_number 被当作页号使用时,任何一页的缺失都会让后面全部错位。所以渲染失败时不能跳过,必须塞一张白图。

@staticmethod
def _placeholder_page_png() -> bytes:
buffer = BytesIO()
PILImage.new("RGB", (612, 792), "white").save(buffer, format="PNG")
return buffer.getvalue()

core/services/ingestion_service.py:1401(612×792 是 Letter 尺寸的点数)。

可迁移的规则: 只要你的下游依赖"第 i 个元素对应第 i 个原始单位",错误处理就必须是"替换"而不是"删除"。

1.2 批内闭环控制内存

妙在哪: "全部嵌入 → 全部入库"的写法在大文档上必然 OOM。改成每 16 页走完"嵌入 → 建对象 → 入库 → 释放"的完整闭环,峰值内存和文档大小解耦。

core/workers/ingestion_worker.py:1161-1213,靠 _create_chunk_objects(start_index=start_idx) 保证全局编号连续。

1.3 每个原生扩展都配一份等价 Python 实现

妙在哪: core/utils/fast_ops.py 里每个函数都是 if HAS_RUST: ... else: <纯 Python>。Rust 扩展编译失败、平台不支持、开发环境没装,程序照跑,只是慢。

更讲究的是行为对齐:控制字符清理的正则被特意写成和 Rust 版一致(fast_ops.py:22-26),注释写明"so the pure-Python fallback below produces byte-identical output"。

可迁移的规则: 性能扩展应该是加速器,不是硬依赖。

1.4 降级阶梯:宁可存成不可搜,也不丢文件

妙在哪: 第 01 章那三级降级的终点不是"失败",而是"成功但标记为不可检索"。用户上传的文件不会凭空消失,只是搜不到,而且状态字段说清楚了为什么。

core/workers/ingestion_worker.py:992-1018

1.5 一个坏候选不能 500 整个查询

妙在哪: 快路检索时如果某页的 .npy 丢了(删除与重摄取竞态、S3 最终一致性),丢掉这一个候选继续打分,而不是抛异常。

注释把踩过的坑也留下了——turbopuffer.Row 是 pydantic 模型,支持下标但没有 .get(),用 .get() 会在这条降级路径上再炸一次,"defeating the purpose of the fix"(core/vector_store/fast_multivector_store.py:576-579)。

这类注释比代码本身值钱:它记录的是修复的修复。

1.6 该做的优化实测反而要撤掉

妙在哪: 摄取时顺手把多向量写进磁盘缓存,看起来是白赚的。但注释算了另一笔账(fast_multivector_store.py:750-755):新摄取的页在被 LRU 淘汰前基本不会被查,这次写入只会挤掉真正的热数据,还给每页加一次同步 I/O。于是删掉,改由读路径首次未命中时回填。

1.7 一个配置管住所有出站并发

S3_UPLOAD_CONCURRENCY 同时被复用为慢路内容上传、快路多向量上传的信号量上限。运维只需要调一个数字。

1.8 用负载特征自适应切换策略

PDF 渲染前先算 MB/页,超过 1.0 就切成每次 2 页(ingestion_service.py:1586-1595)。用输入的统计特征而不是固定阈值来选执行策略,比"全都保守"和"全都激进"都好。


2. 边界与局限

诚实清单。有些是设计取舍,有些是待补的洞。

2.1 明确不做的事

不做什么依据
不做混合检索开 ColPali 就只用多向量结果(document_service.py:474-475),普通向量搜索连发都不发
一个文档只进一个库摄取时 if processed_chunks and not using_colpali 才生成普通嵌入(ingestion_worker.py:1045)
没有文档级 ACL访问控制只有 app_idowner_id 一层(postgres_database.py:1199-1217)
图片不能用于问答/query 直接拒绝 query_image(core/api.py:647-652)
不做知识图谱代码里有 EntityExtractionPromptOverride 等 prompt 模型(core/models/prompts.py),但核心检索路径没有图谱环节

2.2 参数存在但不生效

min_score 全程无效。 它出现在 RetrieveRequest(core/models/request.py:138)、SDK、retrieve_chunksretrieve_docsquery 的所有签名里,一路传递,但在 core/services/document_service.py 中从未参与任何过滤。想卡阈值只能在调用方自己做。

2.3 性能上会在哪崩

场景会发生什么
慢路 + 上万文档max_sim 无索引可用,全表扫描,延迟线性增长
padding + 纯文本结果返回空列表,不是"不做 padding"(document_service.py:593-596)
本地 ColPali + 大 PDFbatch_size = 1,几百页要跑几百次前向;job_timeout 已经放宽到 2 小时
高分辨率单图被压到宽 256、JPEG 质量 70(ingestion_service.py:1490-1499),细节丢失
Word/PPT 无 LibreOfficesoffice 不在 PATH 时转换直接跳过(morphik_parser.py:297-299),只能退回文本

2.4 依赖与许可

  • 快路强依赖 Turbopuffer,一个外部托管服务,需要 API key。自托管想要快路就得接受这个外部依赖。
  • 定长编码库的源码不在克隆内fde/ 只有 pyproject.toml,算法细节无法从这份代码核实。
  • 许可是 BSL 1.1,不是 OSI 开源。 个人/小规模免费,商业部署月营收超过 2000 美元需要商业授权,每个版本发布满四年后自动转 Apache 2.0(见 LICENSEREADME.md)。选型时必须先算这笔账。
  • README 明确说明自托管不提供完整支持

2.5 代码库自身的状态

  • core/database/postgres_database.py 3063 行、core/workers/ingestion_worker.py 2151 行、core/services/document_service.py 2204 行。几个核心文件都很大,读的时候建议先用符号名定位。
  • 存在 v1 / v2 两条并行的通道(ChunkV2StoreV2DocumentServiceprocess_v2_ingestion_jobcore/routes/v2.py),v2 把 app_id/folder_path/page_number 平铺成列。两套目前共存。
  • 慢路检索把查询向量拼进 SQL 字符串而不是参数化(multi_vector_store.py:743-748),代码标注为内部用途。删除操作也用了 f-string 拼 document_id(第 937 行)。
  • 部分注释掉的错误处理还留在文件里(如 multi_vector_store.py:819-822)。

3. 横向对比

3.1 它在 RAG 光谱上的位置

纯文本 RAGMorphik Core
解析PDF → OCR/解析 → 文本块PDF → 渲染成页面图片
表示单向量(768~3072 维)多向量(每页上千个 128 维)
检索余弦相似度 top-kMaxSim top-k
喂给模型文本块进提示词页面原图进提示词的 image_url
取舍省钱、成熟、可解释保留版面、图表可检索、存储与算力更贵

3.2 关键取舍维度

下表是选型时该问自己的问题,以及 Morphik 的答案。同货架其他 rag-retrieval 项目在同一维度上的答案见各自的子库文档。

维度Morphik Core 的答案
文档理解入口渲染成图,交视觉模型;只有非原生格式才走 Docling 抽文本
检索粒度页(ColPali 路径)或 6000 字符文本块
向量表示多向量(late interaction),不做池化
索引结构慢路无索引全扫;快路定长编码 + ANN + 精确重排
混合检索不支持,ColPali 与文本路互斥
摄取模型全异步队列,HTTP 只排队
组件可替换性高:嵌入/补全经 LiteLLM,存储/向量库/重排器都是可换实现
多租户单层 app_id,贯穿数据库、存储路径、向量命名空间
许可BSL 1.1(非 OSI 开源)

3.3 什么时候不该选它

  • 文档就是纯 Markdown/代码/日志 → 视觉那套没有收益,只有成本。
  • 需要真正的混合检索(稀疏 + 稠密融合)→ 这份代码里两条路是互斥的。
  • 需要细粒度权限(按用户、按文档、按字段)→ 只有 app_id 一层,要自己加。
  • 商业规模部署且不愿买授权 → 先读 LICENSE

3.4 同货架可对照的兄弟项目

同在 ai-agent-referencerag-retrieval 区,处理相邻问题、取舍不同的有:ragflowr2rkotaemondoclingmineruleanndsragchonkietxtai。其中 docling 正是 Morphik 自己在文本路上依赖的解析器,mineru 与它在"文档理解入口"这一维度上直接可比。具体差异请读各自的子库文档。


4. 总代码地图

4.1 想理解全局,按这个顺序读

步骤文件路径符号名为什么先读它
1core/services_init.pydocument_servicecolpali_vector_store一屏看完所有组件怎么装配
2core/workers/ingestion_worker.pyprocess_ingestion_job摄取全流程的唯一主线
3core/services/document_service.pyretrieve_chunks检索全流程的唯一主线
4core/vector_store/multi_vector_store.pyinitialize(看 max_sim SQL)MaxSim 最直白的一份实现
5core/vector_store/fast_multivector_store.pyquery_similar两阶段检索的完整六步
6core/completion/litellm_completion.pyprocess_context_chunks图片进模型的那一刻

4.2 按主题查

主题文件路径符号名
应用入口 / 生命周期core/api.pycore/app_factory.pyapplifespan
组件装配core/services_init.pyparserembedding_modelcompletion_modelreranker
配置core/config.pymorphik.tomlSettingsget_settings
摄取接口core/routes/ingest.pyingest_fileingest_textrequeue_ingest_jobs
摄取服务core/services/ingestion_service.pyingest_file_content_create_chunks_multivector_store_chunks_and_doc
后台 workercore/workers/ingestion_worker.pyprocess_ingestion_jobWorkerSettings_get_worker_colpali_store
解析core/parser/morphik_parser.pycore/parser/xml_chunker.pyMorphikParserRecursiveCharacterTextSplitterXMLChunker
视频解析core/parser/video/parse_video.pyVideoParser
ColPali 嵌入core/embedding/colpali_embedding_model.pycolpali_api_embedding_model.pyColpaliEmbeddingModelColpaliApiEmbeddingModel
通用嵌入core/embedding/litellm_embedding.pyLiteLLMEmbeddingModel
多向量存储core/vector_store/multi_vector_store.pyfast_multivector_store.pydual_multivector_store.pyMultiVectorStoreFastMultiVectorStoreDualMultiVectorStore
单向量存储core/vector_store/pgvector_store.pychunk_v2_store.pyPGVectorStoreChunkV2Store
检索与问答core/services/document_service.pyretrieve_chunksretrieve_chunks_groupedquery_apply_padding_to_chunks
数据库与权限core/database/postgres_database.pymetadata_filters.pyfind_authorized_and_filtered_documents_build_access_filter_optimized
补全与多模态提示词core/completion/litellm_completion.pyLiteLLMCompletionModelprocess_context_chunksformat_user_content
重排core/reranker/flag_reranker.pyFlagReranker
存储core/storage/local_storage.pys3_storage.pyutils_file_extensions.pyLocalStorageS3Storageis_colpali_native_format
认证与配额core/auth_utils.pycore/limits_utils.pyverify_tokencheck_and_increment_limits
原生加速与兜底core/utils/fast_ops.pymorphik_rust/src/binary_quantize_packedclean_control_chars
v2 并行通道core/routes/v2.pycore/services/v2_document_service.pyV2DocumentServiceprocess_v2_ingestion_job
测试(行为最好的说明书)core/tests/unit/core/tests/integration/test_multivector.pytest_colpali_integrate_multivector.py