رفتن به محتوای اصلی
هوش مصنوعی۲۰ شهریور ۱۴۰۵۸ دقیقه مطالعه

جستجوی ترکیبی روی لبه: پیاده‌سازی هم‌زمان BM25 و برداری با SQLite-vec بدون دیتابیس ابری

چرا اتکای صرف به جستجوی وکتوری در سیستم‌های RAG باعث خطاهای مرگبار در کلیدواژه‌ها می‌شود؟ راهنمای پیاده‌سازی Hybrid Search فوق‌سریع و سبک با تلفیق FTS5 و SQLite-vec.

#rag#sqlite-vec#bm25#hybrid-search#vector-database#embeddings

سلام رفقا!

با داغ شدن تب هوش مصنوعی و تبادل اطلاعات در سیستم‌های RAG (بازیابی کانتکست برای مدل‌ها)، اکثر تیم‌ها اولین کاری که می‌کنند این است: یک دیتابیس برداری ابری (مثل Pinecone یا Weaviate) می‌خرند، تمام متن‌ها را ایمبد می‌کنند و یک جستجوی شباهت کسینوسی (Cosine Similarity) روی آن می‌زنند.

اما پس از انتشار در محیط عملیاتی، صدای کاربران درمی‌آید:

  • کاربر شماره فاکتور خاصی مثل INV-2026-908 را جستجو می‌کند، ولی مدل فاکتور دیگری برمی‌گرداند!
  • کاربر اسم یک تابع خاص در کدبیس مثل calculateTaxesV2 را سرچ می‌کند و ایمبدینگ معنایی، تابعی کاملاً بی‌ربط را فقط چون کلمات عمومی مشابهی دارد ترجیح می‌دهد!

این همان نقطه‌ای است که درک نیاز به Hybrid Search (جستجوی ترکیبی) ضروری می‌شود: ترکیب قدرت جستجوی لغوی دقیق (BM25 / Keyword) با جستجوی معنایی عمیق (Dense Vector). و جالب‌ترین بخش؟ شما اصلاً نیازی به سرورهای سنگین یا هزینه‌های ابری ندارید؛ می‌توان همه این کارها را روی یک دیتابیس محلی سبک با SQLite FTS5 + sqlite-vec اجرا کرد.


۱. ضعف پنهان جستجوی صرفاً وکتوری چیست؟

مدل‌های ایمبدینگ معنایی برای درک موضوعات کلی عالی هستند. مثلاً اگر سرور کنید «چطور سرعت سایت را بالا ببرم»، وکتور به مقالاتی درباره «بهینه‌سازی دیتابیس و فشرده‌سازی تصاویر» لینک می‌شود.

اما وکتورها در موارد زیر کور هستند:

  1. شناسه‌های یکتا و کدهای رهگیری (کد خطاها مثل ERR_CONN_RESET_04)
  2. اصطلاحات تخصصی و کلمات اختصاری جدید
  3. جستجوی املایی دقیق (Exact match)

اینجاست که الگوریتم سنتی BM25 (که نسخه ارتقایافته TF-IDF است) نجات‌بخش می‌شود. ترکیب امتیاز این دو متد، پایداری نتایج جستجو را چندین برابر می‌کند.


۲. معماری سبک با SQLite: چرا ترکیب FTS5 و sqlite-vec؟

به جای نگهداری دو سیستم مجزا (مثلاً Elasticsearch برای متن + Qdrant برای وکتور) که نگهداری و سینک بودن آن‌ها مصیبت مهندسی است، می‌توان همه چیز را در یک فایل دیتابیس منفرد تجمیع کرد:

  • ماژول FTS5 بومی در SQLite: ساخت ایندکس فوق‌سریع BM25 روی فیلدهای متنی
  • اکستنشن سبک sqlite-vec: اجرای محاسبات فاصله کسینوسی و L2 مستقیم با دستورات استاندارد SQL روی آرایه‌های برداری

بیایید ساختار جدول را در عمل ببینیم:

-- ۱. ساخت جدول اطلاعات اصلی
CREATE TABLE documents (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    body TEXT NOT NULL
);

-- ۲. ساخت جدول جستجوی لغوی (FTS5 برای BM25)
CREATE VIRTUAL TABLE documents_fts USING fts5(
    title,
    body,
    content='documents',
    content_rowid='id'
);

-- ۳. ساخت جدول جستجوی برداری (sqlite-vec)
CREATE VIRTUAL TABLE vec_documents USING vec0(
    doc_id INTEGER PRIMARY KEY,
    embedding float[768] -- ابعاد مدل‌های معروفی مثل nomic-embed یا bge
);

۳. فرمول Reciprocal Rank Fusion (RRF): تجمیع جادویی نتایج

چطور دو مقیاس کاملاً نامتجانس را با هم ترکیب کنیم؟ امتیاز BM25 مقداری مثبت و متغیر است، در حالی که شباهت کسینوسی عددی بین -1 تا +1 است. جمع کردن خام این دو عدد فاجعه‌بار است.

استاندارد صنعت برای این چالش، تکنیک Reciprocal Rank Fusion (RRF) است. به جای جمع کردن امتیازها، رتبه سندها در هر دو لیست را با هم ترکیب می‌کنیم:

RRF\_Score(d) = \sum_{m \in M} rac{1}{k + rank_m(d)}

مقدار ثابت $k$ معمولاً عدد ۶۰ انتخاب می‌شود تا اثر داکیومنت‌های انتهای لیست کنترل شود.

# پیاده‌سازی تجمیع نتایج RRF در پایتون
def reciprocal_rank_fusion(keyword_results, vector_results, k=60):
    scores = {}
    
    # پردازش رتبه‌های جستجوی کلمات کلیدی
    for rank, doc_id in enumerate(keyword_results):
        scores[doc_id] = scores.get(doc_id, 0.0) + (1.0 / (k + rank + 1))
        
    # پردازش رتبه‌های جستجوی وکتوری
    for rank, doc_id in enumerate(vector_results):
        scores[doc_id] = scores.get(doc_id, 0.0) + (1.0 / (k + rank + 1))
        
    # مرتب‌سازی نزولی بر اساس بیشترین امتیاز کلی
    sorted_docs = sorted(scores.items(), key=lambda item: item[1], reverse=True)
    return [doc_id for doc_id, score in sorted_docs]

با این روش، اگر یک سند هم از نظر معنایی رتبه بالایی بگیرد و هم کلیدواژه دقیق کاربر در آن پیدا شود، قاطعانه به صدر نتایج جهش می‌کند.


۴. مزایای عملیاتی برای سیستم‌های محلی و سرورهای خلوت

  1. Zero Dev-Ops: کل زیرساخت جستجوی شما فقط یک فایل دیتابیس است. بدون نیاز به کانتینرهای چند گیگابایتی JVM، ردیس یا اشتراک‌های دلاری سنگین.
  2. تراکنش‌های امن (ACID): اگر سندی حذف شود یا تغییر کند، ایندکس لغوی و وکتور در همان تراکنش واحد آپدیت می‌شوند؛ دیگر نگران از دست رفتن داده‌های سینک‌شده نباشید.
  3. اجرا روی دستگاه کاربر یا سرورهای ارزان: اکستنشن sqlite-vec با زبان C ساده نوشته شده و به هیچ کتابخانه سنگین خارجی وابسته نیست و حتی روی سیستم‌های با حافظه ۵۱۲ مگابایت هم روان پرواز می‌کند.

نتیجه‌گیری

سادگی بالاترین درجه بلوغ مهندسی است. قبل از اینکه معماری RAG خود را به ده‌ها سرویس خارجی وکتوری و کلاودهای پیچیده گره بزنید، توانایی‌های شگفت‌انگیز ابزارهای توکار مثل SQLite FTS5 و sqlite-vec را محک بزنید. یک خط لوله جستجوی هیبریدی محلی، هم کاربران شما را از خطاهای مضحک املایی نجات می‌دهد و هم ساختار فنی سیستم شما را ساده و پایدار نگه می‌دارد.

گفتوگو

این مطلب برایتان مفید بود؟

اگر سؤالی دارید یا میخواهید عمیقتر برویم، از راه تماس بپرسید — جواب میدهم.