开源分析-joyagent多模态RAG实现
摘要多模态 RAG 的常见痛点是 PDF 里的图片、表格、扫描件检索不到关键词命中率低信息不全时 LLM 容易瞎编。JoyAgent 的 mrag 用 MinerU 把 PDF 拆成文本、图片、页面截图分别入库检索时稠密向量与 BM25 稀疏向量并行召回再 cross-encoder 重排信息不够就自动改写查询多轮查直到够用。一、多模态 RAG 要解决什么企业里常见的资料是产品手册 PDF、销售报告 Word、架构图图片、各种表格。想问「产品 X 的性能指标是多少」「这张架构图说明了什么」直接问通用大模型行不通——它没见过内部资料。把资料全塞进 prompt 也不现实量太大、成本高而且上下文窗口装不下。RAGRetrieval-Augmented Generation检索增强生成的做法是先把资料处理成可检索的形式存起来提问时先检索相关片段再把片段连同问题一起交给大模型回答。相当于给大模型外挂了一个「专属资料库」。为什么要「多模态」普通 RAG 只能处理文字。但真实资料里有图、有表、有图表。问「分析这张走势图」纯文字 RAG 抓瞎。多模态 RAG 能理解图片内容把图也纳入检索和回答。这就是 mrag 名字里「m」multimodal的含义——同时处理文本和图像两种模态。二、整体架构与两条链路mrag 有两条主要链路写入建知识库和查询问答。理解这两条链路就理解了整个模块。写入流文档上传 → 异步下载 → 解析MinerU 输出 markdown 图片 页面预览→ 图片和页面上传 OSS 替换链接 → 切分 → 三种嵌入文本/图像/BM25→ 存 Qdrant SQLite 元数据。查询流提问 → Agentic 多轮循环生成子查询 → 4 路并发检索 → 总结 → 判断信息够不够→ 重排过滤 → LLM/VLM 生成 → SSE 流式返回。下面逐层拆。三、写入链路第一步文档解析3.1 为什么解析这么麻烦上传 PDF 不能直接用。PDF 里的文字、图片、表格混在一起大模型没法直接读得先拆开文字提取成 markdown对 LLM 友好图片单独抽出来要向量化每页渲染成预览图页面级检索用。document/parser.py干的就是这件事。3.2 MinerUPDF 解析的主力MinerU 是什么上海 AI Lab 开源的文档解析工具能把 PDF尤其是带图表、公式的复杂 PDF解析成 markdown 图片。mrag 不在本地跑 MinerU而是调它的 HTTP API——需要单独部署一个 MinerU 服务配MINERU_BASE_URL。代码里PdfParser在document/parser.py:948起_call_mineru_api在 990 行提交任务拿task_id_wait_for_mineru_result在 1004 行轮询结果。智能分页调度大 PDF 优化大 PDF 一次扔给 MinerU 容易超时。mrag 的办法10 页以下直接整文件处理超过就每 4 页拆一块逐块调 MinerU结果带!-- Chunk N: Pages x-y --标记拼接图片存images/chunk_{N}/子目录防冲突。分块处理在document/parser.py:1054的_process_single_pdf_chunk。配置SMALL_PDF_PAGE_THRESHOLD10、PDF_CHUNK_SIZE4。3.3 四种格式四种解析器get_document_parser工厂按扩展名分发docx 的标题识别有多细Word 文档的标题样式Heading 1-6会被识别成 markdown 的 H1-H6。中文文档常用的「一、」「一」「1.」「(1)」这种手动编号也会被映射成对应层级标题。这样切分时能按标题结构切上下文保留得好。统一产物结构所有解析器输出都长这样work_dir/├── {filename}.md # 纯文本 markdownLLM 能读├── images/ # 文档内提取的图表要向量化└── pages/ # 每页预览图 page_{i}.png页面检索用四、写入链路第二步文本切分4.1 为什么要切分一本书 10 万字不能整本塞给 LLM超 token 上限也不能整本变一个向量太粗检索不准。得切成小块chunk每块几百字独立向量化、独立检索。chunk 是什么chunk 文本块。把长文档切成一个个小段落每段是一个独立检索单元。比如一段 500 字的产品说明就是一个 chunk有自己的向量和元数据。检索时按 chunk 粒度返回。切分逻辑在document/splitter.py入口是get_text_splitter(chunk_type, chunk_size, chunk_overlap)工厂120 行起默认从环境变量读CHUNK_TYPEmarkdown/CHUNK_SIZE500/CHUNK_OVERLAP100。4.2 两种切分器自研切分器的核心是SplitSentencesplitter.py:21做句子切分再用双指针贪心splitter.py:153-207按chunk_size组句成块。为什么要有 overlap重叠假设一句话正好在切块边界被切成两半前半句在前一个 chunk后半句在后一个 chunk。检索时可能只命中一个语义不全。overlap 让相邻块重叠一部分比如 100 字。前一个 chunk 的末尾 100 字也是后一个 chunk 的开头 100 字。即使边界切断语义重叠部分能兜住。代价是存储冗余但检索质量好。markdown 切分为什么要保留标题层级一个 chunk 检索出来时光看 chunk 内容不知道它属于哪一节。切分时把「这个 chunk 在 ## 安装 ### Windows 安装 下」这种层级信息存进元数据检索时就能告诉 LLM 出处答案更靠谱。build_contextquery_processor.py:83干的就是这件事。五、写入链路第三步多模态嵌入核心创新这一步把文字、图片都变成向量存起来而且要让「文字 query 能搜到图片」。5.1 三种嵌入模型三种嵌入各干什么文本嵌入TextEmbeddingembedding/embedding.py:28把文字变稠密向量dense vector1024 个连续浮点数。稠密向量的特点是每个维度都有值能编码语义——搜「手机」能找到「智能手机」因为两者向量距离近。图像嵌入ImageEmbeddingembedding/embedding.py:59QwenVL 实现embedding/image_embedding.py:13把图片变向量让图片可被语义检索。BM25 嵌入embedding/bm25_embedding.py:4把文字变稀疏向量sparse vector大部分维度是 0只在命中的关键词维度上有值。BM25 是经典的关键词检索算法擅长精确匹配专有名词。它和稠密向量互补稠密向量懂语义但可能漏关键词BM25 懂关键词但不懂同义词一起用召回全。不是 ColQwen代码里image_embedding.py:18用的是 dashscope 的qwen2.5-vl-embedding一图一向量不是 ColQwen晚交互模型。区别ColQwen 给一张图生成多个向量patch 级把图切成小区域各算一个向量检索时做「晚交互」逐 patch 打分更准但更慢。qwen2.5-vl-embedding 一图一向量简单快。mrag 选了简单方案。5.2 图文跨模态对齐最巧妙的设计核心难题文字 query 怎么搜到图片文字向量和图像向量在不同的向量空间里直接比距离没意义——就像用米和千克比大小。mrag 的解法不是训练一个图文共享嵌入空间而是把图像转成文字描述再走文本嵌入OCR 和 Caption 是什么OCROptical Character Recognition光学字符识别识别图里有什么字比如柱状图上的标签、表格里的数字。机械提取utils/ocr_utils.py提供 DeepSeekOCR / VLMOCR 两种实现。Caption图像字幕用 VLM视觉语言模型描述图讲什么比如「这是展示季度销量趋势的柱状图Q3 最高」。语义理解utils/caption_utils.py调 VLM 生成不超过 100 字。两个都做OCR 给精确文字caption 给语义概括检索时都能命中。冗余但召回全。为什么这个设计聪明不依赖共享嵌入空间那个很难训练。靠 OCR Caption 把图像「翻译」成文字文字再走标准文本嵌入。这样文字检索能命中「图片内容」通过 ocr_text/caption 文本命中后通过 payload 里的image_id反查到原图降级友好——就算图像嵌入模型很差靠文字描述也能兜底代价是依赖 OCR 和 Caption 的质量且存了多份向量图像向 ocr 文本向 caption 文本向。5.3 三种 chunk 处理DocumentProcessor编排三种处理对应三种内容document/processor.py文本处理批量 10 个processor.py:123图像处理批量 5 个199 行都先 resize 再嵌入。子块增强可选配ENABLE_SUB_CHUNK_ENHANCE后processor.py:155文本块再用 100 字小切器二次切生成更细粒度的 chunk。提升小粒度检索召回代价是存储翻倍。LightRAG 图谱补充可选配LIGHTRAG_SERVER_BASE_URL后全文 POST 到 LightRAG 做 graph RAG 索引。Graph RAG 用知识图谱做关联检索是向量检索之外的另一种 RAG 范式这里作为补充。六、查询链路第一步Agentic 多轮检索核心这是查询链路的精髓。不是「检索一次就答」而是像人查资料一样多轮检索、反思、补充直到信息够用。6.1 为什么需要「Agentic」普通 RAGquery → 检索一次 → 拼进 prompt → 回答。简单但有几个问题复杂问题一次检索不够「对比 A 和 B 的优劣」要分别检索 A 和 B检索结果可能不相关但没有机制判断信息缺口没法补。Agentic RAG 让 LLM 当「检索指挥官」自主决定检索几次、每次查什么、什么时候信息够了。6.2 AgenticRAG 核心循环详解入口是AgenticRAG.run()query/aigent.py:154主循环在 182 行while True硬上限loop 3205 行。四大 Agentic 特征都齐了多轮检索循环最多 3 轮信息不够再来一轮query 改写每轮根据上轮结果改写查询子查询扩展把一个问题拆成多个同义/多视角子查询LLM 自我反思POST_CHECK 判断信息缺口并给出改写方向第 0 步——快速通道避免无谓检索aigent.py:157-170有图片输入 →extract_image_contentquery_processor.py:62生成图片 captionsimple_query_checkquery_processor.py:150问候/常识题如「你好」「11」直接llm_answer不检索simple_image_query_checkquery_processor.py:161图片问答类直接vlm_answer主循环while True硬上限 loop3POST_CHECK 充分性判断query_processor.py:104的generate_next_instructionPOST_CHECK 的精髓这是「反思」机制。LLM 检索完不急着答先自问「这些信息够回答原问题吗」。够就答不够就指出还缺什么rewrite_query 给出下一步检索方向。这模拟了人查资料的过程——查一次不够根据已有信息调整搜索词再查。第 3 步——重排生成aigent.py:239起循环退出后合并所有检索结果重排生成答案为什么 page 只取 top1page 是整页预览图。多页塞给 VLM 会超 token 且互相干扰。只取最相关的 1 页作为附图让 VLM 结合文本和这页图回答。这是 token 消耗和效果之间的权衡。七、查询链路第二步4 路并发检索检索是 RAG 的心脏。mrag 不只一路检索而是 4 路并发覆盖不同召回角度。入口在retrieval/retriever.py:38用ThreadPoolExecutor(max_workers4)并发提交。7.1 四路是什么路检索什么怎么检索阈值① 文本向量text 集合query 文本向量化 → cosine 搜0.7② BM25text 集合query BM25 稀疏向量化 → 关键词搜无③ 文本查图image 集合query 文本 → 图像向量空间搜0.6④ 文本查页面page 集合query 文本 → 页面向量空间搜0.35学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】