内容
# RAG 研究助理
> 您自己的图书馆的研究助理。引用答案,而不是自信的猜测。
上传研究论文(或粘贴 DOI),然后与它们聊天。每个答案都基于其来源的段落,并附有逐页引用。当图书馆没有答案时,助理会说明 - 只有在您明确同意的情况下,才会搜索网络。
该项目故意设计为一个单一的完整系统,而不是一个笔记本:一个 FastAPI 后端,一个 Vite + React + Tailwind 前端,以及一个 MCP 服务器,通过 Model Context Protocol 向 Claude Desktop 公开相同的管道。

## 有趣的特点
- **纠正式 RAG(Yan,2024 年)**:检索 → 等级 → 回答或拒绝 → 选择性网络回退。一个小型 LLM 分级器检查检索到的段落,并明确决定它们是否支持有根据的答案。如果不支持,则在运行任何网络搜索之前询问用户。
- **强化拒绝**:即使分级器通过,答案 LLM 也被指示发出单个 `INSUFFICIENT_CONTEXT` 令牌,而不是臆造。编排器捕获此令牌作为第二个安全网,并将其转换为冷静的拒绝消息 - 没有虚假来源,没有填充。
- **混合检索 + 神经重新排序**:BGE 双编码器嵌入(60%)与 BM25 关键词分数(40%)相结合,以实现广泛的召回,然后 CrossEncoder 将顶部候选重新排序以实现精度。
- **流式管道 UI**:前端从编排器渲染服务器发送事件 - 首先是活动阶段(`检索中 → 评估中 → 生成中`),然后是答案令牌到达时,以及引用。代理流程 *可见*,而不是隐藏在加载动画后面。
- **内联引用**:LLM 发出 `[1] [2] [3]` 标记,前端将其转换为上标脚注,并带有 hover-card 弹出窗口,显示源代码片段。
- **DOI ingestion**:粘贴 arXiv 或开放访问 DOI,后端通过 Unpaywall(或直接 arXiv 快速通道)解析它并摄取 PDF。付费论文会因论文标题而大声失败,用户知道尝试了什么。
- **MCP 服务器**:相同的管道通过 Model Context Protocol 公开,因此 Claude Desktop(或任何 MCP 客户端)可以 `list_libraries`、`create_library`、`search_library`、`ask_library`。
- **编辑 UI**:奶油 + 暖棕色 + 单个 oxblood 强调色。Lora 衬线用于标题,⌘K 调色板用于库切换,framer-motion 用于阶段转换和引用弹出窗口。
## 架构
```
PDF 或 DOI
│
▼
┌─────────────┐ BGE 嵌入 ┌──────────────┐
│ 摄取 │ ───────────────────▶ │ ChromaDB │
│ 分块器 │ │ (每库) │
└─────────────┘ BM25 分词 ──▶ │ + BM25 索引 │
└──────┬───────┘
│
查询 ──▶ 混合检索(0.6 语义 + 0.4 BM25)
│
CrossEncoder 重新排序
│
▼
┌────────────────┐
│ LLM 分级器 │ ←─ "足够?"
└────────┬───────┘
│
足够 ──────────┤──────── 不足够
│ │
▼ ▼
┌───────────┐ ┌──────────────────┐
│ 答案 │ │ 询问用户同意 │
│ 带有 │ └────────┬─────────┘
│ 引用 │ │ (是)
└───────────┘ ▼
┌─────────────┐
│ 重写查询 │
└──────┬──────┘
▼
┌──────────────┐
│ Tavily 网络 │
│ + 答案 LLM │
└──────────────┘
```
## 技术栈
**后端(Python 3.13,uv)**:FastAPI,ChromaDB,sentence-transformers(BGE 双编码器 + CrossEncoder),rank-bm25,langchain(仅实用导入 - `Document`、`RecursiveCharacterTextSplitter`、`PyPDFLoader`,Chroma 包装器),OpenAI 兼容客户端指向 Mistral/OpenAI/Groq/OpenRouter/Gemini。
**前端(Node 25,Vite)**:React + TypeScript,Tailwind,framer-motion,cmcmd(命令调色板),jsPDF(客户端聊天导出)。
**MCP 服务器**:官方 `mcp` Python SDK 通过 stdio。
**LLM**:默认为 Mistral(`mistral-small-latest` - 免费层,无需信用卡)。通过一个环境变量轻松切换到任何 OpenAI 兼容的提供商。
**网络搜索**:Tavily(每月 1000 次免费搜索)。
## 本地运行
```bash
# 1. 获取免费的 Mistral 密钥(无需信用卡):https://console.mistral.ai
# (可选)获取免费的 Tavily 密钥用于网络回退:https://tavily.com
# 2. 复制 env 模板,填写密钥
cp .env.example .env
$EDITOR .env
# 3. 一个命令同时运行后端 + 前端,带有前缀输出
./dev.sh
```
后端在 `http://localhost:8000` 提供服务。前端在 `http://localhost:5173` 提供服务。
Ctrl-C 清洁地终止两者。该脚本还会在上一次运行遗留的情况下释放 `:8000` 和 `:5173`。
### 分别运行各个部分
```bash
# 后端
cd backend && uv run uvicorn app.main:app --reload
# 前端
cd frontend && npm install && npm run dev
# MCP 服务器(单独运行,用于 Claude Desktop)
cd mcp_server && uv run python server.py
```
## 项目结构
```
RAG-research-assistant/
├── backend/ # FastAPI 服务
│ └── app/
│ ├── api/ # HTTP 处理器
│ │ ├── chat.py # POST /chat, POST /chat/stream (SSE)
│ │ ├── libraries.py # CRUD + 上传 + DOI 摄取
│ │ └── search.py # 原始检索,无 LLM
│ ├── services/ # 业务逻辑
│ │ ├── orchestrator.py # 纠正式 RAG 状态机
│ │ ├── grader.py # LLM 评估检索
│ │ ├── query_rewriter.py # 查询 → 网络搜索表单
│ │ ├── retriever.py # 混合搜索 + 重新排序
│ │ ├── reranker.py # BGE CrossEncoder
│ │ ├── ingest.py # PDF 分块,库 CRUD
│ │ ├── doi.py # arXiv + Unpaywall 解析器
│ │ ├── web_search.py # Tavily 客户端
│ │ └── llm.py # 提供商无关的 LLM 客户端
│ ├── core/
│ │ ├── config.py # pydantic 设置,读取根 .env
│ │ ├── chroma.py # 共享,锁定保护的 chromadb 客户端
│ │ └── embeddings.py # BGE 双编码器加载器
│ └── schemas/ # pydantic 请求/响应模型
│
├── frontend/ # Vite + React + Tailwind UI
│ └── src/
│ ├── App.tsx # 单文件应用:标题,⌘K 调色板,
│ │ # 流式聊天,文件选项卡等
│ └── index.css # 全局样式 + 奶油/棕色令牌
│
├── mcp_server/ # MCP 包装器后端
│ └── server.py # 四个工具,stdio 传输
│
├── dev.sh # 同时运行后端 + 前端
├── .env.example # 模板 - 复制到 .env
└── README.md
```
## MCP 集成(可选)
要将此插件插入 Claude Desktop,请在 `~/Library/Application Support/Claude/claude_desktop_config.json` 中添加:
```json
{
"mcpServers": {
"rag-research-assistant": {
"command": "uv",
"args": [
"--directory",
"/绝对/路径/到/RAG-research-assistant/mcp_server",
"run",
"python",
"server.py"
],
"env": { "BACKEND_URL": "http://localhost:8000" }
}
}
}
```
重启 Claude Desktop。你将看到四个工具:`list_libraries`、`create_library`、`search_library`、`ask_library`。`ask_library` 工具的描述指示 Claude 在设置 `allow_web_search=true` 之前询问用户同意,匹配纠正式 RAG 合同。
## 配置
所有配置均通过环境变量(参见 `.env.example`)。主要变量:
| 变量 | 默认值 | 备注 |
|---|---|---|
| `LLM_PROVIDER` | `mistral` | 任何 OpenAI 兼容的 API |
| `LLM_MODEL` | `mistral-small-latest` | 提供商特定的模型 ID |
| `MISTRAL_API_KEY` | — | 如果提供商是 `mistral`,则必填 |
| `TAVILY_API_KEY` | — | 可选;启用网络回退 |
| `UNPAYWALL_EMAIL` | 占位符 | 推荐用于 DOI 查找的真实电子邮件 |
## 路线图
- [ ] Dockerize + 将后端 + 前端部署到 Cloud Run,并带有速率限制
- [ ] README 中的演示 GIF
- [ ] 持久聊天历史(目前每个会话)
## 致谢
- 纠正式 RAG 想法改编自 Yan 等人,2024 年。
- BGE 嵌入和 CrossEncoder 来自 BAAI。
- Unpaywall API 由 Our Research(非营利组织)提供,用于开放访问 PDF 发现。
- arXiv API 用于 arXiv DOI 快速通道。
## 许可证
[MIT](LICENSE)