Files
mnote/design/07-ai/reference/7-54-raganything-multimodal-retrieval-alignment-v1.md
T
Agent Board b798f628ee chore: land tree view-state, vault, Pi module split, and repo hygiene
Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
2026-07-21 05:13:05 +08:00

508 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 7-54 RAG-Anything 对照后的多模态检索设计 v1
> 2026-07-19 归类说明:当前知识库主线是 LightRAG + Pi Rust Page AI。本文保留为多模态索引与失败模式的参考材料,不作为当前可直接执行的实施稿。
> 创建时间:2026-06-08
>
> 当前状态:`reference`
>
> Owner07-ai / 03-rust-web / knowledge-rag / search
>
> 取代:`7-53` 已放弃,不再作为搜索方案依据。
>
> 参考代码:
> - `reference-code/RAG-Anything`
> - repo`https://github.com/HKUDS/RAG-Anything`
> - commit`1bbca28`
> - CodeGraph`codegraph init && codegraph index . --force --quiet`49 files / 922 nodes / 2308 edges
> - 关键文件:`raganything/query.py`、`raganything/processor.py`、`raganything/utils.py`、`raganything/config.py`、`README.md`、`docs/multimodal_rag_failure_modes.md`
## 1. 新结论
7-53 的问题是仍在围绕“普通搜索是否混入 OCR”打转,设计重心不对。
RAG-Anything 的检索设计说明:成熟的 PDF / DOCX / Office / 图片检索,不应该依赖 MNote 自己扫 OCR sidecar 做字符串匹配,而应该把多模态内容作为一等内容写进 RAG 索引:
```text
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。
第一类是纯文本查询:
```python
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
```python
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
```python
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
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
```python
{
"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),
}
```
4. chunk 同时写入:
```text
lightrag.text_chunks
lightrag.chunks_vdb
```
5. 多模态主实体写入:
```text
knowledge graph
entities_vdb
full_entities
```
6. 对多模态 chunk 调 LightRAG `extract_entities(...)`,再追加 `belongs_to` 关系,最后调用 `merge_nodes_and_edges(...)` 合并回 graph。
7. `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
但普通搜索仍有一条不成熟路径:
```text
/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
```text
# 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 全盘资料库检索
搜索弹窗必须有明确入口:
```text
范围:当前页 / 工作区 / 全盘资料库
```
选择 `全盘资料库` 后:
- 调 LightRAG retrieval,而不是 MNote sidecar grep。
- 覆盖已纳入资料库索引目录的 PDF、DOCX、Office、图片 OCR、表格、公式、Markdown。
- 返回 references/chunks 作为搜索结果。
- 不默认生成 answer。
- 每条结果有来源、snippet、类型、页码 / bbox / block / resource fallback。
- 结果能直接打开对应 resource tab。
- 对未入库目录明确显示“未纳入资料库索引”,而不是让用户误以为 LightRAG 漏检。
建议协议:
```text
POST /api/knowledge-rag/search
```
请求:
```json
{
"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 需要把“全盘资料库检索”建立在显式索引范围上:
```text
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 状态”:
```text
.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=lightrag``matchSource=lightrag_reference`
验收:
- 默认搜索短词不出现 PDF/OCR 假阳性。
- 选择全盘资料库后可以搜到 PDF/DOCX/OCR 资料。
- 搜索结果能打开对应 resource tab。
### Phase B:新增 references-only retrieval facade
目标:把问答和人工检索分开。
新增:
```text
POST /api/knowledge-rag/search
```
内部:
- 调 LightRAG `/query/data`
- `include_references=true`
- `include_chunk_content=true`
- `mode=hybrid``mix`,后续用 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_search``knowledgeRag=false``ocrSidecarFallback=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=lightrag``matchSource=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 并可打开来源。
验证:
```text
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`
设计:
```text
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`:只能打开文件,定位信息不足。