Context Budget للـ AI: قلّل تاريخ الشات قبل ما يكسرك
هتعرف تبني بوابة بسيطة تقلل تاريخ محادثة الـ AI قبل الإرسال، فتقلل التكلفة والبطء بدون ما تفقد أهم القرارات القديمة.
مستوى القارئ: متوسط
المشكلة باختصار
لو عندك chatbot لخدمة العملاء أو مساعد داخلي للمطورين، أول 10 رسائل بتعدي عادي. بعد 80 رسالة، الطلب الواحد ممكن يبقى 100K token input قبل ما النموذج يكتب كلمة واحدة. الطريقة الشائعة الغلط هي إنك تبعت كل history كما هي. الطريقة دي بتفشل لما المستخدم يفتح نفس المحادثة أسبوع كامل، أو لما الفريق يحط logs وملفات طويلة في نفس thread.
الافتراض هنا إن عندك تطبيق شات يرسل messages array إلى نموذج LLM، وعندك حد آمن مثل 40K input tokens لكل طلب. الرقم مش مقدس. المهم إن الحد يبقى صريح ومقاس.
مثال بسيط قبل التعريف العلمي
ركز في المثال ده: عميل فتح تذكرة دعم يوم السبت. يوم الأحد أضاف لقطة خطأ. يوم الاثنين غير الخطة. يوم الثلاثاء سأل: “ينفع تلخصلي آخر قرار؟”. لو بعت كل الرسائل القديمة، النموذج هيقرأ لقطات وتفاصيل انتهت. الأفضل إنك تحتفظ بثلاث حاجات: system prompt، آخر 8 رسائل، وملخص قصير للقرارات القديمة.
التعريف الدقيق: Context Budget هو حد تشغيلي لحجم السياق المرسل للنموذج، مع سياسة واضحة لتقسيم التاريخ بين محتوى ثابت، رسائل حديثة، وملخصات مضغوطة. الهدف مش حذف عشوائي. الهدف إنك تبعت أقل سياق كافي لاتخاذ القرار الصحيح.
الحل العملي: بوابة قبل استدعاء النموذج
أفضل طريقة هنا هي إضافة خطوة قبل API call. هذه الخطوة تعدّ tokens تقريبية، تحتفظ بالرسائل الأحدث، وتضيف summary للقرارات القديمة عند الحاجة. لو عايزها تدعم أدوات tool calls، خليك حذر: لا تكسر ترتيب tool message بعد assistant message.
const MAX_INPUT_TOKENS = 40000;
const KEEP_LAST_MESSAGES = 8;
function estimateTokens(text) {
return Math.ceil(String(text).length / 4);
}
function msgTokens(msg) {
const content = Array.isArray(msg.content)
? msg.content.map(p => p.text || '').join('\n')
: msg.content || '';
return estimateTokens(`${msg.role}: ${content}`) + 6;
}
function buildContext(messages, oldSummary) {
const system = messages.find(m => m.role === 'system');
const recent = messages.filter(m => m.role !== 'system').slice(-KEEP_LAST_MESSAGES);
const summaryMsg = oldSummary ? {
role: 'system',
content: `ملخص القرارات القديمة: ${oldSummary}`
} : null;
let selected = [system, summaryMsg, ...recent].filter(Boolean);
let total = selected.reduce((sum, m) => sum + msgTokens(m), 0);
while (total > MAX_INPUT_TOKENS && recent.length > 2) {
recent.shift();
selected = [system, summaryMsg, ...recent].filter(Boolean);
total = selected.reduce((sum, m) => sum + msgTokens(m), 0);
}
return { messages: selected, estimatedTokens: total };
}الكود ده مش tokenizer رسمي. هو guard سريع. في الإنتاج استخدم tokenizer مناسب للموديل أو مكتبة framework بتتعامل مع الرسائل، لكن وجود estimate رخيص أفضل من انتظار خطأ context length في وقت المستخدم.
قياس قبل وبعد
في سيناريو واقعي: شات دعم داخلي فيه 140 رسالة، كل رسالة بين 300 و1200 حرف، ومعاه system prompt طويل. قبل الإدارة كان الطلب يرسل حوالي 118K input tokens. بعد trim فقط نزل إلى 52K. بعد trim مع summary للقرارات القديمة نزل إلى 28K. لو سعر input عندك 5 دولارات لكل مليون token، الطلب الواحد ينزل تقريبًا من 0.59$ إلى 0.14$. الرقم تقديري، لكنه يوضح الاتجاه.
الـ trade-off هنا واضح. بتكسب تكلفة أقل وزمن استجابة أقل. بتخسر جزء من التفاصيل القديمة. عشان كده الملخص لازم يحتوي قرارات، قيود، وأسماء ملفات أو طلبات مهمة، وليس كلام عام مثل “ناقشنا المشكلة”.
متى تلخص ومتى تحذف
- احذف الرسائل القديمة لو كانت logs متكررة أو محاولات فاشلة انتهت.
- لخّص الرسائل القديمة لو فيها قرار منتج أو شرط تعاقدي أو تفضيل مستخدم.
- احتفظ حرفيًا بآخر رسائل المستخدم لأن فيها عادة الطلب الحالي والسياق القريب.
- احتفظ بالـ system prompt في البداية. OpenAI تشرح أن ترتيب المحتوى الثابت في أول prompt يساعد prompt caching عند وجود prefix متكرر، ومؤشر cached_tokens يظهر في usage.
LangChain مثلًا يوفر trim messages كآلية جاهزة لتقليل chat history تحت حد معين مع الحفاظ على شكل الرسائل الصحيح. استخدم المكتبة لو stack بتاعك مبني عليها بدل ما تعيد اختراع كل قواعد ترتيب الرسائل.
متى لا تستخدم هذه الطريقة
لا تستخدم trim عدواني في مراجعات قانونية أو طبية أو مالية تعتمد على كل كلمة قديمة. لا تستخدم summary مولّد فقط لو القرار يحتاج دليل حرفي. في هذه الحالات استخدم retrieval من مصدر موثوق، أو خزّن المقتطفات المهمة كما هي مع references. كذلك لا تعتمد على estimateTokens لو أنت قريب جدًا من حد السياق؛ استخدم عدّاد حقيقي.
مصادر اعتمدت عليها
- OpenAI Prompt Caching docs: لشرح cached_tokens وفكرة ترتيب المحتوى الثابت في بداية الطلب.
- LangChain trim_messages reference: كمرجع لفكرة قص تاريخ الشات مع الحفاظ على SystemMessage والرسائل الحديثة.
- LangChain Messages docs: لفهم بنية رسائل system وhuman وAI في تطبيقات الشات.
الخطوة التالية
افتح كود استدعاء النموذج عندك، وسجّل input tokens لكل طلب لمدة يوم واحد. لو لقيت P95 أعلى من 40K، ضيف بوابة Context Budget قبل أي تحسين آخر.