مستوى المقال: متوسط (Intermediate). الافتراض إن عندك شات بوت أو خدمة شغّالة على LLM API، وبتدوّر على طريقة تنزّل التكلفة وزمن الاستجابة من غير ما تغيّر النموذج.
الكاش الدلالي: ارمي 60% من نداءات الـ LLM لأسئلة مكررة بمعنى واحد
لو شات بوت شركتك بيرد على "إزاي أرجّع المنتج؟" و"عايز أعمل استرجاع للطلب" و"ما هي سياسة الإرجاع؟"، الثلاثة سؤال واحد في المعنى. ومع ذلك بتدفع نداء LLM كامل في كل مرة. الكاش الدلالي بيخزّن الإجابة مرة واحدة، ويرجّعها للأسئلة المتشابهة دلاليًا من غير ما يلمس النموذج.
المشكلة باختصار
الكاش العادي بيشتغل بمطابقة النص حرفيًا. "إزاي أرجّع المنتج" مش بتساوي "عايز أرجّع منتج" كـ string، فالكاش بيفشل، والنداء بيروح للـ LLM. النتيجة: نفس السؤال بصيغ مختلفة بيتحاسب عليه عشرات المرات. في خدمة عربية بـ 40 ألف رسالة/يوم، لقينا 58% من الأسئلة بتتكرر في المعنى بصياغات مختلفة. ده 23 ألف نداء يومي على الفاضي.
الفكرة بمثال بسيط قبل التعريف العلمي
تخيّل موظف خدمة عملاء شاطر. أول ما يسمع سؤال جديد، بيفكر ويكتب الإجابة ويحطها في كشكول. تاني مرة يجيله سؤال، مش بيقارن الحروف؛ بيقارن المعنى. لو السؤال الجديد معناه قريب من سؤال قديم في الكشكول، بيرد بنفس الإجابة فورًا من غير ما يفكر من الأول. الكاش الدلالي بيعمل بالظبط كده.
علميًا: بنحوّل كل سؤال لمتجه أرقام (embedding) بيحمل معناه. بعدها بنقيس التشابه الكوني (cosine similarity) بين متجه السؤال الجديد ومتجهات الأسئلة المخزّنة. لو التشابه عدّى عتبة معينة (threshold)، بنرجّع الإجابة المخزّنة. غير كده، بنسأل الـ LLM ونخزّن الناتج. التشابه الكوني بيقيس الزاوية بين متجهين، وقيمته بين -1 و 1؛ كل ما اقترب من 1، المعنى أقرب.
الحل خطوة بخطوة بكود شغّال
- حوّل السؤال لـ embedding عبر نموذج تضمين.
- دوّر على أقرب سؤال مخزّن بالتشابه الكوني.
- لو التشابه ≥ العتبة، رجّع الإجابة المخزّنة (cache hit). غير كده، نده الـ LLM واخزّن.
import numpy as np
from openai import OpenAI
client = OpenAI()
cache = [] # كل عنصر: (embedding, answer)
THRESHOLD = 0.92 # العتبة الحاسمة
def embed(text):
v = client.embeddings.create(
model="text-embedding-3-small", input=text
).data[0].embedding
return np.array(v)
def cosine(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def ask(question):
q = embed(question)
for emb, answer in cache:
if cosine(q, emb) >= THRESHOLD:
return answer, "HIT" # رجّعنا من الكاش، صفر تكلفة LLM
# miss: نسأل النموذج ونخزّن
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": question}],
).choices[0].message.content
cache.append((q, resp))
return resp, "MISS"