具有代理工具调用的多模式RAG
使用以下命令从摄入的文档中检索相关信息 基于多模态嵌入的语义搜索 (带文本和图像的PDF)和工具 模型上下文协议(MCP)两阶段检索:使用单向量嵌入进行ANN搜索,然后使用多向量嵌入进行重新排序。
Agentic Tool System (MCP + Internal Services Integration)
该系统使用统一的工具注册表,该注册表结合了:
- 外部MCP工具:来自外部MCP服务器的工具(stdio通信)请参阅MCP_server_docs/
- 通过包裹 MCPToolAdapter 匹配内部工具界面 - 示例:网络搜索、URL获取(来自 mcp_server_docs/)
- 内部服务工具:在后端直接调用函数
- 实施 BaseTool 直接接口 - 例子: retrieve_documents (语义文档搜索)
这两种工具类型都在中注册 ToolRegistry 并且看起来与LLM完全相同 AgentOrchestrator.
目录
.
├── backend/
│ ├── api/
│ │ ├── routes/
│ │ └── schemas/
│ ├── core/
│ │ ├── config.py
│ │ ├── exceptions.py
│ │ └── startup.py
│ ├── domain/ # Business logic
│ │ ├── agentic/ # Agent orchestration, mcp client, tools
│ │ ├── rag/ # Chunking, embedding, retrieval
│ │ └── evaluation/ # Retrieval evaluation
│ ├── services/ # Orchestrates domain logic
│ ├── storage/
│ │ ├── document_sql_store.py # PostgreSQL (documents & chunks metadata)
│ │ ├── single_vector_store.py # ChromaDB (single-vector embeddings)
│ │ ├── multi_vector_store.py # Mmap numpy (multi-vector embeddings)
│ │ └── file_store.py # Filesystem
│ └── data/
│ ├── documents/ # Uploaded PDFs
│ ├── chunks/ # Uploaded PDFs chunked by page
│ ├── single_vector_db/
│ └── multi_vector_db/
├── frontend/ # React frontend (Vite)
├── mcp_server_docs/ # External MCP server (stdio)
├── docker-compose.yml # Currently just Postgres Service
└── Makefile 快速开始
后端:
# Backend
# Runs on `http://localhost:8000`
# Auto-starts MCP server as it's set to stdio for transport
make dev-back
# Frontend - WIP
# Runs on `http://localhost:3000`
make dev-front
应用程序编程接口
后端运行后,API文档可在以下位置获得:
- Swagger用户界面:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
Tech Stack
后端
| 区域 | 组件 | 即时(开发/早期产品) | 稍后 |
|---|---|---|---|
| Web | 框架 | FastAPI | -- |
| ASGI服务器 | 乌维科恩 | 古尼科恩 | |
| MCP | MCP服务器 | 本地(stdio) | -- |
| 数据 | 数据库 | Postgres(Docker) | 数据库/渲染 |
| 文件存储 | 本地 | S3/GCS | |
| 单矢量存储 | ChromaDB(本地) | ChromaDBCloud | |
| 多向量存储 | mmap-numpy文件 | -- | |
| ORM | SQLAlchemy | -- | |
| 移民 | Alembic | -- | |
| API边界 | 验证 | Pydantic | - |
| 版本控制 | 基于URL | 向后兼容的模式演变 | |
| 身份验证 | 身份提供者 | 后端颁发的JWT | 职员 |
| Auth Flow | FastAPI中的JWT验证 | IDP处理的OAuth | |
| 配置和秘密 | 设置加载器 | 复制设置 | -- |
| 秘密来源 | .env 文件 | 平台环境变量+秘密管理器 | |
| 测试 | 测试 | pytest(CI强制) | -- |
| 绒布 | 黑色,褶边(CI强制) | -- | |
| 部署 | 容器 | Docker编写 | 托管或Kubernetes |
| CI/CD | 仅关键 | 自动化流水线 | |
| 健康检查 | -- | /health、数据库检查、工作人员心跳 | |
| 可观察性 | 日志记录 | -- | 结构化日志记录(structlog) |
| 指标/追踪 | -- | 普罗米修斯/Grafana | |
| 后台作业 | 作业队列 | -- | Celery |
| 代理 | -- | Redis | |
| 日程安排 | -- | 芹菜节拍 | |
| 缓存 | 缓存层 | -- | Redis |
前端
| 区域 | 组成部分 | 即时(MVP/早期) | 后期(规模/成熟) |
|---|---|---|---|
| 应用程序 | 框架 | Next.js | -- |
| 渲染 | 策略 | 客户端(CSR) | SSR/SSG |
| 造型 | CSS | 顺风CSS | shadcn/ui,Radix |
| 数据获取 | 服务器状态 | 获取 | TanStack查询 |
| 客户端状态 | UI状态 | React状态 | Zustand |
| 身份验证 | 身份UI | 职员SDK | -- |
| 表单 | 表单状态 | 反作用钩子表单 | 反作用钩表单+Zod |
| 验证 | 模式 | 内联验证 | Zod模式 |
| 工具 | 构建系统 | Next.js默认值 | -- |
| 测试 | 单元/E2E | -- | 小丑/剧作家 |
| 部署 | 托管 | Vercel | CDN/Edge |
Query Flow
当用户发送问题时。..
| 第一步 | 第二步 | 第三步 |
|---|---|---|
| 独角兽服务器: 从前端接收HTTP POST请求,并将解析后的请求传递给FastAPI | FastAPI: 火柴 @router.post("/query") → 路线至 handle_mcp_query → 调用依赖关系并验证 QueryRequest 模型 | MCP客户端: 调用异步 mcp_client.process_query → LLM+MCP工具(外部MCP服务器和内部工具)→ 答复 |
Inspect Data (DEV)
PostgreSQL检查
brew install --cask dbeaver-community
# If permission errors, fix permissions once, then retry
sudo chown -R $(whoami) /usr/local/var/homebrew______________________________________________________________________
用于单向量和多向量存储的Python REPL
cd backend && uv run python
import chromadb
from pathlib import Path
import pickle
import numpy as np
import json
from pprint import pprint
# ---------------- ChromaDB ----------------
client = chromadb.PersistentClient(
path=str(Path("data/single_vector_db"))
)
collection = client.get_collection("embeddings")
print(f"\nCollection: {collection.name}. Total vectors: {collection.count()}\n")
# Get sample data (must explicitly include embeddings)
sample = collection.get(
limit=1,
include=['embeddings', 'metadatas', 'documents']
# Note: ChromaDB's get() doesn't return embeddings/metadatas/documents by default
# (for performance - embeddings can be large). Must explicitly include them.
)
# Investigate metadata
print(f"\nSample ID: {sample.get('ids')[0]}\n")
print("Metadata:")
pprint(sample.get('metadatas', []), indent=4, width=100)
# Investigate embedding
# Embedding first 5 dimensions
embeddings = sample.get('embeddings')
print(f" Shape: {len(embeddings[0])} dimensions")
print(f" First 5 values: {embeddings[0][:5]}")
# Query random vector
query_vec = embeddings[0]
results = collection.query(
query_embeddings=[query_vec],
n_results=2,
include=['embeddings', 'metadatas', 'documents', 'distances']
)
# Query results
pprint(results, indent=2, width=100)
# ---------------- mmap files ----------------
index_path = Path("data/multi_vector_db/multi_vector_index.pkl")
with open(index_path, "rb") as f:
index = pickle.load(f)
print(f"\nTotal chunks: {len(index)}\n")
print("\nFirst 2 chunk IDs:", list(index.keys())[:2])
print()
# First chunk details
first_id = list(index.keys())[0]
emb = index[first_id]
print(f"\nChunk {first_id}:")
vec = np.array(emb)
print(f"\nVector shape: {vec.shape}, dtype: {vec.dtype}")
References
- 主控程序https://modelcontextprotocol.io/docs/develop/build-server#python
- 在claude桌面中启用mcp工具:
{
"mcpServers": {
"documentation": {
"command": "/Users/jamessukanto/.local/bin/uv",
"args": [
"--directory",
"/Users/jamessukanto/Desktop/codes/exps/mcp/mcp_server_docs",
"run",
"main.py"
]
}
}
}