يمكن أن يكون استدعاء النموذج بسيطًا كإرسال طلب HTTP. سنفحص طلبًا عن قرب، ونقرأ ردّ JSON، ثم نبني محادثة عبر عدة استدعاءات. وسنقارن الطلب المحلي بـ API مستضاف، ونحدّد وسائل التحكّم في سحب العيّنات، وحدّ المُخرَجات، وحقول الردّ التي يستخدمها الـ harness.
قبل أن نرسل أي شيء، لنشرح الآلية باختصار. طلب HTTP هو رسالة من عميل إلى خادم، موجّهة إلى عنوان URL. يحمل الطلب method، مثل GET لجلب شيء أو POST لإرسال شيء، وبعض الـ headers التي تصف الرسالة، وعادةً جسمًا (body). في الجسم يوضع المحتوى. وفي كل API على الويب تقريبًا يكون JSON، وهي صيغة نصية تتألّف من حقول مسمّاة داخل أقواس متعرّجة، يسهل على البرامج كتابتها وقراءتها.
يجيب الخادم بردّ. فيه رمز حالة (status code)، قيمته 200 إذا سار كل شيء على ما يرام، وجسم خاص به، JSON أيضًا. الطلب وردّه معًا يشكّلان استدعاء API واحدًا.
إتاحة برنامج بهذه الطريقة، كمجموعة عناوين URL تستقبل JSON وتعيده عبر HTTP، تُسمّى REST API. وهي الطريقة المعيارية لإتاحة الخدمات عبر الإنترنت، وخادم النموذج هو خدمة أخرى من هذا النوع. نعرض هنا الشكل العام لهذه الرسائل فقط. وحين تحتاجون إلى بناء رسالة كهذه بأنفسكم، يمكنكم بالطبع الاستعانة بالوكيل عند الحاجة. الفكرة أن ننظر بعمق أكبر فيما يتبادله الـ harness والنموذج فعلًا.
يرسل curl طلبات HTTP من الطرفية ويطبع الردّ. وهو متاح على Linux وmacOS وWindows. نستخدمه هنا لإرسال محتوى ملف JSON إلى Ollama. الطلب هو POST إلى نقطة النهاية /api/chat في الخادم المحلي. يحدّد جسمه اسم النموذج، ويقدّم مصفوفة messages، فيها دور (role) ومحتوى (content) لكل رسالة. يحتوي هذا الطلب الأول على سؤال واحد من المستخدم. قيمة stream هي false، لذا يعود الردّ ككائن JSON واحد.
يحتوي الردّ على رسالة من المساعد (assistant) محتواها Paris. ويتضمّن أيضًا اسم النموذج، وحقولًا تصف كيف انتهى التوليد، وسنعود إليها قرب النهاية.
يسأل طلبنا الأول عن عاصمة فرنسا بكلمة واحدة. والردّ هو Paris، كما هو متوقّع. تذكّروا أن السؤال والردّ كليهما متاحان للبرنامج الذي أجرى هذا الاستدعاء.
أما الطلب الثاني فلا يرسل إلا "And of Italy?". والردّ المحفوظ يتحدث عن النبيذ الإيطالي. السؤال السابق عن العواصم غائب عن هذا الطلب، لذا على النموذج أن يفسّر النص الجديد من دونه.
لا يحتفظ الخادم بحالة المحادثة. فهو لا يحتفظ بشيء من الاستدعاء الأول، لذا يصل الاستدعاء الثاني دون رسائل سابقة. بل يحتفظ الـ harness بالمحادثة. ومع كل استدعاء، يرسل سجلّ المحادثة كاملًا مرة أخرى داخل الطلب.
لنبنِ سجلّ المحادثة هذا بشكل صريح. في مصفوفة messages الأولى عنصر واحد: سؤال المستخدم. ويعيد الخادم إجابة المساعد: Paris.
يرسل الاستدعاء الثاني ثلاث رسائل: السؤال الأصلي، وإجابة المساعد، والسؤال الجديد عن إيطاليا. صارت الإجابة السابقة جزءًا من المُدخَلات. مع هذا السياق، يكون الردّ هذه المرة: Rome.
يرسل الاستدعاء الثالث خمس رسائل، منها زوجا السؤال والإجابة السابقان، والسؤال الجديد عن أي المدينتين أبعد شمالًا. ويجيب النموذج: Paris. يتضمّن كل استدعاء نصّ المحادثة المتراكم، لذا يكبر الطلب كلما استمرت المحادثة.
البنية الأساسية نفسها تظهر في الـ APIs المستضافة. على اليسار طلبنا المحلي إلى Ollama، وفيه اسم النموذج وقائمة رسائل، لكل منها دور ومحتوى.
وعلى اليمين مثال توضيحي لطلب إلى Messages API من Anthropic. فيه عنوان URL مستضاف، وheader لمفتاح API، وheader للإصدار، وحقول إضافية في الجسم. حدّ المُخرَجات هنا 1024 token، ويتضمّن المثال أيضًا تعليمات النظام وتعريفات الأدوات. هذا الطلب لا يتطابق مع طلب Ollama بايتًا ببايت. وجه الشبه المفيد هو أنه يقدّم تعليمات ورسائل محادثة ليكملها النموذج.
هذا هو الجزء الذي يبني الطلبات في الـ harness. فهو يجمع تعليمات النظام، وما يلزم من سجلّ المحادثة، وتعريفات الأدوات، ثم يرسلها بالصيغة الخاصة بمزوِّد الخدمة. ويعالج كود آخر في الـ harness الردّ والصلاحيات وتشغيل الأدوات قبل الاستدعاء التالي.
هناك عدة وسائل تحكّم تؤثر في التوليد. درجة الحرارة تغيّر التوزيع الاحتمالي. ويقيّد top-p وtop-k الـ tokens التي تؤخذ في الحسبان. والحدّ الأقصى للـ tokens يقيّد طول المُخرَجات. والـ seed، إن كان مدعومًا، يتحكّم بتسلسل الأرقام العشوائية، مما يساعد على تكرار تجربة حين يبقى باقي الإعداد ثابتًا.
ويعتمد توفّر الوسائل وأسماء حقولها وقيمها الافتراضية على الـ API والنموذج. يتيح Ollama هذه الوسائل عبر حقل options، بينما قد لا يدعم نموذج مستضاف إلا بعضها. وحتى مع الـ seed، لا تفترضوا أن النتائج ستتطابق على عتاد مختلف أو مع إصدارات مختلفة من الخادم.
ويخبر الردّ البرنامج المستدعي أيضًا بما حدث أثناء التوليد. وفيه نوعان مفيدان من المعلومات: سبب توقّف التوليد، وأعداد الـ tokens. قد يشير سبب التوقّف إلى اكتمال الردّ، أو إلى طلب لتشغيل أداة، أو إلى بلوغ حدّ المُخرَجات. وتساعد أعداد الـ tokens في المُدخَلات والمُخرَجات على قياس الاستخدام وفهم التكلفة، في API مدفوع. الاستدلال المحلي يستهلك موارد حوسبة أيضًا، لكن لا تترتّب عليه فاتورة الـ API التي يحسبها المزوّد لكل token.
يجمع الـ harness بين سبب التوقّف ومحتوى الردّ ليقرّر كيف يتابع. إذا طلب النموذج أداة، يتحقّق الـ harness من الإجراء المطلوب، ويشغّله إذا كان ذلك مسموحًا، ويضمّن النتيجة في الاستدعاء التالي.
يمكنكم الآن بناء استدعاء للنموذج بأنفسكم، وشرح حاجة السؤال التالي إلى سجلّ المحادثة. في هذه الأمثلة، المحادثة هي قائمة الرسائل التي نرسلها، وليست محادثة يستعيدها الخادم تلقائيًا.
يستخدم المثالان المحلي والمستضاف الفكرة نفسها، مع نقاط نهاية ومصادقة وحقول جسم خاصة بكل مزوِّد. وقراءة هذه الحقول تجعل الطلب مفهومًا دون أن نعامل الـ API كخدمة محادثة خفية. يُؤتمت الـ harness هذا العمل طوال حلقة الوكيل. وحين تفحصون بنية الطلب، تميّزون ما يرسله الـ harness وتفسّرون ما يعود إليه.
نراكم في الفيديو التالي.