Skip to content

Repository files navigation

🧠 知识图谱系统 (Knowledge Graph System)

一个支持多用户的知识图谱系统,整合 Neo4j 图数据库和 ChromaDB 向量数据库,实现文档知识管理、可视化图谱与 RAG 对话。 让散落各处的知识连成一张可探索的网 ✨

⚠️ 安全提醒

🚨 本项目的 .env(根目录与 backend/)包含真实 API 密钥和 JWT 签名密钥。任何 fork、镜像或截图前必须:

  1. 在硅基流动 / Moonshot / 阿里云百炼控制台**轮换(撤销并重新签发)**这些 API 密钥
  2. 用 python -c "import secrets; print(secrets.token_urlsafe(48))" 生成新 JWT_SECRET 并替换
  3. 🙅 永远不要把 .env 提交到 git(已在 .gitignore 中,但仍请小心处理历史记录)

详见 安全配置 一节。

核心功能

  1. 📚 文档知识库:支持上传 PDF/Word/TXT/MD 格式,自动转换为 Markdown 并按层级切块;支持粘贴 URL 抓取网页直接入库(readability 正文提取 + SSRF 防护链,FEAT-019)、粘贴 Markdown/纯文本直接入库(FEAT-022)、失败文档一键重新处理(FEAT-017)
  2. 🔍 混合检索:多查询 + 硅基流动 Qwen3-Embedding-8B 向量检索 + BM25 关键词 + 图谱通道 + RRF 融合 + Qwen3-Reranker 重排序 + 父文档扩展再重排(统一管线 services/retriever.py,TTL+LRU 缓存)
  3. 🕸️ 知识图谱可视化:d3 + Vue 3 实现的交互式力导向图谱,支持节点拖拽、实体编辑、合并与删除
  4. 💬 大模型对话:基于 Kimi / 百炼 (qwen3.7-flash) 的 RAG 问答,支持流式 / 非流式、图谱增强 RAG、对比模式、消息反馈、深度思考开关(Qwen 混合思考,推理过程以 event: thinking 帧流式展示)、意图路由(闲聊 / 拒答绕过检索,分类失败回退到 RAG);会话历史有界加载(超过阈值自动折叠为 LLM 摘要,成本与质量不随对话变长恶化);引用来源卡片可点击跳转文档详情(FEAT-020)、一键导出会话为 Markdown(FEAT-023)
  5. 📊 仪表盘与时间线:文档 / 实体 / 标签统计、月度增长、近期活动、实体首现时间线
  6. 🗺️ 文档聚类地图:2D PCA 投影可视化所有文档的语义分布
  7. 🔌 MCP Server:标准 MCP (stdio) 把检索 / 图谱能力暴露给 Claude Desktop / Claude Code / codex 等客户端,四个只读工具零前端投入直接查询知识库
  8. 📏 RAG 测评框架:检索指标(Hit@K / MRR / Precision@K / Recall@K / nDCG@K)+ LLM-as-judge 生成指标(Faithfulness / Hallucination / Relevance / Citation / Correctness + 置信度),13 个 gold 用例,提示词改动的回归门禁(见 backend/eval/);反馈驱动评测集(FEAT-018):对话中 👎 消息一键转 gold 用例落 SQLite eval_cases 表,runner 与静态 gold 合并运行(--no-db 关闭),管理页 /eval 可增删启停;评测报告落库与趋势(FEAT-021):eval.runner --save 把聚合指标落 eval_runs 表,/eval/runs 页呈现指标趋势与运行记录,门禁有历史证据链
  9. 🔐 用户隔离:JWT 账号密码认证,SQLite 存储用户数据,Neo4j/ChromaDB 通过 user_id 标签隔离
  10. 🛡️ 健壮性:统一 logging(请求级 X-Request-ID 关联)、请求体大小全局兜底(413)、批量写入(Neo4j UNWIND)、输入校验、4xx 不重试、防 401 重定向循环、API 限流中间件、embedding 缓存自愈(损坏 blob 自动剔除)、BM25 启动预热、卡死文档启动对账(reconcile)、检索结果 TTL+LRU 缓存、向量索引零成本重建脚本、SQLite 增量迁移(schema_version 追踪)、全量备份脚本、上下文注入预算熔断(<context> 区硬 token 上限,超限按块裁剪不炸窗口)

技术架构

层级 技术
🎨 前端 Vue 3.5 + Vite 7 + Pinia + Vue Router + d3.js · 学术雅致派双主题(Fraunces + Plus Jakarta Sans + JetBrains Mono)
⚙️ 后端 API FastAPI 0.115 + Python 3.11 + uvicorn
📄 文档转换 firecrawl-anydoc 0.2.4(Rust,零传递依赖;docx/doc/pdf/pptx/xlsx 等 14 种格式 → GFM Markdown,按内容特征探测格式,内置资源上限)
🕸️ 图数据库 Neo4j 5.14 (Docker, APOC 插件)
🧬 向量数据库 ChromaDB 0.4.18 (Docker)
💾 用户数据 SQLite + SQLAlchemy 2.0 + aiosqlite (单文件)
🧩 嵌入模型 硅基流动 Qwen3-Embedding-8B (API)
🎯 重排序 硅基流动 Qwen3-Reranker-8B (API)
🤖 大模型 Kimi API (Moonshot, kimi-k2) / 百炼 qwen3.7-flash (阿里云 DashScope) / 硅基流动 Qwen3-8B
🔑 密码哈希 bcrypt 4.1.3(原生)
📝 日志 Python logging + RotatingFileHandler(统一在 app/logger.py,text/JSON 双格式)

项目目录结构

D:/NC/
├── docker-compose.yml           # Neo4j + ChromaDB 服务定义
├── .env / .env.example          # 部署环境变量(example 为模板)
├── package.json                 # 根级 npm 脚本(concurrently 一键启动)
├── start-dev.bat / start-dev.sh # Windows / Bash 一键启动脚本
├── backend/
│   ├── .env / .env.example      # 后端实际加载的 .env
│   ├── app/
│   │   ├── api/                 # API 端点
│   │   │   ├── auth.py          # 注册/登录/me
│   │   │   ├── documents.py     # 文档上传/列表/详情/删除/切块/标签/聚类
│   │   │   ├── chat.py          # RAG 对话(流式 + 非流式 + 反馈)
│   │   │   ├── graph.py         # 实体/图谱查询/可视化/编辑/合并
│   │   │   ├── search.py        # 语义检索
│   │   │   ├── progress.py      # 文档处理进度(SSE + 历史)
│   │   │   ├── tags.py          # 用户级标签聚合
│   │   │   ├── timeline.py      # 时间线聚合数据
│   │   │   └── dashboard.py     # 仪表盘汇总
│   │   ├── models/              # Pydantic 数据模型(含字段校验)
│   │   ├── services/            # 核心服务
│   │   │   ├── retriever.py     # 统一检索管线(多查询 + 图谱 + 父文档扩展 + 再重排 + TTL 缓存)
│   │   │   ├── intent.py        # 查询意图分类(fact_retrieval / chitchat / should_reject)
│   │   │   ├── embedding.py     # 硅基流动嵌入(限流 + JSON 缓存)
│   │   │   ├── llm.py           # 百炼 / Kimi / 硅基流动 多 LLM
│   │   │   ├── chunker.py       # Markdown 层级切块(可配 overlap)
│   │   │   ├── entity_extractor.py  # 实体 + 关系提取(LLM 模式)
│   │   │   ├── neo4j_client.py  # Neo4j 封装(含 UNWIND 批量)
│   │   │   ├── chroma_client.py # ChromaDB 封装
│   │   │   ├── bm25.py          # BM25 关键词检索(per-user 索引 + 启动预热)
│   │   │   ├── fusion.py        # RRF / 加权融合
│   │   │   ├── reranker.py      # 硅基流动 Rerank
│   │   │   ├── query_processor.py  # 查询改写 / 变体 / 实体抽取
│   │   │   ├── history.py       # 会话历史有界加载 + LLM 摘要折叠(summary 行)
│   │   │   ├── context_budget.py # 注入预算熔断(token 估算 + 按块裁剪)
│   │   │   ├── reconcile.py     # 卡死文档启动对账(标记 failed)
│   │   │   ├── progress_tracker.py  # SSE 进度跟踪
│   │   │   └── doc_status.py    # 文档处理状态机(pending/document_created/indexed/graphed/ready/failed)
│   │   ├── auth/                # JWT 鉴权 + bcrypt 密码哈希 + 限流中间件
│   │   ├── prompts/             # LLM 提示词模板(templates/*.md + loader,启动自检)
│   │   ├── middleware.py        # 纯 ASGI 中间件(请求体限流 + X-Request-ID)
│   │   ├── utils/md_parser.py   # Markdown 解析(anydoc 转换 + 失败降级)
│   │   ├── config.py            # 配置管理(含 CORS 白名单 + JWT 占位符拦截)
│   │   ├── database.py          # SQLite 初始化 + 增量迁移(schema_version 追踪)
│   │   ├── logger.py            # 统一 logging 配置(请求 ID 关联 + text/JSON 双格式)
│   │   └── main.py              # FastAPI 入口(lifespan:预热 / 对账 / 就绪探针)
│   ├── migrations/001_baseline.sql  # 迁移基线(stamp version 1,未来增量迁移按序应用)
│   ├── mcp_server/              # 知识库 MCP Server(stdio,四个只读工具;见其 README)
│   ├── eval/                    # RAG 测评框架(gold 用例 + 检索指标 + LLM-as-judge + 历次报告)
│   ├── scripts/rebuild_chroma.py # 向量索引零成本重建(SQLite chunks + embedding 缓存 → Chroma)
│   ├── clean_user_data.py        # 跨 SQLite/Chroma/Neo4j/BM25 清理单个用户数据(破坏性,需确认)
│   └── Dockerfile                # 从仓库根构建:docker build -f backend/Dockerfile .
├── requirements.txt              # Python 依赖清单(backend 与脚本共用,CI 也从根目录安装)
├── docker-compose.yml            # Neo4j + ChromaDB 服务定义
├── frontend/                     # Vue 3 + d3 前端(学术雅致派双主题)
│   ├── src/
│   │   ├── components/
│   │   │   ├── GraphPanel.vue   # 图谱核心面板(d3 力导向)
│   │   │   ├── layout/          # Layout / Sidebar
│   │   │   ├── decor/           # 装饰层(Decor / shapes)
│   │   │   └── ui/              # 通用 UI(Button/Card/Tag/Stat/Switch/...)
│   │   ├── views/
│   │   │   ├── Home.vue                  # 登录 / 注册
│   │   │   ├── DocumentsPage.vue         # 文档列表 + 上传
│   │   │   ├── DocumentDetailPage.vue    # 文档详情(标签 / 实体 / 关联)
│   │   │   ├── ClusterMapPage.vue        # 2D PCA 聚类地图
│   │   │   ├── GraphPage.vue             # 图谱主页(搜索 / 可视化)
│   │   │   ├── EntityDetailPage.vue      # 实体详情页
│   │   │   ├── EntityTimelineAnimationPage.vue  # 实体时间线动画
│   │   │   ├── DashboardPage.vue         # 仪表盘
│   │   │   ├── TimelinePage.vue          # 时间线
│   │   │   ├── ChatPage.vue              # RAG 对话
│   │   │   └── SearchPage.vue            # 语义检索
│   │   ├── composables/
│   │   │   └── useTheme.js       # 亮/暗主题切换 + localStorage 持久化
│   │   ├── api/                  # axios 客户端封装(auth/documents/chat/graph/...)
│   │   ├── router/index.js       # 路由 + 鉴权守卫
│   │   ├── store/auth.js         # Pinia auth store
│   │   ├── utils/                # categorize / timelineAnim / SSE 流式解析 工具
│   │   ├── styles/variables.css  # 学术雅致派设计令牌(墨蓝+琥珀双主题)
│   │   ├── App.vue
│   │   └── main.js
│   ├── index.html
│   ├── package.json
│   └── vite.config.js
├── scripts/                      # 根级脚本
│   ├── run-backend.mjs           # npm run backend 调用(跨平台启动 uvicorn)
│   └── backup.sh                 # 全量备份(SQLite 在线 .backup + Neo4j/Chroma/uploads)
└── data/                         # 数据目录 (gitignore)
    ├── sqlite/                  # SQLite 数据库
    ├── uploads/                 # 上传的原文件
    ├── logs/                    # 应用日志(RotatingFileHandler)
    ├── neo4j/                   # Neo4j 数据卷
    └── chromadb/                # ChromaDB 数据卷

🚀 快速开始

1. 🐳 启动基础设施服务

docker-compose up -d

这将启动:

2. 🔑 配置环境变量(从模板复制,不要直接编辑 .env.example)

# 根目录(用于 IDE / 工具)
cp .env.example .env

# 后端实际加载的 .env
cd backend
cp .env.example .env

必填项:

  • SILICON_FLOW_API_KEY — 硅基流动 API 密钥(用于 Embedding + Rerank + 备用 LLM)
  • BAILIAN_API_KEY — 阿里云百炼 API 密钥(用于 LLM)
  • KIMI_API_KEY — Moonshot Kimi API 密钥(备用 LLM)
  • JWT_SECRET — 生产环境必须用 python -c "import secrets; print(secrets.token_urlsafe(48))" 生成

可选调整:

  • CORS_ALLOWED_ORIGINS — 逗号分隔的允许来源(默认 http://localhost:5173)
  • APP_ENV — development(默认)或 production(生产环境会拒绝默认 JWT_SECRET 启动)
  • UPLOAD_DIR / SQLITE_PATH / LOG_DIR — 数据与日志目录
  • ENABLE_LLM_EXTRACTION / USE_RULE_EXTRACTION — 实体提取策略(默认纯 LLM 模式)
  • ENTITY_BATCH_SIZE — 实体提取批大小(默认 200)

3. 📦 安装后端依赖

# 使用仓库根 .venv(依赖清单在根 requirements.txt;Windows 为 Scripts/,mac/Linux 为 bin/)
python -m venv .venv
.venv/Scripts/python.exe -m pip install -r requirements.txt

4. ▶️ 运行后端服务

cd backend
../.venv/Scripts/python.exe -m uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload

或使用一键启动脚本(Windows / Bash 双版本 / 根级 npm 脚本):

# Windows
start-dev.bat

# Bash (Git Bash / WSL)
./start-dev.sh

# 或:根目录 npm 脚本(需要先 npm install)
npm run dev          # concurrently 同时启动前后端
npm run backend      # 仅后端
npm run frontend     # 仅前端

API 文档地址:http://localhost:8001/docs 健康检查: http://localhost:8001/health

5. 🎨 安装并运行前端

cd frontend
npm install
npm run dev

前端访问地址:http://localhost:5173 Vite 已配置 /api 代理到 http://localhost:8001。

🔌 API 接口设计

所有需要鉴权的接口都要求 Authorization: Bearer <jwt> header。SSE 进度接口优先使用 header 鉴权;但由于原生 EventSource 客户端无法设置自定义 header,作为兼容回退也接受 ?token= 查询参数。⚠️ 查询参数形式会把 token 泄露进反向代理访问日志与浏览器历史,请将此类 URL 视为敏感信息(前端 EventSource 即使用此回退方式)。

🔐 认证 /api/auth

  • POST /api/auth/register — 用户注册(用户名 3-50 字符 [A-Za-z0-9_.-]、密码 ≥ 8 字符且必须含字母+数字)
  • POST /api/auth/login — 用户登录(OAuth2 form-data,返回 JWT)
  • GET /api/auth/me — 获取当前用户信息

📄 文档 /api/documents

  • POST /api/documents/upload — 上传文档(multipart/form-data,支持 .pdf/.docx/.doc/.txt/.md/.markdown,≤ 10MB)
  • POST /api/documents/ingest-url — 抓取网页入库(FEAT-019:SSRF 防护链逐跳复检;html→readability+html2text,pdf/txt 落盘复用 anydoc;html 源 file_path=NULL 不可 reprocess;限流 10 次/分)
  • POST /api/documents/ingest-text — 粘贴文本入库(FEAT-022:Markdown/纯文本经 clean_markdown 落盘 {doc_id}.md,file_path 非空可 reprocess;TEXT_INGEST_MAX_CHARS 上限 422;限流独立 10 次/分)
  • GET /api/documents — 列出用户文档(分页:?skip=0&limit=100;可选 ?tag=xxx 过滤)
  • GET /api/documents/{id}/detail — 文档详情(metadata + 标签 + 切块统计 + 关键实体 + 关联文档)
  • GET /api/documents/{id}/chunks — 文档切块列表
  • GET /api/documents/cluster-map — 2D PCA 聚类地图(所有文档的语义投影)
  • DELETE /api/documents/{id} — 删除文档(联动清理 SQLite + ChromaDB + Neo4j)
  • POST /api/documents/{id}/reprocess — 重新处理失败文档(FEAT-017:仅 failed 状态可重试;复位 pending、清旧进度事件防 SSE 重放旧失败、派发与上传一致的摄取管线;202 返回)
  • GET /api/documents/{id}/tags — 文档标签列表
  • POST /api/documents/{id}/tags — 添加文档标签(幂等,返回最新标签列表)
  • DELETE /api/documents/{id}/tags/{tag:path} — 移除文档标签(返回最新标签列表)

🔍 检索 /api/search

  • POST /api/search — 语义检索(向量 + BM25 + Rerank + 图谱关联;可选 use_graph_rag 显式开图谱通道)
  • POST /api/search/debug — 检索管线调试(FEAT-024:绕过缓存跑全价管线,返回改写/通道召回/RRF/重排/扩展各阶段快照 + 耗时;页面 /search/debug)

🕸️ 图谱 /api/graph

  • GET /api/graph/entities?query=xxx — 实体名称模糊搜索
  • POST /api/graph/query — 语义图谱查询(按 query 在向量空间找相关 chunk,再展开图谱)
  • GET /api/graph/visualization — 获取当前用户的全量图谱可视化数据
  • GET /api/graph/entities/{name:path}/detail — 实体详情(实体 + 统计 + 文档 + 关联实体 + 示例 chunk)
  • PATCH /api/graph/entities/{name:path} — 更新实体的类型 / 描述
  • DELETE /api/graph/entities/{name:path} — 删除实体(清理 MENTIONS / RELATES_TO)
  • POST /api/graph/entities/merge — 合并实体({source, target},source 被删除并重新指向 target;自动把 source 记为 target 的别名,防旧名复活分裂)
  • GET /api/graph/entities/duplicates — 疑似重复实体建议(FEAT-025:case/punct 变体分组,仅建议;页面 /graph/duplicates)
  • GET /api/graph/entities/{name:path}/aliases — 列出实体吸收的别名
  • POST /api/graph/aliases/delete — 解绑别名({alias};不影响已合并的图)

💬 对话 /api/chat

  • POST /api/chat — 发送消息(非流式 RAG 问答,支持 use_graph_rag / compare_mode / enable_thinking;意图路由自动判定是否检索)
  • POST /api/chat/stream — 发送消息(Server-Sent Events 流式)

流式帧顺序:event: sources(参考来源)→ event: thinking(仅当 enable_thinking=true,模型推理过程,可折叠)→ 默认 data: 帧(回答正文 chunk)→ event: done(终止)。 深度思考模式:enable_thinking 透传 Qwen 混合思考参数,模型先流式输出 reasoning_content 再输出正文;对不支持该参数的模型层,HTTP 400 时自动去参重试一次。首字计时锚定到正文首 token,非推理首 token。

  • GET /api/chat/conversations — 获取对话列表
  • GET /api/chat/conversations/{id}/messages — 获取对话历史
  • DELETE /api/chat/conversations/{id} — 删除对话
  • POST /api/chat/messages/{id}/feedback — 提交消息反馈({rating, note?})
  • GET /api/chat/messages/{id}/feedback — 获取消息反馈
  • DELETE /api/chat/messages/{id}/feedback — 删除消息反馈

🏷️ 标签 /api/tags

  • GET /api/tags?q=xxx — 用户级标签聚合(按使用频次倒排,可选模糊搜索)

📏 评测用例 /api/eval(FEAT-018 / FEAT-021)

  • GET /api/eval/cases — 列出当前用户的评测用例(可选 ?enabled=true/false)
  • POST /api/eval/cases — 手动新建用例(query 必填,纯空白 422)
  • POST /api/eval/cases/from-message — 把 assistant 消息转为用例(query=前置 user 提问、chunk_ids=message_sources 按 rank;部分唯一索引幂等,重复转换返回 200 + created:false;409=无前置提问)
  • PATCH /api/eval/cases/{id} — 部分更新(query/expected_*/difficulty/tags/enabled)
  • DELETE /api/eval/cases/{id} — 删除用例
  • GET /api/eval/runs — 评测运行历史(FEAT-021:eval.runner --save [--label xxx] 写入 eval_runs,聚合 summary JSON;只读,?limit=50)

runner 侧:python -m eval.runner --user-id 1 默认合并 eval_cases(db 用例在同 query 上优先,被弃文件用例计入 skipped_duplicate_file_cases);--no-db 关闭合并。

🕒 时间线 /api/timeline

  • GET /api/timeline — 文档月度分布 + 近期文档 + 实体首现时间线

📊 仪表盘 /api/dashboard

  • GET /api/dashboard/summary — 仪表盘汇总(统计 + 近期活动 + 热门实体 + 热门标签 + 月度增长)

⏳ 进度 /api/progress

  • GET /api/progress/{doc_id} — SSE 流式进度事件(30 秒 keepalive,完成/错误自动关闭)
  • GET /api/progress/{doc_id}/history — 历史进度事件列表

🔌 MCP Server(stdio,非 HTTP)

标准 Model Context Protocol 服务,任何 MCP 客户端可直接挂载(配置示例见 backend/mcp_server/README.md):

工具 签名 说明
search_knowledge (query, user_id, top_k=5) 混合检索,返回 chunk 预览与来源标题
search_graph (entity_or_query, user_id, depth=1) 实体名搜索 + 1–3 跳邻域展开
get_entity_detail (name, user_id) 实体详情(统计 / 文档 / 关联实体 / 样例 chunk)
list_documents (user_id,) 文档清单(状态 + 时间戳)

全部只读、按 user_id 显式隔离(MCP 层无 JWT 会话,调用时显式声明读谁的库)。

接入步骤:

  1. 生成 token(缺失或占位符时进程拒绝启动,exit 2):

    python -c "import secrets; print(secrets.token_urlsafe(32))"
  2. 在客户端注册(三选一;命令/路径按需调整):

    Claude Code(推荐,token 存 local 作用域不进 git):

    claude mcp add nc-knowledge-base --env KG_MCP_TOKEN=<your-token> -- D:/NC/.venv/Scripts/python.exe D:/NC/backend/mcp_server/server.py

    Claude Desktop(claude_desktop_config.json):

    {
      "mcpServers": {
        "nc-knowledge-base": {
          "command": "D:/NC/.venv/Scripts/python.exe",
          "args": ["D:/NC/backend/mcp_server/server.py"],
          "env": { "KG_MCP_TOKEN": "<your-token>" }
        }
      }
    }

    codex(~/.codex/config.toml):

    [mcp_servers.nc-knowledge-base]
    command = "D:/NC/.venv/Scripts/python.exe"
    args = ["D:/NC/backend/mcp_server/server.py"]
    env = { "KG_MCP_TOKEN" = "<your-token>" }
  3. 使用前提:Neo4j + Chroma 容器运行中(检索与图谱工具需要);SQLite 数据文件已由主服务初始化(list_documents 只依赖它)。无需手动启动 server——客户端会以 stdio 子进程方式拉起它,也无需设 PYTHONPATH(server.py 自行定位 backend 根)。

💓 健康检查

  • GET /health — 存活探针(liveness),返回 {"status": "healthy"}
  • GET /health/ready — 就绪探针(readiness),逐个 ping SQLite / ChromaDB / Neo4j,任一核心存储不可用返回 503
  • GET / — 返回 API 元信息

🌊 数据流架构

📤 文档上传流程

PDF/Word/TXT/MD → anydoc → Markdown → 层级解析 → 语义切块
    → 硅基流动 Embedding (Qwen3-Embedding-8B) → ChromaDB 存储
    → Neo4j 实体关系提取 (BM25 索引同步) → SSE 进度推送

转换失败(损坏 / 加密 / 超资源上限 / 扫描件无文本层)一律安全降级为空串 → 上传接口返回 400,且不残留上传文件;排队等待摄取闸的文档保持 pending 不发进度。

🔍 检索对话流程

用户 Query
  ├─ 意图分类 (LLM, ENABLE_INTENT_ROUTING)
  │     ├─ chitchat / should_reject → 跳过检索,模板直接应答
  │     └─ fact_retrieval (默认 / 失败回退) → 进入统一检索管线 retrieve()
  │
  └─ retrieve()(services/retriever.py,结果按 TTL+LRU 缓存)
        1. 并行 LLM 预处理:会话改写 + 多查询变体(+ 图谱实体抽取)
        2. 并行 Embedding(逐文本缓存)
        3. 逐查询 向量 + BM25 召回(multi-query)
        4. 图谱通道作为独立 RRF 列表(GRAPH_RAG_MODE: auto / on / off)
        5. 多列表 RRF 融合(图谱权重 GRAPH_RRF_WEIGHT 可配)
        6. Qwen3-Reranker → seed chunks
        7. 扩展:前后邻居 (Chroma) + 父文档同节兄弟 (SQLite),去重
        8. 扩展集再重排(按 relevance 排序,不驱逐邻居)
        9. 实体 / 关系富化 (Neo4j)
        → 构建 Prompt(提示词模板 app/prompts/templates/ + <context> 注入预算熔断)→ Kimi / 百炼 LLM → 流式返回结果
        ├─ enable_thinking=true:  先流式 reasoning_content (event: thinking)
        └─ 默认:                  直接流式正文 (data: chunk)

🧬 核心数据模型

📦 Chunk 数据结构

{
    "chunk_id": "uuid",
    "document_id": "doc_uuid",
    "user_id": "user_uuid",
    "content": "文本内容",
    "hierarchy": {
        "level": 2,                    # 标题层级
        "path": ["标题1", "标题1.1"],    # 层级路径
        "parent_id": "parent_chunk_id"
    },
    "position": {
        "start_line": 10,
        "end_line": 25,
        "prev_chunk_id": "uuid",       # 前一块(用于上下文召回)
        "next_chunk_id": "uuid"        # 后一块
    }
}

🕸️ Neo4j 图谱数据模型

// 节点
(:User {user_id, username, password_hash, created_at})
(:Document {doc_id, title, user_id, file_path, created_at})
(:Chunk {chunk_id, content, embedding_id, user_id, position, hierarchy_path})
(:Entity {name, type, description, user_id})              // 从文本提取

// 关系
(:User)-[:OWNS]->(:Document)
(:Document)-[:CONTAINS]->(:Chunk)
(:Chunk)-[:NEXT]->(:Chunk)                               // 文档顺序
(:Chunk)-[:MENTIONS]->(:Entity)                          // 提及实体
(:Entity)-[:RELATES_TO {relation_type}]->(:Entity)       // 实体关系

🔐 安全配置

项 状态 备注
🔑 JWT 签名 必须替换 python -c "import secrets; print(secrets.token_urlsafe(48))"
🌐 CORS 来源 白名单 通过 CORS_ALLOWED_ORIGINS 配置,禁用通配符
🔒 密码哈希 bcrypt 72 字节硬截断;不 mutate 调用方入参
🗝️ API 密钥 环境变量 勿硬编码到代码;.env 已 gitignore
🚦 JWT 占位符 任意环境拦截 get_settings() 在任何环境(含 development)检测到公开占位符即 RuntimeError;APP_ENV 仅为环境标记
🚪 401 处理 拦截器去重 防重入 + 派发 auth:logout 事件
📡 进度 SSE Authorization header 优先 EventSource 回退接受 ?token=(会进代理日志,视为敏感 URL)
💾 嵌入缓存 JSON 序列化 取代 pickle(防反序列化漏洞);损坏 blob 自愈剔除
🚦 API 限流 中间件 app/auth/rate_limit.py 对认证接口限流,防暴力枚举
📦 请求体大小 全局兜底 RequestBodyLimitMiddleware 按 MAX_REQUEST_BODY 在消费 body 前拒绝超大请求(413);上传端点另按 MAX_FILE_SIZE 流式校验
🆔 请求追踪 correlation id RequestIDMiddleware 为每个请求打 X-Request-ID,回写响应头并注入日志
✅ 注册校验 强校验 用户名 [A-Za-z0-9_.-]、密码 ≥ 8 字符含字母+数字
👥 Neo4j 删除 跨用户隔离 delete_document step 4 强制 user_id 过滤
⚡ 批量写入 UNWIND 实体/关系/MENTIONS 由 N 次往返降为 1 次

⚙️ 配置说明

🛠️ 环境变量

变量名 说明 默认值 必填
APP_ENV 运行环境 development 否
NEO4J_URI Neo4j 连接地址 bolt://localhost:7687 否
NEO4J_USER Neo4j 用户名 neo4j 否
NEO4J_PASSWORD Neo4j 密码 12345678 否
CHROMA_HOST / CHROMA_PORT ChromaDB 主机端口 localhost / 8000 否
SQLITE_PATH SQLite 数据库路径 ./data/sqlite/app.db 否
SILICON_FLOW_API_KEY 硅基流动 API 密钥(Embedding + Rerank + 备用 LLM) - 是
SILICON_FLOW_API_KEYS 多密钥池(ADR-009):逗号分隔多把 SiliconFlow key,Embedding+Rerank 共用,least-inflight 分摊、429 冷却+秒级 failover;留空回退单 key 空 否
SILICON_FLOW_PER_KEY_CONCURRENCY 多密钥池每 key 在途请求上限 8 否
SILICON_FLOW_KEY_LEASE_TIMEOUT 等待可用 key 的最长秒数(也是 429 冷却上限) 20 否
SILICON_FLOW_BASE_URL 硅基流动 base URL https://api.siliconflow.cn/v1 否
KIMI_API_KEY Moonshot Kimi 密钥(备用 LLM) - 否
KIMI_BASE_URL Moonshot base URL https://api.moonshot.cn/v1 否
BAILIAN_API_KEY 阿里云百炼 LLM 密钥 - 是
BAILIAN_BASE_URL 百炼 base URL https://dashscope.aliyuncs.com/compatible-mode/v1 否
BAILIAN_MODEL 百炼模型 qwen3.7-flash 否
LLM_MODEL_KIMI Kimi 模型 kimi-k2-0905-preview 否
LLM_MODEL_SILICON 硅基流动模型 Qwen/Qwen3-8B-Instruct 否
JWT_SECRET JWT 签名密钥 占位符(生产必须替换) 是
JWT_ALGORITHM JWT 算法 HS256 否
ACCESS_TOKEN_EXPIRE_MINUTES Token 有效期(分钟) 60 否
UPLOAD_DIR 上传文件目录 ./data/uploads 否
MAX_FILE_SIZE 最大文件大小(字节) 10485760(10MB) 否
EMBEDDING_MODEL 嵌入模型名 Qwen/Qwen3-Embedding-8B 否
EMBEDDING_DIM 嵌入维度 1024 否
EMBED_BATCH_SIZE 每次 /embeddings 请求的输入条数(查询路径 ≤4 条单批;摄取按此分批) 32 否
EMBED_BATCH_DELAY_SECONDS 摄取路径多于一批时的批间间隔(秒) 0.3 否
RERANK_MODEL Rerank 模型 Qwen/Qwen3-Reranker-8B 否
CORS_ALLOWED_ORIGINS 允许的 CORS 来源(逗号分隔) localhost 开发地址 否
MAX_REQUEST_BODY 全局请求体大小上限(字节) 15728640(15MB) 否
URL_ALLOWED_SCHEMES URL 摄取允许的协议(逗号分隔,FEAT-019) http,https 否
URL_FETCH_TIMEOUT_SECONDS / URL_FETCH_CONNECT_TIMEOUT URL 抓取总/连接超时(秒) 30 / 10 否
URL_FETCH_MAX_BYTES URL 抓取响应体上限(字节,超限流式中断) 5242880(5MB) 否
URL_FETCH_MAX_REDIRECTS URL 手动重定向跳数上限(逐跳复检 SSRF) 5 否
URL_FETCH_USER_AGENT URL 抓取 User-Agent NC-KG/1.0 (knowledge-ingest) 否
ENABLE_INTENT_ROUTING 启用查询意图路由(闲聊/拒答绕过检索) True 否
INTENT_CLASSIFY_TIMEOUT 意图分类超时(秒) 3.0 否
GRAPH_RAG_MODE 图谱 RAG 模式(auto/on/off) auto 否
MULTI_QUERY_NUM_VARIANTS 多查询变体数 3 否
QUERY_REWRITE_MIN_LEN 触发查询改写的最短长度(字符) 20 否
RERANK_RECALL_K 每路召回送入 RRF/重排的候选数 25 否
GRAPH_RRF_WEIGHT 图谱通道 RRF 权重 1.0 否
ENABLE_EXPANSION_RERERANK 扩展集再重排 True 否
RETRIEVAL_CACHE_TTL 检索结果缓存 TTL(秒) 300 否
BM25_PREWARM 启动预热 per-user BM25 索引 True 否
PARENT_SECTION_MAX_CHARS 父文档扩展最大字符数 2000 否
PARENT_SECTION_SIBLING_LIMIT 父文档扩展兄弟块上限 4 否
CONVERSATIONAL_REWRITE_HISTORY_TURNS 会话改写参考的历史轮数 4 否
CHUNK_OVERLAP 切块重叠字符数(仅影响新上传) 50 否
ENABLE_LLM_EXTRACTION 启用 LLM 实体提取 True 否
USE_RULE_EXTRACTION 同时使用规则提取 False 否
ENTITY_BATCH_SIZE 实体提取批大小 200 否
ENTITY_EXTRACTION_DELAY 实体提取批间延迟(秒) 0 否
LLM_EXTRACTION_CONCURRENCY 实体提取并发上限 20 否
LLM_EXTRACT_MAX_TOKENS 实体提取 max_tokens 1024 否
RAG_MAX_TOKENS RAG 回答 max_tokens 4000 否
CONTEXT_BUDGET_ENABLED 上下文注入预算熔断开关 True 否
CONTEXT_BUDGET_TOKENS <context> 区 token 硬上限 8000 否
HISTORY_COMPACT_ENABLED 会话历史自动折叠开关 True 否
HISTORY_WINDOW_LIMIT 发给模型的近期消息窗口条数 10 否
HISTORY_COMPACT_THRESHOLD 触发折叠的消息数阈值 24 否
HISTORY_KEEP_RECENT 折叠时保留的最近原始消息数 6 否
DOC_INGEST_CONCURRENCY 同时运行的文档摄取流水线数(超限上传排队,保持 pending,≥1) 2 否
QUERY_CONCURRENCY 查询并发闸(ADR-009):同时运行的完整检索数,超出排队;缓存命中不占名额 4 否
QUERY_MAX_QUEUE_SECONDS 查询排队上限秒数,超时拒绝(/api/search → 429+Retry-After;聊天流 → 终端 error 事件) 5.0 否
PROGRESS_POLL_SECONDS 进度 SSE 轮询 SQLite 间隔(秒;越低越跟手、轮询越多) 1.0 否
KG_MCP_TOKEN MCP Server 访问令牌(仅 mcp_server 进程) - MCP 必填
LOG_DIR / LOG_LEVEL 日志目录与级别 ./data/logs / INFO 否
LOG_FORMAT 日志格式:text / json;留空按 APP_ENV 自动(development=text / production=json) 空(自动) 否

🧭 前端路由总览

路径 页面 说明
/login Home.vue 登录 / 注册
/documents DocumentsPage.vue 文档列表与上传
/documents/:id DocumentDetailPage.vue 文档详情
/documents/map ClusterMapPage.vue 2D PCA 聚类地图
/graph GraphPage.vue 图谱主页(搜索 + 可视化)
/graph/timeline-animation EntityTimelineAnimationPage.vue 实体时间线动画
/entities/:name EntityDetailPage.vue 实体详情
/dashboard DashboardPage.vue 仪表盘
/timeline TimelinePage.vue 时间线
/chat ChatPage.vue RAG 对话(含导出 Markdown,FEAT-023)
/chat/history ConversationHistoryPage.vue 历史会话管理
/search SearchPage.vue 语义检索
/eval EvalCasesPage.vue 评测用例管理(FEAT-018)
/eval/runs EvalRunsPage.vue 评测报告趋势(FEAT-021)

除 /login 外所有路由均需要登录,由 router/index.js 的 beforeEach 守卫统一拦截。

🛠️ 开发指南

📋 环境要求

  • Python 3.11+
  • Node.js 18+
  • Docker & Docker Compose

🧪 测试

# 全量测试(无需真实密钥,conftest 注入临时 JWT_SECRET 与隔离 SQLite)
cd backend
../.venv/Scripts/python.exe -m pytest tests/ -q

# RAG 测评(军规②:提示词改动的回归门禁;需要 Docker 服务 + API key)
../.venv/Scripts/python.exe -m eval.runner --user-id 1 --markdown

单元测试无需真实密钥:conftest.py 会 setdefault 一个临时 JWT_SECRET(CI 中也显式注入),JWT 占位符拦截不会阻断测试。

✅ 持续集成(CI)

.github/workflows/ci.yml 在 push 到 main/master 及 PR 时自动运行 backend 的 pytest tests/ -q(Python 3.11)。无需真实密钥——JWT_SECRET 与 APP_ENV=test 由 workflow 注入。

🗃️ 数据库迁移

表结构由 database.init_db 的 CREATE TABLE IF NOT EXISTS 创建;其后 _run_migrations 按 backend/migrations/NNN_*.sql 文件名序号顺序应用,进度记录在 schema_version 表。001_baseline.sql 仅 stamp 版本 1(无结构变更),新增增量迁移直接加 002_*.sql 即可。

🗄️ 备份

# 全量备份(SQLite 在线 .backup + Neo4j/Chroma/uploads 打包)
# 默认数据根是 backend/data(应用的所有启动方式 CWD=backend,相对路径
# ./data/... 实际落在 backend/data/),备份产物在 backend/data/backups/
./scripts/backup.sh
# 自定义路径:DATA_DIR=/var/lib/kg OUT_DIR=/tmp ./scripts/backup.sh
# 注意:脚本会拒绝备份缺失或 0 字节的 SQLite 文件(历史上曾静默备份空库)

🧹 清理单个用户数据(运维,破坏性)

# 从仓库根或 backend/ 运行均可(SQLite 路径自动锚定到 backend/data)
cd backend
../.venv/Scripts/python.exe clean_user_data.py 3 --dry-run   # 预览
../.venv/Scripts/python.exe clean_user_data.py 3 --yes       # 真删
# 若应用正在运行,清理后需重启以清空内存中的检索/聚类缓存

♻️ 向量索引重建(运维)

当 Chroma 向量丢失(容器重建 / 误删 / 迁移)但 SQLite 的 chunks 表与 embedding_cache 仍在时,可零 API 成本回灌:

cd backend
../.venv/Scripts/python.exe scripts/rebuild_chroma.py
# 输出示例:
# [info] chunks with cached embedding: 54
# [info] upserted batch 1: 54/54
# [done] upserted 54 chunks; collection now holds 54 vectors

🐳 Docker 部署

# 构建并运行基础设施(Neo4j + ChromaDB;后端本身不在此 compose 内)
docker-compose up -d

# 单独构建后端镜像 —— 必须从仓库根构建(requirements.txt 在根、app 在 backend/)
docker build -f backend/Dockerfile -t kg-backend .
docker run -p 8001:8001 --env-file backend/.env kg-backend

多 worker 部署须知:进度 SSE 已改为从 SQLite progress_history 轮询(非进程内存队列), 因此跨 worker 正确、断线重连可回放。但上 uvicorn --workers N(需关闭 --reload)前要清楚 以下状态是每进程独立的:

  • BM25 索引在内存中——每个 worker 各自 prewarm(内存/启动 CPU ×N),且 add_to_index 只更新本进程索引:worker B 上可能搜不到刚在 worker A 上传文档的 BM25 命中(向量通道不受 影响)。跨 worker 新鲜度需另行解决(如按 corpus 版本校验重建),本系统默认单进程运行。
  • 检索结果缓存 / 聚类缓存每进程各一份(重复占内存,失效不互通);embedding Semaphore(5) 每进程一份 → 最多 5N 并发 embed 请求,留意 provider RPM;auth 限流计数每进程。
  • 生产多进程:
    uvicorn app.main:app --host 0.0.0.0 --port 8001 --workers 2

📦 关键依赖版本

# backend
fastapi==0.115.0
uvicorn==0.32.0
python-jose[cryptography]==3.3.0
bcrypt==4.1.3
python-multipart==0.0.17
neo4j==6.1.0
chromadb==0.4.18
numpy==1.26.4
httpx==0.28.1
starlette==0.38.6
pydantic==2.13.4
pydantic-settings==2.11.0
mcp==1.29.0
firecrawl-anydoc==0.2.4
readability-lxml==0.9
html2text==2025.4.15
jieba==0.42.1
rank-bm25==0.2.2
python-dotenv==1.0.0
aiosqlite==0.20.0

# dev / test only
pytest==8.3.3
pytest-asyncio==0.24.0

注意:httpx / starlette / pydantic / pydantic-settings 与 mcp 是一组联动版本——mcp 2.x 会拖入 starlette≥1.0 导致 fastapi 0.115 启动即崩(on_startup 参数被移除),因此锁死 mcp 1.x 并整组固定。升级 fastapi 时必须连带重验这一组。

# frontend
vue ^3.5.24
vue-router ^4.6.3
pinia ^2.3.1
d3 ^7.9.0
axios ^1.7.9
@tanstack/vue-virtual ^3.13.28
vite ^7.2.4

注意:原 passlib[bcrypt]==1.7.4 已移除,改为原生 bcrypt==4.1.3(passlib 与新版 bcrypt 存在兼容问题;且 chromadb 0.4.18 依赖 bcrypt>=4.0.1,钉 3.x 会让全新环境的 pip 解析直接失败——CI 曾因此一直红)。app/auth/security.py 在哈希前主动截断 72 字节、验证时捕获 ValueError,兼容 4.x 行为;存量 $2b$ 哈希可直接验证。

注意:原 markitdown==0.0.1a3 已移除,换为 firecrawl-anydoc==0.2.4(markitdown 对部分 docx 会静默倾倒 zip 内原始 OOXML 污染下游切块/抽取、异常继承 BaseException 导致 except Exception 接不住、对恶意构造文件无资源上限会挂死转换线程;anydoc 为 Rust 实现、零传递依赖,错误全部继承 Exception 且内置资源限制,典型文档转换耗时从数百 ms 降至个位数 ms)。转换失败契约不变:降级空串 → 上传 400(见注意事项 7)。

📌 注意事项

  1. 🗝️ API 密钥保护:.env 中的密钥若已泄露,立即在控制台轮换
  2. 🔑 JWT_SECRET:任意环境检测到公开占位符即拒绝启动(不限于 production);用 python -c "import secrets; print(secrets.token_urlsafe(48))" 生成
  3. ⚡ 硅基流动限速:嵌入服务实现了批量处理、异步队列和并发控制(Semaphore=5)
  4. 🧮 NumPy 版本:必须使用 NumPy 1.x(<2.0)以保证 ChromaDB 兼容性
  5. 🧬 ChromaDB 版本:客户端和服务端必须都使用 0.4.18 版本
  6. 🐳 Docker 内存:Neo4j 需要充足内存,建议 4GB+
  7. 🛡️ 转换降级:utils/md_parser.py 转换失败(损坏/加密/超限)一律返回空串 → 上传接口回 400 而非 500;扫描版 PDF 无文本层同样返回空串,OCR 需另接服务
  8. 🧩 Neo4j APOC:docker-compose 启用了 APOC 插件,UNWIND 批量写入依赖其函数
  9. ⚠️ 实体合并:POST /api/graph/entities/merge 会硬删 source 并将所有引用指向 target,操作不可逆
  10. 🔧 Chroma entrypoint 绕过:docker-compose.yml 覆盖了 chromadb 0.4.18 镜像 entrypoint——原 entrypoint 每次启动 pip install --force-reinstall chroma-hnswlib,新版会拉入 numpy 2.x 导致 np.float_ 崩溃。改为直接跑 uvicorn,沿用镜像内可用的 hnswlib
  11. ♻️ 向量索引重建:若 Chroma 向量丢失(容器重建/误删/迁移),运行 backend/scripts/rebuild_chroma.py 可从 SQLite chunks + embedding_cache(md5-keyed)零 API 成本回灌,复用 get_chroma_client 保证集合名/cosine/upsert 与摄入路径一致
  12. 🔌 MCP 依赖联动:mcp / httpx / starlette / pydantic 四个版本必须整组升级——单独升 mcp 到 2.x 会拉崩 fastapi(见关键依赖版本的注意说明)
  13. 📏 提示词纪律:所有 LLM 提示词集中在 backend/app/prompts/templates/;改提示词必须重跑 RAG 测评(python -m eval.runner --user-id 1)作为回归门禁,缺模板会在启动时 fail-fast

📜 许可证

MIT

About

一个支持多用户的知识图谱系统,整合 Neo4j 图数据库和 ChromaDB 向量数据库,实现文档知识管理、可视化检索和 RAG 智能问答。

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages