Skip to content

Repository files navigation

chatbox

一个面向本地资料的个性化知识库问答系统。项目以 Streamlit 提供 Web 界面,支持上传和管理文档,将文档解析、切片、索引到本地 FAISS/JSON 存储中,并通过 RAG、混合检索、重排、引用生成和对话记忆来回答问题。

项目当前使用智谱 OpenAI-compatible API:

  • 对话模型:glm-4.7
  • 向量模型:embedding-3
  • 可选重排模型:BAAI/bge-reranker-v2-m3

功能概览

  • 本地知识库问答:支持 txtmdpdfdocxxlsxcsv
  • 多种检索模式:vectorkeywordhybrid
  • RAG 回答:基于召回片段和长期记忆生成答案,并输出 [document_id:chunk_index] 引用。
  • Agentic RAG:可让 LangGraph ReAct Agent 调用知识库检索、记忆召回、深读 chunk、保存偏好和更新工作记忆等工具。
  • 文档管理:支持上传导入、目录导入、重建索引、删除文档。
  • 个性化记忆:包括短期会话窗口、核心工作记忆、用户偏好、长期语义记忆。
  • 降级回答:模型限流或不可用时,可基于已检索片段生成抽取式答案。
  • Docker 部署:提供 Dockerfiledocker-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:命令行入口,支持 ingestask
  • 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 都属于运行时或隐私数据,不建议提交到公开仓库。

文档入库流程

  1. 文件来源:

    • Web 上传:st.file_uploader
    • 本地目录:默认扫描 data/
    • CLI:python main.py ingest
  2. 去重与记录:

    • DocumentService.ingest_bytes() 对文件内容计算 SHA-256。
    • 如果同 hash 的成功记录已存在,返回 duplicate
    • 原始文件保存到 raw_documents/{hash前12位}_{file_name}
    • 文档元数据写入 json_store/documents.json
  3. 文档解析:

    • txtmd:依次尝试 utf-8utf-8-siggbk
    • pdf:使用 pypdf.PdfReader 提取每页文本。
    • docx:使用 python-docx 提取段落。
    • xlsxcsv:使用 pandas 转成表格文本。
    • 解析后会做空行和 BOM 清理,如果无法提取可用文本则入库失败。
  4. 父子切片:

    • 父 chunk:默认 1200 字符,重叠 120
    • 子 chunk:默认 320 字符,重叠 80
    • 检索命中子 chunk,但回答上下文会带上对应父 chunk,兼顾命中精度和上下文完整性。
    • 每个 chunk 保存 document_idchunk_indexparent_idsection_titlefile_name 等元数据。
  5. 索引写入:

    • vectorhybrid 入库时,子 chunk 会通过 embedding-3 生成向量并写入 FAISS。
    • 所有模式都会写入 chunks.json,因此关键词检索可在无 embedding 的情况下工作。
    • keyword 入库不会写 FAISS,适合无 API key 或轻量测试。

RAG 检索流程

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 个候选,之后进入融合和重排。

RRF 融合

ReciprocalRankFusion 使用 1 / (k + rank) 计算每个结果在各路召回中的贡献,默认 k=60。查询变体本身也有权重,关键词召回会乘以 0.92,让向量召回略占优势但不完全压过关键词信号。

融合后的候选会记录:

  • fusion_score
  • dense_similarity
  • keyword_score
  • retrieval_sources
  • query_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 错误,会降级为抽取式答案:直接整理命中片段和扩展上下文。

Agentic RAG 流程

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 预算。

核心记忆

核心记忆保存当前任务状态,包括:

  • goal
  • current_plan
  • completed_steps
  • next_step
  • constraints
  • notes

这些字段由 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/

快速开始

1. 创建环境

python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt

Linux/macOS:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

2. 配置 API Key

复制环境变量示例:

copy .env.example .env

Linux/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_key

Linux/macOS:

export ZHIPU_API_KEY=your_zhipu_api_key

3. 启动 Web UI

streamlit run streamlit_app.py

浏览器打开:

http://localhost:8501

4. 使用界面

  1. 在侧边栏确认 Storage dirData dirRetrieval modeAnswer mode
  2. 上传文件,点击 Import Uploaded
  3. 或将文件放到 data/,点击 Import Folder
  4. 在右侧输入问题并发送。
  5. 展开 References 查看引用来源。

CLI 用法

导入本地目录

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 重排

默认关闭 CrossEncoder,因为 BAAI/bge-reranker-v2-m3 需要额外模型下载、磁盘空间和内存。

安装依赖:

pip install -r requirements-reranker.txt

设置环境变量:

set CHATBOX_ENABLE_CROSS_ENCODER=true

Linux/macOS:

export CHATBOX_ENABLE_CROSS_ENCODER=true

然后重新启动应用。

如果部署在轻量服务器上,建议保持:

CHATBOX_ENABLE_CROSS_ENCODER=false

Docker 部署

本地 Docker

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

Linux/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

Docker Compose

copy .env.example .env
docker compose up -d --build

Linux/macOS:

cp .env.example .env
docker compose up -d --build

Compose 会把运行时数据挂载到:

./runtime/data
./runtime/storage

VPS 部署建议

推荐使用支持 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.jsondocuments.jsonqa_logs.json 是 JSON 文件存储,适合个人或小规模使用,不适合高并发生产场景。
  • CrossEncoder 默认关闭;开启后会增加冷启动时间、内存占用和模型下载成本。
  • 短期记忆保存在进程内字典中,应用重启后短期会话窗口会丢失;偏好、核心记忆和长期语义记忆会持久化。
  • Agentic RAG 依赖模型工具调用质量,虽然设置了递归限制和工具调用约束,但稳定性仍弱于普通 RAG。
  • 当前没有完整鉴权体系。部署到公网前应增加登录认证、上传限制、文件大小限制和访问控制。
  • 当前没有自动化测试覆盖。修改检索、记忆或文档入库逻辑后,建议补充单元测试和端到端问答测试。

后续可改进方向

  • 引入 BM25 或中文分词倒排索引,增强关键词召回。
  • 增加 OCR 支持,处理扫描版 PDF 和图片资料。
  • 引入更稳健的文档结构解析,保留标题层级、页码、表格和章节路径。
  • 给索引构建增加后台任务队列,避免大文件导入阻塞 UI。
  • 用 SQLite/PostgreSQL 替代部分 JSON 文件存储,提升并发和可维护性。
  • 增加用户登录、多租户隔离和权限控制。
  • 增加检索评测集,跟踪召回率、引用准确率和幻觉率。
  • 增加导出功能,例如导出问答记录、引用材料、记忆状态。
  • 为部署版增加健康检查、日志采集和异常监控。

开发提示

  • 修改模型、chunk、检索阈值时,优先看 config_data.py
  • 修改 RAG 检索排序时,优先看 services/retrieval/pipeline.pyfusion.pyreranker.py
  • 修改文档解析和入库时,优先看 services/document_parser.pydocument_service.pyvector_chunks_store.py
  • 修改记忆行为时,优先看 services/memory_service.pyChatService.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

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages