Skip to content

JJasonSun/spark_multi_rag

 
 

Repository files navigation

Spark Multi-RAG — 企业财报多模态智能问答系统

基于 RAG 的多模态文档问答产品,让非技术人员也能用自然语言从海量 PDF 中精准获取信息。

产品背景

用户痛点

痛点 现状 本方案
信息检索效率低 财报动辄上百页,手动翻找特定数据耗时 10-30 分钟 自然语言提问,秒级返回答案 + 原文页码
图表信息难以利用 PDF 中的柱状图、趋势图无法被关键词搜索命中 多模态解析自动生成图表描述,纳入语义检索
传统搜索精度差 关键词匹配无法理解"同比增长""环比下降"等语义 向量检索 + Rerank 重排序,理解业务语义
缺乏可信溯源 通用大模型回答无法验证来源 每条回答附带文件名 + 页码 + 页面截图证据链

目标用户

  • 分析师/研究员:需要从多份财报中快速提取关键指标
  • 产品经理/运营:需要理解业务数据但不熟悉专业术语
  • 企业知识库管理员:需要为团队搭建内部文档问答系统

典型场景

用户:去年第三季度研发投入最高的子公司是哪家?
系统:根据财报数据,XX科技在2024年Q3研发投入为5.2亿元,位居首位。
      📎 来源:XX集团2024年报.pdf · 第47页
      [页面截图] [原文片段] [图表描述]

产品架构

┌─────────────────────────────────────────────────────┐
│                   Web Demo (Streamlit)               │
│         macOS 风格 UI · 实时问答 · 证据链展示          │
└──────────────────────┬──────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────┐
│                 RAG Query Engine                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │ Embedding │  │ Vector   │  │ Rerank   │          │
│  │ (bge-m3)  │→│ Search   │→│ (bge-    │→ LLM 生成  │
│  │ 1024-dim  │  │ Cosine   │  │ reranker)│ ecnu-max  │
│  └──────────┘  └──────────┘  └──────────┘          │
└──────────────────────▲──────────────────────────────┘
                       │
┌──────────────────────┴──────────────────────────────┐
│              Document Processing Pipeline            │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │ PyMuPDF  │  │ Image    │  │ Semantic  │          │
│  │ 文本提取  │→│ Vision   │→│ Chunking  │→ JSON     │
│  │ + 图片   │  │ Caption  │  │ 语义切片   │          │
│  │ + 截图   │  │ ecnu-plus│  │ ≤1500字符  │          │
│  └──────────┘  └──────────┘  └──────────┘          │
└─────────────────────────────────────────────────────┘

技术选型与决策理由

决策 选择 理由
模型服务 ECNU LLM Open Platform API OpenAI 兼容协议,一套 SDK 覆盖 chat/embedding/vision/rerank,切换成本低
向量存储 numpy 内存计算 文档量级 < 1万 chunk,无需引入 Milvus/Pinecone 等外部向量库,降低部署复杂度
PDF 解析 PyMuPDF + Vision caption 轻量无需 GPU,图片通过 API 生成描述而非本地 OCR,平衡效果与成本
Rerank 策略 两阶段检索(向量召回 → Rerank 精排) 向量检索召回率高但排序粗糙,Rerank 补充交叉注意力提升 precision@k
前端方案 Streamlit 零前端代码出 Demo,适合 PM 快速验证产品形态,非工程化部署方案
Embedding 离线化 独立脚本批量构建 + 缓存文件 解耦计算与展示,支持断点续存,避免 API 故障时丢进度
语义切片 段落→句号→字符三级兜底 确保 chunk ≤ 1500 字符,避免超长文本触发 API 限制

竞品对比

维度 本项目 通用 RAG 框架(LangChain/LlamaIndex) 企业级产品(通义千问文档问答)
多模态图片理解 ✅ 图片 caption 纳入检索 ❌ 需额外集成 ✅ 内置
Rerank 精排 ✅ 两阶段检索 ⚠️ 需手动配置 ✅ 内置
回答溯源 ✅ 文件名 + 页码 + 页面截图 ⚠️ 取决于实现 ✅ 支持
部署门槛 低(API 调用,无 GPU) 中(需配置多个组件) 低(SaaS)
可定制性 高(代码完全可控) 低(黑盒)
成本 按 API 调用计费 免费 + 自付模型费用 按量计费

快速开始

环境配置

# 安装依赖(Python 3.11+)
uv sync

# 配置 API
cp .env.example .env
# 编辑 .env 填入 ECNU API Key

.env 配置:

LOCAL_API_KEY=your_ecnu_api_key
LOCAL_BASE_URL=https://chat.ecnu.edu.cn/open/api/v1
LOCAL_TEXT_MODEL=ecnu-plus          # vision 模型,用于图片 caption
LOCAL_CHAT_MODEL=ecnu-max           # 生成模型,用于 RAG 回答
LOCAL_EMBEDDING_MODEL=ecnu-embedding-small
LOCAL_RERANK_MODEL=ecnu-rerank

使用流程

# 1. PDF 解析(文本提取 + 图片 caption + 语义切片 + 页面截图)
uv run python core/fitz_pipeline_all.py

# 2. 构建 embedding 缓存(离线批量,支持断点续存)
uv run python scripts/build_embeddings.py

# 3. 启动 Web Demo
uv run streamlit run app/main.py

可选工具

# RAG 评测(20 条标注集,输出 Recall/引用/回答正确率)
uv run python scripts/eval_rag.py
uv run python scripts/eval_rag.py --skip-llm      # 只测检索,不调 LLM

# 重新切片(切片逻辑变更时,无需重跑 PDF 解析)
uv run python scripts/rechunk.py

# Rerank 对比实验
uv run python scripts/eval_rerank.py

# API 健康检查
uv run python scripts/health_check.py

数据准备

将 PDF 文件放入 datas/ 目录,支持嵌套文件夹结构。

核心特性

语义切片

按段落边界切分 chunk,三级兜底确保每条 chunk ≤ 1500 字符:

  1. 按双换行(段落边界)拆分
  2. 合并过短段落(< 200 字符)
  3. 按单换行 → 句号 → 字符强制截断拆分过长段落

图片去重

  • 单次运行内:SHA256 内容哈希去重,避免同 PDF 内重复图片多次调用 Vision API
  • 跨运行:磁盘缓存 (caches/image_caption_cache.json),相同图片直接命中缓存

证据链展示

Streamlit 回答附带完整证据链:

  • 页面截图(PDF 页面渲染为 PNG)
  • 原文片段(命中的 chunk 文本)
  • 图片描述(Vision 模型生成的 caption)

Embedding 离线化

  • scripts/build_embeddings.py:批量构建 embedding(batch_size=50),每 batch 成功后立即保存
  • 支持 Ctrl+C 中断 + 自动断点续存
  • Streamlit 只读缓存,秒级启动

评估指标与迭代验证

评测方法

构建 20 条标注评测集(eval_set.json),覆盖 5 种题型:数值提取、趋势分析、业务策略、跨文档对比、拒答测试。每条标注目标公司、关键词、期望答案。通过 scripts/eval_rag.py 自动化评测。

指标结果

指标 Phase 4 基线 Phase 3 迭代后 变化
Evidence Recall@5 (向量) 80% 95% +15%
Evidence Recall@5 (Rerank) 75% 90% +15%
跨文档 Recall (向量) 0/3 (0%) 3/3 (100%) +100%
跨文档 Recall (Rerank) 0/3 (0%) 3/3 (100%) +100%
Citation Accuracy 100% 100% 持平
Answer Correctness 95% 90% -5%

迭代过程:数据驱动的 Bad Case 分析

第一步:基线评测(Phase 4)

运行评测脚本,发现系统瓶颈:

  • 跨文档对比题("对比千味央厨和伊利股份的应收账款")全部召回失败(0/3)
  • 根因:单向量检索自然坍缩到最相似的文档簇,"千味央厨"主导了 embedding 语义
  • 年报精确页码未命中:检索能找到年报但不是目标页码

第二步:LLM 驱动查询规划(Phase 3)

用 LLM 替代关键词匹配,实现语义级查询理解:

  • LLM 分析问题语义,判断是否需要拆分("对比千味和伊利的应收账款"→需要拆分)
  • 提取实体(公司名),生成优化后的子查询
  • 子查询自动扩展相关关键词(如"应收账款 管理 政策 账期 坏账")
  • 效果:跨文档向量 Recall 从 0% 提升到 100%

第三步:Rerank 多样性约束

Rerank 模型对合并后的结果倾向单公司排序:

  • 新增 ensure_company_diversity(),强制每家公司至少保留 1 个 chunk
  • 效果:跨文档 Rerank Recall 从 0% 提升到 100%

第四步:拒答 Prompt 优化

在 Prompt 中增加规则:数据不足时回答"未在当前资料中找到依据",避免编造数据。

按题型表现

题型 数量 向量 Recall Rerank Recall
数值提取 5 5/5 (100%) 4/5 (80%)
趋势分析 5 4/5 (80%) 4/5 (80%)
业务策略 5 5/5 (100%) 5/5 (100%)
跨文档对比 3 3/3 (100%) 3/3 (100%)
拒答测试 2 2/2 (100%) 2/2 (100%)

剩余 Bad Case(5 条)

问题 原因 改进方向
伊利股份2024年营业总收入 年报在 top-5 但非目标页码(p7 vs p13) 年报 chunks 加权
伊利奶粉业务营收同比增长 LLM 输出 2.53%,实际应为 7.53% 检索结果中数据源冲突
伊利股份2022年营收增长 检索返回研报而非 2022 年报 实际财报优先级提升
千味央厨2022年Q1营收 检索结果中无直接季度数据 扩大检索范围
中恒电气2024年营收增长 LLM 给出数字而非拒答 拒答 prompt 进一步优化

迭代路线

阶段 目标 状态
Phase 1 接入 ECNU API,统一模型服务 ✅ 完成
Phase 2 Streamlit Demo + 产品化包装 ✅ 完成
Phase 2.5 语义切片 + Embedding 离线化 + 证据链展示 ✅ 完成
Phase 3 多文档对比问答(LLM 查询规划 + 分公司检索 + Rerank 多样性) ✅ 完成
Phase 4 自动化评测体系(20 条标注集 + Bad Case 驱动迭代) ✅ 完成
Phase 5 产品文档完善 + 简历表达优化 ✅ 完成

迭代逻辑:Phase 4 先做评测基线 → 发现跨文档检索是瓶颈 → Phase 3 针对性解决 → 重新评测验证效果。用数据驱动迭代,而非凭直觉优化。

项目结构

spark_multi_rag/
├── app/
│   └── main.py                        # Streamlit Web Demo(只读缓存)
├── core/                              # RAG 核心逻辑
│   ├── fitz_pipeline_all.py           # PDF 解析(语义切片 + 图片去重 + 截图)
│   ├── rag_from_page_chunks.py        # RAG 检索 + Rerank + 问答
│   ├── get_text_embedding.py          # 文本向量化
│   └── extract_json_array.py          # JSON 结构提取
├── image_utils/                       # 图片分析工具
│   ├── async_image_analysis.py        # Vision caption(异步)
│   └── caption_cache.py               # 图片 caption SHA256 磁盘缓存
├── scripts/
│   ├── build_embeddings.py            # 离线批量构建 embedding(batch=50, 断点续存)
│   ├── rechunk.py                     # 重新切片(无需重跑 PDF 解析)
│   ├── eval_rag.py                    # RAG 评测脚本(20 条标注集 + 指标计算)
│   ├── eval_rerank.py                 # Rerank 对比实验
│   └── health_check.py               # API 健康检查
├── notebooks/                         # Jupyter Notebook
│   ├── data_process.ipynb             # 数据预处理
│   ├── model_finetune.ipynb           # Qwen2.5-7B LoRA 微调
│   └── Qwen3_14B_finetune.ipynb       # Qwen3-14B LoRA 微调
├── docs/
│   ├── plan.md                        # 产品迭代规划
│   ├── JD.md                          # 目标岗位分析
│   ├── resume_project.md              # 简历项目经历
│   ├── eval_report.md                 # 评测报告(Markdown)
│   └── eval_report.json               # 评测完整结果(JSON)
├── eval_set.json                      # 20 条标注评测集(开源)
├── datas/                             # 原始 PDF 及测试集(gitignore)
├── caches/                            # 缓存目录(自动创建)
│   ├── embeddings.npy                 # embedding 向量缓存
│   ├── chunks.json                    # chunk 列表缓存
│   ├── image_caption_cache.json       # 图片 caption 缓存
│   └── page_images/                   # 页面截图
└── .env                               # 环境变量(gitignore)

技术栈

组件 方案
PDF 解析 PyMuPDF + ECNU Vision (ecnu-plus, 语义切片 + 图片去重)
Embedding ECNU Embedding (bge-m3, 1024-dim, 批量离线构建)
Rerank ECNU Rerank (bge-reranker-v2-m3, Cohere 兼容)
生成模型 ECNU Chat (ecnu-max, DeepSeek-V4-Flash)
视觉模型 ECNU Vision (ecnu-plus, Qwen3.6-27B)
向量存储 numpy (cosine similarity)
前端 Streamlit + macOS glassmorphism CSS

About

科大讯飞多模态RAG图文问答挑战赛

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 73.0%
  • Jupyter Notebook 27.0%