Claude Prompt Caching في TypeScript: تقليل التكاليف ٢٠٢٦
١٥ يونيو ٢٠٢٦
يعيد تخزين المطالبة المؤقت (Prompt caching) في Claude استخدام بادئة ثابتة — مثل تعليمات نظام طويلة، أو تعريفات أدوات، أو مستند مرجعي — عبر الطلبات المتعددة، مع محاسبتك بنسبة 10% تقريبًا من سعر الإدخال عند كل تكرار. في TypeScript، تقوم بتمييز البادئة باستخدام cache_control وتتأكد من نجاح العملية (hit) من خلال حقول الاستخدام (usage) في الاستجابة.
ملخص
ستقوم بإضافة تخزين المطالبة المؤقت إلى تطبيق Claude مبني بـ TypeScript باستخدام @anthropic-ai/sdk إصدار 0.104.1: قم بتمييز تعليمات النظام الثابتة بـ cache_control: { type: 'ephemeral' }، وتأكد من نجاح التخزين المؤقت عبر قراءة cache_creation_input_tokens و cache_read_input_tokens من الاستجابة، ثم احسب فرق التكلفة. تعليمات نظام مكونة من 2,000 توكن (token) تُعاد استخدامهما 100 مرة على Sonnet 4.6 تنخفض تكلفتها من 0.60 دولار إلى حوالي 0.067 دولار — أي خفض بنسبة 88.8% على تلك البادئة. ستستخدم أيضًا التخزين المؤقت التلقائي (على المستوى الأعلى) الأحدث للمحادثات المتنامية، ومدة الصلاحية (TTL) البالغة ساعة واحدة، وتتعلم الحد الأدنى للتوكنز لكل نموذج والذي قد يتسبب في فشل التخزين المؤقت بصمت إذا لم تلتزم به. خصص حوالي 25 دقيقة لهذا الدرس.
ما ستتعلمه
- كيف يعمل تخزين المطالبة المؤقت في Claude: الإدخالات المؤقتة، بادئة
tools → system → messages، وسعر القراءة بنسبة 10% - كيفية إضافة نقطة توقف تخزين مؤقت صريحة باستخدام
cache_controlعلى كتلة النظام (system block) - كيفية قياس نجاح التخزين المؤقت من كائن
usageفي الاستجابة - كيفية حساب التوفير الحقيقي في التكلفة بأسعار موثقة
- كيفية استخدام التخزين المؤقت التلقائي (على المستوى الأعلى) للمحادثات متعددة الأدوار
- كيفية تمديد التخزين المؤقت إلى ساعة واحدة ووضع نقاط توقف متعددة
- لماذا يفشل التخزين المؤقت بصمت، وكيفية إصلاح كل سبب
المتطلبات الأساسية
- Node.js 24 LTS (دعم نشط حتى أبريل 2028)1
- مفتاح Anthropic API في المتغير
ANTHROPIC_API_KEY - حزم محددة الإصدار، محدثة على npm بتاريخ 15 يونيو 2026:2
npm install --save-exact @anthropic-ai/sdk@0.104.1
npm install --save-exact -D TypeScript@6.0.3 tsx@4.22.4 @types/node@24.13.2
استخدم "type": "module" في ملف package.json وإعدادات tsconfig.json صارمة ("module": "nodenext"، "strict": true). قم بتشغيل السكربتات باستخدام node --import tsx src/<file>.ts، والذي ينفذ TypeScript بدون خطوة بناء؛ عميل new Anthropic() يقرأ ANTHROPIC_API_KEY من البيئة، لذا لا داعي للاعتماد على حزمة dotenv.
كيف يعمل تخزين المطالبة المؤقت في Claude
يقوم تخزين المطالبة المؤقت بحفظ بادئة من طلبك في الذاكرة بحيث يقوم الطلب التالي الذي يبدأ بنفس البادئة بقراءتها بدلاً من إعادة معالجتها. التخزين المؤقت "مؤقت" (ephemeral): مدة حياة الإدخال هي خمس دقائق افتراضيًا، وتتجدد مجانًا في كل مرة يُعاد استخدامه فيها، ويمكنك اختيار مدة حياة تصل إلى ساعة واحدة.3 يغطي التخزين المؤقت طلبك بترتيب ثابت — tools، ثم system، ثم messages — وتقوم نقطة التوقف بتخزين كل شيء حتى الكتلة التي قمت بتمييزها (بما في ذلك الكتلة نفسها).3
التسعير هو السبب الرئيسي للاهتمام بهذه الميزة. تكلفة قراءة التخزين المؤقت هي 0.1× من سعر الإدخال الأساسي. كتابة إدخال جديد لمدة خمس دقائق تكلف 1.25× من السعر الأساسي (تدفع الرسوم الإضافية مرة واحدة، ثم توزعها على كل عملية نجاح تخزين مؤقت)؛ أما الإدخال لمدة ساعة فيكلف 2× من السعر الأساسي.4 في نموذج Claude Sonnet 4.6، سعر الإدخال الأساسي هو 3 دولارات لكل مليون توكن، وقراءة التخزين المؤقت هي 0.30 دولار، وكتابة الخمس دقائق هي 3.75 دولار.4
الخطوة 1: إنشاء خط أساس بدون تخزين مؤقت
ابدأ بعميل مشترك ووظيفة مساعدة صغيرة تطبع حقول الاستخدام المتعلقة بالتخزين المؤقت. أنشئ ملف src/client.ts:
import Anthropic from '@anthropic-ai/sdk';
export const client = new Anthropic(); // يقرأ ANTHROPIC_API_KEY من البيئة
export function summarizeUsage(usage: Anthropic.Usage): string {
const write = usage.cache_creation_input_tokens ?? 0;
const read = usage.cache_read_input_tokens ?? 0;
const fresh = usage.input_tokens;
return `write=${write} read=${read} fresh=${fresh} output=${usage.output_tokens}`;
}
الآن أرسل طلبًا مع تعليمات النظام كسلسلة نصية عادية — بدون cache_control في أي مكان. أنشئ ملف src/baseline.ts:
import { readFile } from 'node:fs/promises';
import { client, summarizeUsage } from './client.js';
const SYSTEM_PROMPT = await readFile('style-guide.md', 'utf8');
const res = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 512,
system: SYSTEM_PROMPT, // سلسلة نصية عادية: لا يوجد cache_control، لا يتم تخزين أي شيء مؤقتًا
messages: [{ role: 'user', content: 'Summarize the refund policy in one line.' }],
});
console.log(summarizeUsage(res.usage)); // write=0 read=0 fresh=<كل الإدخال> output=<n>
قم بتوفير أي ملف style-guide.md أطول من 1,024 توكن (حوالي 750 كلمة أو أكثر). قم بتشغيله مرتين خلال دقيقة واحدة:
node --import tsx src/baseline.ts
ستظهر في كلا المرتين write=0 read=0، وسيكون fresh مساويًا لكامل تعليمات النظام بالإضافة إلى رسالة المستخدم في كل مرة. لم يتم تخزين أي شيء مؤقتًا، لذا ستدفع سعر الإدخال الأساسي على كامل البادئة في كل مكالمة.
الخطوة 2: إضافة نقطة توقف للتخزين المؤقت
تخزين البادئة الثابتة مؤقتًا يتطلب حقلاً واحدًا فقط. انقل تعليمات النظام إلى كتلة محتوى وأرفق بها cache_control. أنشئ ملف src/cached-system.ts:
import { readFile } from 'node:fs/promises';
import { client, summarizeUsage } from './client.js';
// كتلة تعليمات كبيرة وثابتة تُعاد استخدامها في كل طلب. يجب أن تصل إلى
// الحد الأدنى لكل نموذج (1,024 توكن لـ Sonnet 4.6) ليتم تخزينها مؤقتًا بالفعل.
const SYSTEM_PROMPT = await readFile('style-guide.md', 'utf8');
export async function ask(question: string) {
const res = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 512,
system: [
{
type: 'text',
text: SYSTEM_PROMPT,
cache_control: { type: 'ephemeral' }, // نقطة توقف على البادئة الثابتة
},
],
messages: [{ role: 'user', content: question }],
});
console.log(summarizeUsage(res.usage));
return res;
}
توضع نقطة التوقف على الكتلة التي تظل متطابقة عبر الطلبات — وهي تعليمات النظام — وليس على رسالة المستخدم المتغيرة. إذا وضعت نقطة توقف على محتوى متغير (مثل طابع زمني أو السؤال الوارد)، فلن يتطابق هاش (hash) البادئة أبدًا، وبالتالي لن ينجح التخزين المؤقت أبدًا.3
هناك تفصيل واحد يحدد ما إذا كان هذا سيعمل أم لا: يجب أن تصل البادئة المخزنة مؤقتًا إلى الحد الأدنى للطول لكل نموذج. إذا كانت أقل من ذلك، فسيتم معالجة الطلب بدون تخزين مؤقت ولن يظهر أي خطأ — ستستمر فقط في دفع السعر الكامل.5
| النموذج | الحد الأدنى للتوكنز القابلة للتخزين |
|---|---|
| Claude Sonnet 4.6 | 1,024 |
| Claude Opus 4.8 | 1,024 |
| Claude Opus 4.6 / 4.5 | 4,096 |
| Claude Haiku 4.5 | 4,096 |
رقم Haiku هذا من السهل الخطأ فيه: يحتاج Claude Haiku 4.5 إلى 4,096 توكن للتخزين المؤقت، وليس رقم 2,048 الذي كان ينطبق على موديلات Haiku الأقدم.5 إذا قمت بتخزين بادئة مكونة من 2,000 توكن مؤقتًا على Haiku 4.5، فلن يحدث شيء بصمت.
الخطوة 3: قياس نجاح التخزين المؤقت
يخبرك كائن usage في الاستجابة بما حدث بالضبط. هناك ثلاثة حقول مهمة:6
cache_creation_input_tokens— التوكنز التي تمت كتابتها في التخزين المؤقت في هذا الطلبcache_read_input_tokens— التوكنز التي تمت قراءتها من التخزين المؤقت في هذا الطلبinput_tokens— التوكنز الموجودة بعد آخر نقطة توقف فقط، وليس كامل المطالبة
قم باستدعاء ask() مرتين خلال نافذة الخمس دقائق:
import { ask } from './cached-system.js';
await ask('What is the refund window?'); // المكالمة الأولى
await ask('How do I escalate a billing issue?'); // المكالمة الثانية، نفس البادئة المخزنة
المكالمة الأولى تكتب الإدخال: write=<حجم البادئة> read=0. المكالمة الثانية تقرأه: write=0 read=<حجم البادئة>، مع بقاء fresh محتويًا فقط على رسالة المستخدم القصيرة. تم إرسال كامل المطالبة في المرتين، ولكن الطلب الثاني حاسبك على البادئة المخزنة بسعر القراءة.
أوضح تشخيص هو قاعدة الصفر-صفر: إذا كان كل من cache_creation_input_tokens و cache_read_input_tokens يساوي 0، فهذا يعني أنه لم يتم تخزين أي شيء مؤقتًا — وغالبًا ما يكون ذلك بسبب أن البادئة كانت أقل من الحد الأدنى للطول.5 لاحظ أيضًا أن total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens؛ حقل input_tokens وحده لا يمثل الحجم الإجمالي لمطالبتك.6
الخطوة 4: حساب التوفير
التسعير عبارة عن مجموعة ثابتة من المضاعفات على سعر الإدخال الأساسي: الكتابة لمدة خمس دقائق هي 1.25×، والكتابة لمدة ساعة واحدة هي 2×، والقراءة هي 0.1×.4 قم بترميز الجدول ودالة التكلفة. أنشئ src/cost.ts:
import type Anthropic from '@anthropic-ai/sdk';
export interface Pricing {
base: number; // $ / MTok, uncached input
write5m: number; // 5-minute cache write
write1h: number; // 1-hour cache write
read: number; // cache hit / refresh
output: number;
}
export const PRICING: Record<string, Pricing> = {
'claude-sonnet-4-6': { base: 3, write5m: 3.75, write1h: 6, read: 0.3, output: 15 },
'claude-haiku-4-5': { base: 1, write5m: 1.25, write1h: 2, read: 0.1, output: 5 },
'claude-opus-4-8': { base: 5, write5m: 6.25, write1h: 10, read: 0.5, output: 25 },
};
const perToken = (mtokPrice: number) => mtokPrice / 1_000_000;
/** Dollar cost of one real response, from its usage object. */
export function requestCost(usage: Anthropic.Usage, p: Pricing): number {
const read = usage.cache_read_input_tokens ?? 0;
const fresh = usage.input_tokens;
const out = usage.output_tokens;
const w5 = usage.cache_creation?.ephemeral_5m_input_tokens
?? usage.cache_creation_input_tokens ?? 0;
const w1 = usage.cache_creation?.ephemeral_1h_input_tokens ?? 0;
return (
fresh * perToken(p.base) +
read * perToken(p.read) +
w5 * perToken(p.write5m) +
w1 * perToken(p.write1h) +
out * perToken(p.output)
);
}
/** Cost of a static prefix of `tokens` reused across `requests` calls (write once, read after). */
export function prefixCost(tokens: number, requests: number, p: Pricing) {
const cached = tokens * perToken(p.write5m) + (requests - 1) * tokens * perToken(p.read);
const uncached = requests * tokens * perToken(p.base);
return { cached, uncached, savedPct: (1 - cached / uncached) * 100 };
}
لنفترض وجود موجه نظام (system prompt) مكون من 2,000 توكن تمت الإجابة عليه 100 مرة على Sonnet 4.6، مع تقارب المكالمات زمنياً بما يكفي لبقاء الإدخال نشطاً. بدون التخزين المؤقت، ستدفع مقابل 2,000 توكن بسعر 3 دولارات لكل مليون توكن في جميع المكالمات الـ 100: 100 × 2000 × $3 / 1e6 = $0.60. مع التخزين المؤقت، تكتب مرة واحدة بسعر 3.75 دولار لكل مليون توكن وتقرأ 99 مرة بسعر 0.30 دولار لكل مليون توكن: 2000 × $3.75/1e6 + 99 × 2000 × $0.30/1e6 = $0.0669. هذا يمثل تخفيضاً بنسبة 88.8% على البادئة.4 تطبق الأداة المساعدة requestCost نفس الحسابات على كائن usage حقيقي حتى تتمكن من تسجيل التكلفة الدقيقة بالدولار لأي استجابة.
الخطوة 5: التخزين المؤقت التلقائي للمحادثات
نقاط التوقف الصريحة دقيقة ولكنها مملة في الدردشة متعددة الأدوار، حيث تتحرك "آخر كتلة مستقرة" في كل دور. يحل التخزين المؤقت التلقائي ذلك: قم بتعيين cache_control واحد على المستوى الأعلى للطلب وسيقوم النظام بتطبيق نقطة التوقف على آخر كتلة قابلة للتخزين المؤقت، مع تقديمها مع نمو السجل.3 أنشئ src/automatic.ts:
import { client, summarizeUsage } from './client.js';
import type Anthropic from '@anthropic-ai/sdk';
// Automatic caching: one top-level cache_control marker. The breakpoint moves
// to the last cacheable block on every request as the history grows.
export async function chat(history: Anthropic.MessageParam[]) {
const res = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 512,
cache_control: { type: 'ephemeral' }, // top-level => automatic breakpoint
system: 'You are a terse senior engineer.',
messages: history,
});
console.log(summarizeUsage(res.usage));
return res;
}
يقوم كل دور بتخزين كل شيء مؤقتاً حتى الكتلة الأخيرة ويقرأ الأدوار السابقة مرة أخرى. يستخدم التخزين المؤقت التلقائي نفس البنية التحتية لنقاط التوقف الصريحة — نفس التسعير، والحدود الدنيا، ونظرة للخلف لـ 20 كتلة — ويستهلك إحدى فتحات نقاط التوقف الأربع الخاصة بك.3 يمكنك الجمع بين الاثنين: نقطة توقف صريحة على موجه نظام طويل بالإضافة إلى التخزين المؤقت التلقائي للمحادثة. إذا كانت الكتلة الأخيرة تحمل بالفعل علامة صريحة بنفس مدة البقاء (TTL)، فإن حقل المستوى الأعلى يكون بلا تأثير؛ ومع مدة بقاء مختلفة، يعيد API خطأ 400.3
الخطوة 6: مدة بقاء ممتدة ونقاط توقف متعددة
نافذة الخمس دقائق جيدة للطلبات المتتالية ولكنها تفقد الإدخال بين المكالمات المتباعدة. اختر عمر ساعة واحدة عن طريق إضافة ttl إلى العلامة. أنشئ src/extended-ttl.ts:
import { readFile } from 'node:fs/promises';
import { client } from './client.js';
const REFERENCE_DOC = await readFile('handbook.md', 'utf8');
export async function queryDoc(question: string) {
const res = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 512,
system: [
{
type: 'text',
text: REFERENCE_DOC,
cache_control: { type: 'ephemeral', ttl: '1h' }, // hold for one hour
},
],
messages: [{ role: 'user': question }],
});
const c = res.usage.cache_creation;
console.log(`5m_write=${c?.ephemeral_5m_input_tokens ?? 0} 1h_write=${c?.ephemeral_1h_input_tokens ?? 0}`);
return res;
}
تكلفة الكتابة لمدة ساعة واحدة هي 2× الأساس بدلاً من 1.25×، لذا احجزها للموجهات التي يتم إعادة استخدامها بشكل أقل تكراراً من كل خمس دقائق؛ أي شيء يتم استخدامه بشكل أكثر تكراراً يكون أرخص مع مدة البقاء الافتراضية، والتي يتم تحديثها مجاناً مع كل استخدام.4 عندما تخلط بين المدد، يقوم usage.cache_creation بتقسيم الكتابة إلى ephemeral_5m_input_tokens و ephemeral_1h_input_tokens.6
يمكنك تحديد ما يصل إلى أربع نقاط توقف. السبب لإضافة نقطة ثانية هو حد النظرة للخلف لـ 20 كتلة: يتحقق النظام من 20 موضعاً كحد أقصى للخلف من نقطة التوقف بحثاً عن كتابة سابقة في التخزين المؤقت، لذا في محادثة طويلة، فإن نقطة التوقف التي تنجرف لأكثر من 20 كتلة بعد آخر كتابة تتوقف عن العمل. نقطة توقف ثانية أقرب إلى ذلك الموضع تحافظ على إدخال قابل للاستخدام نشطاً.3 نقاط التوقف نفسها مجانية — أنت تدفع فقط مقابل التوكنات المكتوبة والمقروءة فعلياً.3
التحقق
كل ما سبق يتم التحقق من نوعه مقابل SDK المثبت. حسابات التكلفة قابلة للاختبار أيضاً بدون مفتاح API. أنشئ src/cost.test.ts:
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { PRICING, prefixCost } from './cost.js';
test('2k-token Sonnet prefix reused 100x saves ~89%'() => {
const p = PRICING['claude-sonnet-4-6']!;
const { cached savedPct } = prefixCost(2000100);
console.log(`uncached=$${uncached.toFixed(4)} cached=$${cached.toFixed(4)} saved=${savedPct.toFixed(1)}%`);
assert.ok(Math.abs(uncached - 0.6) < 1e-9);
assert.ok(savedPct > 88 && savedPct < 90);
});
ثم قم بتشغيل كلا التحققين:
npx tsc --noEmit # TypeScript 6.0.3 strict: clean
node --import tsx --test src/cost.test.ts
يؤكد الاختبار الرقم الرئيسي: مثال 2,000 توكن في 100 مرة ينتهي بـ uncached=$0.6000 cached=$0.0669 saved=88.8%. ونظراً لأن requestCost و prefixCost لا يحتاجان إلى مفتاح API، يمكنك دمج تأكيدات التكلفة في CI. بالنسبة للسكربتات التي تستدعي API، فإن التحقق المباشر هو حقول الاستخدام: يجب أن تطبع المكالمة الثانية داخل النافذة read= مساوياً لحجم البادئة الخاصة بك.
الأخطاء الشائعة
كلا عدادي الاستخدام 0. لم تصل البادئة إلى الحد الأدنى للطول. يحتاج Sonnet 4.6 إلى 1,024 توكن؛ ويحتاج Haiku 4.5 إلى 4,096. قم بإطالة المحتوى المخزن مؤقتاً أو انقل الموجهات الأقصر إلى نموذج بحد أدنى أقل.5
التخزين المؤقت يكتب ولكنه لا يقرأ أبداً. نقطة التوقف الخاصة بك موجودة على كتلة تتغير في كل طلب — طابع زمني، أو سياق خاص بكل طلب، أو رسالة المستخدم نفسها. انقل العلامة إلى نهاية البادئة الثابتة بحيث تكون البادئة المجمعة (hashed) متطابقة عبر المكالمات.3
تتوقف النجاحات (hits) بعد فجوة زمنية. يعيش الإدخال الافتراضي لمدة خمس دقائق من آخر استخدام له. المكالمات المتباعدة بشكل أكبر تحتاج إلى علامة ttl: '1h'، أو وتيرة طلبات أقل من خمس دقائق للحفاظ على الإدخال نشطاً مجاناً.3
النجاح (hit) يتحول فجأة إلى كتابة (write). تغير شيء ما في بداية البادئة. يؤدي تغيير تعريفات الأدوات (tools) إلى إبطال التخزين المؤقت بالكامل؛ ويؤدي تحرير موجه النظام إلى إبطال تخزين النظام والرسائل؛ كما أن تبديل tool_choice أو إضافة أو إزالة صورة في أي مكان في الموجه يفرض إدخالاً جديداً.3 يتبع التخزين المؤقت التسلسل الهرمي tools → system → messages، وأي تغيير في أي مستوى يبطل ذلك المستوى وكل ما يليه.3
الطلبات المتوازية تفشل جميعاً في أول دفعة. لا يصبح إدخال التخزين المؤقت متاحاً إلا بعد بدء الاستجابة الأولى، لذا أرسل طلباً واحداً، وانتظر وصوله، ثم أرسل بقية الدفعة.5
الخطوات التالية ومزيد من القراءة
يتكامل تخزين الموجهات مؤقتاً مع بقية أجزاء Claude API. اجمعه مع المخرجات المهيكلة في TypeScript لتخزين موجه نظام طويل يحتوي على مخطط (schema) عبر عملية استخراج، أو مع حلقة الوكيل لاستخدام الأدوات حيث تكون تعريفات الأدوات المستقرة أهدافاً رئيسية للتخزين المؤقت، أو ضعه فوق دروس Claude Batch API — حيث يتم تطبيق مضاعفات التخزين المؤقت فوق خصم الدفعة (batch).4 ابدأ بالتخزين المؤقت التلقائي للمحادثات واستخدم نقاط التوقف الصريحة عندما تتغير الأقسام المختلفة بمعدلات مختلفة.3
Footnotes
-
جدول إصدارات Node.js، إصدار Node 24 "Krypton" Active LTS. https://nodejs.org/en/about/previous-releases ↩
-
تم التحقق من إصدارات الحزم عبر
npm view <pkg> version، بتاريخ 15 يونيو 2026. https://www.npmjs.com/package/@anthropic-ai/sdk ↩ -
Anthropic، "Prompt caching" — التخزين المؤقت التلقائي مقابل الصريح، نقاط التوقف (breakpoints)، مراجعة الـ 20 بلوك السابقة، TTL، وإبطال الصلاحية. https://platform.claude.com/docs/en/build-with-claude/prompt-caching ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13
-
Anthropic، جدول تسعير "Prompt caching" والمعاملات (كتابة 5 دقائق 1.25×، كتابة ساعة واحدة 2×، قراءة 0.1×). https://platform.claude.com/docs/en/build-with-claude/prompt-caching#pricing ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Anthropic، قيود "Prompt caching" — الحد الأدنى لأطوال الـ prompt القابلة للتخزين لكل نموذج وسلوك عدم التخزين الصامت. https://platform.claude.com/docs/en/build-with-claude/prompt-caching#cache-limitations ↩ ↩2 ↩3 ↩4 ↩5
-
Anthropic، تتبع أداء "Prompt caching" —
cache_creation_input_tokens،cache_read_input_tokens،input_tokens. https://platform.claude.com/docs/en/build-with-claude/prompt-caching#tracking-cache-performance ↩ ↩2 ↩3