使用说明
RAG文档服务器v2.1
纯确定性工具服务器 用于文档处理、分块和 矢量检索。 里面没有法学硕士 --带上你自己的经纪人。
可通过以下方式访问 主控程序 (模型上下文协议)用于AI代理集成(Claude、Copilot、LangChain等)
随着 streamable-http 和 stdio 运输。
┌────────────────────────────┐
│ AI Agent (Claude, Copilot,│
│ LangChain + LLM) │
└─────────────┬──────────────┘
│ MCP protocol
▼
╔════════════════════════════════════════════════════════════════════════════╗
║ RAG Document Server (no LLM) ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ ┌─ MCP Server ──────────────────────────────────────────────────────┐ ║
║ │ FastMCP · /mcp · streamable-http · stdio │ ║
║ └──────────────┬────────────────────────────────────────────────────┘ ║
╠═════════════════╩════════════════════════════════════════════════════════╣
║ MIDDLEWARE ─ request-id · rate-limit · timeout · logging ║
╠═════════════════════════════════════════════════════════════════════════════╣
║ TOOLS (13) RESOURCES (2) ║
║ ├─ query.py ──────────────────┐ ├─ rag://supported-formats ║
║ │ process_document │ └─ rag://tool-descriptions ║
║ │ chunk_document │ ║
║ │ retrieve_chunks │ ║
║ │ query_spreadsheet │ ║
║ ├─ extract.py ────────────────┤ ║
║ │ pdf · docx · pptx │ ║
║ │ xlsx · csv · image │ ║
║ ├─ utility.py ────────────────┤ ║
║ │ detect_language │ ║
║ │ get_system_health │ ║
║ │ manage_cache │ ║
║ └─────────────────────────────┘ ║
╠═════════════════════════════════════════════════════════════════════════════╣
║ ┌─ Services ──────────┐ ┌─ Processors ─────────┐ ┌─ Core ──────────┐ ║
║ │ ▸ downloader (3×) │ │ ▸ PDF (PyMuPDF) │ │ ▸ config │ ║
║ │ ▸ cache (3-layer) │ │ ▸ DOCX (python-docx)│ │ ▸ errors │ ║
║ │ ▸ chunking │ │ ▸ PPTX (python-pptx)│ │ ▸ logging │ ║
║ │ ▸ retrieval (FAISS)│ │ ▸ XLSX/CSV (pandas) │ │ ▸ models │ ║
║ │ ▸ language detect │ │ ▸ Image (pytesseract)│ │ ▸ schemas │ ║
║ └────────────────────┘ │ ▸ HTML/TXT (BS4) │ └────────────────┘ ║
║ │ ▸ URL extractor │ ║
║ └──────────────────────┘ ║
╠═════════════════════════════════════════════════════════════════════════════╣
║ ML MODELS (eager-loaded at startup · no LLM) ║
║ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────────┐ ║
║ │ MiniLM-L6-v2 │ │ BGE-small-en │ │ ms-marco-MiniLM │ ║
║ │ fast embeddings│ │ accurate embed. │ │ cross-encoder reranker│ ║
║ └─────────────────┘ └──────────────────┘ └─────────────────────────┘ ║
╚═════════════════════════════════════════════════════════════════════════════╝
架构图
flowchart TB
%% ── Clients ──────────────────────────────────────────────────
C1(["🌐 HTTP Client<br/>curl · Postman · Frontend"])
C2(["🤖 AI Agent + LLM<br/>Claude · Copilot · LangChain"])
%% ── Transport ────────────────────────────────────────────────
subgraph Transport[" 🔌 Transport Layer "]
direction LR
MCP["⚡ MCP Protocol<br/>FastMCP · /mcp<br/>streamable-http · stdio"]
end
%% ── Middleware ────────────────────────────────────────────────
subgraph MW[" 🛡️ Middleware Pipeline "]
direction LR
M2["⏱️ Rate Limit<br/>Token bucket"]
M3["✅ Validation<br/>URL · text"]
M4["📋 Logging<br/>JSON · Request-ID"]
M5["⏳ Timeout<br/>30s–300s"]
end
%% ── Tools ────────────────────────────────────────────────────
subgraph ToolsGroup[" 🔧 MCP Tools (13) + Resources (2) "]
direction LR
subgraph TQ[" query.py "]
direction TB
Q1(["process_document"])
Q2(["chunk_document"])
Q3(["retrieve_chunks"])
Q4(["query_spreadsheet"])
end
subgraph TE[" extract.py "]
direction TB
E1(["extract_pdf_text"])
E2(["extract_docx_text"])
E3(["extract_pptx_text"])
E4(["extract_xlsx_tables"])
E5(["extract_csv_tables"])
E6(["extract_image_text"])
end
subgraph TU[" utility.py "]
direction TB
U1(["detect_language"])
U2(["get_system_health"])
U3(["manage_cache"])
end
end
%% ── Services ─────────────────────────────────────────────────
subgraph Services[" ⚙️ Service Layer "]
direction LR
DL["📥 Downloader<br/>HTTP · 3× retry"]
CACHE["💾 3-Layer Cache<br/>Download · Document<br/>Retriever · 30 min TTL"]
CHUNK["✂️ Adaptive Chunking<br/>Type-aware sizes<br/>Importance scoring"]
RET["🔍 Retrieval Engine<br/>FAISS vector search<br/>Cross-encoder rerank<br/>Diversity filter"]
LANG["🌍 Language Detection<br/>3-round sampling"]
end
%% ── Processors ───────────────────────────────────────────────
subgraph Processors[" 📄 Document Processors "]
direction LR
PDF["PDF<br/>PyMuPDF"]
DOCX["DOCX<br/>python-docx"]
PPTX["PPTX<br/>python-pptx"]
XLSX["XLSX · CSV<br/>pandas"]
IMG["Image<br/>pytesseract"]
HTML["HTML · TXT<br/>BeautifulSoup"]
URLP["URL extract<br/>regex"]
end
%% ── Models ───────────────────────────────────────────────────
subgraph Models[" 🧠 ML Models — eager-loaded · no LLM "]
direction LR
EMB1["🚀 MiniLM-L6-v2<br/>Fast embeddings"]
EMB2["🎯 BGE-small-en-v1.5<br/>Accurate embeddings"]
RERANK["📊 ms-marco-MiniLM<br/>Cross-encoder reranker"]
end
%% ── Edges ────────────────────────────────────────────────────
C1 -- "MCP" --> MCP
C2 -- "MCP" --> MCP
MCP --> MW
M2 -.-> M3 -.-> M4 -.-> M5
MW --> ToolsGroup
TQ --> DL & CHUNK & RET
TE --> DL
TU --> LANG & CACHE
DL --> CACHE
DL --> Processors
CHUNK --> RET
RET --> Models
Processors --> LANG
Processors --> URLP
%% ── Styles ───────────────────────────────────────────────────
style C1 fill:#bbdefb,stroke:#1565c0,stroke-width:2px,color:#0d47a1
style C2 fill:#b3e5fc,stroke:#0277bd,stroke-width:2px,color:#01579b
style Transport fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#e65100
style MCP fill:#ffe0b2,stroke:#f57c00,stroke-width:1px,color:#e65100
style MW fill:#fce4ec,stroke:#c62828,stroke-width:2px,color:#b71c1c
style M2 fill:#ffcdd2,stroke:#e53935,stroke-width:1px,color:#b71c1c
style M3 fill:#ffcdd2,stroke:#e53935,stroke-width:1px,color:#b71c1c
style M4 fill:#ffcdd2,stroke:#e53935,stroke-width:1px,color:#b71c1c
style M5 fill:#ffcdd2,stroke:#e53935,stroke-width:1px,color:#b71c1c
style ToolsGroup fill:#e0f2f1,stroke:#00695c,stroke-width:2px,color:#004d40
style TQ fill:#b2dfdb,stroke:#00897b,stroke-width:1px,color:#004d40
style TE fill:#b2dfdb,stroke:#00897b,stroke-width:1px,color:#004d40
style TU fill:#b2dfdb,stroke:#00897b,stroke-width:1px,color:#004d40
style Services fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1b5e20
style DL fill:#c8e6c9,stroke:#43a047,stroke-width:1px,color:#1b5e20
style CACHE fill:#c8e6c9,stroke:#43a047,stroke-width:1px,color:#1b5e20
style CHUNK fill:#c8e6c9,stroke:#43a047,stroke-width:1px,color:#1b5e20
style RET fill:#c8e6c9,stroke:#43a047,stroke-width:1px,color:#1b5e20
style LANG fill:#c8e6c9,stroke:#43a047,stroke-width:1px,color:#1b5e20
style Processors fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#4a148c
style PDF fill:#e1bee7,stroke:#8e24aa,stroke-width:1px,color:#4a148c
style DOCX fill:#e1bee7,stroke:#8e24aa,stroke-width:1px,color:#4a148c
style PPTX fill:#e1bee7,stroke:#8e24aa,stroke-width:1px,color:#4a148c
style XLSX fill:#e1bee7,stroke:#8e24aa,stroke-width:1px,color:#4a148c
style IMG fill:#e1bee7,stroke:#8e24aa,stroke-width:1px,color:#4a148c
style HTML fill:#e1bee7,stroke:#8e24aa,stroke-width:1px,color:#4a148c
style URLP fill:#e1bee7,stroke:#8e24aa,stroke-width:1px,color:#4a148c
style Models fill:#fff8e1,stroke:#f9a825,stroke-width:2px,color:#f57f17
style EMB1 fill:#fff9c4,stroke:#fbc02d,stroke-width:1px,color:#f57f17
style EMB2 fill:#fff9c4,stroke:#fbc02d,stroke-width:1px,color:#f57f17
style RERANK fill:#fff9c4,stroke:#fbc02d,stroke-width:1px,color:#f57f17
目录
- 快速开始
- 客户端代理
- MCP工具参考
- 项目结构
- 配置深度学习
- 安全和中间件管道
- 缓存架构
- 文档处理器——内部
- 自适应分块算法
- 检索引擎
- 急切的模型加载
- 结构化日志记录
- 错误层次结构
- 数据模式
- 语言检测
- 支持格式
- 环境变量
- 客户端配置示例
- 开发指南
快速开始
1.安装依赖项
pip install -r requirements.txt
关键包:
mcp[cli]>=1.26.0,fastapi,uvicorn,langchain-huggingface,langchain-community,sentence-transformers,torch,PyMuPDF,python-docx,python-pptx,openpyxl,pandas,pytesseract,beautifulsoup4,faiss-cpu(或faiss-gpu用于CUDA加速)。
2.设置环境变量
服务器使用 .env 用于配置。无需设置--
内置了合理的默认值:
# .env (copy from .env.example and customise)
# MCP_RATE_LIMIT_RPM=60 # requests per minute per user (default: 60)
# MCP_REQUEST_TIMEOUT=300 # seconds per tool call (default: 300)
# GPU_CONCURRENCY=2 # max concurrent FAISS build/retrieval ops (default: 2)
注: 不
GOOGLE_API_KEY服务器需要它——它不包含LLM。 LLM密钥仅在以下情况下需要 客户代理.
3.启动服务器
# ── MCP transport (default: streamable-http) ──────────────────────
python -m mcp_server # streamable-http, localhost:8000
python -m mcp_server --transport stdio # stdio (piped)
# ── Production (multi-worker for concurrent users) ────────────────
python -m mcp_server --workers 4 # 4 worker processes
python -m mcp_server --workers 4 --host 0.0.0.0 # expose to network
# ── Development mode (auto-reload on code changes) ────────────────
python -m mcp_server --reload # watches mcp_server/ for changes
| CLI参数 | 选项 | 默认值 |
|---|---|---|
--transport |
streamable-http, stdio |
streamable-http |
--host |
任何绑定地址 | 127.0.0.1 |
--port |
任何端口号 | 8000 |
--workers |
uvicorn工人流程数量 | 1 |
--reload |
标志(无值) | 关闭 |
注:
--reload和--workers > 1是相互排斥的(uvicorn限制)。 在--reload模式,工人总是被迫1。每个工人都加载自己的副本 ML模型的内存(约1.5 GB),因此在扩展工作人员时,请确保有足够的GPU/RAM。
4.验证
服务器暴露 /health 和 /info 通过MCPRouter连接端点。
使用任何MCP客户端或捆绑的 client/agent.py 连接并验证工具是否可用。
客户端代理
这 client/ 文件夹包含 独立过程 --由LangChain提供动力
连接到正在运行的MCP服务器并使用其工具的ReAct代理
其自己的法学硕士(Gemini、OpenAI等)。所有的推理都发生在客户身上;
服务器只是一个工具提供者。
cd client
pip install -r requirements.txt
cp .env.example .env # add your GOOGLE_API_KEY or OPENAI_API_KEY
python agent.py # interactive REPL mode
python agent.py "Summarise https://example.com/report.pdf" # one-shot
┌────────────────────┐ MCP (streamable-http) ┌──────────────────────┐
│ client/agent.py │ ◄────────────────────────► │ MCP Server │
│ │ │ (pure tools) │
│ • LLM (Gemini) │ tool calls: │ • extract_pdf_text │
│ • ReAct agent │ – process_document │ • chunk_document │
│ • Reasoning │ – retrieve_chunks │ • retrieve_chunks │
│ • Answers │ – detect_language … │ • FAISS + rerank │
└────────────────────┘ └──────────────────────┘
看 client/README.md 有关代理的完整详细信息
架构、LLM选择、环境变量和示例对话。
端到端示例:通过MCP代理查询电子表格
本演练显示了完整的流程——托管文件、启动MCP服务器、, 并通过LangChain代理进行查询。
第一步——在本地提供文件 (独立终端):
cd docs/ # folder containing your files
python -m http.server 9090 # serves files at http://localhost:9090/
步骤2--启动MCP服务器 (独立终端):
python -m mcp_server # streamable-http on http://127.0.0.1:8000
步骤3--运行代理 (独立终端):
cd client
python agent.py
第4步--与您的数据聊天:
LangChain MCP Agent
Type 'quit' to exit
> get the phone number of John Doe from http://localhost:9090/Student_Data.xlsx
[TOOL CALL] query_spreadsheet(search_value='John Doe', document_url='http://localhost:9090/Student_Data.xlsx')
[TOOL RESULT] query_spreadsheet → [{'type': 'text', 'text': '{\n "matches": [\n {\n "NAME": "John Doe",\n "PHONE NUMBER": "9876543210",\n "EMAIL ID": "johndoe@example.com",\n ...
The phone number for John Doe is 9876543210.
> summarise https://example.com/quarterly-report.pdf
[TOOL CALL] process_document(document_url='https://example.com/quarterly-report.pdf')
...
The report covers Q3 revenue growth of 12% ...
代理自动选择正确的MCP工具(query_spreadsheet
对于行查找, retrieve_chunks 对于语义搜索, extract_* 为了
原始提取等)。
提示: 您也可以直接传递一个一次性查询:
python agent.py "Find email of Jane Smith from http://localhost:9090/Student_Data.xlsx"
MCP工具参考
文档工具
| # | 工具 | 输入 | 输出 | 超时 |
|---|---|---|---|---|
| 1 | process_document |
document_url: str |
{content (≤50K chars), content_length, metadata, tables[], images[], urls[], detected_language, detected_language_name} |
300秒 |
| 2 | chunk_document |
document_url: str |
{chunks[{text (≤5K), chunk_index, total_chunks, importance_score, content_type}], chunk_count, document_type} |
300秒 |
| 3 | retrieve_chunks |
document_url: str, query: str, top_k: int (1–20, default 5) |
{results[{text, chunk_index, importance_score, content_type}], total_chunks_indexed} |
300秒 |
| 4 | query_spreadsheet |
document_url: str, search_value: str |
{matches[{row data}], match_count, sheets_searched} |
300秒 |
retrieve_chunks 内部管道:
- 下载文档→ 处理它→ 自适应地将其分块
- 选择嵌入模型(如果≤50个块,则快速,否则精确——交叉编码器重新排序补偿)
- 从所有块构建FAISS向量索引
- 运行3次重复检索的相似性搜索(最多20个候选者)
- 使用交叉编码器重新排序(如果可用)
- 应用多样性过滤器(支持看不见的内容类型)
- 退货
top_k最佳大块 - 缓存已处理的文档和FAISS检索器(由
sha256(url)[:16])
query_spreadsheet --pandas行查找:
- 下载XLSX/CSV文件
- 将所有工作表加载到pandas DataFrames中
- 在所有列中执行不区分大小写的子字符串匹配
- 将匹配的行作为带有工作表名称的字典返回
- 用于特定行查找(例如“查找John的电话号码”)
提取工具
| # | 工具 | 输入 | 输出 | 超时 |
|---|---|---|---|---|
| 5 | extract_pdf_text |
document_url: str |
{text (≤50K chars), char_count} |
120秒 |
| 6 | extract_docx_text |
document_url: str |
{text (≤50K chars), char_count} |
120秒 |
| 7 | extract_pptx_text |
document_url: str |
{text (≤50K chars), char_count} |
120秒 |
| 8 | extract_xlsx_tables |
document_url: str |
{tables[{content (≤5K), table_type, location, metadata}], table_count} |
120秒 |
| 9 | extract_csv_tables |
document_url: str |
{tables[{content (≤5K), table_type, location, metadata}], table_count} |
120秒 |
| 10 | extract_image_text |
image_url: str |
{ocr_results[{text, confidence, metadata}]} |
120秒 |
实用工具
| # | 工具 | 输入 | 输出 | 超时 |
|---|---|---|---|---|
| 11 | detect_language |
text: str |
{language_code, language_name} |
30秒 |
| 12 | get_system_health |
(无) | 完整运行状况报告:状态、版本、功能、安全性、型号、格式、设备、缓存统计信息、时间戳 | 30秒 |
| 13 | manage_cache |
action: str ("stats" / "clear") |
每层缓存统计信息或驱逐计数 | 30秒 |
MCP资源
| URI | 描述 |
|---|---|
rag://supported-formats |
所有支持的文档格式的可读列表 |
rag://tool-descriptions |
所有13种工具及其参数的总结 |
项目结构
├── README.md
├── requirements.txt # Server dependencies (no LLM)
├── .env.example # Example environment variables
├── .gitignore
├── LICENSE # MIT
│
├── mcp_server/ # ─── Server package ───
│ ├── __init__.py
│ ├── __main__.py # CLI: --transport streamable-http|stdio --reload --workers N
│ ├── server.py # FastMCP instance, lifespan, tool registration
│ ├── _asgi.py # ASGI factory for --reload mode (uvicorn)
│ │
│ ├── core/
│ │ ├── config.py # Frozen dataclass configs, feature flags, device detection
│ │ ├── concurrency.py # GPU semaphore, FAISS build coalescing, dedicated thread pool
│ │ ├── logging.py # Structured JSON logging to stderr, request-id ContextVar
│ │ ├── errors.py # Exception hierarchy (6 error types)
│ │ ├── schemas.py # ProcessedDocument, ExtractedTable, ExtractedImage, ExtractedURL
│ │ └── models.py # Eager-loaded ML models (embeddings + reranker only)
│ │
│ ├── middleware/
│ │ ├── __init__.py # @guarded() decorator — full middleware chain
│ │ └── guards.py # Per-user + global rate-limit, URL/text validation, MCPRouter
│ │
│ ├── services/
│ │ ├── cache.py # Generic _TTLCache, 3 singleton layers
│ │ ├── downloader.py # Async httpx downloads with connection pooling + 3× retry
│ │ ├── language.py # Multi-round majority-vote language detection
│ │ ├── chunking.py # Adaptive chunking strategy + importance scoring
│ │ └── retrieval.py # FAISS vector search + cross-encoder reranking + diversity filter
│ │
│ ├── processors/
│ │ ├── __init__.py # detect_document_type(), TargetedDocumentProcessor dispatcher
│ │ ├── pdf.py # PyMuPDF — dict-based extraction with layout preservation
│ │ ├── docx.py # python-docx — heading hierarchy + table extraction
│ │ ├── pptx.py # python-pptx — slides, notes, tables, hyperlinks
│ │ ├── xlsx.py # pandas + openpyxl — header detection, column analysis; also CSV
│ │ ├── image.py # pytesseract — per-word OCR with confidence scores
│ │ └── url.py # Regex URL extraction with context + categorisation
│ │
│ ├── tools/
│ │ ├── query.py # process_document, chunk_document, retrieve_chunks, query_spreadsheet
│ │ ├── extract.py # Per-format extraction (PDF, DOCX, PPTX, XLSX, CSV, Image)
│ │ └── utility.py # detect_language, get_system_health, manage_cache
│ │
│ ├── resources/
│ │ └── __init__.py # rag://supported-formats, rag://tool-descriptions
│ │
│ ├── temp_files/ # Auto-created — temporary download / OCR staging + file uploads
│ ├── faiss_indexes/ # Auto-created — persisted FAISS indexes (survives restarts)
│ └── request_logs/ # Auto-created — structured request logs
│
└── client/ # ─── Separate agent (has LLM) ───
├── README.md
├── requirements.txt # langchain, langchain-google-genai, langchain-mcp-adapters
├── .env.example
└── agent.py # LangChain ReAct agent connecting via MCP
配置深度学习
所有配置都存在 core/config.py 作为 冻结数据类 (不可变
在导入时创建的单例)。不 .yaml 或 .toml --只是Python常量
具有可选的环境变量覆盖安全设置。
路径常量
| 常量 | 值 | 目的 |
|---|---|---|
BASE_DIR |
家长 mcp_server/ package |
临时/日志目录的根路径 |
TEMP_FILES_PATH |
<BASE_DIR>/temp_files/ |
临时下载、OCR暂存 |
REQUEST_LOGS_PATH |
<BASE_DIR>/request_logs/ |
结构化请求日志 |
如果这两个目录不存在,则会在导入时自动创建。
设备检测
在导入时运行一次:
torch.cuda.is_available()→"cuda"torch.backends.mps.is_available()→"mps"(苹果硅)- 回落到
"cpu"(包括何时torch未安装)
功能标志(优雅降级)
| 标志 | 依赖关系 | 回退 |
|---|---|---|
RERANK_AVAILABLE |
sentence_transformers.CrossEncoder |
跳过重新排名;按原样返回相似性结果 |
OCR_AVAILABLE |
pytesseract |
OCR工具返回错误消息 |
LANG_DETECT_AVAILABLE |
langdetect |
始终默认为 "en" |
配置数据类
ServerConfig
| 字段 | 类型 | 默认值 |
|---|---|---|
name |
str |
"RAG Document Server" |
version |
str |
"2.1.0" |
host |
str |
"127.0.0.1" |
port |
int |
8000 |
transport |
str |
"streamable-http" |
ModelConfig
| 字段 | 类型 | 默认值 |
|---|---|---|
embedding_fast |
str |
"sentence-transformers/all-MiniLM-L6-v2" |
embedding_accurate |
str |
"BAAI/bge-small-en-v1.5" |
reranker |
str |
"cross-encoder/ms-marco-MiniLM-L-6-v2" |
CacheConfig
| 字段 | 类型 | 默认值 |
|---|---|---|
default_ttl |
int |
1800 (30分钟) |
max_download_entries |
int |
50 |
max_document_entries |
int |
50 |
max_retriever_entries |
int |
20 |
max_download_bytes |
int |
524,288,000 (500 MB) |
SecurityConfig
| 字段 | 类型 | 默认值 | 环境变量 |
|---|---|---|---|
rate_limit_rpm |
int |
60 |
MCP_RATE_LIMIT_RPM |
max_url_length |
int |
2048 |
— |
max_text_length |
int |
100,000 |
— |
request_timeout |
int |
300 |
MCP_REQUEST_TIMEOUT |
安全和中间件管道
每次工具调用都要经过 @guarded(timeout=...)
装饰师。这个装饰器实现了一个 完整的中间件链 这确保了
工具从不向客户端引发异常。
中间件步骤(按顺序)
Request → [1] Request ID → [2] Rate Limit → [3] Execute w/ Timeout → [4] Log → Response
-
请求ID生成 —
uuid4().hex[:12]存储在aContextVar对于日志 整个调用堆栈的相关性。 -
速率限制 (
check_rate_limit(tool_name, api_key))--两层令牌桶:- 每个用户存储桶:容量=
rate_limit_rpm(默认60)每个API键 - 全球桶:5×每用户速率(默认300 rpm)--服务器范围的安全上限
- 重新填充率=
rpm / 60.0每秒令牌数 - 延迟充值:每次充值代币
consume()调用(无后台线程) - 每个用户的bucket在1000个条目时被FIFO驱逐,以防止内存泄漏
- 加薪
RateLimitError当每个用户或全局令牌耗尽时
- 每个用户存储桶:容量=
-
超时执行 —
asyncio.wait_for(fn(...), timeout=...):- 文档工具:300秒
- 提取工具:120秒
- 实用工具:30秒
- 加薪
TimeoutError(被装修工抓住,作为{"code": "TIMEOUT"})
-
结构化日志记录 --发射
tool.start,tool.success(随着时间的推移), 或tool.timeout/tool.known_error/tool.unhandled_error事件。 -
错误转换 --所有异常都会被捕获并转换为错误字典:
MCPServerError子类→{"error": exc.message, "code": exc.code}asyncio.TimeoutError→{"error": "...", "code": "TIMEOUT"}- 其他
Exception→{"error": "...", "code": "INTERNAL_ERROR"} request_id_var.reset(token)在finally块
输入验证
| 验证器 | 规则 | 加薪 |
|---|---|---|
validate_url(url) |
非空字符串,≤2048个字符, ^https?://[safe-url-chars]+$ |
ValidationError |
validate_text(text, field) |
必须是字符串,≤100000个字符 | ValidationError |
缓存架构
缓存系统使用通用 _TTLCache 类--线程安全(threading.Lock),
大小有限制,过期时间有限制。每个缓存条目都是 _CacheEntry
数据类包含 value, expires_at (浮动时间戳),以及 size_bytes.
三个缓存层
| 层 | 键 | 存储 | TTL | 最大条目 | 最大字节数 |
|---|---|---|---|---|---|
| 下载 | URL字符串 | 原始HTTP响应字节 | 30分钟 | 50 | 500 MB |
| 文档 | sha256(url)[:16] |
ProcessedDocument 对象 |
30分钟 | 50 | -- |
| 检索器 | sha256(url)[:16] |
EnhancedRetriever (失败指数+块) |
30分钟 | 20 | -- |
驱逐算法
在每一个 put() 调用后,将运行以下驱逐序列:
- 清除已过期 --删除以下所有条目
now > expires_at - 更新现有 --如果密钥已经存在,请先将其删除
- 字节限制 --虽然
total_bytes > max_download_bytes,驱逐最旧的入口 - 入境限制 --虽然
len(cache) >= max_entries,驱逐最旧的入口 - “最古老” =最小(最早)的条目
expires_at价值
缓存操作
// Inspect cache statistics (per-layer hit/miss rates)
{"tool": "manage_cache", "arguments": {"action": "stats"}}
// Clear all three cache layers
{"tool": "manage_cache", "arguments": {"action": "clear"}}
公共缓存API(内部使用)
| 功能 | 目的 |
|---|---|
get_cached_download(url) / put_cached_download(url, data) |
下载层 |
get_cached_document(key) / put_cached_document(key, doc) |
文档层 |
get_cached_retriever(key) / put_cached_retriever(key, ret) |
检索器存储层 |
get_retriever_with_disk_fallback(hash, emb) |
记忆→ disk → 无查找 |
put_retriever_with_disk(hash, ret) |
保存到内存+持久到磁盘 |
clear_faiss_disk() |
删除所有持久的FAISS索引 |
faiss_disk_stats() |
磁盘上索引的计数和大小 |
clear_all() |
刷新所有层(内存+磁盘) |
cache_stats() |
每层命中率+磁盘统计数据 |
文档处理器——内部
调度员(processors/__init__.py)
detect_document_type(url) --解析URL路径并映射文件扩展名:
| 扩展 | 类型 | 处理器 |
|---|---|---|
.pdf |
"pdf" |
extract_text_from_pdf() |
.doc, .docx |
"docx" |
extract_text_from_docx() |
.ppt, .pptx |
"pptx" |
extract_text_from_pptx() |
.xls, .xlsx |
"xlsx" |
extract_tables_from_xlsx() |
.csv |
"csv" |
extract_tables_from_csv() |
.txt |
"txt" |
UTF-8解码 |
.htm, .html |
"html" |
WebBaseLoader→ BeautifulSoup回退 |
.png, .jpg, .jpeg |
"image" |
extract_text_from_image() |
| 还有别的吗 | "unknown" |
UTF-8解码 errors="replace" |
后备安全: 如果任何特定于格式的处理器抛出异常
调度员抓住了它,又回到了原始状态 file_content.decode("utf-8", errors="replace").
提取后,调度员还:
- 通过从文本中提取URL
URLExtractor - 通过以下方式检测语言
detect_language_robust() - 返回a
ProcessedDocument数据类
PDF处理器(processors/pdf.py)
- 图书馆: PyMuPDF(
fitz) - 初级提取: 基于骰子,保留布局--
page.get_text("dict", sort=True),用页面标记重新组装文本块--- Page N --- - 回退1: 原始
page.get_text()任何例外 - 回退2: 如果原始提取失败,则为空字符串
DOCX处理器(processors/docx.py)
- 图书馆:
python-docx - 标题层次结构: 将标题级别保留为Markdown
# heading,## heading等等。 - 桌子: 提取为管道分隔的Markdown表
| cell | cell |
PPTX处理器(processors/pptx.py)
- 图书馆:
python-pptx - 每张幻灯片提取: 标题、正文(带项目符号缩进级别)、表格、演讲者备注
- 超链接: 从幻灯片关系和内联URL中提取
XLSX处理器(processors/xlsx.py)
- 图书馆:
pandas+openpyxl - 标头自动检测: 扫描前10行,按以下方式对每个候选人进行评分:
uniqueness × 0.5+text_ratio × 0.3+coverage × 0.2
- 显示限制: 每页最多渲染20行
- 柱分析: 每列数据类型推断(如果数字大于80%,则为数字,按关键字显示日期时间,否则为文本),数据密度计算
- 跨表关系: 检测工作表中的常见列
CSV处理器(processors/xlsx.py)
- 图书馆:
pandas - 解析:
pd.read_csv()具有自动标头检测功能 - 输出: 与XLSX相同的格式化管道(列分析、类型推断等)
图像处理器(processors/image.py)
- 图书馆:
pytesseract+Pillow - 管道: 转换为RGB→ 保存临时PNG→
image_to_data对于每一个字的信心→ 过滤器conf > 0→ 计算平均置信度 - 清理: 临时文件已删除
finally即使失败,也要阻止
URL提取器(processors/url.py)
- 正则表达式:
https?://[^\s<>"']+或www.[^\s<>"']+.[^\s<>"']+ - 背景: URL前后100个字符
- 分类:
api_endpoint,navigation,image,或general - 信心: 硬编码
0.9
自适应分块算法
分块服务(services/chunking.py)用途 AdaptiveChunkingStrategy 一
基于文档确定最优块参数的静态方法集
类型和内容长度。
按文档类型分组参数
| 文档类型 | 块大小 | 重叠 | 分隔符 |
|---|---|---|---|
pdf |
1500 | 300 | \n\n, \n, . , |
pptx |
800 | 150 | \n---\n, \n\n, \n, . , |
xlsx / csv |
1200 | 200 | \n===, \n---, \n\n, \n, |
docx / html |
1500 | 300 | \n\n, \n, . , |
| 默认值 | 1200 | 250 | \n\n, \n, . , |
基于内容长度的动态缩放
| 内容长度 | 缩放 |
|---|---|
| >100000个字符 | chunk_size×1.5,重叠×1.3 |
| <5000个字符 | chunk_size÷2(最小400),重叠÷2 |
| 5000–100000 | 无缩放 |
重要性评分算法
每个块在中都会获得一个重要性分数 [0.0, 1.0]:
| 条件 | 分数增量 |
|---|---|
| 基础分数 | +0.5 |
检测到标题(^#{1,3}\s 或 ^ALL-CAPS-LINE$) |
+0.2 |
数字/货币(\d+\.?\d*%, $\d+, €\d+) |
+0.15 |
关键词: important, key, critical, summary, conclusion, result, finding, recommendation |
+0.1 |
| 文本长度<50个字符 | −0.2 |
结果被夹紧 [0.0, 1.0] 并四舍五入到小数点后2位。
内容类型检测
每个块被分类为以下之一:
| 类型 | 检测规则 |
|---|---|
"table" |
包含管道 | 字符或制表符 |
"list" |
包含要点(- , • , * ) |
"heading" |
匹配markdown标题语法 |
"text" |
默认值 |
块元数据
每个区块都包含: chunk_index, total_chunks, importance_score, content_type, doc_type.
底层拆分器是LangChain的 RecursiveCharacterTextSplitter.
检索引擎
检索服务(services/retrieval.py)执行器 EnhancedRetriever —
具有交叉编码器重新排序的动态FAISS矢量搜索引擎,
分集滤波, 磁盘持久性,以及 并发控制.
管道步骤
Chunks → Embedding → FAISS Index → Save to disk → Similarity Search (3× over-retrieval)
→ Cross-Encoder Reranking → Diversity Filter → top_k results
-
指标构建 —
FAISS.from_documents(chunks, embeddings)从langchain_community.vectorstores基于每一份新文件,然后 坚持到faiss_indexes/<url_hash>/并缓存在内存中。 在后续查询中(即使在重新启动后),索引也会从磁盘加载 通过FAISS.load_local()而不是重建。并发控制 (从
core/concurrency.py):- GPU信号量 --FAISS构建和检索运行
run_in_gpu_pool(), 限于GPU_CONCURRENCY(默认2)同时操作。阻止 在突发交通中OOM。 - 构建聚合 --如果10个请求到达同一个URL,则只有一个
构建索引;其他9个等待每个URL
asyncio.Lock,然后阅读 从缓存中。消除了冗余的嵌入工作。
- GPU信号量 --FAISS构建和检索运行
-
嵌入模型选择:
- ≤50块→
get_embeddings_fast()(MiniLM-L6-v2)——交叉编码器重新排序补偿 -
50块→
get_embeddings_accurate()(BGE-small-en-v1.5)
- ≤50块→
-
相似性搜索 —
vectorstore.similarity_search(query, k=min(top_k * 3, 20)). 获取所需候选人数量的3倍(最多20人)。 -
交叉编码器重新排序 (如果
RERANK_AVAILABLE和use_reranking=True):- 创建
[query, chunk_text]对 - 分数通过
CrossEncoder.predict(pairs)使用ms-marco-MiniLM-L-6-v2 - 按分数降序排序,取
top_k - 退路: 在任何异常情况下,都会记录警告并回退到截断的相似性结果
- 创建
-
多样性过滤器 (
_diversity_filter):- 按以下方式对候选人排序
importance_score下降 - 贪婪地挑选大块,偏爱看不见的
content_type价值观 - 如果满足以下条件,则始终添加块
content_type还没有看到, 或 如果len(selected) < top_k - 停在
top_k
- 按以下方式对候选人排序
急切的模型加载
加载所有ML模型(嵌入+重新链接) 急切地等待服务器启动
通过 _ensure_models_loaded() 在FastMCP寿命期间(或
_asgi.py 工厂为 --reload 模式)。每个模型都用✓
加载时选中标记。
螺纹安全双重检查锁紧
A模块级 _loaded 首先检查布尔值(快速路径),然后重新检查
内部a threading.Lock (安全路径)。这确保了模型被准确加载
即使在并发请求下也是如此。
模型
| 变量 | 类 | 模型ID | 关键设置 |
|---|---|---|---|
_embeddings_fast |
HuggingFaceEmbeddings |
sentence-transformers/all-MiniLM-L6-v2 |
normalize_embeddings=True, batch_size=32,自动装置 |
_embeddings_accurate |
HuggingFaceEmbeddings |
BAAI/bge-small-en-v1.5 |
相同的设置 |
_reranker |
CrossEncoder |
cross-encoder/ms-marco-MiniLM-L-6-v2 |
max_length=512,仅在以下情况下加载 RERANK_AVAILABLE |
公共API
| 函数 | 返回 |
|---|---|
get_embeddings_fast() |
快速嵌入模型实例 |
get_embeddings_accurate() |
精确嵌入模型实例 |
get_reranker() |
交叉编码器重定器(或 None) |
models_loaded() |
bool --模型是否已初始化 |
结构化日志记录
所有日志都是结构化的JSON,发送到 标准错误 (保持stdout自由
MCP标准传输)。此外,日志被写入每日轮换文件
在 request_logs/server_YYYY-MM-DD.log.
日志格式
{
"ts": "2025-01-15T10:30:00.000Z",
"level": "INFO",
"logger": "mcp_server.tools.query",
"msg": "tool.success",
"rid": "a1b2c3d4e5f6",
"tool": "process_document",
"elapsed": 2.45
}
领域
| 字段 | 来源 | 描述 |
|---|---|---|
ts |
自动 | UTC ISO时间戳 |
level |
汽车 | INFO, WARNING, ERROR |
logger |
自动 | 模块路径 |
msg |
代码 | 事件名称(tool.start, tool.success, tool.timeout等等) |
rid |
ContextVar |
12个字符的请求ID(默认 "system") |
tool |
额外 | 工具名称 |
elapsed |
额外 | 执行时间(秒) |
url |
额外 | 文档URL(相关时) |
code |
额外 | 错误代码 |
attempt, wait |
额外 | 从下载器重试元数据 |
bytes |
额外 | 下载大小 |
静音记录仪
httpx, httpcore, urllib3, sentence_transformers, filelock --全部设置
到 WARNING 以降低噪音。
错误层次结构
所有自定义异常都继承自 MCPServerError 并携带一个马厩 .code
用于程序化匹配的字符串加上人类可读的字符串 .message:
MCPServerError(Exception) code="INTERNAL_ERROR"
├── RateLimitError code="RATE_LIMITED" msg="Rate limit exceeded"
├── ValidationError code="VALIDATION_ERROR"
├── DownloadError code="DOWNLOAD_ERROR"
├── ProcessingError code="PROCESSING_ERROR"
└── ModelLoadError code="MODEL_LOAD_ERROR"
这 @guarded decorator捕获所有这些并将其转换为
{"error": ..., "code": ...} 字典——工具 永远不要提高 到MCP客户端
或MCP消费者。
其他超时错误显示为 {"code": "TIMEOUT"}.
数据模式
所有数据对象都是纯Python @dataclass 实例(没有Pydantic在
服务器核心):
| 数据类 | 字段 |
|---|---|
ExtractedTable |
content: str, table_type: str = "unknown", location: str = "", metadata: Dict |
ExtractedImage |
image_path: str, ocr_text: str, metadata: Dict, confidence: float = 0.0 |
ExtractedURL |
url: str, context: str, source_location: str, confidence: float = 0.0, url_type: str = "general" |
ProcessedDocument |
content: str, metadata: Dict, tables: List[ExtractedTable], images: List[ExtractedImage], extracted_urls: List[ExtractedURL], detected_language: str = "en" |
语言检测
这 detect_language 工具(services/language.py)使用a 多轮
多数票 鲁棒性算法:
- 取输入文本的前5000个字符
- 跑
langdetect.detect()3次 - 多数票通过
Counter.most_common(1) DetectorFactory.seed = 0为了再现性
回退:
langdetect未安装→ 回报"en"- 文本<10个字符→ 回报
"en" - 任何异常→ 回报
"en"
支持的语言(名称映射): 英语、西班牙语、法语、德语、意大利语, 葡萄牙语、印地语、孟加拉语、泰卢固语、泰米尔语、马拉地语、马拉雅拉姆语、卡纳达语, 古吉拉特语、旁遮普语、乌尔都语、汉语、日语(18种语言)。
支持格式
| 类别 | 格式 | 处理器库 | 主要功能 |
|---|---|---|---|
| 文档 | PyMuPDF(fitz) |
基于骰子的提取,具有布局保留、页面标记和2级回退功能 | |
| DOCX | python-docx |
标题层次结构,表格提取为Markdown | |
| PPTX | python-pptx |
每张幻灯片标题/正文/注释/表格,超链接提取 | |
| TXT | 内置 | UTF-8解码 | |
| HTML | BeautifulSoup / WebBaseLoader |
双重回退 | |
| 表格 | XLSX | pandas + openpyxl |
表头自动检测(前10行评分)、列分析、跨表关系 |
| CSV | pandas |
与XLSX相同的格式化管道 | |
| 图片 | PNG、JPEG、JPG | pytesseract + Pillow |
带置信度分数的逐字OCR |
输出截断限制
| 内容类型 | 最大长度 |
|---|---|
| 完整文档内容 | 50000个字符 |
| 单个块文本 | 5000个字符 |
| 每个表的表内容 | 5000个字符 |
| 每张纸XLSX行 | 20行 |
环境变量
服务器变量(在中设置 .env 项目根)
| 变量 | 必填 | 默认 | 描述 |
|---|---|---|---|
MCP_RATE_LIMIT_RPM |
没有 | 60 |
每用户速率限制(请求/分钟);全球上限为该值的5倍 |
MCP_REQUEST_TIMEOUT |
没有 | 300 |
默认工具超时时间(秒) |
GPU_CONCURRENCY |
没有 | 2 |
最大并发FAISS构建/检索操作(GPU信号量) |
HUGGINGFACE_TOKEN |
否 | -- | HuggingFace模型访问权限(适用于门控模型) |
GOOGLE_API_KEY/OPENAI_API_KEY是 仅 需要在client/代理--服务器没有LLM。
客户端变量(in client/.env)
| 变量 | 必填 | 默认 | 描述 |
|---|---|---|---|
GOOGLE_API_KEY |
是(其中之一) | -- | Gemini LLM(默认) |
OPENAI_API_KEY |
是(其中之一) | -- | OpenAI回退 |
MCP_SERVER_URL |
没有 | http://127.0.0.1:8000/mcp |
MCP服务器端点 |
可选跟踪变量
| 变量 | 目的 |
|---|---|
LANGCHAIN_API_KEY |
LangSmith跟踪密钥 |
LANGSMITH_TRACING |
启用LangSmith跟踪 |
LANGSMITH_ENDPOINT |
自定义跟踪端点 |
LANGCHAIN_PROJECT |
LangSmith项目名称 |
客户端配置示例
VS代码副本-MCP(.vscode/mcp.json)
{
"servers": {
"rag-pipeline": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp"
}
}
}
克劳德桌面(claude_desktop_config.json)
{
"mcpServers": {
"rag-pipeline": {
"url": "http://127.0.0.1:8000/mcp"
}
}
}
光标IDE(.cursor/mcp.json)
{
"mcpServers": {
"rag-pipeline": {
"url": "http://127.0.0.1:8000/mcp"
}
}
}
开发指南
运行服务器
# MCP server (streamable-http)
python -m mcp_server 2>&1
# MCP server (stdio — for piped agent connections)
python -m mcp_server --transport stdio
# Development mode (auto-reload on code changes)
python -m mcp_server --reload
添加新工具
- 在中创建您的函数
tools/query.py,tools/extract.py,或tools/utility.py - 用…装饰
@mcp.tool()然后@guarded(timeout=...):@mcp.tool() @guarded(timeout=120) async def my_new_tool(document_url: str) -> dict: validate_url(document_url) # ... implementation ... return {"result": "..."} - 该工具通过模块导入自动注册
server.py - 更新
resources/__init__.py将该工具包含在rag://tool-descriptions
添加新的文档处理程序
- 在中创建处理器函数
processors/ - 在中添加文件扩展名映射
processors/__init__.py→detect_document_type() - 将新型电线连接到
TargetedDocumentProcessor.process_document() - 在中添加专用提取工具(可选)
tools/extract.py - 在中添加块大小配置文件
services/chunking.py→_get_chunk_params()
关键边缘案例和回退行为
| 场景 | 行为 |
|---|---|
| 缺少可选依赖项(pytesseract、langdetect等) | 功能标志会正常禁用——不会崩溃 |
| PDF提取失败 | 2级回退:基于字典→ 原始文本→ 空字符串 |
| HTML处理失败 | 从 WebBaseLoader 到 BeautifulSoup |
| 未知文档类型 | 通过UTF-8解码处理为纯文本 errors="replace" |
| 任何处理器抛出 | Dispatcher捕获并回退到原始UTF-8解码 |
| 空内容 | 返回 {chunks: [], chunk_count: 0} 或 {results: [], total_chunks_indexed: 0} |
| 重新排序失败 | 记录为警告,返回截断的相似性结果 |
| 下载失败 | 1s/3s/5s回退重试3次,然后引发 DownloadError |
| 输出太大 | 内容限制为50K字符,表限制为5K,XLSX限制为20行 |
top_k 超出范围 |
夹紧: max(1, min(top_k, 20)) |
| 图像临时文件 | 已清理 finally 即使失败 |
| 并发模型加载 | 通过双重检查锁定实现线程安全 threading.Lock |
许可证
麻省理工学院