Skip to content
medrec-sysPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

29 Commits

Folders and files

Repository files navigation

Medrec · 后端服务

面向 PDF 文档的 RAG 知识库问答平台后端(项目名 Medrec):把文档变成可检索的知识,让 AI 的回答每句话都能追溯到原文的具体页码与位置。

Java Spring Boot Spring AI Vector DB MySQL MinIO Frontend

前端仓库:medrec-sys/front


一、项目简介

Medrec 是一个完整的 RAG(Retrieval-Augmented Generation)应用后端。用户可以:

  1. 创建知识库(一个知识库对应 Qdrant 中一个 collection,可自定义向量维度);
  2. 上传 PDF 文档,系统异步完成 解析 → 结构化 → 父子分块 → 向量化入库;
  3. 创建AI 智能体,为它配置提示词、温度、topK、相似度阈值、最大记忆轮数,并挂载任意多个知识库;
  4. 与智能体流式对话,回答中带有 <ID>文档ID</ID> 引用标记,点击即可在后端返回的 page + bbox 指引下跳转到 PDF 原文位置。

与"拿现成框架拼一个 demo"不同,本项目的重点在于 RAG 全链路的工程化实现:文档结构化解析、父子分块检索、多向量库并发检索、查询扩展、引用溯源、异步入库与失败补偿,都是在 Spring AI 之上自行实现的。


二、功能特性

模块 能力
认证 注册 / 登录,JWT 鉴权 + BCrypt 密码加密,拦截器统一校验并注入用户上下文
AI 智能体 增删改查;配置提示词、温度(0~1)、max_message、top_k、相似度阈值;挂载 / 解绑多个知识库
知识库(向量库) 增删改查;自定义向量维度;直接发起向量相似度检索并查看命中片段
文档管理 PDF 上传(异步任务,返回 taskId);大文件自动分片;分页查询;删除(同时清理 DB、MinIO、向量)
对话 SSE 流式输出;会话记忆持久化到 MySQL;历史记录查询 / 单条删除 / 清空;把本轮命中文档回传前端
异步任务 上传解析全流程进度可查(等待许可 → 文件转 md → 保存元数据 → 文本转换 → 写向量库 → 完成 / 失败)

三、技术栈

分类 技术 版本 / 说明
语言 Java 21
框架 Spring Boot 4.1.1(spring-boot-starter-webmvc + webflux:前者提供 MVC,后者提供 Flux 流式响应)
AI 框架 Spring AI 2.0.1(spring-ai-rag、spring-ai-vector-store-advisor、spring-ai-starter-model-openai)
大模型 阿里云百炼 DashScope(OpenAI 兼容模式) 对话 qwen-flash、多模态 qwen3.5-flash、向量 text-embedding-v4
向量数据库 Qdrant 通过 gRPC(默认 6334)访问,每个知识库独立 collection
关系数据库 MySQL 8 + MyBatis-Plus MyBatis-Plus 3.5.16,含 mybatis-plus-jsqlparser
对象存储 MinIO 8.5.13,保存上传的原始 PDF
文档解析 MinerU 云端 API mineru.net/api/v4,PDF → Markdown / content_list.json(含页码与 bbox)
PDF 处理 Apache PDFBox 3.0.8,用于页数统计与超大 PDF 分片
缓存 Caffeine 3.2.4,缓存向量库实例、智能体实例、异步任务状态
认证 JJWT 0.13.0 + jBCrypt 0.4
其他 commons-compress 1.28.0、simplemagic 1.17、fastjson 2.0.64、Lombok 1.18.42 解压解析结果、MIME 类型识别等

四、系统架构

┌───────────────────────────────────────────────────────────────────────┐
│                     前端 Vue 3(medrec-sys/front)                     │
│      对话窗口 · 智能体管理 · 知识库管理 · 检索 · PDF 原文定位            │
└──────────────────────────┬────────────────────────────────────────────┘
                           │ REST + SSE(Flux 流式)
┌──────────────────────────▼────────────────────────────────────────────┐
│                      Spring Boot 4 + Spring AI                         │
│                                                                        │
│  Controller ──▶ LoginInterceptor(JWT) ──▶ Service ──▶ Store / Client   │
│                                                                        │
│  ┌── RAG 问答链路 ────────────────────────────────────────────────┐    │
│  │ MyQueryExpander   查询扩展(消解指代 → 生成 N 个检索句式)        │    │
│  │        ↓                                                       │    │
│  │ MultiVectorStoreDocumentRetriever  多知识库并发检索             │    │
│  │        ↓                                                       │    │
│  │ MyDocumentPostProcessor  按相似度重排 + 截断 topK               │    │
│  │        ↓                                                       │    │
│  │ MyContextualQueryAugmenter  拼装上下文(带 <ID> 标记)           │    │
│  │        ↓                                                       │    │
│  │ OpenAiChatModel → SSE 流式输出                                  │    │
│  └────────────────────────────────────────────────────────────────┘    │
└───┬──────────────┬───────────────┬────────────────┬───────────────────┘
    │              │               │                │
┌───▼────┐  ┌──────▼─────┐  ┌──────▼─────┐  ┌───────▼───────┐
│ Qdrant │  │  MySQL 8   │  │   MinIO    │  │    MinerU     │
│ 向量检索│  │ 业务+会话记忆│  │ 原始 PDF    │  │ PDF → 结构化   │
└────────┘  └────────────┘  └────────────┘  └───────┬───────┘
                                                    │
                                            ┌───────▼────────┐
                                            │ DashScope 模型  │
                                            │ 对话 / VLM / 向量│
                                            └────────────────┘

分包结构

包 职责
controller REST 接口层,统一返回 Result<T> / PageVO<T>
service / service.impl 业务逻辑,含文档异步入库主流程
Ai RAG 核心:AiAgent、VectorStore 及自研 Advisor / Retriever / Expander
store 有状态组件管理器:VectorStoreManager、AgentManager、AsyncTaskStore(均基于 Caffeine 缓存)
client 外部服务客户端:ModelClient(模型)、MinerClient(MinerU)、MinioClient(对象存储)
mapper MyBatis-Plus Mapper
domain entity / dto / bo / common(Result、PageDTO、PageVO)
interceptor / utils JWT 拦截器与 UserContext、文本处理与工具类
exception BusinessException + GlobalExceptionHandler 全局异常收敛

五、核心实现

5.1 文档入库流水线(异步 + 独立事务 + 失败补偿)

KnowledgeServiceImpl#saveAsync 是整个项目的入口级流程:

步骤 说明
1. 参数校验 仅接受 PDF(simplemagic 识别 MIME),校验知识库存在
2. 并发控制 使用 Semaphore(1) 全局串行执行解析任务,避免多个大文件同时解析压垮服务
3. 文档解析 上传 MinerU 转 Markdown,轮询解析状态,并把实时进度(含分片解析的页数进度)写入任务
4. 元数据落库 用 @Transactional(REQUIRES_NEW) 独立事务插入 t_knowledge 拿到 ID,不受异步线程事务影响
5. 结构化分块 TextUtil 把解析结果转为 TextSegment,再做父子分块
6. 回填分块数 更新 t_knowledge.chunk
7. 存原始文件 原始 PDF 写入 MinIO
8. 写入向量库 批量并发写入 Qdrant
9. 失败补偿 任一步失败即回滚三处数据:删除 MySQL 记录、删除 MinIO 文件、删除已写入的向量

5.2 结构化文档解析(而不是"整篇切文本")

MinerClient 针对 MinerU 返回的元素类型分别处理,尽量保留文档语义:

  • 过滤噪声:丢弃页眉(HEADER)与页码(PAGE_NUMBER);
  • 长文本:超过 2000 字再二次切分;
  • 表格:转为"表格标题 + 表格内容 + 表格脚注"的自然语言文本,便于向量检索命中;
  • 列表:相邻列表项合并到 2000 字以内,避免碎片化;
  • 图片:交给多模态模型 qwen3.5-flash 生成结构化描述(图片类型 / 核心内容 / 详细描述 / 关键词 / 适用场景),并发处理、10 分钟超时,单张失败降级为占位文本而不影响整体入库;
  • 小文本块合并:同页且不足 500 字的相邻块合并,并合并两者的 bbox 范围;
  • 超大 PDF 分片:超过 200 页自动用 PDFBox 切分后分段解析,并在回收结果时修正分片导致的页码偏移(pageIdx + 1 + 分片序号 × 200)。

5.3 父子分块 + 子分数量回填

TextUtil 构建两级结构,解决"检索要准、喂给模型要全"的矛盾:

  • 父块 = MinerU 的段落级内容,parentId = "0",携带 page 与 bbox;
  • 子块 = 用 BreakIterator 按中文句子切分父块(丢弃 ≤10 字的碎句),parentId = 知识ID_父块ID。

检索在 VectorStore#similaritySearchWithSplitSentence 中完成:

  1. 以 topK × 2 只在子块中检索(过滤 parentId != "0");
  2. 按 parentId 聚合,取每个父块下最高的子块相似度;
  3. 按父块 ID 批量取回父块完整原文;
  4. 把子块分数回填到父块(同时修正 distance = 1 - score)并降序排序。

即:用细粒度子块决定相关性排序,用完整父块喂给模型生成,兼顾准确率与上下文完整性。

5.4 自研多向量库并发检索

Spring AI 原生的 DocumentRetriever 只支持单个向量库,而本项目一个智能体可挂载多个知识库。MultiVectorStoreDocumentRetriever 实现了 DocumentRetriever 接口,用 CompletableFuture 并发检索所有挂载的知识库并合并结果。

5.5 自研 Advisor 链

AiAgent 在运行时按智能体配置动态构建 ChatClient,装配以下组件:

组件 解决的问题
MessageChatMemoryAdvisor + JDBC 仓库 会话记忆持久化到 SPRING_AI_CHAT_MEMORY,按智能体的 max_message 做窗口截断
MyQueryExpander(查询扩展) 用 LLM 结合对话历史消解代词指代,生成 4 个适合向量检索的完整问句,与原问题一起检索;历史仅保留最近 10 条;扩展失败自动退回原查询
MultiVectorStoreDocumentRetriever 多知识库并发检索
MyDocumentPostProcessor(后处理) 多库 × 多查询会召回远超 topK 的文档,此处按相似度降序截断到 topK
MyContextualQueryAugmenter(查询增强) 改写自 Spring AI 源码:原生实现会 new Query(...) 从而丢失历史消息,此处改为 query.mutate() 保留历史;同时自定义文档格式化,把每段上下文渲染为 <ID>文档ID</ID> + 正文
DocumentAdvisor 从 Advisor 上下文中取出本轮检索到的文档并挂载回 AiAgent,供接口返回给前端展示
自定义提示词模板 约束模型"只依据上下文回答"、每句话尾标注 <ID>id</ID>、无依据时明确说明未提供相关信息

5.6 引用溯源闭环(跨前后端)

  1. 后端在拼装上下文时把文档渲染为 <ID>文档ID</ID>正文,并在提示词中要求模型逐句标注来源 ID;
  2. 前端把 <ID>x</ID> 渲染为可点击元素,点击后按 ID 调用 /api/knowledge/docs 拉取该文档;
  3. 文档 metadata 中的 page 与 bbox 直接驱动右侧 PDF 预览组件滚动到指定页并高亮坐标区域。

这样"AI 的每一句话"都可被验证,而不是黑盒回答。

5.7 性能与稳定性优化

  • 批量并发写入向量库:每批 10 条,线程池大小取 min(CPU 核数, 批次数),CompletableFuture.allOf 汇合,并做优雅关闭(超时 shutdownNow);
  • 实例缓存:VectorStoreManager 与 AgentManager 用 Caffeine 缓存向量库 / 智能体实例(上限 1000,访问后 30 分钟过期),避免每次对话重复构建 embedding 模型与 Qdrant 连接;
  • 并发检索 + 历史窗口限制:查询扩展并发化、会话历史限制条数后,端到端响应速度明显提升(见提交记录 8676e55、6ecca24);
  • 大文件处理:200 页 / 200MB 上限 + 自动分片,前端上传接口改为异步任务,避免长连接超时。

六、快速开始

6.1 环境要求

  • JDK 21+
  • Maven 3.9+
  • Docker / Docker Compose(用于中间件)
  • 一个可用的 DashScope API Key 与 MinerU Token

6.2 启动中间件

仓库提供了 md-com.yml,一键拉起所需依赖:

docker compose -f md-com.yml up -d
服务 容器名 端口映射 用途
MySQL 8.0 md-mysql 9006:3306 业务数据 + 会话记忆
MinIO md-minio 9090:9000(S3 API)、9000:9001(控制台) 原始 PDF 存储
Qdrant md-qdrant 16333:6333(REST)、16334:6334(gRPC) 向量检索
Nginx md-nginx 9050:80 前端静态资源(使用预构建镜像 medrec-web:latest)
后端 md-tomcat 9016:8080 后端服务(使用预构建镜像 medrec-server:latest)

注意:md-com.yml 中的 md-nginx / md-tomcat 使用的是预构建镜像,需要先自行执行两端的 docker build;另外 volume 路径(/volume/Medrec/...)是 Linux 部署写法,本地运行请改为自己的路径。

6.3 初始化数据库

data/table.sql 会重建 medrec 库并创建全部表:

mysql -h127.0.0.1 -P9006 -uroot -p < data/table.sql

6.4 修改配置

文件 用途
src/main/resources/args-dev.yaml 本地开发配置(默认被 application.yaml 通过 spring.config.import 导入):MySQL、MinIO、Qdrant 地址与端口
src/main/resources/args.yaml 容器 / 生产配置模板:地址使用 compose 中的服务名(md-mysql、md-minio、md-qdrant)
src/main/resources/application.yaml 模型接入(base-url、api-key、对话 / 向量模型名)、MinerU Token、文件上传大小上限、MyBatis-Plus 配置

请按自己的环境填写 DashScope API Key 与 MinerU Token;若使用其他 OpenAI 兼容服务,只需替换 base-url、api-key 与模型名。

6.5 启动服务

# 开发模式
mvn spring-boot:run

# 打包运行(产物为 target/ROOT.jar)
mvn clean package -DskipTests
java -jar target/ROOT.jar

默认端口 8080(由 args-dev.yaml 中的 internet.PORT 决定)。前端默认请求 http://localhost:9016,对应 compose 中的 9016:8080 映射。

6.6 构建镜像

docker build -t medrec-server:latest .

Dockerfile 为多阶段构建:maven:3.9-eclipse-temurin-21 中借助 .mvn/settings.xml(阿里云镜像)下载依赖并打包 → eclipse-temurin:21-jre 运行 target/ROOT.jar。注意构建时会 COPY .mvn/settings.xml,该文件不可缺失。


七、REST API 一览

所有接口统一返回 Result<T>:code = 1 表示成功,message = "NOT_LOGIN" 表示未登录。除 /api/login/** 外的请求都需要在 Authorization 头中携带 JWT。

登录 / 用户

方法 路径 说明
POST /api/login/login 登录,返回 JWT
POST /api/login/register 注册
POST /api/user/list 用户分页查询
GET /api/user/{id} 用户详情
POST / PUT / DELETE /api/user、/api/user、/api/user/{id} 新增 / 修改 / 删除

AI 智能体

方法 路径 说明
POST /api/agent/list 分页查询
GET /api/agent/{id} 详情
POST / PUT /api/agent 新增 / 修改
DELETE /api/agent/{id} 删除
POST /api/agent/knowledge 为智能体挂载知识库(agentId、vectorId)
DELETE /api/agent/knowledge 解绑知识库

知识库(向量库)

方法 路径 说明
POST /api/vector/list 分页查询
GET /api/vector/{id} 详情
POST / PUT /api/vector 新增 / 修改
DELETE /api/vector/{id} 删除
POST /api/vector/search/{id} 在指定知识库中做向量相似度检索
GET /api/vector/agent/{id} 查询某智能体已挂载的知识库

文档(知识)

方法 路径 说明
POST /api/knowledge/list 分页查询
GET /api/knowledge/{id} 详情
POST /api/knowledge/{vectorId} 上传 PDF,异步入库,返回 taskId
PUT /api/knowledge 修改
DELETE /api/knowledge/{id} 删除(同步清理向量与 MinIO 文件)
GET /api/knowledge/vector/{id} 查询某知识库下的全部文档
POST /api/knowledge/docs 按文档 ID 列表批量取回文档(引用溯源用)

AI 对话

方法 路径 说明
POST /api/ai/chat/{id} 与指定智能体对话,SSE 流式返回
POST /api/ai/query 查询扩展预览:传入原问题、生成数量 n 与历史消息,流式返回 LLM 生成的检索问句
GET /api/ai/history/{id} 查询会话历史
DELETE /api/ai/{agentId}/{id} 删除单条会话记录
DELETE /api/ai/all/{id} 清空某智能体会话
GET /api/ai/vectors/{id} 获取本轮检索命中的文档

异步任务

方法 路径 说明
GET /api/task?taskId=xxx 查询任务状态与进度描述

八、数据库设计

data/table.sql

表 说明
t_user 用户(账号唯一、BCrypt 密码、注册 / 登录时间)
t_agent AI 智能体(提示词、温度、max_message、top_k、相似度,温度有 0~1 的 CHECK 约束)
t_vector 知识库 / 向量库(名称、描述、向量维度 dim)
t_knowledge 知识文档(源文件名、MinIO 路径、所属知识库、分块数)
t_agent_vector 智能体 ↔ 知识库 的多对多关联
SPRING_AI_CHAT_MEMORY Spring AI 会话记忆(会话 ID + 序号,含联合索引)

九、项目结构

Spring/
├── data/table.sql                  # 建库建表脚本
├── md-com.yml                      # Docker Compose:MySQL / MinIO / Qdrant / Nginx / 后端
├── Dockerfile                      # 后端多阶段构建
├── pom.xml
└── src/main/
    ├── java/fun/medrec/spring/
    │   ├── Application.java
    │   ├── Ai/                     # RAG 核心:AiAgent、VectorStore、自研 Advisor/Retriever/Expander
    │   ├── client/                 # ModelClient、MinerClient、MinioClient
    │   ├── config/                 # MyBatis-Plus、异步线程池、MVC 配置
    │   ├── controller/             # 7 个 REST Controller
    │   ├── domain/                 # entity / dto / bo / common
    │   ├── exception/              # 业务异常与全局异常处理
    │   ├── interceptor/            # JWT 登录拦截器、UserContext
    │   ├── mapper/                 # MyBatis-Plus Mapper
    │   ├── service/                # 业务接口与实现
    │   ├── store/                  # VectorStoreManager、AgentManager、AsyncTaskStore
    │   └── utils/                  # JWT、BCrypt、文本分块、时间工具
    └── resources/
        ├── application.yaml        # 模型接入与全局配置
        ├── args.yaml               # 容器环境配置模板
        ├── args-dev.yaml           # 本地开发配置
        └── logback-spring.xml      # 日志配置

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages