0Pricing
AI Engineering Academy · درس

الفهرسة: تحويل الأجزاء إلى Embeddings وتخزينها

حوّلوا كل جزء إلى embedding باستخدام OpenAI embeddings API، وأضيفوا المتجهات الناتجة مع البيانات الوصفية باستخدام upsert إلى مخزن متجهي، لبناء فهرس قابل للبحث لمستنداتكم.

الفهرسة: تحويل الأجزاء إلى Embeddings وتخزينها درس مجاني في AI Engineering Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Engineering Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.

مرحلة الفهرسة: نظرة عامة

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

إنشاء التضمينات عبر OpenAI API

النهج الأكثر شيوعًا هو استدعاء واجهة embeddings API من OpenAI باستخدام نص المقطع. وينشئ النموذج text-embedding-3-small متجهات ذات 1536 بُعدًا، وتبلغ تكلفته 0.02 دولار لكل مليون رمز، وهي تكلفة منخفضة للغاية لمعظم أعباء العمل. أرسل نصوصًا متعددة في استدعاء API واحد (بحد أقصى 2048 إدخالًا) لزيادة معدل المعالجة إلى أقصى حد. وتتضمن الاستجابة متجه تضمين واحدًا لكل نص إدخال بالترتيب نفسه.

from openai import OpenAI

client = OpenAI()

def embed_batch(texts, model='text-embedding-3-small'):
    response = client.embeddings.create(
        model=model,
        input=texts  # up to 2048 texts per call
    )
    return [item.embedding for item in response.data]

# Embed one batch of 100 chunk texts
texts = [chunk['text'] for chunk in chunks[:100]]
vectors = embed_batch(texts)
print(f'Embedding dimension: {len(vectors[0])}')
print(f'Embedded {len(vectors)} chunks')

المعالجة الدفعية لتحقيق الكفاءة

عند فهرسة آلاف المقاطع، تكون الكفاءة مهمة. عالج المقاطع على دفعات من 100 إلى 500 لتحقيق توازن بين معدل المعالجة واستخدام الذاكرة. وتتبع موضعك حتى تتمكن من الاستئناف بعد حدوث فشل، من دون إعادة إنشاء تضمينات المقاطع التي عولجت مسبقًا. وسجّل التقدم بانتظام. فعند معالجة 100,000 مقطع على دفعات من 500، ستجري 200 استدعاء لـ API، ويكتمل ذلك عادةً خلال بضع دقائق.

def embed_all_chunks(chunks, batch_size=200):
    embedded = []
    total = len(chunks)
    for i in range(0, total, batch_size):
        batch = chunks[i:i+batch_size]
        texts = [c['text'] for c in batch]
        vectors = embed_batch(texts)
        for chunk, vector in zip(batch, vectors):
            embedded.append({
                **chunk,
                'embedding': vector
            })
        if (i // batch_size) % 10 == 0:
            print(f'Progress: {min(i+batch_size, total)}/{total}')
    return embedded

معالجة حدود المعدل أثناء الفهرسة

تفرض واجهة embeddings API من OpenAI حدودًا للمعدل تُقاس بعدد الرموز في الدقيقة (TPM). وتصل مهام الفهرسة الكبيرة إلى هذه الحدود وتتلقى RateLimitError. نفّذ التراجع الأسي مع التذبذب العشوائي: عند حدوث خطأ في حد المعدل، انتظر فترة عشوائية قصيرة قبل إعادة المحاولة، وضاعف مدة الانتظار مع كل فشل لاحق. ويوزّع ذلك عمليات إعادة المحاولة ويمنع جميع العاملين المتوازيين من إرسال طلبات مكثفة إلى API في اللحظة نفسها.

import time
import random
from openai import RateLimitError

def embed_batch_with_retry(texts, max_retries=5):
    for attempt in range(max_retries):
        try:
            return embed_batch(texts)
        except RateLimitError:
            if attempt == max_retries - 1:
                raise
            wait = (2 ** attempt) + random.uniform(0, 1)
            print(f'Rate limited. Waiting {wait:.1f}s...')
            time.sleep(wait)
    return []

إدراج المتجهات أو تحديثها في Pinecone

بعد إنشاء التضمينات، أدرجها أو حدّثها في مخزن المتجهات. يعني الإدراج أو التحديث إدراج متجهات جديدة أو تحديث المتجهات الموجودة إذا كان المعرّف نفسه موجودًا مسبقًا — وهو مصمم ليكون idempotent. في Pinecone، يحتوي كل سجل مُدرج أو مُحدّث على معرّف المتجه، وقيم التضمين، وقاموس بيانات وصفية للحقول التي تريد استخدامها للتصفية أو عرضها لاحقًا. أجرِ عمليات الإدراج أو التحديث على دفعات تصل إلى 100 سجل في كل استدعاء لتحقيق أفضل معدل نقل.

import pinecone

pc = pinecone.Pinecone(api_key='YOUR_KEY')
index = pc.Index('rag-index')

def upsert_to_pinecone(embedded_chunks, batch_size=100):
    for i in range(0, len(embedded_chunks), batch_size):
        batch = embedded_chunks[i:i+batch_size]
        vectors = [
            (
                chunk['id'],
                chunk['embedding'],
                {
                    'text': chunk['text'],
                    'source': chunk['metadata']['source'],
                    'page': chunk['metadata'].get('page', 0)
                }
            )
            for chunk in batch
        ]
        index.upsert(vectors=vectors)
        print(f'Upserted {min(i+batch_size, len(embedded_chunks))}/{len(embedded_chunks)}')

التخزين في pgvector

باستخدام pgvector، تُدرج التضمينات مباشرةً في جدول PostgreSQL باستخدام SQL القياسي. يقبل نوع البيانات vector قائمة Python من القيم العشرية المتسلسلة كسلسلة نصية. بعد إدراج جميع الصفوف، أنشئ فهرس HNSW لإجراء استعلامات الجار الأقرب التقريبية بسرعة. قد تستغرق فهرسة جدول موجود يضم ملايين الصفوف عدة دقائق، لذا أنشئ الفهرس بعد الإدراج الجماعي وليس قبله.

import psycopg2
from psycopg2.extras import execute_values

def upsert_to_pgvector(conn, embedded_chunks):
    with conn.cursor() as cur:
        records = [
            (
                chunk['id'],
                chunk['text'],
                chunk['metadata']['source'],
                chunk['metadata'].get('page', 0),
                chunk['embedding']   # list of floats
            )
            for chunk in embedded_chunks
        ]
        execute_values(cur, '''
            INSERT INTO document_chunks (id, text, source, page, embedding)
            VALUES %s
            ON CONFLICT (id) DO UPDATE
            SET text = EXCLUDED.text, embedding = EXCLUDED.embedding
        ''', records)
    conn.commit()

إنشاء فهرس HNSW

إن HNSW (Hierarchical Navigable Small World) هو نوع الفهرس الذي يتيح إجراء بحث سريع عن الجار الأقرب التقريبي. بخلاف البحث بالقوة الغاشمة، الذي يقارن متجه الاستعلام بكل متجه مخزّن، ينشئ HNSW بنية رسم بياني متعددة الطبقات تقلّص نطاق البحث. يتحكم المعلّم m في عدد الاتصالات لكل عقدة (تؤدي القيمة الأعلى إلى استرجاع أفضل، لكنها تتطلب ذاكرة أكبر)، بينما يتحكم ef_construction في جودة الفهرس أثناء بنائه.

-- Build HNSW index after bulk insertion
CREATE INDEX CONCURRENTLY ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- Set ef_search at query time to trade recall vs speed
SET hnsw.ef_search = 100;

-- Verify index was created
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'document_chunks';

تصميم مخطط البيانات الوصفية

تتيح البيانات الوصفية المخزنة إلى جانب كل متجه إجراء استرجاع مُصفّى قوي. صمّم مخطط البيانات الوصفية قبل الفهرسة — إذ تتطلب إضافة حقول جديدة لاحقًا إعادة الفهرسة. أدرج الحقول التي ستستخدمها في التصفية (القسم، ونوع المستند، والنطاق الزمني)، والحقول التي ستعرضها في الاستشهادات (العنوان، والصفحة، والمؤلف)، والحقول المفيدة لتصحيح الأخطاء (فهرس الجزء، وإجمالي الأجزاء، ووقت الفهرسة). اجعل قيم البيانات الوصفية بسيطة: فالسلاسل النصية والأرقام والقيم المنطقية تُفهرس وتُصفّى بكفاءة، أما الكائنات المتداخلة فلا.

# Well-designed metadata schema
METADATA_SCHEMA = {
    # For filtering at retrieval time
    'department': 'HR',         # string
    'doc_type': 'policy',       # string
    'year': 2025,               # integer
    'is_active': True,          # boolean

    # For display in citations
    'title': 'Employee Handbook 2025',
    'author': 'HR Team',
    'page': 12,
    'source': 's3://docs/handbook_2025.pdf',

    # For debugging and updates
    'chunk_index': 3,
    'total_chunks': 24,
    'indexed_at': '2025-09-01T10:00:00Z'
}

حفظ نقاط التحقق لمهام الفهرسة الطويلة

قد تستغرق فهرسة مجموعة كبيرة من البيانات ساعات. ويؤدي حدوث عطل في منتصف العملية إلى إهدار كل التقدم. نفّذ ملف نقاط تحقق يسجل الأجزاء التي فُهرست بنجاح. عند إعادة التشغيل، تخطَّ الأجزاء المفهرسة مسبقًا وتابع من الموضع الذي توقفت عنده. يجعل ذلك مهمة الفهرسة idempotent وآمنة للاستئناف. خزّن نقطة التحقق كمجموعة من معرّفات الأجزاء المعالَجة في ملف JSON أو جدول قاعدة بيانات.

import json
from pathlib import Path

CHECKPOINT_FILE = '/tmp/index_checkpoint.json'

def load_checkpoint():
    if Path(CHECKPOINT_FILE).exists():
        return set(json.loads(Path(CHECKPOINT_FILE).read_text()))
    return set()

def save_checkpoint(indexed_ids):
    Path(CHECKPOINT_FILE).write_text(json.dumps(list(indexed_ids)))

def index_with_checkpoint(chunks, index):
    done = load_checkpoint()
    remaining = [c for c in chunks if c['id'] not in done]
    print(f'Resuming: {len(done)} done, {len(remaining)} remaining')
    for chunk in remaining:
        upsert_to_pinecone([chunk], index)
        done.add(chunk['id'])
        save_checkpoint(done)

التحقق من اكتمال الفهرس

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

def verify_index(index, chunks, sample_size=10):
    index_stats = index.describe_index_stats()
    total_vectors = index_stats.total_vector_count
    expected = len(chunks)
    print(f'Index vectors: {total_vectors}, Expected: {expected}')
    if total_vectors != expected:
        print('WARNING: mismatch — some chunks may not have been indexed')

    # Spot-check retrieval
    import random
    sample = random.sample(chunks, sample_size)
    for chunk in sample:
        vec = embed_batch([chunk['text']])[0]
        results = index.query(vector=vec, top_k=1, include_metadata=True)
        top_id = results.matches[0].id if results.matches else None
        if top_id != chunk['id']:
            print(f'WARNING: expected {chunk["id"]}, got {top_id}')

بدائل التضمين المحلية

بالنسبة إلى البيانات الحساسة من ناحية الخصوصية التي لا يمكن أن تغادر بنيتك التحتية، استخدم نماذج التضمين المستضافة محليًا. توفر مكتبة sentence-transformers نماذج عالية الجودة مثل all-MiniLM-L6-v2 (بُعد 384، وحجم 22MB، وسرعة عالية جدًا) وbge-large-en-v1.5 (بُعد 1024، وجودة أفضل). شغّلها على CPU لأحمال العمل المتوسطة، أو على GPU لمهام الفهرسة الكبيرة. تلغي النماذج المحلية تكاليف واجهة API وتكاليف إخراج البيانات، لكنها تتطلب إدارة ملفات النماذج وموارد الحوسبة.

from sentence_transformers import SentenceTransformer

# Load once at startup
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

def embed_locally(texts, batch_size=64):
    # encode() handles batching internally
    embeddings = model.encode(
        texts,
        batch_size=batch_size,
        show_progress_bar=True,
        convert_to_numpy=True
    )
    return embeddings.tolist()  # convert numpy array to Python list

vectors = embed_locally([c['text'] for c in chunks])

تحقق سريع

اختبر مدى استيعابك لمفاهيم هندسة الذكاء الاصطناعي الواردة في هذا الدرس.

مراجعة الدرس

تعلّمت في هذا الدرس: إنشاء التضمينات على دفعات باستخدام OpenAI API ومعالجة حدود معدل الطلبات بالتراجع الأسي، وإدراج المتجهات أو تحديثها في Pinecone وpgvector مع البيانات الوصفية، وإنشاء فهارس HNSW لإجراء بحث سريع عن الجار الأقرب التقريبي، وأفضل الممارسات في بيئات الإنتاج، بما في ذلك الاستئناف المستند إلى نقاط التحقق، وتصميم مخطط البيانات الوصفية، والتحقق من اكتمال الفهرس. بعد ذلك سنبني مسار الاستعلام الذي يسترجع الأجزاء وينشئ إجابات مستندة إلى السياق.

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

هل درس «الفهرسة: تحويل الأجزاء إلى Embeddings وتخزينها» مجاني؟

نعم — نص درس «الفهرسة: تحويل الأجزاء إلى Embeddings وتخزينها» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Engineering Academy، انتقل إلى CoddyKit PRO. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.

ماذا ستتعلم في «الفهرسة: تحويل الأجزاء إلى Embeddings وتخزينها»؟

حوّلوا كل جزء إلى embedding باستخدام OpenAI embeddings API، وأضيفوا المتجهات الناتجة مع البيانات الوصفية باستخدام upsert إلى مخزن متجهي، لبناء فهرس قابل للبحث لمستنداتكم. تتمرن على AI Engineering Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Engineering Academy؟

لا تُشترط خبرة سابقة. AI Engineering Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «الفهرسة: تحويل الأجزاء إلى Embeddings وتخزينها»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Engineering Academy هذا؟

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

جميع الدروس في هذه الدورة

  1. تحميل المستندات واستخراج النصوص
  2. استراتيجيات تقسيم النص: ثابت أم حسب الجمل أم تكراري
  3. الفهرسة: تحويل الأجزاء إلى Embeddings وتخزينها
  4. الاستعلام والاسترجاع والتوليد
← العودة إلى AI Engineering Academy