study_rag
基于 LlamaIndex 的企业知识库检索服务,通过 MCP 协议暴露给 Agent 调用。
Enterprise knowledge base retrieval service built on LlamaIndex, exposed to AI agents via MCP.
项目简介
study_rag 是一个可插拔、多租户的企业级 RAG(检索增强生成)知识库服务。它把「知识库管理 + 文档处理 + 多策略检索」打包成一组能力,既提供 MCP Tool 供 AI Agent 直接调用,也提供 Admin REST + Web 控制台 供人工管理。
核心价值:
- Agent 即用:符合 MCP 规范,Claude / Cursor / 任意 MCP 客户端接上即可检索你的知识库
- 多策略检索:Dense / Sparse / Hybrid / Milvus 原生 BM25 / Hybrid-Milvus 五种策略可按请求切换
- 能力可插拔:Embedding / VectorStore / Reranker / Parser 均为接口化设计,配置即切换
- 生产就绪:structlog 结构化日志、Prometheus 指标、限流、熔断、Docker Compose 一键部署
技术栈
| 层 | 技术 |
|---|---|
| 后端 | Python 3.10+、FastAPI、Pydantic、structlog |
| 检索框架 | LlamaIndex(NodeParser 切块)、自研 RetrievalEngine 抽象 |
| 向量库 | Milvus 2.5+(原生 BM25)、可扩展 Qdrant / InMemory |
| Embedding | OpenAI / Ollama / BGE,接口化可替换 |
| Reranker | BGE / Cohere / TEI,接口化可替换 |
| MCP | FastMCP、Streamable HTTP transport |
| 前端 | React + Vite + TypeScript + shadcn/ui |
| 可观测 | Prometheus 指标、structlog 日志、限流、熔断 |
| 部署 | Docker Compose(admin + mcp 双进程)、tini、非 root |
| 工程化 | ruff(lint)、pytest、justfile、TypeScript typecheck |
知识库管理端UI
特性
- 多知识库管理:支持按部门/主题配置多个知识库
- 多租户隔离:基于 api_key 的用户 ↔ 知识库权限矩阵
- MCP 协议(Streamable HTTP):10 个核心 Tool,提供发现/检索/文档管理完整能力
- 能力可插拔:Embedding / VectorStore / Reranker 均可替换实现
- LlamaIndex 整合:NodeParser 切块、双检索路径(自研 + LlamaIndex)
- 文档管理增强:支持文本输入 / 文件上传(txt / md / html / pdf / docx),
多种切块策略(whole / sentence / token / semantic 通过命名 parser),
分块预览(提交前看到每块内容/大小),策略通过
configs/llamaindex.yaml配置 - 分块查看:管理 UI「文档」列表的「查看分块」按钮 → Drawer 显示该文档
所有 chunk 的
#索引 / 字符数 / 完整文本 / 源 metadata,分页 20/页 (对应GET /admin/kbs/{kb_id}/documents/{doc_id}/chunks端点) - Parser Registry:命名 parser 实体,类似 embedder / reranker 的注册表设计,
提供
get_parser(name)/list_parsers()接口 - 新 API 端点:
GET /admin/parsers/POST /documents/preview-chunk/POST /documents/upload - 统一管理面:FastAPI 提供知识库 CRUD + 健康检查
文档
| 文档 | 内容 |
|---|---|
| 部署指南 | Docker Compose、镜像构建、Milvus、生产清单、运维操作 |
| 配置参考 | 所有 YAML 配置文件、环境变量、provider 对照表 |
| MCP Tool | MCP Tool 完整 API、客户端示例、错误码 |
快速开始
Docker Compose(推荐)
# 1. 准备环境变量
cp .env.example .env
# 按需编辑 .env(至少配置一个 embedding 后端的 API_KEY)
# 2. 构建并启动 admin + mcp
docker compose -f docker/docker-compose.yml up -d --build
# 或: just docker-up
# 3. 带 Milvus 向量库一体化启动
docker compose -f docker/docker-compose.yml --profile vector up -d --build
# 或: just docker-up-vector
# 端点:
# 管理 UI http://localhost:3200/admin/ui/
# OpenAPI 文档 http://localhost:3200/docs
# MCP 端点 http://localhost:3220/mcp
# 健康检查 http://localhost:3200/health / http://localhost:3220/health
详见 部署指南。
本地 Python
# 安装依赖
pip install -e ".[dev,llamaindex,vector-milvus,embedding-bge,reranker-bge]"
# 启动 admin REST(port 3200)
study-rag
# 启动 MCP standalone server(port 3220,streamable_http transport)
study-rag-mcp
# MCP 端点
http://localhost:3220/mcp
# Admin REST 端点
http://localhost:3200/admin/docs
本地开发
# 一键脚本(PowerShell)
pwsh scripts/dev.ps1 mcp # 启 MCP server
pwsh scripts/dev.ps1 inspector # 启 MCP Inspector(浏览器调试)
pwsh scripts/dev.ps1 admin # 启 admin REST
pwsh scripts/dev.ps1 all-verify # 跑 lint + typecheck + 全 verify
架构
study_rag/
├── capabilities/ # 能力层(embedding/vector_store/reranker/llamaindex/retrieval)
├── knowledge_bases/ # 知识库管理(registry/manager/models/config_store)
├── auth/ # 鉴权(占位实现,含 writable_kbs)
├── mcp/ # MCP 服务(10 个 Tool + 3 Resource + 2 Prompt + filter)
├── api/ # FastAPI 管理面(admin/health)
├── jobs/ # 异步任务(文档切块流水线)
├── observability/ # 日志(structlog)/指标(Prometheus)/限流/熔断/中间件
├── web/ # Admin UI SPA 构建产物挂载(/admin/ui/)
├── mcp_standalone.py # MCP 独立部署入口(streamable_http)
├── app.py # Admin REST 入口
├── settings.py # 全局配置(pydantic-settings)
├── frontend/ # React + Vite + shadcn/ui 管理控制台源码
├── configs/ # YAML 配置(支持 ${ENV_VAR} 占位符)
│ ├── knowledge_bases.yaml
│ ├── embeddings.yaml
│ ├── vector_store.yaml
│ ├── reranker.yaml
│ ├── retrieval.yaml
│ ├── llamaindex.yaml
│ └── llm.yaml
├── docker/ # 容器化(Dockerfile + docker-compose + entrypoint)
├── docs/ # 文档(deployment.md / configuration.md)
└── scripts/ # 开发脚本(dev.ps1 / inspector.ps1)
部署拓扑(两进程独立扩缩容):
Agent / LLM ──MCP──▶ mcp:3220 ──▶ ┌─ embeddings (OpenAI/BGE/Ollama)
├─ rerankers (BGE/Cohere/TEI)
浏览器 ──HTTP──▶ admin:3200 ──▶ └─ vector_store (Milvus/Qdrant/mock)
└─ /admin/ui/ (React SPA)
└─ /admin/* (KB/文档 CRUD)
└─ /metrics (Prometheus)
详见 配置参考。
环境变量
核心变量(完整列表见 .env.example 和 配置参考):
| 变量 | 默认 | 说明 |
|---|---|---|
STUDY_RAG_PORT |
3200 |
admin REST 端口 |
MCP_PORT |
3220 |
MCP 端口 |
STUDY_RAG_LOG_LEVEL |
INFO |
日志级别 |
STUDY_RAG_ADMIN_TOKEN |
(空) | admin Bearer Token(生产必设) |
STUDY_RAG_MCP_REQUIRE_API_KEY |
false |
强制 MCP api_key |
MILVUS_URI |
http://milvus:19530 |
向量库地址 |
OPENAI_API_KEY |
(空) | OpenAI embedding key |
YAML 配置内的 ${VAR} 占位符在运行时解析为环境变量,无需改代码即可切换后端。
MCP Tool 完整列表
study_rag 在 Streamable HTTP transport 下暴露 10 个核心 Tool。
所有 Tool 都接受 api_key: str 作为第一个参数(当前为占位鉴权)。
返回值为 JSON 字符串(避免 FastMCP 对 list[dict] 的副作用),客户端用 json.loads(text) 解析。
1. list_accessible_kbs — 发现可用知识库
任何检索操作的第一步。先调用本 Tool 了解有哪些 KB 可用、每个 KB 的描述。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
api_key |
str | ✓ | 用户凭证 |
返回:list[KBInfo],每个元素包含:
[
{
"kb_id": "rd_frontend",
"name": "前端研发文档",
"description": "React / Vue 组件库、状态管理、性能优化",
"department": "rd",
"enabled": true,
"document_count": 42
}
]
异常:
- 鉴权失败 →
permission_denied
适用场景:
- 任何检索前必调
- 不确定有哪些 KB
- 需要根据 description 判断查询相关 KB
2. get_kb_info — 获取知识库详情
确认 KB 的 embedding / reranker / 文档数等细节,决定是否检索它。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
api_key |
str | ✓ | |
kb_id |
str | ✓ | 知识库 ID(命名规范 {dept}_{name}) |
返回:
{
"kb_id": "rd_frontend",
"name": "前端研发文档",
"description": "...",
"department": "rd",
"enabled": true,
"document_count": 42,
"embedding": "bge-large-zh",
"reranker": "bge-reranker-large",
"extra": {}
}
异常:
- KB 不存在 / 用户无权 →
kb_not_found
3. search_kb — 单知识库检索(核心)
在指定 KB 中检索 top-k 相关文档。可选启用 rerank、metadata filter。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
api_key |
str | ✓ | ||
kb_id |
str | ✓ | 知识库 ID | |
query |
str | ✓ | 检索问题 | |
top_k |
int | 5 | 返回数量,范围 (0, 50] | |
use_rerank |
bool | true | 是否启用重排 | |
filter_expr |
dict | null | metadata 过滤条件 |
filter_expr 操作符后缀:
| 后缀 | 含义 | 示例 |
|---|---|---|
无 / __eq |
等于 | {"source": "wiki"} |
__ne |
不等于 | {"status__ne": "draft"} |
__in |
在列表中 | {"tag__in": ["api", "db"]} |
__nin |
不在列表中 | {"tag__nin": ["internal"]} |
__gt / __gte |
大于 / ≥ | {"year__gte": 2024} |
__lt / __lte |
小于 / ≤ | {"score__lt": 0.5} |
__contains |
字符串包含 | {"title__contains": "API"} |
__exists |
字段是否存在 | {"draft__exists": false} |
多条件用 AND 连接:{"year__gte": 2024, "tag__in": ["api"]} 表示 year >= 2024 AND tag in [api]。
返回:
[
{
"doc_id": "doc-123",
"title": "React 18 并发渲染机制",
"text": "React 18 引入了并发渲染...",
"score": 0.87,
"metadata": {
"source": "wiki",
"year": 2024,
"tag": "frontend"
}
}
]
异常:
- KB 不存在 / 无权 →
kb_not_found query为空 /top_k超范围 →invalid_parameterfilter_expr字段名非法 →invalid_parameter
4. search_all_accessible_kbs — 跨知识库综合检索
不确定查哪个 KB 时的兜底入口。在所有可访问 KB 中并行检索,按相关度合并排序。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
api_key |
str | ✓ | ||
query |
str | ✓ | ||
top_k |
int | 5 | 最终返回条数 | |
use_rerank |
bool | true | ||
filter_expr |
dict | null | 下推到每个 KB |
返回:与 search_kb 一致,但每条 hit 的 metadata._source_kb 标识来源 KB ID。
实现细节:每个 KB 内部用各自 reranker 重排,跨 KB 层仅按分数合并(不同 KB 的 reranker 分数量纲不同,硬合并不严谨)。
5. get_document — 获取文档完整内容
search_kb 返回的是文档片段,看全文用本 Tool。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
api_key |
str | ✓ | |
kb_id |
str | ✓ | |
doc_id |
str | ✓ | 文档 ID |
返回:文档元数据 + 完整 content。
异常:
- 文档不存在 →
document_not_found
6. list_documents — 列出 KB 中的文档
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
api_key |
str | ✓ | ||
kb_id |
str | ✓ | ||
limit |
int | 100 | 返回条数上限,范围 (0, 1000] | |
offset |
int | 0 | 跳过条数(分页) |
返回:list[DocumentSummary],按创建时间倒序。
7. add_document — 添加文档(整篇一个 chunk)
需要写权限(writable_kbs 包含 kb_id)。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
api_key |
str | ✓ | ||
kb_id |
str | ✓ | ||
doc_id |
str | ✓ | KB 内唯一 | |
title |
str | ✓ | ||
content |
str | ✓ | ||
source |
str | null | 来源;自动存为 metadata['source'] |
|
metadata |
dict | null | 额外 metadata | |
overwrite |
bool | false | 已存在时是否覆盖 |
source / metadata 字段可在后续 search_kb 的 filter_expr 中引用。
异常:
- 无写权限 →
permission_denied - doc_id 已存在 +
overwrite=False→document_already_exists - 参数非法 →
invalid_parameter
8. add_document_chunked — 添加文档(NodeParser 切块)
需要写权限。整篇文档会按 LlamaIndex NodeParser 切块后入库。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
api_key |
str | ✓ | ||
kb_id |
str | ✓ | ||
doc_id |
str | ✓ | ||
title |
str | ✓ | ||
content |
str | ✓ | ||
source |
str | null | ||
metadata |
dict | null | ||
parser_config |
dict | null | NodeParser 配置 | |
overwrite |
bool | false |
parser_config 示例:
{
"strategy": "sentence",
"chunk_size": 512,
"chunk_overlap": 50
}
适用场景:长文档(> 1000 字)建议用本接口。
9. add_documents_batch — 批量添加文档
需要写权限。失败的单条不影响其他(通过日志记录)。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
api_key |
str | ✓ | ||
kb_id |
str | ✓ | ||
documents |
list[dict] | ✓ | 每项:{"doc_id", "title", "content", "source"?, "metadata"?} |
|
overwrite |
bool | false |
上限 500 条/次。
10. delete_document — 删除文档
需要写权限。向量 + meta 一起删。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
api_key |
str | ✓ | |
kb_id |
str | ✓ | |
doc_id |
str | ✓ |
异常:
- 无写权限 →
permission_denied - 文档不存在 →
document_not_found
典型工作流
1. list_accessible_kbs(api_key) # 了解有哪些 KB
2. (可选) get_kb_info(api_key, kb_id) # 确认 KB 范围
3. search_kb(api_key, kb_id, query, top_k=5) # 检索
4. (可选) get_document(api_key, kb_id, doc_id) # 拿完整文档
兜底:search_all_accessible_kbs(api_key, query) 跨 KB 检索。
客户端示例
Python(用官方 mcp SDK)
import asyncio
import json
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
async with streamablehttp_client("http://localhost:3220/mcp") as (
read_stream,
write_stream,
_get_session_id,
):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
# 列工具
tools = await session.list_tools()
# 检索
result = await session.call_tool(
"search_kb_tool",
arguments={
"api_key": "test",
"kb_id": "rd_frontend",
"query": "React 18 怎么用并发渲染?",
"top_k": 5,
},
)
# content[0].text 是 JSON 字符串
hits = json.loads(result.content[0].text)
for h in hits:
print(f"[{h['score']:.2f}] {h['title']}")
asyncio.run(main())
TypeScript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client({ name: "my-agent", version: "1.0.0" });
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3220/mcp")
);
await client.connect(transport);
const result = await client.callTool({
name: "search_kb_tool",
arguments: {
api_key: "test",
kb_id: "rd_frontend",
query: "React 18 怎么用并发渲染?",
top_k: 5,
},
});
const hits = JSON.parse(result.content[0].text);
Claude Desktop 配置
~/Library/Application Support/Claude/claude_desktop_config.json(macOS)或对应平台路径:
{
"mcpServers": {
"study-rag": {
"url": "http://localhost:3220/mcp",
"transport": "streamable_http"
}
}
}
错误码
| code | 含义 | 触发场景 |
|---|---|---|
permission_denied |
鉴权失败 | api_key 无效 / 用户无权访问该 KB |
kb_not_found |
KB 不存在 | kb_id 不存在或被禁用 |
document_not_found |
文档不存在 | doc_id 不存在 |
document_already_exists |
文档已存在 | add_document 时 doc_id 重复,且未传 overwrite |
invalid_parameter |
参数非法 | query 为空、top_k 超范围、filter_expr 字段名非法 |
所有错误通过 MCP 标准的 error.code 字段返回(JSON-RPC 格式)。
调试
# 启动 MCP server
pwsh scripts/dev.ps1 mcp
# 启动 MCP Inspector(浏览器)
pwsh scripts/dev.ps1 inspector
# 浏览器打开 http://localhost:3210
# 选 transport: streamable_http
# URL: http://localhost:3220/mcp
# Connect → List Tools → 选 Tool → 填参数 → Call Tool
协议版本
- MCP protocol:
2025-03-26 - Transport: Streamable HTTP
- Python:
>=3.10 - FastMCP:
>=1.0
No comments yet
Be the first to share your take.