# 7-52 LightRAG 图片 OCR 与搜索召回链路加固 v1

> 创建时间：2026-06-07
>
> 当前状态：`DONE`
>
> 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 只有 `![image](...)` 占位，没有把 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![image.png](<image.png>)` 被标记为 `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 双索引状态。
