ابنِ ذكاءً اصطناعياً محلياً باستخدام Ollama و Qwen 3: الـ RAG، والـ Agents، وما وراء ذلك.

٥ مارس ٢٠٢٦

Build Local AI with Ollama and Qwen 3: RAG, Agents, and Beyond

ملخص

  • قم بتشغيل نماذج لغوية كبيرة (LLMs) قوية بالكامل على جهازك — بدون مفاتيح API، ولا فواتير سحابية، ولا بيانات تخرج من شبكتك
  • ابنِ مسار RAG جاهز للإنتاج يقوم باستيعاب ملفات PDF وصفحات الويب والبيانات المهيكلة في مخزن متجهات (vector store) محلي
  • أنشئ وكلاء ذكاء اصطناعي ذاتيين (autonomous AI agents) مع ميزات استدعاء الأدوات، والذاكرة، والتفكير متعدد الخطوات
  • قم بتنسيق نماذج محلية متعددة لمهام متخصصة (البرمجة، التفكير، التضمين (embedding)، التلخيص)
  • قم بتحسين الأداء باستخدام استراتيجيات التكميم (quantization)، وضبط نافذة السياق، وإدارة ذاكرة وحدة معالجة الرسومات (GPU)
  • قم بتأمين إعداداتك باستخدام أفضل ممارسات الأمان لنشر الذكاء الاصطناعي محلياً
  • يتضمن كوداً كاملاً وقابلاً للتشغيل لكل مكون

ما ستتعلمه

  • كيف يقوم Ollama بتبسيط تعقيدات تشغيل نماذج LLMs محلياً ويوفر API متوافقاً مع OpenAI
  • بنية نماذج Qwen 3 الكثيفة (dense) ونماذج خليط الخبراء (Mixture-of-Experts) — ومتى تستخدم كل منها
  • بناء مسار RAG كامل: استيعاب المستندات، استراتيجيات التجزئة (chunking)، التضمين، تخزين المتجهات، الاسترجاع، والتوليد
  • إنشاء وكلاء ذكاء اصطناعي يمكنهم استدعاء الأدوات، وتصفح الملفات، والاستعلام من قواعد البيانات، وتسلسل الإجراءات
  • الأنماط المتقدمة: ذاكرة المحادثة، الاستجابات المتدفقة (streaming)، التوجيه بين نماذج متعددة، والبحث الهجين
  • ضبط الأداء: ترحيل المهام إلى GPU، مقايضات التكميم، المعالجة بالدفعة (batch processing)، وحجم نافذة السياق
  • الاعتبارات الأمنية لنشر الذكاء الاصطناعي محلياً في البيئات المهنية

المتطلبات الأساسية

  • تثبيت Python 3.10+ على نظامك
  • 8 جيجابايت رام كحد أدنى (ينصح بـ 16 جيجابايت+ للنماذج ذات 8 مليار بارامتر)
  • الإلمام بسطر الأوامر — ستستخدم أوامر التيرمينال طوال الوقت
  • معرفة أساسية بلغة Python — الدوال، الكلاسات، pip، والبيئات الافتراضية
  • وجود GPU مخصص اختياري ولكنه يحسن الأداء بشكل كبير (NVIDIA CUDA أو Apple Silicon)

إذا كان لديك جهاز Mac بمعالج Apple Silicon (M1/M2/M3/M4)، فأنت في وضع ممتاز — حيث يستفيد Ollama من تسريع Metal بشكل أصلي.


لماذا تشغل الذكاء الاصطناعي محلياً؟

لقد هيمن نهج "السحابة أولاً" في الذكاء الاصطناعي على الصناعة، ولكنه يأتي مع مقايضات كبيرة يلغيها النشر المحلي تماماً.

مزايا الذكاء الاصطناعي المحلي

الاهتمام الذكاء الاصطناعي السحابي الذكاء الاصطناعي المحلي
خصوصية البيانات تُرسل البيانات إلى خوادم طرف ثالث البيانات لا تغادر جهازك أبداً
التكلفة الدفع مقابل كل توكن، تزداد مع الاستخدام تكلفة عتاد لمرة واحدة، استخدام غير محدود
زمن الاستجابة (Latency) رحلة الشبكة الذهاب والإياب + وقت الانتظار وصول مباشر للعتاد، لا عبء للشبكة
التوفر يعتمد على وقت تشغيل المزود يعمل بدون إنترنت، لا حاجة للاتصال
التخصيص محدود ببارامترات API الخاصة بالمزود تحكم كامل في النموذج، والبرومبت، والمسار
الامتثال متطلبات معقدة لمكان تخزين البيانات البيانات تبقى في نطاقك القانوني افتراضياً
حدود الاستخدام يتم تقييد السرعة أثناء ذروة الطلب محدود فقط بقدرات عتادك

متى يكون الذكاء الاصطناعي المحلي منطقياً

يكون الذكاء الاصطناعي المحلي قيماً بشكل خاص عندما:

  • تقوم بمعالجة مستندات حساسة (قانونية، طبية، مالية، كود برمجي خاص)
  • تحتاج إلى تكاليف متوقعة — لا فواتير مفاجئة من أعباء العمل كثيفة التوكنات
  • بيئتك لديها وصول محدود أو معدوم للإنترنت
  • تريد التجربة بحرية دون القلق بشأن تكاليف API أثناء التطوير
  • تحتاج إلى تخصيص سلوك النموذج بما يتجاوز ما تسمح به واجهات السحابة
  • يتطلب الامتثال التنظيمي (GDPR, HIPAA, SOC 2) بقاء البيانات داخل المنشأة

متى يظل الذكاء الاصطناعي السحابي أفضل

كن صريحاً بشأن المقايضات. النماذج السحابية لا تزال تتفوق في:

  • أحدث مستويات الأداء في أصعب مهام التفكير (نماذج القمة مثل Claude و GPT-4o)
  • نوافذ سياق ضخمة (أكثر من 200 ألف توكن) دون الحاجة لذاكرة VRAM مكافئة
  • صفر إدارة للبنية التحتية — لا تعريفات GPU، لا تحديثات للنماذج، لا أعطال في العتاد
  • النماذج الأولية السريعة حيث يهم وقت الإعداد أكثر من التكاليف المستمرة

الخبر السار: الذكاء الاصطناعي المحلي والسحابي لا يستبعد أحدهما الآخر. تستخدم العديد من أنظمة الإنتاج النماذج المحلية للمهام الروتينية وتصعد إلى النماذج السحابية للتفكير المعقد.

Ollama Cloud: الخيار الهجين

يوفر Ollama أيضاً API مستضافاً سحابياً على ollama.com، مما يمنحك الوصول إلى نماذج ضخمة (مثل qwen3:235b-a22b أو deepseek-v3.1:671b) التي سيكون من غير العملي تشغيلها محلياً. الـ API متوافق مع OpenAI ويستخدم نفس الواجهة مثل Ollama المحلي، لذا فإن التبديل بين المحلي والسحابي يتطلب فقط تغيير عنوان URL الخاص بالمضيف:

# Set up cloud access
export OLLAMA_API_KEY="your-API-key-here"
from langchain_ollama import ChatOllama

# Local model — runs on your hardware
llm_local = ChatOllama(
    model="qwen3:8b",
    base_url="http://localhost:11434",
)

# Cloud model — runs on Ollama's servers
llm_cloud = ChatOllama(
    model="qwen3:235b-a22b",
    base_url="https://API.ollama.com",
    headers={"Authorization": f"Bearer {os.getenv('OLLAMA_API_KEY')}"},
)

يتيح لك ذلك التطوير والاختبار محلياً باستخدام نماذج أصغر، ثم استخدام النماذج الكبيرة المستضافة سحابياً للإنتاج أو الاستعلامات المعقدة — كل ذلك من خلال نفس الـ API.


فهم المكدس التكنولوجي

Ollama: خادم النماذج المحلي الخاص بك

Ollama هو طبقة وقت التشغيل (runtime) التي تجعل تشغيل نماذج LLMs محلياً بسيطاً مثل تشغيل حاوية Docker. في الخلفية، يتولى Ollama:

  1. تحميل وإدارة النماذج — ملفات نماذج ذات إصدارات مع تحديثات تلقائية
  2. التكميم (Quantization) — يحول النماذج كاملة الدقة إلى تنسيقات 4-بت أو 8-بت لتناسب عتاد المستهلكين
  3. تسريع GPU — اكتشاف واستخدام NVIDIA CUDA أو Apple Metal أو AMD ROCm تلقائياً
  4. تقديم الـ API — يوفر API REST متوافقاً مع OpenAI على localhost:11434
  5. تحميل النماذج المتزامن — تشغيل نماذج متعددة في وقت واحد (إذا سمحت الذاكرة)
graph LR
    A[تطبيقك] -->|HTTP API| B[خادم Ollama]
    B --> C[مدير النماذج]
    C --> D[ملفات النماذج المكممة]
    B --> E[مجدول GPU]
    E --> F[CUDA / Metal / ROCm]
    B --> G[مدير السياق]
    G --> H[KV Cache]

Qwen 3: عائلة النماذج

Qwen 3، الذي طورته Alibaba Cloud، هو عائلة من نماذج LLM مفتوحة المصدر تنافس نماذج أكبر منها بأضعاف. تأتي في بنيتين:

النماذج الكثيفة (Dense Models) — يتم تفعيل جميع البارامترات لكل توكن:

النموذج البارامترات VRAM (4-بت) الأفضل لـ
qwen3:0.6b 0.6 مليار ~1 جيجابايت أجهزة الحافة، التصنيف البسيط
qwen3:1.7b 1.7 مليار ~2 جيجابايت التلخيص، الأسئلة والأجوبة الأساسية
qwen3:4b 4 مليار ~3 جيجابايت الدردشة العامة، البرمجة الخفيفة
qwen3:8b 8 مليار ~6 جيجابايت التوازن الموصى به — تفكير قوي + برمجة
qwen3:14b 14 مليار ~10 جيجابايت التحليل المعقد، الكتابة المفصلة
qwen3:32b 32 مليار ~20 جيجابايت أداء قريب من نماذج القمة، الأبحاث

نماذج خليط الخبراء (Mixture-of-Experts - MoE) — يتم تفعيل مجموعة فرعية فقط من الشبكات الفرعية "الخبيرة" لكل توكن:

الموديل إجمالي البارامترات البارامترات النشطة الذاكرة (4-bit) الأفضل لـ
qwen3:30b-a3b 30B 3B ~19 جيجابايت الاستنتاج السريع مع سعة معرفية كبيرة
qwen3:235b-a22b 235B 22B ~142 جيجابايت أداء من الفئة الرائدة (يتطلب وحدات GPU متعددة)

لماذا تهم بنية MoE: يحتوي موديل qwen3:30b-a3b على 30 مليار بارامتر إجمالي، لكنه ينشط 3 مليارات فقط لكل توكن. تنبيه هام: لا تزال جميع الـ 30 مليار بارامتر بحاجة إلى التحميل في الذاكرة — ميزة MoE هي سرعة الاستنتاج، وليست توفير الذاكرة. ستحصل على القدرة المعرفية لموديل 30B مع سرعة توليد توكنات أقرب لموديل 3B. بالنسبة لأعباء عمل RAG والوكلاء حيث تهم السرعة، يعد هذا خيارًا ممتازًا إذا كان لديك الذاكرة الكافية.

كيف تتناسب الأجزاء معًا

graph TD
    subgraph "جهازك"
        A[تطبيق Python] --> B[إطار عمل LangChain]
        B --> C[عميل Ollama Python]
        C --> D[خادم Ollama localhost:11434]
        D --> E[موديل Qwen 3]
        D --> F[موديل التضمين (Embedding)]

        B --> G[مخزن المتجهات ChromaDB]
        G --> H[ملفات SQLite + Parquet محلية]

        B --> I[سجل الأدوات]
        I --> J[أدوات نظام الملفات]
        I --> K[أدوات كشط الويب]
        I --> L[أدوات قواعد البيانات]
        I --> M[وظائف مخصصة]
    end

التثبيت والإعداد

الخطوة 1: تثبيت Ollama

نظام macOS (باستخدام Homebrew):

brew install ollama

نظام macOS / Linux (تثبيت مباشر):

curl -fsSL https://ollama.com/install.sh | sh

نظام Windows: قم بتحميل المثبت من ollama.com/download.

تحقق من التثبيت:

ollama --version
# المخرجات المتوقعة: ollama version 0.x.x

الخطوة 2: سحب الموديلات الخاصة بك

ستحتاج إلى موديلين على الأقل — واحد للتوليد والآخر للتضمين (embeddings):

# موديل التوليد الأساسي (نقطة بداية موصى بها)
ollama pull qwen3:8b

# موديل التضمين لـ RAG (خفيف وسريع)
ollama pull nomic-embed-text

# اختياري: موديل MoE لمهام الاستنتاج المعقدة
ollama pull qwen3:30b-a3b

# اختياري: موديل صغير للتصنيف/التوجيه السريع
ollama pull qwen3:0.6b

تحقق من توفر الموديلات الخاصة بك:

ollama list

اختبر بشكل تفاعلي:

ollama run qwen3:8b
>>> ما هي الاختلافات الرئيسية بين RAG والضبط الدقيق (fine-tuning)؟
>>> /bye

الخطوة 3: تشغيل خادم Ollama

يحتاج Ollama للعمل كخادم في الخلفية لكي تتصل به تطبيقات Python الخاصة بك:

ollama serve

هذا يبدأ تشغيل الـ API على http://localhost:11434. يمكنك التحقق من أنه يعمل:

curl http://localhost:11434/api/tags

نصيحة: في نظام macOS، يقوم تطبيق Ollama لسطح المكتب بتشغيل الخادم تلقائيًا. في نظام Linux، قد ترغب في إعداده كخدمة systemd للتشغيل التلقائي.

الخطوة 4: إعداد بيئة Python الخاصة بك

# إنشاء مجلد المشروع
mkdir local-ai-project && cd local-ai-project

# إنشاء وتفعيل البيئة الافتراضية
python -m venv venv
source venv/bin/activate  # macOS/Linux
# venv\Scripts\activate   # Windows

# تثبيت التبعيات
pip install \
  langchain \
  langchain-community \
  langchain-core \
  langchain-ollama \
  langchain-chroma \
  langchain-text-splitters \
  chromadb \
  sentence-transformers \
  pypdf \
  python-dotenv \
  unstructured[pdf] \
  tiktoken \
  rich

وظيفة كل حزمة:

الحزمة الغرض
langchain إطار العمل الأساسي لبناء تطبيقات LLM
langchain-ollama تكامل خاص بـ Ollama (موديلات الدردشة، التضمينات)
langchain-chroma تكامل LangChain لمخزن المتجهات ChromaDB
langchain-text-splitters تقسيم المستندات باستراتيجيات متعددة
chromadb قاعدة بيانات متجهات محلية — لا تتطلب خادمًا
sentence-transformers موديلات تضمين بديلة عبر HuggingFace
pypdf استخراج النصوص من ملفات PDF
unstructured[pdf] تحليل متقدم لملفات PDF (الجداول، الصور، التخطيطات)
tiktoken عد التوكنات لإدارة نافذة السياق (context window)
rich مخرجات طرفية جميلة لتصحيح الأخطاء

قم بإنشاء ملف .env للتكوين:

# .env
OLLAMA_BASE_URL=http://localhost:11434
LLM_MODEL=qwen3:8b
EMBEDDING_MODEL=nomic-embed-text
CHROMA_PATH=./chroma_db
DATA_PATH=./data

بناء نظام RAG بمستوى إنتاجي

يعد RAG (التوليد المعزز بالاسترجاع) النمط الأكثر عملية لجعل موديلات LLM مفيدة مع بياناتك الخاصة. بدلاً من الضبط الدقيق للموديل (وهو مكلف وبطيء ويتطلب خبرة)، يقوم RAG بتزويد الموديل بالسياق ذي الصلة في وقت الاستعلام.

تعمق في بنية RAG

graph TD
    subgraph "خط معالجة الإدخال (يعمل مرة واحدة لكل مستند)"
        A[مستندات المصدر] --> B[محمل المستندات]
        B --> C[مقسم النصوص]
        C --> D[محسن الأجزاء]
        D --> E[موديل التضمين]
        E --> F[مخزن المتجهات]
    end

    subgraph "خط معالجة الاستعلام (يعمل لكل سؤال)"
        G[سؤال المستخدم] --> H[تضمين الاستعلام]
        H --> I[بحث التشابه]
        F --> I
        I --> J[تجميع السياق]
        J --> K[قالب التلقين (Prompt)]
        K --> L[توليد LLM]
        L --> M[الاستجابة]
    end

الخطوة 1: هيكل المشروع

local-ai-project/
├── .env
├── data/                    # توضع مستندات المصدر هنا
│   ├── papers/
│   ├── docs/
│   └── web/
├── chroma_db/               # مخزن المتجهات (يُنشأ تلقائيًا)
├── rag_pipeline.py          # تنفيذ RAG الرئيسي
├── document_loaders.py      # تحميل مستندات بتنسيقات متعددة
├── chunking.py              # استراتيجيات تقسيم متقدمة
├── embeddings.py            # تكوين التضمينات
├── agent.py                 # تنفيذ وكيل الذكاء الاصطناعي
└── utils.py                 # أدوات مساعدة مشتركة

الخطوة 2: تحميل المستندات بتنسيقات متعددة

يغطي البرنامج التعليمي الأصلي ملفات PDF فقط. في الممارسة العملية، ستحتاج إلى إدخال تنسيقات متعددة. إليك محمل يتعامل مع ملفات PDF، صفحات الويب، ملفات النص، و Markdown:

# document_loaders.py
import os
from typing import List
from langchain_core.documents import Document
from langchain_community.document_loaders import (
    PyPDFLoader,
    TextLoader,
    UnstructuredMarkdownLoader,
    WebBaseLoader,
    DirectoryLoader,
)


def load_pdf(file_path: str) -> List[Document]:
    """تحميل ملف PDF، وإرجاع مستند واحد لكل صفحة."""
    loader = PyPDFLoader(file_path)
    docs = loader.load()
    # إضافة بيانات وصفية للمصدر لضمان التتبع
    for doc in docs:
        doc.metadata["source_type"] = "pdf"
        doc.metadata["file_name"] = os.path.basename(file_path)
    print(f"تم تحميل {len(docs)} صفحة من {file_path}")
    return docs


def load_text(file_path: str) -> List[Document]:
    """تحميل ملف نصي عادي."""
    loader = TextLoader(file_path, encoding="utf-8")
    docs = loader.load()
    for doc in docs:
        doc.metadata["source_type"] = "text"
        doc.metadata["file_name"] = os.path.basename(file_path)
    return docs


def load_markdown(file_path: str) -> List[Document]:
    """تحميل ملف Markdown مع الحفاظ على الهيكل."""
    loader = UnstructuredMarkdownLoader(file_path)
    docs = loader.load()
    for doc in docs:
        doc.metadata["source_type"] = "markdown"
        doc.metadata["file_name"] = os.path.basename(file_path)
    return docs


def load_web_page(url: str) -> List[Document]:
    """تحميل المحتوى من رابط ويب."""
    loader = WebBaseLoader(url)
    docs = loader.load()
    for doc in docs:
        doc.metadata["source_type"] = "web"
        doc.metadata["url"] = url
    print(f"تم تحميل المحتوى من {url}")
    return docs


def load_directory(dir_path: str, glob_pattern: str = "**/*.pdf") -> List[Document]:
    """تحميل جميع الملفات المطابقة بشكل متكرر من مجلد."""
    loader = DirectoryLoader(
        dir_path,
        glob=glob_pattern,
        loader_cls=PyPDFLoader,
        show_progress=True,
        use_multithreading=True,
    )
    docs = loader.load()
    print(f"تم تحميل {len(docs)} مستند من {dir_path}")
    return docs


def load_documents(data_path: str) -> List[Document]:
    """
    اكتشاف وتحميل جميع الملفات المدعومة تلقائيًا من مجلد.
    يدعم ملفات: .pdf, .txt, .md.
    """
    all_docs = []
    supported_extensions = {
        ".pdf": load_pdf,
        ".txt": load_text,
        ".md": load_markdown,
    }

    for root, _, files in os.walk(data_path):
        for file_name in files:
            ext = os.path.splitext(file_name)[1].lower()
            if ext in supported_extensions:
                file_path = os.path.join(root, file_name)
                try:
                    docs = supported_extensions[ext](file_path)
                    all_docs.extend(docs)
                except Exception as e:
                    print(f"تحذير: فشل تحميل {file_path}: {e}")

    print(f"إجمالي المستندات المحملة: {len(all_docs)}")
    return all_docs

الخطوة 3: استراتيجيات تقسيم متقدمة

التقسيم (Chunking) هو المرحلة التي ينجح أو يفشل فيها معظم تطبيقات RAG. حجم الجزء الخاطئ ينتج عنه إما سياق قليل جدًا (لا يستطيع الموديل الإجابة) أو ضجيج كبير جدًا (المعلومات غير ذات الصلة تطغى على الإجابة).

# chunking.py
from typing import List
from langchain_core.documents import Document
from langchain_text_splitters import (
    RecursiveCharacterTextSplitter,
    MarkdownHeaderTextSplitter,
)


def chunk_by_size(
    documents: List[Document],
    chunk_size: int = 1000,
    chunk_overlap: int = 200,
) -> List[Document]:
    """
    تقسيم المستندات باستخدام التقسيم المتكرر للحروف.

    هذه هي الاستراتيجية الأكثر عمومية. تحاول التقسيم عند
    فواصل الفقرات أولاً، ثم الجمل، ثم الكلمات، مع الحفاظ على
    حدود النص الطبيعية.

    المعاملات:
        chunk_size: عدد الحروف المستهدف لكل جزء. أكبر = سياق أكثر
                    لكل عملية استرجاع ولكن عدد أجزاء إجمالي أقل.
        chunk_overlap: الحروف المشتركة بين الأجزاء المتجاورة.
                       يمنع فقدان المعلومات عند حدود الأجزاء.
    """
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        separators=["\n\n", "\n", ". ", " ", ""],
    )
    chunks = splitter.split_documents(documents)
    print(f"تم تقسيم {len(documents)} مستند إلى {len(chunks)} جزء")
    print(f"  متوسط حجم الجزء: {sum(len(c.page_content) for c in chunks) // len(chunks)} حرف")
    return chunks


def chunk_markdown_by_headers(
    documents: List[Document],
    chunk_size: int = 1000,
    chunk_overlap: int = 200,
) -> List[Document]:
    """
    تقسيم مستندات Markdown حسب العناوين أولاً، ثم حسب الحجم.

    هذا يحافظ على الهيكل المنطقي للتوثيق. يرث كل جزء
    تسلسل العناوين كبيانات وصفية، حتى تعرف إلى أي
    قسم ينتمي.
    """
    headers_to_split_on = [
        ("#", "header_1"),
        ("##", "header_2"),
        ("###", "header_3"),
    ]

    md_splitter = MarkdownHeaderTextSplitter(
        headers_to_split_on=headers_to_split_on,
        strip_headers=False,
    )

    # المرحلة الأولى: التقسيم حسب العناوين
    header_chunks = []
    for doc in documents:
        splits = md_splitter.split_text(doc.page_content)
        for split in splits:
            split.metadata.update(doc.metadata)
            header_chunks.append(split)

    # المرحلة الثانية: تقسيم أقسام العناوين الكبيرة حسب عدد الحروف
    size_splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
    )
    final_chunks = size_splitter.split_documents(header_chunks)

    print(f"تم تقسيم {len(documents)} مستند → {len(header_chunks)} قسم عناوين → {len(final_chunks)} جزء")
    return final_chunks


# دليل استراتيجية التقسيم
CHUNKING_GUIDE = """
اختيار حجم الجزء المناسب:

| حالة الاستخدام          | chunk_size | chunk_overlap | لماذا                                  |
|------------------------|-----------|---------------|----------------------------------------|
| سؤال وجواب حول التوثيق | 800-1200  | 150-250       | سياق متوازن لكل عملية استرجاع         |
| مراجعة قانونية/عقود    | 1500-2000 | 300-400       | البنود تحتاج إلى السياق المحيط بها     |
| توثيق الكود البرمجي    | 500-800   | 100-150       | الدوال قصيرة بطبيعتها                 |
| دردشة/تفاعلي           | 300-500   | 50-100        | إجابات قصيرة ومركزة                   |
| أوراق أكاديمية         | 1000-1500 | 200-300       | المحتوى الكثيف يحتاج لفقرات كاملة     |
"""

الخطوة 4: تكوين التضمينات (Embeddings)

تقوم التضمينات بتحويل النص إلى متجهات عددية تلتقط المعنى الدلالي. هناك نهجان يعملان بشكل جيد محليًا:

# embeddings.py
from langchain_ollama import OllamaEmbeddings
from langchain_community.embeddings import HuggingFaceEmbeddings


def get_ollama_embeddings(model_name: str = "nomic-embed-text") -> OllamaEmbeddings:
    """
    استخدام موديلات التضمين المقدمة عبر Ollama.

    المميزات: متوافق مع بيئة LLM الخاصة بك، مسرع بواسطة GPU، إعداد سهل.
    العيوب: يتطلب تشغيل خادم Ollama، أبطأ قليلاً من المكتبات المخصصة.

    الموديلات المتاحة:
        - nomic-embed-text: 137M بارامتر، 768 بعدًا، جيد للأغراض العامة
        - mxbai-embed-large: 335M بارامتر، 1024 بعدًا، جودة أعلى
        - all-minilm: 23M بارامتر، 384 بعدًا، الخيار الأسرع
    """
    embeddings = OllamaEmbeddings(model=model_name)
    print(f"تم تهيئة تضمينات Ollama: {model_name}")
    return embeddings


def get_huggingface_embeddings(
    model_name: str = "all-MiniLM-L6-v2",
) -> HuggingFaceEmbeddings:
    """
    استخدام HuggingFace sentence-transformers مباشرة.

    المميزات: لا يعتمد على Ollama، تشكيلة موديلات ضخمة، تم اختبارها جيدًا.
    العيوب: تحميل منفصل، يعمل على CPU فقط افتراضيًا.

    الموديلات الموصى بها:
        - all-MiniLM-L6-v2: سريع، خفيف، 384 بعدًا
        - all-mpnet-base-v2: جودة أعلى، 768 بعدًا
        - BAAI/bge-large-en-v1.5: أفضل جودة، 1024 بعدًا، أبطأ
    """
    embeddings = HuggingFaceEmbeddings(
        model_name=model_name,
        model_kwargs={"device": "cpu"},  # قم بتغييره إلى "mps" لأجهزة Apple Silicon
        encode_kwargs={"normalize_embeddings": True},
    )
    print(f"تم تهيئة تضمينات HuggingFace: {model_name}")
    return embeddings


# مقارنة موديلات التضمين
EMBEDDING_COMPARISON = """
| الموديل                  | الأبعاد | السرعة | الجودة | VRAM    |
|--------------------------|-----------|--------|---------|---------|
| all-MiniLM-L6-v2         | 384       | سريع   | جيد     | ~100MB  |
| nomic-embed-text         | 768       | متوسط  | أفضل    | ~300MB  |
| all-mpnet-base-v2        | 768       | متوسط  | أفضل    | ~400MB  |
| mxbai-embed-large        | 1024      | بطيء   | الأفضل  | ~700MB  |
| BAAI/bge-large-en-v1.5   | 1024      | بطيء   | الأفضل  | ~1.3GB  |
"""

الخطوة 5: مخزن المتجهات باستخدام ChromaDB

يعمل ChromaDB محليًا بالكامل — لا حاجة لخادم قاعدة بيانات خارجي. تستمر البيانات كملفات SQLite + Parquet على القرص.

# rag_pipeline.py



السيناريو
VRAM المستخدمة
RAM المستخدمة
السرعة




النموذج يناسب VRAM بالكامل
النموذج كاملاً
أدنى حد
الأسرع


النموذج جزئياً في VRAM
جزئي
فائض (Overflow)
متوسطة


النموذج بالكامل في RAM (CPU)
لا يوجد
النموذج كاملاً
الأبطأ (5-10 أضعاف)



القواعد الأساسية:

  • كل 1 مليار (1B) بارامتر ≈ 0.5-0.7 جيجابايت من الذاكرة عند تكميم 4-bit (بالإضافة إلى عبء ذاكرة KV cache)
  • num_ctx يستهلك أيضاً VRAM — سياق أكبر = ذاكرة KV cache أكثر
  • راقب الأداء باستخدام nvidia-smi (NVIDIA) أو Activity Monitor (macOS)
  • إذا سمعت صوت مراوح جهازك تعمل بقوة، فهذا يعني أن نموذجك قد انتقل للعمل على CPU

دليل التكميم (Quantization)

يقوم Ollama بتشغيل النماذج المكممة افتراضياً. إليك ما تعنيه مستويات التكميم:

التكميم الحجم مقارنة بالأصلي فقدان الجودة السرعة
Q4_0 ~25% ملحوظ الأسرع
Q4_K_M ~27% طفيف سريع
Q5_K_M ~33% أدنى حد متوسط
Q6_K ~40% ضئيل جداً أبطأ
Q8_0 ~50% لا يوجد الأبطأ
FP16 100% لا يوجد يتطلب VRAM ضخمة

معظم نماذج Ollama تأتي افتراضياً بتنسيق Q4_K_M، والذي يوفر أفضل توازن. للحصول على جودة أعلى:

# Pull a specific quantization
ollama pull qwen3:8b-q6_K

معالجة الدفعات لمجموعات المستندات الكبيرة

عند إدخال العديد من المستندات، قم بمعالجتها في دفعات (batches) لإدارة الذاكرة:

def batch_ingest(
    rag: LocalRAG,
    data_path: str,
    batch_size: int = 50,
):
    """Ingest documents in batches to prevent memory exhaustion."""
    from document_loaders import load_documents
    from chunking import chunk_by_size

    all_docs = load_documents(data_path)
    chunks = chunk_by_size(all_docs)

    total = len(chunks)
    for i in range(0, total, batch_size):
        batch = chunks[i : i + batch_size]
        rag.vector_store.add_documents(batch)
        print(f"Indexed batch {i // batch_size + 1} "
              f"({min(i + batch_size, total)}/{total} chunks)")

    print(f"Ingestion complete: {total} chunks indexed")

التحكم في وضع التفكير في Qwen 3

يدعم Qwen 3 قدرة فريدة على التفكير الهجين. يمكنك التبديل بين التفكير العميق والاستجابات السريعة على مستوى الطلب (prompt):

from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser

llm = ChatOllama(model="qwen3:8b", temperature=0)

# Deep reasoning mode — slower but more accurate for complex problems
response_think = llm.invoke(
    "Prove that the square root of 2 is irrational. /think"
)

# Fast mode — skip the internal chain-of-thought for simple queries
response_fast = llm.invoke(
    "What is the capital of France? /no_think"
)

متى تستخدم كل وضع:

الوضع المحفز الأفضل لـ
/think تفكير معقد البراهين الرياضية، تصحيح الأكواد، التحليل متعدد الخطوات
/no_think بحث بسيط الأسئلة الواقعية، التنسيق، التصنيف
افتراضي النموذج يقرر الاستخدام العام — يختار النموذج بناءً على التعقيد

بالنسبة لـ RAG تحديداً، غالباً ما يعمل وضع /no_think بشكل أفضل لأن الإجابة يجب أن تأتي من السياق المسترجع، وليس من تفكير مطول.


الاعتبارات الأمنية

تشغيل الذكاء الاصطناعي محلياً لا يجعله آمناً تلقائياً. إليك المجالات الرئيسية التي يجب معالجتها:

تطهير المدخلات (Input Sanitization)

import re


def sanitize_query(query: str, max_length: int = 2000) -> str:
    """
    Sanitize user input before passing to the LLM.
    Prevents prompt injection and resource exhaustion.
    """
    # Truncate to prevent context window abuse
    query = query[:max_length]

    # Remove common prompt injection patterns
    injection_patterns = [
        r"ignore\s+(previous|all|above)\s+instructions",
        r"you\s+are\s+now\s+",
        r"system\s*:\s*",
        r"<\|.*?\|>",
    ]
    for pattern in injection_patterns:
        query = re.sub(pattern, "[filtered]", query, flags=re.IGNORECASE)

    return query.strip()

ضوابط الوصول إلى الملفات

عندما تمتلك الوكلاء (agents) أدوات لنظام الملفات، قم بتقييد ما يمكنهم الوصول إليه:

import os

ALLOWED_DIRECTORIES = [
    os.path.abspath("./data"),
    os.path.abspath("./output"),
]


def validate_file_path(file_path: str) -> bool:
    """Ensure file access stays within allowed directories."""
    abs_path = os.path.abspath(file_path)
    return any(
        abs_path.startswith(allowed_dir)
        for allowed_dir in ALLOWED_DIRECTORIES
    )

عزل الشبكة

يرتبط Ollama بـ localhost افتراضياً، وهو أمر صحيح لعمليات النشر المحلية فقط. إذا كنت بحاجة إلى وصول عبر الشبكة:

# ONLY do this if you need remote access — and use a firewall
OLLAMA_HOST=0.0.0.0:11434 ollama serve

# Better: use a reverse proxy with authentication
# nginx, caddy, or traefik in front of Ollama

الأخطاء الشائعة

المشكلة السبب الحل
connection refused على المنفذ 11434 خادم Ollama لا يعمل قم بتشغيل ollama serve أو ابدأ تطبيق Ollama لسطح المكتب
استجابات النموذج بطيئة جداً النموذج يتجاوز VRAM، وينتقل إلى CPU استخدم نموذجاً أصغر أو قلل num_ctx
خطأ out of memory النموذج + السياق لا يتناسبان مع الذاكرة المتاحة انتقل إلى نموذج أصغر (مثلاً qwen3:4b) أو أغلق التطبيقات الأخرى
إجابات RAG فارغة أو غير منطقية حجم القطعة (chunk) صغير جداً أو k منخفض جداً زد chunk_size إلى 1200 و k إلى 5
الوكيل يدخل في حلقات مفرغة دون إجابة شرح الأدوات (docstrings) غير واضح، أو النموذج صغير جداً حسن شرح الأدوات؛ استخدم qwen3:8b كحد أدنى للوكلاء
خطأ model not found لم يتم سحب (pull) النموذج بعد قم بتشغيل ollama pull <model_name>
نتائج مكررة في الاسترجاع فقدان عملية إزالة التكرار أثناء الإدخال استخدم معرفات content-hash (كما هو موضح في طريقة ingest أعلاه)
ChromaDB permission denied مجلد مخزن المتجهات مقفل احذف chroma_db/ وأعد عملية الإدخال
عدم تطابق أبعاد التضمينات (Embeddings) تغيير نموذج التضمين بعد الفهرسة احذف chroma_db/ وأعد الإدخال باستخدام النموذج الجديد
الوكيل يستخدم أداة خاطئة شرح الأدوات غامض اجعل أوصاف الأدوات أكثر تحديداً وحصرية متبادلة

الخلاصات الرئيسية

الذكاء الاصطناعي المحلي جاهز للإنتاج. مع Ollama و Qwen 3، يمكنك بناء خطوط معالجة RAG ووكلاء ذكاء اصطناعي تنافس الحلول السحابية في معظم أعباء العمل العملية — مع الحفاظ على خصوصية بياناتك وتكاليفك المتوقعة.

المبادئ الأساسية:

  1. ابدأ صغيراً، ثم توسع: ابدأ بـ qwen3:4b للتحقق من خط المعالجة الخاص بك، ثم قم بالترقية إلى 8b أو 30b-a3b للإنتاج
  2. التقطيع (Chunking) هو كل شيء: تعتمد جودة نظام RAG الخاص بك على استراتيجية التقطيع أكثر من حجم النموذج
  3. الأدوات تحتاج إلى توثيق ممتاز: موثوقية استدعاء الأدوات من قبل الوكيل تتناسب طردياً مع جودة شرح الدوال (docstrings)
  4. راقب مواردك: راقب استخدام VRAM واضبط num_ctx قبل أن يصبح عنق زجاجة
  5. طبق النماذج في طبقات: استخدم النماذج الصغيرة للتصنيف/التوجيه والنماذج الكبيرة للتوليد
  6. آمن افتراضياً: قيد الوصول إلى الملفات، طهر المدخلات، وأبقِ Ollama على localhost

الخطوات التالية

  • إضافة المزيد من أنواع المستندات: قم بتوسيع المحمل للتعامل مع ملفات CSV و JSON و HTML و DOCX باستخدام محملات مجتمع LangChain
  • بناء واجهة ويب: قم بتغليف نظام RAG الخاص بك بـ FastAPI كخلفية برمجية (backend) وواجهة أمامية (frontend) باستخدام React أو Streamlit
  • تنفيذ التقييم: استخدم ragas أو deepeval لقياس جودة الاسترجاع ودقة الإجابات
  • استكشاف Modelfiles: قم بإنشاء Ollama Modelfiles مخصصة لضبط مطالبات النظام الافتراضية، ودرجة الحرارة (temperature)، ونوافذ السياق (context windows) لكل حالة استخدام
  • إعداد المراقبة: تتبع زمن وصول الاستعلام (latency)، ودرجات صلة الاسترجاع، واستهلاك موارد النموذج بمرور الوقت

المراجع

الأسئلة الشائعة

ج: ذاكرة RAM سعة 8 جيجابايت بدون GPU مخصص يمكنها تشغيل qwen3:0.6b و qwen3:1.7b . بالنسبة لـ qwen3:8b الموصى به، تحتاج على الأقل إلى 6-8 جيجابايت VRAM (أو 16 جيجابايت ذاكرة موحدة على Apple Silicon). بالنسبة للنماذج الأكبر التي لا تناسب جهازك، فكر في Ollama Cloud — فهو يستخدم نفس API لذا سيعمل كودك دون تغيير.

نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.