Files
mnote/design/07-ai/process/7-54-raganything-multimodal-retrieval-alignment-v1.md
T

19 KiB
Raw Blame History

7-54 RAG-Anything 对照后的多模态检索设计 v1

2026-06-28 覆盖说明:本文围绕 LightRAG 增强撰写,已被 WeKnora / OpenHub 知识库主线覆盖。保留为多模态检索参考材料,不作为当前实施依据。

创建时间:2026-06-08

当前状态:process

Owner07-ai / 03-rust-web / knowledge-rag / search

取代:7-53 已放弃,不再作为搜索方案依据。

参考代码:

  • reference-code/RAG-Anything
  • repohttps://github.com/HKUDS/RAG-Anything
  • commit1bbca28
  • CodeGraphcodegraph init && codegraph index . --force --quiet49 files / 922 nodes / 2308 edges
  • 关键文件:raganything/query.pyraganything/processor.pyraganything/utils.pyraganything/config.pyREADME.mddocs/multimodal_rag_failure_modes.md

1. 新结论

7-53 的问题是仍在围绕“普通搜索是否混入 OCR”打转,设计重心不对。

RAG-Anything 的检索设计说明:成熟的 PDF / DOCX / Office / 图片检索,不应该依赖 MNote 自己扫 OCR sidecar 做字符串匹配,而应该把多模态内容作为一等内容写进 RAG 索引:

PDF / DOCX / Office / Image
  -> parser content_list
  -> text content -> LightRAG text insert
  -> image/table/equation -> multimodal processor
  -> templated multimodal chunks
  -> LightRAG text_chunks + chunks_vdb + entities_vdb + graph
  -> query uses vector + graph retrieval

所以 MNote 的目标应改为:

  1. 默认普通搜索仍可保持轻量本地搜索。
  2. 必须有明确的“资料库检索 / 全盘资料库检索”入口,覆盖用户显式纳入资料库索引范围的 PDF、DOCX、Office、图片 OCR。
  3. 全盘资料库检索必须走 LightRAG 的 chunk/reference 检索,不再走 MNote 侧 OCR sidecar 字符串补结果。
  4. 多模态资料的 ingest 要尽量变成 RAG-Anything 式的结构化 chunk / entity / relation,而不是只生成 wrapper Markdown。
  5. 搜索结果必须是可打开的来源命中;问答答案是另一层能力。
  6. “全盘”只表示在已配置资料库索引目录内全盘检索,不表示自动索引整个 local folder。

2. RAG-Anything 是怎么做检索的

2.1 查询入口分三种

raganything/query.py 提供三类 query。

第一类是纯文本查询:

query_param = QueryParam(mode=mode, **kwargs)
result = await self.lightrag.aquery(query, param=query_param, system_prompt=system_prompt)

它直接复用 LightRAG 的 local/global/hybrid/naive/mix/bypass 模式。README 推荐示例里人工检索常用 mode="hybrid"

第二类是 VLM enhanced query

query_param = QueryParam(mode=mode, only_need_prompt=True, **kwargs)
raw_prompt = await self.lightrag.aquery(query, param=query_param)

它先只拿 LightRAG 检索出来的 prompt/context,不直接生成最终答案;然后从检索上下文中提取 Image Path:,做安全目录校验,编码图片为 base64,再把文本 context + 图片一起交给 VLM 回答。

第三类是显式 multimodal query

enhanced_query = await self._process_multimodal_query_content(query, multimodal_content)
result = await self.aquery(enhanced_query, mode=mode, system_prompt=system_prompt, **kwargs)

用户把表格、公式、图片等内容作为 query 附件传入时,它先用对应 processor 生成描述,再把描述拼回查询文本,最后仍走 LightRAG 检索。

2.2 ingest 不是 OCR sidecar,而是一等 chunk

raganything/utils.py 先把 parser 的 content_list 分成:

text_content
multimodal_items

文本走 LightRAG 原生 ainsert(...),并传 file_paths 用于引用。

多模态内容走 raganything/processor.py

  1. 图片、表格、公式先用对应 processor 生成增强描述。
  2. _apply_chunk_template(...) 把原始路径、caption、footnote、邻近文本、章节路径、表格 body、公式文本、增强描述拼成可检索 chunk。
  3. _convert_to_lightrag_chunks_type_aware(...) 生成 LightRAG 标准 chunk
{
  "content": formatted_chunk_content,
  "tokens": tokens,
  "full_doc_id": doc_id,
  "chunk_order_index": chunk_order_index,
  "file_path": file_ref,
  "is_multimodal": True,
  "modal_entity_name": entity_info["entity_name"],
  "original_type": data["content_type"],
  "page_idx": data["item_info"].get("page_idx", 0),
}
  1. chunk 同时写入:
lightrag.text_chunks
lightrag.chunks_vdb
  1. 多模态主实体写入:
knowledge graph
entities_vdb
full_entities
  1. 对多模态 chunk 调 LightRAG extract_entities(...),再追加 belongs_to 关系,最后调用 merge_nodes_and_edges(...) 合并回 graph。
  2. doc_status.chunks_list/chunks_count 追加多模态 chunk id。

这才是“全盘资料库可检索”的关键:图片、表格、公式不是旁路 OCR 文件,而是进入 LightRAG 的 chunk 向量库和图谱。

2.3 检索失败排查也围绕多模态索引

RAG-Anything 的 failure checklist 不是让 UI 再补一层字符串搜索,而是检查:

  • parser Markdown / content_list.json 是否可信。
  • table block 的 table_body 是否保留结构。
  • image path / caption / page index 是否对齐。
  • multimodal processing flags 是否启用。
  • embedding path 是否看到 enriched text,而不是只看到 raw OCR。

这对 MNote 很重要:搜索不准时,第一诊断点应该是“资料有没有以正确 chunk 进入 LightRAG”,不是前端结果列表怎么融合。

3. MNote 当前差距

当前 MNote 已经有:

  • /api/knowledge-rag/ingest
  • /api/knowledge-rag/query
  • source registry
  • LightRAG /documents/scan
  • /query/data + references 映射
  • citationUrl / citationMarkdown / locator enrich

但普通搜索仍有一条不成熟路径:

/api/search/documents
  -> local_search_index
  -> includeOcr 时追加 query_knowledge_rag_ocr_sidecar_results

这条 knowledge_rag_ocr_sidecar_results 是 MNote 遍历 registry sidecar 后做文本匹配,不是 LightRAG vector/graph retrieval。它会让搜索看起来“用了 OCR”,但排序、召回、语义、图谱关系和多模态 chunk 都不是一套成熟系统。

另一个差距是 ingest。MNote 当前对 LightRAG scan 支持文件直接 symlink;对非 scan 支持文件生成 Markdown wrapper

# file
![file](<asset>)

这对图片入口有帮助,但不等于 RAG-Anything 的 multimodal chunk / entity / relation 管线。后者会把图片、表格、公式的描述、上下文、页码、路径都写进 LightRAG chunk 和 graph。

另一个必须保留的边界是索引范围。MNote 是 local-folder 产品,不能默认把整个本地目录都纳入 LightRAG。用户需要人工控制哪些目录参与资料库检索,否则会带来:

  • 大目录首次索引成本不可控。
  • 隐私和权限边界不清。
  • 搜索噪音扩大,资料库结果质量下降。
  • 删除/移动/重建的 watcher 压力过高。

因此资料库检索依赖显式索引目录,而不是自动覆盖整个 workspace。

4. 新搜索边界

4.1 默认搜索

默认搜索只做快速定位:

  • Markdown 页面标题
  • Markdown 正文
  • 路径
  • 最近打开 / 最近修改
  • 本地轻索引资源元信息

默认不混入 OCR sidecar,也不混入 LightRAG 语义结果。

4.2 全盘资料库检索

搜索弹窗必须有明确入口:

范围:当前页 / 工作区 / 全盘资料库

选择 全盘资料库 后:

  • 调 LightRAG retrieval,而不是 MNote sidecar grep。
  • 覆盖已纳入资料库索引目录的 PDF、DOCX、Office、图片 OCR、表格、公式、Markdown。
  • 返回 references/chunks 作为搜索结果。
  • 不默认生成 answer。
  • 每条结果有来源、snippet、类型、页码 / bbox / block / resource fallback。
  • 结果能直接打开对应 resource tab。
  • 对未入库目录明确显示“未纳入资料库索引”,而不是让用户误以为 LightRAG 漏检。

建议协议:

POST /api/knowledge-rag/search

请求:

{
  "workspaceId": "...",
  "rootUri": "file:///...",
  "query": "压缩率 Canterbury corpus",
  "mode": "hybrid",
  "topK": 12,
  "chunkTopK": 24,
  "includeChunkContent": true,
  "includeReferences": true,
  "answer": false
}

实现上可以先复用 /api/knowledge-rag/query/query/data 调用,只是响应包装成 search results,不展示 answer。

4.2.1 资料库索引范围

MNote 需要把“全盘资料库检索”建立在显式索引范围上:

Knowledge Search Scope
  includeRoots:
    - docs/research
    - resources/papers
  excludePatterns:
    - "**/draft-private/**"
    - "**/.git/**"
    - "**/node_modules/**"
  runOnChange: true|false

产品语义:

  • includeRoots 是用户主动加入资料库索引的目录或文件。
  • excludePatterns 后期必须支持,用于排除私密目录、临时目录、构建产物、大型无关资产。
  • LightRAG 只保证检索这些已入库范围。
  • 普通本地搜索仍可搜索 Markdown / 本地轻索引,不要求 LightRAG 入库。
  • 如果用户在全盘资料库模式下搜不到某文件,UI 要能提示它是否未入库、入库中、失败、已排除或已删除。

实现上,source registry 需要从“单个 source 列表”升级为“索引范围 + source 状态”:

.mnote/index/lightrag-source-registry.json
  indexedRoots[]
    rootRelativePath
    recursive
    excludes[]
    runOnChange
    updatedAt
  entries[]
    sourceRootRelativePath
    sourceHash
    lightRagDocId
    chunkIds
    status
    excludedBy

这保留了当前“人工控制哪些需要全盘搜索”的价值,同时给后续自动 watcher / 增量重建留出边界。

4.3 Knowledge Ask

Knowledge Ask / Page AI 才生成答案:

  • 用同一套 LightRAG references。
  • 展示 Context used
  • 引用使用 citationMarkdown / citationUrl
  • 可选 VLM enhanced:先拿 retrieved context,再把命中的图片资源交给 VLM 参与回答。

4.4 当前页搜索

当前页内查找仍不走 LightRAG

  • Markdowneditor / PageAggregate 内查找。
  • PDFviewer 文本层 / sidecar locator。
  • Office:预览/解析层查找。

这是局部查找,不是全盘检索。

5. MNote 应参考 RAG-Anything 的实施方案

Phase A:放弃 7-53,移除 sidecar 补结果主路径

目标:先停止把 sidecar grep 当成熟 OCR 搜索。

改动:

  1. 默认搜索关闭 includeOcr
  2. includeOcr 不再调用 query_knowledge_rag_ocr_sidecar_results 作为普通结果补充。
  3. UI 增加 全盘资料库 scope,先复用 /api/knowledge-rag/query 的 references 返回。
  4. 结果标注 provider=lightragmatchSource=lightrag_reference

验收:

  • 默认搜索短词不出现 PDF/OCR 假阳性。
  • 选择全盘资料库后可以搜到 PDF/DOCX/OCR 资料。
  • 搜索结果能打开对应 resource tab。

Phase B:新增 references-only retrieval facade

目标:把问答和人工检索分开。

新增:

POST /api/knowledge-rag/search

内部:

  • 调 LightRAG /query/data
  • include_references=true
  • include_chunk_content=true
  • mode=hybridmix,后续用 benchmark 决定默认值。
  • 不把 answer 放入搜索结果。
  • 对 references 做 registry mapping、locator enrich、ranking。

验收:

  • API 返回 mnote.knowledge_rag.search_results.v1
  • 每条 result 有 citationUrl / citationMarkdown / locatorPrecision
  • 对无 locator 的命中明确降级到文件级打开。

Phase CRAG-Anything 式多模态 chunk 入库

目标:让图片、表格、公式、Office/PDF 结构进入 LightRAG retrieval,而不是只存在 sidecar。

设计:

  1. MNote parser 输出统一 content_list
    • text
    • image
    • table
    • equation
    • generic
  2. text 走 LightRAG 文本 insert / scan。
  3. multimodal items 生成 templated chunk
    • source path
    • page index
    • block id / bbox
    • caption / footnote
    • table body
    • equation text / latex
    • neighbor text / section path
    • enhanced description
  4. 写入 LightRAG chunk/vector/graph,或通过扩展 LightRAG/RAG-Anything adapter 实现。
  5. registry 保存 sourceHash -> lightRagDocId -> chunkIds -> locator map

验收:

  • 查询“图 3 展示了什么”能召回图片 chunk,而不是只召回邻近正文。
  • 查询表格中的指标能召回 table chunksnippet 包含表格 body。
  • query result 能通过 chunkId 映射回 MNote resource tab 的 page/bbox/block。

Phase DVLM enhanced Ask

目标:回答中真正看图,而不是只读图片 caption。

参考 RAG-Anything

  1. 先让 LightRAG 返回 retrieval prompt/context。
  2. 从 context 中提取命中图片路径或 MNote resource id。
  3. 做 allowed roots / resource permission 校验。
  4. 把图片编码或转为可访问 asset URL。
  5. 文本 context + image 一起交给 VLM。

这一步只用于 Ask,不用于普通搜索结果。

6. 关键约束

  • 全盘资料库检索是必需能力,不能因为默认关闭 OCR 而消失。
  • 默认搜索和全盘资料库检索必须是两个明确模式,不能暗中混合。
  • 不再把 sidecar grep 当成熟资料库搜索。
  • locator 不足时只能声明降级,不能伪造页码/bbox。
  • file_path 不能作为唯一真相;MNote registry 必须维护绝对路径、root-relative path、source hash、LightRAG doc id、chunk id、locator map。
  • 多模态 chunk 的 source path / image path 必须做权限和安全目录校验。

7. 立即建议

下一步不要继续修 7-53。

应直接做:

  1. 删除/停用普通搜索里的 OCR sidecar 补结果。
  2. 新增 资料库索引范围 设置,至少支持 include rootsexcludes 先设计协议,后续实现。
  3. 新增 全盘资料库 搜索 scope,只检索已入库范围,调用 LightRAG references。
  4. 7-54 Phase A/B 的 smoke:默认搜索不混 OCR;未入库 PDF/DOCX 在资料库检索中明确提示未入库;加入索引目录后可搜到并打开。
  5. 后续再做 RAG-Anything 式多模态 chunk 入库。

这样既保留 MNote 的核心价值,也把检索系统拉回成熟 RAG 项目的设计方向。

8. Phase A/B 实施记录

时间:2026-06-08

已完成:

  1. 普通搜索不再把 includeOcr=true 转成 query_knowledge_rag_ocr_sidecar_results 补结果;/api/search/documents 的边界固定回 ordinary_local_searchknowledgeRag=falseocrSidecarFallback=false
  2. 新增 POST /api/knowledge-rag/search,作为 references-only retrieval facade
    • 调 LightRAG /query/data
    • include_references=true
    • 默认保留 include_chunk_content=true
    • 复用 registry mapping / locator enrich / citationUrl / citationMarkdown
    • 返回 mnote.knowledge_rag.search_results.v1
    • 搜索结果标记 provider=lightragmatchSource=lightrag_reference
  3. source registry 增加兼容字段 indexedRoots[]ingest 显式 source 时记录 include root
    • rootRelativePath
    • recursive
    • excludePatterns
    • runOnChange
    • updatedAtMs 旧 registry JSON 缺少 indexedRoots 时默认空数组。
  4. 搜索弹窗新增 全盘资料库开关;默认工作区搜索发送 includeOcr=false,打开开关后调用 /api/knowledge-rag/search
  5. scripts/task538-knowledge-rag-source-scope-api-smoke.js 增加 /api/knowledge-rag/search 验证,确认 sourcePaths post-filter 后只返回目标 source,且结果来自 LightRAG references 并可打开来源。

验证:

node --check rust/crates/mnote-web/browser/sidebar-tree-runtime.js
node --check scripts/task538-knowledge-rag-source-scope-api-smoke.js
cargo check --manifest-path rust/Cargo.toml -p mnote-web
cargo test --manifest-path rust/Cargo.toml -p mnote-web search_documents_route_is_owned_by_mnote_web -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web search_documents_local_folder_does_not_promote_evidence_sqlite_body_hits -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web indexed_root -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web references_only_search_result -- --nocapture
node scripts/task125-rust-web-search-server-first-smoke.js
node scripts/task538-knowledge-rag-source-scope-api-smoke.js
Playwright 登录态验证:默认搜索 includeOcr=false;全盘资料库开关调用 /api/knowledge-rag/search

scripts/task128-rust-web-wolai-search-modal-smoke.js 未作为本次验收依据:当前默认登录态要求下它直接访问 / 等待 topbar,曾因未建立登录态而超时。已用登录态 Playwright 脚本覆盖本次新增搜索 scope 行为。

未完成:

  • excludePatterns 仅进入 registry 协议,尚未实现 UI 编辑和 glob 过滤。
  • RAG-Anything 式 multimodal chunks / entities / relations 入库仍是 Phase C,未标完成。

9. Phase B.1chunk 上下文段落级定位

时间:2026-06-08

新增结论:

当前用户需要的是“搜索结果点击后落到对应段落”,不是必须文字级 bbox。对 DOCX / Office 这类当前缺少 page/bbox 的资料,短期不应强制改成 DOCX -> PDF viewer 管线;更低风险的路径是复用 LightRAG 已返回的 chunk 上下文做段落级定位。

参考 rag-knowledge-system 的可采用点:

  • 检索结果必须保留稳定 chunk metadata。
  • 点击定位不能只用 query 短词,应使用 chunk/snippet 上下文作为 anchor。
  • 目录/文件夹 scope 是 retrieval 层的过滤条件,不是 UI 结果列表后处理。

不直接照搬的点:

  • rag-knowledge-system 的 PDF highlight 先有 page_num,再在页内 search_for(anchor)MNote 当前 DOCX sidecar 没有页码,所以只能先做到文档内段落级定位。
  • 对 DOCX 缺 page/bbox 时,不能伪造页码或 bbox,应返回 locatorPrecision=paragraph / locatorDegraded=true

设计:

LightRAG /query/search
  -> chunk content / occurrence
  -> MNote registry mapping
  -> sidecar block match
  -> locator:
       blockId
       sourceMapPath
       evidenceText = query-centered block/chunk context
       locatorPrecision = bbox | paragraph | file
  -> office-preview:
       clean evidenceText
       generate multiple anchors
       score rendered paragraphs/tables by context overlap
       scroll/highlight best paragraph
       fallback to old short text candidates only after paragraph scoring fails

验收样例:

  • 吡咯烷:不应因为短词优先命中 N-甲基吡咯烷酮,应优先选择与 returned chunk 上下文整体重合最高的段落。
  • 三乙基硅 / 三甲基硅:存在 bbox 时仍使用 bbox;没有 bbox 时段落级定位,不伪造页码。
  • 同一已打开 DOCX 点击不同搜索结果时,resource tab 不重新加载,只通过 postMessage 更新 locator 并重新定位。

降级语义:

  • locatorPrecision=bbox:有 page + bbox,可页内精确框选。
  • locatorPrecision=paragraph:有 block/context,但缺 page/bbox,只保证段落级定位。
  • locatorPrecision=file:只能打开文件,定位信息不足。