自托管 AI 知识库与联网搜索全套方案:SearXNG + mcp-searxng + Infinity + R2R + pgvector
目标:用 4 个开源组件 + 1 个 MCP 桥接服务,在本地搭一套完全自托管的「向量知识库(RAG)+ 隐私联网搜索」基础设施,供 Claude、OpenCode、Cursor 等 AI 客户端调用。
一、方案总览
1.1 组件与镜像
| 组件 | 镜像 | 作用 |
|---|---|---|
| SearXNG | searxng/searxng:latest | 自托管元搜索引擎,聚合 Google/Bing/DuckDuckGo 等结果,不追踪用户 |
| mcp-searxng | isokoliuk/mcp-searxng:latest(或 npx 本地运行) | MCP 协议桥,把 SearXNG 的搜索能力暴露给 AI 客户端(Claude Desktop/Code、Cursor、OpenCode 等) |
| Infinity | michaelf34/infinity:0.0.77-cpu | 高吞吐 Embedding/Rerank 推理服务,提供 OpenAI 兼容的/embeddings接口,CPU 版无需显卡 |
| R2R (RAG to Riches) | sciphiai/r2r:3.6.6-amd64 | 生产级 RAG 服务:文档解析、切片、向量化入库、混合检索、RAG API |
| pgvector | pgvector/pgvector:pg16 | 内置 pgvector 扩展的 PostgreSQL 16,作为向量数据库持久层 |
1.2 架构图
┌──────────────────────────────┐ │ AI 客户端(Claude/OpenCode)│ └──────┬───────────────┬───────┘ MCP 协议 │ │ HTTP API ▼ ▼ ┌─────────────────┐ ┌──────────────────┐ │ mcp-searxng │ │ R2R :7272 │◄── 你自己的应用 │ (搜索工具桥) │ │ RAG 服务 │ (/v1/retrieval 等) └────────┬────────┘ └───────┬──────────┘ │ HTTP JSON │ 调用 embedding ▼ ▼ ┌─────────────────┐ ┌──────────────────┐ │ SearXNG :8080 │ │ Infinity :7997 │ │ 元搜索 │ │ bge-m3 向量模型 │ └────────┬────────┘ └──────────────────┘ │ 抓取各搜索引擎 │ 向量读写 ▼ ▼ Google/Bing/... ┌──────────────────┐ │ pgvector (PG16) │ │ 5432 向量存储 │ └──────────────────┘两条能力线相互独立又互补:
- 联网搜索线:客户端 → mcp-searxng → SearXNG → 各大搜索引擎。解决「模型不知道最新信息」的问题。
- 私有知识线:文档 → R2R → Infinity 向量化 → pgvector 存储 → 检索问答。解决「模型不知道你的私有资料」的问题。
1.3 为什么选这套组合
- 全部可自托管:搜索记录不出内网;Embedding 在本地 CPU 跑,不依赖 OpenAI。
- 版本都经过社区验证:Infinity 0.0.77 与 R2R 3.6.x 是各自项目稳定期版本,镜像多架构齐全。
- 标准协议对接:MCP 是 AI 工具生态事实标准;Infinity 和 R2R 都讲 OpenAI 兼容 HTTP,替换任何一个组件都不影响整体。
二、环境准备
| 要求 | 说明 |
|---|---|
| Docker ≥ 24 + Compose v2 | 所有服务容器化部署 |
| 内存 ≥ 8GB | Infinity 加载 bge-m3 约占 2~3GB;R2R 约占 1GB |
| 磁盘 ≥ 20GB | 含模型权重(bge-m3 约 2GB)、Postgres 数据 |
| 可访问 HuggingFace | 首次启动拉取模型;国内建议配HF_ENDPOINT=https://hf-mirror.com |
目录结构约定:
/opt/ai-stack/ ├── docker-compose.yml ├── .env # 密码、密钥等敏感配置 ├── searxng/ │ ├── settings.yml # SearXNG 主配置 │ └── limiter.toml ├── r2r/ │ └── r2r.toml # R2R 配置(指向 infinity + pgvector) └── pgdata/ # PG 数据卷三、逐个部署
3.1 pgvector:PostgreSQL 16 + 向量扩展
pgvector/pgvector:pg16就是官方 PostgreSQL 16 镜像加了 pgvector 扩展,用法与普通 PG 完全一致:
postgres: image: pgvector/pgvector:pg16 restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-r2r} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env} POSTGRES_DB: ${POSTGRES_DB:-r2r} volumes: - ./pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-r2r}"] interval: 10s timeout: 5s retries: 5验证扩展可用(R2R 会自动建表,但可以手动确认扩展存在):
docker compose exec postgres psql -U r2r -c "CREATE EXTENSION IF NOT EXISTS vector;" docker compose exec postgres psql -U r2r -c "SELECT extname, extversion FROM pg_extension WHERE extname='vector';"注意:pgvector 的索引(HNSW/IVFFlat)和查询参数由 R2R 自动管理,一般不需要手工建表。向量维度必须与 Embedding 模型输出一致——本文用 bge-m3(1024 维),中途换模型必须重建集合并重新入库。
3.2 Infinity:CPU 版 Embedding 服务
选BAAI/bge-m3:多语言(中英效果好)、1024 维、支持长文本(8192 tokens),是中文知识库的主力选择。
compose 片段:
infinity: image: michaelf34/infinity:0.0.77-cpu restart: unless-stopped environment: HF_ENDPOINT: ${HF_ENDPOINT:-} # 国内镜像加速,如 https://hf-mirror.com command: > v2 --model-id BAAI/bge-m3 --served-model-name bge-m3 --port 7997 --batch-size 16 volumes: - ./infinity-cache:/app/.cache # 缓存模型权重,重启不用重新下载 ports: - "127.0.0.1:7997:7997" # 只暴露给本机/容器网络验证(OpenAI 兼容接口):
curl http://127.0.0.1:7997/embeddings \ -H "Content-Type: application/json" \ -d '{"model":"bge-m3","input":["你好,世界"]}' # 返回 JSON 中 data[0].embedding 应为长度 1024 的数组要点说明:
0.0.77版本起命令行入口为v2子命令,所有参数也可用INFINITY_前缀环境变量代替(如INFINITY_MODEL_ID=BAAI/bge-m3;)。-cpu镜像内置 ONNX/CPU 优化,无 NVIDIA 显卡时的最佳选择;有卡请换默认镜像并加--gpus all。- 还可以再挂一个 rerank 模型(如
--model-id BAAI/bge-reranker-base)供 R2R 重排用,按需增加。 - 首次启动要下载约 2GB 权重,耐心等待日志出现
Uvicorn running再测接口。
3.3 R2R:RAG 编排服务
R2R 负责最重的活:文档摄取(PDF/DOCX/HTML/MD)、语义切块、调 Infinity 向量化、写入 pgvector、提供检索与 RAG 问答 API。
r2r/r2r.toml关键配置(把 embedding 指到本地 Infinity,把存储指到本地 pgvector):
[completion] # 生成模型仍需要一个 LLM 提供方;provider 用 litellm 时模型名要带 openai/ 前缀 provider = "litellm" concurrent_request_limit = 16 [completion.generation_config] model = "openai/gpt-4o-mini" # 也可以指向任意 OpenAI 兼容的本地大模型 temperature = 0.1 max_tokens_to_sample = 1024 stream = true [embedding] provider = "openai" # 走 OpenAI 兼容协议 → 即本地 Infinity base_model = "bge-m3" # 对应 --served-model-name [database] provider = "pgvector"配套环境变量(写入.env或 compose 的environment):
# 让 R2R 的 openai/litellm 客户端打到本地 Infinity OPENAI_API_BASE=http://infinity:7997/v1 OPENAI_API_KEY=empty # Infinity 不校验,占位即可 # pgvector 连接 POSTGRES_HOST=postgres POSTGRES_PORT=5432 POSTGRES_USER=r2r POSTGRES_PASSWORD=your-strong-password POSTGRES_DB=r2r提示:不同小版本的 toml 字段名偶有调整,以上结构以 R2R GitHub 仓库 v3.6.x 的示例配置为准,冲突时以官方模板为准。
compose 片段:
r2r: image: sciphiai/r2r:3.6.6-amd64 restart: unless-stopped depends_on: postgres: condition: service_healthy infinity: condition: service_started environment: OPENAI_API_BASE: http://infinity:7997/v1 OPENAI_API_KEY: empty POSTGRES_HOST: postgres POSTGRES_PORT: "5432" POSTGRES_USER: ${POSTGRES_USER:-r2r} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-r2r} volumes: - ./r2r/r2r.toml:/app/config/r2r.toml - ./r2r/data:/app/data ports: - "7272:7272" # R2R API常用操作:
# 上传文档建立知识库(会自动切片→Infinity向量化→入pgvector) curl -X POST http://127.0.0.1:7272/v1/documents \ -F "file=@./manual.pdf" # RAG 问答:检索 + LLM 生成 curl -X POST http://127.0.0.1:7272/v1/retrieval/rag \ -H "Content-Type: application/json" \ -d '{"query":"设备保修政策是什么?"}'排错速查:
| 现象 | 原因与处理 |
|---|---|
| R2R 启动即退出 | pgvector 未就绪或密码不对,看docker logs r2r |
| 入库报维度不匹配 | 换过 Embedding 模型;删掉对应 collection 重新入库 |
| 入库很慢 | Infinity 首次下载模型中;CPU 下 bge-m3 吞吐有限属正常,可调大--batch-size |
3.4 SearXNG:隐私元搜索
SearXNG 必须开启 JSON 输出格式,否则 mcp-searxng 拿不到数据(最常见的坑)。
searxng/settings.yml最小可用配置:
use_default_settings: true server: secret_key: "change-me-to-random-string" # openssl rand -hex 32 生成 limiter: false # 仅本机/内网使用时可关 image_proxy: true search: safe_search: 0 formats: # ← 关键!默认没有 json - html - json engines: - name: google disabled: false - name: bing disabled: false - name: duckduckgo disabled: falsecompose 片段(SearXNG 官方推荐 Redis 做缓存):
redis: image: valkey/valkey:8-alpine restart: unless-stopped command: valkey-server --save 30 1 --loglevel warning searxng: image: searxng/searxng:latest restart: unless-stopped depends_on: - redis environment: SEARXNG_BASE_URL: http://127.0.0.1:8080/ volumes: - ./searxng/settings.yml:/etc/searxng/settings.yml:ro ports: - "8080:8080"验证 JSON API:
curl "http://127.0.0.1:8080/search?q=docker+searxng&format=json" # 返回 results 数组即成功;返回 403 说明 formats 里没开 json3.5 mcp-searxng:给 AI 客户端装上搜索
mcp-searxng 不是 SearXNG 插件,而是独立的 MCP Server(Node.js 进程),唯一必填变量是SEARXNG_URL。它通常跑在客户端一侧(STDIO 模式),不必进 compose。
Claude Code 注册(用户级):
claude mcp add --scope user --env SEARXNG_URL=http://127.0.0.1:8080 --transport stdio searxng -- npx -y mcp-searxngClaude Desktop / Cursor / OpenCode 等(JSON 配置):
{ "mcpServers": { "searxng": { "command": "npx", "args": ["-y", "mcp-searxng"], "env": { "SEARXNG_URL": "http://127.0.0.1:8080", "SEARXNG_MAX_RESULTS": "10", "SEARXNG_DEFAULT_LANGUAGE": "zh-CN" } } } }常用可选变量:
| 变量 | 默认 | 说明 |
|---|---|---|
SEARXNG_URL | 必填 | SearXNG 地址,支持分号分隔多实例做故障转移 |
SEARXNG_FANOUT | false | true 时并行查询所有实例并合并去重 |
SEARXNG_DEFAULT_LANGUAGE | all | 默认搜索语言,如 zh-CN |
SEARXNG_MAX_RESULTS | 10 | 返回结果条数上限 |
SEARXNG_TIMEOUT_MS | 10000 | 单次搜索超时 |
配置完成后在客户端里说一句"搜一下 xxx",能看到工具调用 SearXNG 即接入成功。
四、完整 docker-compose.yml(汇总)
services: postgres: image: pgvector/pgvector:pg16 restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-r2r} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env} POSTGRES_DB: ${POSTGRES_DB:-r2r} volumes: - ./pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-r2r}"] interval: 10s timeout: 5s retries: 5 infinity: image: michaelf34/infinity:0.0.77-cpu restart: unless-stopped environment: HF_ENDPOINT: ${HF_ENDPOINT:-} command: > v2 --model-id BAAI/bge-m3 --served-model-name bge-m3 --port 7997 --batch-size 16 volumes: - ./infinity-cache:/app/.cache ports: - "127.0.0.1:7997:7997" r2r: image: sciphiai/r2r:3.6.6-amd64 restart: unless-stopped depends_on: postgres: condition: service_healthy environment: OPENAI_API_BASE: http://infinity:7997/v1 OPENAI_API_KEY: empty POSTGRES_HOST: postgres POSTGRES_PORT: "5432" POSTGRES_USER: ${POSTGRES_USER:-r2r} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-r2r} volumes: - ./r2r/r2r.toml:/app/config/r2r.toml - ./r2r/data:/app/data ports: - "7272:7272" redis: image: valkey/valkey:8-alpine restart: unless-stopped command: valkey-server --save 30 1 --loglevel warning searxng: image: searxng/searxng:latest restart: unless-stopped depends_on: - redis environment: SEARXNG_BASE_URL: http://127.0.0.1:8080/ volumes: - ./searxng/settings.yml:/etc/searxng/settings.yml:ro ports: - "8080:8080".env示例:
POSTGRES_USER=r2r POSTGRES_PASSWORD=please-change-me POSTGRES_DB=r2r HF_ENDPOINT=https://hf-mirror.com一键启停:
docker compose up -d docker compose ps # 全部 healthy/running 即就绪 docker compose logs -f infinity # 观察模型加载五、验收清单
| # | 验证项 | 命令 | 预期 |
|---|---|---|---|
| 1 | pgvector 扩展 | psql -U r2r -c "CREATE EXTENSION IF NOT EXISTS vector;" | 无报错 |
| 2 | Embedding 服务 | curl 127.0.0.1:7997/embeddings ... | 返回 1024 维向量 |
| 3 | RAG 入库+问答 | POST/v1/documents后 POST/v1/retrieval/rag | 能引用上传文档作答 |
| 4 | 搜索 JSON API | curl ".../search?q=test&format=json" | 返回 results 数组 |
| 5 | MCP 搜索 | AI 客户端内发起一次搜索 | 工具调用成功返回网页摘要 |
六、安全与运维建议
- 不要把端口裸奔公网:SearXNG/R2R/Infinity/PG 只绑
127.0.0.1或走反向代理加认证;mcp-searxng 若以 HTTP 模式对外,务必设置MCP_HTTP_AUTH_TOKEN并启用 hardened 模式。 - 密钥管理:
settings.yml的secret_key、.env里的数据库密码都要换掉示例值;csdcn.env类凭据文件不要进 git。 - 备份:定期备份
pgdata/目录(或用pg_dump),向量库重建成本高。 - 升级策略:Infinity 与 R2R 升级可能改变向量维度或表结构,升级前备份,升级后先跑第五节验收清单。