内容
# 开发工具快速启动器
最小但完整的本地 RAG 端到端栈:文档 → 向量 → 搜索,可通过 **MCP** 连接到任何代理。这里的 RAG 是一个**独立层**,而非特定代理的一部分:同一个服务器可以连接到 `qwen-code` 和 `Claude Code` 而无需修改。
```
离线 (cron / watch — 自动更新):
docs/ ─► Reader ─► Splitter ─► ollama(bge-m3) ─► Qdrant
│ │ ▲ ▲
└── docstore (哈希值按 doc_id) ─┘ 不变→跳过,
更改→更新,删除→删除
查询 (运行时,任意代理):
代理 ─MCP─► search(q) ─► ollama(bge-m3) ─► Qdrant top-k ─► 片段 ─► 代理
```
**角色:** `ollama` 仅运行嵌入模型(向量化,本地)· `Qdrant` 存储和搜索向量 · `MCP 服务器` — 代理的统一入口。响应生成由代理使用其自己的模型(Qwen / Claude)完成。
---
## 要求
- **Docker** (用于 Qdrant)
- **Python 3.10+**
- **[ollama](https://ollama.com)** 已安装并运行
## 快速开始
```bash
# 1. 启动 Qdrant
docker compose up -d
# 2. 下载嵌入模型(多语言,RU/EN/代码)
ollama pull bge-m3
# 3. Python 环境
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
# 4. 索引文档(已在 ./docs 中提供示例)
python ingest.py ./docs
# 5. 检查服务器是否启动(按 Ctrl+C 退出)
python mcp_server.py
```
准备就绪 — RAG 已启动。接下来只需连接代理(见下文)。
## 增量更新机制
`ingest.py` 将状态存储在 `pipeline_storage/` 中(已索引的文件 + 其哈希值)。在重新启动时:
| 文件状态 | 操作 |
|----------|------|
| 未更改 | 跳过(基于哈希值) |
| 已更改 | 重新嵌入并更新到 Qdrant |
| 从 `./docs` 中删除 | 从 Qdrant 中删除其分段 |
无需完全重新索引 — 只需增量更新即可。因此,可以每分钟运行一次。
### 自动更新
**Cron** (每 15 分钟):
```cron
*/15 * * * * cd /path/to/devitway-rag-starter && .venv/bin/python ingest.py ./docs >> ingest.log 2>&1
```
**Watch** (立即响应更改,需要 `inotify-tools`):
```bash
while inotifywait -r -e modify,create,delete ./docs; do
.venv/bin/python ingest.py ./docs
done
```
## 连接代理
两个代理可以连接到**同一个**服务器。在已运行的代理上测试,然后添加第二个代理 — 无需修改。
> ⚠️ 在 `command` 中指定 **.venv 中 Python 的绝对路径** — 代理在不激活环境的情况下启动,否则依赖项将无法找到。
### qwen-code
在 `settings.json` 中(通常为 `~/.qwen/settings.json` 或项目中的 `.qwen/settings.json`):
```json
{
"mcpServers": {
"rag": {
"command": "/path/to/devitway-rag-starter/.venv/bin/python",
"args": ["/path/to/devitway-rag-starter/mcp_server.py"]
}
}
}
```
### Claude Code
```bash
claude mcp add rag -- /path/to/devitway-rag-starter/.venv/bin/python /path/to/devitway-rag-starter/mcp_server.py
```
检查:询问代理关于 `./docs` 内容的问题 — 它应该调用 `search` 工具。
## 主要约束(请勿违反)
**索引和搜索必须使用相同的嵌入模型。** 如果使用不同的模型索引和搜索(例如 1024d 和 384d),则可能会得到错误的结果或维度错误。因此,模型和地址在 `config.py` 中定义 — 两个脚本都从此处获取它们。更改模型时,请在 `config.py` 中更改并完全重新索引(删除 `pipeline_storage/` 和 Qdrant 中的集合)。
## 故障排除
| 症状 | 原因 / 解决方案 |
|---------|-------------------|
| `OpenAI API key` 错误 | LlamaIndex 默认使用 OpenAI 嵌入器。在此存储库中,`Settings.embed_model` 已明确设置 — 请检查是否未被覆盖。 |
| 搜索返回错误结果 / 空结果 | 索引和搜索模型不同。请保持一致(参见上面的约束)。 |
| 连接到 Qdrant 时被拒绝 | 未运行 `docker compose up -d` 或端口 6333 被占用。 |
| `model not found` | 未运行 `ollama pull bge-m3`。 |
| 代理未看到 MCP 服务器 | `command` 必须指向 `.venv/bin/python` (绝对路径),而不是系统 Python。 |
| 服务器已连接,但代理未调用 `search` (即使在明确请求时) | 通常是本地模型的问题,而不是 RAG。Qwen3 通过 ollama 不提供 tool_calls 流式传输 ([ollama#14601](https://github.com/ollama/ollama/issues/14601)) — 返回空响应,提示无法解决。请单独检查层:`python -c "from mcp_server import search; print(search('ollama'))"` — 如果分段返回,则更改模型:`ollama pull gpt-oss:20b` (tool calling 稳定)或云模型(在 qwen-code 中 — 默认 Qwen OAuth)。对于 qwen-code,需要版本 ≥0.18.4 — 旧版本与任何本地模型都存在 tool calling 问题。 |
## 结构
```
devitway-rag-starter/
├── docker-compose.yml # Qdrant
├── requirements.txt
├── config.py # 统一配置(嵌入模型、地址)— 约束的源头
├── ingest.py # 增量式文档索引到 Qdrant
├── mcp_server.py # 只读 MCP:search() 工具
├── env.example # 复制到 .env
└── docs/ # 文档存放位置
└── example.md
```
## 未来扩展
- **Reranker** 作为第二阶段 (`bge-reranker-v2-m3`) — 显著提高准确率。
- **Redis-docstore** 代替基于文件的存储 — 适用于大型文档集和并行索引。
- **更多格式** — `SimpleDirectoryReader` 已支持 pdf/docx/md/txt;连接器(Google Drive, Notion, 数据库) — 独立包 `llama-index-readers-*`。