الخطوة 3 في RAG: إعداد pgvector

تضيف pgvector البحث المتجهي إلى PostgreSQL. وبدلًا من SQLite، سنستخدم PostgreSQL مع امتداد pgvector.

لماذا PostgreSQL + pgvector؟

الميزة SQLite PostgreSQL + pgvector
البحث المتجهي غير مدعوم معامل <=> أصلي
المستخدمون المتزامنون يُقفل أثناء الكتابة يتعامل مع عمليات كتابة كثيرة
جاهز للإنتاج لا نعم
دعم Supabase لا مدمج

الخطوة 1: تشغيل PostgreSQL باستخدام Docker Compose

أنشئ advanced/docker-compose.rag.yml:

services:
  db:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_USER: aiproject
      POSTGRES_PASSWORD: aiproject
      POSTGRES_DB: aiproject
    ports:
      - "5432:5432"
    volumes:
      - pgvector_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U aiproject"]
      interval: 5s
      timeout: 5s
      retries: 5

  app:
    build: ../backend
    ports:
      - "5000:5000"
    volumes:
      - ../backend:/app
      - sqlite_data:/app/instance
    env_file:
      - ../backend/app/.env
    environment:
      DATABASE_URL: postgresql://aiproject:aiproject@db:5432/aiproject
    depends_on:
      db:
        condition: service_healthy

volumes:
  pgvector_data:
  sqlite_data:

يضيف هذا الملف خدمة PostgreSQL إلى جانب تطبيق Flask لديك. وتتصل خدمة app بقاعدة البيانات عبر متغير البيئة DATABASE_URL.

شغّل الخدمتين:

cd aiproject/advanced
docker compose -f docker-compose.rag.yml up

الخطوة 2: تثبيت التبعيات

pip install psycopg2-binary sentence-transformers

الخطوة 3: إضافة نموذج المستند

أنشئ نموذجًا جديدًا لتخزين أجزاء المستندات وتضميناتها. أضف إلى backend/app/models.py:

from pgvector.sqlalchemy import Vector


class Document(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(500), nullable=False)
    content = db.Column(db.Text, nullable=False)
    source = db.Column(db.String(500), default="")
    category = db.Column(db.String(100), default="general")
    embedding = db.Column(Vector(384), nullable=False)

    def to_dict(self):
        return {
            "id": self.id,
            "title": self.title,
            "content": self.content[:200],
            "source": self.source,
            "category": self.category,
        }

يخزّن عمود Vector(384) التضمينات، وسيبحث المعامل <=> حسب مسافة جيب التمام.

أضف أيضًا pgvector إلى requirements.txt:

pgvector
psycopg2-binary
sentence-transformers

الخطوة 4: إنشاء جداول قاعدة البيانات

يلزم إنشاء جدول التضمينات. شغّل التطبيق مرة واحدة بعد ربط PostgreSQL:

cd aiproject
docker compose -f advanced/docker-compose.rag.yml up --build

سينشئ التطبيق الجداول تلقائيًا (عبر db.create_all())، ويمكنك التحقق من ذلك:

docker compose -f advanced/docker-compose.rag.yml exec db psql -U aiproject -c "\dt"

ينبغي أن ترى جداول من بينها جدول document الذي يضم عمود embedding.

الخطوة 5: اختبار الاتصال والنموذج

شغّل هذا الفحص السريع:

# test_pgvector.py
from app import create_app, db
from app.models import Document

app = create_app()
with app.app_context():
    db.create_all()
    count = Document.query.count()
    print(f"Connection OK. Documents in DB: {count}")

شغّله:

DATABASE_URL="postgresql://aiproject:aiproject@localhost:5432/aiproject" python test_pgvector.py
# → Connection OK. Documents in DB: 0

اكتب اختبارًا آليًا

أضف اختبارًا يتحقق من أن نموذج Document يعمل مع pgvector. أنشئ advanced/rag/tests/test_knowledge_base.py:

import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from sample_data import DOCUMENTS
from knowledge_base import chunk_text, get_all_chunks


def test_chunk_text_creates_chunks():
    text = "word " * 500
    chunks = chunk_text(text, chunk_size=100, overlap=20)
    assert len(chunks) >= 5
    assert all(isinstance(c, str) for c in chunks)


def test_chunk_text_short_text():
    text = "short text"
    chunks = chunk_text(text, chunk_size=100, overlap=20)
    assert len(chunks) == 1
    assert chunks[0] == "short text"


def test_get_all_chunks_returns_list():
    chunks = get_all_chunks(DOCUMENTS[:3])
    assert len(chunks) > 0
    assert all("title" in c for c in chunks)
    assert all("content" in c for c in chunks)

شغّلها:

cd aiproject
python -m pytest advanced/rag/tests/ -v

تعمل هذه الاختبارات دون قاعدة بيانات — فهي تختبر منطق التقطيع بمعزل عن بقية الأجزاء.

اكتب اختبارًا سريعًا:

# test_pgvector.py
from app import create_app, db
from app.models import Document

app = create_app()
with app.app_context():
    db.create_all()
    count = Document.query.count()
    print(f"Connection OK. Documents in DB: {count}")

شغّله:

DATABASE_URL="postgresql://aiproject:aiproject@localhost:5432/aiproject" python test_pgvector.py
# → Connection OK. Documents in DB: 0

كيف يعمل

┌─────────────────────────────────────────────────────────┐
│                    pgvector                              │
│                                                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │  document table                                  │    │
│  │  id │ title │ content │ source │ embedding (384) │    │
│  ├─────┼───────┼─────────┼────────┼─────────────────┤    │
│  │  1  │ Tut…  │ Clark…  │ url…   │ [0.04, -0.13,…] │    │
│  │  2  │ Fin…  │ Reso…   │ url…   │ [0.21, 0.07,…]  │    │
│  │  3  │ Dis…  │ The…    │ url…   │ [-0.09, 0.33,…] │    │
│  └─────────────────────────────────────────────────┘    │
│                                                         │
│  Query: SELECT * FROM document                          │
│         ORDER BY embedding <=> :query_vec               │
│         LIMIT 5                                         │
└─────────────────────────────────────────────────────────┘