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
curl · Postman · Frontend"])
C2(["🤖 AI Agent + LLM
Claude · Copilot · LangChain"])
%% ── Transport ────────────────────────────────────────────────
subgraph Transport[" 🔌 Transport Layer "]
direction LR
MCP["⚡ MCP Protocol
FastMCP · /mcp
streamable-http · stdio"]
end
%% ── Middleware ────────────────────────────────────────────────
subgraph MW[" 🛡️ Middleware Pipeline "]
direction LR
M2["⏱️ Rate Limit
Token bucket"]
M3["✅ Validation
URL · text"]
M4["📋 Logging
JSON · Request-ID"]
M5["⏳ Timeout
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
HTTP · 3× retry"]
CACHE["💾 3-Layer Cache
Download · Document
Retriever · 30 min TTL"]
CHUNK["✂️ Adaptive Chunking
Type-aware sizes
Importance scoring"]
RET["🔍 Retrieval Engine
FAISS vector search
Cross-encoder rerank
Diversity filter"]
LANG["🌍 Language Detection
3-round sampling"]
end
%% ── Processors ───────────────────────────────────────────────
subgraph Processors[" 📄 Document Processors "]
direction LR
PDF["PDF
PyMuPDF"]
DOCX["DOCX
python-docx"]
PPTX["PPTX
python-pptx"]
XLSX["XLSX · CSV
pandas"]
IMG["Image
pytesseract"]
HTML["HTML · TXT
BeautifulSoup"]
URLP["URL extract
regex"]
end
%% ── Models ───────────────────────────────────────────────────
subgraph Models[" 🧠 ML Models — eager-loaded · no LLM "]
direction LR
EMB1["🚀 MiniLM-L6-v2
Fast embeddings"]
EMB2["🎯 BGE-small-en-v1.5
Accurate embeddings"]
RERANK["📊 ms-marco-MiniLM
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_* 为了 原始提取等)。
提示: 您也可以直接传递一个一次性查询: ``bash 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 | /temp_files/ | 临时下载、OCR暂存 |
REQUEST_LOGS_PATH | /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 |
| \/`** 并缓存在内存中。 |
在后续查询中(即使在重新启动后),索引也会从磁盘加载 通过 FAISS.load_local() 而不是重建。
并发控制 (从 core/concurrency.py):
- GPU信号量 --FAISS构建和检索运行 run_in_gpu_pool(), 限于 GPU_CONCURRENCY (默认2)同时操作。阻止 在突发交通中OOM。 - 构建聚合 --如果10个请求到达同一个URL,则只有一个 构建索引;其他9个等待每个URL asyncio.Lock,然后阅读 从缓存中。消除了冗余的嵌入工作。
- 嵌入模型选择:
- ≤50块→ get_embeddings_fast() (MiniLM-L6-v2)——交叉编码器重新排序补偿 - > 50块→ get_embeddings_accurate() (BGE-small-en-v1.5)
- 相似性搜索 —
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) 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 |
______________________________________________________________________
许可证
麻省理工学院
