Hybrid RAG با BM25، Reranker و Qdrant: راهنمای مهندسی کامل برای زیرساخت‌های ایرانی

راهنمای جامع مهندسی برای پیاده‌سازی Hybrid Retrieval‑Augmented Generation (RAG) با ترکیب BM25، Reranker و Qdrant؛ شامل معماری، مقایسه فنی، کد نمونه، چالش‌های بومی‌سازی در ایران و پرسش‌های متداول.

Hybrid RAG ترکیبی از BM25 برای بازیابی متنی دقیق و Qdrant برای جستجوی برداری مقیاس‌پذیر است؛ با افزودن یک Reranker می‌توان دقت نهایی را تا ۲۲٪ بهبود داد و latency پرس‌و‌جو را زیر ۱۲۰ ms در زیرساخت‌های ایرانی حفظ کرد.

معرفی Hybrid RAG و ضرورت ترکیب BM25 با بردارهای توزیعی

Hybrid Retrieval‑Augmented Generation (RAG) رویکردی است که مزایای بازیابی متنی کلاسیک (BM25) و جستجوی برداری (مانند Qdrant) را در یک لولهٔ یکپارچه ترکیب می‌کند. در محیط‌های با داده‌های فارسی، BM25 به‌دلیل توانایی تحلیل دقیق توکن‌های زبان، اشکال املایی و نگارشی را بهتر مدیریت می‌کند؛ در حالی که بردارهای توزیعی توانایی درک معانی معنایی عمیق را فراهم می‌آورند.

چرا BM25؟

  • سرعت ثابت: زمان پاسخ معمولاً زیر ۲ ms برای ایندکس‌های زیر ۲ میلیون سند است.
  • حافظه کم: فقط نیاز به ایندکس معکوس دارد؛ مصرف RAM حدود ۲‑۳ GB برای ۲ میلیون سند.
  • دقت در جستجوی دقیق: برای عبارات کوتاه و کلیدواژه‌های خاص فارسی بسیار مؤثر است.

چرا Qdrant؟

  • پشتیبانی از HNSW: زمان جستجو در مقیاس ۱‑۲ ms برای ۱۰ میلیون بردار 384‑بعدی.
  • قابلیت بومی‌سازی: می‌توان بر روی سرورهای داخل کشور (Ubuntu 22.04، Docker) مستقر شد.
  • قابلیت فیلترگذاری: فیلترهای متا‑دیتا برای محدود کردن دامنهٔ جستجو (مثلاً برچسب‌های حوزهٔ مالی).

معماری ترکیبی RAG با Reranker

معماری پیشنهادی شامل چهار ماژول اصلی است:

┌─────────────────────┐   ┌─────────────────────┐
│  Query Encoder (LLM)│   │   BM25 Engine       │
└─────────┬───────────┘   └───────┬─────────────┘
          │                     │
          ▼                     ▼
   ┌─────────────────────┐   ┌─────────────────────┐
   │  Vectorizer (Sentence│   │  Qdrant (HNSW)      │
   │   Transformers)    │   └───────┬─────────────┘
   └───────┬─────────────┘           │
           │                       ▼
           ▼               ┌─────────────────────┐
   ┌─────────────────────┐ │   Hybrid Merger      │
   │  Top‑K Fusion (k=20)│ └───────┬─────────────┘
   └───────┬─────────────┘         │
           │                       ▼
           ▼               ┌─────────────────────┐
   ┌─────────────────────┐ │   Reranker (MiniLM) │
   │  Final Scorer (LLM) │ └───────┬─────────────┘
   └─────────────────────┘         │
                                     ▼
                               ┌───────────────┐
                               │   Generator   │
                               └───────────────┘

در این معماری، پس از استخراج بردارهای پرس‌و‌جو توسط Encoder، هم‌زمان دو مسیر BM25 و Qdrant فعال می‌شوند. خروجی‌های هر دو مسیر به‌صورت اسکور وزن‌دار (به‌عنوان مثال 0.6 برای BM25 و 0.4 برای بردار) ترکیب می‌شوند و ۲۲ سند برتر به Reranker ارسال می‌گردند. Reranker که معمولا یک مدل MiniLM‑v2 بهینه‌شده برای فارسی است، اسکور نهایی را محاسبه و سندهای منتخب را به Generator می‌فرستد.

پارامترهای کلیدی

  • k‑BM25: 50 (تعداد اسناد اولیه)
  • k‑Vector: 50 (تعداد اسناد برداری)
  • k‑Fusion: 20 (نتیجه ترکیبی)
  • Batch Size Reranker: 16‑32 (به‌ازای هر درخواست)
  • Latency هدف: ≤ 120 ms (پایان‑به‑پایان)

بهینه‌سازی جستجو با BM25 و Qdrant در زیرساخت‌های ایرانی

در ایران، محدودیت پهنای باند و هزینهٔ انرژی می‌تواند بر عملکرد سیستم تاثیر بگذارد. برای بهینه‌سازی موارد زیر توصیه می‌شود:

  • استفاده از SSD NVMe 2 TB: زمان دسترسی به ایندکس BM25 زیر 1 ms و به‌روزرسانی‌های روزانه بدون Downtime.
  • کاهش ابعاد بردار: از مدل‌های Distil‑BERT‑FA یا MiniLM‑v2 با 384‑بعد به‌جای 768‑بعد استفاده کنید؛ مصرف حافظه RAM برای 10 M بردار حدود 12 GB می‌شود.
  • پیکربندی HNSW: ef_construction=256، M=48؛ این تنظیمات تعادل بین سرعت (≈ 1.8 ms) و دقت (Recall@10 ≈ 0.92) را فراهم می‌کند.
  • Cache لایهٔ BM25: با Redis‑Cluster در داخل دیتاسنتر می‌توانید نتایج مکرر را تا 30 % سرعت بخشید.

نمونه پیکربندی Qdrant (Docker‑Compose)

version: "3.8"
services:
  qdrant:
    image: qdrant/qdrant:v1.7.0
    container_name: qdrant
    ports:
      - "6333:6333"
    volumes:
      - ./data/qdrant:/qdrant/storage
    environment:
      - QDRANT__SERVICE__GRPC_PORT=6334
      - QDRANT__LOGGING__LEVEL=INFO
    deploy:
      resources:
        limits:
          memory: 24G
          cpus: "8"
        reservations:
          memory: 16G
          cpus: "4"

پیاده‌سازی گام‌به‌گام و نمونه کد Python

در ادامه یک اسکریپت سادهٔ Python نشان می‌دهد چگونه می‌توان پرس‌و‌جو را به‌صورت ترکیبی اجرا کرد. این کد از کتابخانهٔ qdrant-client، elasticsearch و transformers استفاده می‌کند.

import os
from elasticsearch import Elasticsearch
from qdrant_client import QdrantClient
from transformers import AutoTokenizer, AutoModel
import torch

# ---------- تنظیمات ----------
ES_HOST = os.getenv("ES_HOST", "http://localhost:9200")
QD_HOST = os.getenv("QD_HOST", "http://localhost:6333")
MODEL_NAME = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"

es = Elasticsearch([ES_HOST])
qdrant = QdrantClient(url=QD_HOST)

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModel.from_pretrained(MODEL_NAME)
model.eval()

def embed(text: str):
    inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=256)
    with torch.no_grad():
        vectors = model(**inputs).last_hidden_state.mean(dim=1)
    return vectors.squeeze().cpu().numpy()

def hybrid_search(query: str, top_k: int = 20):
    # 1️⃣ BM25
    bm25_res = es.search(index="fa_docs", body={"query": {"match": {"content": query}}}, size=top_k)
    bm25_ids = [hit["_id"] for hit in bm25_res["hits"]["hits"]]

    # 2️⃣ Vector search
    q_vec = embed(query)
    vec_res = qdrant.search(collection_name="fa_vectors", query_vector=q_vec.tolist(), limit=top_k)
    vec_ids = [point.id for point in vec_res]

    # 3️⃣ Fusion (simple weighted sum)
    scores = {}
    for doc_id in bm25_ids:
        scores[doc_id] = scores.get(doc_id, 0) + 0.6  # وزن BM25
    for doc_id in vec_ids:
        scores[doc_id] = scores.get(doc_id, 0) + 0.4  # وزن برداری
    # مرتب‌سازی و برش
    ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_k]
    return [doc_id for doc_id, _ in ranked]

if __name__ == "__main__":
    q = "روش‌های بهینه‌سازی هزینهٔ پردازش در دیتاسنترهای ایران"
    results = hybrid_search(q)
    print("Top documents:", results)

به‌کارگیری Reranker MiniLM

پس از دریافت لیست ترکیبی، می‌توان اسکورهای دقیق‌تر را با MiniLM‑v2 محاسبه کرد. نمونهٔ زیر نشان می‌دهد چگونه می‌توانید اسکورهای نهایی را به‌دست آورید:

from transformers import AutoModelForSequenceClassification, AutoTokenizer

reranker = AutoModelForSequenceClassification.from_pretrained("microsoft/MiniLMv2-L12-H384-uncased")
reranker_tokenizer = AutoTokenizer.from_pretrained("microsoft/MiniLMv2-L12-H384-uncased")

def rerank(query, doc_texts):
    inputs = reranker_tokenizer([query]*len(doc_texts), doc_texts, truncation=True, padding=True, return_tensors="pt")
    with torch.no_grad():
        scores = reranker(**inputs).logits.squeeze()
    ranked = sorted(zip(doc_texts, scores.tolist()), key=lambda x: x[1], reverse=True)
    return ranked

چالش‌های بومی‌سازی، امنیت و مقیاس‌پذیری در زیرساخت ایران

پیاده‌سازی Hybrid RAG در ایران با چالش‌های خاصی مواجه است:

  • قابلیت بومی‌سازی شبکه: اکثر سرویس‌های ابری خارجی مسدود یا دارای تاخیر بالا هستند؛ بنابراین استفاده از دیتاسنترهای داخل‌کشوری (مثلاً ابر آروان یا ابر دیجی‌کالا) ضروری است.
  • محدودیت پهنای باند: برای انتقال بردارهای 384‑بعدی (≈ 1.5 KB) به‌صورت batch باید از فشرده‌سازی protobuf یا msgpack استفاده کرد تا مصرف باند زیر ۵ Mbps برای ۱۰ k درخواست در ثانیه باشد.
  • امنیت داده‌ها: به‌کارگیری TLS 1.3 برای تمام ارتباطات، رمزنگاری در‑حفظ (AES‑256‑GCM) برای ایندکس‌های Qdrant و استفاده از نقش‑محور (RBAC) در Elasticsearch الزامی است.
  • مقیاس‌پذیری افقی: برای افزایش توان پردازشی می‌توان از Kubernetes با Helm chart مخصوص Qdrant استفاده کرد؛ هر نود می‌تواند ۲۴ CPU و 64 GB RAM داشته باشد و افقی‌سازی HNSW با Sharding به‌صورت native پشتیبانی می‌شود.
  • هزینه انرژی: با به‌کارگیری سرورهای ARM (مثل Ampere Altra) می‌توان مصرف برق را تا ۳۰٪ کاهش داد؛ این موضوع در مراکز داده‌های محلی که هزینه انرژی بالا است، بسیار مهم است.

مقایسه فنی روش‌های مختلف Retrieval‑Augmented Generation

روش پایگاه داده سرعت متوسط (ms) دقت (Recall@10) حافظه مصرفی (GB) هزینه عملیاتی (IRR/ماه)
Hybrid RAG (BM25 + Qdrant + Reranker) Elasticsearch + Qdrant 95 0.92 38 ۲٫۵ میلیون
Pure RAG (FAISS + LLM) FAISS (IVF‑PQ) 130 0.86 28 ۲ میلیون
Sparse Retrieval (Elasticsearch فقط) Elasticsearch 70 0.78 22 ۱٫۲ میلیون
Hybrid با Milvus Milvus + BM25 110 0.89 34 ۲٫۲ میلیون

نتیجه‌گیری و توصیه‌های نهایی

Hybrid RAG با ترکیب BM25، Qdrant و یک Reranker سبک، بهترین تعادل بین سرعت (≤ 120 ms) و دقت (Recall ≈ 0.92) را برای کاربردهای فارسی در زیرساخت‌های داخلی فراهم می‌کند. برای بومی‌سازی موفق، حتماً به نکات امنیتی، فشرده‌سازی داده و استفاده از سرورهای ARM یا GPU‑T4 توجه کنید. با پیاده‌سازی جدول مقایسه‌ای فوق می‌توانید تصمیم‌گیری هزینه‑به‑دقت را به‌صورت داده‑محور انجام دهید.

پرسش‌های متداول مهندسی (FAQ)

۱. آیا می‌توان از مدل‌های LLM بزرگتر (مثل GPT‑4) در این معماری استفاده کرد؟

بله، اما هزینهٔ پردازش و حافظه به‌طور نمایی افزایش می‌یابد؛ برای حفظ latency زیر 200 ms توصیه می‌شود از مدل‌های 7‑13 B با quantization 4‑bit استفاده کنید.

۲. چه زمانی باید از Sharding در Qdrant استفاده کرد؟

اگر حجم داده‌ها بیش از ۲۵ میلیون سند (≈ 15 TB ذخیره‌سازی) باشد یا درخواست‌های همزمان بیش از ۲ k RPS داشته باشید، Sharding افقی باعث توزیع بار و کاهش latency می‌شود.

۳. آیا می‌توان Reranker را به‌صورت سرویس مستقل (microservice) اجرا کرد؟

بله؛ با Docker‑Compose یا Kubernetes می‌توانید Reranker را به‌عنوان یک سرویس gRPC جداگانه مستقر کنید. این کار امکان مقیاس‌پذیری مستقل (horizontal pod autoscaling) و به‌روزرسانی بدون توقف سرویس اصلی را فراهم می‌سازد.