# nova-retrieve
**Repository Path**: liukai_1900/nova-retrieve
## Basic Information
- **Project Name**: nova-retrieve
- **Description**: Nova Retrieve — Enterprise Agentic RAG
- **Primary Language**: Python
- **License**: Not specified
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-05-19
- **Last Updated**: 2026-06-01
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Nova Retrieve — 企业级 Agentic RAG
基于 **LangChain + LangGraph + Qdrant + BGE-M3** 的可生产部署 Agentic RAG 框架。
> 英文版:[README.md](README.md)
## 特性
- **LangGraph 状态机驱动**:查询改写 → 路由 → 检索 → 文档评分 → 生成 → 幻觉检测 → 答案有用性评估,全部带回退路径。
- **CRAG / Self-RAG 思路**:检索不够时自动改写重试;生成有幻觉时自动重写;多轮失败兜底走 Web 搜索。
- **本地向量化(BGE-M3)**:通过 sentence-transformers 加载本地模型目录,数据不出域。设 `EMBEDDING_LOCAL_PATH` 即可。
- **Qdrant 向量库**:Docker 一键起,自动建集合。
- **OpenAI 兼容 LLM**:可对接 DeepSeek / 通义 / 智谱 / 任何兼容 endpoint。
- **Tavily Web 兜底**:知识库召回不足时切换实时网络搜索。
- **FastAPI + SSE 流式**:节点级事件流,前端可实时展示 agent 推理轨迹。
- **内置 Web UI**:零构建的单页前端(`/ui/`),实时显示每个 agent 步骤、耗时、引用来源。
- **MCP server**:另以 MCP 工具(`rag_search` / `rag_answer` / `rag_collections`)暴露整套能力,支持 stdio 与 streamable-http,**Hermes** 等 agent 可原生调用本知识库。
## 架构
```
┌──────────────┐
│ rewrite_query│
└──────┬───────┘
▼
┌──────────────┐
│route_question│
└──┬────────┬──┘
vectorstore│ │web_search
▼ ▼
┌──────┐ ┌─────────┐
│retrieve │web_search│
└──┬───┘ └────┬────┘
▼ │
┌──────────────┐ │
│grade_documents│ │
└──┬───────┬────┘ │
relevant│ none │ │
▼ ▼ │
┌────────┐ transform │
│generate│◄──query──┐ │
└───┬────┘ │ │
▼ retry│ │
┌────────────────┐ │ │
│hallucination_ │──no──┘ │
│grader (CRAG) │ │
└───┬────────────┘ │
▼ yes │
┌────────────┐ │
│answer_grader│──no→transform_query
└───┬────────┘
▼ useful
END
```
## 快速开始
### 1. 起 Qdrant
```bash
docker compose up -d qdrant
```
### 2. 装依赖
```bash
python -m venv .venv && source .venv/bin/activate
pip install -e .
```
把已下载的 BGE-M3 目录路径填入 `.env` 的 `EMBEDDING_LOCAL_PATH`(应包含 `config.json` / `tokenizer.json` / `model.safetensors` 等)。若留空则首次运行从 HuggingFace 下载。
### 3. 配置
```bash
cp .env.example .env
# 编辑 LLM_BASE_URL / LLM_API_KEY / TAVILY_API_KEY
```
### 4. 灌库
```bash
python -m scripts.ingest_docs ./data/docs
```
### 5. 启服务
```bash
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
打开浏览器访问 即进入 Web UI(自动重定向到 `/ui/`)。
或交互式 CLI:
```bash
python -m scripts.chat_cli
```
## API
| 端点 | 方法 | 说明 |
|---|---|---|
| `/health` | GET | 健康检查 |
| `/ingest` | POST | 摄入文件/目录 |
| `/chat` | POST | 阻塞式问答,返回完整答案 + 引用 |
| `/chat/stream` | POST | SSE 流:`step` 事件按节点推送,`answer` 事件返回最终结果 |
### 示例
```bash
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"question":"我们的退款政策是什么?"}'
```
SSE 流:
```bash
curl -N -X POST http://localhost:8000/chat/stream \
-H "Content-Type: application/json" \
-d '{"question":"GPT-5 发布了吗?"}'
```
## MCP server(对接 Hermes / 外部 agent)
除 HTTP API 外,整套 RAG 还以 **MCP server**(`app/mcp_server.py`)形式暴露,支持 MCP 的 agent(如 **Hermes**)可把它当原生工具调用。它是薄适配层:复用同一套 retriever 和 LangGraph 流程,不重复任何逻辑。
### 工具
| 工具 | 开销 | 说明 |
|---|---|---|
| `rag_search` | 便宜,不走 LLM | 语义检索,返回带来源与相似度分数的原始 chunk,由 agent 自己推理并引用。完全离线(本地 BGE-M3 + Qdrant)。 |
| `rag_answer` | 较重,自带 LLM 调用 | 跑完整 Agentic-RAG 流程,返回经幻觉评估的合成答案 + 来源列表。 |
| `rag_collections` | 便宜 | 列出 Qdrant 集合(知识库)及其点数。 |
### 传输方式
由 `RAG_MCP_TRANSPORT` 选择:
- **`stdio`**(默认):由 MCP 客户端拉起本进程,走 stdin/stdout。适合同机集成。
- **`http`**:常驻网络服务(streamable-http),地址 `http://:/mcp`。适合服务器间 / 离线机集成。
> stdio 下 **stdout 是 JSON-RPC 通道**:本服务把所有日志固定到 stderr,且从不调用 `setup_logging()`。工具内不要往 stdout 打印。
### 启动
```bash
pip install -e . # 装上 `mcp` 依赖与 `nova-mcp` 命令
# A) HTTP 服务 —— Hermes 在另一台机器时推荐
RAG_MCP_TRANSPORT=http RAG_MCP_WARMUP=1 nova-mcp
# → streamable-http 服务于 http://0.0.0.0:8765/mcp
# B) stdio —— 同机,客户端拉起进程
RAG_MCP_TRANSPORT=stdio nova-mcp # 或:python -m app.mcp_server
```
`RAG_MCP_WARMUP=1` 在启动时预加载 embedding 模型 + Qdrant 集合,让首个请求不再承担该开销 —— http 常驻服务建议开启。
### 接入 Hermes
HTTP 传输(RAG 网络可达):
```yaml
# Hermes cli-config.yaml
mcp_servers:
knowledge_base:
url: http://:8765/mcp
```
stdio 传输(RAG 与 Hermes 同机):
```yaml
mcp_servers:
knowledge_base:
command: nova-mcp
args: []
env:
RAG_MCP_TRANSPORT: stdio
EMBEDDING_LOCAL_PATH: /abs/path/to/bge-m3
QDRANT_URL: http://localhost:6333
LLM_BASE_URL: http:///v1
LLM_API_KEY:
```
> stdio 下 Hermes 会全新拉起进程,**不会**继承你的 shell 或 `.env`。RAG 需要的配置(embedding 路径、Qdrant 地址、LLM endpoint)都要在 `env:` 里显式传入。注意 `rag_search` 不需要 LLM,仅 `rag_answer` 会调用 `LLM_BASE_URL`。
### 配置项
| 环境变量 | 默认值 | 含义 |
|---|---|---|
| `RAG_MCP_TRANSPORT` | `stdio` | `stdio` 或 `http` |
| `RAG_MCP_HOST` | `0.0.0.0` | http 绑定地址 |
| `RAG_MCP_PORT` | `8765` | http 绑定端口 |
| `RAG_MCP_MAX_CHARS` | `1500` | `rag_search` 每个 chunk 正文截断上限(保护 agent 上下文窗口) |
| `RAG_MCP_WARMUP` | `0` | `1` = 启动即预加载 embedding/集合 |
### 排错 —— 弱本地模型死活不检索
**现象**:MCP server 连上了(`hermes mcp test ` 能列出工具),但 agent 凭自己的知识答,从不调 `rag_search` / `rag_answer`。
强模型很少出这问题;小本地模型(如 vLLM 跑的 **llama-3.1-8B**)几乎必然出 —— 病根在*工具面*,不在模型。按这个顺序排查:
1. **模型到底能不能发 tool call?** 直接 `curl` LLM endpoint,带一个假工具 + `"tool_choice":"auto"`,确认 `choices[0].message.tool_calls` 有值。vLLM 跑 Llama 3.1 必须带 `--enable-auto-tool-choice --tool-call-parser llama3_json`,否则任何带 tools 的请求都 HTTP 400。
2. **工具真注册上了吗?** `hermes mcp test `、`/tools` 命令,或 grep `~/.hermes/logs/agent.log` 里的 `registered N tool(s)`。
3. **这次请求带了几个工具?** 看 verbose 日志的 `Tools: N` / `prompt_tokens` —— 25 个工具(约 1.2 万 token 的 schema)足以淹没 8B。
4. **它实际调的是哪个工具?** 调错工具 = 有干扰工具在抢。
我们在 llama-3.1-8B 上踩到的四个根因及修法(除注明外均在 `~/.hermes/config.yaml`):
| # | 根因 | 修法 |
|---|---|---|
| 1 | 默认 `hermes-cli` 预设带 25 个工具,淹没 8B | `platform_toolsets: { cli: [file, mcp-] }` —— 白名单**必须**含 `mcp-`,否则会把 RAG 工具一起静默丢掉 |
| 2 | 通用工具 `clarify` 抢跑 | `agent: { disabled_toolsets: [clarify] }` |
| 3 | 无常驻引导 —— Hermes 丢弃 MCP server 的 `instructions`,只把每个工具的 `description` 发给模型 | 加一条 `agent.personalities`,里面点名 `mcp__rag_search`,并用 `/personality` 启用。`mcp_server.py` 里强化过的工具 docstring 也有用 —— 那是 Hermes 唯一会转发的逐工具通道。 |
| 4 | 8B 瞎编可选参数 `collection` → 查到不存在的 Qdrant 集合 → "无结果" | 服务端把 `rag_search`/`rag_answer` 收成单个必填参数(本仓库已做)。再砍掉 MCP 的 prompt/resource 元工具:`mcp_servers..tools: { prompts: false, resources: false, include: [rag_search, rag_answer, rag_collections] }` |
每改一处:先校验 YAML(`python3 -c "import yaml; yaml.safe_load(open('/root/.hermes/config.yaml'))"`),重启 Hermes,再用 `/verbose` 复查。健康状态:`Tools:` ≈ 7,第一个 `Tool call:` 是 `mcp__rag_search` / `rag_answer` 且只带 `query` / `question` 参数,RAG 日志查的是**默认集合**(`QDRANT_COLLECTION`)。
> 收敛工具面、干掉干扰/元工具、把引导放到宿主真正会转发的地方(工具描述 + personality,而非 MCP `instructions`)、给工具最简签名。强模型能容忍杂乱工具面,弱本地模型不能。
#### 保留 Hermes 其它功能又不毁掉检索
上面那刀(`cli: [file, mcp-]`)是**调试动作** —— 把 Hermes 砍到只剩文件+RAG 是为了隔离过载问题,**并非**你想要的终态。两个机制要懂(`model_tools.py`):
- `platform_toolsets.` 是**工具集名白名单**。一旦写了,就只有列进去的 toolset 生效 —— MCP 工具是普通的 `mcp-` toolset,**不会自动挂上**,没列就静默消失。
- `agent.disabled_toolsets` **最后做减法**,连 `hermes-cli` 这种复合预设里的工具也能精准剔除 —— 只删 `clarify` 不伤其它的正道。
- 名字可组合:`[hermes-cli, mcp-]` = 完整预设 ∪ RAG 工具。
代价是一个**仅对弱模型成立**的真实取舍:完整预设 + RAG ≈ 28 个工具,8B 直接回到过载(不再调 RAG)。这是 8B 的能力天花板,不是配置 bug —— 换强编排模型就没有这个取舍。8B 约束下按优先级三选一:
| 方案 | `platform_toolsets.cli` | 结果 |
|---|---|---|
| A — 功能完整优先 | `[hermes-cli, mcp-]` | Hermes 不缺;RAG 检索**不稳**(8B 被淹),只能靠 personality 部分兜底 |
| B — 折中(推荐) | `[file, web, terminal, todo, mcp-]` | 保住日常工具 + RAG;约 12 个工具,8B 仍能较稳检索 |
| C — RAG 优先 | `[file, mcp-]` | RAG 最稳;Hermes 退化成只会读写文件 |
三者都保留 `agent.disabled_toolsets: [clarify]` 和引导 personality。推荐路径:从 B 出发,按你实际会用的 toolset 往回加(web/terminal/browser/skills/todo/tts/cronjob —— 见 `cli-config.yaml.example` 的 *Available toolsets*),每加一个就 `/verbose` 看 `Tools: N` —— 经验上 8B 在 ≈12 个工具内可靠,过 ≈20 就开始漏 RAG。
## 目录结构
```
app/
├── config.py # pydantic-settings 配置
├── main.py # FastAPI app + /ui 静态挂载
├── mcp_server.py # 给 Hermes 用的 MCP 适配层(stdio / streamable-http)
├── api/ # 路由与 schema
├── core/ # llm / embeddings / vectorstore / logging
├── ingest/ # loader / chunker / pipeline
├── retrieval/ # 检索器
└── agent/ # state / nodes / edges / prompts / graph / tools
web/ # 前端单页(无构建)
├── index.html
├── styles.css
└── app.js
scripts/
├── ingest_docs.py
└── chat_cli.py
```
## 可扩展点
- **Reranker**:在 `retrieval/` 加 BGE-Reranker 二阶段精排。
- **多租户**:`ChatRequest.collection` 已经留好按租户隔离的钩子。
- **缓存**:在 `route_question` / `grade_documents` 上加 Redis 缓存可显著降本。
- **观测**:设置 `LANGSMITH_API_KEY` 即可全链路追踪。