الانتقال إلى المحتوى الرئيسي

نظرة عامة

المهارات والوكلاء حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
يمكن للتطبيقات تعريف قدرات ذكاء اصطناعي تعمل داخل مساحة العمل — تعليمات مهارات قابلة لإعادة الاستخدام ووكلاء بموجهات نظام مخصّصة.
تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم defineSkill() لتعريف مهارات مع تحقّق مدمج:
src/skills/example-skill.ts
النقاط الرئيسية:
  • name هي سلسلة معرّف فريدة للمهارة (يُنصَح باستخدام kebab-case).
  • label هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم.
  • content يحتوي على تعليمات المهارة — وهو النص الذي يستخدمه وكيل الذكاء الاصطناعي.
  • icon (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم.
  • description (اختياري) يوفّر سياقًا إضافيًا حول غرض المهارة.
الوكلاء هم مساعدون ذكاء اصطناعي يعيشون داخل مساحة العمل لديك. استخدم defineAgent() لإنشاء وكلاء بموجه نظام مخصّص:
src/agents/example-agent.ts
النقاط الرئيسية:
  • name هي سلسلة معرّف فريدة للوكيل (يُنصح باستخدام kebab-case).
  • label هو اسم العرض الظاهر في واجهة المستخدم.
  • prompt هو موجه النظام الذي يحدّد سلوك الوكيل.
  • description (اختياري) يوفّر سياقًا حول ما يفعله الوكيل.
  • icon (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم.
  • modelId (اختياري) يتجاوز نموذج الذكاء الاصطناعي الافتراضي الذي يستخدمه الوكيل.
  • responseFormat (اختياري) يحدد شكل الإخراج الافتراضي للوكيل. القيمة الافتراضية هي { type: 'text' } للنص الحر. استخدم { type: 'json', schema } لفرض مخرجات JSON منظمة. يمكن لاستدعاء runAgent() أو لخطوة ذكاء اصطناعي في سير عمل طلب شكل مختلف، ويكون له الأسبقية.
  • يربط roleUniversalIdentifier (اختياري) الوكيل بدور يحدده تطبيقك. يجب ألا يمنح ذلك الدور صلاحيات أكثر من دور التطبيق، وإلا فسيفشل الإصدار؛ راجع أدوار الوكلاء.
  • يُفعّل modelConfiguration (اختياري) الأدوات الأصلية للنموذج، كما تفعل صفحة إعدادات الوكيل: { webSearch: { enabled: true }, twitterSearch: { enabled: true } }. لا تعمل كل أداة إلا مع النماذج التي تدعمها. اتركه خارجًا لإبقائها معطلة.
  • يُخفي isSystem (اختياري، وقيمته الافتراضية false) الوكيل من قوائم الوكلاء، مثل الوكلاء الذين ينشئهم Twenty لخطوات الذكاء الاصطناعي لسير العمل. إنه يغيّر ما يُعرض فقط: لا يمكن تغيير وكلاء التطبيق إلا من خلال التطبيق، بغض النظر عما تقوله هذه العلامة.
بشكل افتراضي، يعيد الوكيل نصًا حرًا. للحصول على مخرجات منظمة، عيّن responseFormat إلى { type: 'json' } ووفّر schema:
src/agents/structured-agent.ts
ملاحظات حول المخطط:
  • المخطط كائن مسطح: يجب أن يكون type لكل خاصية نوعًا بدائيًا (string أو number أو boolean). الكائنات المتداخلة والمصفوفات غير مدعومة.
  • description (اختياري) على كل خاصية يوجه النموذج لما يجب وضعه هناك.
  • required (اختياري) يسرد الخصائص التي يجب على النموذج إرجاعها دائمًا.
  • additionalProperties: false (اختياري) يمنع أي خاصية غير معرّفة في properties.
خوادم MCP في مرحلة بيتا وليست متاحة بعد: يمكنك تعريفها، لكن لا يمكن لمساحات العمل استخدام أدواتها حتى يتم إصدار الميزة.
استخدم defineMcpServer() لإصدار خادم MCP بعيد (HTTP قابل للبث) مع تطبيقك، بحيث يتمكن ذكاء اصطناعي مساحة العمل من استخدام أدواته دون مزامنة تلك البيانات مع Twenty:
src/mcp-servers/github.ts
النقاط الرئيسية:
  • name بصيغة snake_case (أحرف صغيرة وأرقام وشرطات سفلية، يبدأ بحرف، وبحد أقصى 32 حرفًا) وفريد في مساحة العمل. يُضاف كبادئة إلى أسماء أدوات الخادم.
  • يجب أن يكون url عنوان URL من نوع HTTP أو HTTPS.
  • يشير connectionProvider (اختياري) إلى موفر اتصال معلن بواسطة التطبيق نفسه. ترسل الاستدعاءات رمز الوصول الخاص بذلك الاتصال كرمز Bearer. احذفه لخادم عام.
  • لا يمكن أن يتغير عنوان URL في إصدار لاحق أثناء ربط موفر اتصال، لذلك لا تُرسل الرموز أبدًا إلى مضيف آخر.
  • يمكن لمساحات العمل إلغاء تنشيط الخادم؛ ويحافظ إصدار لاحق من التطبيق على هذا الخيار.
  • يتطلب "engines": { "twenty": ">=2.53.0" }.
تتيح runAgent() لدالة منطقية تشغيل أحد وكلاء تطبيقك (مع مهاراته وأدواته). عرِّف الوكيل باستخدام universalIdentifier الذي مررته إلى defineAgent()، ومرِّر ما تريد أن يعمل عليه باعتباره input:
src/logic-functions/run-enricher.ts
النقاط الرئيسية:
  • input عبارة عن سلسلة نصية، أو قائمة تضم من 1 إلى 100 رسالة ({ role: 'user' | 'assistant', content: string }). يمكن أن تتضمن رسالة user أيضًا مرفقات ملفات (انظر أدناه). لإجراء محادثة، مرّر thread بدلًا من إعادة تشغيل الأدوار السابقة بنفسك.
  • افتراضيا، ينتظر الاستدعاء الوكيل، الذي يمكنه قراءة/تحديث السجلات بنفسه عبر أدواته الخاصة — يُرجع runAgent() بمجرد اكتمال التشغيل. مرر waitForCompletion: false للإرجاع بمجرد بدء التشغيل: تكون status حينها RUNNING وresult: null، ويصل الرد إلى محادثة threadId. يُرجع التشغيل الذي يستمر لأكثر من 15 دقيقة أيضا بالحالة RUNNING.
  • يمكن للوكيل أيضًا التوقف مؤقتًا حتى يتغير سجل أو يمر بعض الوقت. تُرجع runAgent() بعد ذلك success: true وstatus: 'SUSPENDED' و result: null؛ ثم يواصل الوكيل العمل بنفسه لاحقًا، ويصل ردّه إلى محادثة threadId. يفشل استدعاء thread الذي لا يزال وكيله في حالة انتظار إلى أن ينتهي.
  • يُخزَّن كل استدعاء مع مدخلاته وحالته، بالإضافة إلى رده واستخدامه عند اكتماله، بحيث يظهر في عمليات تشغيل الوكيل. من دون thread، يحصل الاستدعاء على محادثة خاصة به: يسمّيها threadId، ولا يراها أي استدعاء لاحق مطلقًا.
  • لا يمكن للتطبيق تشغيل سوى وكلائه الخاصين.
  • يجب أن يمنح الدور الافتراضي للتطبيق علامة الإذن AI — أضِف SystemPermissionFlag.AI إلى permissionFlagUniversalIdentifiers الخاصة به (أو عيِّن canAccessAllTools: true). بدون ذلك، تفشل runAgent() بخطأ في الأذونات.
  • اضبط قيمة كبيرة لـ timeoutSeconds على الدالة المنطقية — قد يستغرق تشغيل الوكيل عدة ثوانٍ.
  • تكون status بالقيمة COMPLETED مع result غير فارغ عند اكتمال التشغيل دون انتظار؛ وبالقيمة SUSPENDED مع success: true وresult: null أثناء الانتظار؛ وبالقيمة RUNNING مع success: true وresult: null عندما لا ينتظر الاستدعاء التشغيل؛ وبالقيمة FAILED مع success: false واحتواء error على السبب (على سبيل المثال، عندما تنفد أرصدة الذكاء الاصطناعي لمساحة العمل أثناء التشغيل).
src/roles/default-role.ts
تجنب الحلقات: إذا استدعيت runAgent() من مشغل حدث قاعدة بيانات من نوع *.updated وقام الوكيل بتحديث نفس السجل، فحدد نطاق المشغل باستخدام updatedFields إلى حقل لا يكتبه الوكيل أبدًا (مثل عنوان URL المصدر)، أو تحقَّق مما إذا كان أي حقل مستهدف لا يزال فارغًا قبل استدعاء runAgent().

إرسال الملفات مع رسالة

يمكن أن تحمل رسالة user attachments، بحيث يرى الوكيل لقطة شاشة أو ملف PDF بدلاً من اسمه فقط. يشير كل إدخال إلى ملف سبق لتطبيقك تحميله إلى مجلد agent-chat:
src/logic-functions/describe-screenshot.ts
يتطلب التحميل علامة الإذن UPLOAD_FILE في الدور الافتراضي للتطبيق، إلى جانب AI. filename اختياري ويُستخدم فقط لتسمية الملف للنموذج.
  • يمكن لرسائل user فقط أن تحمل مرفقات. لا يمكن تمثيلها في دور assistant، لذا ترفضها runAgent() هناك بدلاً من إسقاطها.
  • بحد أقصى 10 مرفقات لكل رسالة.
  • يجب أن يشير fileId إلى ملف تم تحميله بالكامل في مساحة العمل نفسها، ضمن مجلد agent-chat. أي شيء آخر يؤدي إلى فشل الاستدعاء بدلاً من الإجابة بهدوء من دون الملف.

طلب نتيجة منظَّمة

مرّر responseFormat للحصول على شكل لهذه المكالمة فقط. له الأسبقية على responseFormat الخاص بالوكيل، لذا يمكن لوكيل واحد خدمة المستدعين الذين يحتاجون إلى مخرجات مختلفة. يستخدم المخطط نفسه مثل defineAgent():
src/logic-functions/score-lead.ts

متابعة محادثة

مرّر thread وسيتولى Twenty الاحتفاظ بالمحادثة لك: لا يرسل كل استدعاء سوى الإدخال الجديد، ويرى الوكيل الأدوار السابقة، بما في ذلك ما أعادته أدواته. استخدم أي معرّف ثابت من جانبك كمفتاح، مثل قناة Slack وطابع زمني لسلسلة المحادثة:
src/logic-functions/reply-in-thread.ts
  • تقتصر المفاتيح على تطبيقك ووكيلك، لذا لا يشترك تطبيقان مطلقًا في محادثة واحدة. لا يمكن تمرير thread إلا باستخدام رموز وصول التطبيق.
  • input لا يمكن أن يحتوي إلا على رسائل user: فالمحادثة تحتوي بالفعل على ردود الوكيل.
  • additionalInstructions تُعطى للنموذج لهذا الاستدعاء فقط ولا يتم تخزينها، وهي مناسبة للإرشادات الخاصة بكل طلب، مثل قواعد التنسيق أو موعد نهائي. وهي تعمل دون thread أيضًا.
  • يُخزَّن كل تشغيل كدور مع حالته، بما في ذلك عمليات التشغيل الفاشلة. يُخزَّن استخدامه عند عودة الوكيل، حتى إذا نفدت أرصدته. إن التشغيل الفاشل ليس جزءًا من المحادثة، لذا لا تراه المكالمة التالية له.
  • تعمل الاستدعاءات على سلسلة المحادثة نفسها واحدًا تلو الآخر: ينتظر الاستدعاء الثاني حتى ينتهي الأول، لذا يرى كلٌّ منها الأدوار التي سبقته.
  • عندما تُنفَّذ الأدوار بصفتها أعضاء مختلفين (انظر أدناه)، لا يرى كل منها سوى النص الخاص بأدوار الآخرين، ولا يرى مطلقًا نتائج أدواتهم، إذ قُرئت تلك النتائج باستخدام أذونات شخص آخر.

التشغيل نيابةً عن أحد أعضاء مساحة العمل

مرر runAsWorkspaceMemberId عندما يتم تشغيل التنفيذ بواسطة شخص — مثل روبوت دردشة يجيب على رسالة، مثلاً — لكي يتصرف الوكيل بصفة ذلك العضو بدلًا من التطبيق:
src/logic-functions/answer-question.ts
ثم يعمل التنفيذ باستخدام دور العضو نفسه: يمكنه القيام بكل ما يمكن لذلك العضو القيام به ولا أكثر، وتنسب إليه السجلات التي ينشئها، وتُطبَّق أذوناتُه على مستوى الصف. لا يشارك دور الوكيل — فهو الإعداد الافتراضي للتطبيق لعمليات التنفيذ التي لا يقف خلفها أي شخص. احذف الحقل في عمليات التنفيذ الذاتية (المهام المجدولة، مشغلات أحداث قاعدة البيانات): في هذه الحالة يحتفظ الوكيل بدوره الخاص. يبقى التطبيق الفاعل مرتبطًا بسياق التنفيذ لأغراض التتبّع وإثبات المصدر، دون تقييد أذونات العضو.تطبيقك مسؤول عن تعيين الشخص الذي شغّل التنفيذ إلى عضو في مساحة العمل. يتطلب تسمية عضو ما رمز وصول للتطبيق، وما يمكن للرمز تسميته يعتمد على ما إذا كان يتضمن مستخدمًا أم لا:
  • يمكن لرمز بدون مستخدم مرفق أن يسمّي أي عضو. تعمل الدالة المنطقية بدون مستخدم، وكذلك الرموز المُنشأة عبر client_credentials أو من مفتاح API.
  • لا يمكن لرمز مُصدَر نيابةً عن مستخدم، كما يستقبله مكوّن الواجهة الأمامية، أن يسمّي إلا العضو الخاص بذلك المستخدم فقط.
لا يمكن لأي شيء آخر — جلسة مستخدم عادية، أو مفتاح API بدون رمز تطبيق — أن يسمّي عضوًا على الإطلاق.
يقوم runAgent() بإطلاق استثناء عندما يتعذّر حل عضو مساحة العمل — مثل عضو غير معروف أو تمت إزالته، أو ليس لديه أي دور. لا يعود إطلاقًا إلى دور الوكيل نفسه، لأن ذلك سيمنح صلاحيات وصول أكثر مما طلبه المستدعي.

متابعة محادثة

يقرأ التطبيق المحادثات التي يجريها مع وكلائه عبر واجهة برمجة التطبيقات نفسها المستخدمة للدردشة: تُرجع listConversationMessages() سجلها، وتُنتج streamConversation() أحداثها المباشرة، رمزًا تلو الآخر. سمِّ المحادثة باستخدام الوكيل ومفتاح thread الذي تمرره إلى runAgent()؛ ويمكنك بدء متابعتها قبل أن يصل إليها التشغيل الأول.
src/logic-functions/follow-conversation.ts
  • الأحداث خاصة بالدردشة نفسها: يحمل stream-chunk كائن UIMessageChunk من AI SDK ومعرّف seq، وتوضح الإشعارات مثل message-persisted (تم حفظ رسالة: رسالة عضو أو الرد، الذي يحمل turnId الخاص بتشغيله)، وcredits-exhausted (توقف الرد عند نفاد الأرصدة من مساحة العمل)، وqueue-updated أو stream-error متى تقرأ السجل مرة أخرى. يصل keepalive عند الاتصال، ثم كل بضع ثوانٍ: اقرأ السجل عند أول واحد، لأن ما حدث قبله موجود فيه.
  • لتحويل المقاطع إلى رسائل كما تفعل الدردشة، مررها إلى readUIMessageStream() من الحزمة ai.
  • تُرجع listConversationMessages() القيمة { messages, streamCatchup }: الرسائل المحفوظة ككائنات UIMessage من AI SDK، كما تنشئها الدردشة، ومقاطع رد ما زال يُبث. اعرض تلك المقاطع، ثم تخطَّ المقاطع المباشرة التي يكون فيها seq أقل من أو مساويًا لـ streamCatchup.maxSeq، لأنها تكررها.
  • تُرجع runAgent() القيمة runId، أي الدور الذي تفتحه المكالمة: تحمل رسائل ذلك التشغيل هذه القيمة كـ metadata.turnId. يستمر التشغيل الذي ينتظر في أدوار لاحقة من المحادثة.
  • يبقى الدفق مفتوحًا حتى تُلغي signal الخاص به أو تستخدم break، ويُطلق خطأً إذا انقطع الاتصال: أعد إنشاء الدفق للاستئناف.
  • يقرأ التطبيق محادثاته الخاصة فقط. تُظهر الأدوار التي فتحها عضو في مكان آخر، مثل الدردشة، نصها فقط في السجل والبث المباشر وفي التشغيل التالي للوكيل: في البث المباشر، تصل مقاطعها الأخرى كمقاطع message-metadata فارغة، لذلك يبقى seq بلا فجوات. تبقى الأدوار التي شغّلها تطبيقك كعضو (انظر أعلاه) كاملة عندما يقرأها بنفسه.
  • يتطلب "engines": { "twenty": ">=2.56.0" }.
تستدعي ai.generate() نموذجًا من دالة منطقية. بخلاف runAgent()، لا تحتاج إلى defineAgent(): تحمل المكالمة تعليماتها الخاصة، ولا تفتح أي محادثة. استخدمها للمهام التي تُنفذ لمرة واحدة، مثل التصنيف أو التلخيص أو الاستخراج.
  • input عبارة عن سلسلة، أو قائمة برسائل { role: 'user' | 'assistant', content }.
  • يضيف instructions (اختياري) تعليمات النظام الخاصة بالمتصل.
  • يختار modelId (اختياري) النموذج. من دونه، تستخدم المكالمة النموذج الافتراضي لمساحة العمل.
  • يعمل responseFormat (اختياري) كما في runAgent(). مع { type: 'json', schema }، يُضاف المخطط إلى طلب النموذج نفسه، لذا يتطلب جواب JSON طلبًا واحدًا (بالإضافة إلى خطوات الأداة عندما يمنح scope الأدوات).
  • يتيح scope (اختياري) للمكالمة استخدام قاعدة البيانات، كما هو موضح أدناه.
يُحل إلى { success, text, object, error, runId, usage }:
  • text هو رد الاستجابة النصية، ويكون null لاستجابة JSON.
  • object هو نتيجة استجابة JSON، ويكون null بخلاف ذلك.
  • يشرح error التشغيل الفاشل عندما تكون success هي false. يُحل التشغيل الذي يفشل بهذه الطريقة بدلًا من إطلاق خطأ؛ أما المكالمة التي لا يمكنها بدء تشغيل، مثل مكالمة ذات إدخال غير صالح، فتُطلق خطأً.
  • يحدد runId التشغيل المسجل، مع نموذجه واستخدامه وأرصدته.
  • usage هو { inputTokens, outputTokens }: ما أنفقه التشغيل، بما في ذلك الخطوات التي انتهت قبل الإخفاق.
تتصرف المكالمة بصفتها الجهة التي أجرتها: العضو الذي ينتمي إليه رمز مميز بنطاق مستخدم، وإلا فتطبيقك، وإلا مفتاح API. من دالة منطقية، تتبع العملاء الافتراضيين: تتصرف بصفتها الشخص الذي شغّل التشغيل، أو بصفتها تطبيقك عندما لا يكون هناك أحد. لا تتجاوز مطلقًا دور تطبيقك، ولا يمكن لتطبيق من دون دور افتراضي استدعاؤها.من دون scope، لا تقرأ المكالمة أي سجلات ولا تكتبها. للسماح لها باستخدام قاعدة البيانات، مرر scope، مكتوبًا مثل أذونات الدور:
النطاق لا يؤدي إلا إلى التضييق: فهو لا يمنح أي شيء لا يملكه المتصل بالفعل.لعرض الرد أثناء كتابته، مرر onChunk. تُحل المكالمة مع ذلك إلى النتيجة نفسها، بمجرد تمرير المقطع الأخير إلى onChunk. إذا لم تصل نهاية الدفق مطلقًا، تتوقف عن الانتظار بمجرد وصول النتيجة ومرور 5 ثوانٍ من دون مقطع جديد، وتظل تمرر كل مقطع تلقته حتى ذلك الحين:
  • تتبع المكالمة التشغيل عبر الاشتراك نفسه الذي تستخدمه streamConversation(): تشترك أولًا، وتنتظر حتى يصبح الاشتراك مباشرًا، ثم تبدأ التشغيل، لذلك لا يفوتها أي مقطع.
  • المقاطع خاصة بالدردشة نفسها: مررها إلى readUIMessageStream() من حزمة ai لإنشاء الرسالة.
  • يلغي signal المكالمة: يُرفض بخطأ الإلغاء ويغلق الدفق. يستمر التشغيل نفسه، ويُسجل كالمعتاد.
  • بمجرد أن يصبح الدفق مباشرًا، لا يؤدي فشله إلا إلى إيقاف المقاطع: تظل المكالمة تُحل بنتيجة التشغيل، التي تُفوتر في كلتا الحالتين. تُرفض المكالمة عندما يفشل الاشتراك قبل أن يصبح مباشرًا، لذلك لا يبدأ التشغيل مطلقًا، أو عندما يُطلق onChunk خطأً.
  • يتابع التطبيق عمليات التشغيل الخاصة به فقط، لذا يحتاج onChunk إلى رمز مميز للتطبيق.
  • يتطلب "engines": { "twenty": ">=2.56.0" }.
تتطلب ai.generate() علامة إذن الذكاء الاصطناعي في الدور الافتراضي للتطبيق، مثل runAgent().
ينشر sendInboxMessage() رسالة من تطبيقك في دردشات عضو واحد أو أكثر من أعضاء مساحة العمل. يمكن أن ينتهي باستدعاء أداة يجيب عنه العضو من الدردشة، وبعد ذلك تستمر المحادثة مع مساعد Twenty. اطّلع على البريد الوارد لمعرفة خياراته ومساعدات الإشارة وكيفية النشر من مكوّن أمامي.