Skip to content
ningkaikokPublic

About

中文小说 RAG 问答系统:向量 + BM25 混合检索、RRF 融合与重排、层级检索,检索过程可逐层观测与评测;PostgreSQL + pgvector,全本地可跑(Ollama),也可切 Claude / 智谱 GLM

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

124 Commits

Folders and files

Repository files navigation

通用知识库 Agent 平台

CI Release License Python Ruff pre-commit React

一个把 RAG 和 Agent 做「透明」的通用知识库平台:每次回答都能逐层展开向量、BM25、RRF 融合与重排的候选排名、分数变化和耗时,看清答案是怎么被找出来的 —— 以及在哪一步被弄丢的。小说是当前的兼容适配器和演示场景。

基于本地向量检索的知识库问答与 Agent demo。当前小说流程用于展示可核验检索、引用和 Agent 轨迹;检索和 Embedding 全部在本机运行。生成模型默认用本地 Ollama(不需要任何外部 API Key),也可以按需切换到你自己的 Claude 订阅、Codex 订阅或智谱 GLM(见下文“切换生成模型”)。

四个和一般 RAG demo 不太一样的地方

  1. 混合检索是完整的一条链,不是只算一次向量相似度。 段落聚合切分(约 500 字、片段间重叠)→ 向量召回(bge-small-zh-v1.5)+ BM25 → RRF 融合 → 重排;另建章节与全书摘要做层级检索,主题类和跨书比较类问题先定位书与章节,再回到原文参与融合。

  2. 检索过程可观测,也可评测。 界面里能展开每一层的候选排名与分数变化,用来定位「答案不对,是哪一步把证据丢了」;tests/ 维护问答评测集与历史基线,改检索策略前后能直接对比回归,而不是凭感觉调参。

  3. 索引是增量的,而且保证一致。 按小说文件 SHA-256 与索引配置指纹识别变化,只重建新增、修改或删除的书;每本书的向量与 BM25 索引在同一事务内原子切换,不会出现「向量更新了、BM25 还是旧的」这种中间态。

  4. 默认全本地零成本,也能随时换云端模型。 默认 Ollama(qwen2.5:7b)+ 本地 Embedding,不需要任何 API Key;需要更强生成质量时可在界面里切到 Claude 或智谱 GLM。另有独立的 Agent Lab:不依赖任何 Agent 框架手写的有限步只读工具循环,通过 SSE 逐步展示工具选择、结果与证据。

技术栈:

  • 文本切分:按段落聚合成约 500 字的片段,片段间有重叠,避免切断语义。
  • Embedding:sentence-transformers,本地模型 BAAI/bge-small-zh-v1.5(中文效果较好,体积约 95MB)。
  • 数据库:PostgreSQL + pgvector;小说兼容链路默认继续读写 V1 novel_chunks,通用文档已写入 knowledge_v2,并可通过显式开关或灰度模式接入真实问答检索。
  • 增量索引:按小说文件 SHA-256 和索引配置指纹识别变化,只更新新增、修改或删除的书;每本书的向量与 BM25 在同一事务中原子切换。
  • 层级检索:为片段建立章节摘要和全书摘要;主题、成长、跨书比较先定位书与章节,再回到原文参与融合和重排。
  • 检索可观测性:每次问答可展开查看向量、BM25、RRF、重排的候选排名、分数变化与耗时。
  • Agent Lab:独立的 3~5 步只读工具循环,逐步展示选择、工具结果与证据,不依赖 Agent 框架。
  • 生成模型:默认本地 Ollama(qwen2.5:7b),界面里可随时切换到其他本地模型、你自己的 Claude 订阅、Codex 订阅或智谱 GLM(云端模型会发送问题和召回片段,见下文)。
  • 后端:FastAPI(backend/main.py),把检索/生成逻辑包成 HTTP 接口,回答用 SSE 逐字流式返回。
  • 前端:React + Vite + TypeScript + Ant Design(frontend/),书卷气界面「书虫」。左侧目录使用通用知识库/文档术语;通用文档支持上传、版本历史、删除、重建和失败恢复,旧小说入口继续兼容。前后端类型契约由 OpenAPI 生成(schemas.py → openapi.json → api-generated.ts,CI 有 drift 检查),改 Pydantic 模型后跑 uv run python scripts/export_openapi.py && cd frontend && npm run gen:api。
  • 对话体验:生成中可以点「停止」——不只是前端不再显示新字,后端会真的停止向模型索取内容(用云端模型时不多花钱);刷新页面或重开浏览器后,之前的问答、原文出处、思考过程会自动恢复,中途被停止的那轮也会如实标出来;往上翻看历史时,下面来了新回答会提示「有新回复」,不会悄悄错过。

目录结构

novel-rag/
├── src/           # 核心业务逻辑(切分/入库/检索/重排),不依赖 Web 框架
├── backend/       # FastAPI 后端(只做"把 src 包成 HTTP"这一件事)
├── frontend/      # React + Vite 前端(类型由 OpenAPI 生成,见 api-generated.ts)
├── scripts/       # 独立工具(检索评测、质量门禁、OpenAPI 导出等)
├── docs/          # 学习文档与实验报告,见下
├── tests/         # pytest + 问答评测集与历史基线
├── data/novels/   # 放小说 .txt 文件
├── Dockerfile     # 多阶段构建:前端 dist + Python 运行时,单镜像部署
└── docker-compose.yml  # 应用 + pgvector 一键起(模型缓存/文本挂载持久化)

📖 学习路线

这个项目也是一份 RAG 学习材料,每个技术点都配了真实的失败案例和实测数据, 不是纸上谈兵:

文档 讲什么 什么时候看
系统技术架构 当前全栈架构图:分层、问答链路、索引流水线、数据模型、工程化设施 ⭐ 想快速建立全局认识
RAG 学习总览 四个杠杆是什么、完整链路、所有实测数据汇总(含负面结果)、方法论教训 从这里深入 RAG
代码导读 这份代码怎么读、建议的阅读顺序、可直接上手跑的实验 第一次接触这个项目
RAG 核心技术 检索评测、BM25、重排、Contextual Retrieval、多轮改写、GraphRAG 的原理与实测 想深入某个具体技术
问答模式与自动路由 一个输入框如何区分开放问题与小说问题,以及怎样离线评测 想理解新增的路由层
章节元数据与可核验引用 章节识别、[n] 引用定位、兼容迁移和引用评测 想理解回答如何回到原文证据
增量索引与后台任务 文件哈希、单书事务、进度、取消和失败重试 想理解长任务如何保持数据一致性
M3 层级检索 片段→章节→全书、摘要导航、原文回溯和跨书公平召回 想处理主题与人物成长问题
检索可视化评测 向量/BM25/RRF/重排排名怎样逐层变化,如何定位丢失点 想调试检索质量
检索是否真的在起作用 用对照实验区分「模型凭记忆答对」和「真的用了检索片段」 想验证检索有效性
Agent Lab 五个只读工具、有限步循环、安全边界和 SSE 轨迹 想从普通 Python 学 Agent
架构决策:是否需要 LangGraph 当前请求链路为什么保持显式编排、GraphRAG 和 LangGraph 的区别、什么情况下再引入 想学习技术选型
Agent 平台化架构 Control/Data Plane、Tool/Model Gateway、安全、状态、Trace、MCP 与生产演进 想从 Agent Lab 走向生产设计
项目路线图 已完成能力、后续里程碑和每阶段验收标准 想继续完善项目
生产部署与数据运维 独立租户、鉴权、配额、保留删除、备份恢复和告警 准备多用户私有部署
检索实验报告 chunk 粒度与 embedding 模型对照的实测数据(含大部头验证) 改检索参数前先看结论

另有两份面试向的整理:流式中断与 UI 性能、 从项目里提炼的 28 道面试题。

当前项目不依赖 LangChain/LangGraph。这是有意的架构选择,不是遗漏:先用普通 Python 函数看清切分、召回、融合、重排和生成的数据流。Agent Lab 也故意先用最多 五步的普通循环展示基本原理;当它需要跨进程恢复、人工审批或复杂并行分支时,再引入 LangGraph。Tool Registry、Tool Gateway、MCP 和生产级 Trace 的演进边界见 Agent 平台化架构,判断依据见上面的架构决策文档。

环境准备

  1. 安装 Ollama 并拉取一个模型(推荐 7b,效果更好;机器配置一般可以换成 qwen2.5:3b 甚至 1.5b):

    brew install ollama
    brew services start ollama
    ollama pull qwen2.5:7b
  2. 安装 PostgreSQL 和 pgvector,并创建数据库(如果使用本机 Homebrew 安装):

    brew install postgresql@18 pgvector
    brew services start postgresql@18
    createdb novel_rag
    psql -d novel_rag -c 'CREATE EXTENSION IF NOT EXISTS vector;'

    如果数据库地址、用户名或密码不同,设置 DATABASE_URL,例如:

    export DATABASE_URL=postgresql://user:password@127.0.0.1:5432/novel_rag
  3. 安装 uv 并同步 Python 依赖(需要 Python 3.11/3.12/3.13,3.14 太新,tokenizers 等库还没有对应的预编译包)。uv sync 会按 uv.lock 锁定版本创建 .venv,保证和 CI 完全一致:

    # macOS: brew install uv;或 curl -LsSf https://astral.sh/uv/install.sh | sh
    uv sync
    source .venv/bin/activate

    开发工具(ruff / pyright / pre-commit)在 dev 依赖组里,uv sync 一并装好。提交前建议启用 Git 钩子:uv run pre-commit install。

  4. 安装前端依赖(需要 Node.js 20.19+,推荐 22——vite 8 与 playwright 1.62 的硬要求;nvm use 22):

    cd frontend
    npm install
    cd ..

添加知识库文档(当前兼容范围)

当前前端目录展示已经通用化为“知识库 / 文档”,小说仍是默认的兼容适配器和演示场景。有两种方式,任选其一:

  • 网页上传(推荐):启动网页后选择 .txt/.md/.markdown/.pdf 文件。文件保存后会自动在后台建立增量索引,侧栏显示切分、Embedding、BM25 和入库进度;无需再手动全库重建。
  • 手动放置:把 .txt 文件放进 data/novels/ 目录,再运行 python src/ingest.py。

网页上传走通用的 /api/knowledge/documents 入口:TXT/Markdown/PDF 都会被解析、 Embedding、BM25 索引并写入 knowledge_v2 schema;STORAGE_SCHEMA 默认仍是 v1,小说问答的生产读写路径不受影响。Markdown/PDF 等非小说文档默认只会被索引、 不参与实际回答,需要设置 V2_NATIVE_RETRIEVAL_ENABLED=1 才会作为一路召回真正 参与检索和引用(见 知识库领域迁移)。每个 TXT 文件同时作为一个兼容文档、文件名会作为来源标注;目录里已经有一篇原创的示例 短篇小说 雾隐山庄.txt,用于快速验证整个流程,可以直接删掉换成你自己的合法文本。

V2 原生 read path 支持可回滚灰度:设置 V2_NATIVE_RETRIEVAL_MODE=gray 和 V2_NATIVE_RETRIEVAL_GRAY_PERCENT=10 后,按 session_id(没有会话时按本轮请求) 稳定分桶 10% 流量;回滚只需重启并设回 off。灰度期间查询缓存自动绕过,避免同一个 问题在不同灰度组之间串结果。固定问答集可用 uv run python scripts/eval_v2_native.py --save /tmp/v2-native.json 运行,脚本只保存 排名、来源类型和耗时,不保存正文。

启动(FastAPI + React,推荐)

需要开两个终端:

终端 1 — 后端 API(端口 8000):

source .venv/bin/activate
uvicorn backend.main:app --port 8000

终端 2 — 前端 dev server(端口 45173,刻意避开 Vite 默认的 5173——本机常有别的 项目占用这个端口):

cd frontend
npm run dev

浏览器打开 http://localhost:45173。左侧「知识库」展示文档目录、来源类型和索引状态;可以上传/删除 TXT/Markdown/PDF 文档、查看后台索引进度、安全停止或重试任务,并在「更多设置」里检查文件变化和调整参考原文数量。上传走通用 V2 索引流水线,但默认只有小说(V1)参与实际问答;非小说文档要参与检索需要开启 V2_NATIVE_RETRIEVAL_ENABLED。底部保持一个输入框;工作模式可选择稳定的“标准 RAG”或教学用“Agent Lab”。标准 RAG 提供三种回答模式:

  • 自动判断:开放问题直接问模型,小说问题先检索原文;拿不准时保守地检索。
  • 仅依据原文:强制执行混合检索、重排和引用,资料不足就明确拒答。
  • 自由问答:不搜索小说书架,适合技术概念、写作、翻译和闲聊;没有索引也能用。

实际采用的路径和原因会显示在「思考过程」中。原文问答命中片段后会自动带入同一本书前后相邻片段,回答逐字流式显示;正文中的 [1] 可以点击并定位到对应的书名、章节名和片段编号,方便核实依据。前端 /api 请求由 Vite 代理到后端 127.0.0.1:8000。

对主题、人物成长和跨书比较问题,标准 RAG 会自动增加层级摘要导航;回答下方的 “检索评测”可逐层查看排名。切到 Agent Lab 后,界面会改为展示最多五次 “选择工具 → 获得观察 → 继续判断”,最终仍只能依据可点击的原文出处回答。

生产部署(Docker,推荐):

docker compose up --build -d
# 把 .txt 小说放进 data/novels/ 后建索引:
docker compose exec api uv run python src/ingest.py

打开 http://localhost:8000——单端口同时服务 API 与前端页面。PostgreSQL(pgvector)、 模型缓存、小说文本分别持久化在独立卷/挂载里;容器内访问宿主机 Ollama 走 host.docker.internal(Linux 由 compose 的 host-gateway 提供)。

⚠️ 端口只绑定在本机(compose 里写的是 127.0.0.1:8000:8000)。此单用户入口所有 接口都没有鉴权、也没有限流,如果改成 8000:8000 发布到所有网卡,同网段的任何人 都能读你的书架和聊天记录、并无上限地消耗你配置的 Claude/GLM 额度。确需局域网 访问时,请自己补上防火墙或反向代理鉴权。

MCP(实验性):uv run python scripts/mcp_server.py 提供通用知识库只读 MCP 服务器 (stdio),可在 Claude Code 等客户端注册后查询目录和文档证据;小说通过 legacy adapter 兼容。Agent/MCP 当前不读取 V2 数据库、不提供认证或多租户权限,引用摘录最多 80 字。

不用 Docker 的手动路径:cd frontend && npm run build 生成静态文件后, FastAPI 会自动检测 frontend/dist 并托管(存在即挂载,开发模式不受影响)。

多用户生产部署

多用户生产部署请使用新的认证入口,见 生产部署与数据运维。 运行 python scripts/production_init.py --users alice bob 可生成私有 Compose 部署: 每位用户独立 worker、PostgreSQL/pgvector 和数据目录,统一认证、配额及监控。 此前的根目录 Compose 与 backend.main:app 仍用于本地单用户运行。

切换生成模型(本地 Ollama / Claude 订阅 / Codex 订阅 / 智谱 GLM)

输入框上方的模型下拉框支持四类来源,选完立即生效,不需要重启服务:

  • 💻 本地(Ollama,完全离线):自动列出 ollama list 里已安装的模型(如 qwen2.5:3b、qwen2.5:7b)。
  • ☁️ 我的 Claude 订阅(云端):如果本机装了 Claude Code CLI 并已登录,会额外出现 haiku/sonnet/opus 三档,不需要单独配置 ANTHROPIC_API_KEY——直接复用你本地已登录的 Claude 订阅(后端通过 claude --print 非交互调用)。
  • ☁️ 我的 Codex 订阅(云端):如果本机装了 OpenAI Codex CLI(npm install -g @openai/codex)并已登录(codex login),会额外出现一档 codex:default,同样不需要单独配置 OPENAI_API_KEY——直接复用你本地已登录的 ChatGPT 订阅(后端通过 codex exec --json 非交互调用)。实测 ChatGPT 账号登录下传具体模型名(gpt-5/o3 等)会被 CLI 直接拒绝(not supported when using Codex with a ChatGPT account),所以默认不传 -m,用账号当前的默认模型;如果你是用 API Key 登录、账号支持指定模型,用环境变量 CODEX_MODEL_ALIASES(逗号分隔的具体模型名)覆盖。CLI 不在 PATH 里(比如只在某个项目目录下装了独立版本)时,用 CODEX_BIN 指向可执行文件的绝对路径,不需要手动软链。
  • ☁️ 智谱 GLM(云端):设置了环境变量 ZHIPU_API_KEY 时出现 glm-4-flash/glm-4.5-air/glm-4.5/glm-4.6 四档。

选择任何云端模型时请注意(界面上的胶囊标签和 Tooltip 也会显示同样的提示):

  • 不再是"完全本地":检索到的原文片段和你的问题会发送到对应厂商(Anthropic / OpenAI / 智谱)的服务器。
  • 计入你自己的账号用量,不是免费的(glm-4-flash 除外)。
  • Claude/Codex 这两条路径都是复用本机已登录的 CLI 会话,回答风格可能会受你本机全局配置(~/.claude/CLAUDE.md、~/.codex/config.toml 等)的轻微影响(例如强制用中文回复)。

配置智谱 GLM 的 API Key

Key 存在项目根目录的 .env 里,后端启动时自动加载(backend/dotenv_lite.py,零依赖),不需要每次手动 export,也不会进版本库(.env 已被 .gitignore 忽略):

cp .env.example .env   # 然后编辑 .env 填入你的 key
uvicorn backend.main:app --port 8000

启动日志会打印一行 [env] 已从 .env 加载:ZHIPU_API_KEY(只打变量名、不打值)确认生效。

优先级:真实环境变量 > .env,所以想临时换个 key 或模型,启动前 export 同名变量即可覆盖:

ZHIPU_API_KEY=另一个key uvicorn backend.main:app --port 8000

没配置这个 key 时,下拉框里不会出现 GLM 分组,其他功能不受影响。Key 在智谱开放平台申请和吊销——不要把 key 贴到聊天、issue 或截图里,一旦泄露立即到该页面吊销重建。

实现细节见 backend/claude_cli.py、backend/codex_cli.py 和 backend/zhipu.py。

单独同步索引(命令行)

如果你是手动往 data/novels/ 放的文件,或想在命令行同步:

source .venv/bin/activate
python src/ingest.py

正式写入前也可以先做一次不改数据库的质量预检:

python scripts/check_index_quality.py --novel data/novels/雾隐山庄.txt

预检会使用当前 Embedding 模型的真实 tokenizer 检查输入 token 长度,并报告编码方式、 空/重复片段、章节覆盖和乱码提示。索引同步时会再次执行同一门禁;超长输入、空内容、 异常向量或维度不一致会阻止当前书替换旧索引。质量摘要会随 index_manifest 保存, 不包含小说原文。

这一步会比较文件哈希和 index_manifest 清单,只处理发生变化的书。准备新数据时旧索引 仍可查询;最后在一个 PostgreSQL 事务里同时替换该书的向量、BM25 和清单记录。 任务失败或被取消时当前书自动回滚,其他书不受影响。

从 M2 之前的版本升级时,第一次运行会把现有书逐本迁移并建立哈希清单;以后没有 变化时只扫描文件就结束。网页的 POST /api/reindex?force=true 可用于明确要求全部重做, 平时不要使用强制模式。

从 M3 之前的版本升级时,文件未变化的书只会补建章节/全书摘要,不会重算基础片段 向量和 BM25。层级摘要使用独立清单,后续只改摘要算法也能单独迁移。

可调参数(环境变量)

变量 默认值 说明
EMBEDDING_MODEL BAAI/bge-small-zh-v1.5 本地 embedding 模型(双编码器,负责快速粗筛)
RERANKER_MODEL BAAI/bge-reranker-base 本地重排模型(交叉编码器,约 1.1GB,首次使用自动下载)
RERANK_ENABLED 1 设成 0 关闭重排(模型下载不了、或想对比重排前后效果时用)
CHUNK_SIZE 500 每个片段的字符数上限
CHUNK_OVERLAP 80 相邻片段的重叠字符数
OLLAMA_HOST http://localhost:11434 Ollama 服务地址
OLLAMA_MODEL qwen2.5:7b 生成用的模型名
DATABASE_URL 当前用户本机 novel_rag PostgreSQL 连接字符串
TOP_K 3 送进 prompt 的片段数(默认值是实测出来的,见 docs/rag-techniques.md 第 5 节)
FULL_TEXT_MAX_CHARS 8000 点名的书全文小于这个字数时跳过检索、直接给模型全文
RECALL_K 20 关键词和向量检索各自召回的候选片段数量
CONTEXT_NEIGHBORS 1 问答时每个命中片段前后额外带入的相邻片段数
HIERARCHY_ENABLED 1 对全局问题开启章节/全书摘要导航;最终证据仍是原文
HIERARCHY_SUMMARY_MAX_CHARS 800 单个抽取式摘要的最大字符数
HIERARCHY_UNTITLED_CHUNKS 12 无章节标题小说的虚拟章节窗口大小
HIERARCHY_TOP_K 6 全局问题最多选择的章节摘要节点数
BM25_K1 1.2 BM25 词频饱和速度,越小饱和越快(一般不用动)
BM25_B 0.75 BM25 文档长度归一化强度,0=不归一化、1=完全按长度惩罚(一般不用动)
CONTEXTUAL_MODE auto 上下文增强三档:off 关闭 / auto 小体量书默认构建、大部头跳过且要求生成后端可用 / on 强制(仍受片段数上限拦截)
CONTEXTUAL_ENABLED 空 旧开关,兼容保留:设 1 等价 CONTEXTUAL_MODE=on;设 0 且未另设 MODE 时等价 off
CONTEXTUAL_MAX_CHUNKS_PER_BOOK 2000 成本闸门:超过这个片段数的书直接跳过,不做上下文增强
CONTEXTUAL_MODEL glm:glm-4-flash 生成上下文说明用的模型(用便宜的小模型就够)
CONTEXTUAL_WORKERS 8 生成上下文的并发数
QUERY_REWRITE_ENABLED 1 多轮追问时先补全指代再检索;设成 0 关闭
QUERY_REWRITE_MODEL glm:glm-4-flash 改写用的模型(用便宜快速的小模型,别用推理型大模型)
QUERY_CACHE_ENABLED 1 启用有界的普通查询检索缓存;只缓存来源,不缓存回答正文
QUERY_CACHE_MAX_ENTRIES 128 单进程 LRU 检索缓存最多保留的查询数
MODEL_ROUTING_ENABLED 1 启用任务级模型路由;回答仍尊重界面当前选择的模型
MODEL_QUERY_REWRITE 同 QUERY_REWRITE_MODEL Model Gateway 为查询改写任务选择的模型
MODEL_SUMMARY 同 HISTORY_SUMMARY_MODEL Model Gateway 为会话摘要任务选择的模型
MODEL_QUERY_EXPAND 同 QUERY_EXPAND_MODEL Model Gateway 为查询扩展任务选择的模型
MODEL_JUDGE 空 Model Gateway 为引用 Judge 任务选择的模型;为空时沿用当前模型
MODEL_FALLBACK_MODEL 空 首个 token 前模型失败时的显式备用模型;空值不自动切换
MODEL_CLOUD_ALLOWED 1 是否允许 Claude/智谱等云端模型;设 0 可强制本地模型边界
MODEL_MAX_OUTPUT_CHARACTERS 0 单次生成输出字符预算,0 表示不限制
MODEL_MAX_ESTIMATED_COST_USD 0 单次生成估算成本上限,0 表示不限制
MODEL_CHARS_PER_TOKEN 1.5 无供应商 tokenizer 时用于成本观测的字符/token 估算比例
MODEL_INPUT_USD_PER_MILLION_TOKENS 0 输入 token 估算单价;需按实际供应商价格配置
MODEL_OUTPUT_USD_PER_MILLION_TOKENS 0 输出 token 估算单价;需按实际供应商价格配置
V2_SHADOW_ENABLED 0 设为 1 时额外读取 V2 候选并写入 trace;V1 仍负责最终回答
V2_NATIVE_RETRIEVAL_ENABLED 0 设为 1 时,上传的非小说 V2 文档(Markdown/PDF 等)作为额外一路召回真正参与回答和引用;小说仍走 V1
V2_NATIVE_RETRIEVAL_MODE off V2 原生 read path 发布模式:off、gray 或 on;旧 V2_NATIVE_RETRIEVAL_ENABLED=1 仍兼容
V2_NATIVE_RETRIEVAL_GRAY_PERCENT 0 gray 模式的稳定分桶百分比,范围 0~100;按会话或请求键计算
FAITHFULNESS_SHADOW_ENABLED 0 回答完成后后台运行引用忠实度影子核验,不阻塞回答、不自动改写
FAITHFULNESS_JUDGE_MODE two_step 影子/按需核验方式:single_step 或 two_step
HISTORY_IN_PROMPT 1 最终回答的 prompt 里带上「对话背景」段(M3.6);设成 0 回到只有当前问题和检索证据
HISTORY_MAX_TURNS 6 背景段最多带几轮历史,超出丢最旧的
HISTORY_MAX_CHARS 1200 背景段的总字数预算,超出继续从最旧的整轮丢弃
HISTORY_PER_TURN_CHARS 220 单轮最多保留多少字,超出截断(助手回答动辄上千字,整段塞进去会把证据挤没)
SESSION_FACTS_MAX_CHARACTERS 8 结构化会话事实(M3.6)里最多记住多少个提到过的人物,超出丢最旧提到的。不调用模型,随对话背景默认生效,无开关
AGENT_TOOL_MAX_CHARS 6000 Agent Lab 单次工具输出的字数上限(M3.6):条数上限拦不住体积,超出按「离中心最远 / 末尾」丢弃并写进 observation
HISTORY_SUMMARY_ENABLED 1 滚动会话摘要(M3.6):把掉出历史窗口的更早对话压成一段摘要。长会话评测已跑过两个模型均零摘要漂移,默认开启;设成 0 关闭,见 docs/experiments/m36-session-summary.md
HISTORY_SUMMARY_MODEL 同 QUERY_REWRITE_MODEL 生成摘要的模型(压缩任务,用便宜快速的小模型)
HISTORY_SUMMARY_EVERY 4 攒够多少轮掉出窗口的对话才更新一次摘要;短会话一次都不触发
HISTORY_SUMMARY_MAX_CHARS 400 摘要自身的字数上限,超出硬截断(它是每轮都要付的固定开销)
QUERY_EXPAND_ENABLED 0 低置信度自适应查询扩展(M3.4):信号不足时生成最多 2~3 个改写再补救检索一次;默认关闭
QUERY_EXPAND_MODEL 同 QUERY_REWRITE_MODEL 扩展变体生成的模型
QUERY_EXPAND_MAX_VARIANTS 3 单次补救最多生成的改写变体数
CHAPTER_EXPANSION_MODE off 证据带入方式:off/neighbors 用现有邻居机制 / chapter 命中片段所在整章按原文顺序进入 prompt
CHAPTER_EXPANSION_MAX_TOKENS 3000 整章扩展的真实 token 预算闸门,超预算从离命中最远处截断并写 trace
GRAPH_ENABLED 0 设成 1 建人物关系图,让"某某有哪些伴侣/师父"这类问题能查图而不是靠碰运气
GRAPH_MAX_CHUNKS_PER_RELATION 80 成本闸门:每个「书×关系」最多采样多少片段去抽人名
GRAPH_MODEL glm:glm-4-flash 抽人名/关系用的模型(便宜的小模型就够)
GRAPH_REQUIRE_EXPLICIT 1 M4 质量门槛:只把「明确关系陈述」的边放进问答线索;共现边留在库里供审核界面人工把关
GRAPH_MIN_CONFIDENCE 0.7 边进入在线结果的最低置信度(与上一项配合)
MAX_UPLOAD_BYTES 20971520 单个上传文件的大小上限(字节),按 1MB 分块流式读取,超限返回 413
LOG_LEVEL INFO 后端日志级别(DEBUG/INFO/WARNING/ERROR)
DB_POOL_MIN_SIZE 1 PostgreSQL 连接池最小连接数(只有 FastAPI 后端会用到)
DB_POOL_MAX_SIZE 10 PostgreSQL 连接池最大连接数
ZHIPU_API_KEY 空 智谱开放平台 API Key,设置后界面出现 GLM 模型分组
ZHIPU_API_URL https://open.bigmodel.cn/api/paas/v4/chat/completions 智谱 chat completions 接口地址

例如临时换用更小的模型作为默认值启动后端:

OLLAMA_MODEL=qwen2.5:3b uvicorn backend.main:app --port 8000

已知限制(demo 阶段)

  • 文档上传支持 .txt、.md、.markdown 和文本型 .pdf;暂不支持 EPUB,也不对扫描版 PDF 做 OCR。单文件默认上限为 20 MiB,并限制 PDF 页数和解析后的片段数。
  • 章节识别覆盖常见“第 N 章/卷、序章、番外”等标题;非常规排版仍可能识别不到,此时章节字段为空但片段仍可检索。
  • 后台任务状态会落库(index_task_runs 表):刷新网页可以恢复进度,重启后端后侧栏也能恢复上一次任务的最后已知状态;上次进程遗留的进行中任务会被如实标记为 failed 并提示重试。数据库按书事务和哈希清单仍会保留,重新点击同步即可从未完成的书继续。
  • 网页上传没有认证、用户隔离、用户级存储配额或内容审核,仍适合本地单人使用。文件大小和格式限制已实现;对外提供服务前仍需补齐身份权限、配额和运维保护。
  • 文本编码:loader.py 会依次尝试 UTF-8 → GB18030 解码,覆盖国内小说站常见的两种编码。如果上传后回答明显文不对题(比如问主角是谁答非所问),先检查文件真实编码(file -I data/novels/xxx.txt),极少数生僻编码可能仍需手动转码为 UTF-8 后再上传。
  • 小模型幻觉:qwen2.5:3b 参数量较小,即使检索到了正确的原文片段,也可能忽略上下文、凭训练时的记忆编造答案(例如把小说人物错认成其他知名小说的角色)。真实使用建议用 qwen2.5:7b 或更大的模型,指令遵循和上下文依据能力明显更强。
  • 层级摘要是导航摘要:当前采用抽取式采样,不等于完整的文学主题总结。它能把全局问题分散到多个相关章节,但深层主题仍取决于原文覆盖和生成模型判断。
  • Agent Lab 不做持久恢复:轨迹只属于当前请求;刷新后不会从中间工具步骤继续。需要检查点和人工审批时再考虑 LangGraph。

版权与许可

  • 代码:MIT 许可,见 LICENSE。
  • 小说文本不随仓库分发。data/novels/ 已被 .gitignore 排除,只保留原创的示例短篇 雾隐山庄.txt 用于跑通流程。你需要自备合法获得的小说文本,放进 data/novels/ 后同步索引。
  • 不要把版权原文提交进版本库,也包括测试结果文件:tests/run_qa_tests.py 只落盘 80 字摘录 + novel/chunk_id 定位信息(早期版本会把整段原文写进结果 JSON, 一次测试就写入十几万字,已修正)。需要核对完整原文时按 novel + chunk_id 去库里查。
  • 本项目仅供个人学习与本地使用,请勿用于分发或提供公开的小说内容服务。

About

中文小说 RAG 问答系统:向量 + BM25 混合检索、RRF 融合与重排、层级检索,检索过程可逐层观测与评测;PostgreSQL + pgvector,全本地可跑(Ollama),也可切 Claude / 智谱 GLM

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages