ابني ذكاء اصطناعي محلي باستخدام Ollama و Qwen 3: الـ RAG، والـ Agents، وما بعدها
٥ مارس ٢٠٢٦
ملخص
- قم بتشغيل نماذج لغوية كبيرة (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 مليار معلمة "8B parameter")
- الإلمام بسطر الأوامر — ستستخدم أوامر التيرمينال طوال الوقت
- معرفة أساسية بلغة Python — الدوال، الفئات (classes)، pip، والبيئات الافتراضية
- وجود GPU مخصص اختياري ولكنه يحسن الأداء بشكل كبير (NVIDIA CUDA أو Apple Silicon)
إذا كان لديك جهاز Mac بمعالج Apple Silicon (M1/M2/M3/M4)، فأنت في وضع ممتاز — حيث يستفيد Ollama من تسريع Metal بشكل أصلي.
لماذا تشغل الذكاء الاصطناعي محلياً؟
لقد هيمن نهج "السحابة أولاً" في الذكاء الاصطناعي على الصناعة، ولكنه يأتي مع مقايضات كبيرة يلغيها النشر المحلي تماماً.
مزايا الذكاء الاصطناعي المحلي
| الاهتمام | الذكاء الاصطناعي السحابي | الذكاء الاصطناعي المحلي |
|---|---|---|
| خصوصية البيانات | تُرسل البيانات إلى خوادم طرف ثالث | البيانات لا تغادر جهازك أبداً |
| التكلفة | الدفع مقابل كل "توكن"، تزداد مع الاستخدام | تكلفة الأجهزة لمرة واحدة، استخدام غير محدود |
| زمن الاستجابة (Latency) | رحلة البيانات عبر الشبكة + وقت الانتظار | وصول مباشر للأجهزة، لا يوجد عبء شبكة |
| التوفر | يعتمد على وقت تشغيل المزود | يعمل بدون اتصال، لا حاجة للإنترنت |
| التخصيص | محدود بمعلمات API الخاصة بالمزود | تحكم كامل في النموذج، والمطالبات، والنظام |
| الامتثال | متطلبات معقدة لمكان تخزين البيانات | تبقى البيانات في نطاق اختصاصك افتراضياً |
| حدود المعدل (Rate Limits) | يتم تقييده أثناء ذروة الطلب | محدود فقط بقدرات أجهزتك |
متى يكون الذكاء الاصطناعي المحلي منطقياً
يكون الذكاء الاصطناعي المحلي ذا قيمة خاصة عندما:
- تقوم بمعالجة مستندات حساسة (قانونية، طبية، مالية، كود برمجي خاص)
- تحتاج إلى تكاليف متوقعة — لا فواتير مفاجئة من أعباء العمل الكثيفة
- بيئتك لديها وصول محدود أو معدوم للإنترنت
- تريد التجربة بحرية دون القلق بشأن تكاليف API أثناء التطوير
- تحتاج إلى تخصيص سلوك النموذج بما يتجاوز ما تسمح به واجهات البرمجة السحابية
- يتطلب الامتثال التنظيمي (GDPR، HIPAA، SOC 2) بقاء البيانات في الموقع
متى لا يزال الذكاء الاصطناعي السحابي أفضل
كن صريحاً بشأن المقايضات. لا تزال النماذج السحابية تتفوق في:
- الأداء المتطور (State-of-the-art) في أصعب مهام التفكير (النماذج الرائدة مثل 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 هو طبقة التشغيل التي تجعل تشغيل نماذج LLMs محلياً بسيطاً مثل تشغيل حاوية Docker. في الخلفية، يتولى Ollama:
- تحميل وإدارة النماذج — ملفات نماذج ذات إصدارات محددة مع تحديثات تلقائية
- التكميم (Quantization) — يحول النماذج كاملة الدقة إلى تنسيقات 4-بت أو 8-بت لتناسب الأجهزة الاستهلاكية
- تسريع GPU — الكشف التلقائي والاستفادة من NVIDIA CUDA أو Apple Metal أو AMD ROCm
- خدمة الـ API — يوفر API REST متوافقاً مع OpenAI على
localhost:11434 - تحميل النماذج المتزامن — تشغيل نماذج متعددة في وقت واحد (إذا سمحت الذاكرة)
graph LR
A[Your Application] -->|HTTP API| B[Ollama Server]
B --> C[Model Manager]
C --> D[Quantized Model Files]
B --> E[GPU Scheduler]
E --> F[CUDA / Metal / ROCm]
B --> G[Context Manager]
G --> H[KV Cache]
Qwen 3: عائلة النماذج
Qwen 3، الذي طورته Alibaba Cloud، هو عائلة نماذج LLM مفتوحة المصدر تنافس نماذج أكبر منها بكثير. يأتي في بنيتين:
النماذج الكثيفة (Dense Models) — يتم تفعيل جميع المعلمات لكل توكن:
| النموذج | المعلمات | VRAM (4-بت) | الأفضل لـ |
|---|---|---|---|
qwen3:0.6b |
0.6B | ~1 جيجابايت | أجهزة الحافة، التصنيف البسيط |
qwen3:1.7b |
1.7B | ~2 جيجابايت | التلخيص، الأسئلة والأجوبة الأساسية |
qwen3:4b |
4B | ~3 جيجابايت | الدردشة العامة، البرمجة الخفيفة |
qwen3:8b |
8B | ~6 جيجابايت | التوازن الموصى به — تفكير قوي + برمجة |
qwen3:14b |
14B | ~10 جيجابايت | التحليل المعقد، الكتابة التفصيلية |
qwen3:32b |
32B | ~20 جيجابايت | أداء قريب من النماذج الرائدة، الأبحاث |
نماذج خليط الخبراء (MoE) — يتم تفعيل مجموعة فرعية فقط من الشبكات الفرعية "الخبيرة" لكل توكن:
| الموديل | إجمالي البارامترات | البارامترات النشطة | الذاكرة (4-bit) | الأفضل لـ |
|---|---|---|---|---|
qwen3:30b-a3b |
30B | 3B | ~19 جيجابايت | الاستنتاج السريع مع قدرة معرفية كبيرة |
qwen3:235b-a22b |
235B | 22B | ~142 جيجابايت | أداء من فئة Frontier (يتطلب وحدات GPU متعددة) |
لماذا تهم بنية MoE: يحتوي موديل qwen3:30b-a3b على 30 مليار بارامتر إجمالي، لكنه ينشط 3 مليارات فقط لكل توكن. تحذير هام: لا تزال جميع الـ 30 مليار بارامتر بحاجة إلى التحميل في الذاكرة — ميزة MoE هي سرعة الاستنتاج، وليست توفير الذاكرة. ستحصل على القدرة المعرفية لموديل 30B مع سرعة توليد توكنات أقرب لموديل 3B. بالنسبة لأعباء عمل RAG والوكلاء حيث تهم السرعة، يعد هذا خيارًا ممتازًا إذا كانت لديك الذاكرة الكافية.
كيف تترابط الأجزاء معًا
graph TD
subgraph "Your Machine"
A[Python Application] --> B[LangChain Framework]
B --> C[Ollama Python Client]
C --> D[Ollama Server localhost:11434]
D --> E[Qwen 3 Model]
D --> F[Embedding Model]
B --> G[ChromaDB Vector Store]
G --> H[Local SQLite + Parquet Files]
B --> I[Tool Registry]
I --> J[File System Tools]
I --> K[Web Scraping Tools]
I --> L[Database Tools]
I --> M[Custom Functions]
end
التثبيت والإعداد
الخطوة 1: تثبيت Ollama
macOS (Homebrew):
brew install ollama
macOS / Linux (تثبيت مباشر):
curl -fsSL https://ollama.com/install.sh | sh
Windows: قم بتحميل برنامج التثبيت من ollama.com/download.
تحقق من التثبيت:
ollama --version
# Expected output: ollama version 0.x.x
الخطوة 2: سحب الموديلات الخاصة بك
ستحتاج إلى موديلين على الأقل — واحد للتوليد والآخر للتضمينات (embeddings):
# Primary generation model (recommended starting point)
ollama pull qwen3:8b
# Embedding model for RAG (lightweight, fast)
ollama pull nomic-embed-text
# Optional: MoE model for complex reasoning tasks
ollama pull qwen3:30b-a3b
# Optional: Small model for fast classification/routing
ollama pull qwen3:0.6b
تحقق من توفر الموديلات الخاصة بك:
ollama list
الاختبار التفاعلي:
ollama run qwen3:8b
>>> What are the key differences between RAG and 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 الخاصة بك
# Create project directory
mkdir local-ai-project && cd local-ai-project
# Create and activate virtual environment
python -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
# Install dependencies
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 |
عد التوكنات لإدارة نافذة السياق |
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 "Ingestion Pipeline (runs once per document)"
A[Source Documents] --> B[Document Loader]
B --> C[Text Splitter]
C --> D[Chunk Optimizer]
D --> E[Embedding Model]
E --> F[Vector Store]
end
subgraph "Query Pipeline (runs per question)"
G[User Question] --> H[Query Embedding]
H --> I[Similarity Search]
F --> I
I --> J[Context Assembly]
J --> K[Prompt Template]
K --> L[LLM Generation]
L --> M[Response]
end
الخطوة 1: هيكل المشروع
local-ai-project/
├── .env
├── data/ # Source documents go here
│ ├── papers/
│ ├── docs/
│ └── web/
├── chroma_db/ # Vector store (auto-created)
├── rag_pipeline.py # Main RAG implementation
├── document_loaders.py # Multi-format document loading
├── chunking.py # Advanced chunking strategies
├── embeddings.py # Embedding configuration
├── agent.py # AI agent implementation
└── utils.py # Shared utilities
الخطوة 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]:
"""Load a PDF file, returning one Document per page."""
loader = PyPDFLoader(file_path)
docs = loader.load()
# Add source metadata for traceability
for doc in docs:
doc.metadata["source_type"] = "pdf"
doc.metadata["file_name"] = os.path.basename(file_path)
print(f"Loaded {len(docs)} pages from {file_path}")
return docs
def load_text(file_path: str) -> List[Document]:
"""Load a plain text file."""
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]:
"""Load a Markdown file with structure preservation."""
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]:
"""Load content from a web URL."""
loader = WebBaseLoader(url)
docs = loader.load()
for doc in docs:
doc.metadata["source_type"] = "web"
doc.metadata["url"] = url
print(f"Loaded content from {url}")
return docs
def load_directory(dir_path: str, glob_pattern: str = "**/*.pdf") -> List[Document]:
"""Recursively load all matching files from a directory."""
loader = DirectoryLoader(
dir_path,
glob=glob_pattern,
loader_cls=PyPDFLoader,
show_progress=True,
use_multithreading=True,
)
docs = loader.load()
print(f"Loaded {len(docs)} documents from {dir_path}")
return docs
def load_documents(data_path: str) -> List[Document]:
"""
Auto-detect and load all supported files from a directory.
Handles: .pdf, .txt, .md files.
"""
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"Warning: Failed to load {file_path}: {e}")
print(f"Total documents loaded: {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]:
"""
Split documents using recursive character splitting.
This is the most general-purpose strategy. It tries to split on
paragraph breaks first, then sentences, then words, preserving
natural text boundaries.
Args:
chunk_size: Target characters per chunk. Larger = more context
per retrieval but fewer total chunks.
chunk_overlap: Characters shared between adjacent chunks.
Prevents information loss at chunk boundaries.
"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_documents(documents)
print(f"Split {len(documents)} documents into {len(chunks)} chunks")
print(f" Avg chunk size: {sum(len(c.page_content) for c in chunks) // len(chunks)} chars")
return chunks
def chunk_markdown_by_headers(
documents: List[Document],
chunk_size: int = 1000,
chunk_overlap: int = 200,
) -> List[Document]:
"""
Split Markdown documents by headers first, then by size.
This preserves the logical structure of documentation. Each chunk
inherits the header hierarchy as metadata, so you know which
section it belongs to.
"""
headers_to_split_on = [
("#", "header_1"),
("##", "header_2"),
("###", "header_3"),
]
md_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split_on,
strip_headers=False,
)
# First pass: split by headers
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)
# Second pass: split oversized header sections by character count
size_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
)
final_chunks = size_splitter.split_documents(header_chunks)
print(f"Split {len(documents)} docs → {len(header_chunks)} header sections → {len(final_chunks)} chunks")
return final_chunks
# Chunking strategy guide
CHUNKING_GUIDE = """
Choosing the right chunk size:
| Use Case | chunk_size | chunk_overlap | Why |
|------------------------|-----------|---------------|----------------------------------------|
| Q&A over documentation | 800-1200 | 150-250 | Balanced context per retrieval |
| Legal/contract review | 1500-2000 | 300-400 | Clauses need surrounding context |
| Code documentation | 500-800 | 100-150 | Functions are naturally short |
| Chat/conversational | 300-500 | 50-100 | Short, focused answers |
| Academic papers | 1000-1500 | 200-300 | Dense content needs full paragraphs |
"""
الخطوة 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:
"""
Use Ollama-served embedding models.
Pros: Consistent with your LLM stack, GPU-accelerated, easy setup.
Cons: Requires Ollama server running, slightly slower than dedicated libs.
Available models:
- nomic-embed-text: 137M params, 768 dims, good general-purpose
- mxbai-embed-large: 335M params, 1024 dims, higher quality
- all-minilm: 23M params, 384 dims, fastest option
"""
embeddings = OllamaEmbeddings(model=model_name)
print(f"Initialized Ollama embeddings: {model_name}")
return embeddings
def get_huggingface_embeddings(
model_name: str = "all-MiniLM-L6-v2",
) -> HuggingFaceEmbeddings:
"""
Use HuggingFace sentence-transformers directly.
Pros: No Ollama dependency, huge model selection, well-benchmarked.
Cons: Separate download, CPU-only by default.
Recommended models:
- all-MiniLM-L6-v2: Fast, lightweight, 384 dims
- all-mpnet-base-v2: Higher quality, 768 dims
- BAAI/bge-large-en-v1.5: Best quality, 1024 dims, slower
"""
embeddings = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs={"device": "cpu"}, # Change to "mps" for Apple Silicon
encode_kwargs={"normalize_embeddings": True},
)
print(f"Initialized HuggingFace embeddings: {model_name}")
return embeddings
# Embedding model comparison
EMBEDDING_COMPARISON = """
| Model | Dimensions | Speed | Quality | VRAM |
|--------------------------|-----------|--------|---------|---------|
| all-MiniLM-L6-v2 | 384 | Fast | Good | ~100MB |
| nomic-embed-text | 768 | Medium | Better | ~300MB |
| all-mpnet-base-v2 | 768 | Medium | Better | ~400MB |
| mxbai-embed-large | 1024 | Slow | Best | ~700MB |
| BAAI/bge-large-en-v1.5 | 1024 | Slow | Best | ~1.3GB |
"""
الخطوة 5: مخزن المتجهات مع ChromaDB
يعمل ChromaDB محليًا بالكامل — لا حاجة لخادم قاعدة بيانات خارجي. تستمر البيانات كملفات SQLite + Parquet على القرص.
# rag_pipeline.py
import os
from typing import List, Optional
from dotenv import load_dotenv
from langchain_chroma import Chroma
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from document_loaders import load_documents
from chunking import chunk_by_size
دليل التكميم (Quantization)
يقوم Ollama بتشغيل النماذج المكممة افتراضياً. إليك ما تعنيه مستويات التكميم:
التكميم (Quantization)
الحجم مقارنة بالكامل
فقدان الجودة
السرعة
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
المعالجة بالدفعة لمجموعات المستندات الكبيرة
عند إدخال العديد من المستندات، قم بمعالجتها في دفعات لإدارة الذاكرة:
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 size) صغير جداً أو k منخفض جداً
زد chunk_size إلى 1200 و k إلى 5
الوكيل يدخل في حلقات مفرغة دون إجابة
شرح الأدوات (docstrings) غير واضح، أو النموذج صغير جداً
حسن شرح الأدوات؛ استخدم qwen3:8b كحد أدنى للوكلاء
خطأ model not found
لم يتم سحب النموذج بعد
قم بتشغيل ollama pull <model_name>
نتائج مكررة في الاسترجاع
فقدان ميزة إزالة التكرار أثناء الإدخال
استخدم معرفات content-hash (كما هو موضح في طريقة ingest أعلاه)
ChromaDB permission denied
مجلد مخزن المتجهات مغلق
احذف chroma_db/ وأعد الإدخال
عدم تطابق أبعاد التضمينات (Embeddings)
تغيير نموذج التضمين بعد الفهرسة
احذف chroma_db/ وأعد الإدخال باستخدام النموذج الجديد
الوكيل يستخدم أداة خاطئة
شرح الأدوات غامض
اجعل أوصاف الأدوات أكثر تحديداً وحصرية
أهم النقاط المستفادة
الذكاء الاصطناعي المحلي جاهز للإنتاج. مع Ollama و Qwen 3، يمكنك بناء خطوط معالجة RAG ووكلاء ذكاء اصطناعي تضاهي الحلول السحابية في معظم أعباء العمل العملية — مع الحفاظ على خصوصية بياناتك وتكاليفك المتوقعة.
المبادئ الأساسية:
- ابدأ صغيراً، ثم توسع: ابدأ بـ
qwen3:4b للتحقق من خط المعالجة الخاص بك، ثم قم بالترقية إلى 8b أو 30b-a3b للإنتاج
- التقطيع (Chunking) هو كل شيء: تعتمد جودة نظام RAG الخاص بك على استراتيجية التقطيع أكثر من حجم النموذج
- الأدوات تحتاج إلى توثيق ممتاز: موثوقية استدعاء الأدوات من قبل الوكيل تتناسب طردياً مع جودة شرح الدوال (docstrings)
- راقب مواردك: راقب استخدام VRAM واضبط
num_ctx قبل أن يصبح عائقاً
- طبق النماذج في طبقات: استخدم النماذج الصغيرة للتصنيف/التوجيه والنماذج الكبيرة للتوليد
- الأمان افتراضياً: قيد الوصول إلى الملفات، طهر المدخلات، وأبقِ Ollama على localhost
الخطوات التالية
- إضافة المزيد من أنواع المستندات: قم بتوسيع المحمل للتعامل مع ملفات CSV و JSON و HTML و DOCX باستخدام محملات مجتمع LangChain
بناء واجهة ويب: قم بتغليف نظام RAG الخاص بك بـ FastAPI كخلفية برمجية (backend) وواجهة أمامية (frontend) باستخدام React أو Streamlit
تنفيذ التقييم: استخدم ragas أو deepeval لقياس جودة الاسترجاع ودقة الإجابات
استكشاف Modelfiles: قم بإنشاء Ollama Modelfiles مخصصة لضبط مطالبات النظام الافتراضية، ودرجة الحرارة (temperature)، ونوافذ السياق (context windows) لكل حالة استخدام
إعداد المراقبة: تتبع زمن وصول الاستعلامات، ودرجات صلة الاسترجاع، واستهلاك موارد النموذج بمرور الوقت
المراجع