Files
rag/docs/RAG引用跳转-优化计划.md
lacerate551 ee5295cc97 fix(parser): PDF/DOCX 切片层级结构修复与 MinerU V2 解析增强
核心修复:
- _post_process_chunks: 用 _buffer_has_body 标志替代 buffer.text_level=0,
  标题+正文合并后保留 text_level 不置零,修复层级信息丢失
- heading_rules numeric_level2: 正则从 ^\d+\.\d+[\.、\s] 改为
  ^\d+\.\d+(?!\.\d),修复无空格标题如"2.1运行调度"无法匹配
- V2 title handler: heading_rules 优先(模式匹配可靠),VLM 仅兜底
  (VLM 常给所有标题 level=1)

MinerU V2 解析增强:
- 两轮 TOC 过滤:多行目录块检测 + 孤立标题/单字符残留清理
- 封面 logo 过滤、重复标题去重
- chart VLM 描述和 Markdown 数据表提取
- ChromaDB metadata 新增 text_level/bbox/table_type/sub_type 字段

配置调整:
- CLUSTER_SECTION_PREFIX_LEVELS 1→2(两级 section 聚类更精确)
- MINERU_LOCAL_BACKEND 默认改为 pipeline

文档:
- RAG检索流程逻辑.md 更新 MinerUChunk 字段、ChromaDB metadata、
  MinerU 解析策略等章节
- 新增 RAG引用跳转-优化计划.md、云端MinerU输出分析与优化方案.md
2026-06-19 23:56:13 +08:00

13 KiB
Raw Blame History

RAG 引用溯源跳转 — 优化计划

本文档基于 dev-ui 前端Vue3 + NaiveUI和 chat_routes.py 后端的现有实现,分析引用跳转功能的优化方向。供讨论后实施。


一、现有实现现状

1.1 数据流

SSE finish 事件 → citations[] 数组_build_citation 构建)
    ↓
Chat.vue extractCitations() → 解析 [ref:chunk_id] → 渲染为 <sup>[N]</sup>
    ↓
用户点击引用项 → jumpToChunk(citation)
    ↓
打开 1200px 分屏弹窗:
  ├─ 左侧PdfViewer / DocxViewer / TextViewer按 doc_type 选择)
  └─ 右侧:切片上下文列表(/documents/preview API 返回)
    ↓
弹窗内切换切片 → navigateToChunk(chunk) → 更新 viewer 响应式 prop

1.2 各组件当前能力

组件 文件 当前能力 局限
PdfViewer viewers/PdfViewer.vue pdfjs-dist 渲染,initialPage prop 页码跳转watch 响应外部页码变化 仅页级跳转,无页内 bbox 高亮;单页渲染(非连续滚动)
DocxViewer viewers/DocxViewer.vue mammoth 转 HTML2-gram Jaccard 段落匹配,<mark> 高亮 + scrollIntoView 无法精确定位到文本范围(仅段落级);大文档性能未测试
Chat.vue views/Chat.vue jumpToChunk 调用 preview API 获取完整 chunk text 替代 300 字截断 preview API 失败时回退到截断文本,匹配精度下降
_build_citation api/chat_routes.py:1147 按 doc_type 构建定位信息PDF 含 bbox/pageWord 含 section_chunk_id bbox 已返回但前端未使用content 截断 300 字

1.3 后端已提供但前端未消费的字段

字段 后端来源 当前状态
bbox ChromaDB metadata → _build_citation 解析 JSON → 返回数组 PdfViewer 未接收,未渲染高亮
sub_type ChromaDB metadataMinerU V2 解析) _build_citation 未返回此字段
table_type ChromaDB metadataMinerU V2 解析) _build_citation 未返回此字段
vlm_description MinerUChunkVLM 视觉描述) 未进入 citation 数据
chart_markdown MinerUChunk图表数据表 未进入 citation 数据

二、优化项清单

Phase 1PDF 页内 bbox 高亮(核心优化)

目标:在 PDF 页码跳转的基础上,叠加半透明矩形标注引用区域。

涉及文件

  • dev-ui/src/components/viewers/PdfViewer.vue(主要改动)
  • dev-ui/src/views/Chat.vue(传递 bbox prop
  • api/chat_routes.py无需改动bbox 已返回)

PdfViewer.vue 改动方案

  1. 新增 highlightBbox prop
const props = defineProps({
  url: { type: String, required: true },
  initialPage: { type: Number, default: 1 },
  highlightBbox: { type: Array, default: null }  // [x0, y0, x1, y1] 或 null
})
  1. renderPage()page.render() 完成后叠加绘制高亮矩形:
async function renderPage() {
  // ... 现有的 canvas 渲染逻辑 ...
  await page.render({ canvasContext: ctx, viewport }).promise

  // bbox 高亮叠加
  if (props.highlightBbox && currentPage.value === props.initialPage) {
    drawBboxHighlight(ctx, viewport, props.highlightBbox)
  }
}
  1. 新增 drawBboxHighlight() 函数:
function drawBboxHighlight(ctx, viewport, bbox) {
  // MinerU bbox 坐标系PDF 原始坐标,原点左下角
  // PDF.js viewport原点左上角已通过 getViewport 转换
  const [x0, y0, x1, y1] = bbox
  // 方式1直接传 viewport 转换(如果 bbox 是 PDF 用户空间坐标)
  const topLeft = viewport.convertToViewportPoint(x0, y1)  // 注意 y 翻转
  const bottomRight = viewport.convertToViewportPoint(x1, y0)
  
  const w = bottomRight[0] - topLeft[0]
  const h = bottomRight[1] - topLeft[1]
  
  // 半透明填充
  ctx.fillStyle = 'rgba(99, 102, 241, 0.2)'
  ctx.fillRect(topLeft[0], topLeft[1], w, h)
  // 边框
  ctx.strokeStyle = 'rgba(99, 102, 241, 0.6)'
  ctx.lineWidth = 2
  ctx.strokeRect(topLeft[0], topLeft[1], w, h)
}
  1. watch highlightBbox 变化时重新渲染(切换切片时):
watch(() => props.highlightBbox, () => {
  if (pdfDoc) renderPage()
})

Chat.vue 改动方案

  1. 新增响应式变量 citationBbox
  2. jumpToChunk() 中从 citation 取 bboxcitationBbox.value = citation.bbox || null
  3. navigateToChunk() 中从 chunk metadata 取 bboxcitationBbox.value = chunk.metadata?.bbox ? JSON.parse(chunk.metadata.bbox) : null
  4. 模板中传递给 PdfViewer<PdfViewer :highlight-bbox="citationBbox" ...>

风险点

  • MinerU 的 bbox 坐标系需确认。PDF 标准坐标系原点左下角、y 向上PDF.js viewport 默认原点左上角、y 向下。viewport.convertToViewportPoint 应能处理转换,但需实际测试坐标是否正确。
  • 部分切片可能没有 bboxDOCX 来源、或 MinerU 解析遗漏),需做空值保护。
  • bbox 可能是页面坐标系0-1 归一化)或绝对像素坐标,需确认 MinerU 输出格式。

Phase 2后端 citation 数据增强

目标:让前端获得更多结构化信息,支持差异化渲染和更精准的定位。

涉及文件

  • api/chat_routes.py_build_citation() 函数

改动方案

_build_citation() 的通用字段部分新增:

citation = {
    # ... 现有字段 ...
    # 新增MinerU 结构化元数据
    "sub_type": meta.get('sub_type', ''),        # 图片/图表子类型
    "table_type": meta.get('table_type', ''),     # 表格类型
    "chunk_type": meta.get('chunk_type', 'text'), # 已有,但确保传递
}

在 PDF 分支中新增 bbox 的 page 关联信息(当前已有 bbox 和 page无需额外改动

在 Word 分支中新增更长的 content 截断:

elif doc_type == 'word':
    citation.update({
        "section_chunk_id": meta.get('section_chunk_id'),
        "content": (full_content or meta.get('preview', ''))[:500],  # 300→500提升 DOCX 匹配精度
    })

收益

  • sub_type 可让前端在引用列表中区分图表类型bar/line/natural_image
  • table_type 可让前端判断是否用表格样式展示
  • Word content 截断从 300→500 字,减少 preview API 依赖,提升 fallback 场景下的匹配精度

Phase 3引用列表差异化展示

目标:在消息底部的引用列表中,根据 chunk_type 和 sub_type 显示类型图标/标签,提升可读性。

涉及文件

  • dev-ui/src/views/Chat.vue(模板和 CSS

改动方案

在引用列表项(.citation-item)中增加类型标签:

<div v-for="c in group.items" :key="c.displayIndex" class="citation-item clickable" @click="jumpToChunk(c)">
  <span class="citation-num">[{{ c.displayIndex }}]</span>
  <!-- 新增:类型标签 -->
  <n-tag v-if="c.chunk_type === 'table'" size="tiny" :bordered="false" type="warning">表格</n-tag>
  <n-tag v-else-if="c.chunk_type === 'image' || c.chunk_type === 'chart'" size="tiny" :bordered="false" type="info">
    {{ c.sub_type || '图片' }}
  </n-tag>
  <span v-if="c.page" class="citation-location">第{{ c.page }}页</span>
  <span v-if="c.section" class="citation-section">{{ c.section }}</span>
</div>

效果:用户可以一眼看出引用的是文本、表格还是图表,点击前有预期。


Phase 4弹窗内图表/表格的增强展示

目标:在引用预览弹窗的右侧切片面板中,对表格和图片/图表类型的切片提供差异化展示。

涉及文件

  • dev-ui/src/views/Chat.vue(切片面板模板)
  • 可选:dev-ui/src/components/viewers/ 下新增或复用 viewer

改动方案

  1. 表格切片(chunk_type === 'table'):如果切片 metadata 中有 table_html,渲染为 HTML 表格而非纯文本
  2. 图片/图表切片(chunk_type === 'image' || 'chart'):显示 image_path 对应的图片缩略图
  3. 图表数据预览:如果 chart_markdown 可用,在图表切片下方展示 Markdown 数据表
<div class="citation-chunk" :class="{ target: chunk.is_target }">
  <div class="chunk-header">
    <!-- 现有 header 内容 -->
    <n-tag v-if="chunk.metadata?.chunk_type === 'table'" size="tiny" type="warning">表格</n-tag>
    <n-tag v-if="chunk.metadata?.sub_type" size="tiny" type="info">{{ chunk.metadata.sub_type }}</n-tag>
  </div>
  <!-- 表格:尝试渲染 HTML -->
  <div v-if="chunk.metadata?.chunk_type === 'table' && chunk.metadata?.table_html"
    class="chunk-table" v-html="chunk.metadata.table_html" />
  <!-- 图片/图表:显示缩略图 -->
  <div v-else-if="['image','chart'].includes(chunk.metadata?.chunk_type) && chunk.metadata?.image_path"
    class="chunk-image">
    <img :src="`/api/documents/${citationPreviewSource}/image/${chunk.metadata.image_path}`" />
  </div>
  <!-- 默认:纯文本 -->
  <div v-else class="chunk-text">{{ chunk.document || chunk.content }}</div>
</div>

注意preview API 返回的 chunk metadata 中是否包含 table_htmlimage_path 等字段,需确认后端 /documents/<path>/preview 接口是否透传了这些字段。如果未透传,需要后端配合补充。


Phase 5DOCX 多段落高亮(可选)

目标:当一个切片内容跨多个段落时,高亮所有匹配段落而非仅最佳匹配的一个。

涉及文件

  • dev-ui/src/components/viewers/DocxViewer.vue

改动方案

修改 highlightAndScroll() 中的选择逻辑,从"选最佳单个"改为"选所有超过阈值的"

// 当前:只保留 bestEl
if (score > bestScore) { bestScore = score; bestEl = block }

// 优化:收集所有超过阈值的匹配
const SCORE_THRESHOLD = 30
const matches = []
// ... 遍历后 ...
for (const block of blocks) {
  // ... 计算 score ...
  if (score >= SCORE_THRESHOLD) {
    matches.push({ el: block, score })
  }
}
// 按 score 降序,高亮所有匹配
matches.sort((a, b) => b.score - a.score)
matches.forEach((m, i) => {
  m.el.classList.add('citation-highlight-block')
  if (i === 0) {
    setTimeout(() => m.el.scrollIntoView({ behavior: 'smooth', block: 'center' }), 100)
  }
})

风险:可能高亮过多不相关段落(特别是短文本切片匹配到多处相似内容时)。需配合阈值调优。


三、实施优先级建议

优先级 优化项 改动范围 预估工作量 收益
P0 Phase 1PDF bbox 高亮 PdfViewer.vue + Chat.vue 2-3小时 核心功能补齐PDF 引用精确定位
P1 Phase 2后端 citation 增强 chat_routes.py 30分钟 为后续前端优化提供数据基础
P2 Phase 3引用列表差异化展示 Chat.vue 模板 30分钟 提升可读性,改动低风险
P3 Phase 4弹窗内图表/表格增强 Chat.vue 模板 + 后端 preview 1-2小时 丰富引用预览信息
P4 Phase 5DOCX 多段落高亮 DocxViewer.vue 1小时 可选优化,当前单段落高亮已够用

四、需确认的技术细节

  1. MinerU bbox 坐标系:需确认是 PDF 用户空间坐标72 DPI原点左下角还是归一化坐标0-1还是像素坐标。这决定了 viewport.convertToViewportPoint 的调用方式。建议用一个已知 PDF 的 bbox 值做实验。

  2. preview API 的 metadata 透传范围document_routes.py 的 preview 接口返回 chunk metadata 时,是否包含 table_htmlimage_pathbbox 等字段?如果 ChromaDB metadata 中已存储,理论上会自动包含,但需验证。

  3. PdfViewer 连续滚动 vs 单页模式:当前是单页渲染(一次只显示一页),切换页码时重新渲染。如果要做跨页 bbox 高亮(切片跨两页),需要支持连续滚动模式或双页渲染。当前单页模式已能覆盖大多数场景。

  4. mammoth 对复杂排版的还原度DOCX 转 HTML 时mammoth 对表格嵌套、图片嵌入、特殊字体的还原度有限。如果文档包含复杂排版,高亮匹配可能偏离实际位置。这是 mammoth 库的固有限制。

  5. SSE citations 数据量控制:当前 content 截断 300 字。如果增加到 500 字Phase 2每次 RAG 回答的 SSE 数据量会增加。按平均 5-8 个引用计算,增量约 1-2KB可接受。


五、不在本次范围

以下为更远期优化方向,不纳入本轮实施:

  • 全文档连续滚动预览PdfViewer 改为渲染所有页面(类似浏览器内置 PDF 查看器),支持滚动到任意位置。需要 pdfjs-dist 的多页渲染支持,性能开销大。
  • DOCX 原文编辑:在预览弹窗中支持对 DOCX 内容的标注/批注。需要 DOCX 编辑器集成,超出引用溯源范畴。
  • 引用覆盖率统计:回答中每个段落的引用覆盖情况可视化。需要前端渲染层深度改造。
  • 跨文档对比视图:多个引用来自不同文档时,左右分屏对比。当前单文档预览已满足需求。