التعامل مع AI APIs· الدرس 2 من 7
- 1أول Call — تنادي الموديل من كودّك7 دقيقة
- 2الـ Streaming — الرد بيوصل كلمة كلمة7 دقيقة
- 3الـ Structured Outputs — تطلّع JSON تقدر تعتمد عليه8 دقيقة
- 4الأخطاء والـ Retries — لمّا الـ call يقع9 دقيقة
- 5الـ Rate Limits — سقف الطلبات في الدقيقة6 دقيقة
- 6المفاتيح والـ Secrets — تأمين مفتاح الـ API6 دقيقة
- 7الـ Timeouts والـ Idempotency — طلبات ما تتكرّرش بالغلط8 دقيقة
الـ Streaming — الرد بيوصل كلمة كلمة
في سطر واحد · بدل ما تستنّى الرد كله يخلص، الـ streaming بيبعتهولك token ورا token وقت ما بيتولّدوا — نفس إحساس الشات بتاع ChatGPT.
المستخدم بيحسّ إن المنتج أسرع بكتير لمّا يشوف الكلام بيطلع فورًا، حتى لو الوقت الكلّي واحد. ده بيفرّق جدًا في تجربة أي شات.
الفرق زي بث مباشر ضد تحميل فيديو كامل: بدل ما تقعد تبصّ على spinner لحد ما كل حاجة تجهز، بتبدأ تشوف أول جملة وإنت لسه بتستنّى الباقي.
زي المذيع اللي بيترجم فوري: مش بيستنّى المتكلّم يكمّل الخطبة كلها وبعدين يترجم، بيقولك كلمة بكلمة وهو ماشي — فإنت فاهم من أول ثانية.
تفتح الـ stream: true فبدل response واحد، بتوصلك أحداث SSE ورا بعض، كل واحد سطر data: فيه شويّة نص (delta) — بتلزقهم على بعض وتعرضهم أول بأول لحد ما ييجي سطر [DONE] يقولك خلص.
بتضيف stream: true للـ request، فبدل JSON واحد الاتصال بيفضل مفتوح والسيرفر يبعت أحداث SSE: كل سطر data: فيه chunk صغير جوّاه delta من النص. إنت بتقرا الـ body كـ stream، تقسّمه سطور، تعمل parse لكل chunk، وتلزق الـ deltas على بعض في الـ UI أول بأول. لما يوصلك سطر [DONE] بتقفل وتعتبر الرد اكتمل — وأي انقطاع قبله معناه إن اللي عندك ناقص.
const res = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ model: "model-name", stream: true, messages }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let text = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
for (const line of decoder.decode(value).split("\n")) {
if (!line.startsWith("data: ") || line.includes("[DONE]")) continue;
const delta = JSON.parse(line.slice(6)).choices[0].delta.content ?? "";
text += delta; // render incrementally here
}
}🧪 جرّب بنفسك: فعّل stream: true واعرض الرد كلمة كلمة في الترمينال أو صفحة بسيطة، لحد ما يوصل [DONE]. وبعدين اقطع الاتصال في النص (بـ AbortController) وشوف كودك هيتعامل مع الرد الناقص إزاي.
(١) تجمع الـ stream كله في متغيّر وتعرضه في الآخر — دفعت تعقيد الـ streaming من غير أي فايدة للمستخدم. (٢) متتعاملش مع الانقطاع في النص: الاتصال ممكن يقع بعد نص الرد، ولو خزّنت الجزء ده كأنه رد كامل هتبوّظ الداتا. (٣) تعمل JSON.parse على الـ body كله — الـ stream سطور SSE منفصلة، مش JSON واحد.
أي شات أو رد طويل بيتعرض لمستخدم بيستنّى قدّام الشاشة — أول كلمة بدري بتغيّر الإحساس بالمنتج كله.
الشغل اللي في الخلفية (batch jobs) أو الردود اللي كودّك بيعمل لها parse كاملة زي الـ JSON المهيكل — هناك الـ non-streaming أبسط بكتير ومفيش حد بيتفرّج أصلًا.