البرمجيات الدليل 2 من 3
ما هو التخزين المؤقّت للموجِّهات
آخر تحقّق من المصادر في .
أغلب ما يرسله تطبيقك إلى النموذج هو نفسه في كلّ مرّة: التعليمات نفسها، والأمثلة نفسها، والوثيقة الطويلة نفسها التي تُسأل عنها كلّ الأسئلة — تُرسَل مع كلّ طلب، وتُقرأ من أوّلها عند كلّ وصول. وأنت تدفع ثمن هذه القراءة في كلّ مرّة.
التخزين المؤقّت للموجِّهات هو الترتيب الذي يوقف إعادة القراءة. التخزين المؤقّت للموجِّهات يعيد استخدام العمل حين تشترك الطلبات في بادئة الموجِّه نفسها. أنت تعلّم الجزء الذي لا يتغيّر من الطلب، فتحتفظ المنصّة بما توصّلت إليه عن ذلك الجزء، والطلب التالي الذي يبدأ بالبداية نفسها ينطلق من هناك بدل أن ينطلق من الصفر.
في هذا الدليل نبني القياس بأيدينا. تكتب ملفّاً واحداً، وتشغّله مرّة، وتقرأ ثلاث مجموعات من الأرقام: السؤال نفسه بلا تخزين، ثمّ مع العلامة التي تكتب الكاش، ثمّ مرّة أخرى والكاش ما زال دافئاً. والأرقام في هذه الصفحة هي ما طبعه ذلك الملفّ في اليوم الذي يحمله الختم. وفي النهاية تعرف ماذا تعلّم، وكم يوفّر لك ذلك، والأخطاء الثلاثة التي تُطفئ التخزين بصمت.
بنهاية هذا الدليل ستقدر
- تشغّل التخزين المؤقّت بإضافة سطرين إلى طلبك، وتُثبت من أرقام الردّ نفسه أنّه اشتغل.
- تقرأ
cached_tokensوcache_write_tokensوتقول أيّ طلب دفع ثمن كتابة وأيّ طلب قرأ بعُشر السعر. - ترتّب طلبك بحيث يصير أكبر جزء ممكن منه قابلاً للتخزين.
- تميّز ثلاثة أشياء تُفرِغ الكاش بهدوء: سطر متغيّر فوق العلامة، وأداة أو نموذج تغيّرا، وكتلة أقصر من الحدّ الأدنى.
قبل أن تبدأ
أربعة أشياء، تحقّق من كلّ واحد قبل أن تكتب سطراً.
- Node.js إصدار 22 أو أحدث. شغّل
node --version. يطبع إصداراً مثلv24.14.1، وهو الإصدار الذي جرت عليه تشغيلة هذه الصفحة. إن طبع رسالة خطأ، نزّل Node.js من موقعه الرسميّ. - مفتاح API من OpenAI. أنشئه من صفحة مفاتيح API في لوحة تحكّمك واحفظه في مكان آمن. لا يدخل الشيفرة أبداً.
- مجلّد فيه حزمة SDK. شغّل
mkdir caching-demoثمّcd caching-demoثمّnpm init -yثمّnpm install openai@7.15.0— الإصدار الذي جرت عليه هذه التشغيلة. - المفتاح في بيئتك، في نفس نافذة الطرفيّة التي ستشغّل منها الملفّ. على PowerShell في ويندوز:
$env:OPENAI_API_KEY = "ضع مفتاحك هنا". على macOS أو لينكس:export OPENAI_API_KEY="ضع مفتاحك هنا". الحزمة تقرأ المتغيّر بنفسها، ولهذا لا تذكر الشيفرة المفتاح إطلاقاً.
والنموذج هنا gpt-5.6-luna. النموذج GPT-5.6 Luna، ومعرّفه gpt-5.6-luna، هو نموذج GPT-5.6 الذي تصفه OpenAI بأنّه مُحسَّن لأحمال العمل الحسّاسة للتكلفة. وأيّ نموذج من GPT-5.6 فما بعد يتصرّف التصرّف نفسه؛ لكنّ الأرقام التي تقارن بها أرقامك هي أرقام Luna.
شغّل الملفّ مرّة واحدة. المثال الثاني يكتب الكاش والثالث يقرأه. وإذا شغّلت الملفّ كلّه من جديد خلال نصف ساعة، وجد المثالُ الثاني المدخلةَ موجودةً فقرأها بدل أن يكتبها — وهذا هو الكاش يعمل كما ينبغي، لا المخرَج الذي تصفه الخطوة 2. وقسم «القاعدة الوحيدة» يقول لماذا نصف ساعة.
الخطوة 1 — أرسل الطلب بلا تخزين وانظر إلى الأرقام
أنشئ ملفّاً اسمه caching.mjs وضع فيه هذا:
no-cache import OpenAI from 'openai';
const client = new OpenAI(); // reads OPENAI_API_KEY from the environment; the key is never in the code
// A "document" long enough to be worth caching: one sentence, repeated. A real application would
// send a real document; the numbers behave the same way.
const document = 'The quarterly report covers revenue, costs, and headcount for each region. '.repeat(160);
const question = 'What does the report cover? Answer in one sentence.';
// The four numbers that tell the whole story, and nothing else from `usage`.
const usageOf = (response) => {
const { input_tokens, input_tokens_details, output_tokens } = response.usage;
return { input_tokens, cached_tokens: input_tokens_details.cached_tokens, cache_write_tokens: input_tokens_details.cache_write_tokens, output_tokens };
};
const noCache = await client.responses.create({
model: 'gpt-5.6-luna',
prompt_cache_options: { mode: 'explicit' }, // only breakpoints we place count — and here we place none
input: [
{ role: 'developer', content: [{ type: 'input_text', text: `Answer from this document only:\n${document}` }] },
{ role: 'user', content: question },
],
});
console.log(usageOf(noCache));
المخرجات
{
input_tokens: 2428,
cached_tokens: 0,
cache_write_tokens: 0,
output_tokens: 18
}
شغّلناه في node@24.14.1 openai 7.15.0
شغّله بالأمر node caching.mjs. ترجع أربعة أرقام. اقرأها واحداً واحداً، فبقيّة الدليل هي هذه الأربعة وهي تتغيّر.
input_tokens: 2428— كلّ ما قرأه النموذج: الوثيقة والسؤال وما تضعه المنصّة حولهما. والرمز (token) وحدة العدّ والمحاسبة معاً؛ والكلمة الإنجليزيّة العاديّة رمز أو أكثر بقليل.cached_tokens: 0— لم يُقرأ شيء من كاش. وهذا متوقّع: هذا الطلب لم يعلّم شيئاً.cache_write_tokens: 0— ولم يُكتب شيء في كاش. وهذا السطر هو ما يثبت أنّ الطلب خرج فعلاً من التخزين. ضبط prompt_cache_options.mode على explicit يجعل المنصّة تستخدم نقاط التخزين التي يختارها المطوّر وحدها، وتُعلَّم كلّ نقطة بإضافة prompt_cache_breakpoint إلى كتلة محتوى مدعومة داخل رسالة إدخال؛ وحين لا تُوضع أيّ نقطة صريحة، لا يستخدم الطلب التخزين المؤقّت ولا ينشئ أيّ كتابة في الكاش. ولهذا يضبط الملفّmode: 'explicit'ولا يعلّم شيئاً: ليصير «بلا تخزين» طلباً كتبته أنت، لا افتراضاً ترجوه.output_tokens: 18— طول الجواب نفسه، والتخزين لا يمسّه.
وفي الشيفرة تفصيلان يهمّان لاحقاً. الوثيقة document جملة واحدة مكرّرة 160 مرّة، وهذا يكفي لتجاوز الحدّ الأدنى الذي تستوفيه الخطوة 2. وهي في رسالة developer ككتلة input_text، لا في حقل instructionsفي المستوى الأعلى، لأنّ هذا هو الموضع الذي تُقبل فيه العلامة: يستطيع الطلب الواحد أن ينشئ أربع كتابات كاش كحدّ أقصى، والتعليمات في المستوى الأعلى لا يجوز أن تحمل نقطة تخزين صريحة: تعليمات المطوّر القابلة لإعادة الاستخدام تُوضع في كتلة input_text داخل رسالة developer.
شغّل الملفّ مرّة ثانية إن شئت. تُقرأ الرموز 2,428 نفسها من جديد وتُحاسَب من جديد، مع أنّ الوثيقة لم يتغيّر فيها حرف. هذه هي المشكلة.
الخطوة 2 — أضف العلامة وشاهد الكاش يُكتب
أضف هذا أسفل ما كتبته في الخطوة 1، في الملفّ نفسه:
cache-write const cacheWrite = await client.responses.create({
model: 'gpt-5.6-luna',
prompt_cache_options: { mode: 'explicit' },
input: [
{
role: 'developer',
content: [
{
type: 'input_text',
text: `Answer from this document only:\n${document}`,
prompt_cache_breakpoint: { mode: 'explicit' }, // the one new line: cache everything up to and including this block
},
],
},
{ role: 'user', content: question },
],
});
console.log(usageOf(cacheWrite));
المخرجات
{
input_tokens: 2428,
cached_tokens: 0,
cache_write_tokens: 2410,
output_tokens: 18
}
شغّلناه في node@24.14.1 openai 7.15.0
شغّل node caching.mjs من جديد. تُطبع أرقام الخطوة 1 أوّلاً، ثمّ هذه. سطر جديد وسطر تغيّر:
cache_write_tokens: 2410— قرأت المنصّة الكتلة المعلَّمة واحتفظت بما توصّلت إليه. قارنه بـinput_tokens: 2428: صار الطلب كلّه تقريباً في الكاش، إلّا نحو ثمانية عشر رمزاً هي السؤال الذي يقع تحت العلامة. والمحفوظ ليس النصّ. التخزين المؤقّت يحفظ حالة النموذج الوسيطة لبادئة قابلة لإعادة الاستخدام — أي الرموز غير المتغيّرة في أوّل الموجِّه — ويعيد استخدامها حين يأتي طلب لاحق ببادئة مطابِقة، مع أنّه يظلّ يعالج أيّ إدخال جديد.cached_tokens: 0— لم يُقرأ شيء من كاش، لأنّه لم يكن هناك شيء بعد. هذا الطلب لم يوفّر عليك شيئاً؛ بل كلّفك أكثر قليلاً.في GPT-5.6 وما بعده تكلفة كتابة الكاش 1.25 ضعف سعر رمز الإدخال غير المخزَّن، وتكلفة القراءات التالية 0.1 من ذلك السعر؛ وكتابة بادئة مرّة وإعادة استخدامها كاملةً مرّة واحدة تكلّف 1.35 ضعف كلفتها العاديّة، مقابل ضعفين لمعالجتها مرّتين بلا تخزين.
والتغيير الوحيد في الشيفرة هو سطر prompt_cache_breakpoint على كتلة الوثيقة. هذه هي الواجهة كلّها: علامة تقول «كلّ ما يسبقني وأنا معه هو الجزء الثابت». ولم يتحرّك شيء آخر.
الخطوة 3 — أرسله من جديد وشاهد الكاش يُقرأ
أضف هذا أسفل شيفرة الخطوة 2:
cache-read // The same request again, sent within the cache's lifetime of the one above.
const cacheRead = await client.responses.create({
model: 'gpt-5.6-luna',
prompt_cache_options: { mode: 'explicit' },
input: [
{
role: 'developer',
content: [
{
type: 'input_text',
text: `Answer from this document only:\n${document}`,
prompt_cache_breakpoint: { mode: 'explicit' },
},
],
},
{ role: 'user', content: question },
],
});
console.log(usageOf(cacheRead));
المخرجات
{
input_tokens: 2428,
cached_tokens: 2410,
cache_write_tokens: 0,
output_tokens: 18
}
شغّلناه في node@24.14.1 openai 7.15.0
شغّل node caching.mjs مرّة ثالثة — يخرج المثال الثالث بعد الثاني بثوانٍ، أي في عمر الكاش بمسافة واسعة. وهذا هو الرقم الذي من أجله كُتب الدليل كلّه:
cached_tokens: 2410— الرموز 2,410 نفسها، قُرئت من الكاش بدل أن تُعالَج من جديد، بعُشر السعر. والقراءة تجدّد المدخلة أيضاً.cache_write_tokens: 0— لم يُكتب شيء جديد؛ البادئة كانت هناك، بلا تغيّر حرف.input_tokens: 2428— كما هو، ويستحقّ الفهم: هذا الرقم هو ما قرأه النموذج، لا ما حُوسبت عليه بالسعر الكامل. فمن هذه الرموز 2,410 قراءات كاش، والباقي وحده إدخال عاديّ.
ضع التشغيلات الثلاث جنباً إلى جنب يصر الحساب واضحاً. الطلب الأوّل دفع السعر الكامل لـ2,428 رمزاً. والثاني دفع 1.25 ضعفاً لـ2,410 منها، واشترى مدخلة. والثالث دفع 0.1 لتلك الـ2,410. وكلّ طلب بعده، ما دامت المدخلة حيّة، يدفع العُشر نفسه. على عشرة طلبات، كتابة واحدة وتسع قراءات كاملة تكلّف 2.15 ضعف كلفة الإدخال العاديّة، مقابل عشرة أضعاف بلا تخزين. الفوائد ثلاث: تجنّب إعادة حساب بادئة عالجها النموذج من قبل، ودفع سعر الإدخال المخفَّض للرموز المُعاد استخدامها — بخصم يصل إلى 90% — وتقليل الوقت الذي يُقضى في معالجة الإدخال قبل أن يبدأ الردّ. والتوفير هنا ليس نسبة صغيرة؛ في تطبيق يعيد إرسال بادئة ثابتة طويلة، هو أغلب فاتورة الإدخال.
القاعدة الوحيدة
الثابت أوّلاً، والمتغيّر أخيراً، والعلامة بينهما. إعادة استخدام الكاش تتطلّب تطابق البادئة المُصيَّرة بكاملها؛ وإذا تغيّر محتوى أو إعداد ذو أثر قبل نقطة التخزين، فالبادئة بعد ذلك التغيير لا يمكن أن تطابق المدخلة الموجودة. فالبادئة تُطابَق من أوّلها حرفاً بحرف، ولا يُبحث عن جمل مألوفة في وسط الطلب.
| الطلب بالترتيب | هل يُخزَّن؟ |
|---|---|
| الأدوات | نعم، فهي فوق العلامة |
| رسالة developer: التعليمات ثمّ الوثيقة — العلامة هنا | نعم، حتى العلامة ومعها |
| رسالة المستخدم: سؤال اليوم | لا؛ بالسعر الكامل في كلّ طلب |
فترتيب بناء الطلب هو ما يقرّر كم منه يمكن تخزينه:
- كلّ ما هو نفسه في كلّ طلب — التعليمات والأمثلة والوثيقة الطويلة وتعريفات الأدوات — يوضع فوق العلامة.
- وكلّ ما يتغيّر — سؤال المستخدم، طابع زمنيّ، بيانات اليوم — يوضع تحتها.
- وحرف واحد يتغيّر فوق العلامة يجعلها بادئة مختلفة، فيدفع الطلب التالي كاملاً.
وحدّان يتبعان القاعدة نفسها. على الكتلة أن تكون طويلة بما يكفي لتُخزَّن: على البادئة أن تبلغ الحدّ الأدنى من الرموز القابلة للتخزين في النموذج قبل أن تُخزَّن، ورموز محتوى النظام المخفيّ الذي توفّره OpenAI لا تُحسب ضمن هذا الحدّ. الحدّ الأدنى لطول الموجِّه القابل للتخزين هو 1,024 رمزاً في GPT-5.6 وما بعده، ويختلف باختلاف إعدادات الطلب في النماذج الأقدم.والمدخلة لا تعيش إلى الأبد: المُعامِل prompt_cache_options.ttl يضبط الحدّ الأدنى لعمر الكاش؛ وقيمته الوحيدة المدعومة 30m هي الافتراضيّة أيضاً، وتبقى البادئة المخزَّنة صالحة لإعادة الاستخدام ثلاثين دقيقة بعد آخر كتابة أو إعادة استخدام.
وإن كنت تفضّل ألّا تضع العلامة بنفسك، فالمنصّة تضع واحدة. في الوضع الضمنيّ تضع OpenAI نقطة تخزين في نهاية آخر رسالة مؤهَّلة، والرسائل المؤهَّلة هي: رسائل المستخدم، وآخر استجابة أداة في مجموعة متتالية، وآخر رسالة developer في المجموعة المتتالية الأولى. وهذا هو الافتراض، ويناسب محادثةً تكبر، إذ تكون الرسالة الأحدث هي الحدّ الذي تريده بالضبط. أمّا وثيقة ثابتة مع سؤال متغيّر فيناسبها أقلّ، وهي الحالة التي قاسها هذا الدليل — ومن هناmode: 'explicit' وعلامة تتحكّم بها أنت.
متى تستخدمه ومتى لا
| الحالة | القرار | السبب |
|---|---|---|
| تعليمات طويلة أو أمثلة كثيرة تخرج مع كلّ طلب | استخدمه | هذا ما صُنع له؛ ومن الطلب الثاني يصير التوفير أغلب فاتورة الإدخال |
| أسئلة كثيرة عن وثيقة واحدة كبيرة | استخدمه | الوثيقة هي البادئة الثابتة وكلّ سؤال صغير — أفضل حالة ممكنة |
| محادثة طويلة مع مستخدم واحد | استخدمه ضمنيّاً | السجلّ يكبر ويتكرّر، والنقطة الافتراضيّة عند أحدث رسالة صحيحة أصلاً |
| طلبات متباعدة أكثر من عمر الكاش | غالباً لا | المدخلة تنتهي بين طلب وآخر، فيدفع كلّ طلب علاوة الكتابة ولا يجني أحد القراءة |
| كلّ طلب مختلف بالكامل ولا شيء مشترك | لا تستخدمه | لا بادئة مشتركة تُخزَّن؛ ستدفع 1.25 ضعفاً بلا مقابل |
| كتلة معلَّمة تحت الحدّ الأدنى للنموذج | لا، أو أطِلها | تحت الحدّ لا يُخزَّن شيء، وبصمت |
مصطلحات مرّت معنا
- الموجِّه (prompt) — كلّ ما ترسله في طلب واحد: التعليمات والوثيقة والأدوات والسؤال.
- الرمز (token) — وحدة العدّ والمحاسبة؛ والكلمة الإنجليزيّة العاديّة رمز أو أكثر بقليل.
- البادئة (prefix) — بداية الطلب حتى نقطة معيّنة. والكاش يطابق البادئات، من أوّل حرف.
- نقطة التخزين (cache breakpoint) — العلامة التي تقول أين ينتهي الجزء الثابت؛ وهي هنا
prompt_cache_breakpointعلى كتلة محتوى. - الوضع الصريح —
prompt_cache_options.mode: 'explicit': لا تُحسب إلّا العلامات التي تضعها أنت، وبلا علامة لا يحدث تخزين. - الوضع الضمنيّ — الافتراض، وفيه تضع المنصّة نقطة واحدة عند نهاية آخر رسالة مؤهَّلة.
- كتابة الكاش — أوّل طلب ببادئة ما، يخزّنها بـ1.25 ضعف سعر الإدخال العاديّ؛ ويعدّها
cache_write_tokens. - قراءة الكاش — طلب لاحق يطابق، ويُحاسَب 0.1 على الرموز المطابِقة؛ وتعدّها
cached_tokens. - العمر (TTL) — كم تبقى المدخلة صالحة لإعادة الاستخدام؛ وهو هنا ثلاثون دقيقة، تتجدّد مع كلّ استخدام.
- رسالة developer — الدور الذي يحمل تعليمات تطبيقك؛ وتُقبل العلامة على كتله، بخلاف التعليمات في المستوى الأعلى.
usage— الكائن في الردّ الذي يحمل الأرقام الأربعة التي يقرؤها هذا الدليل.
الخلاصة
التخزين المؤقّت يحفظ ما توصّل إليه النموذج عن بداية طلبك التي لا تتغيّر، ويعيد استخدامه في الطلب التالي الذي يبدأ بالبداية نفسها، بعُشر السعر. علّم نهاية ذلك الجزء الثابت، وأبقِ كلّ ما يتغيّر تحت العلامة، وتأكّد أنّ ما فوقها يتجاوز الحدّ الأدنى لطول النموذج. ثمّ اقرأ cached_tokens في الطلب الثاني: إن لم يكن قريباً من حجم بادئتك، فشيء فوق العلامة يتحرّك.
ما تغيّر مؤخراً
لم يتغيّر أيّ مصدر من مصادر هذا الدليل منذ آخر فحص.