Skip to main content
استدعاء دالة واحدة أمر سهل. الجزء المثير هو الحلقة المحيطة به، لأن النموذج نادرًا ما يحصل على ما يحتاجه من الاستدعاء الأول. يبحث عن معلومة، يرى النتيجة، ثم يقرّر ماذا يطلب بعد ذلك. يبني هذا الدليل التعليمي وكيلًا يعمل من سطر الأوامر يُجيب عن أسئلة تتعلق بقاعدة بيانات SQLite لم يرَها من قبل. لا يوجد مخطط في موجّهه (prompt). يحصل على ثلاث أدوات للقراءة فقط ويستنتج الباقي بنفسه:
على طول الطريق سنقوم بما يلي:
  1. نمنح النموذج قاعدة بيانات وثلاث أدوات تقرأ منها
  2. نصف تلك الأدوات ليعرف النموذج متى يلجأ إلى كل منها
  3. نُشغّل الحلقة التي تحوّل استدعاءات الأدوات إلى نتائج
  4. نراقبه وهو يطلب عدة أدوات دفعة واحدة
  5. نُعيد الأخطاء إلى النموذج بدلًا من رفعها كاستثناءات
  6. نُميّز بين ما لن يفعله النموذج وما لا يستطيع فعله
يغطي دليل استدعاء الدوال شكل الطلب بحد ذاته. هذه الصفحة تدور حول ما يحدث بعد أن يعود أول ردّ.

الإعداد

تحتاج إلى Python 3.9 أو أحدث، وحزمة requests، ومفتاح Venice API. راجع إنشاء مفتاح API إن لم يكن لديك واحد. كل شيء آخر موجود في المكتبة القياسية.
أنشئ ملف agent.py مع سطور الاستيراد وكتلة الترويسة التي يُعيد كل استدعاء استخدامها:
ليس كل نموذج قادرًا على استدعاء الأدوات، ومعرّفات النماذج تتغيّر، لذا اسأل واجهة API عن أيّها تستخدم بدلًا من تثبيت اسم سيتقادم مع الزمن:
يقوم GET /models/traits بربط أسماء السمات الثابتة بأيّ نموذج يشغل ذلك الدور حاليًا. قراءة function_calling_default عند بدء التشغيل تعني أن وكيلك سيظل يعمل عند استبدال النموذج الأساسي. راجع النماذج للاطلاع على القائمة الكاملة للسمات.

1. قاعدة بيانات تستحقّ السؤال عنها

سيفي أي ملف SQLite بالغرض. هذا الملف عبارة عن متجر صغير فيه عملاء ومنتجات وطلبات تربطهم، وهو ما يكفي لكي يتطلّب سؤال حقيقي عملية JOIN وتجميعًا:

2. ثلاث أدوات يمكن للنموذج اللجوء إليها

تُحاكي الأدوات الطريقة التي يتعرّف بها شخص ما على قاعدة بيانات غير مألوفة: اكتشاف ما فيها، ثم النظر في جدول واحد عن كثب، ثم الاستعلام منه.
كل واحدة منها تُعيد سلسلة JSON، بما في ذلك حالات الفشل. هذا مقصود، والقسم الخامس يشرح السبب. الآن صِف الأدوات للنموذج. حقل description ليس تعليقًا، بل هو الشيء الوحيد الذي يقرأه النموذج عند تقرير أيّ أداة يستدعيها وما الذي يضعه فيها:

3. الحلقة

استدعاء الدوال محادثة، وليس طلبًا واحدًا. يردّ النموذج باستدعاءات أدوات، فتُشغّلها، ثم تُلحق النتائج، ثم تسأل مرة أخرى. تنتهي الحلقة عندما يردّ النموذج بمحتوى بدلًا من استدعاءات.
ثلاث تفاصيل في تلك الحلقة أهمّ ممّا تبدو عليه. تُعاد رسالة المساعد كما هي دون تعديل إلى messages قبل النتائج. فهي تحمل tool_calls التي تُجيب عنها النتائج، وفي نموذج تفكير (reasoning model) تحمل أيضًا حقل reasoning_content. إعادة بناء الرسالة يدويًا وإسقاط الحقول التي لم تتوقّعها هي أكثر طريقة شيوعًا لكسر الجولة الثانية. تُطابَق كل نتيجة مع استدعائها بواسطة tool_call_id. لا شيء آخر يُميّزها. max_rounds حدّ حقيقي، لا مجرد شكليّة. النموذج الذي يستمر بالاستعلام دون التوصّل إلى نتيجة سيدور في حلقة حتى ينفد صبرك أو رصيدك.
استدعاءات الأدوات تحمل أيضًا حقل index، ويُغري استخدامه لمطابقة النتائج مع الاستدعاءات. لا تفعل ذلك. حين يطلب النموذج ثلاث أدوات دفعة واحدة، يمكن أن تصل الثلاثة بنفس index، لأنه يُرقّم دور المساعد لا الاستدعاء داخله. id وحده هو الفريد.

4. ما الذي يفعله فعلًا

اربط كتلة main وشغّله:
تُطبع استدعاءات الأدوات إلى stderr أثناء حدوثها، فيمكنك متابعة عمله:
استغرق ذلك خمس جولات. شكل هذه الجولات جدير بالقراءة عن قرب، لأنه هو المبرر الكامل للحلقة: الجولة الرابعة هي الجزء الذي لا يستطيع استدعاء دالة واحدة القيام به. لم يكن بمقدور النموذج كتابة ذلك الاستعلام قبل أن يرى إجابة الاستعلام الذي سبقه. لن يُطابق تشغيلك هذا التشغيلَ استدعاءً باستدعاء. أحيانًا يصف النموذج الجداول الثلاثة دفعة واحدة، وأحيانًا واحدًا في كل مرة، وأحيانًا يتخطّى list_tables ويُخمّن اسمًا. الأرقام ثابتة لأنها تأتي من قاعدة البيانات؛ أما الطريق إليها فليس كذلك.
أعادت الجولة الثانية ثلاثة استدعاءات أدوات في ردٍّ واحد، والحلقة أعلاه تُشغّلها واحدًا تلو الآخر. وهي مستقلّة، لذا يستحقّ استخدام ThreadPoolExecutor هنا فور أن تُنفّذ أدواتك عمليات إدخال/إخراج فعلية. حافظ على ترتيب رسائل tool بنفس ترتيب الاستدعاءات التي أنتجتها.
تُعيد كل جولة إرسال المحادثة بأكملها، لذا يكبر الموجّه مع عمل الوكيل. تقوم Venice بتخزين البادئة المستقرّة مؤقتًا بشكل تلقائي، وتُظهر كتلة usage أن ذلك يؤتي ثماره:
في الجولة الأخيرة، خُدم 960 من أصل 1020 من رموز الموجّه من الذاكرة المؤقتة. يغطي دليل التخزين المؤقت للموجّهات كيفية إبقاء تلك البادئة مستقرّة.

5. دع الأخطاء تصل إلى النموذج

الغريزة هي رفع استثناء عند استعلام سيّئ. قاوم ذلك. الخطأ معلومة، والنموذج يستطيع التصرّف بناءً عليها. اطلب جدولًا غير موجود:
فشل الاستعلام الأول. ولأن run_query أعادت {"error": "OperationalError: no such table: purchases"} بوصفها نتيجة أداة عادية بدلًا من رفع استثناء، قرأها النموذج، واستدعى list_tables ليعرف ما هو موجود فعلًا، ثم صحّح نفسه. لو انتقل الاستثناء إلى أعلى، لكان السكربت قد توقّف بسبب خطأ إملائي. هذا هو السبب في أن كل أداة تُعيد JSON في مسار الفشل أيضًا. القاعدة بسيطة: إذا كان مبرمج يُصحّح أداتك سيرغب في رؤية الرسالة، فإن النموذج يرغب في ذلك أيضًا.

6. ما لن يفعله، وما لا يستطيع فعله

اطلب من الوكيل تدمير شيء:
شغّل ذلك مرتين وقد تحصل على سلوكين مختلفين. مرّة رفض قبل أن يمسّ أي أداة:
ومرّة أخرى بحث أولًا، وأجرى SELECT عن العملاء الإسبان، ولم يجد أحدًا لأن العمود يحفظ ES وليس Spain، فأفاد بذلك بدلًا من الحذف:
كلاهما معقول. ولا شيء منهما إجراء أمني. قرأ النموذج عبارة “read-only” في وصف الأداة واختار احترامها، وقد يُنتج نموذج مختلف أو محادثة أطول أو مستخدم أكثر إلحاحًا اختيارًا مختلفًا. الحماية داخل run_query هي الجزء الذي لا يعتمد على اختيار:
اكتب الوصف بحيث نادرًا ما يحاول النموذج. واكتب الحماية بحيث لا يهمّ الأمر حين يحاول.
السطر الثاني هو سبب التقاط run_query لـ sqlite3.Warning إلى جانب sqlite3.Error. يرفض مُشغّل Python تنفيذ عبارات مكدّسة، لكنه يرفع Warning من أجلها، وWarning ليست فئة فرعية من Error. الاكتفاء بالتقاط sqlite3.Error يترك العبارة المكدّسة تُفلت من المُعالج وتُنهي الحلقة بدلًا من إعادة رسالة يستطيع النموذج قراءتها.
فحص البادئة يمنع الكتابة، لكنه لا يقول شيئًا عن القراءة. أي SELECT يكتبه النموذج يستطيع الوصول إلى كل جدول في الملف، بما في ذلك جداول لم تقصد كشفها أبدًا. يستحقّ إجراء تغييرين قبل أن يمسّ هذا بيانات حقيقية: افتح قاعدة البيانات للقراءة فقط باستخدام sqlite3.connect("file:shop.db?mode=ro", uri=True)، الذي يُفشل عمليات الكتابة بـ attempt to write a readonly database مهما فوّت فحص السلسلة، ووجِّه الوكيل إلى قاعدة بيانات أو مجموعة من العروض (views) تحتوي فقط على الأعمدة التي يُسمح له برؤيتها.

التحكّم في متى تُستخدم الأدوات

يقرّر tool_choice قدر الصلاحية المتاحة للنموذج: "required" أفظّ ممّا يبدو. سؤال هذا الوكيل What is 2 + 2? مع ضبط tool_choice على "required" يجعله يستدعي list_tables، وينظر في قاعدة بيانات لا حاجة له بها، ثم يجيب 4 في الجولة التالية. مع "auto" يجيب 4 فورًا ولا يستدعي شيئًا. لجأ إلى "required" عندما يجب على أداةٍ أن تعمل فعلًا، مثل تسجيل طلب، ودعها وشأنها في غير ذلك.

ضبط الوكيل

الخطوات التالية

الحلقة التي أصبحت لديك الآن هي نفسها الحلقة التي تقف خلف معظم الوكلاء. تتغيّر الأدوات فقط.

استدعاء الدوال

مرجع لمصفوفة الأدوات و tool_choice.

الاستجابات المُهيكلة

قيّد الإجابة النهائية بمخطط JSON.

التخزين المؤقت للموجّهات

أبقِ المحادثة المتنامية زهيدة الثمن.

وكيل البحث الخاص

الحلقة نفسها مع أدوات ويب ومخطّط.