# 7-55 LightRAG DOCX 引用定位与 rerank 对齐设计 v1 > 创建时间:2026-06-08 > > 当前状态:`process` > > Owner:07-ai / knowledge-rag / 03-rust-web / office-preview > > 关联: > - `design/07-ai/done/7-50-lightrag-knowledge-rag-provider-v1.md` > - `design/07-ai/done/7-51-lightrag-post-commit-hardening-v1.md` > - `design/07-ai/process/7-54-raganything-multimodal-retrieval-alignment-v1.md` > > 参考代码: > - LightRAG:`/mnt/Data1T/Mnote_data/lightrag/LightRAG` > - NexusRAG:`/tmp/mnote-rag-eval-nexusrag` > - MNote:`rust/crates/mnote-web/src/routes/knowledge_rag.rs` ## 1. 背景 当前 DOCX / Office 资料检索的主要问题不是 LightRAG 完全不能检索,而是检索结果映射回 MNote resource tab 时容易错位。 已验证的现状: - MNote 已通过 `/api/knowledge-rag/query` 调 LightRAG `/query/data`,并请求 `include_references=true` / `include_chunk_content=true`。 - LightRAG 返回的 reference / chunk 可以包含 `file_path`、`chunk_id`、`content`,但不直接返回 MNote 可用的 `resourcePath/page/bbox/blockId`。 - MNote 当前 `map_reference_plan(...)` 会用 chunk quote / query 去 sidecar `.blocks.jsonl` 里反查 block,再生成 `EvidenceLocator`。 - PDF / image 经 MinerU 或 Docling 解析后通常有 `positions.type=bbox`,可以转 page/bbox。 - DOCX native parser 生成的是 `positions.type=paraid`,真实样本里大量 `range=[null,null]`,不能可靠转 page/bbox。 - 当前 LightRAG 已有 reranker,不应在 MNote 重复实现一套通用 reranker。 本设计目标是把“LightRAG 已有能力”和“MNote 必须补的引用定位层”明确切开。 ## 2. CodeGraph 核对结论 ### 2.1 LightRAG 已有 reranker 主链 源码证据: - `lightrag/base.py:83` `QueryParam` 默认 `mode="mix"`。 - `lightrag/base.py:148` `enable_rerank` 由 `RERANK_BY_DEFAULT` 控制,默认 true。 - `lightrag/utils.py:3278` `apply_rerank_if_enabled(...)` 调 `global_config["rerank_model_func"]`,支持 index-based rerank result,并把 `rerank_score` 写回 chunk。 - `lightrag/utils.py:3362` `process_chunks_unified(...)` 的顺序是 rerank -> `min_rerank_score` 过滤 -> `chunk_top_k` 截断 -> token truncation。 - `lightrag/api/lightrag_server.py:1894` server 支持 `cohere` / `jina` / `aliyun` rerank binding;vLLM 可走 Cohere-compatible endpoint。 - `docs/LightRAG-API-Server.md` 明确 rerank 是 query-time improvement,不需要重建索引。 当前本机运行态: ```json { "core_version": "1.5.1", "configuration": { "enable_rerank": false, "rerank_binding": "null", "rerank_model": null, "min_rerank_score": 0.0 }, "rerank_queue_status": { "available": false } } ``` 结论:MNote 不新增通用 reranker。MNote 只负责配置、健康状态展示、请求参数透传和结果引用消费。 ### 2.2 LightRAG 已有 DOCX native parser 与 sidecar 源码证据: - `lightrag/pipeline.py:2897` `parse_native(...)` 对 pending DOCX 调 `extract_docx_blocks(...)`。 - `lightrag/parser/docx/parse_document.py:1611` `extract_docx_blocks(...)` 按 heading / paragraph / table 拆 block。 - `lightrag/parser/docx/ir_builder.py:304` DOCX block position 写为 `IRPosition(type="paraid", range=[uuid_start, uuid_end])`。 - `lightrag/sidecar/writer.py:256` `.blocks.jsonl` content row 包含 `blockid/content/heading/parent_headings/level/positions`。 真实 sidecar 现状: ```json { "type": "content", "blockid": "5c6cda46137773fba8cf06fcfc08c313", "content": "...", "heading": "Preface/Uncategorized", "positions": [ {"type": "paraid", "range": [null, null]} ] } ``` 结论:DOCX 引用定位不能伪造成 PDF 式 page/bbox。短期正确目标是段落级 / block 级定位。 ### 2.3 NexusRAG 的强参考点 NexusRAG 不是只针对 PDF。 - `backend/app/services/document_parser/docling_parser.py` 支持 `.pdf/.docx/.pptx/.html`。 - `backend/app/services/document_parser/marker_parser.py` 支持 `.pdf/.docx/.pptx/.xlsx/.html/.epub`。 可借鉴点: - parser abstraction:不同 parser 都产出统一 `ParsedDocument`。 - chunk contract:`EnrichedChunk` 持有 `source_file/document_id/page_no/heading_path/contextualized/images/tables`。 - retrieval result:source card 展示文件名、页码、标题路径、相关性分数。 - citation UI:答案内 citation badge 与 source card 分开,但共享同一 citation model。 不直接照搬点: - NexusRAG 的引用 ID / source card 是产品层 contract,不代表其 DOCX 一定有比 LightRAG 更强的源文档坐标。 - MNote 已选 LightRAG 作为默认 provider,不能为了 citation UI 整套替换 RAG provider。 - MNote 的 open-reference 必须回到 local-folder resource tab / Page Aggregate / Resource Tree,不应引入第二套文档 viewer 真相。 ## 3. 设计原则 1. LightRAG 负责检索质量:parse、chunk、vector、graph、rerank、query。 2. MNote 负责来源真相:source registry、权限、root-relative path、resource tab 打开、citation URL、locator 降级语义。 3. 引用定位按精度分层,不伪造: - `bbox`:有 page + bbox,可精确高亮。 - `paragraph`:有 block / heading / context,可滚动到段落并高亮上下文。 - `file`:只能打开文件。 4. DOCX 默认先做 paragraph locator,不把 DOCX 强制转换成 PDF viewer 管线。 5. rerank 只走 LightRAG provider。MNote 可以做 source scope post-filter 和 citation ranking,但不能把它包装成 provider rerank。 6. Agent final answer 只能引用 MNote 过滤和映射后的 `citations/references`,不能引用 raw LightRAG chunks。 ## 4. 目标架构 ```text LightRAG /query/data -> raw.data.references / raw.data.chunks -> MNote source registry -> Evidence Mapping Layer - match provider file_path/doc_id -> source entry - match chunk_id/content/query -> sidecar block - classify locatorPrecision - build citationUrl/citationMarkdown -> knowledge-rag result - references[] - citations[] - raw kept for diagnostics only -> Page AI / Search UI / Source Card -> resource tab open-reference - bbox: page + bbox highlight - paragraph: block/context anchor highlight - file: file-level open ``` ## 5. 数据合同 ### 5.1 MNote citation model 新增内部规范,不要求一次性改完所有 API 字段,但 `knowledge-rag`、Page AI、搜索结果和 source card 应逐步收口到这组字段。 ```rust #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct KnowledgeRagCitation { pub schema: String, pub provider: String, pub citation_id: String, pub source_id: Option, pub source_path: String, pub source_root_relative_path: String, pub light_rag_doc_id: Option, pub light_rag_file_path: String, pub light_rag_chunk_id: Option, pub block_id: Option, pub heading_path: Vec, pub quote: Option, pub quote_source: String, pub locator_precision: LocatorPrecision, pub locator_degraded: bool, pub citation_url: String, pub citation_markdown: String, pub relevance_score: Option, pub diagnostics: CitationDiagnostics, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CitationDiagnostics { pub quote_source: String, pub provider_rerank_requested: bool, pub provider_rerank_available: bool, pub raw_reference_mapped: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum LocatorPrecision { Bbox, Paragraph, File, } ``` ### 5.2 citation ID 采用 NexusRAG 式短 ID,但 ID 只作为 UI/display contract,不作为事实主键。 格式: ```text [a3z1] 普通文本来源 [IMG-p4f2] 图片来源 ``` 生成规则: - 每次 query result 内稳定即可,不要求跨 query 永久稳定。 - 基于 `provider + source_id + chunk_id/block_id + occurrence_index` 做 hash,base36/base62 截断 4 字符。 - 同一 query 内冲突则加 salt 重算。 - citation card 使用 `citation_id` 关联 answer badge。 ### 5.3 locator precision 计算 ```rust fn locator_precision_from_block(block: &serde_json::Value) -> LocatorPrecision { let has_bbox = block .get("positions") .and_then(|value| value.as_array()) .is_some_and(|positions| { positions.iter().any(|position| { position.get("type").and_then(|value| value.as_str()) == Some("bbox") && position.get("anchor").is_some() && position.get("range").and_then(|value| value.as_array()).is_some() }) }); if has_bbox { return LocatorPrecision::Bbox; } let has_paragraph_anchor = block.get("blockid").and_then(|value| value.as_str()).is_some() || block.get("heading").and_then(|value| value.as_str()).is_some() || block .get("positions") .and_then(|value| value.as_array()) .is_some_and(|positions| { positions.iter().any(|position| { position.get("type").and_then(|value| value.as_str()) == Some("paraid") }) }); if has_paragraph_anchor { return LocatorPrecision::Paragraph; } LocatorPrecision::File } ``` ## 6. DOCX paragraph locator ### 6.1 后端 locator 当 sidecar block 命中但没有 bbox: ```json { "locatorPrecision": "paragraph", "locatorDegraded": true, "locator": { "resourceKind": "office", "resourcePath": "资料/保护基.docx", "blockId": "2844bf0675454eb5e83f97b596866e53", "sourceMapPath": ".mnote/.../__parsed__/保护基.blocks.jsonl", "openAction": { "params": { "provider": "lightrag", "chunkId": "doc-xxx-chunk-001", "searchQuery": "三乙基硅", "evidenceText": "query-centered chunk/block context" } } } } ``` 要求: - 有 `blockid` 必须传 `blockId`。 - 有 sidecar path 必须传 `sourceMapPath`。 - `evidenceText` 优先取 sidecar block content 的 query-centered window,其次 chunk content。 - `page/bbox` 缺失时不得写假值。 - `citationMarkdown` 文案可带“定位降级”,但链接仍应可点击打开 resource tab。 ### 6.2 前端 office-preview anchor office-preview / docx-preview 收到 paragraph locator 后: ```text locator.evidenceText -> normalize text -> build anchors: 1. query-centered phrase 2. long rare terms 3. heading + quote window 4. fallback query -> scan rendered paragraphs / table cells -> score by token overlap + rare term bonus + heading match -> scroll into view + transient highlight ``` 禁止: - 只用 query 短词直接 `find()`,否则 `吡咯烷` 容易错跳到 `N-甲基吡咯烷酮`。 - DOCX 无 page/bbox 时展示成精确页码。 - 同一 resource tab 重新加载整个 document 来响应每次 citation click;应优先 postMessage 更新 locator。 ## 7. Rerank 对齐 ### 7.1 配置策略 MNote 不实现 rerank model 调用,只管理 LightRAG 配置状态。 推荐配置优先级: 1. 用户显式关闭:`enable_rerank=false`。 2. LightRAG health 显示 `configuration.enable_rerank=true` 且 `rerank_queue_status.available=true`:MNote query 默认传 `enable_rerank=true`。 3. LightRAG health 显示 rerank 不可用:MNote 仍可传 `enable_rerank=true`,但 diagnostics 标注 provider 未配置;或在 UI 选择“禁用 rerank”后传 false。 `.env` 示例: ```env RERANK_BINDING=cohere RERANK_MODEL=BAAI/bge-reranker-v2-m3 RERANK_BINDING_HOST=http://127.0.0.1:8000/rerank RERANK_BINDING_API_KEY=local-rerank RERANK_BY_DEFAULT=True MIN_RERANK_SCORE=0.0 MAX_ASYNC_RERANK=4 RERANK_TIMEOUT=30 ``` 说明: - 本地 vLLM / rerank 服务必须先真实验证 endpoint。 - 之前本机测过常见 NVIDIA rerank endpoint 返回 404;不能仅凭模型名就标“rerank 已启用”。 - rerank 是 query-time 能力,启用后不要求重建 LightRAG 索引。 ### 7.2 MNote query request `/api/knowledge-rag/query` 和 `/api/knowledge-rag/search` 请求 LightRAG 时应显式传: ```json { "query": "...", "mode": "mix", "top_k": 20, "chunk_top_k": 20, "include_references": true, "include_chunk_content": true, "enable_rerank": true } ``` `enable_rerank` 来源: - API body 可传。 - UI 设置可覆盖。 - 未设置时使用 MNote 从 LightRAG health 缓存到的 provider capability。 ### 7.3 UI diagnostics 资料库设置 / dashboard 显示: ```text LightRAG: healthy Embedding: nvidia/baai/bge-m3 Rerank: disabled Rerank binding: null Rerank queue: unavailable ``` 当用户看到检索排序异常时,第一屏就能判断当前没有 rerank。 ## 8. Source card 与 answer citation 借鉴 NexusRAG 的 UI contract: ### 8.1 inline badge 答案文本中使用短 badge: ```text 三乙基硅基通常可作为保护基使用 [a3z1]。 ``` 规则: - 每句话最多 3 个来源。 - 只引用直接支持该句的来源。 - badge 点击打开对应 source card,同时可直接打开 citation URL。 - LLM 不自行编造 citation ID;由 MNote tool result 提供可用 citation set。 ### 8.2 source card 每个 citation card 展示: ```text [a3z1] 保护基.docx 定位:段落级 标题路径:酚羟基保护 > 硅基保护 相关性:LightRAG rerank_score / vector order / MNote score 引用片段:... ``` 字段来源: - 文件名:source registry。 - 页码:只有 locator 有 page 时显示。 - 标题路径:sidecar `parent_headings + heading`。 - 相关性: - 优先 LightRAG `rerank_score`,如果 provider 返回。 - 否则使用 MNote 现有 reference ranking score,标注为 `mnoteScore`。 - 图片引用:sidecar drawings / image OCR 命中使用 `[IMG-xxxx]`。 ## 9. 实施阶段 ### Phase A:rerank capability 透出 - [ ] `KnowledgeRagQueryRequest` / `KnowledgeRagSearchRequest` 增加 `enable_rerank`。 - [ ] 调 LightRAG `/query/data` / `/query/search` 时显式传 `enable_rerank`。 - [ ] status/dashboard 暴露 LightRAG health 中的 rerank 字段。 - [ ] diagnostics 标注 `providerRerankEnabled`、`providerRerankAvailable`、`rerankModel`。 - [ ] 不新增 MNote reranker。 验收: - LightRAG `.env` 为 `RERANK_BINDING=null` 时,UI 明确显示 rerank disabled。 - API result metadata 能看出本次 query 是否请求 rerank、provider 是否实际可用。 ### Phase B:citation contract 收口 - [ ] 在 knowledge-rag mapped reference 上补 `citationId`。 - [ ] 输出统一 `citations[]`,字段包含 `citationMarkdown/citationUrl/locatorPrecision/quote/headingPath`。 - [ ] Page AI / Hermes tool result 只暴露 filtered citations,不暴露 raw chunks 给 final answer 引用。 - [ ] source card 消费 `citations[]`,不重新解析 raw LightRAG response。 验收: - 同一回答内 citation badge 能映射到 source card。 - unmapped / stale / deleted provider reference 不进入可引用 citation set。 ### Phase C:DOCX paragraph locator - [ ] `lightrag_locator_for_reference(...)` 对 DOCX sidecar block 无 bbox 时返回 paragraph locator。 - [ ] `locatorPrecision=paragraph` 时保留 `blockId/sourceMapPath/evidenceText/headingPath`。 - [ ] `citationMarkdown` 对 paragraph/file 降级文案明确,但链接可点击。 - [ ] office-preview 支持 `blockId/evidenceText` 定位,按 chunk context 打分滚动。 - [ ] 同一 DOCX 多次点击 citation 使用 postMessage 更新定位,不重复 reload。 验收: - `吡咯烷` 不错跳到仅短词匹配的其他段落。 - `三乙基硅` / `三甲基硅` 在 DOCX 无 bbox 时至少段落级定位。 - 无 sidecar 或 quote 不匹配时只返回 file locator,不伪造 paragraph。 ### Phase D:NexusRAG 式来源卡片 - [ ] source card 展示 citation ID、文件名、标题路径、定位精度、相关性、quote。 - [ ] 有 page/bbox 时显示页码;无 page/bbox 不显示页码。 - [ ] 图片 citation 使用 `[IMG-xxxx]` 并打开原图 / PDF 页。 - [ ] answer renderer 对 citation badge 做点击联动。 验收: - 答案和搜索结果都能从同一 citation model 打开来源。 - source card 不依赖 LLM 生成的自由文本解析。 ## 10. 测试计划 ### Rust 单测 目标文件:`rust/crates/mnote-web/src/routes/knowledge_rag.rs` - `mapped_reference_generates_short_citation_id` - `docx_paraid_block_returns_paragraph_locator` - `docx_missing_block_degrades_to_file_locator` - `bbox_block_keeps_bbox_locator` - `query_request_passes_enable_rerank` - `provider_rerank_disabled_is_reported_in_diagnostics` ### Browser smoke 新增或扩展: - `scripts/task540-knowledge-rag-office-result-open-locator-smoke.js` - `scripts/task543-knowledge-rag-pyrrolidine-top5-locator-smoke.js` 新增断言: - DOCX citation click 后 resource tab 不重载。 - paragraph locator 触发可见高亮。 - `locatorPrecision` 与 UI 文案一致。 - rerank disabled 状态在资料库设置中可见。 ### Provider smoke 只在配置真实 rerank endpoint 后执行: ```bash curl /health curl /query/data -d '{"query":"...", "mode":"mix", "enable_rerank":true}' ``` 验收: - `/health.configuration.enable_rerank=true`。 - `rerank_queue_status.available=true`。 - query 日志出现 rerank 成功或 result chunk 带 `rerank_score`。 ## 11. 不做 - 不替换 LightRAG 为 NexusRAG。 - 不在 MNote 复制 LightRAG vector / graph / rerank。 - 不把 DOCX 无 page/bbox 的命中伪造成 PDF 精确定位。 - 不让普通本地搜索强依赖 LightRAG。 - 不让 LLM 自己生成 citation ID。 - 不把 raw LightRAG chunk 暴露为 agent final answer 可直接引用材料。 ## 12. 开放问题 1. LightRAG `/query/data` 当前 `convert_to_user_format(...)` 没有把 `rerank_score` 放入 `formatted_chunks`。如果上游 rerank 已经写回 chunk,是否要向 LightRAG 提 patch 暴露 `rerank_score`,还是 MNote 只显示本地 ranking score? 2. DOCX `paraid range=[null,null]` 的样本较多。是否需要在 LightRAG native parser 侧增强 paraId 恢复或写入 block ordinal,给 MNote 一个更稳定的 `blockOrder`? 3. `/query/search` 与 `/query/data` 的 reference schema 不完全一致时,MNote 是否应该先在 connector 层 normalize 为同一个 `ProviderReference`? 4. Source scope 当前仍是 MNote post-filter。长期是否需要 LightRAG provider 支持按 `file_path/doc_id` 过滤候选,以避免 raw retrieval 污染?