إجابة سريعة: تعمل استدعاءات الأدوات غير المتزامنة (Async Tool Calls) في وكلاء الذكاء الاصطناعي على تقسيم استدعاء run() الفردي الذي يحظر التنفيذ (Blocking) إلى بروتوكول تعاقدي بسيط: تعيد دالة start() مقبض المهمة (Job handle) على الفور، وتُبلغ status() عن مستوى التقدم، بينما تتولى stop() الإلغاء، ثم تعود النتيجة لاحقاً كحدث يقرؤه الوكيل في دورته (Turn) التالية. وبذلك يواصل الوكيل التحدث واستقبال تعليمات جديدة أثناء تنفيذ المهام البطيئة في الخلفية.
تعتمد معظم الوكلاء اليوم على استدعاء الأداة ثم الانتظار. فإذا طرحت سؤالاً ثانياً أثناء تشغيل اختبارات تستغرق 6 دقائق، فسيظل السؤال عالقاً في قائمة الانتظار. قد يكون هذا مقبولاً لعملية استعلام تستغرق 200 مللي ثانية، لكنه مزعج للغاية عند نشر التعليمات البرمجية (Deploy) أو الزحف على الويب (Crawl).
أطلقت ورقة بحثية حديثة لفريق DeepSeek Harness بعنوان “Exploring Super-agent Design” اسم "الازدواج الكامل" (Full-duplex) على هذا الحل: استبدال دالة run() الحاجبة بدالتي start() وstop(). يغطي هذا الدليل الجانب الخاص بحلقة الوكيل (Agent loop): بروتوكول الأداة، وكيفية إعادة توجيه النتائج، والإلغاء، وواجهة البرمجة (API) التي تحتاجها كل أداة. أما بالنسبة لجانب HTTP في عمليات الاستدعاء الفردية البطيئة، فيمكنك مراجعة مقال وكلاء الذكاء الاصطناعي واستدعاءات واجهات البرمجة طويلة الأمد: الاستقصاء الدوري مقابل خطافات الويب (Polling vs Webhooks).
ما هي استدعاءات الأدوات غير المتزامنة في حلقة الوكيل؟
استدعاء الأداة غير المتزامن هو استدعاء للأداة يعيد مقبضاً للمهمة بدلاً من إرجاع النتيجة مباشرة. تسجل حلقة الوكيل هذا المقبض، وتنهي دورتها، وتظل متفرغة للمستخدم. وعندما يكتمل العمل، تضخ بيئة التشغيل (Runtime) حدث اكتمال في المحادثة، ليقرأه النموذج في دورته التالية كأي رسالة أخرى.
يكمن الفرق الأساسي في الطرف الذي يتحمل الانتظار: فمع run() ينتظر النموذج، بينما مع start() تتولى بيئة التشغيل مهمة الانتظار.
run() الحاجبة |
start() غير المتزامنة |
|
|---|---|---|
| المُخرَج | النتيجة النهائية | معرّف المهمة (Job id)، وحالة "بدأت" (Started) |
| حالة الوكيل أثناء العمل | مُجمّد تماماً | يتحاور، ويخطط، ويبدأ مهاماً أخرى |
| رسالة مستخدم جديدة | تنتظر في طابور حتى تنتهي الأداة | تُعالج في الحال |
| الإلغاء | إنهاء الدورة بالكامل قسراً | stop(job_id) |
تصف مقالة DeepSeek هذا النهج بأنه نمط معماري وليس مجرد ميزة برمجية جاهزة للتشغيل. وتوضح المقالة أن نمط الازدواج الكامل (Full-duplex) تم تطبيقه في DeepSeek Harness ولكنه غير مفعّل بشكل افتراضي، كما أن أجزاءً منه مدمجة في إضافة تجريبية تسمى dsh-superagent-prototype، والتي يؤكد المؤلفون أنها ليست منتجاً رسمياً من DeepSeek. لذا، ينبغي التعامل مع هذا الأسلوب كنمط تصميمي للاقتباس والتطبيق. للاطلاع على الخلفية التقنية، راجع مقال ما هو DeepSeek Harness؟ والمقال المصاحب له حول كيفية بناء وكيل فائق (Super Agent).
مشكلة الحظر: أداة بطيئة واحدة تجمد الوكيل بالكامل
تخيل وكيلاً برمجياً طُلب منه تشغيل حزمة الاختبارات (وهي عملية تستغرق 5 دقائق) والإجابة عن سؤال بخصوص خطأ في استيراد ملف (Import). مع الاستدعاءات الحاجبة، سيظل سؤالك عالقاً خلف الاختبارات على الرغم من أنه لا يعتمد عليها إطلاقاً. وهنا تحدث ثلاث مشكلات رئيسية:
- تراكم زمن الاستجابة (Latency Stacking): تضيف كل أداة بطيئة كامل مدتها الزمنية إلى كل ما يليها في قائمة الانتظار.
- فوات أوان التعليمات: إذا طلبت من الوكيل بعد دقيقتين "تجاوز اختبارات e2e في الواقع"، فلن يتمكن الوكيل من قراءة هذا الطلب حتى ينتهي التشغيل بالكامل.
- أخطاء انتهاء المهلة (Timeouts): تقوم البوابات (Gateways) بقطع الاتصالات المتروكة مفتوحة لعدة دقائق، مما قد يؤدي إلى فشل استدعاء
run()الطويل حتى لو تمت العملية بنجاح في الخلفية.
تطلق المقالة على السمة المطلوبة هنا وصف "الإنتاجية" (Productive): أي أن الوكيل يتلقى التعليمات التالية قبل أن ينتهي من تنفيذ سابقتها. وتعد الأدوات غير المتزامنة هي السبيل لتحقيق ذلك.

عقد الأدوات غير المتزامنة: start وstatus وstop
امنح النموذج ثلاث أدوات لكل وظيفة طويلة المدى بدلاً من أداة واحدة، مع الحفاظ على مخرجات مقتضبة للغاية.
{
"name": "start_test_run",
"description": "Start the test suite. Returns immediately with a job_id. The result arrives later as a job_completed event; do not wait for it.",
"input_schema": {
"type": "object",
"properties": {
"suite": { "type": "string", "enum": ["unit", "e2e", "all"] },
"idempotency_key": { "type": "string" }
},
"required": ["suite", "idempotency_key"]
}
}
تعيد start_test_run النتيجة {"job_id": "job_8f2", "status": "queued"} فقط دون أي شيء آخر. بينما تعيد get_job_status(job_id) إحدى الحالات التالية: queued أو running أو done أو failed، بالإضافة إلى ملاحظة موجزة حول التقدم. أما stop_job(job_id) فتطلب الإلغاء وتعيد الحالة التي استقرت عندها المهمة.
هناك قاعدتان تضمنان التزام النموذج بالسلوك المطلوب: ضع عبارة "do not wait" (لا تنتظر) بوضوح في الوصف، وإلا فسيقوم بعمل استقصاء دوري لأداة get_job_status في حلقة تكرارية مستمرة؛ واحرص تماماً على ألا تُرجع دالة start أي بيانات فعلية، وإلا سيضطر النموذج إلى تخمين بنية المخرجات التي تلقاها.
كيف تعود النتائج إلى حلقة الوكيل؟
يعد الاستقصاء الدوري من قِبل النموذج حلاً بديلاً للطوارئ، وليس الخطة المثالية. التصميم الأفضل يعتمد على مراقبة بيئة التشغيل للمهام وضخ حدث الاكتمال مباشرة في سياق المحادثة كرسالة جديدة:
[event] job_completed job_id=job_8f2 tool=start_test_run status=done
summary: 412 passed, 3 failed (auth/session.test.ts)
details: get_job_result(job_8f2)
احرص على أن يكون نص الحدث مختصراً، وضع البيانات الكاملة خلف أداة جلب مخصصة، حتى لا ينتهي الأمر بسجل يحتوي على 40,000 سطر داخل سياق المحادثة (Context).
ثم تأتي مرحلة المعالجة على دفعات (Batching). تصف مقالة DeepSeek موجهاً (Router) يجمع المحفزات التي تصل أثناء الدورة ويدمجها في استدعاء النموذج التالي، مدخلاً طويلاً ومخرجاً قصيراً، مدمجين في مرحلة المعالجة المسبقة (Prefill). وفي تجربتهم، ارتفعت المحفزات من 1 إلى 40 في الدقيقة، وزاد متوسط حجم الدفعة من 1.0 إلى 2.8، وانخفض الوقت المستغرق لكل قرار من 6.4 ثانية إلى 1.6 ثانية. هذه الأرقام مستمدة من تجارب المقالة الخاصة وليست معياراً عاماً، لكن استدعاءً واحداً يقرأ ثلاثة أحداث يتفوق بلا شك على ثلاثة استدعاءات يقرأ كل منها حدثاً واحداً. وهناك قاعدة بسيطة هنا: أثناء قيام النموذج بالتوليد، قم بوضع الأحداث ورسائل المستخدم في قائمة الانتظار؛ وعندما تنتهي الدورة، أرسلها معاً حسب ترتيب وصولها.

الترتيب والإلغاء ومفاتيح منع التكرار (Idempotency Keys)
الترتيب: تنتهي المهام بأي ترتيب عشوائي. فإذا كانت المهمة B تعتمد على المهمة A، فيجب توضيح هذا الاعتماد صراحةً: إما ببدء B فقط بعد وصول حدث اكتمال A، أو بجعل واجهة البرمجة تقبل معامل depends_on.
الإلغاء: يعد استدعاء stop مجرد طلب وليس ضماناً حتمياً. أرجع الحالة النهائية الحقيقية للمهمة (مثل cancelled، أو done إذا اكتملت قبل وصول الإلغاء) ودع النموذج يبلغ عنها بمصداقية. ويتبنى ملحق Tasks في بروتوكول MCP الموقف نفسه: حيث تشير وثائقه إلى أن الإلغاء يتم بطريقة تعاونية (Cooperative)، وبالتالي قد تصل المهمة إلى حالة نهائية أخرى غير cancelled.
مفاتيح منع التكرار (Idempotency Keys): تقوم النماذج بإعادة المحاولة تلقائياً، لذا قد يتسبب انقطاع الاستجابة في إطلاق استدعاء start ثانٍ. اطلب وجود idempotency_key مع كل استدعاء start، واجعل واجهة البرمجة تعيد المهمة الحالية عند استقبال نفس المفتاح مجدداً. وبدون ذلك، قد يتكرر تنفيذ أمر مثل "تنفيذ ترحيل البيانات" (Run migration) مرتين.
النتائج الجزئية: أتح الوصول إلى المخرجات الأولية المبكرة (مثل أول 50 صفحة تم الزحف إليها) عبر get_job_result(job_id, cursor) بدلاً من الانتظار حتى اكتمال المهمة كلياً.
تضخم السياق: قم بتلخيص المهام المكتملة في سطر واحد بمجرد أن يتعامل النموذج معها وينتهي منها.
انتهاء المهلة (Timeouts): حدد موعداً نهائياً لكل مهمة، وأطلق الحدث job_timed_out عند تجاوزه.
الشكل المطلوب لواجهة البرمجة (API) الأساسية
تعتمد كفاءة الأداة غير المتزامنة على جودة واجهة مهام الـ API التي تستند إليها: قبول العمل، وإرجاع معرّف، والسماح للمستدعي بالتحقق من الحالة. يعرّف معيار RFC 9110 رمز الحالة 202 Accepted بأنه "تم قبول الطلب للمعالجة، ولكن المعالجة لم تكتمل بعد"، ويوضح مرجع 202 على MDN تفاصيل الاستخدام العملي.
واجهة برمجة مهام مبسطة لأداة تعتمد على start():
| الاستدعاء | الاستجابة | تستخدمه |
|---|---|---|
POST /jobs (مع ترويسة Idempotency-Key) |
202 مع {"job_id", "status":"queued"} وترويسة Location: /jobs/{id} |
start |
GET /jobs/{id} |
200 مع إحدى الحالات: queued، أو running، أو done، أو failed |
status، ومراقب المهام |
GET /jobs/{id}/result |
200 مع المخرجات، أو 409 إذا لم تكتمل بعد |
أداة الجلب (Fetch tool) |
DELETE /jobs/{id} |
202 لطلب الإلغاء، ثم 404 أو 409 في الاستدعاءات اللاحقة |
stop |
| رابط رد نداء اختياري (Callback URL) | POST إلى المستدعي عند الاكتمال |
مراقب المهام |
يتيح رد النداء (Callback) لبيئة التشغيل دفع الأحداث فوراً بدلاً من الاستقصاء الدوري المستمر. وإذا كنت بحاجة للمساعدة في الاختيار بينهما، يمكنك مراجعة الفروق والمفاضلات في مقال خطافات الويب مقابل الاستقصاء الدوري (Webhooks vs Polling)، كما تم تناول جانب واجهات البرمجة الموجهة بالأحداث في مقال ما هو AsyncAPI؟.
وإذا كانت أدواتك تدعم بروتوكول MCP، فقد أصبح هناك معيار قياسي لهذا الغرض. تسرد مواصفات MCP (إصدار 2026-07-28) ملحق Tasks كملحق اختياري: حيث يمكن للخادم الرد على tools/call بنتيجة CreateTaskResult تتضمن taskId، ويقوم العميل باستقصاء tasks/get (أو الاستماع إلى إشعارات notifications/tasks)، بينما يطلب استدعاء tasks/cancel إيقاف المهمة. وتتضمن الحالات المتاحة: working، وinput_required، وcompleted، وfailed، وcancelled. يلزم تفعيل هذا الخيار من قِبل كل من العميل والخادم، كما يتفاوت مستوى دعم المضيفين، لذا تحقق من دعم العميل لديك قبل الاعتماد عليه.
اجعل وكيل البرمجة يبني واجهة المهام ويختبرها باستخدام Apidog CLI
واجهة برمجة المهام هي الجزء الذي يمكنك التحقق من صحته قبل أن يتعامل معه أي وكيل. سلم التصميم والاختبارات إلى وكيلك البرمجي واجعله يدير أداة Apidog CLI. تكتب أنت مطالبة واحدة (Prompt)، بينما يتولى الوكيل كتابة المواصفات وحالات الاختبار وتشغيلها.
يتطلب الإعداد ثلاثة أوامر فقط:
npm install -g apidog-cli@latest
apidog auth login --with-token <YOUR_TOKEN>
npx -y skills add https://apidog.com # pick your agent when prompted
بعد ذلك، وجه لوكيلك مطالبة واحدة كالتالي:
In Apidog project <projectId>, design the job API a start() tool needs:
POST /jobs returns 202 with job_id, GET /jobs/{id} returns queued|running|done|failed,
DELETE /jobs/{id} cancels. Create the endpoints, then create test cases that assert:
202 and a job_id on create, a status that reaches done within 60 seconds,
the same job_id when the Idempotency-Key repeats, and 409 or 404 on GET result after cancel.
Run them against the staging environment <envId> and summarize failures.
يقرأ الوكيل هيكل البيانات لكل حمولة من واجهة السطر الأوامري (CLI)، ويتحقق من صحتها، ثم ينشئها ويشغلها:
apidog cli-schema get endpoint-create
apidog cli-schema validate endpoint-create --file post-jobs.json
apidog endpoint create --project <projectId> --file post-jobs.json
apidog cli-schema get test-case-create
apidog cli-schema validate test-case-create --file case-create-202.json
apidog test-case create --project <projectId> --file case-create-202.json
apidog run --test-case <caseId> --project <projectId> -e <envId> -r cli,json
يعرض تقرير cli حالات النجاح والفشل لكل تأكيد اختبار في الطرفية (Terminal)، بينما يوفر تقرير json للوكيل مخرجات منظمة يمكنه قراءتها وتفسيرها. يمكنك تشغيل نفس الأمر داخل بيئة التكامل المستمر (CI) باستخدام -r junit بمجرد اجتياز الاختبارات؛ ويشرح دليل سير عمل الوكيل عبر Apidog CLI هذه الحلقة بالتفصيل، بينما يغطي مقال استخدام Apidog CLI في Claude Code خطوات إعداد الوكيل. وتتيح خطة Apidog المجانية الاستخدام لما يصل إلى 4 مستخدمين.
متى يجدر بك استخدام الأدوات غير المتزامنة ومتى تتجنبها؟
استخدم النمط غير المتزامن عندما:
- تستغرق الأداة بانتظام أكثر من بضع ثوانٍ (عمليات البناء والتجميع، وحزم الاختبارات، والزحف، والرندرة، ونشر التعليمات البرمجية).
- يتفاعل المستخدمون بالحديث مع الوكيل أثناء تشغيل المهام.
التزم بالنمط الحاظر (Blocking) عندما:
- تعود الاستجابة في غضون ثانية أو ثانيتين.
- تكون الخطوة التالية غير قادرة على البدء بدون الحصول على النتيجة على أي حال.
- لا يتوفر لديك دعم حقيقي لواجهة مهام API في الخلفية؛ فالأداة غير المتزامنة الزائفة التي تعتمد على الحظر داخلياً تزيد من التعقيد دون أي فائدة حقيقية.
قاعدة تقسيم عملية: اجعل عمليات القراءة حاجبة، واجعل عمليات الكتابة والحسابات الطويلة غير متزامنة.
الأسئلة الشائعة
ما الفرق بين استدعاءات الأدوات غير المتزامنة (Async) والمتوازية (Parallel)؟ تقوم استدعاءات الأدوات المتوازية بتشغيل عدة أدوات في وقت واحد داخل دورة واحدة، ويظل النموذج ينتظر اكتمالها جميعاً. أما استدعاءات الأدوات غير المتزامنة فترجع استجابتها فوراً وتكتمل عبر دورات لاحقة، مما يسمح للنموذج بمواصلة المحادثة أثناء تنفيذها.
هل أحتاج إلى بروتوكول MCP لبناء أدوات غير متزامنة؟ لا. فأي أداة تعيد معرّف مهمة (job id) وتعمل ضمن بيئة تشغيل تضخ أحداث الاكتمال ستؤدي الغرض بنجاح. يوفر ملحق Tasks الاختياري في MCP فقط طريقة قياسية موحدة لإدارة المقابض، والاستقصاء، وأوامر الإلغاء إذا كان العميل والخادم يدعمانه معاً.
كيف يعرف الوكيل متى تنتهي المهمة؟ تتولى بيئة التشغيل إخباره بذلك؛ حيث يقوم مراقب المهام باستقصاء واجهة المهام أو استقبال استدعاء رد نداء (Callback)، ثم يضيف حدث اكتمال موجز إلى المحادثة، ليقرأه النموذج في دورته التالية.
ماذا يحدث إذا ألغى المستخدم المهمة في منتصفها؟ يستدعي الوكيل دالة stop(job_id)، وتسجل واجهة البرمجة الطلب، ثم تبلغ المهمة عن حالتها النهائية الحقيقية. وإذا كان العمل قد اكتمل بالفعل قبل وصول طلب الإلغاء، فيجب على الوكيل توضيح ذلك بمصداقية بدلاً من الادعاء بأنه تم إيقافها.
هل نمط الازدواج الكامل (full-duplex) من DeepSeek ميزة يمكنني تفعيلها مباشرة؟ ليس كميزة منتج جاهزة؛ فالورقة البحثية توضح أنه مطبق داخل DeepSeek Harness لكنه غير مفعل افتراضياً، والإضافة البرمجية المرتبطة به مجرد نموذج أولي يؤكد المؤلفون أنه ليس منتجاً رسمياً من DeepSeek. لذا، استخدمه كمرجع وتصميم إرشادي للبناء عليه.
