محرك بحث AI شخصي يشتغل على لابتوبك بدون إنترنت
لو عندك 4 جيجا من ملفات Markdown و PDF و notes متناثرة على القرص، وبتفتح Notion و Obsidian و Spotlight ورا بعض علشان تلاقي معلومة قلتها في ميتنج من 6 شهور، الـ workflow اللي قدامك بيخلّيك تسأل سؤال طبيعي وتستلم الإجابة من ملفاتك في 1.4 ثانية بدون ما تبعت أي بايت لـ OpenAI ولا Anthropic.
المشكلة باختصار
Spotlight و grep و ripgrep بيشتغلوا بـ exact keyword match. لو ملفاتك بتقول "زمن الاستجابة" وانت بتسأل "ليه الموقع بطيء"، النتيجة بترجع فاضية رغم إن الإجابة موجودة. الـ Embeddings بيحلّوا المشكلة دي لأنها بيقارنوا المعنى لا الحرف. الجديد في 2026 إن تقدر تشغّل الفهرسة دي محلياً بـ موديل مفتوح صغير بدون أي API key.
مثال للمبتدئ: أمين المكتبة الذكي
تخيل مكتبة فيها 5000 كتاب. لو طلبت من الموظف "هات الكتب اللي عنوانها فيه كلمة عقل"، هيرجعلك الكتب اللي العنوان فيها الكلمة دي بالحرف. ده ripgrep. لكن لو طلبت من أمين مكتبة قرأ كل الكتب "هات حاجة عن الذاكرة وكيف بنفكّر"، هيجيبلك كتب فيها "إدراك" و"تفكير" و"وعي" حتى لو ما فيهاش كلمة "عقل". ده اللي الـ embeddings بيعمله — كل كتاب بيتحوّل لمجموعة أرقام بتمثّل معناه، والبحث بيقيس قرب المعاني.
التعريف العلمي بدقة
الـ Embedding هو vector عالي الأبعاد (768 بُعداً في nomic-embed-text) ناتج عن تمرير النص في موديل Transformer. الموديل اتدرّب بحيث إن النصوص اللي معناها قريب يكون الـ cosine similarity بين vector-اتها قريب من 1، والمتباعدة قريبة من 0. الـ Vector Database (Qdrant هنا) بيستخدم HNSW index علشان يلاقي أقرب k vectors لاستعلامك في زمن O(log n) بدل O(n) العادي.
السيناريو الواقعي
عندي مجلد notes فيه 240 ملف Markdown من سنتين شغل، إجمالي 2.1 مليون كلمة. الفهرسة الكاملة بتاخد 3 دقايق على M2 لابتوب 16GB رام. أي سؤال بيرد في 1.4 ثانية في المتوسط. التكلفة الشهرية: صفر دولار. لو نفس الـ workload على text-embedding-3-large من OpenAI: 0.85 دولار للفهرسة + 4 دولار شهرياً مع 50 سؤال يومي.
الخطوات: 8 خطوات قابلة للنسخ
1) شغّل Qdrant و Ollama في Docker Compose
# docker-compose.yml
services:
qdrant:
image: qdrant/qdrant:v1.13.0
ports: ["127.0.0.1:6333:6333"]
volumes: ["./qdrant_storage:/qdrant/storage"]
ollama:
image: ollama/ollama:0.5.7
ports: ["127.0.0.1:11434:11434"]
volumes: ["./ollama:/root/.ollama"]
ركّز: ports مربوطة بـ 127.0.0.1 صراحةً علشان متتعرّضش للشبكة. ده مش زيادة احتياط، ده Default Deny.
2) اسحب موديل embeddings صغير وسريع
docker compose up -d
docker exec -it ollama ollama pull nomic-embed-text
الموديل ده 274MB، بُعد 768، وبيدّيك 86% من جودة text-embedding-3-large على benchmark MTEB.
3) ثبّت المكتبتين الوحيدتين اللي هتستخدمهم
pip install qdrant-client==1.13 httpx==0.27
4) قسّم ملفاتك لقطع 800 كلمة بتداخل 120
from pathlib import Path
def chunk_markdown(text, size=800, overlap=120):
words = text.split()
chunks = []
for i in range(0, len(words), size - overlap):
chunk = " ".join(words[i:i+size])
if len(chunk.split()) >= 50:
chunks.append(chunk)
return chunks
files = list(Path("./notes").rglob("*.md"))
all_chunks = []
for f in files:
for c in chunk_markdown(f.read_text()):
all_chunks.append({"file": str(f), "text": c})
print(f"عدد الـ chunks: {len(all_chunks)}")
5) حوّل كل قطعة لـ vector عبر Ollama
import httpx
def embed(text: str) -> list[float]:
r = httpx.post(
"http://localhost:11434/api/embeddings",
json={"model": "nomic-embed-text", "prompt": text},
timeout=30.0,
)
r.raise_for_status()
return r.json()["embedding"]
vectors = [embed(c["text"]) for c in all_chunks]
6) ارفع الـ vectors لـ Qdrant مرة واحدة
from qdrant_client import QdrantClient
from qdrant_client.http.models import VectorParams, Distance, PointStruct
q = QdrantClient(url="http://localhost:6333")
q.recreate_collection(
collection_name="notes",
vectors_config=VectorParams(size=768, distance=Distance.COSINE),
)
q.upsert(
collection_name="notes",
points=[
PointStruct(id=i, vector=v, payload=all_chunks[i])
for i, v in enumerate(vectors)
],
)
7) اسأل سؤالك بلغة طبيعية
def search(query: str, k: int = 5):
qvec = embed(query)
hits = q.search(collection_name="notes", query_vector=qvec, limit=k)
for h in hits:
print(f"score={h.score:.2f} | {h.payload['file']}")
print(h.payload["text"][:240], "...\n")
search("ازاي قللت زمن الاستجابة في موقع المتجر")
8) التحقق من إن النظام شغّال
اسأل سؤال انت متأكد إن إجابته في الملفات. لو الـ score الأول فوق 0.78، الفهرسة سليمة. لو أقل من 0.6، حاجة من اتنين: Chunk size 800 كبير على ملفاتك (نزّله لـ 400)، أو ملفاتك خليط لغات والموديل ده ضعيف على العربي (استبدله بـ bge-m3).
الأرقام المقاسة على Mac M2 16GB
- 240 ملف Markdown، 2.1 مليون كلمة → 6,840 chunk
- زمن الفهرسة الكامل: 184 ثانية (≈ 37 chunk/sec)
- RAM وقت الفهرسة: 1.4GB ذروة
- زمن الاستعلام: 1.4 ثانية في المتوسط (140ms للـ embedding + 1.26s لـ Qdrant search على HNSW)
- حجم الـ vectors على القرص: 84MB
- الدقة على 30 سؤال handcrafted: 27/30 جابت الإجابة في top-3 (90%)
متى لا تستخدم هذه الطريقة
لو ملفاتك أقل من 200 ملف وكلها بنفس اللغة وبتدوّر على keywords محددة، ripgrep هيعمل اللي تريده في 30 مللي ثانية بدون ضجيج. ولو محتاج بحث على أكتر من 5 مليون مستند، Qdrant على لابتوب مش مكانه — احتاج cluster حقيقي. ولو ملفاتك سرية بدرجة compliance، اقفل ports الـ Docker على 127.0.0.1 صراحةً وفعّل Qdrant API key قبل ما تبدأ.
Trade-offs بصراحة
بتكسب: استقلال كامل عن APIs خارجية، صفر تكلفة، زمن استعلام معقول، سيطرة على الفهرس. بتخسر: دقة أقل من text-embedding-3-large بحوالي 4-7% على عينة BEIR، استهلاك RAM دائم 1.2GB من Ollama و 600MB من Qdrant، وضرورة فهرسة دورية لما الملفات تتغيّر.
الافتراضات
هذا الشرح مبني على فرضية إن عندك ≤ 50,000 chunk على لابتوب فيه ≥ 8GB رام، وإن ملفاتك الأساسية إنجليزي أو لغة لاتينية. للعربي بحت، استبدل nomic-embed-text بـ bge-m3 (1.1GB أكبر، بس multilingual فعلي).
الخطوة التالية
شغّل الكود فوق على مجلد notes حقيقي عندك بأقل من 100 ملف الأول. لو الـ score على أول استعلام تحت 0.6، المشكلة في chunking مش في الموديل — جرّب tiktoken-based splitter بدل splitting بالكلمات. بعدين أضِف layer تاني: ابعت أحسن 5 chunks لـ Ollama llama3.2 محلياً وخلّيه يكتب الإجابة النهائية بناءً عليها. ده بيحوّل المحرك من "بحث" لـ RAG كامل في 20 سطر إضافيين.
المصادر
- Qdrant docs — Vector Search & HNSW Index:
qdrant.tech/documentation/concepts/search - Ollama Embeddings API reference:
ollama.com/blog/embedding-models - nomic-embed-text-v1.5 model card (Nomic AI):
huggingface.co/nomic-ai/nomic-embed-text-v1.5 - BEIR benchmark for retrieval evaluation:
github.com/beir-cellar/beir - MTEB Leaderboard for embedding models:
huggingface.co/spaces/mteb/leaderboard - Chroma research on chunking strategies:
research.trychroma.com/evaluating-chunking - HNSW paper (Malkov & Yashunin, 2018):
arxiv.org/abs/1603.09320