Tool Calling والمخرجات المنظَّمة: العقد الذي يصمد
24 استدعاءً، JSON سليم دائمًا، وتاريخان صالحان. ثم endpoint نفسه بوصف أفضل، وما لا يستطيع schema إصلاحه.
في هذه الصفحة
امنح نموذجًا أداة للبحث عن الرحلات واطلب منه العثور على رحلة من مدريد إلى برلين. هذا ما يعود إليك:
<tool_call>
{"name": "search_flights",
"arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>الـ JSON صالح. اسم الأداة صحيح. كل حقل مطلوب موجود. ومع ذلك فالاستدعاء عديم الفائدة: لا تقبل أي API للرحلات "Madrid" حيث تريد رمز مطار، ولا "3rd October 2026" حيث تريد تاريخًا.
هذه الفجوة — سليمة نحويًا، وغير قابلة للاستخدام دلاليًا — هي موضوع هذا الفصل، وأول ما يجب تثبيته أنها ليست مشكلة JSON. عبر أربعٍ وعشرين طلبًا بهذه الأداة، أنتج النموذج 24 tool calls صالحة وصفر JSON مكسور. لم يفشل ولا مرة واحدة في الجزء الذي ينشغل الجميع بتصحيحه.
النموذج لا ينفّذ شيئًا
رابط إلى القسم: النموذج لا ينفّذ شيئًاقبل التفاصيل العملية، هذه هي الجملة التي تمنع معظم الالتباس: tool call هو طلب، وليس فعلًا.
يُصدر النموذج رسالة منظَّمة تقول أريد استدعاء search_flights بهذه الوسائط. ثم يتوقف. يتلقى كودك تلك الرسالة، ويقرر هل يلبّيها أم لا، ويستدعي ما سيستدعيه، ثم يرسل النتيجة مرة أخرى كرسالة أخرى. لم يلمس النموذج قاعدة بياناتك، ولم يجرِ طلب HTTP، ولم تكن لديه أي بيانات اعتماد.
كل ما يتعلق بأمان agent في الفصل 30 ينبع من هذا الفصل بين الأدوار، وكذلك كل ما يتعلق بتصميم agent في الفصل 23: النموذج يقترح وكودك يقرر، والكود هو المكان الذي تعيش فيه كل الضمانات.
لذلك، إذا جرّدنا الأداة من المصطلحات، فهي شيئان:
Schema. JSON Schema يصف function: اسمها، وما تفعله، وما الوسائط التي تقبلها مع أنواعها وقيودها. هذا هو ما يدخل في prompt، وهو الشيء الوحيد الذي يراه النموذج أصلًا.
Endpoint. function في كودك تأخذ تلك الوسائط وتعيد شيئًا. لا يراها النموذج أبدًا، ولا يعرف بأي لغة كُتبت، ولا يستطيع التمييز بين استعلام قاعدة بيانات وسلسلة نصية ثابتة.
ترسل schemas مع الطلب
رابط إلى القسم: ترسل schemas مع الطلبتدخل تعريفات الأدوات في prompt، مسلسلة إلى أي صيغة تدرّب النموذج عليها. وهي تكلّف tokens في كل استدعاء منفرد — حقيقة تعود لاحقًا في هذا الفصل برقم.
يجيب النموذج باستدعاء بدلًا من نص
رابط إلى القسم: يجيب النموذج باستدعاء بدلًا من نصبدل النثر، يحتوي الرد على طلب منظَّم، وتبلّغ API عن سبب انتهاء يشير إلى ذلك. السبب مهم: فهو الطريقة التي يعرف بها كودك أنه يجب تشغيل أداة بدلًا من عرض إجابة للمستخدم.
كودك يشغّله — أو يرفضه
رابط إلى القسم: كودك يشغّله — أو يرفضههذه هي الخطوة التي لا يوجد فيها نموذج. تحقّق من الوسائط مقابل schema، وقرر هل يُسمح لهذا المستدعي بفعل ذلك، ثم نفّذ.
ترسل النتيجة مرة أخرى كرسالة
رابط إلى القسم: ترسل النتيجة مرة أخرى كرسالةتصبح النتيجة دورًا آخر في المحادثة، ضمن دور مخصّص لها. يقرأها النموذج مثل أي context آخر.
يجيب النموذج، أو يطلب أداة أخرى
رابط إلى القسم: يجيب النموذج، أو يطلب أداة أخرىوهذه هي حلقة الفصل 23، والسبب في أن طلبًا واحدًا قد يتحول إلى عشرات الرحلات ذهابًا وإيابًا.
لا شيء من هذا ناشئ تلقائيًا. كما أثبت الفصل 11، فإن tool calling هو سلوك مُدرَّب:1 أثناء ما بعد التدريب رأى النموذج آلاف المحادثات المصاغة تمامًا بهذا الشكل. لهذا تكون الصيغة خاصة بالنموذج، ولهذا تختلف الموثوقية كثيرًا بين نماذج متقاربة الحجم، ولهذا يستطيع النموذج استدعاء أداة لم يرها من قبل — الشكل هو ما تدرّب عليه، أما الأداة المحددة فتأتي من prompt الخاص بك.
تكلفة schema سيئ، بالأرقام
رابط إلى القسم: تكلفة schema سيئ، بالأرقامهذه هي الأداة كما يكتبها معظم الناس في البداية. لاحظ أن لا شيء فيها خاطئ؛ إنها فقط نحيفة:
{
name: "search_flights",
description: "Search for flights.",
parameters: {
type: "object",
properties: {
from: { type: "string", description: "Airport." },
to: { type: "string", description: "Airport." },
date: { type: "string", description: "The date." },
},
required: ["from", "to", "date"],
},
}أربعٌ وعشرون طلبًا، ستة أزواج مدن مضروبة في أربع طرق للتعبير عن تاريخ («الثالث من الشهر القادم»، «الجمعة القادمة»، «15 ديسمبر»، «غدًا»)، مع greedy decoding كي يمكن إعادة إنتاج النتائج:
| تم استدعاء الأداة | JSON مكسور | التاريخ بصيغة ISO | المطارات بصيغة IATA | كل شيء صحيح | |
|---|---|---|---|---|---|
| schema أعلاه | 24/24 | 0 | 2/24 | 4/24 | 1/24 |
اقرأ أول عمودين قبل الأعمدة الثلاثة الأخيرة. يستدعي النموذج الأداة الصحيحة في كل مرة، وينتج JSON صحيح البنية في كل مرة. الفشل كله في القيم، والقيم غير قابلة للاستخدام: "Madrid" بدلًا من MAD، و"3rd October 2026" بدلًا من 2026-10-03.
هذا يستحق التأكيد لأنه يحدد أين تنظر عندما ينكسر شيء. الغريزة هي إضافة محلّل JSON مع إعادة محاولة، أو مطالبة النموذج بحزم أكبر بإخراج JSON صالح. لا يعالج أي منهما أي شيء مما حدث هنا.
الآن غيّر الوصف فقط
رابط إلى القسم: الآن غيّر الوصف فقطنفس endpoint. نفس الكود خلفه. نفس النموذج، نفس prompts، نفس decoding. الشيء الوحيد الذي يتغير هو النص داخل schema:
{
name: "search_flights",
description: "Search scheduled flights between two airports on a given day.",
parameters: {
type: "object",
properties: {
from: {
type: "string",
description: "Departure airport as a three-letter IATA code, e.g. MAD for Madrid. Never a city name.",
pattern: "^[A-Z]{3}$",
},
to: { /* same */ },
date: {
type: "string",
description: "Departure date as an ISO 8601 calendar date, YYYY-MM-DD. Resolve relative dates against today before calling.",
format: "date",
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
},
},
required: ["from", "to", "date"],
},
}| FORMAT التاريخ | VALUE التاريخ | FORMAT المطار | VALUE المطار | |
|---|---|---|---|---|
| schema نحيف | 2/24 | 1/24 | 4/24 | 4/24 |
| schema موصوف | 24/24 | 12/24 | 16/24 | 8/24 |
ينتقل تنسيق التاريخ من 2 من 24 إلى 24 من 24. نتيجة كاملة، من تغيير نصي، بلا لمس للكود وبلا منطق إعادة محاولة. إذا أخذت عادة تشغيلية واحدة من هذا الفصل، فهي هذه: عندما تُستدعى أداة بشكل خاطئ، فالإصلاح يكون غالبًا في الوصف، وهو أرخص إصلاح في النظام.
والآن اقرأ العمود الثاني، فهو النصف الأهم.
schema يقيّد الشكل. لكنه لا يوفّر المعرفة.
رابط إلى القسم: schema يقيّد الشكل. لكنه لا يوفّر المعرفة.التاريخ بصيغة ISO في 24 مرة من 24. لكنه اليوم الصحيح في 12 مرة من 24.
إذن نصف الاستدعاءات يحمل الآن تاريخًا منسقًا بإتقان، لكنه التاريخ الخطأ. أخبر الوصف النموذج بالشكل الذي يجب إنتاجه، فأنتجه النموذج بلا عيب — لكن تحويل «الجمعة القادمة» إلى 2026-09-11 يتطلب معرفة تاريخ اليوم وإجراء حساب تقويمي، ولا يوفّر أي مقدار من الوصف ذلك. والأمر نفسه مع المطارات: انتقل التنسيق من 4 إلى 16، لكن القيمة من 4 إلى 8 فقط، لأن كتابة MAD تتطلب معرفة أن مطار مدريد هو MAD.
هذا التمييز هو الفكرة الحاملة للثقل في الفصل:
schema هو عقد حول الشكل. يستطيع جعل مخرجات النموذج قابلة للتحليل، ومكتوبة الأنواع، ومتسقة. لكنه لا يستطيع جعلها صحيحة، وكل نمط فشل ينجو من schema جيد هو فشل معرفة، لا فشل تنسيق.
يحتاج الاثنان إلى إصلاحات مختلفة، والخلط بينهما يبدد أسابيع. تُصلح إخفاقات التنسيق في الوصف أو باستخدام constrained decoding أدناه. وتُصلح إخفاقات المعرفة عبر وضع المعرفة في prompt — التاريخ الحالي في رسالة النظام، أو أداة بحث عن المطارات كـ أداة ثانية يستدعيها النموذج أولًا، أو enum داخل schema عندما تكون المجموعة صغيرة بما يكفي لتعدادها. لاحظ ما تشترك فيه هذه الثلاثة: إنها تنقل المشكلة من ذاكرة النموذج إلى مدخلاته، وهذا هو كل الفصل 24.
المخرجات المنظَّمة، وما يعنيه «constrained decoding» فعليًا
رابط إلى القسم: المخرجات المنظَّمة، وما يعنيه «constrained decoding» فعليًاكل ما سبق لا يزال يعتمد على أن النموذج يختار إنتاج الشكل الصحيح. هناك ضمان أقوى متاح، وهو أفضل عائد من الفصل 17.
تذكّر كيف يعمل التوليد: في كل خطوة ينتج النموذج logit لكل token في المفردات، ويختار المعيّن واحدًا. Constrained decoding يُدخل خطوة بينهما. انطلاقًا من قواعد — مشتقة من JSON Schema لديك — يحسب أي tokens يمكن أن تأتي قانونيًا تاليًا، ويضبط logits كل الباقي إلى سالب اللانهاية، ثم يترك المعيّن يختار مما تبقى.
إذا قال schema إن الشيء التالي يجب أن يكون {، فكل token ليس { احتماله صفر. ليس «غير مرجح»: صفر. لا يستطيع النموذج إخراج JSON غير صالح لأن tokens غير الصالحة أُزيلت من التوزيع قبل العيّنة.
هذا هو ما تقوم عليه «structured outputs» و«JSON mode» و«guided generation»، وهو يشرح خاصيتين لهما. الضمان كامل لأي شيء تستطيع القواعد التعبير عنه — الأنواع، الحقول المطلوبة، enums، التداخل — لأنه يُفرض آليًا بدل أن يُطلب بأدب. ولا يقول شيئًا عن المحتوى: يمكن لقواعد أن تُجبر "date" على أن يكون سلسلة تطابق نمط تاريخ، ولا يمكنها إجباره على أن يكون اليوم الصحيح. وهذا هو الجدار نفسه من القسم السابق، وصلنا إليه من الجهة الأخرى.
ملاحظتان عمليتان. ليس مجانيًا: يجب حساب القناع في كل خطوة، والقواعد المعقدة تكلّف زمن استجابة قابلًا للقياس. كما أنه يغيّر ما يفعله النموذج — نموذج يُدفَع بعيدًا عن token المفضل لديه قد ينتج محتوى أسوأ مع بنية مثالية، ولهذا يظل «اطلب بلطف وتحقق» خيارًا افتراضيًا معقولًا للأشكال البسيطة، بينما يستحق constrained decoding تكلفته عندما يكون الشكل معقدًا أو يكون المستهلك صارمًا.
الآثار الجانبية، والخاصية الوحيدة التي تهم
رابط إلى القسم: الآثار الجانبية، والخاصية الوحيدة التي تهمقاس الفصل 14 مهلة انتهاء تتبعها إعادة محاولة فترتّب عليها احتساب توليدين لإجابة واحدة. مع الأدوات يصبح الفشل نفسه أسوأ، لأن الأداة يمكن أن تفعل شيئًا.
إذا استدعى كودك charge_card، وانتهت مهلته، ثم أعاد المحاولة، فلديك عمليتا تحصيل. ليس لدى النموذج أي فكرة أن أيًا من هذا حدث؛ هو يرى نتيجة أداة واحدة. الإصلاح هو نفسه كما في أي نظام موزّع، وليس مشكلة النموذج: اجعل العملية idempotent بإعطاء الاستدعاء مفتاحًا، بحيث يتعرف التنفيذ الثاني على الأول ويعيد نتيجته بدل تنفيذ العمل مرة أخرى.
قاعدة التصميم الناتجة تستحق قولها بوضوح. افصل القراءات عن الكتابات في فهرس أدواتك. القراءة يمكن إعادة محاولتها بحرية، وتشغيلها بالتوازي، وتخزينها مؤقتًا. أما الكتابة فلا، ويجب أن تحمل مفتاحًا، وفحص صلاحيات، و— في أي شيء يريد المستخدم أن يعرف عنه قبل حدوثه — خطوة موافقة تضع إنسانًا بين الطلب والفعل. خطوة الموافقة هذه ليست مجاملة: إنها إحدى الأشياء القليلة الواقفة بين prompt injection ونتيجة حقيقية — وكما يقيس الفصل 30، هي أضعفها.
كم أداة قبل أن يتدهور الأداء؟
رابط إلى القسم: كم أداة قبل أن يتدهور الأداء؟تقول الحكاية الشائعة إن تحميل أدوات كثيرة يجعل النموذج يختار بشكل سيئ. الأمر يستحق القياس بدل التكرار، لذا: نفس الطلبات الأربع والعشرين، مع أداة الرحلات إضافة إلى مجموعة متنامية من أدوات أخرى — منها ثلاث أدوات قابلة للالتباس عمدًا (جداول القطارات، معابر العبّارات، مسارات الحافلات).
| الأدوات المحمّلة | prompt tokens | اختار search_flights | التاريخ بصيغة ISO |
|---|---|---|---|
| 1 | 353 | 24/24 | 24/24 |
| 5 | 730 | 24/24 | 24/24 |
| 10 | 1,193 | 21/24 | 21/24 |
| 20 | 2,119 | 24/24 | 24/24 |
لم يتدهور الاختيار. مع عشرين أداة، ثلاث منها قابلة منطقيًا للالتباس، اختار نموذج بنصف مليار معلمة الأداة الصحيحة أربعًا وعشرين مرة من أربعٍ وعشرين. الانخفاض عند عشرة هو ثلاثة استدعاءات سمّت أداة مختلفة، ولا يصمد عند الانتقال إلى عشرين.
هذه نتيجة سلبية ويجب الإبلاغ عنها كذلك: في هذه المهمة، وبهذه الأدوات، لم تكن «الأدوات الكثيرة جدًا» هي المشكلة. ما نما، رتيبًا وبعامل ستة، هو prompt: من 353 tokens إلى 2,119، تُدفع في كل طلب داخل المحادثة، إلى الأبد، سواء استُخدمت أي أداة أم لا.
إذن النسخة الصادقة من الحكاية الشائعة تتعلق بـ التكلفة والـ context، لا الدقة. عشرون أداة ضريبة دائمة على كل رسالة، وقد أظهر الفصل 16 بالفعل ما يفعله prefix دائم بفاتورة عبر أربعين دورًا. عندما يبلّغ الناس أن كثرة الأدوات تؤذي الجودة، فالآلية عادةً أن التعريفات زاحمت context المهم — وهي مشكلة من الفصل 24 ترتدي زي الفصل 18. الأدوات التي تكون فعلًا شبه مكررة من بعضها مشكلة حقيقية أيضًا، وإصلاحها ليس أدوات أقل بل أوصافًا وnamespaces أفضل: سبّقها بالنظام (crm.search_customer، billing.search_customer) حتى لا يتصادم فهرسان مدموجان من فريقين، وحتى يكون لدى النموذج ما يميز بناءً عليه.
ثلاثة أنواع من الأدوات، والنوع الذي يفتح الجزء التالي
رابط إلى القسم: ثلاثة أنواع من الأدوات، والنوع الذي يفتح الجزء التالييساعد فرز الأدوات حسب ما تفعله بالعالم، لأن الهندسة تختلف لكل نوع.
أدوات البيانات تقرأ: تبحث، تجلب، تستعلم. قابلة لإعادة المحاولة، وقابلة للتوازي، وقابلة للتخزين المؤقت. تفشل بإرجاع لا شيء مفيد، وخطرها الرئيسي أنها تجلب نصًا غير موثوق إلى context — وهذا هو كامل سطح الهجوم في الفصل 30.
أدوات الفعل تكتب: ترسل، تنشئ، تحصّل، تحذف. غير قابلة لإعادة المحاولة بلا مفتاح، ولا يمكن تشغيلها بالتوازي بأمان، وهي السبب في وجود مسارات الموافقة.
أدوات التنسيق تستدعي نماذج أخرى. أداة يكون تنفيذها agent آخر، له prompt الخاص به، وأدواته الخاصة، وحلقته الخاصة — وبالنسبة إلى النموذج المستدعي تبدو تمامًا مثل النوعين الآخرين، لأن schema وendpoint هما كل ما يراه أصلًا.
هذا النوع الثالث ليس طرافة. إنه الآلية خلف نصف agent-as-a-tool من الفصل 25 — الطوبولوجيا الأخرى، handoff، تمنح المحادثة لغيرها ولا تستعيدها أبدًا — ويعمل تحديدًا لأن الواجهة في هذا الفصل ضيقة بما يكفي ليختبئ agent كامل خلفها.
إلى أين يذهب هذا بعد ذلك
رابط إلى القسم: إلى أين يذهب هذا بعد ذلكلديك الآن نموذج يستطيع طلب الأشياء، وعقد يجعل الطلب قابلًا للتحليل. ما لا تملكه هو أي شيء يطلب عنه يتجاوز ما يتسع له prompt.
الأداة الأكثر شيوعًا في الإنتاج، بفارق واسع، هي البحث داخل جسم نصي لم يره النموذج أثناء التدريب: وثائقك، وتذاكرك، وعقودك. يبدو ذلك كمشكلة محلولة — اعمل له embedding، واعثر على أقرب الجيران، والصقهم في prompt — لكن الأجزاء غير المحلولة هي التي تقرر هل الإجابة موثوقة: كيف يُقطَّع النص قبل embedding، وما عتبة التشابه المنخفضة بما يكفي لتعني لا أعرف، وكيف تُلحق إحالة بادعاء حتى يستطيع القارئ التحقق منه.
الفصل 19 هو الاسترجاع، وهو الفصل الذي تتوقف فيه الإجابة الخاطئة عن كونها فضولًا وتبدأ بأن تصبح مسؤولية.
المصادر والمنهج
رابط إلى القسم: المصادر والمنهجتأتي القياسات في هذا الفصل من Qwen/Qwen2.5-0.5B-Instruct مع greedy decoding، عبر 24 طلبًا مولّدًا تجمع ستة أزواج مدن مع أربع صياغات للتاريخ، باستخدام قالب الدردشة الخاص بالنموذج نفسه لتعريفات الأدوات. تُعاد إنتاجها تمامًا، وهي لنموذج صغير: اقرأ فصل التنسيق/القيمة كعرض للآلية لا كمعيار لما تفعله النماذج الحالية. يحل نموذج frontier عبارة «الجمعة القادمة» بشكل صحيح في مرات أكثر بكثير — ومع ذلك لا يمكن إجباره على ذلك بواسطة schema، وهذا هو الجزء الذي يعمّم.
مفردات JSON Schema المستخدمة أعلاه (type، properties، required، pattern، format، enum) محددة في مسودة JSON Schema التي تسميها وثائق مزودك؛ الجزء المفيد صغير ومتشابه عبر المزودين، والفروق الموجودة — أي الكلمات المفتاحية تُفرض بواسطة constrained decoding بدل تمريرها فقط إلى النموذج — تستحق القراءة في دليل structured-output لدى المزود بدل افتراضها.
بالنسبة إلى constrained decoding كتقنية، توثّق مكتبات أسلوب guidance ومشروع outlines بناء القواعد إلى قناع logit بطريقة تنطبق مباشرة على sampler في الفصل 17. أما الرحلة ذهابًا وإيابًا نفسها، فأوضح مواصفة ليست درسًا تعليميًا بل بروتوكول: الفصل 26 يقرأه سطرًا بسطر.
المراجع
رابط إلى القسم: المراجع-
Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). الورقة التي جعلت وصفة ما بعد التدريب معيارية؛ شكل tool call يُتعلَّم هناك، من العروض التوضيحية، تمامًا مثل شكل الإجابة. ↩