一个面向本地资料的个性化知识库问答系统。项目以 Streamlit 提供 Web 界面,支持上传和管理文档,将文档解析、切片、索引到本地 FAISS/JSON 存储中,并通过 RAG、混合检索、重排、引用生成和对话记忆来回答问题。
项目当前使用智谱 OpenAI-compatible API:
- 对话模型:
glm-4.7 - 向量模型:
embedding-3 - 可选重排模型:
BAAI/bge-reranker-v2-m3
- 本地知识库问答:支持
txt、md、pdf、docx、xlsx、csv。 - 多种检索模式:
vector、keyword、hybrid。 - RAG 回答:基于召回片段和长期记忆生成答案,并输出
[document_id:chunk_index]引用。 - Agentic RAG:可让 LangGraph ReAct Agent 调用知识库检索、记忆召回、深读 chunk、保存偏好和更新工作记忆等工具。
- 文档管理:支持上传导入、目录导入、重建索引、删除文档。
- 个性化记忆:包括短期会话窗口、核心工作记忆、用户偏好、长期语义记忆。
- 降级回答:模型限流或不可用时,可基于已检索片段生成抽取式答案。
- Docker 部署:提供
Dockerfile和docker-compose.yml。
┌──────────────────────────────┐
│ Streamlit UI │
│ streamlit_app.py │
│ 上传 / 文档管理 / 问答配置 │
└───────────────┬──────────────┘
│
┌───────────────▼──────────────┐
│ ChatService │
│ RAG / Agentic RAG / 降级回答 │
└───────┬───────────────┬──────┘
│ │
│ │
┌───────▼────────┐ ┌────▼─────────────────┐
│ RetrievalPipeline │ │ MemoryManager │
│ query rewrite │ │ 短期 / 核心 / 偏好 / │
│ vector/keyword │ │ 长期语义记忆 │
│ RRF fusion │ └────┬────────────────┘
│ rerank │ │
└───────┬───────────┘ │
│ │
┌───────▼──────────────────▼───┐
│ FaissVectorStore │
│ FAISS index + chunks.json │
│ 文档 chunk / 记忆 chunk │
└───────┬──────────────────────┘
│
┌───────▼──────────────────────┐
│ DocumentService │
│ DocumentParser + Chunker │
│ 原始文件 / JSON 元数据 / 索引 │
└──────────────────────────────┘
核心模块:
streamlit_app.py:Web UI 入口,负责页面渲染、文件上传、检索模式选择、问答表单和引用展示。main.py:命令行入口,支持ingest和ask。config_data.py:集中管理模型名、chunk 参数、检索参数、存储路径和环境变量。services/document_service.py:文档导入、去重、保存原始文件、重建索引、删除文档。services/document_parser.py:解析文本、Markdown、PDF、DOCX、Excel、CSV。services/vector_chunks_store.py:维护 chunk JSON、FAISS 向量索引和关键词检索。services/retrieval/*:查询改写、父子切片、RRF 融合、重排和完整检索管线。services/chat_service.py:RAG、Agentic RAG、引用拼接、证据门控、模型限流降级。services/memory_service.py:用户偏好、核心记忆、短期记忆压缩、长期语义记忆。services/model_factory.py:创建智谱兼容的 chat model、embedding model 和可选 CrossEncoder。
默认运行时目录为 storage/default,可通过 CHATBOX_STORAGE_DIR 或界面侧边栏修改。
storage/default/
├── raw_documents/ # 上传或导入的原始文件副本
├── faiss/
│ ├── index.faiss # 文档向量索引
│ └── index.pkl # LangChain FAISS 元数据
├── json_store/
│ ├── documents.json # 文档元数据、状态、hash、chunk 数
│ ├── chunks.json # 文档 chunk 记录
│ └── qa_logs.json # 问答日志
└── memory/
├── preferences.json # 用户稳定偏好
├── core_memory.json # 当前目标、计划、约束等核心记忆
└── semantic/ # 长期语义记忆的 FAISS/JSON 存储
data/ 是默认的本地导入目录,点击界面里的 Import Folder 或使用 CLI ingest 时会扫描该目录。
注意:data/、storage/、.env、.streamlit/secrets.toml 都属于运行时或隐私数据,不建议提交到公开仓库。
-
文件来源:
- Web 上传:
st.file_uploader - 本地目录:默认扫描
data/ - CLI:
python main.py ingest
- Web 上传:
-
去重与记录:
DocumentService.ingest_bytes()对文件内容计算 SHA-256。- 如果同 hash 的成功记录已存在,返回
duplicate。 - 原始文件保存到
raw_documents/{hash前12位}_{file_name}。 - 文档元数据写入
json_store/documents.json。
-
文档解析:
txt、md:依次尝试utf-8、utf-8-sig、gbk。pdf:使用pypdf.PdfReader提取每页文本。docx:使用python-docx提取段落。xlsx、csv:使用pandas转成表格文本。- 解析后会做空行和 BOM 清理,如果无法提取可用文本则入库失败。
-
父子切片:
- 父 chunk:默认
1200字符,重叠120。 - 子 chunk:默认
320字符,重叠80。 - 检索命中子 chunk,但回答上下文会带上对应父 chunk,兼顾命中精度和上下文完整性。
- 每个 chunk 保存
document_id、chunk_index、parent_id、section_title、file_name等元数据。
- 父 chunk:默认
-
索引写入:
vector或hybrid入库时,子 chunk 会通过embedding-3生成向量并写入 FAISS。- 所有模式都会写入
chunks.json,因此关键词检索可在无 embedding 的情况下工作。 keyword入库不会写 FAISS,适合无 API key 或轻量测试。
RAG 主流程在 RetrievalPipeline.search() 和 ChatService.answer_with_rag() 中完成。
用户问题
│
▼
查询改写 QueryRewriteService
├── original 原始问题
├── history_standalone 结合历史补全追问
├── normalized 去噪标准化
├── expanded_keywords 关键词扩展
└── noise_reduced 聚焦关键词
│
▼
多路召回
├── vector: FAISS similarity_search_with_score
└── keyword: 本地 chunk 文本和文件名关键词命中
│
▼
RRF 融合 ReciprocalRankFusion
│
▼
重排 HeuristicReranker / CrossEncoder
│
▼
证据门控
│
▼
构造上下文 + 长期记忆
│
▼
glm-4.7 生成答案 + 引用
QueryRewriteService 不额外调用模型,而是基于规则和历史问答生成最多 4 个查询变体:
- 原始问题保留最高权重。
- 对“继续说”“这个是什么”等追问,会从最近
MAX_HISTORY_ROUNDS轮历史中抽取关键词,补成独立查询。 - 去掉常见噪声词,如“请问”“帮我”“介绍”“解释”等。
- 生成扩展关键词查询和聚焦查询,提高召回覆盖率。
vector:只走 FAISS 向量召回,需要ZHIPU_API_KEY和已构建 FAISS 索引。keyword:只走本地关键词命中,不需要模型 API,适合作为离线降级。hybrid:同时走向量和关键词召回,是 Web UI 的默认模式。
每个查询变体会召回 RETRIEVAL_CANDIDATES=12 个候选,之后进入融合和重排。
ReciprocalRankFusion 使用 1 / (k + rank) 计算每个结果在各路召回中的贡献,默认 k=60。查询变体本身也有权重,关键词召回会乘以 0.92,让向量召回略占优势但不完全压过关键词信号。
融合后的候选会记录:
fusion_scoredense_similaritykeyword_scoreretrieval_sourcesquery_variants
默认使用 HeuristicReranker,可选叠加 CrossEncoder。
启发式重排会综合:
- RRF 融合分数:
fusion_score - 向量相似度:由 FAISS 距离转换为
1 / (1 + distance) - 关键词得分:相对于本批候选最大关键词分数归一化
- 问题信号词覆盖率:
signal_overlap - 查询变体覆盖:
rewrite_coverage - 原问题精确短语命中
- 文件名命中加权
如果启用了 CrossEncoder,会对前 CROSS_ENCODER_TOP_N=8 个候选构造 (question, chunk_text) pair,调用 BAAI/bge-reranker-v2-m3 打分,再通过 sigmoid 归一化。最终分数为:
rerank_score =
(1 - CROSS_ENCODER_WEIGHT) * heuristic_score
+ CROSS_ENCODER_WEIGHT * cross_encoder_score
默认 CROSS_ENCODER_WEIGHT=0.58。重排后会按 parent_id 去重,避免同一父 chunk 的多个子 chunk 占满结果列表。
ChatService 会先检查检索结果是否足够可靠:
- 没有检索结果:不直接编造资料型答案。
- top chunk 的
rerank_score >= MIN_GROUNDED_RERANK_SCORE时认为有足够证据。 - 如果分数低,则再检查问题信号词是否出现在文件名、命中片段或父上下文中。
证据不足时:
- 模型可用:生成一般知识回答,但会加上“以下回答不基于你的本地资料”的提示。
- 模型不可用或限流:返回“当前资料不足”的固定提示。
证据充足时:
- 取前
MAX_READ_CHUNKS=5个 chunk。 - 每个 chunk 构造成带引用的上下文块。
- 合并长期语义记忆与知识库证据。
- 使用
glm-4.7生成答案。 - 答案末尾拼接
引用:[document_id:chunk_index]。
如果模型发生限流、超时或 API 错误,会降级为抽取式答案:直接整理命中片段和扩展上下文。
Web UI 和 CLI 都支持 answer-mode=agentic。该模式使用 LangGraph 的 create_react_agent,提供以下工具:
query_knowledge_base(query):检索本地知识库候选 chunk。recall_memory(query):召回核心记忆和长期语义记忆。read_file_chunks(chunks):读取指定 chunk 的完整内容。save_user_preference(key, value):保存稳定用户偏好。update_working_memory(field, value):更新目标、计划、下一步、约束、备注等核心记忆。
Agent 被提示必须先查知识库,再选择最相关的 2 到 3 个 chunk 深读,并且最终答案只能基于深读内容、核心记忆和召回记忆组织。为了避免循环,每个工具最多调用一次,默认递归限制为 8。
如果 Agent 调用失败、达到递归限制或遇到限流,会回退到普通 RAG。
项目的“personalized”主要体现在记忆层。
- 每个会话使用
session_id区分。 - 默认保留最近
SHORT_MEMORY_WINDOW=3轮。 - 超出窗口的内容会被压缩成摘要事件。
- 如果上下文过长,会继续压缩到目标 token 预算。
核心记忆保存当前任务状态,包括:
goalcurrent_plancompleted_stepsnext_stepconstraintsnotes
这些字段由 Agent 工具 update_working_memory 更新,并会在后续回答中作为系统上下文。
稳定偏好通过 PreferenceStore 保存:
- 默认保存在
storage/default/memory/preferences.json。 - 如果设置了
REDIS_URL且 Redis 可用,会优先写入 Redis。 - 偏好过多时,会根据当前问题选择最相关的偏好放入上下文。
每轮对话后,MemoryManager 会评估这一轮是否值得长期保存:
- 有 chat model 时,让模型输出重要性、摘要和原因。
- 无模型时,使用长度和结构化摘要作为降级判断。
- 重要性大于
LONG_MEMORY_IMPORTANCE_THRESHOLD=0.58时,将摘要写入独立的语义记忆向量库。
长期语义记忆使用和文档知识库相同的 FaissVectorStore,但存放在 storage/default/memory/semantic/。
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txtLinux/macOS:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt复制环境变量示例:
copy .env.example .envLinux/macOS:
cp .env.example .env然后编辑 .env:
ZHIPU_API_KEY=your_zhipu_api_key
CHATBOX_ENABLE_CROSS_ENCODER=false也可以在当前 shell 中直接设置:
set ZHIPU_API_KEY=your_zhipu_api_keyLinux/macOS:
export ZHIPU_API_KEY=your_zhipu_api_keystreamlit run streamlit_app.py浏览器打开:
http://localhost:8501
- 在侧边栏确认
Storage dir、Data dir、Retrieval mode和Answer mode。 - 上传文件,点击
Import Uploaded。 - 或将文件放到
data/,点击Import Folder。 - 在右侧输入问题并发送。
- 展开
References查看引用来源。
python main.py ingest --data-dir data --storage-dir storage/default --mode vector无 API key 时可使用关键词模式:
python main.py ingest --data-dir data --storage-dir storage/default --mode keyword普通 RAG:
python main.py ask ^
--storage-dir storage/default ^
--mode hybrid ^
--answer-mode rag ^
--session-id cli ^
--question "这批资料主要讲了什么?"Agentic RAG:
python main.py ask ^
--storage-dir storage/default ^
--mode hybrid ^
--answer-mode agentic ^
--session-id cli ^
--question "根据资料总结重点,并记住我偏好中文回答"Linux/macOS 可将 ^ 换成 \。
默认关闭 CrossEncoder,因为 BAAI/bge-reranker-v2-m3 需要额外模型下载、磁盘空间和内存。
安装依赖:
pip install -r requirements-reranker.txt设置环境变量:
set CHATBOX_ENABLE_CROSS_ENCODER=trueLinux/macOS:
export CHATBOX_ENABLE_CROSS_ENCODER=true然后重新启动应用。
如果部署在轻量服务器上,建议保持:
CHATBOX_ENABLE_CROSS_ENCODER=falsedocker build -t chatbox-personalized .
docker run -p 8501:8501 ^
-e ZHIPU_API_KEY=your_zhipu_api_key ^
-e CHATBOX_ENABLE_CROSS_ENCODER=false ^
-v chatbox_data:/app/data ^
-v chatbox_storage:/app/storage ^
chatbox-personalizedLinux/macOS:
docker build -t chatbox-personalized .
docker run -p 8501:8501 \
-e ZHIPU_API_KEY=your_zhipu_api_key \
-e CHATBOX_ENABLE_CROSS_ENCODER=false \
-v chatbox_data:/app/data \
-v chatbox_storage:/app/storage \
chatbox-personalized打开:
http://localhost:8501
copy .env.example .env
docker compose up -d --buildLinux/macOS:
cp .env.example .env
docker compose up -d --buildCompose 会把运行时数据挂载到:
./runtime/data
./runtime/storage
推荐使用支持 Docker 和持久化磁盘的主机,例如:
- 腾讯云 / 阿里云香港:大陆访问较方便且不需要 ICP。
- 大陆服务器:访问稳定,但域名需要 ICP 备案。
- Render、Railway、Fly.io 或普通 VPS:适合国际访问或个人测试。
Vercel 不适合当前版本,因为 Streamlit 是长驻服务,并且应用需要可写、持久化的上传文件和 FAISS 索引目录。
Ubuntu VPS 示例:
git clone <your-repo-url> chatbox_personalized
cd chatbox_personalized
cp .env.example .env
# 编辑 .env,填入 ZHIPU_API_KEY
docker compose up -d --build如需域名访问,可用 Nginx 反向代理到 127.0.0.1:8501,并开启 WebSocket 相关 header。更完整的部署说明见 DEPLOYMENT.md。
| 变量 | 默认值 | 说明 |
|---|---|---|
ZHIPU_API_KEY |
无 | 必填。智谱 API Key。向量检索、混合检索、模型回答需要它。 |
CHATBOX_DATA_DIR |
data |
本地目录导入时扫描的文件夹。 |
CHATBOX_STORAGE_DIR |
storage/default |
JSON、FAISS、原始文件、记忆的持久化目录。 |
CHATBOX_ENABLE_CROSS_ENCODER |
false |
是否启用本地 CrossEncoder 重排。 |
REDIS_URL |
无 | 可选。存在且可连接时,偏好记忆会优先写 Redis。 |
主要配置集中在 config_data.py:
| 配置 | 当前值 | 说明 |
|---|---|---|
PARENT_CHUNK_SIZE |
1200 |
父 chunk 长度。 |
PARENT_CHUNK_OVERLAP |
120 |
父 chunk 重叠。 |
CHILD_CHUNK_SIZE |
320 |
子 chunk 长度。 |
CHILD_CHUNK_OVERLAP |
80 |
子 chunk 重叠。 |
TOP_K |
5 |
最终返回给回答链的 chunk 数。 |
RETRIEVAL_CANDIDATES |
12 |
每路召回候选数。 |
RERANK_TOP_N |
8 |
进入重排的候选数。 |
QUERY_REWRITE_MAX_VARIANTS |
4 |
查询改写最多变体数。 |
MIN_GROUNDED_RERANK_SCORE |
0.2 |
判断证据是否足够的最低重排分。 |
MAX_HISTORY_ROUNDS |
4 |
查询改写参考的最近问答轮数。 |
SHORT_MEMORY_WINDOW |
3 |
短期记忆保留窗口。 |
LONG_MEMORY_IMPORTANCE_THRESHOLD |
0.58 |
写入长期语义记忆的阈值。 |
- PDF 解析依赖
pypdf.extract_text(),对扫描版 PDF、复杂排版、公式和表格支持有限。 - DOCX 只提取段落文本,暂未处理图片、页眉页脚、批注、复杂表格。
- CSV/XLSX 会被线性化成文本,表格结构推理能力有限。
- 关键词检索是简单字符串包含匹配,没有 BM25、分词器或倒排索引,因此中文关键词召回较粗糙。
- FAISS 索引使用本地文件持久化,不适合多实例并发写入。
chunks.json、documents.json、qa_logs.json是 JSON 文件存储,适合个人或小规模使用,不适合高并发生产场景。- CrossEncoder 默认关闭;开启后会增加冷启动时间、内存占用和模型下载成本。
- 短期记忆保存在进程内字典中,应用重启后短期会话窗口会丢失;偏好、核心记忆和长期语义记忆会持久化。
- Agentic RAG 依赖模型工具调用质量,虽然设置了递归限制和工具调用约束,但稳定性仍弱于普通 RAG。
- 当前没有完整鉴权体系。部署到公网前应增加登录认证、上传限制、文件大小限制和访问控制。
- 当前没有自动化测试覆盖。修改检索、记忆或文档入库逻辑后,建议补充单元测试和端到端问答测试。
- 引入 BM25 或中文分词倒排索引,增强关键词召回。
- 增加 OCR 支持,处理扫描版 PDF 和图片资料。
- 引入更稳健的文档结构解析,保留标题层级、页码、表格和章节路径。
- 给索引构建增加后台任务队列,避免大文件导入阻塞 UI。
- 用 SQLite/PostgreSQL 替代部分 JSON 文件存储,提升并发和可维护性。
- 增加用户登录、多租户隔离和权限控制。
- 增加检索评测集,跟踪召回率、引用准确率和幻觉率。
- 增加导出功能,例如导出问答记录、引用材料、记忆状态。
- 为部署版增加健康检查、日志采集和异常监控。
- 修改模型、chunk、检索阈值时,优先看
config_data.py。 - 修改 RAG 检索排序时,优先看
services/retrieval/pipeline.py、fusion.py、reranker.py。 - 修改文档解析和入库时,优先看
services/document_parser.py、document_service.py、vector_chunks_store.py。 - 修改记忆行为时,优先看
services/memory_service.py和ChatService.create_tools()。 - 修改 UI 时,入口在
streamlit_app.py。
基础语法检查:
python -m py_compile main.py streamlit_app.py config_data.py services/*.py services/retrieval/*.py关键词模式离线 smoke test:
python main.py ingest --data-dir data --storage-dir storage/default --mode keyword
python main.py ask --storage-dir storage/default --mode keyword --answer-mode rag --question "资料里有什么内容?"向量或混合模式需要先配置 ZHIPU_API_KEY。