文档处理服务器

RAG Document Server是一个纯确定性的文档处理工具服务器,提供文档分块和向量检索功能,适用于AI代理集成。

  • by Vamshi9415
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 Vamshi9415/docrag-mcp

使用说明

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

目录

  1. 快速开始
  2. 客户端代理
  3. MCP工具参考
  4. 项目结构
  5. 配置深度学习
  6. 安全和中间件管道
  7. 缓存架构
  8. 文档处理器——内部
  9. 自适应分块算法
  10. 检索引擎
  11. 急切的模型加载
  12. 结构化日志记录
  13. 错误层次结构
  14. 数据模式
  15. 语言检测
  16. 支持格式
  17. 环境变量
  18. 客户端配置示例
  19. 开发指南

快速开始

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 内部管道:

  1. 下载文档→ 处理它→ 自适应地将其分块
  2. 选择嵌入模型(如果≤50个块,则快速,否则精确——交叉编码器重新排序补偿)
  3. 从所有块构建FAISS向量索引
  4. 运行3次重复检索的相似性搜索(最多20个候选者)
  5. 使用交叉编码器重新排序(如果可用)
  6. 应用多样性过滤器(支持看不见的内容类型)
  7. 退货 top_k 最佳大块
  8. 缓存已处理的文档和FAISS检索器(由 sha256(url)[:16])

query_spreadsheet --pandas行查找:

  1. 下载XLSX/CSV文件
  2. 将所有工作表加载到pandas DataFrames中
  3. 在所有列中执行不区分大小写的子字符串匹配
  4. 将匹配的行作为带有工作表名称的字典返回
  5. 用于特定行查找(例如“查找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/ 结构化请求日志

如果这两个目录不存在,则会在导入时自动创建。

设备检测

在导入时运行一次:

  1. torch.cuda.is_available() → "cuda"
  2. torch.backends.mps.is_available() → "mps" (苹果硅)
  3. 回落到 "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
  1. 请求ID生成 — uuid4().hex[:12] 存储在a ContextVar 对于日志 整个调用堆栈的相关性。

  2. 速率限制 (check_rate_limit(tool_name, api_key))--两层令牌桶:

    • 每个用户存储桶:容量= rate_limit_rpm (默认60)每个API键
    • 全球桶:5×每用户速率(默认300 rpm)--服务器范围的安全上限
    • 重新填充率= rpm / 60.0 每秒令牌数
    • 延迟充值:每次充值代币 consume() 调用(无后台线程)
    • 每个用户的bucket在1000个条目时被FIFO驱逐,以防止内存泄漏
    • 加薪 RateLimitError 当每个用户或全局令牌耗尽时
  3. 超时执行 — asyncio.wait_for(fn(...), timeout=...):

    • 文档工具:300秒
    • 提取工具:120秒
    • 实用工具:30秒
    • 加薪 TimeoutError (被装修工抓住,作为 {"code": "TIMEOUT"})
  4. 结构化日志记录 --发射 tool.start, tool.success (随着时间的推移), 或 tool.timeout / tool.known_error / tool.unhandled_error 事件。

  5. 错误转换 --所有异常都会被捕获并转换为错误字典:

    • 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() 调用后,将运行以下驱逐序列:

  1. 清除已过期 --删除以下所有条目 now > expires_at
  2. 更新现有 --如果密钥已经存在,请先将其删除
  3. 字节限制 --虽然 total_bytes > max_download_bytes,驱逐最旧的入口
  4. 入境限制 --虽然 len(cache) >= max_entries,驱逐最旧的入口
  5. “最古老” =最小(最早)的条目 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
  1. 指标构建 — 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,然后阅读 从缓存中。消除了冗余的嵌入工作。
  2. 嵌入模型选择:

    • ≤50块→ get_embeddings_fast() (MiniLM-L6-v2)——交叉编码器重新排序补偿
    • 50块→ get_embeddings_accurate() (BGE-small-en-v1.5)

  3. 相似性搜索 — vectorstore.similarity_search(query, k=min(top_k * 3, 20)). 获取所需候选人数量的3倍(最多20人)。

  4. 交叉编码器重新排序 (如果 RERANK_AVAILABLE 和 use_reranking=True):

    • 创建 [query, chunk_text] 对
    • 分数通过 CrossEncoder.predict(pairs) 使用 ms-marco-MiniLM-L-6-v2
    • 按分数降序排序,取 top_k
    • 退路: 在任何异常情况下,都会记录警告并回退到截断的相似性结果
  5. 多样性过滤器 (_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 多轮 多数票 鲁棒性算法:

  1. 取输入文本的前5000个字符
  2. 跑 langdetect.detect() 3次
  3. 多数票通过 Counter.most_common(1)
  4. DetectorFactory.seed = 0 为了再现性

回退:

  • langdetect 未安装→ 回报 "en"
  • 文本<10个字符→ 回报 "en"
  • 任何异常→ 回报 "en"

支持的语言(名称映射): 英语、西班牙语、法语、德语、意大利语, 葡萄牙语、印地语、孟加拉语、泰卢固语、泰米尔语、马拉地语、马拉雅拉姆语、卡纳达语, 古吉拉特语、旁遮普语、乌尔都语、汉语、日语(18种语言)。


支持格式

类别 格式 处理器库 主要功能
文档 PDF 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

添加新工具

  1. 在中创建您的函数 tools/query.py, tools/extract.py,或 tools/utility.py
  2. 用…装饰 @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": "..."}
    
  3. 该工具通过模块导入自动注册 server.py
  4. 更新 resources/__init__.py 将该工具包含在 rag://tool-descriptions

添加新的文档处理程序

  1. 在中创建处理器函数 processors/
  2. 在中添加文件扩展名映射 processors/__init__.py → detect_document_type()
  3. 将新型电线连接到 TargetedDocumentProcessor.process_document()
  4. 在中添加专用提取工具(可选) tools/extract.py
  5. 在中添加块大小配置文件 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

许可证

麻省理工学院