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

526 lines
18 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-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`
> - 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 bindingvLLM 可走 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 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. 目标架构
```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<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,不作为事实主键。
格式:
```text
[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 计算
```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 Arerank 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 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 后执行:
```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 污染?