2026-06-09 09:20:56 +08:00
|
|
|
|
# 7-55 LightRAG DOCX 引用清洗与定位合同对齐设计 v2
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
> 创建时间:2026-06-08
|
|
|
|
|
|
>
|
2026-06-09 09:20:56 +08:00
|
|
|
|
> 当前状态:`done`
|
2026-06-08 20:35:49 +08:00
|
|
|
|
>
|
2026-07-21 05:13:05 +08:00
|
|
|
|
> 2026-07-19 口径回正:当前 runtime 是 Pi Rust Page AI + LightRAG + Turso/libSQL。本文保留为当前默认 LightRAG provider 的 DOCX 引用与定位合同基线;WeKnora 仅为历史/备用边界。
|
2026-06-26 20:01:02 +08:00
|
|
|
|
>
|
2026-06-08 20:35:49 +08:00
|
|
|
|
> 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`
|
2026-07-21 05:13:05 +08:00
|
|
|
|
> - `design/07-ai/reference/7-54-raganything-multimodal-retrieval-alignment-v1.md`
|
2026-06-08 20:35:49 +08:00
|
|
|
|
>
|
|
|
|
|
|
> 参考代码:
|
|
|
|
|
|
> - LightRAG:`/mnt/Data1T/Mnote_data/lightrag/LightRAG`
|
|
|
|
|
|
> - NexusRAG:`/tmp/mnote-rag-eval-nexusrag`
|
|
|
|
|
|
> - MNote:`rust/crates/mnote-web/src/routes/knowledge_rag.rs`
|
2026-06-09 09:20:56 +08:00
|
|
|
|
>
|
|
|
|
|
|
> 决策更新:
|
|
|
|
|
|
> - 当前优先级从 rerank 下调为“统一引用清洗 + DOCX 段落定位合同”。
|
|
|
|
|
|
> - rerank 只保留 provider capability/status 观察,不进入当前实现主路径。
|
|
|
|
|
|
> - 后续实现必须先复用 LightRAG sidecar / parsed block 与 NexusRAG citation/source-card 合同思想,不在 UI 和 preview 各自手搓第二套清洗/定位逻辑。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
## 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。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- 当前 LightRAG 已有 reranker 入口,但本机运行态未启用,且 endpoint 未真实验证;rerank 不应挡住引用清洗和定位合同收口。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
本设计目标是把“LightRAG 已有能力”和“MNote 必须补的引用定位层”明确切开。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
今天围绕 `吡咯烷` / `吗啉` 的连续修复说明:问题根因不是单一正则缺失,而是 raw chunk、展示文本、定位文本、用户 query、sidecar block 没有统一合同,导致搜索结果清洗、去重和 office-preview 段落定位各自补丁化。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
## 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 只负责配置、健康状态展示、请求参数透传和结果引用消费。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
当前阶段不把 rerank 作为 Phase A;只记录 provider 状态,等真实 rerank endpoint 跑通后再单独开设计/实现。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
### 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 真相。
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
### 2.4 本轮实测暴露的合同缺口
|
|
|
|
|
|
|
|
|
|
|
|
已修复但仍需收口为统一合同的问题:
|
|
|
|
|
|
|
|
|
|
|
|
- LightRAG chunk 中混有 `<equation format="latex">`、`<drawing>`、残缺 XML/HTML 标签;搜索 UI 临时清洗后可读,但 Page AI / source card / citation markdown 仍可能各自遇到 raw 文本。
|
|
|
|
|
|
- DOCX chunk 常把目录行、正文段和参考文献拼在一个上下文里;只用完整 chunk 做定位,容易落到后半段长文本或参考文献。
|
|
|
|
|
|
- 只用用户 query 短词定位会错跳;只用长 chunk 定位会被无关长段覆盖。需要 query-centered、block-aware、display/locator 分离的 anchor contract。
|
|
|
|
|
|
- 同一 resource tab 已支持 postMessage 更新 locator,不应因点击同一 DOCX 的另一个结果重新加载。
|
|
|
|
|
|
|
|
|
|
|
|
因此下一步不继续扩大前端正则,而是把清洗和定位输入前移到 `knowledge_rag` mapper:所有消费方拿同一份 `displayQuote` / `locatorEvidenceText` / `searchQuery` / `locatorPrecision`。
|
|
|
|
|
|
|
2026-06-08 20:35:49 +08:00
|
|
|
|
## 3. 设计原则
|
|
|
|
|
|
|
|
|
|
|
|
1. LightRAG 负责检索质量:parse、chunk、vector、graph、rerank、query。
|
|
|
|
|
|
2. MNote 负责来源真相:source registry、权限、root-relative path、resource tab 打开、citation URL、locator 降级语义。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
3. 参考优先:优先复用 LightRAG sidecar block、heading、positions;借鉴 NexusRAG 的 citation/source-card contract;只有 viewer anchoring adapter 允许保留 MNote 自有实现。
|
|
|
|
|
|
4. 清洗只做一次:后端 mapper 产出 raw/display/locator 三类文本,前端只展示或消费,不再各自猜 LightRAG 原始格式。
|
|
|
|
|
|
5. 引用定位按精度分层,不伪造:
|
2026-06-08 20:35:49 +08:00
|
|
|
|
- `bbox`:有 page + bbox,可精确高亮。
|
|
|
|
|
|
- `paragraph`:有 block / heading / context,可滚动到段落并高亮上下文。
|
|
|
|
|
|
- `file`:只能打开文件。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
6. DOCX 默认先做 paragraph locator,不把 DOCX 强制转换成 PDF viewer 管线。
|
|
|
|
|
|
7. rerank 暂缓。MNote 可以展示 provider rerank 状态,但当前不新增 request path、UI 开关或本地 rerank。
|
|
|
|
|
|
8. Agent final answer 只能引用 MNote 过滤和映射后的 `citations/references`,不能引用 raw LightRAG chunks。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
## 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
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- build rawQuote/displayQuote/locatorEvidenceText
|
2026-06-08 20:35:49 +08:00
|
|
|
|
- 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>,
|
2026-06-09 09:20:56 +08:00
|
|
|
|
pub raw_quote: Option<String>,
|
|
|
|
|
|
pub display_quote: Option<String>,
|
|
|
|
|
|
pub locator_evidence_text: Option<String>,
|
2026-06-08 20:35:49 +08:00
|
|
|
|
pub quote_source: String,
|
2026-06-09 09:20:56 +08:00
|
|
|
|
pub search_query: String,
|
2026-06-08 20:35:49 +08:00
|
|
|
|
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 raw_reference_mapped: bool,
|
2026-06-09 09:20:56 +08:00
|
|
|
|
pub sidecar_block_mapped: bool,
|
|
|
|
|
|
pub display_cleaned: bool,
|
|
|
|
|
|
pub locator_text_source: String,
|
2026-06-08 20:35:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum LocatorPrecision {
|
|
|
|
|
|
Bbox,
|
|
|
|
|
|
Paragraph,
|
|
|
|
|
|
File,
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
### 5.2 引用文本清洗合同
|
|
|
|
|
|
|
|
|
|
|
|
后端 mapper 统一产出三份文本:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
pub struct CitationTextBundle {
|
|
|
|
|
|
pub raw_quote: String,
|
|
|
|
|
|
pub display_quote: String,
|
|
|
|
|
|
pub locator_evidence_text: String,
|
|
|
|
|
|
pub search_query: String,
|
|
|
|
|
|
pub normalized_fingerprint: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
字段语义:
|
|
|
|
|
|
|
|
|
|
|
|
- `rawQuote`:LightRAG / sidecar 原始内容,仅诊断可见,不直接给 UI 和 LLM final answer 引用。
|
|
|
|
|
|
- `displayQuote`:搜索结果、source card、citation preview 使用。去除 XML/HTML 外壳、`drawing`、残缺 tag;公式先转为可读 plain text,不在本阶段做 LaTeX 渲染。
|
|
|
|
|
|
- `locatorEvidenceText`:定位使用。优先 sidecar block 的 query-centered window,保留足够上下文和 heading,但去除会干扰 anchor 的 XML/LaTeX 包装。
|
|
|
|
|
|
- `normalizedFingerprint`:用于同段落去重和 smoke 对比,只作为当前 query 内去重辅助,不当长期主键。
|
|
|
|
|
|
|
|
|
|
|
|
清洗规则必须集中在 Rust 侧一个 helper 中,例如:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
raw LightRAG chunk / sidecar block
|
|
|
|
|
|
-> stripXmlShellPreserveText(equation)
|
|
|
|
|
|
-> dropDrawingTags()
|
|
|
|
|
|
-> stripBrokenTags()
|
|
|
|
|
|
-> collapseWhitespace()
|
|
|
|
|
|
-> normalizeLatexCommandsForDisplay()
|
|
|
|
|
|
-> build query-centered locator window
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
禁止:
|
|
|
|
|
|
|
|
|
|
|
|
- 搜索 UI、Page AI、office-preview 分别维护不同的 equation/drawing 正则。
|
|
|
|
|
|
- 为了显示好看删除用户 query 或化学关键词。
|
|
|
|
|
|
- 把 `displayQuote` 反过来当唯一定位依据;定位必须优先用 `locatorEvidenceText + searchQuery + blockId/sourceMapPath`。
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 citation ID
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
采用 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。
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
### 5.4 locator precision 计算
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
```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": "三乙基硅",
|
2026-06-09 09:20:56 +08:00
|
|
|
|
"evidenceText": "locatorEvidenceText",
|
|
|
|
|
|
"displayQuote": "cleaned display quote"
|
2026-06-08 20:35:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
要求:
|
|
|
|
|
|
|
|
|
|
|
|
- 有 `blockid` 必须传 `blockId`。
|
|
|
|
|
|
- 有 sidecar path 必须传 `sourceMapPath`。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- `evidenceText` 使用统一 `locatorEvidenceText`;优先取 sidecar block content 的 query-centered window,其次 chunk content。
|
|
|
|
|
|
- 搜索结果展示使用 `displayQuote`,不再由 UI 从 raw `quote/snippet` 临时清洗。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
- `page/bbox` 缺失时不得写假值。
|
|
|
|
|
|
- `citationMarkdown` 文案可带“定位降级”,但链接仍应可点击打开 resource tab。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 前端 office-preview anchor
|
|
|
|
|
|
|
|
|
|
|
|
office-preview / docx-preview 收到 paragraph locator 后:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
locator.evidenceText
|
2026-06-09 09:20:56 +08:00
|
|
|
|
-> consume backend locatorEvidenceText
|
2026-06-08 20:35:49 +08:00
|
|
|
|
-> build anchors:
|
2026-06-09 09:20:56 +08:00
|
|
|
|
1. sidecar block/query-centered leading anchor
|
|
|
|
|
|
2. heading + quote window
|
|
|
|
|
|
3. long rare terms
|
|
|
|
|
|
4. fallback query only when scoped by block/window
|
2026-06-08 20:35:49 +08:00
|
|
|
|
-> 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。
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
## 7. Rerank 暂缓策略
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
### 7.1 当前决策
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
MNote 当前不推进 rerank 实现,只做状态观察和诊断记录。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
原因:
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- 本机 LightRAG `configuration.enable_rerank=false`、`rerank_queue_status.available=false`。
|
|
|
|
|
|
- 之前测试过常见 NVIDIA rerank endpoint 返回 404,不能把模型名当成可用能力。
|
|
|
|
|
|
- 当前用户痛点集中在“命中能否完整、结果是否干净、点击是否定位到同一段落”,rerank 主要解决排序,不解决 DOCX 段落坐标缺失。
|
|
|
|
|
|
- 提前接 rerank 会扩大变量,影响定位合同验收。
|
|
|
|
|
|
|
|
|
|
|
|
当前只要求 dashboard/status 能展示:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
LightRAG: healthy
|
|
|
|
|
|
Embedding: nvidia/baai/bge-m3
|
|
|
|
|
|
Rerank: disabled
|
|
|
|
|
|
Rerank binding: null
|
|
|
|
|
|
Rerank queue: unavailable
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 后置启用条件
|
|
|
|
|
|
|
|
|
|
|
|
只有满足以下条件,才新增 rerank 实现任务:
|
|
|
|
|
|
|
|
|
|
|
|
1. 有真实 endpoint,并通过 provider smoke 证明 `/health.configuration.enable_rerank=true`、`rerank_queue_status.available=true`。
|
|
|
|
|
|
2. LightRAG query result 能返回可消费的 `rerank_score`,或确认只能显示 provider order。
|
|
|
|
|
|
3. 7-55 的 citation/locator contract 已稳定,`三甲基硅`、`三乙基硅`、`吡咯烷`、`吗啉` smoke 通过。
|
|
|
|
|
|
|
|
|
|
|
|
后置实现仍遵循:
|
|
|
|
|
|
|
|
|
|
|
|
- 不在 MNote 调 rerank model。
|
|
|
|
|
|
- 不复制 LightRAG vector / graph / rerank。
|
|
|
|
|
|
- 只透传 provider 参数、展示状态、消费 provider score。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
`.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 索引。
|
|
|
|
|
|
|
|
|
|
|
|
## 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`。
|
|
|
|
|
|
- 相关性:
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- 当前显示 provider order / MNote mapping score。
|
|
|
|
|
|
- 只有后置 rerank 验证完成后才显示 LightRAG `rerank_score`。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
- 图片引用:sidecar drawings / image OCR 命中使用 `[IMG-xxxx]`。
|
|
|
|
|
|
|
|
|
|
|
|
## 9. 实施阶段
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
### Phase A:统一引用文本清洗合同
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- [x] 在 `knowledge_rag.rs` 增加统一 citation text helper,产出 `rawQuote/displayQuote/locatorEvidenceText/searchQuery/normalizedFingerprint`。
|
|
|
|
|
|
- [x] search result、Page AI、Hermes tool result 全部消费 `displayQuote`,不再直接展示 raw LightRAG chunk。
|
|
|
|
|
|
- [x] locator/openAction 全部消费 `locatorEvidenceText` 和真实 `searchQuery`。
|
|
|
|
|
|
- [x] 去重优先使用 `sourcePath + blockId`,无 block 时再用 `sourcePath + chunkId + normalizedFingerprint`,避免同段落重复但保留同文件不同段落。
|
|
|
|
|
|
- [x] 移除或瘦身 `sidebar-tree-runtime.js` 里的临时 equation/drawing 清洗,前端只做最后一道防御。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
验收:
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- 搜索 `吡咯烷`、`吗啉` 的 UI 结果不显示 `<equation>`、`latex`、`drawing`、残缺 XML 标签。
|
|
|
|
|
|
- `displayQuote` 不丢失用户 query。
|
|
|
|
|
|
- `locatorEvidenceText` 能在 diagnostics 中看到来源是 `sidecar`、`chunk` 还是 fallback。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
### Phase B:citation contract 收口
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- [x] 在 knowledge-rag mapped reference 上补 `citationId`。
|
|
|
|
|
|
- [x] 输出统一 `citations[]`,字段包含 `citationMarkdown/citationUrl/locatorPrecision/displayQuote/locatorEvidenceText/headingPath`。
|
|
|
|
|
|
- [x] Page AI / Hermes tool result 只暴露 filtered citations,不暴露 raw chunks 给 final answer 引用。
|
|
|
|
|
|
- [x] source card 消费 `citations[]`,不重新解析 raw LightRAG response。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
验收:
|
|
|
|
|
|
|
|
|
|
|
|
- 同一回答内 citation badge 能映射到 source card。
|
|
|
|
|
|
- unmapped / stale / deleted provider reference 不进入可引用 citation set。
|
|
|
|
|
|
|
|
|
|
|
|
### Phase C:DOCX paragraph locator
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- [x] `lightrag_locator_for_reference(...)` 对 DOCX sidecar block 无 bbox 时返回 paragraph locator。
|
|
|
|
|
|
- [x] `locatorPrecision=paragraph` 时保留 `blockId/sourceMapPath/locatorEvidenceText/headingPath/searchQuery`。
|
|
|
|
|
|
- [x] `citationMarkdown` 对 paragraph/file 降级文案明确,但链接可点击。
|
|
|
|
|
|
- [x] office-preview 支持 `blockId/locatorEvidenceText/searchQuery` 定位,按 sidecar block / chunk context 打分滚动。
|
|
|
|
|
|
- [x] 同一 DOCX 多次点击 citation 使用 postMessage 更新定位,不重复 reload。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
验收:
|
|
|
|
|
|
|
|
|
|
|
|
- `吡咯烷` 不错跳到仅短词匹配的其他段落。
|
|
|
|
|
|
- `三乙基硅` / `三甲基硅` 在 DOCX 无 bbox 时至少段落级定位。
|
|
|
|
|
|
- 无 sidecar 或 quote 不匹配时只返回 file locator,不伪造 paragraph。
|
|
|
|
|
|
|
|
|
|
|
|
### Phase D:NexusRAG 式来源卡片
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- [x] source card 展示 citation ID、文件名、标题路径、定位精度、quote。
|
|
|
|
|
|
- [x] 有 page/bbox 时显示页码;无 page/bbox 不显示页码。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
- [ ] 图片 citation 使用 `[IMG-xxxx]` 并打开原图 / PDF 页。
|
|
|
|
|
|
- [ ] answer renderer 对 citation badge 做点击联动。
|
|
|
|
|
|
|
|
|
|
|
|
验收:
|
|
|
|
|
|
|
|
|
|
|
|
- 答案和搜索结果都能从同一 citation model 打开来源。
|
|
|
|
|
|
- source card 不依赖 LLM 生成的自由文本解析。
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
### Phase E:rerank status / provider score(后置)
|
|
|
|
|
|
|
|
|
|
|
|
- [x] status/dashboard 暴露 LightRAG health 中的 rerank 字段。
|
|
|
|
|
|
- [x] diagnostics 标注 `providerRerankEnabled`、`providerRerankAvailable`、`rerankModel`。
|
|
|
|
|
|
- [x] 只有真实 endpoint smoke 通过后,才考虑 `KnowledgeRagQueryRequest` / `KnowledgeRagSearchRequest` 增加 `enable_rerank`。
|
|
|
|
|
|
- [x] 不新增 MNote reranker。
|
|
|
|
|
|
|
|
|
|
|
|
验收:
|
|
|
|
|
|
|
|
|
|
|
|
- LightRAG `.env` 为 `RERANK_BINDING=null` 时,UI 明确显示 rerank disabled。
|
|
|
|
|
|
- 不会因为 rerank unavailable 改变当前 query/search 行为。
|
|
|
|
|
|
|
2026-06-08 20:35:49 +08:00
|
|
|
|
## 10. 测试计划
|
|
|
|
|
|
|
|
|
|
|
|
### Rust 单测
|
|
|
|
|
|
|
|
|
|
|
|
目标文件:`rust/crates/mnote-web/src/routes/knowledge_rag.rs`
|
|
|
|
|
|
|
|
|
|
|
|
- `mapped_reference_generates_short_citation_id`
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- `citation_text_bundle_strips_equation_and_drawing_for_display`
|
|
|
|
|
|
- `citation_text_bundle_preserves_query_for_locator`
|
2026-06-08 20:35:49 +08:00
|
|
|
|
- `docx_paraid_block_returns_paragraph_locator`
|
|
|
|
|
|
- `docx_missing_block_degrades_to_file_locator`
|
|
|
|
|
|
- `bbox_block_keeps_bbox_locator`
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- `same_block_same_fingerprint_deduplicates`
|
|
|
|
|
|
- `same_file_different_block_is_not_deduplicated`
|
|
|
|
|
|
- `provider_rerank_disabled_is_reported_in_status`
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
### 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 文案一致。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- 搜索结果不显示 raw equation / drawing / broken tag。
|
|
|
|
|
|
- rerank disabled 状态在资料库设置中可见,但不影响 query/search。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
|
|
|
|
|
|
### 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。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- 当前阶段不启用、不调试、不 UI 化 rerank 参数;只展示 provider 状态。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
- 不把 DOCX 无 page/bbox 的命中伪造成 PDF 精确定位。
|
2026-06-09 09:20:56 +08:00
|
|
|
|
- 不把 raw chunk 清洗逻辑继续分散在搜索 UI、Page AI、office-preview。
|
2026-06-08 20:35:49 +08:00
|
|
|
|
- 不让普通本地搜索强依赖 LightRAG。
|
|
|
|
|
|
- 不让 LLM 自己生成 citation ID。
|
|
|
|
|
|
- 不把 raw LightRAG chunk 暴露为 agent final answer 可直接引用材料。
|
|
|
|
|
|
|
|
|
|
|
|
## 12. 开放问题
|
|
|
|
|
|
|
2026-06-09 09:20:56 +08:00
|
|
|
|
1. DOCX `paraid range=[null,null]` 的样本较多。是否需要在 LightRAG native parser 侧增强 paraId 恢复或写入 block ordinal,给 MNote 一个更稳定的 `blockOrder`?
|
|
|
|
|
|
2. `/query/search` 与 `/query/data` 的 reference schema 不完全一致时,MNote 是否应该先在 connector 层 normalize 为同一个 `ProviderReference`?
|
|
|
|
|
|
3. Source scope 当前仍是 MNote post-filter。长期是否需要 LightRAG provider 支持按 `file_path/doc_id` 过滤候选,以避免 raw retrieval 污染?
|
|
|
|
|
|
4. LightRAG `/query/data` 当前 `convert_to_user_format(...)` 没有把 `rerank_score` 放入 `formatted_chunks`。该问题放到 Phase E;在真实 rerank endpoint 跑通前不处理。
|