151 lines
9.2 KiB
Markdown
151 lines
9.2 KiB
Markdown
# 7-52 LightRAG 图片 OCR 与搜索召回链路加固 v1
|
||
|
||
> 创建时间:2026-06-07
|
||
>
|
||
> 当前状态:`DONE`
|
||
>
|
||
> 2026-07-03 口径回正:当前 runtime 已回到 OpenHub / native agent + LightRAG + Turso/libSQL。本文重新作为当前默认 LightRAG provider 的图片 OCR 与搜索召回链路 hardening 基线;此前 `7-68 OpenHub + WeKnora + MNote Page AI 深度融合` 中将 WeKnora 设为默认 provider 的口径已标记 stale。
|
||
>
|
||
> Owner:07-ai / knowledge-rag / local-search
|
||
>
|
||
> 上位依据:`design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md`
|
||
|
||
## 1. 问题结论
|
||
|
||
这不是 Page AI 某次回答措辞错误,而是 LightRAG 集成链路同时暴露了三层缺口:
|
||
|
||
- 图片 ingest 缺口:MNote 对图片 source 写入 LightRAG input 时使用 Markdown wrapper,当前 indexed chunk 只有 `` 占位,没有把 MinerU OCR 文本写入 LightRAG chunk / reference。
|
||
- 工具契约缺口:`mnote_knowledge_rag_query` 把 `rawMetadata.keywords`、实体和关系计数暴露给 agent,但没有明确这些只是查询处理元数据,agent 容易把它误判为“source OCR 成功且已返回原文”。
|
||
- 搜索召回缺口:`/api/search/documents` 仍是普通 local search,`includeOcr=true` 目前不代表会查 LightRAG OCR 文本,因此搜索“线程作用域”不会命中目标图片。
|
||
|
||
## 2. 现场证据
|
||
|
||
目标 source:
|
||
|
||
```text
|
||
/mnt/Data1T/Mnote_data/users/mnote-e2e/workspaces/my-space/新页面233155/image copy 6.png
|
||
```
|
||
|
||
已确认的事实:
|
||
|
||
- LightRAG source registry 中该图片为 `processed`,`lightRagDocId=doc-781c4b5f39f135886eb6062d8606058e`,`lightRagFilePath=mnote-d0985c21239b677d-image copy 6.png.md`。
|
||
- `kv_store_text_chunks.json` 中对应 chunk 内容只有 `# image copy 6.png` 和 Markdown 图片占位。
|
||
- 直接调用 LightRAG 的 MinerU parser CLI 解析原 PNG 可以识别出包含“线程作用域”的文本,并生成 `blocks.jsonl` 与 bbox。
|
||
- `/query/data` 能在 metadata 里返回 `rawMetadata.keywords.high_level=["线程作用域"]`,但 references/chunks 没有返回这段 OCR 原文;这只能证明查询分析或图谱元数据命中,不能证明引用文本已暴露。
|
||
- `/api/search/documents` 查询 `线程作用域` 返回 0;代码中 `include_ocr` 只进入 meta,没有参与搜索。
|
||
|
||
## 3. 目标边界
|
||
|
||
本修复继续保持 7-50 的边界:
|
||
|
||
- LightRAG 是知识库 OCR / parse / chunk / vector / graph / query provider。
|
||
- MNote 不复制 LightRAG 图谱,不把 LightRAG chunk 当正文真相。
|
||
- MNote 必须负责 source registry、权限、引用打开、普通搜索入口的边界标注,以及 agent 工具契约。
|
||
- 普通搜索可以显示“资料库 OCR 命中”,但不能变成必须依赖 LightRAG 服务才能打开页面或搜索普通 Markdown。
|
||
|
||
## 4. 修复方案
|
||
|
||
### P0:工具契约防误判
|
||
|
||
`mnote_knowledge_rag_query` 返回给 agent 的 compact result 必须明确:
|
||
|
||
- `rawMetadata.keywords`、entity count、relation count 只是 provider 查询处理 / retrieval bookkeeping。
|
||
- OCR 是否真正暴露,以 `references[].quote` 和 `references[].contentDiagnostics.ocrTextExposed` 为准。
|
||
- 当 quote 为空或只有 Markdown 图片占位时,agent 必须说明“返回引用未暴露 OCR 原文”,不能声称 OCR 成功。
|
||
|
||
验收:
|
||
|
||
- 图片 wrapper quote `# image.png\n\n` 被标记为 `quoteOnlyImagePlaceholder=true`、`ocrTextExposed=false`。
|
||
- Agent 工具 compact result 不再把 raw metadata 当 OCR 成功证据;真实 Page AI 回答必须以该工具契约为准。
|
||
|
||
### P1:图片 ingest 让 OCR 文本进入 LightRAG 可引用内容
|
||
|
||
图片 source 不能只靠 Markdown wrapper 占位入库。需要二选一并以真实探针定案:
|
||
|
||
1. 优先方案:扩展 LightRAG `/documents/scan` 支持 `.png/.jpg/.jpeg/.webp/.bmp/.tif/.tiff`,并让 parser routing 对图片走 MinerU。MNote 对图片直接 symlink 原图入 `inputs/`,registry 记录真实图片 source 和 LightRAG basename。
|
||
2. 兼容方案:如果上游 scan 暂不支持图片,MNote connector 调用 LightRAG parser 能力为图片生成 provider sidecar,并写入包含 OCR 文本的受控 `.md` ingestion document,同时保留图片 openAction 指向原 source。该 `.md` 是 LightRAG staging 派生物,不进入 MNote 文件树正文。
|
||
|
||
无论选哪条,必须保证:
|
||
|
||
- LightRAG chunk/reference 中能返回 OCR 文本,不只是图片占位。
|
||
- `blocks.jsonl` 能通过 registry 反查到原图并生成 `EvidenceLocator`。
|
||
- reindex 会删除旧 wrapper doc,避免同一图片同时存在“占位 doc”和“OCR doc”。
|
||
|
||
验收:
|
||
|
||
- 重新索引目标图片后,`/query/data` 查询 `线程作用域` 的 references/chunks 至少一项包含 OCR 文本。
|
||
- `sourceRootRelativePath` 仍映射到 `新页面233155/image copy 6.png`。
|
||
- citation/openAction 打开原图资源,而不是打开派生 wrapper。
|
||
|
||
### P1:reference enrichment 与诊断
|
||
|
||
当 LightRAG 返回的 reference quote 为空、缺失或仅为图片占位时,MNote connector 应做有界补全:
|
||
|
||
- 先按 `chunk_id` 查 `kv_store_text_chunks.json`。
|
||
- 再按 registry 的 `lightRagFilePath` 查 `inputs/__parsed__/<file>.parsed/*.blocks.jsonl`。
|
||
- 对图片占位类 quote,不能直接拿占位去匹配 sidecar;应允许按 query terms / block text 补最相关 OCR block。
|
||
- 返回 `contentDiagnostics`,标明 `quoteSource=chunk|sidecar|missing|placeholder`、`ocrTextExposed`、`locatorSource`。
|
||
|
||
验收:
|
||
|
||
- OCR sidecar 已存在但 chunk 未带内容时,工具仍能返回一段真实 OCR quote,并标记来源为 `sidecar`。
|
||
- 若 sidecar 不存在,工具返回明确诊断,不把 metadata 当证据。
|
||
|
||
### P1:普通搜索 `includeOcr=true` 语义落地
|
||
|
||
`includeOcr=true` 不能继续是 no-op。推荐先做“普通搜索 + 资料库 OCR 有界补充”:
|
||
|
||
- 普通 Markdown / title / resource metadata 仍走本地轻索引。
|
||
- 当 `includeOcr=true` 且普通结果不足 limit 时,追加查询 LightRAG source registry + sidecar/chunk 的 OCR 文本结果。
|
||
- 搜索结果必须标注 `boundary.kind=ordinary_local_search_with_knowledge_ocr`,单条结果标注 `matchSource=knowledge_rag_ocr`。
|
||
- LightRAG 不可用时普通搜索不失败,只设置 `degraded=true`、`degradedReason=knowledge_rag_unavailable`。
|
||
|
||
验收:
|
||
|
||
- `/api/search/documents` 查询 `线程作用域` 且 `includeOcr=true` 能返回目标图片或 owner page。
|
||
- `includeOcr=false` 保持原普通搜索语义。
|
||
- 返回结果包含可打开的 resource locator/citation,不只是一条不可点击文本。
|
||
|
||
### P2:健康检查与 UI 暴露
|
||
|
||
资料库状态面板需要区分:
|
||
|
||
- source 是否 registered。
|
||
- LightRAG doc 是否 processed。
|
||
- OCR text 是否进入 chunk/reference。
|
||
- sidecar 是否存在且可用于 locator。
|
||
- 普通搜索是否能通过 `includeOcr` 召回。
|
||
|
||
验收:
|
||
|
||
- 对目标图片能显示“processed but OCR text not exposed in reference/chunk”这类可操作状态,而不是只有绿灯。
|
||
|
||
## 5. 执行 Checklist
|
||
|
||
- [x] P0 工具契约:compact reference 增加 `contentDiagnostics` 并补单测。
|
||
- [x] P0 agent 工具契约 smoke:确认 compact result 不再把 raw metadata 当 OCR 成功证据。
|
||
- [x] P1 ingest spike:验证 LightRAG 直接 scan 图片扩展 vs MNote 调 parser 生成 OCR ingestion doc,选择一条实现。
|
||
- [x] P1 reindex migration:删除旧图片 wrapper doc 后重新索引目标图片。
|
||
- [x] P1 reference enrichment:sidecar/chunk/query term 补 quote,返回明确 `quoteSource`。
|
||
- [x] P1 搜索:`includeOcr=true` 追加 knowledge OCR 命中,并保持 LightRAG 不可用时普通搜索降级。
|
||
- [x] P1 browser/API smoke:`线程作用域` 在 knowledge-rag 和 `/api/search/documents` 两条入口都可召回并可打开来源。
|
||
- [x] P2 状态 UI:把 OCR 文本是否真正进入 reference/chunk 暴露成诊断项。
|
||
|
||
## 7. 验收证据
|
||
|
||
2026-06-07 已完成目标图片重建索引:
|
||
|
||
- registry:`新页面233155/image copy 6.png` -> `lightRagDocId=doc-ac03e578a6c6af7eb0a3224276e4b954`,`lightRagFilePath=mnote-d0985c21239b677d-image copy 6.[mineru].png`,`lightRagStatus=processed`。
|
||
- LightRAG status diagnostics:`directImageScan=true`、`sidecarExists=true`、`ocrTextExposed=true`、`sidecarMeaningfulBlocks=2`。
|
||
- `/api/knowledge-rag/query` 查询 `线程作用域`:首条 reference 映射到 `新页面233155/image copy 6.png`,`quoteSource=sidecar`,`contentDiagnostics.ocrTextExposed=true`,`locatorDegraded=false`,quote 含 `线程作用域`。
|
||
- `/api/search/documents` 查询 `线程作用域` 且 `includeOcr=true`:返回 1 条 `knowledge_rag_ocr` 命中,path 为 `新页面233155/image copy 6.png`,snippet 含 `线程作用域`,boundary 为 `ordinary_local_search_with_knowledge_ocr`。
|
||
- `/api/search/documents` 查询 `线程作用域` 且 `includeOcr=false`:返回 0 条,boundary 保持 `ordinary_local_search`,证明普通搜索未被强依赖 LightRAG。
|
||
|
||
## 8. 当前禁止事项
|
||
|
||
- 不把 `rawMetadata.keywords` 当 source OCR 证据。
|
||
- 不恢复 LiteParse 为默认 fallback。
|
||
- 不把 OCR 文本写回用户 Markdown 正文。
|
||
- 不让普通搜索强依赖 LightRAG 才能返回 Markdown 页面结果。
|
||
- 不保留同一图片的旧占位 wrapper doc 与新 OCR doc 双索引状态。
|