本地 RAG 智能助手 - 技术方案文档

场景 :技术方案 | 受众 :后端工程师、架构师、开发者 | 字数 :约5000字

背景与目标

业务需求

本项目是一个基于 Streamlit 的本地知识库 RAG(Retrieval-Augmented Generation)智能助手,核心业务需求如下:

需求编号需求描述来源
REQ-001用户可以上传多种格式文档(PDF、Excel、TXT、HTML)app.py 文件上传功能
REQ-002系统自动解析文档内容并构建向量索引document_loader.py 、 vector_store.py
REQ-003支持语义检索,根据用户问题匹配相关文档片段embedding.py 、vector_store.py
REQ-004结合大模型生成基于知识库的精准回答llm.py
REQ-005支持离线演示模式,无需配置大模型也能运行llm.py 的 local_echo 模式
REQ-006配置化管理,支持灵活调整参数config/app.yaml

技术指标

指标类型指标值说明
支持文档格式PDF、XLSX、XLS、TXT、HTML、HTM通过配置扩展
文本切分大小900字符(可配置)平衡语义完整性与检索效率
切分重叠量120字符(可配置)减少语义断裂
向量维度384维(可配置)BGE-small 模型默认
检索召回数Top-5(可配置)控制上下文窗口大小
最小相似度阈值0.0(可配置)过滤低相关结果
大模型温度0.2(可配置)控制回答随机性
最大生成长度1200 token(可配置)控制响应长度

核心价值

  • 数据隐私保护 :文档在本地处理,不传输到外部服务器
  • 离线可用 :支持本地哈希向量和 echo 模式,无需网络也能运行演示
  • 配置灵活 :所有关键参数可通过配置文件调整
  • 易于扩展 :模块化设计,便于替换组件

现状与约束

技术栈现状

分类技术版本用途
框架Streamlit>=1.34.0Web 交互界面
语言Python>=3.10主要开发语言
文档解析PyPDF>=4.0.0PDF 解析
表格处理pandas>=2.0.0Excel 解析
HTML解析beautifulsoup4>=4.12.0HTML 解 析
向量化sentence-transformers(可选)>=2.7.0语义向量生成
大模型OpenAI API>=1.30.0回答生成
向量存储NumPy>=1.24.0向量索引管理
配置管理PyYAML>=6.0.0YAML 配置解析

约束条件

约束类型描述影响
资源约束首次运行可能无网络下载模型需提供降级方案(哈希向量)
存储约束向量文件可能较大使用NumPy二级制格式压缩存储
合规约束用户文档可能包含敏感信息全程本地处理,不上传外部
环境约束可能运行在无GPU环境选用轻量级模型(bge-small)
依赖约束可选依赖安装可能失败使用延迟加载和异常捕获

已有系统分析

项目采用模块化架构,各组件职责清晰:

  • 数据层 : data/knowledge_base/ 存储原始文档, data/vector_store/ 存储向量索引
  • 核心层 : src/rag_assistant/ 包含所有业务逻辑
  • 展示层 : app.py 提供 Streamlit 交互界面
  • 配置层 : config/app.yaml 集中管理所有参数
.
├── app.py                    # Streamlit 交互界面
├── config/
│   └── app.yaml              # 应用配置文件
├── data/
│   ├── knowledge_base/       # 原始文档存储
│   └── vector_store/         # 向量索引存储
├── src/
│   └── rag_assistant/        # 核心业务逻辑
│       ├── config.py         # 配置加载
│       ├── document_loader.py # 文档解析
│       ├── embedding.py      # 向量化模型
│       ├── llm.py            # 大模型客户端
│       ├── pipeline.py       # RAG 管线编排
│       ├── schema.py         # 数据模型定义
│       ├── text_splitter.py  # 文本切分
│       └── vector_store.py   # 向量存储与检索
├── requirements.txt          # 核心依赖
├── requirements-optional.txt # 可选依赖
└── README.md                 # 项目说明

方案概述

核心架构

┌─────────────────────────────────────────────────────────────────┐
│                        Streamlit UI                             │
│  (文件上传、问题输入、结果展示)                                  │
└────────────────────────────┬────────────────────────────────────┘
                             │
┌────────────────────────────▼────────────────────────────────────┐
│                       RAGPipeline                               │
│  (编排索引构建、检索、回答生成流程)                                │
└────────────────────────────┬────────────────────────────────────┘
        │           │           │           │           │
        ▼           ▼           ▼           ▼           ▼
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│Document   │ │Text       │ │Embedding  │ │Vector     │ │LLM        │
│Loader     │ │Splitter   │ │Model      │ │Store      │ │Client     │
│(文档解析)  │ │(文本切分) │ │(向量化)    │ │(向量检索)  │ │(回答生成)  │
└───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘
        │           │           │           │           │
        └───────────┴───────────┴───────────┴───────────┘
                             │
                     ┌───────▼───────┐
                     │   data/       │
                     │ (知识库+向量)  │
                     └───────────────┘

核心思路

索引构建流程

  1. 用户上传文档 ——> 保存到 knowledge_base 文件夹
  2. DocumentLoader 解析文档 ——> 生成 Document 对象列表
  3. TextSplitter 切分文本 ——> 生成带重叠的文本片段
  4. EmbeddingModel 向量化 ——> 生成向量矩阵
  5. VectorStore 构建索引 ——> 持久化到 vector_store 文件夹

关键设计原则

原则实现方式代码位置
模块化每个组件独立封装,职责单一.py 模块
可替换性通过配置切换 Providerembedding.pyllm.py
延迟加载模型按需加载,避免启动耗时embedding.py:_ensure_model()
容错降级支持本地哈希向量和 echo 模式embedding.pyllm.py
配置集中所有参数通过 YAML 管理config.pyschema.py

详细设计

数据模型设计

配置类(Schema)

# 定义位置:src/rag_assistant/schema.py

@dataclass(slots=True)
class Settings:
    app: AppSettings          # 应用配置
    documents: DocumentSettings  # 文档配置
    splitter: SplitterSettings   # 切分配置
    embedding: EmbeddingSettings # 向量化配置
    retrieval: RetrievalSettings # 检索配置
    llm: LLMSettings          # 大模型配置

字段说明 :

配置类字段类型说明默认值
AppSettingstitlestr应用标题“本地 RAG 智能助手”
page_iconstr页面图标“🧠”
knowledge_base_dirPath知识库目录“data/knowledge_base”
vector_store_dirPath向量存储目录“data/vector_store”
DocumentSettingsallowed_extensionslist[str]允许的文档扩展名[".pdf", “.xlsx”, “.xls”, “.txt”, “.html”, “.htm”]
SplitterSettingschunk_sizeint切分块大小900
chunk_overlapint重叠字符数120
EmbeddingSettingsproviderstr向量化提供者“sentence_transformers”
model_namestr模型名称“BAAI/bge-small-zh-v1.5”
fallback_dimensionint降级向量维度384
RetrievalSettingstop_kint检索返回数量5
min_scorefloat最小相似度阈值0.0
LLMSettingsproviderstr大模型提供者“openai_compatible”
modelstr模型名称“gpt-4o-mini”
base_urlstrAPI 基础地址https://api.openai.com/v1"
api_key_envstrAPI Key 环境变量名“OPENAI_API_KEY”
temperaturefloat温度参数0.2
max_tokensint最大生成长度1200

业务数据类

# 定义位置:src/rag_assistant/schema.
py

@dataclass(slots=True)
class Document:
    text: str              # 文档内容
    metadata: dict[str, Any] = field (default_factory=dict)  # 元信息(来源、页码等)

@dataclass(slots=True)
class SearchResult:
    text: str              # 检索到的文本片段
    metadata: dict[str, Any]  # 来源元信息
    score: float           # 相似度分数

@dataclass(slots=True)
class IndexBuildResult:
    document_count: int    # 处理的文档数
    chunk_count: int       # 生成的片段数

@dataclass(slots=True)
class Answer:
    answer: str            # 生成的回答
    sources: list[SearchResult]  # 引用来源列表

模块设计

DocumentLoader(文档加载器)

职责 :将不同格式的文档解析为统一的 Document 对象

支持格式 :

格式扩展名解析方式特殊处理
PDF.pdfPyPDF 的 PdfReader按页拆分,记录页码
Excel.xlsx, .xlspandas按工作表拆分,记录表名
TXT.txt直接读取UTF-8 编码
HTML.html, .htmBeautifulSoup移除 script/style 标签

核心方法 :

def load_directory(self, directory: Path) -> list[Document]
# 输入:目录路径
# 输出:Document 列表
# 遍历目录下所有允许的文件,逐一解析

def load_file(self, path: Path) -> list[Document]
# 输入:文件路径
# 输出:Document 列表(可能多个,如多页PDF)
# 根据扩展名选择对应的解析方法

元信息生成 ( _meta 函数):

字段说明示例
source文件完整路径“data/knowledge_base/doc.pdf”
filename文件名“doc.pdf”
extension文件扩展名“.pdf”
page页码(PDF专用)3
sheet工作表名(Excel专用)“Sheet1”

代码位置 : src/rag_assistant/document_loader.py

TextSplitter(文本切分器)

职责 :按字符长度切分文本,保留重叠区域减少语义断裂

核心参数 :

参数类型说明
chunk_sizeint每个切分块的字符数
chunk_overlapint相邻块的重叠字符数

约束条件 : chunk_overlap < chunk_size (在 init 中检查)

切分算法 :

原始文本:[===============================] (长度 N)
切分结果:
块0: [===========]           位置: 0 ~ chunk_size
块1:      [===========]       位置: chunk_size-overlap ~ 2*chunk_size-overlap
块2:            [===========]   位置: 2*chunk_size-2*overlap ~ 3*chunk_size-2*overlap

文本归一化 ( _normalize_text 函数):

  • 压缩多余空白
  • 移除空行
  • 每行前后去空格

代码位置 : src/rag_assistant/text_splitter.py

EmbeddingModel(向量化模型)

职责 :将文本转换为向量表示,支持真实模型和降级方案

设计模式 :延迟加载 + 降级机制

核心参数 :

参数类型说明
providerstr向量化提供者
model_namestr模型名称或路径
fallback_dimensionint降级向量维度

属性 :

@property
    def dimension(self) -> int:
        self._ensure_model()
        if self._model is not None:
            return int(self._model.get_sentence_embedding_dimension())
        return self.fallback_dimension

核心方法 :

    def encode(self, texts: list[str]) -> np.ndarray:
        self._ensure_model()
        if not texts:
            return np.empty((0, self.dimension), dtype="float32")

        if self._model is not None:
            vectors = self._model.encode(texts, normalize_embeddings=True)
            return np.asarray(vectors, dtype="float32")

        vectors = np.vstack([self._hash_embedding(text) for text in texts]).astype("float32")
        norms = np.linalg.norm(vectors, axis=1, keepdims=True)
        return vectors / np.clip(norms, 1e-12, None)

降级方案 ( _hash_embedding ):

当无法加载真实模型时,使用 SHA-256 哈希生成伪向量:

    def _hash_embedding(self, text: str) -> np.ndarray:
        """无需下载模型的兜底方案,便于项目首次运行和离线演示。"""
        vector = np.zeros(self.fallback_dimension, dtype="float32")
        for token in _tokenize(text):
            digest = hashlib.sha256(token.encode("utf-8")).digest()
            index = int.from_bytes(digest[:4], "little") % self.fallback_dimension
            sign = 1.0 if digest[4] % 2 == 0 else -1.0
            vector[index] += sign
        return vector

分词策略 ( _tokenize ):

  • 中文:按字分词
  • 英文:按词分词(字母数字序列)
  • 统一转为小写

代码位置 : src/rag_assistant/embedding.py

VectorStore(向量存储)

职责 :负责向量索引的内存检索和本地持久化

存储结构 :

文件格式内容
vectors.npyNumPy 二进制归一化后的向量矩阵
documents.jsonJSON文档片段及元信息列表

核心方法 :

方法输入输出说明
build(documents, vectors)文档列表和向量构建内存索引
search(query_vector, top_k, min_score)查询向量、返回数量、阈值SearchResult 列表相似度检索
save()持久化到本地
load()bool加载本地索引

检索算法 :使用点积计算余弦相似度(向量已归一化)

代码位置 : src/rag_assistant/vector_store.py

LLMClient(大模型客户端)

职责 :封装大模型调用,支持多种提供者和降级模式

支持的提供者 :

Provider说明使用场景
openai_compatibleOpenAI API 兼容接口生产环境
local_echo本地模板回答演示、测试

核心方法 :

def generate(self, question: str, contexts: list[SearchResult]) -> str
# 输入:问题、检索到的上下文片段
# 输出:生成的回答文本

Prompt 构建 :

System Prompt ( _system_prompt ):

"你是一个严谨的中文 RAG 智能助手。必须优
先依据给定的本地知识库片段回答。如果资料不
足,请明确说明不足,不要编造。回答要结构清
晰,并在适当位置提及来源。"

User Prompt ( _build_user_prompt ):

问题:{question}

本地知识库片段:
[片段 1 | 来源:xxx | 相似度:0.85]
文本内容...

[片段 2 | 来源:yyy | 相似度:0.72]
文本内容...

降级模式逻辑 :

if provider == "local_echo" 或 未配置 API Key:
    return _local_answer(question, 
    contexts)
else:
    调用 OpenAI API

代码位置 : src/rag_assistant/llm.py

RAGPipeline(RAG 管线)

职责 :编排各组件,提供统一的索引构建和问答接口

组件组合 :

self.loader = DocumentLoader(settings.documents.allowed_extensions)
self.splitter = TextSplitter(settings.splitter.chunk_size, settings.splitter.chunk_overlap)
self.embedding_model = EmbeddingModel(...)
self.vector_store = VectorStore(settings.app.vector_store_dir)
self.llm = LLMClient(settings.llm)

核心方法 :

def rebuild_index(self) -> IndexBuildResult
# 流程:加载文档 → 切分 → 向量化 → 构建索引 → 保存
# 输出:处理统计信息

def load_index(self) -> bool
# 从本地加载已构建的索引

def answer(self, question: str) -> Answer
# 流程:问题向量化 → 检索 → 生成回答
# 输出:Answer 对象(含引用来源)

代码位置 : src/rag_assistant/pipeline.py

Streamlit 应用层

职责 :提供 Web 交互界面,处理用户输入输出

页面结构 :

区域功能实现
Sidebar知识库管理文件上传、重建索引、加载索引
Main对话界面消息历史、问题输入、回答展示

会话状态管理 :

if "messages" not in st.session_state:
    st.session_state.messages = []  
    # 存储对话历史

缓存策略 :

@st.cache_resource
def get_pipeline(config_path: str) -> RAGPipeline:
# 缓存管线实例,避免重复初始化

代码位置 : app.py

技术选型对比

Embedding 模型选型

方案优点缺点选择
entenceTransformers (BGE-small)质量高,中文支持好,轻量级需要下载模型推荐
OpenAI Embedding API质量极高需要联网,有成本备选
本地哈希向量零依赖,即时可用检索质量差降级方案

大模型选型

方案优点缺点选择
OpenAI API (gpt-4o-mini)质量高,响应快,成本低需要联网和 API Key推荐
开源模型 (如 Qwen)本地部署,隐私保护需要 GPU备选扩展
local_echo 模式零依赖,演示友好无真正生成降级方案

向量存储选型

方案优点缺点选择
NumPy + JSON简单轻量,无需额外依赖内存检索,大数据量性能差当前实现
FAISS高效索引,支持 GPU需要额外安装扩展方案
Chroma/Pinecone专业向量数据库部署复杂扩展方案

文档解析选型

格式方案版本理由
PDFPyPDF>=4.0.0轻量
Excelpandas + openpyxl>=2.0.0支持 xlsx/xls,成熟稳定
HTMLBeautifulSoup>=4.12.0简单易用,解析灵活

风险与应对

风险识别与缓解

风险编号风险描述严重程度概率缓解措施
RISK-001Embedding 模型下载失败哈希向量降级方案
RISK-002大模型 API Key 未配置local_echo 模式
RISK-003文档解析失败异常捕获,跳过损坏文件
RISK-004向量文件过大导致内存不足考虑引入 FAISS
RISK-005长文档切分导致语义断裂使用重叠切分
RISK-006配置文件缺失或格式错误参数校验和默认值

降级机制设计

Embedding 降级流程 :

用户启动应用
    │
    ▼
尝试加载 SentenceTransformers 模型
    │
    ├─ 成功 → 使用真实向量
    │
    └─ 失败 → 使用哈希向量(提示用户质量受限)

LLM 降级流程 :

用户提问
    │
    ▼
检查 provider 和 API Key
    │
    ├─ provider="local_echo" → 返回检索摘要
    │
    ├─ API Key 有效 → 调用大模型生成
    │
    └─ API Key 无效 → 返回检索摘要(提示未配置)

测试要点

单元测试用例

测试模块测试场景预期结果
DocumentLoader加载正常 PDF返回 Document 列表
加载加密 PDF异常处理
加载空文件返回空列表
TextSplitter切分短文本返回1个块
切分长文本返回多个块,有重叠
chunk_overlap >= chunk_size抛出 ValueError
EmbeddingModel空文本列表返回空数组
模型加载失败自动降级到哈希向量
VectorStore空索引检索返回空列表
相似度过滤低于阈值的结果被过滤
LLMClient无上下文返回提示信息
RAGPipeline无索引时回答提示先构建索引

集成测试用例

测试场景步骤预期结果
完整索引流程上传文档 → 重建索引成功构建,显示文档数和块数
完整问答流程提问 → 检索 → 回答返回带引用来源的回答
降级模式验证不安装sentence-transformers应用正常启动,使用哈希向量
配置热更新修改配置文件 → 重启应用新配置生效

性能测试

测试项指标目标值
文档解析速度100页PDF解析时间< 30秒
向量生成速度1000个片段向量化< 60秒
检索响应时间单查询检索< 100ms
问答响应时间完整问答流程< 10秒

部署与运维建议

环境要求

分类要求说明
Python>= 3.10推荐 3.11
内存>= 4GB模型加载和向量检索需要
存储>= 1GB知识库和向量存储
网络可选下载模型或调用 API 需要

安装步骤

# 1. 创建虚拟环境
python -m venv .venv

# 2. 激活虚拟环境(Windows)
.venv\Scripts\activate

# 3. 安装核心依赖
pip install -r requirements.txt

# 4. (可选)安装高质量向量模型
pip install -r requirements-optional.txt

# 5. 配置环境变量(如需使用大模型)
set OPENAI_API_KEY=your-api-key

# 6. 启动应用
streamlit run app.py

配置说明

配置文件位置 : config/app.yaml

关键配置项说明 :

配置路径说明建议值
app.knowledge_base_dir知识库目录相对或绝对路径
splitter.chunk_size文本切分大小500-1500
embedding.model_nameEmbedding 模型BAAI/bge-small-zh-v1.5
retrieval.top_k检索数量3-10
llm.temperature温度参数0.1-0.5

运维建议

日志管理 :

  • Streamlit 默认输出到控制台

  • 生产环境可配置日志文件 数据备份 :

  • 定期备份 data/knowledge_base/ 和 data/vector_store/

  • 建议使用版本控制管理配置文件 性能优化 :

  • 对于大规模知识库,考虑引入 FAISS 索引

  • 定期清理过期文档并重建索引 安全建议 :

  • API Key 通过环境变量传递,不要硬编码

  • 限制知识库目录的访问权限

  • 定期更新依赖版本