Skip to content

RAG 知识问答系统 软件需求规格说明书(SRS) ​


1. 引言 ​

1.1 目的与范围 ​

本文档定义「RAG 知识问答系统」v1.0 的系统架构、功能需求、界面设计与接口设计,作为前后端开发、联调与验收的依据。

系统定位:面向中小规模私有知识库(个人/团队级,文档总量 ≤ 1000 篇)的检索增强问答系统。用户上传 txt / pdf / docx 文档,系统切块入库,提供基于「BM25 关键词 + 向量语义 → RRF 融合 → Rerank 精排 → LLM 生成」管线的问答服务,回答附带原文引用。

v1.0 不包含:多用户权限体系、公网部署与高可用、对话式多轮上下文追问、模型微调。

1.2 术语表 ​

术语说明
Embedding将文本映射为稠密向量,语义相近则向量相近(本项目用 bge-small-zh-v1.5,512 维)
BM25基于词频(TF)与逆文档频率(IDF)的经典关键词检索算法,中文需先用 jieba 分词
RRFReciprocal Rank Fusion,按排名累加 1/(K+rank) 融合多路检索结果,K=60
Rerank / 精排Cross-Encoder(bge-reranker-base)将「问题+文档」拼接后精算相关度,对召回的 Top-N 重排序
Chunk / 块文档切块后的最小检索单元
召回从全量块中粗筛候选集(BM25 一路 + 向量一路)
引用回答中以 [1] [2] 标注结论来源,对应检索命中的原文块

2. 总体描述 ​

2.1 系统定位与目标 ​

核心目标:

  1. 全部参数(LLM、模型路径、检索参数、切块参数)可在界面上配置,改配置不改代码
  2. 知识库文档通过界面上传、切块、入库、删除,无需手工放文件
  3. 问答回答带引用来源,可追溯原文
  4. 检索链路各阶段结果可观测:双路调试视图 + 问答页过程面板(用于调参与排障)

2.2 用户与角色 ​

角色说明v1.0 权限
普通用户提问、查看回答与引用问答页全部功能
知识库管理员上传/删除文档、重建索引、向量查询调试知识库页 + 问答页
系统管理员维护配置参数配置页 + 以上全部

v1.0 为单用户模式(无登录鉴权),角色仅用于功能分区;多用户与鉴权列入 v2.0 展望。

2.3 运行环境 ​

项要求
服务端Windows / Linux,Python 3.10+,单进程部署
客户端现代浏览器(Chrome / Edge 最近两个大版本)
模型本地模型目录:bge-small-zh-v1.5(embedding)、bge-reranker-base(精排),手工下载到 backend/models/
LLM任意 OpenAI 兼容 Chat Completions 接口(地址、模型名、Key 均可配置)
存储SQLite(配置与文档元数据)+ Chroma 落盘(向量)+ 磁盘文件(原始上传文件)

2.4 约束与假设 ​

  • 假设:单机单实例部署;模型加载内存占用约 2~3GB(CPU 推理可用,GPU 可选加速)
  • 约束:回答必须严格基于检索命中的内容,无命中时明确告知,禁止模型自由发挥(沿用 rag_core 的系统提示词策略)
  • 假设:文档为「段落型」文本资料;扫描版 PDF(纯图片无文字层)解析出的文本为空时应标记失败并提示 [TBD:是否引入 OCR]

3. 技术栈与项目结构 ​

3.1 技术栈 ​

层选型说明
前端Vue 3 + Vite + Pinia + Vue Router + Element Plus选项式 API,Axios 通信
后端Python + FastAPI + UvicornREST + SSE,Pydantic 校验
检索jieba(分词)+ 自研 BM25 + Chroma(向量库)复用 rag_core.py 成熟实现
模型sentence-transformers(bge-small-zh-v1.5 / bge-reranker-base)本地 models/ 目录加载,离线运行
生成OpenAI 兼容接口(openai SDK)模型名 / 地址 / Key 可配置
文档解析pypdf(pdf)、python-docx(docx)、内置读取(txt)UTF-8 / GBK 自动探测
元数据存储SQLite(sqlite3 标准库)零运维

3.2 项目结构 ​

rag-demo/
├── frontend/                    # Vue3 前端
│   ├── src/
│   │   ├── api/                 # Axios 封装(config.js / kb.js / qa.js)
│   │   ├── views/               # 页面:QaChat / KbManage / ConfigManage
│   │   ├── stores/              # Pinia:配置、知识库状态
│   │   ├── components/          # 引用面板、检索过程面板、上传组件等
│   │   └── router.js
│   └── vite.config.js           # dev 代理 /ragapi → backend:8088
│
├── backend/                     # Python 后端
│   ├── app/
│   │   ├── main.py              # FastAPI 入口、CORS、路由挂载
│   │   ├── routers/             # config.py / kb.py / qa.py
│   │   ├── services/
│   │   │   ├── rag_engine.py    # 移植 rag_core:模型加载、BM25、RRF、Rerank、生成
│   │   │   ├── ingestion.py     # 解析 → 切块 → 向量化 → 入库(含状态机)
│   │   │   └── config_service.py# 配置读写、热更新、校验
│   │   ├── models/              # Pydantic 请求/响应模型
│   │   └── db.py                # SQLite 访问层
│   ├── data/
│   │   ├── uploads/             # 用户上传的原始文件
│   │   └── app.db               # SQLite(配置 + 文档/块元数据)
│   ├── chroma_db/               # Chroma 向量落盘(自动生成)
│   ├── models/                  # 本地模型(bge-small-zh-v1.5、bge-reranker-base)
│   └── requirements.txt
│
└── doc/                         # 本文档

4. 系统架构 ​

4.1 分层架构 ​

mermaid
flowchart LR
    subgraph 前端 [frontend · Vue3]
        UI[页面层<br/>问答 / 知识库 / 配置]
        STORE[Pinia 状态]
    end
    subgraph 后端 [backend · FastAPI]
        API[接口层 routers<br/>REST + SSE]
        SVC[服务层 services<br/>RagEngine / Ingestion / ConfigService]
        REPO[数据层<br/>SQLite + Chroma + 文件]
    end
    LLM[外部 LLM<br/>OpenAI 兼容接口]
    UI -->|HTTP /api| API
    API --> SVC --> REPO
    SVC -->|Chat Completions| LLM

4.2 两条核心数据流 ​

入库流(知识库管理触发)

mermaid
flowchart LR
    A[上传文件<br/>txt/pdf/docx] --> B[解析为纯文本]
    B --> C[切块<br/>窗口+重叠, 参数可配]
    C --> D[bge-small-zh<br/>批量向量化]
    D --> E[(Chroma<br/>embedding+原文+metadata)]
    C --> F[BM25 索引<br/>jieba 分词后内存重建]
    E --> G[(SQLite<br/>文档/块元数据+状态)]
    F --> G

查询流(问答触发,v1.0 核心管线)

mermaid
flowchart LR
    Q[用户问题] --> B1[BM25 召回<br/>Top-50]
    Q --> B2[向量召回<br/>Chroma Top-50]
    B1 --> R["RRF 融合<br/>1/(60+rank) 累加"]
    B2 --> R
    R --> RR[Rerank 精排<br/>bge-reranker 取 Top-6]
    RR --> P[拼装带编号的<br/>参考资料 Prompt]
    P --> L[LLM 流式生成<br/>SSE 逐段推送]
    L --> A1["回答 + [n] 引用标注"]

关键规则:

  • BM25 参数 k1=1.5、b=0.75(进配置页可调)
  • Chroma 距离度量 cosine,返回距离统一转相似度 1 - distance
  • 生成系统提示词:严格依据参考资料回答,资料中没有则回答「根据现有资料无法回答」,关键结论后标注来源编号

4.3 配置热更新机制 ​

  • 所有配置存 SQLite sys_config 表,启动时加载,替代原 .env 方案
  • 修改保存后:检索类参数(topK、RRF K、切块参数等)即时生效;模型类配置(模型路径、LLM 地址)保存后标记「待重载」,由管理员在配置页点击「重载引擎」触发模型重新加载(避免每次保存都数十秒的加载开销)
  • LLM_API_KEY 属敏感信息:接口返回时脱敏(仅显示末 4 位),前端不可回显完整值

5. 功能需求 ​

5.1 功能模块总览与优先级 ​

模块功能点优先级
F1 配置管理查看/编辑配置、分组表单、LLM 连通性测试、恢复默认值、重载引擎P0
F2 知识库管理文件上传、解析切块入库、文档列表与状态、删除文档、全量重建、双路检索调试P0
F3 增强检索问答流式问答、引用来源面板、检索过程可视化、异常兜底P0
F4 会话历史本地保存最近 50 条问答记录、完整回放、单条删除与清空P0

5.2 F1 配置管理 ​

F1.1 配置分组与字段

分组字段类型默认值说明
LLM 生成llm_modelstring-模型名,如 deepseek-chat
llm_api_urlstring-OpenAI 兼容接口地址
llm_api_keysecret-保存后脱敏显示
llm_temperaturefloat0.30~1
检索参数recall_sizeint50每路召回条数
final_top_kint6融合精排后送入 LLM 的块数
rrf_kint60RRF 常数
bm25_k1 / bm25_bfloat1.5 / 0.75BM25 参数
min_similarityfloat0.3相似度阈值,低于此值视为无命中
切块参数chunk_sizeint300每块目标字符数
chunk_overlapint50相邻块重叠字符数
模型路径embedding_model_pathstringmodels/bge-small-zh-v1.5相对 backend/
reranker_model_pathstringmodels/bge-reranker-base同上

F1.2 功能规则

编号规则
F1-1表单按分组分区展示,保存前做类型与范围校验(非法值行内红字提示,禁止提交)
F1-2「测试 LLM 连通性」:后端用当前表单值(未保存也可测)发送一条最小请求,返回 成功/失败+错误详情
F1-3「恢复默认值」:将表单回填为系统默认值(LLM 密钥类不清空),仍需手动保存
F1-4保存成功后提示哪些参数已即时生效、哪些需「重载引擎」,重载期间问答接口返回 503
F1-5模型路径保存时校验目录存在且包含 config.json,否则拒绝保存并提示

5.3 F2 知识库管理 ​

F2.1 文件上传与入库

编号规则
F2-1支持 .txt / .pdf / .docx,单文件 ≤ 20MB,一次最多 10 个;其余扩展名前端拦截
F2-2txt 自动探测编码(UTF-8 优先,失败回退 GBK);pdf 用 pdfplumber 提取文本层;docx 用 python-docx 按段落提取
F2-3解析后正文为空(如扫描版 PDF)→ 该文档状态置「失败」,原因「未解析到文本」,不阻断批次中其他文件
F2-4上传成功即创建文档记录(状态「处理中」),后台异步执行:解析 → 切块(F1 切块参数)→ 批量向量化 → 写入 Chroma → 更新状态「就绪」;任一步异常置「失败」并记录原因
F2-5入库以文件内容 MD5 去重:重复上传返回已有文档记录并提示「已存在」
F2-6新文档入库完成后,BM25 内存索引基于全量「就绪」块增量重建
F2-7删除文档需二次确认;级联删除其全部块(Chroma 按 metadata.doc_id 删除 + SQLite 记录),同步刷新 BM25 索引;原始上传文件一并删除
F2-8「全量重建」:清空 Chroma 集合,按当前切块参数重新处理全部文档(用于改切块参数后重建),执行期间知识库页锁定操作按钮

F2.2 文档列表

字段:文件名、类型、大小、块数、状态(处理中/就绪/失败)、失败原因、上传时间、操作(查看块 / 删除)。列表自动轮询刷新(处理中状态时每 3 秒)。

F2.3 双路检索调试(BM25 + 向量 + RRF)

输入任意文本 + topK,并列返回三组结果,用于对比两路召回差异、验证入库质量与调优检索参数:

结果组内容分数含义
BM25 路Top-K 块(文档名、原文摘要)词面匹配分(无上界)
向量路Top-K 块(文档名、原文摘要)语义相似度 0~1
RRF 融合两路融合后 Top-K累计 RRF 分,附两路各自名次

三列并排展示,同一块横向对齐高亮,直观看到「哪路召回了它、名次多少、融合后排在第几」。

5.4 F3 增强检索问答 ​

编号规则
F3-1输入问题提交后,前端清空上一次回答区,进入「思考中」态(阶段提示:检索中 → 重排中 → 生成中)
F3-2回答通过 SSE 流式渲染;生成完毕后展示引用来源面板
F3-3引用面板列出 Top-K 块:编号、来源文件名、原文摘要(前 80 字)、相似度;回答中的 [n] 与面板编号一一对应;点击 [n] 高亮定位对应来源条目
F3-4所有候选块相似度低于 min_similarity(F1)→ 不调用 LLM,直接返回「根据现有资料无法回答该问题」并提示去知识库补充资料
F3-5LLM 调用失败(超时/鉴权/限流)→ 停止流式,展示错误原因与「重试」按钮;重试复用同一次检索结果,不重复检索
F3-6检索过程可视化:回答完成后可展开「检索过程」折叠面板,按四个阶段展示——① BM25 路 Top-10(词面分)② 向量路 Top-10(相似度)③ RRF 融合排名(累计分)④ Rerank 后 Top-6(精排分);每条含来源文档与块摘要;阶段 ④ 的序号与引用面板 [n] 编号一致,可点击联动

5.5 F4 会话历史 ​

编号规则
F4-1前端 localStorage 保存最近 50 条完整记录:问题、回答全文、引用列表、检索过程数据、耗时、时间戳
F4-2问答页提供「历史」入口,按时间倒序列出;点击条目在对话区完整回放(含引用与检索过程面板)
F4-3支持单条删除与全部清空(清空需二次确认)
F4-4v1.0 不做服务端持久化:换浏览器或清缓存记录丢失,属可接受范围;服务端持久化列入 v2.0 展望

6. 界面设计 ​

6.1 总体布局 ​

左侧固定导航(宽 200px)+ 右侧主区。导航项:问答(默认)、知识库、配置。页头右侧展示知识库状态徽标(就绪块数)。

┌────────┬──────────────────────────────────────────┐
│ ◆ RAG  │  问答                            [知识库: 128 块] │
│        ├──────────────────────────────────────────┤
│ 问答   │                                          │
│ 知识库 │              主内容区                      │
│ 配置   │          (随导航切换页面)                  │
│        │                                          │
└────────┴──────────────────────────────────────────┘

6.2 问答页 ​

┌─────────────────────────────┬───────────────┐
│  对话区(滚动)        [🕘历史]│ 引用来源面板    │
│  ┌───────────────────────┐  │  [1] 安全规程.txt│
│  │ 用户:井下作业注意什么? │  │      “…必须佩戴…”│
│  └───────────────────────┘  │  [2] 同上 0.82  │
│  ┌───────────────────────┐  │               │
│  │ 助手:…关键结论[1]…     │  │               │
│  │ (流式打字渲染)         │  │               │
│  └───────────────────────┘  │               │
│  ▸ 检索过程(四阶段,折叠)   │               │
├─────────────────────────────┴───────────────┤
│  [ 输入问题…                          ] [发送] │
└─────────────────────────────────────────────┘

交互态:发送后按钮禁用并显示阶段进度;错误时回答气泡内展示原因 + 重试按钮。

6.3 知识库页 ​

┌──────────────────────────────────────────────┐
│ [上传文档] [全量重建]        搜索框: [文件名___] │
├──────┬──────┬──────┬──────┬────────┬────────┤
│ 文件名│ 类型 │ 块数 │ 状态 │ 上传时间 │ 操作    │
│ 安全规程.pdf│ pdf │ 42  │ ●就绪│ 10-22  │ 块/删除 │
│ 井下.docx  │ docx│ 18  │ ◐处理中│10-22 │  —     │
├──────┴──────┴──────┴──────┴────────┴────────┤
│ [双路检索调试] 输入文本____ [topK:6] [查询]     │
│  BM25: 1.安全规程.txt(8.2)  向量: 1.同(0.91)   │
│  RRF融合: 1.安全规程.txt  2.井下作业.docx …    │
└──────────────────────────────────────────────┘

上传组件支持拖拽;「处理中」行状态列带转圈图标,处理完成自动刷新。

6.4 配置页 ​

按 F1.1 四个分组做卡片式表单,每张卡片右上角「恢复默认」。页脚横贯操作条:[保存] [测试 LLM 连通] [重载引擎]。保存后顶部出现提示条:「检索参数已生效;模型路径变更需重载引擎」。


7. 接口设计 ​

约定:统一前缀 /api;响应体 { "code": 0, "msg": "ok", "data": ... },非 0 为业务错误码;SSE 接口例外(见 7.4)。错误码表:1001 参数校验失败、2001 LLM 调用失败、2002 模型未加载/重载中、3001 文档解析失败、3002 文档不存在。

7.1 配置接口 ​

方法路径说明
GET/api/config读取全部配置(secret 字段脱敏)
PUT/api/config保存配置(未传的 secret 字段表示不修改)
POST/api/config/test-llm用请求体中的临时配置测试 LLM 连通性
POST/api/config/reload重载模型引擎(阻塞至完成,返回耗时)

GET /api/config 响应 data 示例:

json
{
  "llm": { "llm_model": "deepseek-chat", "llm_api_url": "https://api.xxx.com/v1",
           "llm_api_key": "****abcd", "llm_temperature": 0.3 },
  "retrieval": { "recall_size": 50, "final_top_k": 6, "rrf_k": 60,
           "bm25_k1": 1.5, "bm25_b": 0.75, "min_similarity": 0.3 },
  "chunking": { "chunk_size": 300, "chunk_overlap": 50 },
  "models": { "embedding_model_path": "models/bge-small-zh-v1.5",
              "reranker_model_path": "models/bge-reranker-base" }
}

7.2 知识库接口 ​

方法路径说明
POST/api/kb/documentsmultipart 上传,file 可重复;立即返回文档记录(状态=处理中)
GET/api/kb/documents?page=&size=&keyword=分页文档列表(含块数、状态、失败原因)
GET/api/kb/documents/{doc_id}/chunks该文档块列表(序号、原文)
DELETE/api/kb/documents/删除文档及全部块、原始文件
POST/api/kb/rebuild全量重建(按当前切块参数)
POST/api/kb/search双路检索调试:{"query": "文本", "top_k": 6},返回 BM25 / 向量 / RRF 三组排名

POST /api/kb/search 响应 data 示例:

json
{
  "bm25": [
    { "chunk_id": 12, "doc_name": "安全规程.pdf", "text": "井下作业必须佩戴自救器……", "score": 8.24 }
  ],
  "vector": [
    { "chunk_id": 12, "doc_name": "安全规程.pdf", "text": "井下作业必须佩戴自救器……", "similarity": 0.91 }
  ],
  "rrf": [
    { "chunk_id": 12, "doc_name": "安全规程.pdf", "rrf_score": 0.0317, "bm25_rank": 1, "vector_rank": 2 }
  ]
}

7.3 问答接口(非流式,供集成/调试) ​

POST /api/qa/ask/sync,请求 {"question": "井下作业注意什么?"},响应:

json
{
  "answer": "井下作业应注意……[1]……[2]。",
  "sources": [
    { "no": 1, "doc_name": "安全规程.pdf", "text": "井下作业必须佩戴自救器……", "similarity": 0.91 },
    { "no": 2, "doc_name": "安全规程.pdf", "text": "瓦斯浓度超过1%时……", "similarity": 0.86 }
  ],
  "elapsed_ms": 3120
}

7.4 问答接口(SSE 流式,主用) ​

POST /api/qa/ask,Accept: text/event-stream。事件序列:

eventdata说明
stage{"phase": "recall" / "rerank" / "generate"}阶段进度提示
trace{"bm25": [...], "vector": [...], "rrf": [...], "rerank": [...]}精排完成后推送检索过程明细(结构同 7.2,规模为各路 Top-10 / 精排 Top-6,供过程可视化面板)
delta{"text": "…"}回答增量片段
sources{"sources": [...]}生成完毕推送引用列表(结构同 7.3)
done{"elapsed_ms": 3120}结束
error{"code": 2001, "msg": "…"}任一环节失败

无命中(F3-4)时:无 delta 事件,直接一个 done,answer 在 done 前以单条 delta 给出固定提示文案。

7.5 系统接口 ​

GET /api/health → {"status": "ok", "chunks": 128, "docs": 6, "engine": "ready"}(重载中 engine=loading)。


8. 数据设计 ​

8.1 SQLite 表 ​

sys_config:key TEXT PRIMARY KEY, value TEXT, updated_at —— 全部配置平铺 KV 存储,服务层按分组装配。

kb_document:

字段类型说明
doc_idINTEGER PK自增
file_nameTEXT原文件名
file_typeTEXTtxt/pdf/docx
file_sizeINTEGER字节
content_md5TEXT UNIQUE去重依据
chunk_countINTEGER就绪后回填
statusTEXTprocessing / ready / failed
fail_reasonTEXT失败原因
created_atTEXT上传时间

kb_chunk:

字段类型说明
chunk_idINTEGER PK全局自增,同时作为 Chroma 的 id
doc_idINTEGER外键 → kb_document
chunk_indexINTEGER文档内序号
textTEXT块原文

8.2 Chroma 集合 rag_docs ​

  • id:str(chunk_id)
  • embedding:512 维(bge-small-zh-v1.5,normalize 后)
  • document:块原文
  • metadata:{"doc_id": int, "doc_name": str} —— 支持按文档删除、按来源展示
  • 距离度量:cosine(hnsw:space=cosine)

8.3 内存索引 ​

BM25 索引基于全部「就绪」块在内存构建(jieba 分词 + TF/DF/长度统计),文档增删后全量重建(块规模 ≤ 5 万时重建耗时秒级,可接受)。


9. 非功能需求 ​

类别要求
性能检索阶段(召回+融合+精排,CPU)≤ 2s / 次;首 token ≤ 5s;文档入库 ≤ 5s / 百块 [TBD:验收时实测修订]
容量支持文档 1000 篇 / 块 5 万个不降级
安全API Key 脱敏存储与展示;服务默认绑定 127.0.0.1,开放局域网由部署者显式配置;上传文件类型白名单校验
可维护性检索管线阶段化日志(每个问题记录两路召回与精排耗时);配置变更留审计字段(updated_at)[TBD:是否记录变更人]
可用性LLM 失败可重试不丢检索结果;入库失败单文档隔离,不影响批次

10. 迭代规划 ​

版本范围
v1.0(本文档)全部 P0:配置管理、知识库管理(含双路检索调试)、流式问答 + 引用、检索过程可视化、会话历史
v2.0 展望多用户与权限、多知识库空间、OCR、对话多轮上下文、答案反馈闭环、会话服务端持久化

11. v1.0 验收标准(P0 摘要) ​

  1. 界面修改 LLM 地址/模型名/Key 后,无需重启即可完成问答(必要时仅重载引擎)
  2. 上传 txt/pdf/docx 各一,全部状态「就绪」,问答可引用其内容;重复上传被去重拦截
  3. 删除某文档后,其内容不再出现在任何回答与引用中
  4. 修改 chunk_size 后执行全量重建,块数按新参数变化
  5. 问一个知识库外的问题,得到「无法回答」类固定答复,且未调用 LLM
  6. 断开 LLM(错误 Key)提问,得到明确错误提示,恢复后重试成功
  7. 双路检索调试返回 BM25 / 向量 / RRF 三组结果,同一块在两路中的名次与融合排名可相互核对
  8. 问答完成后展开「检索过程」面板,四个阶段数据完整,阶段 ④ 序号与回答引用编号一致
  9. 刷新或重开浏览器后,最近会话历史仍在且可完整回放(含引用与检索过程)

Released under the MIT License.