Files
mnote/design/07-ai/process/7-55-lightrag-docx-citation-rerank-alignment-v1.md
T

18 KiB
Raw Blame History

7-55 LightRAG DOCX 引用定位与 rerank 对齐设计 v1

创建时间:2026-06-08

当前状态:process

Owner07-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
  • MNoterust/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_pathchunk_idcontent,但不直接返回 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_rerankRERANK_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 bindingvLLM 可走 Cohere-compatible endpoint。
  • docs/LightRAG-API-Server.md 明确 rerank 是 query-time improvement,不需要重建索引。

当前本机运行态:

{
  "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 现状:

{
  "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 contractEnrichedChunk 持有 source_file/document_id/page_no/heading_path/contextualized/images/tables
  • retrieval resultsource 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. 目标架构

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 应逐步收口到这组字段。

#[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<String>,
    pub source_path: String,
    pub source_root_relative_path: String,
    pub light_rag_doc_id: Option<String>,
    pub light_rag_file_path: String,
    pub light_rag_chunk_id: Option<String>,
    pub block_id: Option<String>,
    pub heading_path: Vec<String>,
    pub quote: Option<String>,
    pub quote_source: String,
    pub locator_precision: LocatorPrecision,
    pub locator_degraded: bool,
    pub citation_url: String,
    pub citation_markdown: String,
    pub relevance_score: Option<f32>,
    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,不作为事实主键。

格式:

[a3z1]      普通文本来源
[IMG-p4f2] 图片来源

生成规则:

  • 每次 query result 内稳定即可,不要求跨 query 永久稳定。
  • 基于 provider + source_id + chunk_id/block_id + occurrence_index 做 hashbase36/base62 截断 4 字符。
  • 同一 query 内冲突则加 salt 重算。
  • citation card 使用 citation_id 关联 answer badge。

5.3 locator precision 计算

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

{
  "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 后:

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=truererank_queue_status.available=trueMNote query 默认传 enable_rerank=true
  3. LightRAG health 显示 rerank 不可用:MNote 仍可传 enable_rerank=true,但 diagnostics 标注 provider 未配置;或在 UI 选择“禁用 rerank”后传 false。

.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 时应显式传:

{
  "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 显示:

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

三乙基硅基通常可作为保护基使用 [a3z1]。

规则:

  • 每句话最多 3 个来源。
  • 只引用直接支持该句的来源。
  • badge 点击打开对应 source card,同时可直接打开 citation URL。
  • LLM 不自行编造 citation ID;由 MNote tool result 提供可用 citation set。

8.2 source card

每个 citation card 展示:

[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 Arerank capability 透出

  • KnowledgeRagQueryRequest / KnowledgeRagSearchRequest 增加 enable_rerank
  • 调 LightRAG /query/data / /query/search 时显式传 enable_rerank
  • status/dashboard 暴露 LightRAG health 中的 rerank 字段。
  • diagnostics 标注 providerRerankEnabledproviderRerankAvailablererankModel
  • 不新增 MNote reranker。

验收:

  • LightRAG .envRERANK_BINDING=null 时,UI 明确显示 rerank disabled。
  • API result metadata 能看出本次 query 是否请求 rerank、provider 是否实际可用。

Phase Bcitation 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 CDOCX 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 DNexusRAG 式来源卡片

  • 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 后执行:

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 污染?