Merge branch 'main' into docs/checkpointing-restructure
104
docs/ar/enterprise/features/agent-control-plane/monitoring.mdx
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: "راقب أتمتاتك"
|
||||
description: "راقب صحة الأسطول واستهلاك LLM وسلوك كل أتمتة من تبويب Automations."
|
||||
sidebarTitle: "المراقبة"
|
||||
icon: "gauge"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
## نظرة عامة
|
||||
|
||||
تبويب **Automations** هو عرض العمليات للقراءة فقط في [Agent Control Plane](/ar/enterprise/features/agent-control-plane/overview). يجمع بين بطاقتَي مقاييس و sankey تفاعلي وجدولين فرعيين — **Automations** و **Consumption** — يمكنك البحث والتصفية والفرز فيهما.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
تحترم جميع المخططات والجداول مُحدّد **آخر 24 ساعة / الأسبوع الماضي / آخر 30 يوماً** في أعلى اليمين. تقارن قيم الفرق النافذة المختارة بالنافذة السابقة بنفس الطول.
|
||||
|
||||
<Note>
|
||||
تعرض الصفوف بيانات فقط لعمليات النشر على **crewAI v1.13 أو أحدث** — تظهر عمليات النشر الأقدم في لافتة *"We've detected N other automations that we can't display"* أسفل sankey ولا تساهم بأي مقاييس حتى يتم تحديثها وإعادة نشرها. راجع [نظرة عامة — المتطلبات](/ar/enterprise/features/agent-control-plane/overview#المتطلبات).
|
||||
</Note>
|
||||
|
||||
## لوحة المعلومات
|
||||
|
||||
يحتوي رأس الصفحة على بطاقتَي مقاييس و sankey تفاعلي. النقر على أي من البطاقتين يبدّل sankey بين وضعَين:
|
||||
|
||||
- **وضع الصحة** — `إجمالي الأتمتات → حِزم الحالة (Critical / Warning / Healthy)`. انقر على حِزمة لتصفية جدول Automations إلى عمليات النشر تلك فقط.
|
||||
- **وضع الاستهلاك** — `مزودو النماذج → الأتمتات → التكلفة الإجمالية`. انقر على مزود لتصفية جدول Consumption إلى ذلك المزود.
|
||||
|
||||
| البطاقة | ما تعرضه |
|
||||
|------|---------------|
|
||||
| **Automations** | الأتمتات `active` (والعدد الإجمالي)، إجمالي `errors` في النافذة، `active executions` الحالية (والإجمالي في النافذة)، مع الفرق مقابل الفترة السابقة. |
|
||||
| **Consumption** | إجمالي `cost` و `tokens used`، مع فرق التكلفة مقابل الفترة السابقة. |
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
## جدول Automations
|
||||
|
||||
التبويب الفرعي **Automations** هو تفصيل صحة الأسطول لكل deployment. كل صف هو crew أو flow منشور.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
| العمود | ما يعرضه |
|
||||
|--------|---------------|
|
||||
| **Automation** | اسم الـ deployment وأي وسوم مُسنَدة إليه (مثل `production`، `financial`). |
|
||||
| **Last execution** | الوقت المنقضي منذ آخر تنفيذ. |
|
||||
| **Health Status Breakdown** | شريط مكدّس بنسب `Critical` / `Warning` / `Healthy` لعمليات التنفيذ في النافذة. |
|
||||
| **Executions with Errors** | إجمالي عمليات التنفيذ الفاشلة في النافذة. |
|
||||
| **PII detection applied** | `Yes` إذا كان هناك تكوين PII لكل deployment أو [قاعدة PII](/ar/enterprise/features/agent-control-plane/rules) مطابِقة نشطة. |
|
||||
| **Executions** | إجمالي عمليات التنفيذ في النافذة. |
|
||||
| **Last updated** | متى أُعيد نشر الـ deployment آخر مرة. |
|
||||
| **Crew Version** | إصدار `crewai` الذي يُبلِّغ عنه الـ deployment. يشير أيقونة المعلومات بجانب الإصدارات الأقل من `1.13` إلى صفوف لا يمكنها المساهمة بالمقاييس. |
|
||||
|
||||
ابحث بالاسم، صفِّ حسب `Status` (`Healthy` / `Warning` / `Critical`)، وافرز بأي رأس عمود. انقر على اسم الـ deployment لفتح **لوحة الأتمتة**.
|
||||
|
||||
## جدول Consumption
|
||||
|
||||
التبويب الفرعي **Consumption** هو تفصيل إنفاق LLM واستخدام الرموز لكل deployment.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
| العمود | ما يعرضه |
|
||||
|--------|---------------|
|
||||
| **Automation** | اسم الـ deployment. |
|
||||
| **Last execution** | الوقت المنقضي منذ آخر تنفيذ. |
|
||||
| **Tokens used** | صف واحد لكل مزود LLM تستخدمه هذه الأتمتة، مع الفرق مقابل الفترة السابقة. |
|
||||
| **Cost** | التكلفة لكل مزود LLM، مع الفرق مقابل الفترة السابقة. |
|
||||
| **Total cost** | المجموع عبر جميع المزودين، مع الفرق. |
|
||||
| **Executions** | إجمالي عمليات التنفيذ في النافذة. |
|
||||
| **Last updated** | متى أُعيد نشر الـ deployment آخر مرة. |
|
||||
| **Crew Version** | إصدار `crewai` الذي يُبلِّغ عنه الـ deployment. |
|
||||
|
||||
صفِّ حسب **LLM provider** وافرز حسب `Cost` أو `Executions` أو `Last run`.
|
||||
|
||||
<Info>
|
||||
**عادة ما تعني الخلايا الفارغة (`—` أو `$0.00`) أن الـ deployment أدنى من crewAI v1.13.** في اللقطة أعلاه، تظهر *Automation F* (`1.7.0`) و *Automation I* (`1.12.2`) فارغة في الرموز والتكلفة — لا تزال عمليات التنفيذ تعمل، لكنها لا تُصدِر التليمتري على مستوى المزود الذي يُغذِّي هذا الجدول. حدّث هذه الـ crews وأعد نشرها لبدء جمع بيانات الاستهلاك.
|
||||
</Info>
|
||||
|
||||
## ذو صلة
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Agent Control Plane — نظرة عامة" icon="book-open" href="/ar/enterprise/features/agent-control-plane/overview">
|
||||
ما هو ACP، المتطلبات، مستويات الخطط، و RBAC.
|
||||
</Card>
|
||||
<Card title="Agent Control Plane — القواعد" icon="shield-check" href="/ar/enterprise/features/agent-control-plane/rules">
|
||||
طبّق قواعد PII Redaction على مستوى المؤسسة عبر العديد من الأتمتات.
|
||||
</Card>
|
||||
<Card title="Traces" icon="timeline" href="/ar/enterprise/features/traces">
|
||||
تعمّق في تنفيذ واحد لرؤية تفكير الوكيل واستدعاءات الأدوات واستخدام الرموز.
|
||||
</Card>
|
||||
<Card title="النشر إلى AMP" icon="rocket" href="/ar/enterprise/guides/deploy-to-amp">
|
||||
انشر crew على إصدار crewAI يدعم Agent Control Plane.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="تحتاج مساعدة؟" icon="headset" href="mailto:support@crewai.com">
|
||||
تواصل مع فريق الدعم للمساعدة في تفسير المقاييس داخل Agent Control Plane.
|
||||
</Card>
|
||||
74
docs/ar/enterprise/features/agent-control-plane/overview.mdx
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: نظرة عامة على Agent Control Plane
|
||||
description: "مركز عمليات موحّد للأتمتات الجارية — صحة الأسطول واستهلاك LLM والسياسات على مستوى المؤسسة في مكان واحد."
|
||||
sidebarTitle: نظرة عامة
|
||||
icon: "book-open"
|
||||
---
|
||||
|
||||
## نظرة عامة
|
||||
|
||||
**Agent Control Plane** (ACP) هو مركز العمليات لكل ما يعمل لديك على CrewAI AMP. إنها شاشة واحدة — مقسّمة إلى تبويبَي **Automations** و **Rules** — تمنح فريقك القدرة على:
|
||||
|
||||
- مراقبة **حالة (الصحة)** كل أتمتة حيّة (crew أو flow) بتفصيل `Critical` / `Warning` / `Healthy` وعدد عمليات التنفيذ.
|
||||
- تتبع **استهلاك LLM** — الرموز (tokens) والتكلفة — لكل أتمتة ولكل مزود ولكل نموذج، مع الفرق مقابل الفترة السابقة.
|
||||
- التعمّق في أي أتمتة منفردة أو مزود نماذج لرؤية المخططات الزمنية وتفصيل البيانات لكل مزود.
|
||||
- تطبيق **قواعد (Rules)** على مستوى المؤسسة (اليوم: PII Redaction) عبر العديد من الأتمتات دفعة واحدة بدلاً من تعديل كل deployment على حدة.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
Agent Control Plane مُوسوم حالياً بـ **Beta** في CrewAI Platform.
|
||||
</Note>
|
||||
|
||||
يجيب التبويبان عن سؤالَين مختلفَين:
|
||||
|
||||
- **Automations** — *"كيف يتصرف أسطولي الآن، وكم يكلّفني؟"* راجع [المراقبة](/ar/enterprise/features/agent-control-plane/monitoring).
|
||||
- **Rules** — *"كيف أفرض سياسة (مثل PII redaction) عبر العديد من عمليات النشر دون إعادة نشر كل واحدة؟"* راجع [القواعد](/ar/enterprise/features/agent-control-plane/rules).
|
||||
|
||||
## المتطلبات
|
||||
|
||||
<Warning>
|
||||
يُشترط **crewAI v1.13 أو أحدث** ليتمكن أي أتمتة من تعبئة أي بيانات على هذه الصفحة — تمر بيانات الصحة وعمليات التنفيذ والأخطاء والرموز والتكلفة عبر التليمتري الذي تم تفعيله في `crewai==1.13`. تظهر عمليات النشر الأقدم في لافتة *"We've detected N other automations that we can't display"* ولا تساهم بأي صفوف حتى يتم تحديثها وإعادة نشرها.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
يُشترط **خطة Enterprise أو Ultra** لإنشاء أو تعديل [القواعد](/ar/enterprise/features/agent-control-plane/rules). يمكن للمؤسسات على الخطط الأدنى فتح تبويب Rules وعرض القواعد الموجودة، ولكن يُعرض المحرر للقراءة فقط مع شارة قفل "Enterprise" والتنبيه *"PII Redaction rules require an Enterprise plan."*. المراقبة (تبويب Automations) متاحة في جميع الخطط حيث يكون هذا الميزة مفعّلة.
|
||||
</Warning>
|
||||
|
||||
- يجب أن تكون ميزة **Agent Control Plane** مفعّلة لمؤسستك. إن لم ترها في الشريط الجانبي، اطلب من مالك الحساب تفعيلها.
|
||||
- داخل ACP، يحكم [RBAC](/ar/enterprise/features/rbac) الوصول: `read` للعرض في لوحة المعلومات والقواعد، و`manage` لإنشاء وتعديل وتشغيل/إيقاف وحذف القواعد.
|
||||
- يمكن ضبط نطاق جميع المخططات والجداول إلى **آخر 24 ساعة** أو **الأسبوع الماضي** أو **آخر 30 يوماً** عبر مُحدّد الوقت في أعلى اليمين. تقارن قيم الفرق (`↑ 8 vs yesterday`, `↓ $20.57 vs yesterday` وغيرها) النافذة المختارة بالنافذة السابقة بنفس الطول.
|
||||
|
||||
## ما يمكنك فعله هنا
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="المراقبة" icon="gauge" href="/ar/enterprise/features/agent-control-plane/monitoring">
|
||||
راقب صحة الأسطول وإنفاق LLM عبر بطاقات المقاييس و sankey التفاعلي وجداول لكل أتمتة ولوحات جانبية للتعمق في أي أتمتة أو مزود.
|
||||
</Card>
|
||||
<Card title="القواعد" icon="shield-check" href="/ar/enterprise/features/agent-control-plane/rules">
|
||||
طبّق سياسات PII Redaction على مستوى المؤسسة بنطاق محدد بالأدوات والوسوم. تسري التغييرات في التنفيذ التالي — دون الحاجة لإعادة نشر.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## ذو صلة
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Traces" icon="timeline" href="/ar/enterprise/features/traces">
|
||||
تعمّق في تنفيذ واحد لرؤية تفكير الوكيل واستدعاءات الأدوات واستخدام الرموز.
|
||||
</Card>
|
||||
<Card title="RBAC" icon="users" href="/ar/enterprise/features/rbac">
|
||||
أدِر من يمكنه قراءة Agent Control Plane ومن يمكنه تعديل القواعد.
|
||||
</Card>
|
||||
<Card title="PII Redaction للـ Traces" icon="lock" href="/ar/enterprise/features/pii-trace-redactions">
|
||||
كتالوج الكيانات وضبط PII لكل deployment التي تستند إليها القواعد.
|
||||
</Card>
|
||||
<Card title="النشر إلى AMP" icon="rocket" href="/ar/enterprise/guides/deploy-to-amp">
|
||||
انشر crew على إصدار crewAI يدعم Agent Control Plane.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="تحتاج مساعدة؟" icon="headset" href="mailto:support@crewai.com">
|
||||
تواصل مع فريق الدعم للمساعدة في تفسير المقاييس أو تصميم القواعد.
|
||||
</Card>
|
||||
114
docs/ar/enterprise/features/agent-control-plane/rules.mdx
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: "إعداد القواعد"
|
||||
description: "طبّق سياسات على مستوى المؤسسة عبر العديد من الأتمتات من مكان واحد."
|
||||
sidebarTitle: "القواعد"
|
||||
icon: "shield-check"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
## نظرة عامة
|
||||
|
||||
تتيح لك القواعد تطبيق سياسات — اليوم: **PII Redaction** — عبر العديد من الأتمتات دفعة واحدة، بدلاً من ضبط كل deployment على حدة. افتح تبويب **Rules** في [Agent Control Plane](/ar/enterprise/features/agent-control-plane/overview) لإدارتها.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
تعرض كل بطاقة قاعدة الاسم والوصف و**النطاق (scope)** الذي تنطبق عليه القاعدة (الأدوات والوسوم المختارة) وعدد **الأتمتات المُفعَّلة** — عمليات النشر التي تطابق النطاق حالياً. يقوم المُفتاح على اليمين بتشغيل القاعدة أو إيقافها دون حذفها.
|
||||
|
||||
## المتطلبات
|
||||
|
||||
<Warning>
|
||||
يُشترط **خطة Enterprise أو Ultra** لإنشاء أو تعديل قواعد PII Redaction. يمكن للمؤسسات على الخطط الأدنى فتح تبويب Rules وعرض القواعد الموجودة، ولكن يُعرض المحرر للقراءة فقط مع شارة قفل "Enterprise" والتنبيه *"PII Redaction rules require an Enterprise plan."* — تواصل مع مالك حسابك أو المبيعات للترقية.
|
||||
</Warning>
|
||||
|
||||
- يجب أن تكون ميزة **Agent Control Plane** مفعّلة لمؤسستك. راجع [نظرة عامة — المتطلبات](/ar/enterprise/features/agent-control-plane/overview#المتطلبات).
|
||||
- تحتاج إلى صلاحية `manage` ضمن [RBAC](/ar/enterprise/features/rbac) على Agent Control Plane لإنشاء وتعديل وتشغيل/إيقاف وحذف القواعد. صلاحية `read` كافية لعرضها.
|
||||
- تُسجَّل جميع تغييرات القواعد بإصدارات للتدقيق.
|
||||
|
||||
## أنواع القواعد المتاحة
|
||||
|
||||
| النوع | ما تفعله |
|
||||
|------|---------------|
|
||||
| **PII Redaction** | تطبّق PII redaction على عمليات التنفيذ لكل أتمتة مطابِقة، باستخدام نفس كتالوج الكيانات و recognizers المخصصة الموثَّقة في [PII Redaction للـ Traces](/ar/enterprise/features/pii-trace-redactions). |
|
||||
|
||||
سيتم إضافة أنواع قواعد أخرى مع الوقت.
|
||||
|
||||
## إنشاء قاعدة
|
||||
|
||||
<Frame>
|
||||
<img src="/images/enterprise/acp-rules-edit-side-panel.png" alt="لوحة تعديل قاعدة جانبية بالشروط ونوع قناع PII" width="450" />
|
||||
</Frame>
|
||||
|
||||
<Steps>
|
||||
<Step title="افتح المحرر">
|
||||
انقر على **+ Create new** في أعلى يمين تبويب Rules، أو على **View Details** في بطاقة قاعدة موجودة.
|
||||
</Step>
|
||||
|
||||
<Step title="سَمِّ القاعدة وصِفها">
|
||||
أعطِ القاعدة اسماً واضحاً (مثل *Mask PII (CC)*) ووصفاً يشرح متى تنطبق. يظهر كلاهما على بطاقة القاعدة وفي مودال Engaged Automations.
|
||||
</Step>
|
||||
|
||||
<Step title="اختر النوع">
|
||||
اليوم **PII Redaction** فقط متاحة.
|
||||
</Step>
|
||||
|
||||
<Step title="حدّد الشروط">
|
||||
تحدد الشروط الأتمتات التي تنخرط معها القاعدة. كلاهما اختياري ويستخدم دلالات **مساواة المجموعات (set-equality)**:
|
||||
|
||||
- **Tools** — تنخرط فقط الأتمتات التي تتطابق مجموعة أدواتها **تطابقاً تامّاً** مع الأدوات المختارة. اختر من تطبيقات Studio و MCPs والأدوات مفتوحة المصدر وأدوات سجل Tool Repository.
|
||||
- **Automations** — تنخرط فقط الأتمتات التي تتطابق مجموعة وسومها **تطابقاً تامّاً** مع الوسوم المختارة.
|
||||
|
||||
ترك مُحدِّد فارغ يعني "بدون تصفية على هذا البعد". ترك كليهما فارغَين يعني أن القاعدة تنطبق على **كل** أتمتة في المؤسسة.
|
||||
</Step>
|
||||
|
||||
<Step title="اضبط جدول PII Mask Type">
|
||||
حدّد كل نوع كيان تريد تغطيته واختر **Mask** (يستبدل بتسمية الكيان مثل `<CREDIT_CARD>`) أو **Redact** (يحذف النص المطابِق بالكامل). راجع [PII Redaction للـ Traces](/ar/enterprise/features/pii-trace-redactions) للاطلاع على كتالوج الكيانات الكامل وكيفية إضافة recognizers مخصصة على مستوى المؤسسة.
|
||||
</Step>
|
||||
|
||||
<Step title="احفظ">
|
||||
تنطبق القاعدة على عمليات التنفيذ **المستقبلية** لكل أتمتة مُفعَّلة بمجرد الحفظ. لا حاجة لإعادة النشر.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## الأتمتات المُفعَّلة
|
||||
|
||||
انقر على **Engaged N automations** في أي بطاقة قاعدة لرؤية أي عمليات النشر تطابقها القاعدة حالياً بالضبط، إلى جانب آخر تنفيذ لكل منها.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
هذه هي أسرع طريقة للتحقق من نطاق قاعدة قبل تمكينها — على سبيل المثال، للتأكد من أن قاعدة محدَّدة بنطاق وسم `production` لا تطابق عن طريق الخطأ deployment تجريبي.
|
||||
|
||||
## قواعد على مستوى المؤسسة مقابل إعدادات لكل deployment
|
||||
|
||||
يمكن ضبط PII Redaction في مكانين:
|
||||
|
||||
- **لكل deployment** — ضمن **Settings → PII Protection** على كل deployment على حدة ([الدليل](/ar/enterprise/features/pii-trace-redactions))
|
||||
- **على مستوى المؤسسة** — كقاعدة في هذه الصفحة
|
||||
|
||||
عندما يتطابق نطاق قاعدة مُفعَّلة على مستوى المؤسسة مع deployment، يُجاوز تكوين الكيانات الخاص بالقاعدة **إعدادات PII المملوكة من قبل الـ deployment** لعمليات تنفيذ ذلك الـ deployment — تصبح القاعدة المصدر الوحيد للحقيقة طالما هي مرتبطة. عطّل القاعدة أو فُكَّ ارتباطها (أو غيِّر نطاقها بحيث لا تتطابق بعد الآن) ويعود الـ deployment إلى إعدادات PII Protection الخاصة به.
|
||||
|
||||
فضّل القواعد على مستوى المؤسسة عندما تريد فرض سياسة متسقة عبر العديد من عمليات النشر؛ احتفظ بالضبط لكل deployment للاستثناءات الفردية.
|
||||
|
||||
## ذو صلة
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Agent Control Plane — نظرة عامة" icon="book-open" href="/ar/enterprise/features/agent-control-plane/overview">
|
||||
ما هو ACP، المتطلبات، مستويات الخطط، و RBAC.
|
||||
</Card>
|
||||
<Card title="Agent Control Plane — المراقبة" icon="gauge" href="/ar/enterprise/features/agent-control-plane/monitoring">
|
||||
راقب الأتمتات واستهلاك LLM عبر أسطولك.
|
||||
</Card>
|
||||
<Card title="PII Redaction للـ Traces" icon="lock" href="/ar/enterprise/features/pii-trace-redactions">
|
||||
كتالوج الكيانات، recognizers المخصصة، والضبط لكل deployment.
|
||||
</Card>
|
||||
<Card title="RBAC" icon="users" href="/ar/enterprise/features/rbac">
|
||||
أدِر من يمكنه إنشاء أو تعديل القواعد.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="تحتاج مساعدة؟" icon="headset" href="mailto:support@crewai.com">
|
||||
تواصل مع فريق الدعم للمساعدة في تصميم قواعد لمؤسستك.
|
||||
</Card>
|
||||
@@ -17,15 +17,62 @@ mode: "wide"
|
||||
- حساب Salesforce بالصلاحيات المناسبة
|
||||
- ربط حساب Salesforce الخاص بك عبر [صفحة التكاملات](https://app.crewai.com/integrations)
|
||||
|
||||
<Note>
|
||||
يتطلب Salesforce **تثبيتًا واحدًا يقوم به مسؤول النظام (admin)** لحزمة
|
||||
CrewAI في مؤسستك قبل أن يتمكن أي مستخدم من الاتصال. هذا متطلب من منصة
|
||||
Salesforce لجميع التكاملات المعتمدة على ExternalClientApp اعتبارًا من
|
||||
إصدار Spring '26 — وليس خطوة خاصة بـ CrewAI. تدليلك خطوة Connect
|
||||
Salesforce في CrewAI AMP خلال هذه العملية عند المحاولة الأولى.
|
||||
</Note>
|
||||
|
||||
## إعداد تكامل Salesforce
|
||||
|
||||
### 1. ربط حساب Salesforce الخاص بك
|
||||
|
||||
1. انتقل إلى [تكاملات CrewAI AMP](https://app.crewai.com/crewai_plus/connectors)
|
||||
2. ابحث عن **Salesforce** في قسم تكاملات المصادقة
|
||||
3. انقر على **Connect** وأكمل عملية OAuth
|
||||
4. امنح الصلاحيات اللازمة لإدارة CRM والمبيعات
|
||||
5. انسخ رمز المؤسسة من [إعدادات التكامل](https://app.crewai.com/crewai_plus/settings/integrations)
|
||||
1. انتقل إلى [تكاملات CrewAI AMP](https://app.crewai.com/crewai_plus/unified_tools).
|
||||
2. ابحث عن **Salesforce** في قسم تكاملات المصادقة.
|
||||
3. انقر على **Connect**.
|
||||
|
||||
ما يحدث بعد ذلك يعتمد على ما إذا كان مسؤول Salesforce في مؤسستك قد ثبّت
|
||||
حزمة CrewAI بالفعل:
|
||||
|
||||
- **الحزمة مثبتة بالفعل:** سيتم نقلك مباشرة إلى شاشة موافقة OAuth في
|
||||
Salesforce — اعتمدها وسيكتمل الاتصال.
|
||||
- **الحزمة غير مثبتة بعد:** سترى صفحة **Install CrewAI in Salesforce**.
|
||||
اتبع خطوات التثبيت لمرة واحدة أدناه، ثم عُد إلى CrewAI AMP وانقر على
|
||||
**Connect** مرة أخرى.
|
||||
|
||||
4. امنح الصلاحيات اللازمة لإدارة CRM والمبيعات.
|
||||
5. انسخ رمز المؤسسة من [إعدادات التكامل](https://app.crewai.com/crewai_plus/settings/integrations).
|
||||
|
||||
#### تثبيت لمرة واحدة بواسطة المسؤول (مسؤول Salesforce فقط)
|
||||
|
||||
عند أول نقرة على **Connect Salesforce** من أي مستخدم في مؤسستك، تقوم CrewAI
|
||||
بإعادة توجيهك إلى صفحة تثبيت تُشير إلى حزمة CrewAI المُدارة. يحتاج مسؤول
|
||||
Salesforce إلى تثبيتها مرة واحدة فقط لكامل المؤسسة.
|
||||
|
||||
1. في صفحة التثبيت داخل CrewAI، انقر على **Install in Salesforce**. (يمكنك
|
||||
أيضًا مشاركة عنوان URL لتلك الصفحة مع المسؤول — رابط التثبيت يعمل لأي
|
||||
شخص يفتحه.)
|
||||
2. سجّل الدخول إلى Salesforce بصلاحيات مسؤول. لبيئات Sandbox، استبدل
|
||||
`login.salesforce.com` بـ `test.salesforce.com` في الرابط قبل فتحه.
|
||||
3. اختر **Install for All Users**، ووافق على إشعار تطبيقات الجهات
|
||||
الخارجية، ثم انقر **Install**.
|
||||
4. من Setup في Salesforce، ابحث عن **External Client App Manager** ←
|
||||
**CrewAI App** ← افتح علامة التبويب **Policies** ← **Edit**، واضبط
|
||||
القيم التالية:
|
||||
- **Permitted Users:** All users may self-authorize
|
||||
- **IP Relaxation:** Relax IP restrictions
|
||||
- **Refresh Token Policy:** Refresh token is valid until revoked
|
||||
5. احفظ التغييرات.
|
||||
6. عُد إلى CrewAI AMP وانقر على **Connect Salesforce** مرة أخرى. سيكتمل
|
||||
OAuth هذه المرة.
|
||||
|
||||
<Note>
|
||||
**لست مسؤول Salesforce؟** أعِد توجيه عنوان URL لصفحة التثبيت (أو رابط
|
||||
التثبيت نفسه) إلى مسؤول Salesforce لديكم واطلب منه إكمال الخطوات أعلاه.
|
||||
بمجرد انتهائه، عُد إلى CrewAI AMP وانقر على **Connect** مرة أخرى.
|
||||
</Note>
|
||||
|
||||
### 2. تثبيت الحزمة المطلوبة
|
||||
|
||||
|
||||
144
docs/docs.json
@@ -2384,6 +2384,15 @@
|
||||
{
|
||||
"group": "Manage",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"en/enterprise/features/agent-control-plane/overview",
|
||||
"en/enterprise/features/agent-control-plane/monitoring",
|
||||
"en/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"en/enterprise/features/sso",
|
||||
"en/enterprise/features/rbac"
|
||||
]
|
||||
@@ -2868,6 +2877,15 @@
|
||||
{
|
||||
"group": "Manage",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"en/enterprise/features/agent-control-plane/overview",
|
||||
"en/enterprise/features/agent-control-plane/monitoring",
|
||||
"en/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"en/enterprise/features/sso",
|
||||
"en/enterprise/features/rbac"
|
||||
]
|
||||
@@ -3352,6 +3370,15 @@
|
||||
{
|
||||
"group": "Manage",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"en/enterprise/features/agent-control-plane/overview",
|
||||
"en/enterprise/features/agent-control-plane/monitoring",
|
||||
"en/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"en/enterprise/features/sso",
|
||||
"en/enterprise/features/rbac"
|
||||
]
|
||||
@@ -3836,6 +3863,15 @@
|
||||
{
|
||||
"group": "Manage",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"en/enterprise/features/agent-control-plane/overview",
|
||||
"en/enterprise/features/agent-control-plane/monitoring",
|
||||
"en/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"en/enterprise/features/sso",
|
||||
"en/enterprise/features/rbac"
|
||||
]
|
||||
@@ -9101,6 +9137,15 @@
|
||||
{
|
||||
"group": "Gerenciar",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"pt-BR/enterprise/features/agent-control-plane/overview",
|
||||
"pt-BR/enterprise/features/agent-control-plane/monitoring",
|
||||
"pt-BR/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"pt-BR/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -9562,6 +9607,15 @@
|
||||
{
|
||||
"group": "Gerenciar",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"pt-BR/enterprise/features/agent-control-plane/overview",
|
||||
"pt-BR/enterprise/features/agent-control-plane/monitoring",
|
||||
"pt-BR/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"pt-BR/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -10023,6 +10077,15 @@
|
||||
{
|
||||
"group": "Gerenciar",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"pt-BR/enterprise/features/agent-control-plane/overview",
|
||||
"pt-BR/enterprise/features/agent-control-plane/monitoring",
|
||||
"pt-BR/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"pt-BR/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -10484,6 +10547,15 @@
|
||||
{
|
||||
"group": "Gerenciar",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"pt-BR/enterprise/features/agent-control-plane/overview",
|
||||
"pt-BR/enterprise/features/agent-control-plane/monitoring",
|
||||
"pt-BR/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"pt-BR/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -15671,6 +15743,15 @@
|
||||
{
|
||||
"group": "관리",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"ko/enterprise/features/agent-control-plane/overview",
|
||||
"ko/enterprise/features/agent-control-plane/monitoring",
|
||||
"ko/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"ko/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -16145,6 +16226,15 @@
|
||||
{
|
||||
"group": "관리",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"ko/enterprise/features/agent-control-plane/overview",
|
||||
"ko/enterprise/features/agent-control-plane/monitoring",
|
||||
"ko/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"ko/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -16619,6 +16709,15 @@
|
||||
{
|
||||
"group": "관리",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"ko/enterprise/features/agent-control-plane/overview",
|
||||
"ko/enterprise/features/agent-control-plane/monitoring",
|
||||
"ko/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"ko/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -17093,6 +17192,15 @@
|
||||
{
|
||||
"group": "관리",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"ko/enterprise/features/agent-control-plane/overview",
|
||||
"ko/enterprise/features/agent-control-plane/monitoring",
|
||||
"ko/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"ko/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -22358,6 +22466,15 @@
|
||||
{
|
||||
"group": "الإدارة",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"ar/enterprise/features/agent-control-plane/overview",
|
||||
"ar/enterprise/features/agent-control-plane/monitoring",
|
||||
"ar/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"ar/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -22832,6 +22949,15 @@
|
||||
{
|
||||
"group": "الإدارة",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"ar/enterprise/features/agent-control-plane/overview",
|
||||
"ar/enterprise/features/agent-control-plane/monitoring",
|
||||
"ar/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"ar/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -23306,6 +23432,15 @@
|
||||
{
|
||||
"group": "الإدارة",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"ar/enterprise/features/agent-control-plane/overview",
|
||||
"ar/enterprise/features/agent-control-plane/monitoring",
|
||||
"ar/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"ar/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
@@ -23780,6 +23915,15 @@
|
||||
{
|
||||
"group": "الإدارة",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Agent Control Plane",
|
||||
"icon": "gauge",
|
||||
"pages": [
|
||||
"ar/enterprise/features/agent-control-plane/overview",
|
||||
"ar/enterprise/features/agent-control-plane/monitoring",
|
||||
"ar/enterprise/features/agent-control-plane/rules"
|
||||
]
|
||||
},
|
||||
"ar/enterprise/features/rbac"
|
||||
]
|
||||
},
|
||||
|
||||
104
docs/en/enterprise/features/agent-control-plane/monitoring.mdx
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: "Watch your Automations"
|
||||
description: "Watch fleet health, LLM consumption, and per-automation behavior from the Automations tab."
|
||||
sidebarTitle: "Monitoring"
|
||||
icon: "gauge"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The **Automations** tab is the read-only operations view of the [Agent Control Plane](/en/enterprise/features/agent-control-plane/overview). It combines two metric cards, an interactive sankey, and two sub-tables — **Automations** and **Consumption** — that you can search, filter, and sort.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
All charts and tables respect the **Last 24 hours / Last Week / Last 30 days** selector at the top right. Deltas compare the selected window against the previous one of the same length.
|
||||
|
||||
<Note>
|
||||
Rows only show data for deployments on **crewAI v1.13 or higher** — older deployments appear in the *"We've detected N other automations that we can't display"* banner under the sankey and contribute zero metrics until they're updated and re-deployed. See [Overview — Requirements](/en/enterprise/features/agent-control-plane/overview#requirements).
|
||||
</Note>
|
||||
|
||||
## Dashboard
|
||||
|
||||
The header of the page has two metric cards and an interactive sankey. Clicking either card switches the sankey between two modes:
|
||||
|
||||
- **Health mode** — `Total Automations → status buckets (Critical / Warning / Healthy)`. Click a bucket to filter the Automations table to just those deployments.
|
||||
- **Consumption mode** — `Model Providers → Automations → Total Cost`. Click a provider to filter the Consumption table to that provider.
|
||||
|
||||
| Card | What it shows |
|
||||
|------|---------------|
|
||||
| **Automations** | `active` automations (and total count), total `errors` in the window, currently `active executions` (and total in the window), with a delta vs the previous period. |
|
||||
| **Consumption** | Total `cost` and `tokens used`, with a cost delta vs the previous period. |
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
## Automations table
|
||||
|
||||
The **Automations** sub-tab is the per-deployment breakdown of fleet health. Each row is one deployed crew or flow.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
| Column | What it shows |
|
||||
|--------|---------------|
|
||||
| **Automation** | Deployment name and any tags assigned to it (e.g. `production`, `financial`). |
|
||||
| **Last execution** | Time since the most recent run. |
|
||||
| **Health Status Breakdown** | Stacked bar of `Critical` / `Warning` / `Healthy` percentages for executions in the window. |
|
||||
| **Executions with Errors** | Total failed executions in the window. |
|
||||
| **PII detection applied** | `Yes` if a per-deployment PII config or a matching [PII rule](/en/enterprise/features/agent-control-plane/rules) is active. |
|
||||
| **Executions** | Total executions in the window. |
|
||||
| **Last updated** | When the deployment was last re-deployed. |
|
||||
| **Crew Version** | The `crewai` version reported by the deployment. An info icon next to versions below `1.13` flags rows that can't contribute metrics. |
|
||||
|
||||
Search by name, filter by `Status` (`Healthy` / `Warning` / `Critical`), and sort by any column header. Click a deployment name to open the **Automation panel** (see below).
|
||||
|
||||
## Consumption table
|
||||
|
||||
The **Consumption** sub-tab is the per-deployment breakdown of LLM spend and token usage.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
| Column | What it shows |
|
||||
|--------|---------------|
|
||||
| **Automation** | Deployment name. |
|
||||
| **Last execution** | Time since the most recent run. |
|
||||
| **Tokens used** | One row per LLM provider used by this automation, with the delta vs the previous period. |
|
||||
| **Cost** | Cost per LLM provider, with the delta vs the previous period. |
|
||||
| **Total cost** | Sum across all providers, with the delta. |
|
||||
| **Executions** | Total executions in the window. |
|
||||
| **Last updated** | When the deployment was last re-deployed. |
|
||||
| **Crew Version** | The `crewai` version reported by the deployment. |
|
||||
|
||||
Filter by **LLM provider** and sort by `Cost`, `Executions`, or `Last run`.
|
||||
|
||||
<Info>
|
||||
**Empty cells (`—` or `$0.00`) usually mean the deployment is below crewAI v1.13.** In the screenshot above, *Automation F* (`1.7.0`) and *Automation I* (`1.12.2`) show blanks for tokens and cost — their executions still run, but they don't emit the provider-level telemetry that powers this table. Update and re-deploy these crews to start collecting consumption data.
|
||||
</Info>
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Agent Control Plane — Overview" icon="book-open" href="/en/enterprise/features/agent-control-plane/overview">
|
||||
What ACP is, requirements, plan tiers, and RBAC.
|
||||
</Card>
|
||||
<Card title="Agent Control Plane — Rules" icon="shield-check" href="/en/enterprise/features/agent-control-plane/rules">
|
||||
Apply organization-wide PII Redaction rules across many automations.
|
||||
</Card>
|
||||
<Card title="Traces" icon="timeline" href="/en/enterprise/features/traces">
|
||||
Drill into a single execution to see agent reasoning, tool calls, and token usage.
|
||||
</Card>
|
||||
<Card title="Deploy to AMP" icon="rocket" href="/en/enterprise/guides/deploy-to-amp">
|
||||
Deploy a crew on a crewAI version that supports the Agent Control Plane.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="Need Help?" icon="headset" href="mailto:support@crewai.com">
|
||||
Contact our support team for help interpreting metrics in the Agent Control Plane.
|
||||
</Card>
|
||||
74
docs/en/enterprise/features/agent-control-plane/overview.mdx
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Agent Control Plane Overview
|
||||
description: "Single operations hub for live automations — fleet health, LLM consumption, and organization-wide policies in one place."
|
||||
sidebarTitle: Overview
|
||||
icon: "book-open"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The **Agent Control Plane** (ACP) is the operations hub for everything you have running on CrewAI AMP. It is a single screen — split into **Automations** and **Rules** tabs — that lets your team:
|
||||
|
||||
- Monitor the **health** of every live automation (crew or flow), with `Critical` / `Warning` / `Healthy` breakdowns and execution counts.
|
||||
- Track **LLM consumption** — tokens and cost — per automation, per provider, and per model, with a delta vs the previous period.
|
||||
- Drill into any single automation or model provider for time-series charts and per-provider breakdowns.
|
||||
- Apply organization-wide **Rules** (today: PII Redaction) across many automations at once instead of editing each deployment individually.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
The Agent Control Plane is currently labeled **Beta** in CrewAI Platform.
|
||||
</Note>
|
||||
|
||||
The two tabs answer two different questions:
|
||||
|
||||
- **Automations** — *"How is my fleet behaving right now, and what is it costing me?"* See [Monitoring](/en/enterprise/features/agent-control-plane/monitoring).
|
||||
- **Rules** — *"How do I enforce a policy (e.g. PII redaction) across many deployments without re-deploying each one?"* See [Rules](/en/enterprise/features/agent-control-plane/rules).
|
||||
|
||||
## Requirements
|
||||
|
||||
<Warning>
|
||||
**crewAI v1.13 or higher** is required for an automation to populate any data on this page — health, executions, errors, tokens, and cost all flow through telemetry that lit up in `crewai==1.13`. Older deployments appear in the *"We've detected N other automations that we can't display"* banner and contribute zero rows until they are updated and re-deployed.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
**Enterprise Plan or Ultra Plan** is required to create or edit [Rules](/en/enterprise/features/agent-control-plane/rules). Lower-tier organizations can open the Rules tab and view existing rules, but the editor renders read-only with an "Enterprise" lock pill and the alert *"PII Redaction rules require an Enterprise plan."* Monitoring (the Automations tab) is available on all plans where the feature is enabled.
|
||||
</Warning>
|
||||
|
||||
- The **Agent Control Plane** feature must be enabled for your organization. If you don't see it in the sidebar, ask your account owner to request enablement.
|
||||
- Inside ACP, [RBAC](/en/enterprise/features/rbac) governs access: `read` to view the dashboard and rules, `manage` to create, edit, toggle, or delete rules.
|
||||
- All charts and tables can be scoped to the **Last 24 hours**, **Last Week**, or **Last 30 days** using the time selector at the top right. Deltas (`↑ 8 vs yesterday`, `↓ $20.57 vs yesterday`, etc.) compare the selected window against the previous one of the same length.
|
||||
|
||||
## What you can do here
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Monitoring" icon="gauge" href="/en/enterprise/features/agent-control-plane/monitoring">
|
||||
Watch fleet health and LLM spend with metric cards, an interactive sankey, per-automation tables, and drill-down side panels for any automation or provider.
|
||||
</Card>
|
||||
<Card title="Rules" icon="shield-check" href="/en/enterprise/features/agent-control-plane/rules">
|
||||
Apply organization-wide PII Redaction policies scoped by tools and tags. Changes take effect on the next execution — no re-deploy required.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Traces" icon="timeline" href="/en/enterprise/features/traces">
|
||||
Drill into a single execution to see agent reasoning, tool calls, and token usage.
|
||||
</Card>
|
||||
<Card title="RBAC" icon="users" href="/en/enterprise/features/rbac">
|
||||
Manage who can read the Agent Control Plane and who can edit rules.
|
||||
</Card>
|
||||
<Card title="PII Redaction for Traces" icon="lock" href="/en/enterprise/features/pii-trace-redactions">
|
||||
Entity catalog and per-deployment PII configuration referenced by Rules.
|
||||
</Card>
|
||||
<Card title="Deploy to AMP" icon="rocket" href="/en/enterprise/guides/deploy-to-amp">
|
||||
Deploy a crew on a crewAI version that supports the Agent Control Plane.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="Need Help?" icon="headset" href="mailto:support@crewai.com">
|
||||
Contact our support team for help interpreting metrics or designing rules.
|
||||
</Card>
|
||||
114
docs/en/enterprise/features/agent-control-plane/rules.mdx
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: "Set up the Rules"
|
||||
description: "Apply organization-wide policies across many automations from a single place."
|
||||
sidebarTitle: "Rules"
|
||||
icon: "shield-check"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Rules let you apply policies — today: **PII Redaction** — across many automations at once, instead of configuring each deployment individually. Open the **Rules** tab in the [Agent Control Plane](/en/enterprise/features/agent-control-plane/overview) to manage them.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Each rule card shows the name, description, the **scope** the rule applies to (selected tools and tags), and a count of **engaged automations** — deployments that currently match the scope. The toggle on the right enables or disables the rule without deleting it.
|
||||
|
||||
## Requirements
|
||||
|
||||
<Warning>
|
||||
**Enterprise Plan or Ultra Plan** is required to create or edit PII Redaction rules. Lower-tier organizations can still open the Rules tab and view existing rules, but the editor renders read-only with an "Enterprise" lock pill and the alert *"PII Redaction rules require an Enterprise plan."* — contact your account owner or sales to upgrade.
|
||||
</Warning>
|
||||
|
||||
- The **Agent Control Plane** feature must be enabled for your organization. See [Overview — Requirements](/en/enterprise/features/agent-control-plane/overview#requirements).
|
||||
- The `manage` [RBAC permission](/en/enterprise/features/rbac) on Agent Control Plane is required to create, edit, toggle, or delete rules. The `read` permission is enough to view them.
|
||||
- All rule changes are versioned for auditing.
|
||||
|
||||
## Available rule types
|
||||
|
||||
| Type | What it does |
|
||||
|------|---------------|
|
||||
| **PII Redaction** | Applies PII redaction to executions of every matching automation, using the same entity catalog and custom recognizers documented in [PII Redaction for Traces](/en/enterprise/features/pii-trace-redactions). |
|
||||
|
||||
More rule types will be added over time.
|
||||
|
||||
## Creating a rule
|
||||
|
||||
<Frame>
|
||||
<img src="/images/enterprise/acp-rules-edit-side-panel.png" alt="Rule edit side panel with conditions and PII mask type" width="450" />
|
||||
</Frame>
|
||||
|
||||
<Steps>
|
||||
<Step title="Open the editor">
|
||||
Click **+ Create new** at the top-right of the Rules tab, or **View Details** on an existing rule card.
|
||||
</Step>
|
||||
|
||||
<Step title="Name and describe the rule">
|
||||
Give the rule a clear name (e.g. *Mask PII (CC)*) and a description explaining when it applies. Both show up on the rule card and in the Engaged Automations modal.
|
||||
</Step>
|
||||
|
||||
<Step title="Pick the type">
|
||||
Today only **PII Redaction** is available.
|
||||
</Step>
|
||||
|
||||
<Step title="Set the conditions">
|
||||
Conditions decide which automations the rule engages with. Both are optional and use **set-equality** semantics:
|
||||
|
||||
- **Tools** — only automations whose tool set **exactly matches** the selected tools will engage. Picks from Studio apps, MCPs, OSS tools, and Tool Repository registry tools.
|
||||
- **Automations** — only automations whose tag set **exactly matches** the selected tags will engage.
|
||||
|
||||
Leaving a picker empty means "no filter on this dimension". Leaving both empty means the rule applies to **every** automation in the organization.
|
||||
</Step>
|
||||
|
||||
<Step title="Configure the PII Mask Type table">
|
||||
Check each entity type you want covered and choose **Mask** (replaces with the entity label, e.g. `<CREDIT_CARD>`) or **Redact** (removes the matched text entirely). See [PII Redaction for Traces](/en/enterprise/features/pii-trace-redactions) for the full entity catalog and how to add organization-level custom recognizers.
|
||||
</Step>
|
||||
|
||||
<Step title="Save">
|
||||
The rule applies to **future** executions of every engaged automation as soon as you save. No re-deploy is needed.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Engaged automations
|
||||
|
||||
Click **Engaged N automations** on any rule card to see exactly which deployments the rule is currently matching, along with each one's last execution.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
This is the fastest way to sanity-check a rule's scope before enabling it — for example, to confirm that a rule scoped to the `production` tag isn't accidentally matching a staging deployment.
|
||||
|
||||
## Org-wide rules vs per-deployment settings
|
||||
|
||||
PII Redaction can be configured in two places:
|
||||
|
||||
- **Per-deployment** — under **Settings → PII Protection** on each individual deployment ([guide](/en/enterprise/features/pii-trace-redactions))
|
||||
- **Org-wide** — as a Rule on this page
|
||||
|
||||
When an enabled org-wide rule's scope matches a deployment, the rule's entity configuration **overrides** the deployment-owned PII settings for that deployment's executions — the rule becomes the single source of truth while it's attached. Disable or detach the rule (or change its scope so it no longer matches) and the deployment falls back to its own PII Protection settings.
|
||||
|
||||
Prefer org-wide rules when you want to enforce a consistent policy across many deployments; reserve per-deployment configuration for one-off exceptions.
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Agent Control Plane — Overview" icon="book-open" href="/en/enterprise/features/agent-control-plane/overview">
|
||||
What ACP is, requirements, plan tiers, and RBAC.
|
||||
</Card>
|
||||
<Card title="Agent Control Plane — Monitoring" icon="gauge" href="/en/enterprise/features/agent-control-plane/monitoring">
|
||||
Monitor automations and LLM consumption across your fleet.
|
||||
</Card>
|
||||
<Card title="PII Redaction for Traces" icon="lock" href="/en/enterprise/features/pii-trace-redactions">
|
||||
Entity catalog, custom recognizers, and per-deployment configuration.
|
||||
</Card>
|
||||
<Card title="RBAC" icon="users" href="/en/enterprise/features/rbac">
|
||||
Manage who can create or edit rules.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="Need Help?" icon="headset" href="mailto:support@crewai.com">
|
||||
Contact our support team for help designing rules for your organization.
|
||||
</Card>
|
||||
@@ -17,15 +17,61 @@ Before using the Salesforce integration, ensure you have:
|
||||
- A Salesforce account with appropriate permissions
|
||||
- Connected your Salesforce account through the [Integrations page](https://app.crewai.com/integrations)
|
||||
|
||||
<Note>
|
||||
Salesforce requires a **one-time admin install** of the CrewAI package in
|
||||
your org before any user can connect. This is a Salesforce platform
|
||||
requirement for all ExternalClientApp-based integrations as of the Spring
|
||||
'26 release — not a CrewAI-specific step. The Connect Salesforce flow in
|
||||
CrewAI AMP walks you through it the first time.
|
||||
</Note>
|
||||
|
||||
## Setting Up Salesforce Integration
|
||||
|
||||
### 1. Connect Your Salesforce Account
|
||||
|
||||
1. Navigate to [CrewAI AMP Integrations](https://app.crewai.com/crewai_plus/connectors)
|
||||
2. Find **Salesforce** in the Authentication Integrations section
|
||||
3. Click **Connect** and complete the OAuth flow
|
||||
4. Grant the necessary permissions for CRM and sales management
|
||||
5. Copy your Enterprise Token from [Integration Settings](https://app.crewai.com/crewai_plus/settings/integrations)
|
||||
1. Navigate to [CrewAI AMP Integrations](https://app.crewai.com/crewai_plus/unified_tools).
|
||||
2. Find **Salesforce** in the Authentication Integrations section.
|
||||
3. Click **Connect**.
|
||||
|
||||
What happens next depends on whether a Salesforce admin in your org has
|
||||
already installed the CrewAI package:
|
||||
|
||||
- **Package already installed:** you're taken straight to the Salesforce
|
||||
OAuth consent screen — approve it and you're connected.
|
||||
- **Package not installed yet:** you'll see an **Install CrewAI in
|
||||
Salesforce** page. Follow the one-time install steps below, then come
|
||||
back to CrewAI AMP and click **Connect** again.
|
||||
|
||||
4. Grant the necessary permissions for CRM and sales management.
|
||||
5. Copy your Enterprise Token from [Integration Settings](https://app.crewai.com/crewai_plus/settings/integrations).
|
||||
|
||||
#### One-time admin install (Salesforce admin only)
|
||||
|
||||
The first time anyone in your org clicks **Connect Salesforce**, CrewAI
|
||||
redirects them to an install page that points at the CrewAI managed package.
|
||||
A Salesforce admin needs to install it once for the whole org.
|
||||
|
||||
1. On the install page in CrewAI, click **Install in Salesforce**. (You can
|
||||
also share the page URL with your admin — the install link works for
|
||||
anyone who opens it.)
|
||||
2. Sign in to Salesforce as an admin. For sandboxes, swap `login.salesforce.com`
|
||||
for `test.salesforce.com` in the URL before opening it.
|
||||
3. Choose **Install for All Users**, acknowledge the third-party app prompt,
|
||||
and click **Install**.
|
||||
4. In Salesforce Setup, search **External Client App Manager** → **CrewAI
|
||||
App** → open the **Policies** tab → **Edit** and set:
|
||||
- **Permitted Users:** All users may self-authorize
|
||||
- **IP Relaxation:** Relax IP restrictions
|
||||
- **Refresh Token Policy:** Refresh token is valid until revoked
|
||||
5. Save.
|
||||
6. Return to CrewAI AMP and click **Connect Salesforce** again. OAuth will
|
||||
complete this time.
|
||||
|
||||
<Note>
|
||||
**Not a Salesforce admin?** Forward the install page URL (or the install
|
||||
link itself) to your Salesforce admin and ask them to complete the steps
|
||||
above. Once they're done, return to CrewAI AMP and click **Connect** again.
|
||||
</Note>
|
||||
|
||||
### 2. Install Required Package
|
||||
|
||||
|
||||
BIN
docs/images/enterprise/acp-automation-panel-costs.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
docs/images/enterprise/acp-automation-panel-health.png
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
docs/images/enterprise/acp-automation-panel-tokens.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
docs/images/enterprise/acp-automations-table.png
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
docs/images/enterprise/acp-consumption-table.png
Normal file
|
After Width: | Height: | Size: 327 KiB |
BIN
docs/images/enterprise/acp-overview-automations-sankey.png
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
docs/images/enterprise/acp-overview-consumption-sankey.png
Normal file
|
After Width: | Height: | Size: 372 KiB |
BIN
docs/images/enterprise/acp-provider-panel-cost.png
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
docs/images/enterprise/acp-provider-panel-tokens.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
docs/images/enterprise/acp-rules-edit-side-panel.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
docs/images/enterprise/acp-rules-engaged-modal.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
docs/images/enterprise/acp-rules-list.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
104
docs/ko/enterprise/features/agent-control-plane/monitoring.mdx
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: "자동화 살펴보기"
|
||||
description: "Automations 탭에서 플릿 상태, LLM 소비, 자동화별 동작을 확인합니다."
|
||||
sidebarTitle: "모니터링"
|
||||
icon: "gauge"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
## 개요
|
||||
|
||||
**Automations** 탭은 [Agent Control Plane](/ko/enterprise/features/agent-control-plane/overview)의 읽기 전용 운영 뷰입니다. 두 개의 메트릭 카드, 인터랙티브 sankey, 그리고 **Automations**와 **Consumption** 두 개의 서브 테이블을 결합해 검색·필터·정렬을 지원합니다.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
모든 차트와 테이블은 오른쪽 상단의 **지난 24시간 / 지난 1주 / 지난 30일** 선택기를 따릅니다. 변화량은 선택한 윈도우를 같은 길이의 이전 윈도우와 비교합니다.
|
||||
|
||||
<Note>
|
||||
행은 **crewAI v1.13 이상**의 deployment에 대해서만 데이터를 표시합니다. 그 이전 deployment는 sankey 아래 *"We've detected N other automations that we can't display"* 배너에 나타나며, 업데이트하고 재배포할 때까지 어떤 메트릭에도 기여하지 않습니다. [개요 — 요구사항](/ko/enterprise/features/agent-control-plane/overview#요구사항)을 참고하세요.
|
||||
</Note>
|
||||
|
||||
## 대시보드
|
||||
|
||||
페이지 상단에는 두 개의 메트릭 카드와 인터랙티브 sankey가 있습니다. 어느 카드를 클릭해도 sankey가 다음 두 모드 사이를 전환합니다:
|
||||
|
||||
- **상태 모드** — `전체 자동화 → 상태 버킷(Critical / Warning / Healthy)`. 버킷을 클릭하면 Automations 테이블이 해당 deployment로 필터링됩니다.
|
||||
- **소비 모드** — `모델 공급자 → 자동화 → 총 비용`. 공급자를 클릭하면 Consumption 테이블이 해당 공급자로 필터링됩니다.
|
||||
|
||||
| 카드 | 표시 내용 |
|
||||
|------|---------------|
|
||||
| **Automations** | `active` 자동화(및 전체 개수), 윈도우 내 총 `errors`, 현재 `active executions`(및 윈도우 내 총합), 이전 기간 대비 변화량. |
|
||||
| **Consumption** | 총 `cost`와 `tokens used`, 이전 기간 대비 비용 변화량. |
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
## Automations 테이블
|
||||
|
||||
**Automations** 서브 탭은 deployment 단위의 플릿 상태 분해입니다. 각 행은 배포된 하나의 crew 또는 flow입니다.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
| 열 | 표시 내용 |
|
||||
|--------|---------------|
|
||||
| **Automation** | deployment 이름과 할당된 태그(예: `production`, `financial`). |
|
||||
| **Last execution** | 가장 최근 실행 이후 경과 시간. |
|
||||
| **Health Status Breakdown** | 윈도우 내 실행에 대한 `Critical` / `Warning` / `Healthy` 비율의 누적 막대. |
|
||||
| **Executions with Errors** | 윈도우 내 총 실패 실행 수. |
|
||||
| **PII detection applied** | deployment별 PII 설정 또는 일치하는 [PII 규칙](/ko/enterprise/features/agent-control-plane/rules)이 활성화된 경우 `Yes`. |
|
||||
| **Executions** | 윈도우 내 총 실행 수. |
|
||||
| **Last updated** | deployment의 마지막 재배포 시점. |
|
||||
| **Crew Version** | deployment가 보고한 `crewai` 버전. `1.13` 미만 버전 옆의 정보 아이콘은 메트릭에 기여할 수 없는 행을 표시합니다. |
|
||||
|
||||
이름으로 검색하고 `Status`(`Healthy` / `Warning` / `Critical`)로 필터링하며, 컬럼 헤더로 정렬할 수 있습니다. deployment 이름을 클릭하면 **Automation 패널**이 열립니다.
|
||||
|
||||
## Consumption 테이블
|
||||
|
||||
**Consumption** 서브 탭은 deployment 단위의 LLM 지출 및 토큰 사용량 분해입니다.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
| 열 | 표시 내용 |
|
||||
|--------|---------------|
|
||||
| **Automation** | deployment 이름. |
|
||||
| **Last execution** | 가장 최근 실행 이후 경과 시간. |
|
||||
| **Tokens used** | 이 자동화가 사용한 LLM 공급자별로 한 행, 이전 기간 대비 변화량 포함. |
|
||||
| **Cost** | LLM 공급자별 비용, 이전 기간 대비 변화량 포함. |
|
||||
| **Total cost** | 모든 공급자의 합계, 변화량 포함. |
|
||||
| **Executions** | 윈도우 내 총 실행 수. |
|
||||
| **Last updated** | deployment의 마지막 재배포 시점. |
|
||||
| **Crew Version** | deployment가 보고한 `crewai` 버전. |
|
||||
|
||||
**LLM provider**로 필터링하고 `Cost`, `Executions` 또는 `Last run`으로 정렬할 수 있습니다.
|
||||
|
||||
<Info>
|
||||
**빈 셀(`—` 또는 `$0.00`)은 보통 deployment가 crewAI v1.13 미만임을 의미합니다.** 위 스크린샷에서 *Automation F*(`1.7.0`)와 *Automation I*(`1.12.2`)는 토큰과 비용이 비어 있습니다. 실행은 여전히 동작하지만, 이 테이블을 채우는 공급자 수준 텔레메트리를 emit하지 않습니다. 이 crew들을 업데이트하고 재배포하면 소비 데이터가 수집되기 시작합니다.
|
||||
</Info>
|
||||
|
||||
## 관련 문서
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Agent Control Plane — 개요" icon="book-open" href="/ko/enterprise/features/agent-control-plane/overview">
|
||||
ACP란 무엇이며, 요구사항, 플랜 등급, RBAC에 대해.
|
||||
</Card>
|
||||
<Card title="Agent Control Plane — 규칙" icon="shield-check" href="/ko/enterprise/features/agent-control-plane/rules">
|
||||
조직 단위 PII Redaction 규칙을 여러 자동화에 적용합니다.
|
||||
</Card>
|
||||
<Card title="Traces" icon="timeline" href="/ko/enterprise/features/traces">
|
||||
개별 실행을 드릴다운하여 에이전트의 추론, 도구 호출, 토큰 사용량을 확인합니다.
|
||||
</Card>
|
||||
<Card title="AMP에 배포" icon="rocket" href="/ko/enterprise/guides/deploy-to-amp">
|
||||
Agent Control Plane을 지원하는 crewAI 버전으로 crew를 배포합니다.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="도움이 필요하신가요?" icon="headset" href="mailto:support@crewai.com">
|
||||
Agent Control Plane에서 메트릭을 해석하는 데 도움이 필요하시면 지원 팀에 문의하세요.
|
||||
</Card>
|
||||
74
docs/ko/enterprise/features/agent-control-plane/overview.mdx
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Agent Control Plane 개요
|
||||
description: "라이브 자동화의 통합 운영 허브 — 플릿 상태, LLM 소비, 조직 단위 정책을 한 화면에서."
|
||||
sidebarTitle: 개요
|
||||
icon: "book-open"
|
||||
---
|
||||
|
||||
## 개요
|
||||
|
||||
**Agent Control Plane**(ACP)은 CrewAI AMP에서 실행 중인 모든 워크로드를 위한 운영 허브입니다. **Automations**와 **Rules** 두 개의 탭으로 구성된 단일 화면에서 다음 작업을 할 수 있습니다:
|
||||
|
||||
- 모든 라이브 자동화(crew 또는 flow)의 **상태(health)**를 `Critical` / `Warning` / `Healthy` 분포와 실행 횟수로 모니터링합니다.
|
||||
- 자동화별·공급자별·모델별 **LLM 소비**(토큰 및 비용)를 추적하고, 이전 기간 대비 변화량을 확인합니다.
|
||||
- 임의의 자동화 또는 모델 공급자를 드릴다운하여 시계열 차트와 공급자별 분해를 살펴봅니다.
|
||||
- 조직 전체에 **규칙(Rules)**(현재: PII Redaction)을 적용하여 각 deployment를 개별 편집하지 않고 한 번에 여러 자동화에 정책을 강제합니다.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
Agent Control Plane은 현재 CrewAI Platform에서 **Beta**로 표시되어 있습니다.
|
||||
</Note>
|
||||
|
||||
두 탭은 서로 다른 두 가지 질문에 답합니다:
|
||||
|
||||
- **Automations** — *"지금 내 플릿은 어떻게 동작하고 있고, 얼마나 비용이 들고 있는가?"* [모니터링](/ko/enterprise/features/agent-control-plane/monitoring)을 참고하세요.
|
||||
- **Rules** — *"정책(예: PII redaction)을 매번 재배포하지 않고 여러 deployment에 어떻게 강제할 수 있는가?"* [규칙](/ko/enterprise/features/agent-control-plane/rules)을 참고하세요.
|
||||
|
||||
## 요구사항
|
||||
|
||||
<Warning>
|
||||
이 페이지에서 자동화에 대한 데이터(상태, 실행, 오류, 토큰, 비용)를 채우려면 **crewAI v1.13 이상**이 필요합니다. 모든 데이터는 `crewai==1.13`에서 활성화된 텔레메트리를 통해 흘러갑니다. 그 이전 deployment는 *"We've detected N other automations that we can't display"* 배너에 나타나며, 업데이트하고 재배포할 때까지 어떤 행에도 데이터를 기여하지 않습니다.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
[규칙](/ko/enterprise/features/agent-control-plane/rules)을 생성하거나 편집하려면 **Enterprise 또는 Ultra 플랜**이 필요합니다. 하위 플랜의 조직도 Rules 탭을 열고 기존 규칙을 볼 수 있지만, 편집기는 "Enterprise" 잠금 핀과 *"PII Redaction rules require an Enterprise plan."* 경고와 함께 읽기 전용으로 표시됩니다. 모니터링(Automations 탭)은 기능이 활성화된 모든 플랜에서 사용할 수 있습니다.
|
||||
</Warning>
|
||||
|
||||
- **Agent Control Plane** 기능이 조직에 대해 활성화되어 있어야 합니다. 사이드바에 보이지 않으면 계정 오너에게 활성화를 요청하세요.
|
||||
- ACP 내부에서는 [RBAC](/ko/enterprise/features/rbac)가 접근 권한을 관리합니다: 대시보드 및 규칙을 보려면 `read`, 규칙을 생성·편집·토글·삭제하려면 `manage` 권한이 필요합니다.
|
||||
- 모든 차트와 테이블은 오른쪽 상단의 시간 선택기를 통해 **지난 24시간**, **지난 1주**, **지난 30일**로 범위를 조정할 수 있습니다. 변화량(`↑ 8 vs yesterday`, `↓ $20.57 vs yesterday` 등)은 선택한 윈도우를 같은 길이의 이전 윈도우와 비교합니다.
|
||||
|
||||
## 여기에서 할 수 있는 일
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="모니터링" icon="gauge" href="/ko/enterprise/features/agent-control-plane/monitoring">
|
||||
메트릭 카드, 인터랙티브 sankey, 자동화별 테이블, 자동화 또는 공급자별 드릴다운 사이드 패널로 플릿 상태와 LLM 지출을 살펴봅니다.
|
||||
</Card>
|
||||
<Card title="규칙" icon="shield-check" href="/ko/enterprise/features/agent-control-plane/rules">
|
||||
도구와 태그로 범위를 지정한 PII Redaction 정책을 조직 단위로 적용합니다. 변경 사항은 다음 실행부터 적용되며 재배포가 필요 없습니다.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## 관련 문서
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Traces" icon="timeline" href="/ko/enterprise/features/traces">
|
||||
개별 실행을 드릴다운하여 에이전트의 추론, 도구 호출, 토큰 사용량을 확인합니다.
|
||||
</Card>
|
||||
<Card title="RBAC" icon="users" href="/ko/enterprise/features/rbac">
|
||||
누가 Agent Control Plane을 읽을 수 있고 누가 규칙을 편집할 수 있는지 관리합니다.
|
||||
</Card>
|
||||
<Card title="Traces용 PII Redaction" icon="lock" href="/ko/enterprise/features/pii-trace-redactions">
|
||||
규칙이 참조하는 엔티티 카탈로그 및 deployment 단위 PII 설정.
|
||||
</Card>
|
||||
<Card title="AMP에 배포" icon="rocket" href="/ko/enterprise/guides/deploy-to-amp">
|
||||
Agent Control Plane을 지원하는 crewAI 버전으로 crew를 배포합니다.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="도움이 필요하신가요?" icon="headset" href="mailto:support@crewai.com">
|
||||
메트릭 해석 또는 규칙 설계에 도움이 필요하시면 지원 팀에 문의하세요.
|
||||
</Card>
|
||||
114
docs/ko/enterprise/features/agent-control-plane/rules.mdx
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: "규칙 설정하기"
|
||||
description: "한 곳에서 조직 단위 정책을 여러 자동화에 적용합니다."
|
||||
sidebarTitle: "규칙"
|
||||
icon: "shield-check"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
## 개요
|
||||
|
||||
규칙(Rules)은 각 deployment를 개별 설정하는 대신, 정책 — 현재: **PII Redaction** — 을 한 번에 여러 자동화에 적용할 수 있게 해줍니다. 관리하려면 [Agent Control Plane](/ko/enterprise/features/agent-control-plane/overview)에서 **Rules** 탭을 엽니다.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
각 규칙 카드에는 이름, 설명, 규칙이 적용되는 **범위(scope)**(선택된 도구와 태그), 그리고 현재 범위와 일치하는 deployment의 수인 **engaged automations**가 표시됩니다. 오른쪽 토글로 규칙을 삭제하지 않고 활성/비활성할 수 있습니다.
|
||||
|
||||
## 요구사항
|
||||
|
||||
<Warning>
|
||||
PII Redaction 규칙을 생성하거나 편집하려면 **Enterprise 또는 Ultra 플랜**이 필요합니다. 하위 플랜의 조직도 Rules 탭을 열고 기존 규칙을 볼 수는 있지만, 편집기는 "Enterprise" 잠금 핀과 *"PII Redaction rules require an Enterprise plan."* 경고와 함께 읽기 전용으로 렌더링됩니다. 업그레이드하려면 계정 오너 또는 영업팀에 문의하세요.
|
||||
</Warning>
|
||||
|
||||
- **Agent Control Plane** 기능이 조직에 대해 활성화되어 있어야 합니다. [개요 — 요구사항](/ko/enterprise/features/agent-control-plane/overview#요구사항)을 참고하세요.
|
||||
- 규칙을 생성·편집·토글·삭제하려면 Agent Control Plane에 대한 [RBAC](/ko/enterprise/features/rbac)의 `manage` 권한이 필요합니다. 보려면 `read` 권한만으로 충분합니다.
|
||||
- 모든 규칙 변경은 감사를 위해 버전 관리됩니다.
|
||||
|
||||
## 사용 가능한 규칙 유형
|
||||
|
||||
| 유형 | 동작 |
|
||||
|------|---------------|
|
||||
| **PII Redaction** | 일치하는 모든 자동화의 실행에 PII redaction을 적용합니다. [Traces용 PII Redaction](/ko/enterprise/features/pii-trace-redactions)에 문서화된 동일한 엔티티 카탈로그와 커스텀 recognizer를 사용합니다. |
|
||||
|
||||
향후 더 많은 규칙 유형이 추가될 예정입니다.
|
||||
|
||||
## 규칙 만들기
|
||||
|
||||
<Frame>
|
||||
<img src="/images/enterprise/acp-rules-edit-side-panel.png" alt="조건 및 PII 마스크 유형이 있는 규칙 편집 사이드 패널" width="450" />
|
||||
</Frame>
|
||||
|
||||
<Steps>
|
||||
<Step title="편집기 열기">
|
||||
Rules 탭 오른쪽 상단의 **+ Create new**를 클릭하거나, 기존 규칙 카드의 **View Details**를 클릭합니다.
|
||||
</Step>
|
||||
|
||||
<Step title="규칙 이름과 설명 작성">
|
||||
규칙에 명확한 이름(예: *Mask PII (CC)*)과 적용 시점을 설명하는 description을 부여합니다. 둘 다 규칙 카드와 Engaged Automations 모달에 표시됩니다.
|
||||
</Step>
|
||||
|
||||
<Step title="유형 선택">
|
||||
현재 **PII Redaction**만 사용할 수 있습니다.
|
||||
</Step>
|
||||
|
||||
<Step title="조건 설정">
|
||||
조건은 규칙이 어떤 자동화에 engage 할지 결정합니다. 둘 다 선택 사항이며 **집합 동일성(set-equality)** 의미론을 사용합니다:
|
||||
|
||||
- **Tools** — 도구 집합이 선택된 도구와 **정확히 일치**하는 자동화만 engage 됩니다. Studio 앱, MCP, OSS 도구, Tool Repository registry 도구 중에서 선택합니다.
|
||||
- **Automations** — 태그 집합이 선택된 태그와 **정확히 일치**하는 자동화만 engage 됩니다.
|
||||
|
||||
피커를 비워두면 "이 차원에서 필터링하지 않음"을 의미합니다. 두 피커를 모두 비워두면 규칙이 조직의 **모든** 자동화에 적용됩니다.
|
||||
</Step>
|
||||
|
||||
<Step title="PII Mask Type 테이블 구성">
|
||||
적용할 각 엔티티 유형을 체크하고 **Mask**(엔티티 레이블로 치환, 예: `<CREDIT_CARD>`) 또는 **Redact**(일치하는 텍스트를 완전히 제거) 중에서 선택합니다. 전체 엔티티 카탈로그와 조직 단위 커스텀 recognizer 추가 방법은 [Traces용 PII Redaction](/ko/enterprise/features/pii-trace-redactions)을 참고하세요.
|
||||
</Step>
|
||||
|
||||
<Step title="저장">
|
||||
저장하는 즉시 engage 된 모든 자동화의 **향후** 실행에 규칙이 적용됩니다. 재배포는 필요 없습니다.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Engaged automations
|
||||
|
||||
규칙 카드의 **Engaged N automations**를 클릭하면 현재 규칙이 일치시키고 있는 deployment와 각 deployment의 마지막 실행을 정확히 확인할 수 있습니다.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
규칙을 활성화하기 전에 범위를 빠르게 점검하는 가장 좋은 방법입니다. 예를 들어, `production` 태그로 범위를 지정한 규칙이 의도치 않게 staging deployment를 일치시키지 않는지 확인할 수 있습니다.
|
||||
|
||||
## 조직 단위 규칙 vs deployment 단위 설정
|
||||
|
||||
PII Redaction은 두 곳에서 설정할 수 있습니다:
|
||||
|
||||
- **deployment 단위** — 각 deployment의 **Settings → PII Protection** ([가이드](/ko/enterprise/features/pii-trace-redactions))
|
||||
- **조직 단위** — 이 페이지의 규칙
|
||||
|
||||
활성화된 조직 단위 규칙의 범위가 어떤 deployment와 일치하면, 규칙의 엔티티 구성이 그 deployment의 실행에 대해 **deployment가 소유한 PII 설정을 덮어씁니다**. 규칙이 연결된 동안에는 규칙이 단일 진실 공급원이 됩니다. 규칙을 비활성화하거나 분리하면(또는 범위를 변경하여 더 이상 일치하지 않게 만들면) deployment는 자체 PII Protection 설정으로 되돌아갑니다.
|
||||
|
||||
여러 deployment에 걸쳐 일관된 정책을 강제하고 싶을 때는 조직 단위 규칙을 선호하고, 일회성 예외에 대해서는 deployment 단위 설정을 사용하세요.
|
||||
|
||||
## 관련 문서
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Agent Control Plane — 개요" icon="book-open" href="/ko/enterprise/features/agent-control-plane/overview">
|
||||
ACP란 무엇이며, 요구사항, 플랜 등급, RBAC에 대해.
|
||||
</Card>
|
||||
<Card title="Agent Control Plane — 모니터링" icon="gauge" href="/ko/enterprise/features/agent-control-plane/monitoring">
|
||||
플릿 전반의 자동화와 LLM 소비를 모니터링합니다.
|
||||
</Card>
|
||||
<Card title="Traces용 PII Redaction" icon="lock" href="/ko/enterprise/features/pii-trace-redactions">
|
||||
엔티티 카탈로그, 커스텀 recognizer, deployment 단위 구성.
|
||||
</Card>
|
||||
<Card title="RBAC" icon="users" href="/ko/enterprise/features/rbac">
|
||||
누가 규칙을 만들거나 편집할 수 있는지 관리합니다.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="도움이 필요하신가요?" icon="headset" href="mailto:support@crewai.com">
|
||||
조직의 규칙을 설계하는 데 도움이 필요하시면 지원 팀에 문의하세요.
|
||||
</Card>
|
||||
@@ -17,16 +17,60 @@ Salesforce 통합을 사용하기 전에 다음을 확인하세요:
|
||||
- 적절한 권한이 있는 Salesforce 계정
|
||||
- [통합 페이지](https://app.crewai.com/integrations)를 통해 Salesforce 계정 연결
|
||||
|
||||
<Note>
|
||||
Salesforce는 사용자가 연결하기 전에 **관리자가 CrewAI 패키지를 한 번 설치**
|
||||
해야 합니다. 이는 Spring '26 릴리스부터 모든 ExternalClientApp 기반 통합에
|
||||
적용되는 Salesforce 플랫폼의 요구 사항이며, CrewAI 고유의 단계가 아닙니다.
|
||||
CrewAI AMP의 Connect Salesforce 플로우가 첫 연결 시 이 과정을 안내합니다.
|
||||
</Note>
|
||||
|
||||
## Salesforce 통합 설정
|
||||
|
||||
### 1. Salesforce 계정 연결
|
||||
|
||||
1. [CrewAI AMP 통합](https://app.crewai.com/crewai_plus/connectors)으로 이동합니다.
|
||||
1. [CrewAI AMP 통합](https://app.crewai.com/crewai_plus/unified_tools)으로 이동합니다.
|
||||
2. 인증 통합 섹션에서 **Salesforce**를 찾습니다.
|
||||
3. **연결**을 클릭하고 OAuth 과정을 완료합니다.
|
||||
3. **연결**을 클릭합니다.
|
||||
|
||||
이후 동작은 관리자가 조직에 CrewAI 패키지를 이미 설치했는지에 따라 달라집니다:
|
||||
|
||||
- **패키지가 이미 설치된 경우:** 곧바로 Salesforce OAuth 동의 화면으로
|
||||
이동합니다. 승인하면 연결이 완료됩니다.
|
||||
- **패키지가 아직 설치되지 않은 경우:** **Install CrewAI in Salesforce**
|
||||
페이지가 표시됩니다. 아래의 일회성 설치 단계를 따른 뒤, CrewAI AMP로
|
||||
돌아와 **연결**을 다시 클릭하세요.
|
||||
|
||||
4. CRM 및 영업 관리에 필요한 권한을 부여합니다.
|
||||
5. [통합 설정](https://app.crewai.com/crewai_plus/settings/integrations)에서 Enterprise Token을 복사합니다.
|
||||
|
||||
#### 일회성 관리자 설치 (Salesforce 관리자 전용)
|
||||
|
||||
조직 내 누군가 **Connect Salesforce**를 처음 클릭하면, CrewAI는 CrewAI
|
||||
관리형 패키지의 설치 페이지로 리디렉션합니다. Salesforce 관리자가 조직
|
||||
전체를 위해 한 번만 설치하면 됩니다.
|
||||
|
||||
1. CrewAI 내 설치 페이지에서 **Install in Salesforce**를 클릭합니다.
|
||||
(해당 페이지 URL을 관리자에게 공유해도 됩니다. 설치 링크는 누구든 열 수
|
||||
있도록 동작합니다.)
|
||||
2. 관리자 권한으로 Salesforce에 로그인합니다. 샌드박스 환경에서는 URL의
|
||||
`login.salesforce.com`을 `test.salesforce.com`으로 바꾼 뒤 엽니다.
|
||||
3. **Install for All Users**를 선택하고, 서드파티 앱 동의 항목을 확인한 뒤
|
||||
**Install**을 클릭합니다.
|
||||
4. Salesforce Setup에서 **External Client App Manager** → **CrewAI App** →
|
||||
**Policies** 탭 → **Edit**로 이동하여 다음과 같이 설정합니다:
|
||||
- **Permitted Users:** All users may self-authorize
|
||||
- **IP Relaxation:** Relax IP restrictions
|
||||
- **Refresh Token Policy:** Refresh token is valid until revoked
|
||||
5. 저장합니다.
|
||||
6. CrewAI AMP로 돌아가 **Connect Salesforce**를 다시 클릭합니다. 이번에는
|
||||
OAuth가 정상적으로 완료됩니다.
|
||||
|
||||
<Note>
|
||||
**Salesforce 관리자가 아니신가요?** 설치 페이지의 URL(또는 설치 링크 자체)
|
||||
을 Salesforce 관리자에게 전달하고 위 단계를 진행해 달라고 요청하세요.
|
||||
관리자가 완료하면 CrewAI AMP로 돌아와 **연결**을 다시 클릭하면 됩니다.
|
||||
</Note>
|
||||
|
||||
### 2. 필수 패키지 설치
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: "Observe suas Automações"
|
||||
description: "Acompanhe a saúde da frota, o consumo de LLM e o comportamento por automação a partir da aba Automações."
|
||||
sidebarTitle: "Monitoramento"
|
||||
icon: "gauge"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
## Visão Geral
|
||||
|
||||
A aba **Automações** é a visão de operações somente leitura do [Agent Control Plane](/pt-BR/enterprise/features/agent-control-plane/overview). Ela combina dois cards de métricas, um sankey interativo e duas sub-tabelas — **Automações** e **Consumo** — nas quais você pode buscar, filtrar e ordenar.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Todos os gráficos e tabelas respeitam o seletor **Últimas 24 horas / Última Semana / Últimos 30 dias** no canto superior direito. As variações comparam a janela selecionada com a janela anterior de mesma duração.
|
||||
|
||||
<Note>
|
||||
As linhas só mostram dados de deployments em **crewAI v1.13 ou superior** — deployments mais antigos aparecem no banner *"We've detected N other automations that we can't display"* abaixo do sankey e não contribuem com nenhuma métrica até que sejam atualizados e re-implantados. Veja [Visão Geral — Requisitos](/pt-BR/enterprise/features/agent-control-plane/overview#requisitos).
|
||||
</Note>
|
||||
|
||||
## Dashboard
|
||||
|
||||
O topo da página tem dois cards de métricas e um sankey interativo. Clicar em qualquer um dos cards alterna o sankey entre dois modos:
|
||||
|
||||
- **Modo Saúde** — `Total de Automações → buckets de status (Critical / Warning / Healthy)`. Clique em um bucket para filtrar a tabela Automações para esses deployments.
|
||||
- **Modo Consumo** — `Provedores de Modelo → Automações → Custo Total`. Clique em um provedor para filtrar a tabela Consumo para esse provedor.
|
||||
|
||||
| Card | O que mostra |
|
||||
|------|---------------|
|
||||
| **Automações** | Automações `active` (e total), total de `errors` na janela, `active executions` no momento (e total na janela), com variação em relação ao período anterior. |
|
||||
| **Consumo** | `cost` total e `tokens used`, com variação de custo em relação ao período anterior. |
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
## Tabela de Automações
|
||||
|
||||
A sub-aba **Automações** é o detalhamento por deployment da saúde da frota. Cada linha é uma crew ou flow implantada.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
| Coluna | O que mostra |
|
||||
|--------|---------------|
|
||||
| **Automação** | Nome do deployment e quaisquer tags atribuídas a ele (ex.: `production`, `financial`). |
|
||||
| **Última execução** | Tempo decorrido desde a execução mais recente. |
|
||||
| **Health Status Breakdown** | Barra empilhada com percentuais de `Critical` / `Warning` / `Healthy` para as execuções na janela. |
|
||||
| **Executions with Errors** | Total de execuções com falha na janela. |
|
||||
| **PII detection applied** | `Yes` se houver configuração de PII por deployment ou uma [regra de PII](/pt-BR/enterprise/features/agent-control-plane/rules) correspondente ativa. |
|
||||
| **Executions** | Total de execuções na janela. |
|
||||
| **Last updated** | Quando o deployment foi re-implantado pela última vez. |
|
||||
| **Crew Version** | A versão do `crewai` reportada pelo deployment. Um ícone de informação ao lado de versões abaixo de `1.13` indica linhas que não conseguem contribuir com métricas. |
|
||||
|
||||
Busque por nome, filtre por `Status` (`Healthy` / `Warning` / `Critical`) e ordene por qualquer cabeçalho de coluna. Clique no nome de um deployment para abrir o **painel da Automação**.
|
||||
|
||||
## Tabela de Consumo
|
||||
|
||||
A sub-aba **Consumo** é o detalhamento por deployment de gasto com LLM e uso de tokens.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
| Coluna | O que mostra |
|
||||
|--------|---------------|
|
||||
| **Automação** | Nome do deployment. |
|
||||
| **Última execução** | Tempo decorrido desde a execução mais recente. |
|
||||
| **Tokens used** | Uma linha por provedor de LLM usado por esta automação, com variação em relação ao período anterior. |
|
||||
| **Cost** | Custo por provedor de LLM, com variação em relação ao período anterior. |
|
||||
| **Total cost** | Soma entre todos os provedores, com a variação. |
|
||||
| **Executions** | Total de execuções na janela. |
|
||||
| **Last updated** | Quando o deployment foi re-implantado pela última vez. |
|
||||
| **Crew Version** | A versão do `crewai` reportada pelo deployment. |
|
||||
|
||||
Filtre por **LLM provider** e ordene por `Cost`, `Executions` ou `Last run`.
|
||||
|
||||
<Info>
|
||||
**Células vazias (`—` ou `$0.00`) normalmente significam que o deployment está abaixo do crewAI v1.13.** Na captura acima, *Automation F* (`1.7.0`) e *Automation I* (`1.12.2`) mostram valores em branco para tokens e custo — suas execuções ainda rodam, mas não emitem a telemetria a nível de provedor que alimenta esta tabela. Atualize e re-implante essas crews para começar a coletar dados de consumo.
|
||||
</Info>
|
||||
|
||||
## Relacionados
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Agent Control Plane — Visão Geral" icon="book-open" href="/pt-BR/enterprise/features/agent-control-plane/overview">
|
||||
O que é o ACP, requisitos, planos suportados e RBAC.
|
||||
</Card>
|
||||
<Card title="Agent Control Plane — Regras" icon="shield-check" href="/pt-BR/enterprise/features/agent-control-plane/rules">
|
||||
Aplique regras de PII Redaction em nível de organização em muitas automações.
|
||||
</Card>
|
||||
<Card title="Traces" icon="timeline" href="/pt-BR/enterprise/features/traces">
|
||||
Aprofunde em uma única execução para ver o raciocínio do agente, chamadas de ferramentas e uso de tokens.
|
||||
</Card>
|
||||
<Card title="Deploy no AMP" icon="rocket" href="/pt-BR/enterprise/guides/deploy-to-amp">
|
||||
Implante uma crew em uma versão do crewAI que suporta o Agent Control Plane.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="Precisa de ajuda?" icon="headset" href="mailto:support@crewai.com">
|
||||
Entre em contato com nosso time de suporte para ajudar a interpretar métricas no Agent Control Plane.
|
||||
</Card>
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Visão Geral do Agent Control Plane
|
||||
description: "Hub único de operações para automações ao vivo — saúde da frota, consumo de LLM e políticas em nível de organização em um só lugar."
|
||||
sidebarTitle: Visão Geral
|
||||
icon: "book-open"
|
||||
---
|
||||
|
||||
## Visão Geral
|
||||
|
||||
O **Agent Control Plane** (ACP) é o hub de operações para tudo que você tem rodando no CrewAI AMP. É uma tela única — dividida nas abas **Automações** e **Regras** — que permite à sua equipe:
|
||||
|
||||
- Monitorar a **saúde** de cada automação ao vivo (crew ou flow), com detalhamentos `Critical` / `Warning` / `Healthy` e contagem de execuções.
|
||||
- Acompanhar o **consumo de LLM** — tokens e custo — por automação, por provedor e por modelo, com a variação em relação ao período anterior.
|
||||
- Aprofundar em qualquer automação ou provedor de modelo para ver gráficos de série temporal e detalhamentos por provedor.
|
||||
- Aplicar **Regras** em nível de organização (hoje: PII Redaction) em muitas automações de uma só vez, em vez de editar cada deployment individualmente.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
O Agent Control Plane está atualmente marcado como **Beta** na CrewAI Platform.
|
||||
</Note>
|
||||
|
||||
As duas abas respondem a duas perguntas distintas:
|
||||
|
||||
- **Automações** — *"Como minha frota está se comportando agora e quanto está me custando?"* Veja [Monitoramento](/pt-BR/enterprise/features/agent-control-plane/monitoring).
|
||||
- **Regras** — *"Como aplico uma política (por exemplo, PII redaction) em muitos deployments sem precisar reimplantar cada um?"* Veja [Regras](/pt-BR/enterprise/features/agent-control-plane/rules).
|
||||
|
||||
## Requisitos
|
||||
|
||||
<Warning>
|
||||
**crewAI v1.13 ou superior** é necessário para que uma automação preencha qualquer dado nesta página — saúde, execuções, erros, tokens e custo passam por telemetria que foi habilitada em `crewai==1.13`. Deployments mais antigos aparecem no banner *"We've detected N other automations that we can't display"* e não contribuem com nenhum dado até que sejam atualizados e re-implantados.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
**Plano Enterprise ou Ultra** é necessário para criar ou editar [Regras](/pt-BR/enterprise/features/agent-control-plane/rules). Organizações em planos inferiores ainda podem abrir a aba Regras e visualizar regras existentes, mas o editor é renderizado em modo somente leitura, com um selo "Enterprise" de bloqueio e o alerta *"PII Redaction rules require an Enterprise plan."* O Monitoramento (a aba Automações) está disponível em todos os planos em que o recurso esteja habilitado.
|
||||
</Warning>
|
||||
|
||||
- O recurso **Agent Control Plane** precisa estar habilitado para sua organização. Se você não o vê na barra lateral, peça ao owner da conta para solicitar a habilitação.
|
||||
- Dentro do ACP, o [RBAC](/pt-BR/enterprise/features/rbac) controla o acesso: `read` para visualizar o dashboard e as regras, `manage` para criar, editar, ligar/desligar ou excluir regras.
|
||||
- Todos os gráficos e tabelas podem ser ajustados para **Últimas 24 horas**, **Última Semana** ou **Últimos 30 dias** usando o seletor de tempo no canto superior direito. As variações (`↑ 8 vs ontem`, `↓ $20.57 vs ontem`, etc.) comparam a janela selecionada com a janela anterior de mesma duração.
|
||||
|
||||
## O que você pode fazer aqui
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Monitoramento" icon="gauge" href="/pt-BR/enterprise/features/agent-control-plane/monitoring">
|
||||
Acompanhe a saúde da frota e o gasto com LLM com cards de métricas, um sankey interativo, tabelas por automação e painéis laterais de detalhamento para qualquer automação ou provedor.
|
||||
</Card>
|
||||
<Card title="Regras" icon="shield-check" href="/pt-BR/enterprise/features/agent-control-plane/rules">
|
||||
Aplique políticas de PII Redaction em nível de organização, com escopo por ferramentas e tags. As mudanças entram em vigor na próxima execução — sem necessidade de re-implantação.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Relacionados
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Traces" icon="timeline" href="/pt-BR/enterprise/features/traces">
|
||||
Aprofunde em uma única execução para ver o raciocínio do agente, chamadas de ferramentas e uso de tokens.
|
||||
</Card>
|
||||
<Card title="RBAC" icon="users" href="/pt-BR/enterprise/features/rbac">
|
||||
Gerencie quem pode ler o Agent Control Plane e quem pode editar regras.
|
||||
</Card>
|
||||
<Card title="PII Redaction para Traces" icon="lock" href="/pt-BR/enterprise/features/pii-trace-redactions">
|
||||
Catálogo de entidades e configuração de PII por deployment, referenciados pelas Regras.
|
||||
</Card>
|
||||
<Card title="Deploy no AMP" icon="rocket" href="/pt-BR/enterprise/guides/deploy-to-amp">
|
||||
Implante uma crew em uma versão do crewAI que suporta o Agent Control Plane.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="Precisa de ajuda?" icon="headset" href="mailto:support@crewai.com">
|
||||
Entre em contato com nosso time de suporte para interpretar métricas ou desenhar regras.
|
||||
</Card>
|
||||
114
docs/pt-BR/enterprise/features/agent-control-plane/rules.mdx
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: "Configure as Regras"
|
||||
description: "Aplique políticas em nível de organização em muitas automações a partir de um único lugar."
|
||||
sidebarTitle: "Regras"
|
||||
icon: "shield-check"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
## Visão Geral
|
||||
|
||||
As Regras permitem aplicar políticas — hoje: **PII Redaction** — em muitas automações de uma só vez, em vez de configurar cada deployment individualmente. Abra a aba **Regras** no [Agent Control Plane](/pt-BR/enterprise/features/agent-control-plane/overview) para gerenciá-las.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Cada card de regra mostra o nome, a descrição, o **escopo** ao qual a regra se aplica (ferramentas e tags selecionadas) e a contagem de **automações engajadas** — deployments que atualmente correspondem ao escopo. O toggle à direita ativa ou desativa a regra sem excluí-la.
|
||||
|
||||
## Requisitos
|
||||
|
||||
<Warning>
|
||||
**Plano Enterprise ou Ultra** é necessário para criar ou editar regras de PII Redaction. Organizações em planos inferiores ainda podem abrir a aba Regras e visualizar regras existentes, mas o editor é renderizado em modo somente leitura, com um selo "Enterprise" de bloqueio e o alerta *"PII Redaction rules require an Enterprise plan."* — entre em contato com o owner da sua conta ou com vendas para fazer upgrade.
|
||||
</Warning>
|
||||
|
||||
- O recurso **Agent Control Plane** precisa estar habilitado para sua organização. Veja [Visão Geral — Requisitos](/pt-BR/enterprise/features/agent-control-plane/overview#requisitos).
|
||||
- A permissão `manage` no [RBAC](/pt-BR/enterprise/features/rbac) sobre o Agent Control Plane é necessária para criar, editar, ligar/desligar ou excluir regras. A permissão `read` é suficiente para visualizá-las.
|
||||
- Todas as mudanças de regras são versionadas para auditoria.
|
||||
|
||||
## Tipos de regra disponíveis
|
||||
|
||||
| Tipo | O que faz |
|
||||
|------|---------------|
|
||||
| **PII Redaction** | Aplica PII redaction às execuções de cada automação correspondente, usando o mesmo catálogo de entidades e recognizers customizados documentados em [PII Redaction para Traces](/pt-BR/enterprise/features/pii-trace-redactions). |
|
||||
|
||||
Mais tipos de regras serão adicionados ao longo do tempo.
|
||||
|
||||
## Criando uma regra
|
||||
|
||||
<Frame>
|
||||
<img src="/images/enterprise/acp-rules-edit-side-panel.png" alt="Painel lateral de edição de regra com condições e tipo de máscara de PII" width="450" />
|
||||
</Frame>
|
||||
|
||||
<Steps>
|
||||
<Step title="Abra o editor">
|
||||
Clique em **+ Create new** no canto superior direito da aba Regras, ou em **View Details** em um card de regra existente.
|
||||
</Step>
|
||||
|
||||
<Step title="Dê um nome e descreva a regra">
|
||||
Dê à regra um nome claro (ex.: *Mask PII (CC)*) e uma descrição explicando quando ela se aplica. Ambos aparecem no card da regra e no modal de Automações Engajadas.
|
||||
</Step>
|
||||
|
||||
<Step title="Escolha o tipo">
|
||||
Hoje só **PII Redaction** está disponível.
|
||||
</Step>
|
||||
|
||||
<Step title="Defina as condições">
|
||||
As condições decidem quais automações a regra engaja. Ambas são opcionais e usam a semântica de **igualdade de conjuntos**:
|
||||
|
||||
- **Tools** — apenas automações cujo conjunto de ferramentas **corresponde exatamente** às ferramentas selecionadas serão engajadas. Selecione entre apps do Studio, MCPs, ferramentas OSS e ferramentas do Tool Repository.
|
||||
- **Automations** — apenas automações cujo conjunto de tags **corresponde exatamente** às tags selecionadas serão engajadas.
|
||||
|
||||
Deixar um seletor vazio significa "sem filtro nesta dimensão". Deixar ambos vazios significa que a regra se aplica a **todas** as automações da organização.
|
||||
</Step>
|
||||
|
||||
<Step title="Configure a tabela PII Mask Type">
|
||||
Marque cada tipo de entidade que deseja cobrir e escolha **Mask** (substitui pelo rótulo da entidade, ex.: `<CREDIT_CARD>`) ou **Redact** (remove o texto correspondente por completo). Veja [PII Redaction para Traces](/pt-BR/enterprise/features/pii-trace-redactions) para o catálogo completo de entidades e como adicionar recognizers customizados em nível de organização.
|
||||
</Step>
|
||||
|
||||
<Step title="Salve">
|
||||
A regra se aplica a **futuras** execuções de cada automação engajada assim que você salva. Nenhuma re-implantação é necessária.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Automações engajadas
|
||||
|
||||
Clique em **Engaged N automations** em qualquer card de regra para ver exatamente quais deployments a regra está correspondendo no momento, junto com a última execução de cada um.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
Esta é a forma mais rápida de validar o escopo de uma regra antes de habilitá-la — por exemplo, para confirmar que uma regra com escopo na tag `production` não está acidentalmente correspondendo a um deployment de staging.
|
||||
|
||||
## Regras em nível de organização vs configurações por deployment
|
||||
|
||||
A PII Redaction pode ser configurada em dois lugares:
|
||||
|
||||
- **Por deployment** — em **Settings → PII Protection** em cada deployment individual ([guia](/pt-BR/enterprise/features/pii-trace-redactions))
|
||||
- **Em nível de organização** — como uma Regra nesta página
|
||||
|
||||
Quando o escopo de uma regra habilitada em nível de organização corresponde a um deployment, a configuração de entidades da regra **sobrescreve** as configurações de PII pertencentes ao deployment para as execuções daquele deployment — a regra se torna a fonte única da verdade enquanto está vinculada. Desabilite ou desvincule a regra (ou altere o escopo para que ela não corresponda mais) e o deployment volta às suas próprias configurações de PII Protection.
|
||||
|
||||
Prefira regras em nível de organização quando quiser impor uma política consistente em muitos deployments; reserve a configuração por deployment para exceções pontuais.
|
||||
|
||||
## Relacionados
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Agent Control Plane — Visão Geral" icon="book-open" href="/pt-BR/enterprise/features/agent-control-plane/overview">
|
||||
O que é o ACP, requisitos, planos suportados e RBAC.
|
||||
</Card>
|
||||
<Card title="Agent Control Plane — Monitoramento" icon="gauge" href="/pt-BR/enterprise/features/agent-control-plane/monitoring">
|
||||
Acompanhe automações e consumo de LLM em toda a sua frota.
|
||||
</Card>
|
||||
<Card title="PII Redaction para Traces" icon="lock" href="/pt-BR/enterprise/features/pii-trace-redactions">
|
||||
Catálogo de entidades, recognizers customizados e configuração por deployment.
|
||||
</Card>
|
||||
<Card title="RBAC" icon="users" href="/pt-BR/enterprise/features/rbac">
|
||||
Gerencie quem pode criar ou editar regras.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card title="Precisa de ajuda?" icon="headset" href="mailto:support@crewai.com">
|
||||
Entre em contato com nosso time de suporte para ajudar a desenhar regras para a sua organização.
|
||||
</Card>
|
||||
@@ -17,15 +17,65 @@ Antes de usar a integração Salesforce, certifique-se de que você possui:
|
||||
- Uma conta Salesforce com permissões apropriadas
|
||||
- Sua conta Salesforce conectada via a [página de Integrações](https://app.crewai.com/integrations)
|
||||
|
||||
<Note>
|
||||
O Salesforce exige uma **instalação única feita por um administrador** do
|
||||
pacote CrewAI na sua organização antes que qualquer usuário possa se
|
||||
conectar. Isso é uma exigência da plataforma Salesforce para todas as
|
||||
integrações baseadas em ExternalClientApp a partir da release Spring '26 —
|
||||
não é uma etapa específica da CrewAI. O fluxo Connect Salesforce na CrewAI
|
||||
AMP guia você por esta etapa na primeira vez.
|
||||
</Note>
|
||||
|
||||
## Configurando a Integração Salesforce
|
||||
|
||||
### 1. Conecte sua Conta Salesforce
|
||||
|
||||
1. Acesse [CrewAI AMP Integrações](https://app.crewai.com/crewai_plus/connectors)
|
||||
2. Encontre **Salesforce** na seção Integrações de Autenticação
|
||||
3. Clique em **Conectar** e complete o fluxo OAuth
|
||||
4. Conceda as permissões necessárias para gerenciamento de CRM e vendas
|
||||
5. Copie seu Token Enterprise em [Configurações de Integração](https://app.crewai.com/crewai_plus/settings/integrations)
|
||||
1. Acesse [CrewAI AMP Integrações](https://app.crewai.com/crewai_plus/unified_tools).
|
||||
2. Encontre **Salesforce** na seção Integrações de Autenticação.
|
||||
3. Clique em **Conectar**.
|
||||
|
||||
O que acontece em seguida depende de o administrador Salesforce já ter
|
||||
instalado o pacote CrewAI na sua organização:
|
||||
|
||||
- **Pacote já instalado:** você será levado diretamente à tela de consentimento
|
||||
OAuth do Salesforce — aprove e a conexão estará feita.
|
||||
- **Pacote ainda não instalado:** você verá uma página **Install CrewAI in
|
||||
Salesforce**. Siga as etapas de instalação única abaixo e, depois, volte à
|
||||
CrewAI AMP e clique em **Conectar** novamente.
|
||||
|
||||
4. Conceda as permissões necessárias para gerenciamento de CRM e vendas.
|
||||
5. Copie seu Token Enterprise em [Configurações de Integração](https://app.crewai.com/crewai_plus/settings/integrations).
|
||||
|
||||
#### Instalação única pelo administrador (apenas admin Salesforce)
|
||||
|
||||
Na primeira vez que alguém na sua organização clica em **Connect Salesforce**,
|
||||
a CrewAI redireciona para uma página de instalação que aponta para o pacote
|
||||
gerenciado CrewAI. Um administrador Salesforce precisa instalá-lo uma única
|
||||
vez para toda a organização.
|
||||
|
||||
1. Na página de instalação dentro da CrewAI, clique em **Install in
|
||||
Salesforce**. (Você também pode compartilhar a URL dessa página com seu
|
||||
administrador — o link de instalação funciona para qualquer pessoa que o
|
||||
abra.)
|
||||
2. Entre no Salesforce como administrador. Para sandboxes, troque
|
||||
`login.salesforce.com` por `test.salesforce.com` na URL antes de abrir.
|
||||
3. Escolha **Install for All Users**, confirme o aviso sobre aplicativos de
|
||||
terceiros e clique em **Install**.
|
||||
4. No Setup do Salesforce, busque **External Client App Manager** → **CrewAI
|
||||
App** → abra a aba **Policies** → **Edit** e configure:
|
||||
- **Permitted Users:** All users may self-authorize
|
||||
- **IP Relaxation:** Relax IP restrictions
|
||||
- **Refresh Token Policy:** Refresh token is valid until revoked
|
||||
5. Salve.
|
||||
6. Volte à CrewAI AMP e clique em **Connect Salesforce** novamente. Desta vez
|
||||
o OAuth será concluído.
|
||||
|
||||
<Note>
|
||||
**Não é administrador Salesforce?** Encaminhe a URL da página de instalação
|
||||
(ou o link de instalação em si) para o seu administrador e peça que ele
|
||||
conclua as etapas acima. Quando ele terminar, volte à CrewAI AMP e clique
|
||||
em **Conectar** novamente.
|
||||
</Note>
|
||||
|
||||
### 2. Instale o Pacote Necessário
|
||||
|
||||
|
||||
@@ -186,8 +186,6 @@ class Telemetry:
|
||||
|
||||
self._safe_telemetry_procedure(_operation)
|
||||
|
||||
# --- CLI-facing spans ---------------------------------------------------
|
||||
|
||||
def deploy_signup_error_span(self) -> None:
|
||||
"""Records when an error occurs during the deployment signup process."""
|
||||
|
||||
|
||||
@@ -114,14 +114,12 @@ def format_multimodal_content(
|
||||
content_blocks: list[dict[str, Any]] = []
|
||||
provider_type = _normalize_provider(provider)
|
||||
|
||||
# Add text block first if provided
|
||||
if text:
|
||||
content_blocks.append(_format_text_block(text, provider_type, api))
|
||||
|
||||
if not files:
|
||||
return content_blocks
|
||||
|
||||
# Use API-specific constraints for OpenAI
|
||||
constraints_key: str = provider_type
|
||||
if api == "responses" and "openai" in provider_type.lower():
|
||||
constraints_key = "openai_responses"
|
||||
@@ -186,7 +184,6 @@ async def aformat_multimodal_content(
|
||||
if not files:
|
||||
return content_blocks
|
||||
|
||||
# Use API-specific constraints for OpenAI
|
||||
constraints_key: str = provider_type
|
||||
if api == "responses" and "openai" in provider_type.lower():
|
||||
constraints_key = "openai_responses"
|
||||
|
||||
@@ -245,7 +245,6 @@ class FileResolver:
|
||||
type_constraint = self._get_type_constraint(content_type, constraints)
|
||||
|
||||
if type_constraint is not None:
|
||||
# Check if file exceeds type-specific inline limit
|
||||
if file_size > type_constraint.max_size_bytes:
|
||||
logger.debug(
|
||||
f"File {file.filename} ({file_size}B) exceeds {content_type} "
|
||||
|
||||
@@ -162,7 +162,6 @@ class TestFileProcessorValidate:
|
||||
image=ImageConstraints(max_size_bytes=10),
|
||||
)
|
||||
processor = FileProcessor(constraints=constraints)
|
||||
# Set mode to strict on the file
|
||||
file = ImageFile(
|
||||
source=FileBytes(data=MINIMAL_PNG, filename="test.png"), mode="strict"
|
||||
)
|
||||
@@ -199,7 +198,6 @@ class TestFileProcessorProcess:
|
||||
image=ImageConstraints(max_size_bytes=10),
|
||||
)
|
||||
processor = FileProcessor(constraints=constraints)
|
||||
# Set mode to strict on the file
|
||||
file = ImageFile(
|
||||
source=FileBytes(data=MINIMAL_PNG, filename="test.png"), mode="strict"
|
||||
)
|
||||
@@ -214,7 +212,6 @@ class TestFileProcessorProcess:
|
||||
image=ImageConstraints(max_size_bytes=10),
|
||||
)
|
||||
processor = FileProcessor(constraints=constraints)
|
||||
# Set mode to warn on the file
|
||||
file = ImageFile(
|
||||
source=FileBytes(data=MINIMAL_PNG, filename="test.png"), mode="warn"
|
||||
)
|
||||
|
||||
@@ -93,14 +93,11 @@ class TestFileResolver:
|
||||
resolver = FileResolver(upload_cache=cache)
|
||||
file = ImageFile(source=FileBytes(data=MINIMAL_PNG, filename="test.png"))
|
||||
|
||||
# First resolution
|
||||
resolved1 = resolver.resolve(file, "openai")
|
||||
# Second resolution (should use same base64 encoding)
|
||||
resolved2 = resolver.resolve(file, "openai")
|
||||
|
||||
assert isinstance(resolved1, InlineBase64)
|
||||
assert isinstance(resolved2, InlineBase64)
|
||||
# Data should be identical
|
||||
assert resolved1.data == resolved2.data
|
||||
|
||||
def test_clear_cache(self):
|
||||
@@ -108,7 +105,6 @@ class TestFileResolver:
|
||||
cache = UploadCache()
|
||||
file = ImageFile(source=FileBytes(data=MINIMAL_PNG, filename="test.png"))
|
||||
|
||||
# Add something to cache manually
|
||||
cache.set(file=file, provider="gemini", file_id="test")
|
||||
|
||||
resolver = FileResolver(upload_cache=cache)
|
||||
|
||||
@@ -162,7 +162,6 @@ class TestUploadCache:
|
||||
source=FileBytes(data=MINIMAL_PNG + b"x", filename="test2.png")
|
||||
)
|
||||
|
||||
# Add one expired and one valid entry
|
||||
past = datetime.now(timezone.utc) - timedelta(hours=1)
|
||||
future = datetime.now(timezone.utc) + timedelta(hours=24)
|
||||
|
||||
|
||||
@@ -252,12 +252,10 @@ class CrewAIRagAdapter(Adapter):
|
||||
if filename.startswith("."):
|
||||
continue
|
||||
|
||||
# Skip binary files based on extension
|
||||
file_ext = os.path.splitext(filename)[1].lower()
|
||||
if file_ext in binary_extensions:
|
||||
continue
|
||||
|
||||
# Skip __pycache__ directories
|
||||
if "__pycache__" in root:
|
||||
continue
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ class EnterpriseActionTool(BaseTool):
|
||||
|
||||
schema_props, required = self._extract_schema_info(action_schema)
|
||||
|
||||
# Define field definitions for the model
|
||||
field_definitions = {}
|
||||
for param_name, param_details in schema_props.items():
|
||||
param_desc = param_details.get("description", "")
|
||||
@@ -59,12 +58,10 @@ class EnterpriseActionTool(BaseTool):
|
||||
except Exception:
|
||||
field_type = str
|
||||
|
||||
# Create field definition based on requirement
|
||||
field_definitions[param_name] = self._create_field_definition(
|
||||
field_type, is_required, param_desc
|
||||
)
|
||||
|
||||
# Create the model
|
||||
if field_definitions:
|
||||
try:
|
||||
args_schema = create_model( # type: ignore[call-overload]
|
||||
|
||||
@@ -16,7 +16,6 @@ class RAGAdapter(Adapter):
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
# Prepare embedding configuration
|
||||
embedding_config = {"api_key": embedding_api_key, **embedding_kwargs}
|
||||
|
||||
self._adapter = RAG(
|
||||
|
||||
@@ -14,7 +14,6 @@ from crewai_tools.aws.bedrock.exceptions import (
|
||||
)
|
||||
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
@@ -66,29 +65,24 @@ class BedrockInvokeAgentTool(BaseTool):
|
||||
self.enable_trace = enable_trace
|
||||
self.end_session = end_session
|
||||
|
||||
# Update the description if provided
|
||||
if description:
|
||||
self.description = description
|
||||
|
||||
# Validate parameters
|
||||
self._validate_parameters()
|
||||
|
||||
def _validate_parameters(self) -> None:
|
||||
"""Validate the parameters according to AWS API requirements."""
|
||||
try:
|
||||
# Validate agent_id
|
||||
if not self.agent_id:
|
||||
raise BedrockValidationError("agent_id cannot be empty")
|
||||
if not isinstance(self.agent_id, str):
|
||||
raise BedrockValidationError("agent_id must be a string")
|
||||
|
||||
# Validate agent_alias_id
|
||||
if not self.agent_alias_id:
|
||||
raise BedrockValidationError("agent_alias_id cannot be empty")
|
||||
if not isinstance(self.agent_alias_id, str):
|
||||
raise BedrockValidationError("agent_alias_id must be a string")
|
||||
|
||||
# Validate session_id if provided
|
||||
if self.session_id and not isinstance(self.session_id, str):
|
||||
raise BedrockValidationError("session_id must be a string")
|
||||
|
||||
@@ -113,7 +107,6 @@ class BedrockInvokeAgentTool(BaseTool):
|
||||
),
|
||||
)
|
||||
|
||||
# Format the prompt with current time
|
||||
current_utc = datetime.now(timezone.utc)
|
||||
prompt = f"""
|
||||
The current time is: {current_utc}
|
||||
@@ -132,12 +125,9 @@ Below is the users query or task. Complete it and answer it consicely and to the
|
||||
endSession=self.end_session,
|
||||
)
|
||||
|
||||
# Process the response
|
||||
completion = ""
|
||||
|
||||
# Check if response contains a completion field
|
||||
if "completion" in response:
|
||||
# Process streaming response format
|
||||
for event in response.get("completion", []):
|
||||
if "chunk" in event and "bytes" in event["chunk"]:
|
||||
chunk_bytes = event["chunk"]["bytes"]
|
||||
@@ -161,7 +151,6 @@ Below is the users query or task. Complete it and answer it consicely and to the
|
||||
"response_keys": list(response.keys()),
|
||||
}
|
||||
|
||||
# Add more debug info
|
||||
if "chunk" in response:
|
||||
debug_info["chunk_keys"] = list(response["chunk"].keys())
|
||||
|
||||
|
||||
@@ -135,10 +135,8 @@ class NavigateTool(BrowserBaseTool):
|
||||
def _run(self, url: str, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the sync tool."""
|
||||
try:
|
||||
# Get page for this thread
|
||||
page = self.get_sync_page(thread_id)
|
||||
|
||||
# Validate URL scheme
|
||||
parsed_url = urlparse(url)
|
||||
if parsed_url.scheme not in ("http", "https"):
|
||||
raise ValueError("URL scheme must be 'http' or 'https'")
|
||||
@@ -153,10 +151,8 @@ class NavigateTool(BrowserBaseTool):
|
||||
async def _arun(self, url: str, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the async tool."""
|
||||
try:
|
||||
# Get page for this thread
|
||||
page = await self.get_async_page(thread_id)
|
||||
|
||||
# Validate URL scheme
|
||||
parsed_url = urlparse(url)
|
||||
if parsed_url.scheme not in ("http", "https"):
|
||||
raise ValueError("URL scheme must be 'http' or 'https'")
|
||||
@@ -191,7 +187,6 @@ class ClickTool(BrowserBaseTool):
|
||||
def _run(self, selector: str, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the sync tool."""
|
||||
try:
|
||||
# Get the current page
|
||||
page = self.get_sync_page(thread_id)
|
||||
|
||||
# Click on the element
|
||||
@@ -218,7 +213,6 @@ class ClickTool(BrowserBaseTool):
|
||||
) -> str:
|
||||
"""Use the async tool."""
|
||||
try:
|
||||
# Get the current page
|
||||
page = await self.get_async_page(thread_id)
|
||||
|
||||
# Click on the element
|
||||
@@ -251,7 +245,6 @@ class NavigateBackTool(BrowserBaseTool):
|
||||
def _run(self, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the sync tool."""
|
||||
try:
|
||||
# Get the current page
|
||||
page = self.get_sync_page(thread_id)
|
||||
|
||||
# Navigate back
|
||||
@@ -266,7 +259,6 @@ class NavigateBackTool(BrowserBaseTool):
|
||||
async def _arun(self, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the async tool."""
|
||||
try:
|
||||
# Get the current page
|
||||
page = await self.get_async_page(thread_id)
|
||||
|
||||
# Navigate back
|
||||
@@ -289,7 +281,6 @@ class ExtractTextTool(BrowserBaseTool):
|
||||
def _run(self, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the sync tool."""
|
||||
try:
|
||||
# Import BeautifulSoup
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
except ImportError:
|
||||
@@ -298,10 +289,8 @@ class ExtractTextTool(BrowserBaseTool):
|
||||
" Please install it with 'pip install beautifulsoup4'."
|
||||
)
|
||||
|
||||
# Get the current page
|
||||
page = self.get_sync_page(thread_id)
|
||||
|
||||
# Extract text
|
||||
content = page.content()
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
return soup.get_text(separator="\n").strip()
|
||||
@@ -311,7 +300,6 @@ class ExtractTextTool(BrowserBaseTool):
|
||||
async def _arun(self, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the async tool."""
|
||||
try:
|
||||
# Import BeautifulSoup
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
except ImportError:
|
||||
@@ -320,10 +308,8 @@ class ExtractTextTool(BrowserBaseTool):
|
||||
" Please install it with 'pip install beautifulsoup4'."
|
||||
)
|
||||
|
||||
# Get the current page
|
||||
page = await self.get_async_page(thread_id)
|
||||
|
||||
# Extract text
|
||||
content = await page.content()
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
return soup.get_text(separator="\n").strip()
|
||||
@@ -341,7 +327,6 @@ class ExtractHyperlinksTool(BrowserBaseTool):
|
||||
def _run(self, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the sync tool."""
|
||||
try:
|
||||
# Import BeautifulSoup
|
||||
try:
|
||||
from bs4 import BeautifulSoup, Tag
|
||||
except ImportError:
|
||||
@@ -350,10 +335,8 @@ class ExtractHyperlinksTool(BrowserBaseTool):
|
||||
" Please install it with 'pip install beautifulsoup4'."
|
||||
)
|
||||
|
||||
# Get the current page
|
||||
page = self.get_sync_page(thread_id)
|
||||
|
||||
# Extract hyperlinks
|
||||
content = page.content()
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
links = []
|
||||
@@ -374,7 +357,6 @@ class ExtractHyperlinksTool(BrowserBaseTool):
|
||||
async def _arun(self, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the async tool."""
|
||||
try:
|
||||
# Import BeautifulSoup
|
||||
try:
|
||||
from bs4 import BeautifulSoup, Tag
|
||||
except ImportError:
|
||||
@@ -383,10 +365,8 @@ class ExtractHyperlinksTool(BrowserBaseTool):
|
||||
" Please install it with 'pip install beautifulsoup4'."
|
||||
)
|
||||
|
||||
# Get the current page
|
||||
page = await self.get_async_page(thread_id)
|
||||
|
||||
# Extract hyperlinks
|
||||
content = await page.content()
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
links = []
|
||||
@@ -415,10 +395,8 @@ class GetElementsTool(BrowserBaseTool):
|
||||
def _run(self, selector: str, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the sync tool."""
|
||||
try:
|
||||
# Get the current page
|
||||
page = self.get_sync_page(thread_id)
|
||||
|
||||
# Get elements
|
||||
elements = page.query_selector_all(selector)
|
||||
if not elements:
|
||||
return f"No elements found with selector '{selector}'"
|
||||
@@ -437,10 +415,8 @@ class GetElementsTool(BrowserBaseTool):
|
||||
) -> str:
|
||||
"""Use the async tool."""
|
||||
try:
|
||||
# Get the current page
|
||||
page = await self.get_async_page(thread_id)
|
||||
|
||||
# Get elements
|
||||
elements = await page.query_selector_all(selector)
|
||||
if not elements:
|
||||
return f"No elements found with selector '{selector}'"
|
||||
@@ -465,10 +441,8 @@ class CurrentWebPageTool(BrowserBaseTool):
|
||||
def _run(self, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the sync tool."""
|
||||
try:
|
||||
# Get the current page
|
||||
page = self.get_sync_page(thread_id)
|
||||
|
||||
# Get information
|
||||
url = page.url
|
||||
title = page.title()
|
||||
return f"URL: {url}\nTitle: {title}"
|
||||
@@ -478,10 +452,8 @@ class CurrentWebPageTool(BrowserBaseTool):
|
||||
async def _arun(self, thread_id: str = "default", **kwargs: Any) -> str:
|
||||
"""Use the async tool."""
|
||||
try:
|
||||
# Get the current page
|
||||
page = await self.get_async_page(thread_id)
|
||||
|
||||
# Get information
|
||||
url = page.url
|
||||
title = await page.title()
|
||||
return f"URL: {url}\nTitle: {title}"
|
||||
|
||||
@@ -155,12 +155,10 @@ class ExecuteCodeTool(BaseTool):
|
||||
thread_id: str = "default",
|
||||
) -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
|
||||
# Execute code
|
||||
response = code_interpreter.invoke(
|
||||
method="executeCode",
|
||||
params={
|
||||
@@ -204,12 +202,10 @@ class ExecuteCommandTool(BaseTool):
|
||||
|
||||
def _run(self, command: str, thread_id: str = "default") -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
|
||||
# Execute command
|
||||
response = code_interpreter.invoke(
|
||||
method="executeCommand", params={"command": command}
|
||||
)
|
||||
@@ -237,12 +233,10 @@ class ReadFilesTool(BaseTool):
|
||||
|
||||
def _run(self, paths: list[str], thread_id: str = "default") -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
|
||||
# Read files
|
||||
response = code_interpreter.invoke(
|
||||
method="readFiles", params={"paths": paths}
|
||||
)
|
||||
@@ -270,7 +264,6 @@ class ListFilesTool(BaseTool):
|
||||
|
||||
def _run(self, directory_path: str = "", thread_id: str = "default") -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
@@ -303,12 +296,10 @@ class DeleteFilesTool(BaseTool):
|
||||
|
||||
def _run(self, paths: list[str], thread_id: str = "default") -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
|
||||
# Remove files
|
||||
response = code_interpreter.invoke(
|
||||
method="removeFiles", params={"paths": paths}
|
||||
)
|
||||
@@ -336,12 +327,10 @@ class WriteFilesTool(BaseTool):
|
||||
|
||||
def _run(self, files: list[dict[str, str]], thread_id: str = "default") -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
|
||||
# Write files
|
||||
response = code_interpreter.invoke(
|
||||
method="writeFiles", params={"content": files}
|
||||
)
|
||||
@@ -371,12 +360,10 @@ class StartCommandTool(BaseTool):
|
||||
|
||||
def _run(self, command: str, thread_id: str = "default") -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
|
||||
# Start command execution
|
||||
response = code_interpreter.invoke(
|
||||
method="startCommandExecution", params={"command": command}
|
||||
)
|
||||
@@ -404,12 +391,10 @@ class GetTaskTool(BaseTool):
|
||||
|
||||
def _run(self, task_id: str, thread_id: str = "default") -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
|
||||
# Get task status
|
||||
response = code_interpreter.invoke(
|
||||
method="getTask", params={"taskId": task_id}
|
||||
)
|
||||
@@ -437,12 +422,10 @@ class StopTaskTool(BaseTool):
|
||||
|
||||
def _run(self, task_id: str, thread_id: str = "default") -> str:
|
||||
try:
|
||||
# Get or create code interpreter
|
||||
code_interpreter = self.toolkit._get_or_create_interpreter(
|
||||
thread_id=thread_id
|
||||
)
|
||||
|
||||
# Stop task
|
||||
response = code_interpreter.invoke(
|
||||
method="stopTask", params={"taskId": task_id}
|
||||
)
|
||||
@@ -555,7 +538,6 @@ class CodeInterpreterToolkit:
|
||||
f"Started code interpreter with session_id:{code_interpreter.session_id} for thread:{thread_id}"
|
||||
)
|
||||
|
||||
# Store the interpreter
|
||||
self._code_interpreters[thread_id] = code_interpreter
|
||||
return code_interpreter
|
||||
|
||||
@@ -582,7 +564,6 @@ class CodeInterpreterToolkit:
|
||||
thread_id: Optional thread ID to clean up. If None, cleans up all sessions.
|
||||
"""
|
||||
if thread_id:
|
||||
# Clean up a specific thread's session
|
||||
if thread_id in self._code_interpreters:
|
||||
try:
|
||||
self._code_interpreters[thread_id].stop()
|
||||
@@ -595,7 +576,6 @@ class CodeInterpreterToolkit:
|
||||
f"Error stopping code interpreter for thread {thread_id}: {e}"
|
||||
)
|
||||
else:
|
||||
# Clean up all sessions
|
||||
thread_ids = list(self._code_interpreters.keys())
|
||||
for tid in thread_ids:
|
||||
try:
|
||||
|
||||
@@ -12,7 +12,6 @@ from crewai_tools.aws.bedrock.exceptions import (
|
||||
)
|
||||
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
@@ -69,7 +68,6 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
else:
|
||||
self.retrieval_configuration = retrieval_configuration
|
||||
|
||||
# Validate parameters
|
||||
self._validate_parameters()
|
||||
|
||||
# Update the description to include the knowledge base details
|
||||
@@ -83,7 +81,6 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
"""
|
||||
vector_search_config = {}
|
||||
|
||||
# Add number of results if provided
|
||||
if self.number_of_results is not None:
|
||||
vector_search_config["numberOfResults"] = self.number_of_results
|
||||
|
||||
@@ -92,7 +89,6 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
def _validate_parameters(self) -> None:
|
||||
"""Validate the parameters according to AWS API requirements."""
|
||||
try:
|
||||
# Validate knowledge_base_id
|
||||
if not self.knowledge_base_id:
|
||||
raise BedrockValidationError("knowledge_base_id cannot be empty")
|
||||
if not isinstance(self.knowledge_base_id, str):
|
||||
@@ -106,7 +102,6 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
"knowledge_base_id must contain only alphanumeric characters"
|
||||
)
|
||||
|
||||
# Validate next_token if provided
|
||||
if self.next_token:
|
||||
if not isinstance(self.next_token, str):
|
||||
raise BedrockValidationError("next_token must be a string")
|
||||
@@ -117,7 +112,6 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
if " " in self.next_token:
|
||||
raise BedrockValidationError("next_token cannot contain spaces")
|
||||
|
||||
# Validate number_of_results if provided
|
||||
if self.number_of_results is not None:
|
||||
if not isinstance(self.number_of_results, int):
|
||||
raise BedrockValidationError("number_of_results must be an integer")
|
||||
@@ -138,12 +132,10 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
Returns:
|
||||
Dict[str, Any]: Processed result with standardized format
|
||||
"""
|
||||
# Extract content
|
||||
content_obj = result.get("content", {})
|
||||
content = content_obj.get("text", "")
|
||||
content_type = content_obj.get("type", "text")
|
||||
|
||||
# Extract location information
|
||||
location = result.get("location", {})
|
||||
location_type = location.get("type", "unknown")
|
||||
source_uri = None
|
||||
@@ -160,7 +152,6 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
"sqlLocation": {"field": "query", "type": "SQL"},
|
||||
}
|
||||
|
||||
# Extract the URI based on location type
|
||||
for loc_key, config in location_mapping.items():
|
||||
if loc_key in location:
|
||||
source_uri = location[loc_key].get(config["field"])
|
||||
@@ -168,7 +159,6 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
location_type = config["type"]
|
||||
break
|
||||
|
||||
# Create result object
|
||||
result_object = {
|
||||
"content": content,
|
||||
"content_type": content_type,
|
||||
@@ -176,18 +166,15 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
"source_uri": source_uri,
|
||||
}
|
||||
|
||||
# Add optional fields if available
|
||||
if "score" in result:
|
||||
result_object["score"] = result["score"]
|
||||
|
||||
if "metadata" in result:
|
||||
result_object["metadata"] = result["metadata"]
|
||||
|
||||
# Handle byte content if present
|
||||
if "byteContent" in content_obj:
|
||||
result_object["byte_content"] = content_obj["byteContent"]
|
||||
|
||||
# Handle row content if present
|
||||
if "row" in content_obj:
|
||||
result_object["row_content"] = content_obj["row"]
|
||||
|
||||
@@ -212,13 +199,11 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
# AWS SDK will automatically use AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from environment
|
||||
)
|
||||
|
||||
# Prepare the request parameters
|
||||
retrieve_params = {
|
||||
"knowledgeBaseId": self.knowledge_base_id,
|
||||
"retrievalQuery": {"text": query},
|
||||
}
|
||||
|
||||
# Add optional parameters if provided
|
||||
if self.retrieval_configuration:
|
||||
retrieve_params["retrievalConfiguration"] = self.retrieval_configuration
|
||||
|
||||
@@ -228,16 +213,13 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
if self.next_token:
|
||||
retrieve_params["nextToken"] = self.next_token
|
||||
|
||||
# Make the retrieve API call
|
||||
response = bedrock_agent_runtime.retrieve(**retrieve_params)
|
||||
|
||||
# Process the response
|
||||
results = []
|
||||
for result in response.get("retrievalResults", []):
|
||||
processed_result = self._process_retrieval_result(result)
|
||||
results.append(processed_result)
|
||||
|
||||
# Build the response object
|
||||
response_object = {}
|
||||
if results:
|
||||
response_object["results"] = results
|
||||
@@ -250,7 +232,6 @@ class BedrockKBRetrieverTool(BaseTool):
|
||||
if "guardrailAction" in response:
|
||||
response_object["guardrailAction"] = response["guardrailAction"]
|
||||
|
||||
# Return the results as a JSON string
|
||||
return json.dumps(response_object, indent=2)
|
||||
|
||||
except ClientError as e:
|
||||
|
||||
@@ -37,7 +37,6 @@ class S3ReaderTool(BaseTool):
|
||||
aws_secret_access_key=os.getenv("CREW_AWS_SEC_ACCESS_KEY"),
|
||||
)
|
||||
|
||||
# Read file content from S3
|
||||
response = s3.get_object(Bucket=bucket_name, Key=object_key)
|
||||
result: str = response["Body"].read().decode("utf-8")
|
||||
return result
|
||||
|
||||
@@ -12,15 +12,15 @@ class TextChunker(BaseChunker):
|
||||
if separators is None:
|
||||
separators = [
|
||||
"\n\n\n", # Multiple line breaks (sections)
|
||||
"\n\n", # Paragraph breaks
|
||||
"\n", # Line breaks
|
||||
". ", # Sentence endings
|
||||
"! ", # Exclamation endings
|
||||
"? ", # Question endings
|
||||
"; ", # Semicolon breaks
|
||||
", ", # Comma breaks
|
||||
" ", # Word breaks
|
||||
"", # Character level
|
||||
"\n\n",
|
||||
"\n",
|
||||
". ",
|
||||
"! ",
|
||||
"? ",
|
||||
"; ",
|
||||
", ",
|
||||
" ",
|
||||
"",
|
||||
]
|
||||
super().__init__(chunk_size, chunk_overlap, separators, keep_separator)
|
||||
|
||||
@@ -36,15 +36,15 @@ class DocxChunker(BaseChunker):
|
||||
if separators is None:
|
||||
separators = [
|
||||
"\n\n\n", # Multiple line breaks (major sections)
|
||||
"\n\n", # Paragraph breaks
|
||||
"\n", # Line breaks
|
||||
". ", # Sentence endings
|
||||
"! ", # Exclamation endings
|
||||
"? ", # Question endings
|
||||
"; ", # Semicolon breaks
|
||||
", ", # Comma breaks
|
||||
" ", # Word breaks
|
||||
"", # Character level
|
||||
"\n\n",
|
||||
"\n",
|
||||
". ",
|
||||
"! ",
|
||||
"? ",
|
||||
"; ",
|
||||
", ",
|
||||
" ",
|
||||
"",
|
||||
]
|
||||
super().__init__(chunk_size, chunk_overlap, separators, keep_separator)
|
||||
|
||||
@@ -62,15 +62,15 @@ class MdxChunker(BaseChunker):
|
||||
"\n## ", # H2 headers (major sections)
|
||||
"\n### ", # H3 headers (subsections)
|
||||
"\n#### ", # H4 headers (sub-subsections)
|
||||
"\n\n", # Paragraph breaks
|
||||
"\n```", # Code block boundaries
|
||||
"\n", # Line breaks
|
||||
". ", # Sentence endings
|
||||
"! ", # Exclamation endings
|
||||
"? ", # Question endings
|
||||
"; ", # Semicolon breaks
|
||||
", ", # Comma breaks
|
||||
" ", # Word breaks
|
||||
"", # Character level
|
||||
"\n\n",
|
||||
"\n```",
|
||||
"\n",
|
||||
". ",
|
||||
"! ",
|
||||
"? ",
|
||||
"; ",
|
||||
", ",
|
||||
" ",
|
||||
"",
|
||||
]
|
||||
super().__init__(chunk_size, chunk_overlap, separators, keep_separator)
|
||||
|
||||
@@ -11,15 +11,15 @@ class WebsiteChunker(BaseChunker):
|
||||
):
|
||||
if separators is None:
|
||||
separators = [
|
||||
"\n\n\n", # Major section breaks
|
||||
"\n\n", # Paragraph breaks
|
||||
"\n", # Line breaks
|
||||
". ", # Sentence endings
|
||||
"! ", # Exclamation endings
|
||||
"? ", # Question endings
|
||||
"; ", # Semicolon breaks
|
||||
", ", # Comma breaks
|
||||
" ", # Word breaks
|
||||
"", # Character level
|
||||
"\n\n\n",
|
||||
"\n\n",
|
||||
"\n",
|
||||
". ",
|
||||
"! ",
|
||||
"? ",
|
||||
"; ",
|
||||
", ",
|
||||
" ",
|
||||
"",
|
||||
]
|
||||
super().__init__(chunk_size, chunk_overlap, separators, keep_separator)
|
||||
|
||||
@@ -191,7 +191,6 @@ class RAG(Adapter):
|
||||
metadatas = results.get("metadatas", [None])[0] or []
|
||||
distances = results.get("distances", [None])[0] or []
|
||||
|
||||
# Return sources with relevance scores
|
||||
formatted_results = []
|
||||
for i, doc in enumerate(documents):
|
||||
metadata = metadatas[i] if i < len(metadatas) else {}
|
||||
|
||||
@@ -37,7 +37,6 @@ class DataType(str, Enum):
|
||||
DataType.TEXT: ("text_chunker", "TextChunker"),
|
||||
DataType.DOCX: ("text_chunker", "DocxChunker"),
|
||||
DataType.MDX: ("text_chunker", "MdxChunker"),
|
||||
# Structured formats
|
||||
DataType.CSV: ("structured_chunker", "CsvChunker"),
|
||||
DataType.JSON: ("structured_chunker", "JsonChunker"),
|
||||
DataType.XML: ("structured_chunker", "XmlChunker"),
|
||||
|
||||
@@ -113,10 +113,8 @@ class EmbeddingService:
|
||||
try:
|
||||
from crewai.rag.embeddings.factory import build_embedder
|
||||
|
||||
# Build the configuration for CrewAI's factory
|
||||
config = self._build_provider_config()
|
||||
|
||||
# Create the embedding function
|
||||
self._embedding_function = build_embedder(config)
|
||||
|
||||
logger.info(
|
||||
@@ -287,7 +285,6 @@ class EmbeddingService:
|
||||
if not texts:
|
||||
return []
|
||||
|
||||
# Filter out empty texts
|
||||
valid_texts = [text for text in texts if text and text.strip()]
|
||||
if not valid_texts:
|
||||
logger.warning("No valid texts provided for batch embedding")
|
||||
|
||||
@@ -39,16 +39,12 @@ class MDXLoader(BaseLoader):
|
||||
def _parse_mdx(self, content: str, source_ref: str) -> LoaderResult:
|
||||
cleaned_content = content
|
||||
|
||||
# Remove import statements
|
||||
cleaned_content = _IMPORT_PATTERN.sub("", cleaned_content)
|
||||
|
||||
# Remove export statements
|
||||
cleaned_content = _EXPORT_PATTERN.sub("", cleaned_content)
|
||||
|
||||
# Remove JSX tags (simple approach)
|
||||
cleaned_content = _JSX_TAG_PATTERN.sub("", cleaned_content)
|
||||
|
||||
# Clean up extra whitespace
|
||||
cleaned_content = _EXTRA_NEWLINES_PATTERN.sub("\n\n", cleaned_content)
|
||||
cleaned_content = cleaned_content.strip()
|
||||
|
||||
|
||||
@@ -31,9 +31,7 @@ def sanitize_metadata_for_chromadb(metadata: dict[str, Any]) -> dict[str, Any]:
|
||||
if isinstance(value, (str, int, float, bool)) or value is None:
|
||||
sanitized[key] = value
|
||||
elif isinstance(value, (list, tuple)):
|
||||
# Convert lists/tuples to pipe-separated strings
|
||||
sanitized[key] = " | ".join(str(v) for v in value)
|
||||
else:
|
||||
# Convert other types to string
|
||||
sanitized[key] = str(value)
|
||||
return sanitized
|
||||
|
||||
@@ -27,11 +27,6 @@ def _is_escape_hatch_enabled() -> bool:
|
||||
return os.environ.get(_UNSAFE_PATHS_ENV, "").lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# File path validation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def validate_file_path(path: str, base_dir: str | None = None) -> str:
|
||||
"""Validate that a file path is safe to read.
|
||||
|
||||
@@ -101,10 +96,6 @@ def validate_directory_path(path: str, base_dir: str | None = None) -> str:
|
||||
return validated
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# URL validation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Private and reserved IP ranges that should not be accessed
|
||||
_BLOCKED_IPV4_NETWORKS = [
|
||||
ipaddress.ip_network("10.0.0.0/8"),
|
||||
@@ -185,7 +176,6 @@ def validate_url(url: str) -> str:
|
||||
if not parsed.hostname:
|
||||
raise ValueError(f"URL has no hostname: '{url}'")
|
||||
|
||||
# Resolve DNS and check IPs
|
||||
try:
|
||||
addrinfos = socket.getaddrinfo(
|
||||
parsed.hostname, parsed.port or (443 if parsed.scheme == "https" else 80)
|
||||
|
||||
@@ -62,7 +62,6 @@ class AIMindTool(BaseTool):
|
||||
|
||||
minds_client = Client(api_key=self.api_key)
|
||||
|
||||
# Convert the datasources to DatabaseConfig objects.
|
||||
datasources = []
|
||||
for datasource in self.datasources:
|
||||
config = DatabaseConfig(
|
||||
@@ -74,7 +73,6 @@ class AIMindTool(BaseTool):
|
||||
)
|
||||
datasources.append(config)
|
||||
|
||||
# Generate a random name for the Mind.
|
||||
name = f"{AIMindToolConstants.MIND_NAME_PREFIX}_{secrets.token_hex(5)}"
|
||||
|
||||
mind = minds_client.minds.create(
|
||||
@@ -84,7 +82,6 @@ class AIMindTool(BaseTool):
|
||||
self.mind_name = mind.name
|
||||
|
||||
def _run(self, query: str) -> str | None:
|
||||
# Run the query on the AI-Mind.
|
||||
# The Minds API is OpenAI compatible and therefore, the OpenAI client can be used.
|
||||
openai_client = OpenAI(
|
||||
base_url=AIMindToolConstants.MINDS_API_BASE_URL, api_key=self.api_key
|
||||
|
||||
@@ -186,7 +186,6 @@ class BraveSearchToolBase(BaseTool, ABC):
|
||||
for attempt in range(_max_retries):
|
||||
self._rate_limit()
|
||||
|
||||
# Make the request
|
||||
try:
|
||||
resp = requests.get(
|
||||
self.search_url,
|
||||
@@ -203,7 +202,6 @@ class BraveSearchToolBase(BaseTool, ABC):
|
||||
f"Brave Search API request timed out after {self._timeout}s: {exc}"
|
||||
) from exc
|
||||
|
||||
# Log the rate limit headers and request details
|
||||
logger.debug(
|
||||
"Brave Search API request: %s %s -> %d",
|
||||
"GET",
|
||||
@@ -251,7 +249,6 @@ class BraveSearchToolBase(BaseTool, ABC):
|
||||
|
||||
params = self._common_payload_refinement(params)
|
||||
|
||||
# Validate only schema fields
|
||||
schema_keys = self.args_schema.model_fields
|
||||
payload_in = {k: v for k, v in params.items() if k in schema_keys}
|
||||
|
||||
@@ -301,7 +298,6 @@ class BraveSearchToolBase(BaseTool, ABC):
|
||||
if k not in fields or fields[k].is_required() or v not in self._EMPTY_VALUES
|
||||
}
|
||||
|
||||
# Make sure params has "q" for query instead of "query" or "search_query"
|
||||
query = params.get("query") or params.get("search_query")
|
||||
if query is not None and "q" not in params:
|
||||
params["q"] = query
|
||||
|
||||
@@ -27,7 +27,6 @@ class BraveImageSearchTool(BraveSearchToolBase):
|
||||
return params
|
||||
|
||||
def _refine_response(self, response: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
# Make the response more concise, and easier to consume
|
||||
results = response.get("results", [])
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -27,7 +27,6 @@ class BraveNewsSearchTool(BraveSearchToolBase):
|
||||
return params
|
||||
|
||||
def _refine_response(self, response: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
# Make the response more concise, and easier to consume
|
||||
results = response.get("results", [])
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -68,7 +68,6 @@ class BraveSearchTool(BaseTool):
|
||||
)
|
||||
BraveSearchTool._last_request_time = time.time()
|
||||
|
||||
# Construct and send the request
|
||||
try:
|
||||
# Fallback to "query" or "search_query" for backwards compatibility
|
||||
query = kwargs.get("q") or kwargs.get("query") or kwargs.get("search_query")
|
||||
@@ -123,11 +122,9 @@ class BraveSearchTool(BaseTool):
|
||||
payload["operators"] = operators
|
||||
|
||||
# Limit the result types to "web" since there is presently no
|
||||
# handling of other types like "discussions", "faq", "infobox",
|
||||
# "news", "videos", or "locations".
|
||||
payload["result_filter"] = "web"
|
||||
|
||||
# Setup Request Headers
|
||||
headers = {
|
||||
"X-Subscription-Token": os.environ["BRAVE_API_KEY"],
|
||||
"Accept": "application/json",
|
||||
@@ -136,7 +133,7 @@ class BraveSearchTool(BaseTool):
|
||||
response = requests.get(
|
||||
self.search_url, headers=headers, params=payload, timeout=30
|
||||
)
|
||||
response.raise_for_status() # Handle non-200 responses
|
||||
response.raise_for_status()
|
||||
results = response.json()
|
||||
|
||||
# TODO: Handle other result types like "discussions", "faq", etc.
|
||||
|
||||
@@ -27,7 +27,6 @@ class BraveVideoSearchTool(BraveSearchToolBase):
|
||||
return params
|
||||
|
||||
def _refine_response(self, response: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
# Make the response more concise, and easier to consume
|
||||
results = response.get("results", [])
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -496,7 +496,6 @@ class BrightDataDatasetTool(BaseTool):
|
||||
)
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Step 1: Trigger job
|
||||
async with session.post(
|
||||
f"{BRIGHTDATA_API_URL}/datasets/v3/trigger",
|
||||
params={"dataset_id": dataset_id, "include_errors": "true"},
|
||||
@@ -511,7 +510,6 @@ class BrightDataDatasetTool(BaseTool):
|
||||
trigger_data = await trigger_response.json()
|
||||
snapshot_id = trigger_data.get("snapshot_id")
|
||||
|
||||
# Step 2: Poll for completion
|
||||
elapsed = 0
|
||||
while elapsed < timeout:
|
||||
await asyncio.sleep(polling_interval)
|
||||
@@ -536,7 +534,6 @@ class BrightDataDatasetTool(BaseTool):
|
||||
else:
|
||||
raise TimeoutError("Polling timed out before job completed.")
|
||||
|
||||
# Step 3: Retrieve result
|
||||
async with session.get(
|
||||
f"{BRIGHTDATA_API_URL}/datasets/v3/snapshot/{snapshot_id}",
|
||||
params={"format": output_format},
|
||||
|
||||
@@ -173,15 +173,12 @@ class BrightDataSearchTool(BaseTool):
|
||||
)
|
||||
results_count = kwargs.get("results_count", "10")
|
||||
|
||||
# Validate required parameters
|
||||
if not query:
|
||||
raise ValueError("query is required either in constructor or method call")
|
||||
|
||||
# Build the search URL
|
||||
query = urllib.parse.quote(query)
|
||||
url = self.get_search_url(search_engine, query)
|
||||
|
||||
# Add parameters to the URL
|
||||
params = []
|
||||
|
||||
if country:
|
||||
@@ -214,7 +211,6 @@ class BrightDataSearchTool(BaseTool):
|
||||
if params:
|
||||
url += "&" + "&".join(params)
|
||||
|
||||
# Set up the API request parameters
|
||||
request_params = {"zone": self.zone, "url": url, "format": "raw"}
|
||||
|
||||
request_params = {k: v for k, v in request_params.items() if v is not None}
|
||||
|
||||
@@ -53,7 +53,6 @@ class ContextualAICreateAgentTool(BaseTool):
|
||||
try:
|
||||
import os
|
||||
|
||||
# Create datastore
|
||||
datastore = self.contextual_client.datastores.create(name=datastore_name)
|
||||
datastore_id = datastore.id
|
||||
|
||||
@@ -71,7 +70,6 @@ class ContextualAICreateAgentTool(BaseTool):
|
||||
)
|
||||
document_ids.append(ingestion_result.id)
|
||||
|
||||
# Create agent
|
||||
agent = self.contextual_client.agents.create(
|
||||
name=agent_name,
|
||||
description=agent_description,
|
||||
|
||||
@@ -96,7 +96,6 @@ class ContextualAIParseTool(BaseTool):
|
||||
|
||||
sleep(5)
|
||||
|
||||
# Get parse results
|
||||
results_url = f"{base_url}/parse/jobs/{job_id}/results"
|
||||
result = requests.get(
|
||||
results_url,
|
||||
|
||||
@@ -84,22 +84,18 @@ class CouchbaseFTSVectorSearchTool(BaseTool):
|
||||
"""
|
||||
scope_collection_map: dict[str, Any] = {}
|
||||
|
||||
# Get a list of all scopes in the bucket
|
||||
for scope in self._bucket.collections().get_all_scopes():
|
||||
scope_collection_map[scope.name] = []
|
||||
|
||||
# Get a list of all the collections in the scope
|
||||
for collection in scope.collections:
|
||||
scope_collection_map[scope.name].append(collection.name)
|
||||
|
||||
# Check if the scope exists
|
||||
if self.scope_name not in scope_collection_map:
|
||||
raise ValueError(
|
||||
f"Scope {self.scope_name} not found in Couchbase "
|
||||
f"bucket {self.bucket_name}"
|
||||
)
|
||||
|
||||
# Check if the collection exists in the scope
|
||||
if self.collection_name not in scope_collection_map[self.scope_name]:
|
||||
raise ValueError(
|
||||
f"Collection {self.collection_name} not found in scope "
|
||||
@@ -162,7 +158,6 @@ class CouchbaseFTSVectorSearchTool(BaseTool):
|
||||
"Please check the connection and credentials"
|
||||
) from e
|
||||
|
||||
# check if bucket exists
|
||||
if not self._check_bucket_exists():
|
||||
raise ValueError(
|
||||
f"Bucket {self.bucket_name} does not exist. "
|
||||
|
||||
@@ -172,13 +172,11 @@ class DatabricksQueryTool(BaseTool):
|
||||
if not results:
|
||||
return "Query returned no results."
|
||||
|
||||
# Get column names from the first row
|
||||
if not results[0]:
|
||||
return "Query returned empty rows with no columns."
|
||||
|
||||
columns = list(results[0].keys())
|
||||
|
||||
# If we have rows but they're all empty, handle that case
|
||||
if not columns:
|
||||
return "Query returned rows but with no column data."
|
||||
|
||||
@@ -186,19 +184,14 @@ class DatabricksQueryTool(BaseTool):
|
||||
col_widths = {col: len(col) for col in columns}
|
||||
for row in results:
|
||||
for col in columns:
|
||||
# Convert value to string and get its length
|
||||
# Handle None values gracefully
|
||||
value_str = str(row[col]) if row[col] is not None else "NULL"
|
||||
col_widths[col] = max(col_widths[col], len(value_str))
|
||||
|
||||
# Create header row
|
||||
header = " | ".join(f"{col:{col_widths[col]}}" for col in columns)
|
||||
separator = "-+-".join("-" * col_widths[col] for col in columns)
|
||||
|
||||
# Format data rows
|
||||
data_rows = []
|
||||
for row in results:
|
||||
# Handle None values by displaying "NULL"
|
||||
row_values = {
|
||||
col: str(row[col]) if row[col] is not None else "NULL"
|
||||
for col in columns
|
||||
@@ -208,7 +201,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
)
|
||||
data_rows.append(data_row)
|
||||
|
||||
# Add row count information
|
||||
result_info = f"({len(results)} row{'s' if len(results) != 1 else ''} returned)"
|
||||
|
||||
# Combine all parts
|
||||
@@ -231,14 +223,12 @@ class DatabricksQueryTool(BaseTool):
|
||||
str: Formatted query results
|
||||
"""
|
||||
try:
|
||||
# Get parameters with fallbacks to default values
|
||||
query = kwargs.get("query")
|
||||
catalog = kwargs.get("catalog") or self.default_catalog
|
||||
db_schema = kwargs.get("db_schema") or self.default_schema
|
||||
warehouse_id = kwargs.get("warehouse_id") or self.default_warehouse_id
|
||||
row_limit = kwargs.get("row_limit", 1000)
|
||||
|
||||
# Validate schema and query
|
||||
validated_input = DatabricksQueryToolSchema(
|
||||
query=query,
|
||||
catalog=catalog,
|
||||
@@ -247,7 +237,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
row_limit=row_limit,
|
||||
)
|
||||
|
||||
# Extract validated parameters
|
||||
query = validated_input.query
|
||||
catalog = validated_input.catalog
|
||||
db_schema = validated_input.db_schema
|
||||
@@ -256,26 +245,21 @@ class DatabricksQueryTool(BaseTool):
|
||||
if warehouse_id is None:
|
||||
return "SQL warehouse ID must be provided either as a parameter or as a default."
|
||||
|
||||
# Setup SQL context with catalog/schema if provided
|
||||
|
||||
context: ExecutionContext = {}
|
||||
if catalog:
|
||||
context["catalog"] = catalog
|
||||
if db_schema:
|
||||
context["schema"] = db_schema
|
||||
|
||||
# Execute query
|
||||
statement = self.workspace_client.statement_execution
|
||||
|
||||
try:
|
||||
# Execute the statement
|
||||
execution = statement.execute_statement(
|
||||
warehouse_id=warehouse_id, statement=query, **context
|
||||
)
|
||||
|
||||
statement_id = execution.statement_id
|
||||
except Exception as execute_error:
|
||||
# Handle immediate execution errors
|
||||
return f"Error starting query execution: {execute_error!s}"
|
||||
|
||||
# Poll for results with better error handling
|
||||
@@ -284,7 +268,7 @@ class DatabricksQueryTool(BaseTool):
|
||||
timeout = 300 # 5 minutes timeout
|
||||
start_time = time.time()
|
||||
poll_count = 0
|
||||
previous_state = None # Track previous state to detect changes
|
||||
previous_state = None
|
||||
|
||||
if statement_id is None:
|
||||
return "Failed to retrieve statement ID after execution."
|
||||
@@ -292,27 +276,21 @@ class DatabricksQueryTool(BaseTool):
|
||||
while time.time() - start_time < timeout:
|
||||
poll_count += 1
|
||||
try:
|
||||
# Get statement status
|
||||
result = statement.get_statement(statement_id)
|
||||
|
||||
# Check if finished - be very explicit about state checking
|
||||
if hasattr(result, "status") and hasattr(result.status, "state"):
|
||||
state_value = str(
|
||||
result.status.state # type: ignore[union-attr]
|
||||
) # Convert to string to handle both string and enum
|
||||
|
||||
# Track state changes for debugging
|
||||
if previous_state != state_value:
|
||||
previous_state = state_value
|
||||
|
||||
# Check if state indicates completion
|
||||
if "SUCCEEDED" in state_value:
|
||||
break
|
||||
if "FAILED" in state_value:
|
||||
# Extract error message with more robust handling
|
||||
error_info = "No detailed error info"
|
||||
try:
|
||||
# First try direct access to error.message
|
||||
if (
|
||||
hasattr(result.status, "error")
|
||||
and result.status.error # type: ignore[union-attr]
|
||||
@@ -322,16 +300,13 @@ class DatabricksQueryTool(BaseTool):
|
||||
# Some APIs may have a different structure
|
||||
elif hasattr(result.status.error, "error_message"): # type: ignore[union-attr]
|
||||
error_info = result.status.error.error_message # type: ignore[union-attr]
|
||||
# Last resort, try to convert the whole error object to string
|
||||
else:
|
||||
error_info = str(result.status.error) # type: ignore[union-attr]
|
||||
except Exception as err_extract_error:
|
||||
# If all else fails, try to get any info we can
|
||||
error_info = (
|
||||
f"Error details unavailable: {err_extract_error!s}"
|
||||
)
|
||||
|
||||
# Return immediately on first FAILED state detection
|
||||
return f"Query execution failed: {error_info}"
|
||||
if "CANCELED" in state_value:
|
||||
return "Query was canceled"
|
||||
@@ -341,17 +316,14 @@ class DatabricksQueryTool(BaseTool):
|
||||
if poll_count > 3:
|
||||
return f"Error checking query status: {poll_error!s}"
|
||||
|
||||
# Wait before polling again
|
||||
time.sleep(2)
|
||||
|
||||
# Check if we timed out
|
||||
if result is None:
|
||||
return "Query returned no result (likely timed out or failed)"
|
||||
|
||||
if not hasattr(result, "status") or not hasattr(result.status, "state"):
|
||||
return "Query completed but returned an invalid result structure"
|
||||
|
||||
# Convert state to string for comparison
|
||||
state_value = str(result.status.state) # type: ignore[union-attr]
|
||||
if not any(
|
||||
state in state_value for state in ["SUCCEEDED", "FAILED", "CANCELED"]
|
||||
@@ -372,7 +344,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
|
||||
if has_schema and has_result:
|
||||
try:
|
||||
# Get schema for column names
|
||||
columns = [col.name for col in result.manifest.schema.columns] # type: ignore[union-attr]
|
||||
|
||||
# Debug info for schema
|
||||
@@ -382,16 +353,13 @@ class DatabricksQueryTool(BaseTool):
|
||||
|
||||
# Dump the raw structure of result data to help troubleshoot
|
||||
if _has_data_array(result):
|
||||
# Add defensive check for None data_array
|
||||
if result.result.data_array is None:
|
||||
# Return empty result handling rather than trying to process null data
|
||||
return "Query executed successfully (no data returned)"
|
||||
|
||||
# IMPROVED DETECTION LOGIC: Check if we're possibly dealing with rows where each item
|
||||
# contains a single value or character (which could indicate incorrect row structure)
|
||||
is_likely_incorrect_row_structure = False
|
||||
|
||||
# Only try to analyze sample if data_array exists and has content
|
||||
if (
|
||||
_has_data_array(result)
|
||||
and len(result.result.data_array) > 0
|
||||
@@ -421,7 +389,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
single_digit_count += 1
|
||||
|
||||
# If a significant portion of the first values are single characters or digits,
|
||||
# this likely indicates data is being incorrectly structured
|
||||
if (
|
||||
total_items > 0
|
||||
and (single_char_count + single_digit_count)
|
||||
@@ -465,14 +432,12 @@ class DatabricksQueryTool(BaseTool):
|
||||
else:
|
||||
needs_special_string_handling = False
|
||||
|
||||
# Process results differently based on detection
|
||||
if (
|
||||
"needs_special_string_handling" in locals()
|
||||
and needs_special_string_handling
|
||||
):
|
||||
# We're dealing with data where the rows may be incorrectly structured
|
||||
|
||||
# Collect all values into a flat list
|
||||
all_values: list[Any] = []
|
||||
if (
|
||||
hasattr(result.result, "data_array")
|
||||
@@ -486,10 +451,8 @@ class DatabricksQueryTool(BaseTool):
|
||||
else:
|
||||
all_values.append(item)
|
||||
|
||||
# Get the expected column count from schema
|
||||
expected_column_count = len(columns)
|
||||
|
||||
# Try to reconstruct rows using pattern recognition
|
||||
reconstructed_rows = []
|
||||
|
||||
# PATTERN RECOGNITION APPROACH
|
||||
@@ -509,7 +472,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
# This value looks like an ID, might be the start of a row
|
||||
if i < len(all_values) - 1:
|
||||
next_few_values = all_values[i + 1 : i + 5]
|
||||
# If following values look like they could be part of a title
|
||||
if any(
|
||||
isinstance(v, str) and len(v) > 1
|
||||
for v in next_few_values
|
||||
@@ -517,7 +479,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
id_indices.append(i)
|
||||
|
||||
if id_indices:
|
||||
# If we found potential row starts, use them to extract rows
|
||||
for i in range(len(id_indices)):
|
||||
start_idx = id_indices[i]
|
||||
end_idx = (
|
||||
@@ -526,7 +487,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
else len(all_values)
|
||||
)
|
||||
|
||||
# Extract values for this row
|
||||
row_values = all_values[start_idx:end_idx]
|
||||
|
||||
# Special handling for Netflix title data
|
||||
@@ -535,9 +495,7 @@ class DatabricksQueryTool(BaseTool):
|
||||
"Title" in columns
|
||||
and len(row_values) > expected_column_count
|
||||
):
|
||||
# Try to reconstruct by looking for patterns
|
||||
# We know ID is first, then Title (which may be split)
|
||||
# Then other fields like Genre, etc.
|
||||
|
||||
# Take first value as ID
|
||||
row_dict = {columns[0]: row_values[0]}
|
||||
@@ -546,7 +504,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
title_end_idx = 1
|
||||
for j in range(2, min(100, len(row_values))):
|
||||
val = row_values[j]
|
||||
# Check for common genres or non-title markers
|
||||
if isinstance(val, str) and val in [
|
||||
"Comedy",
|
||||
"Drama",
|
||||
@@ -562,7 +519,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
# Reconstruct title from individual characters
|
||||
if title_end_idx > 1:
|
||||
title_chars = row_values[1:title_end_idx]
|
||||
# Check if they're individual characters
|
||||
if all(
|
||||
isinstance(c, str) and len(c) == 1
|
||||
for c in title_chars
|
||||
@@ -607,24 +563,21 @@ class DatabricksQueryTool(BaseTool):
|
||||
)
|
||||
|
||||
if title_idx >= 0:
|
||||
# Try to detect if title is split across multiple values
|
||||
i = 0
|
||||
while i < len(all_values):
|
||||
# Check if this could be an ID (start of a row)
|
||||
if isinstance(
|
||||
all_values[i], str
|
||||
) and id_pattern.match(all_values[i]):
|
||||
row_dict = {columns[0]: all_values[i]}
|
||||
i += 1
|
||||
|
||||
# Try to reconstruct title if it appears to be split
|
||||
title_chars = []
|
||||
while (
|
||||
i < len(all_values)
|
||||
and isinstance(all_values[i], str)
|
||||
and len(all_values[i]) <= 1
|
||||
and len(title_chars) < 100
|
||||
): # Cap title length
|
||||
):
|
||||
title_chars.append(all_values[i])
|
||||
i += 1
|
||||
|
||||
@@ -633,7 +586,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
title_chars
|
||||
)
|
||||
|
||||
# Add remaining fields
|
||||
for j in range(title_idx + 1, len(columns)):
|
||||
if i < len(all_values):
|
||||
row_dict[columns[j]] = all_values[i]
|
||||
@@ -655,7 +607,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
]
|
||||
|
||||
for chunk in chunks:
|
||||
# Skip chunks that seem to be partial/incomplete rows
|
||||
if (
|
||||
len(chunk) < expected_column_count * 0.75
|
||||
): # Allow for some missing values
|
||||
@@ -663,7 +614,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
|
||||
row_dict = {}
|
||||
|
||||
# Map values to column names
|
||||
for i, col in enumerate(columns):
|
||||
if i < len(chunk):
|
||||
row_dict[col] = chunk[i]
|
||||
@@ -672,7 +622,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
|
||||
reconstructed_rows.append(row_dict)
|
||||
|
||||
# Apply post-processing to fix known issues
|
||||
if reconstructed_rows and "Title" in columns:
|
||||
for row in reconstructed_rows:
|
||||
# Fix titles that might still have issues
|
||||
@@ -680,7 +629,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
isinstance(row.get("Title"), str)
|
||||
and len(row.get("Title")) <= 1 # type: ignore[arg-type]
|
||||
):
|
||||
# This is likely still a fragmented title - mark as potentially incomplete
|
||||
row["Title"] = f"[INCOMPLETE] {row.get('Title')}"
|
||||
|
||||
# Ensure we respect the row limit
|
||||
@@ -689,18 +637,13 @@ class DatabricksQueryTool(BaseTool):
|
||||
|
||||
chunk_results = reconstructed_rows
|
||||
else:
|
||||
# Process normal result structure as before
|
||||
|
||||
# Check different result structures
|
||||
if (
|
||||
hasattr(result.result, "data_array")
|
||||
and result.result.data_array # type: ignore[union-attr]
|
||||
):
|
||||
# Check if data appears to be malformed within chunks
|
||||
for _chunk_idx, chunk in enumerate(
|
||||
result.result.data_array # type: ignore[union-attr]
|
||||
):
|
||||
# Check if chunk might actually contain individual columns of a single row
|
||||
# This is another way data might be malformed - check the first few values
|
||||
if len(chunk) > 0 and len(columns) > 1:
|
||||
# If there seems to be a mismatch between chunk structure and expected columns
|
||||
@@ -714,10 +657,9 @@ class DatabricksQueryTool(BaseTool):
|
||||
len(chunk) > len(columns) * 3
|
||||
): # Heuristic: if chunk has way more items than columns
|
||||
# This chunk might actually be values of multiple rows - try to reconstruct
|
||||
values = chunk # All values in this chunk
|
||||
values = chunk
|
||||
reconstructed_rows = []
|
||||
|
||||
# Try to create rows based on expected column count
|
||||
for i in range(
|
||||
0, len(values), len(columns)
|
||||
):
|
||||
@@ -739,7 +681,7 @@ class DatabricksQueryTool(BaseTool):
|
||||
|
||||
if reconstructed_rows:
|
||||
chunk_results.extend(reconstructed_rows)
|
||||
continue # Skip normal processing for this chunk
|
||||
continue
|
||||
|
||||
# Special case: when chunk contains exactly the right number of values for a single row
|
||||
# This handles the case where instead of a list of rows, we just got all values in a flat list
|
||||
@@ -752,12 +694,9 @@ class DatabricksQueryTool(BaseTool):
|
||||
len(chunk) > 0
|
||||
and len(chunk) % len(columns) == 0
|
||||
):
|
||||
# Process flat list of values as rows
|
||||
for i in range(0, len(chunk), len(columns)):
|
||||
row_values = chunk[i : i + len(columns)]
|
||||
if len(row_values) == len(
|
||||
columns
|
||||
): # Only process complete rows
|
||||
if len(row_values) == len(columns):
|
||||
row_dict = {
|
||||
col: val
|
||||
for col, val in zip(
|
||||
@@ -768,25 +707,19 @@ class DatabricksQueryTool(BaseTool):
|
||||
}
|
||||
chunk_results.append(row_dict)
|
||||
|
||||
# Skip regular row processing for this chunk
|
||||
continue
|
||||
|
||||
# Normal processing for typical row structure
|
||||
for _row_idx, row in enumerate(chunk):
|
||||
# Ensure row is actually a collection of values
|
||||
if not isinstance(row, (list, tuple, dict)):
|
||||
# This might be a single value; skip it or handle specially
|
||||
continue
|
||||
|
||||
# Convert each row to a dictionary with column names as keys
|
||||
row_dict = {}
|
||||
|
||||
# Handle dict rows directly
|
||||
if isinstance(row, dict):
|
||||
# Use the existing column mapping
|
||||
row_dict = dict(row)
|
||||
elif isinstance(row, (list, tuple)):
|
||||
# Map list of values to columns
|
||||
for i, val in enumerate(row):
|
||||
if (
|
||||
i < len(columns)
|
||||
@@ -798,7 +731,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
row_dict[dynamic_col] = val
|
||||
all_columns.add(dynamic_col)
|
||||
|
||||
# If we have fewer values than columns, set missing values to None
|
||||
for col in columns:
|
||||
if col not in row_dict:
|
||||
row_dict[col] = None
|
||||
@@ -824,7 +756,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
row_dict[dynamic_col] = val
|
||||
all_columns.add(dynamic_col)
|
||||
|
||||
# If we have fewer values than columns, set missing values to None
|
||||
for i, col in enumerate(columns):
|
||||
if i >= len(row):
|
||||
row_dict[col] = None
|
||||
@@ -840,7 +771,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
}
|
||||
normalized_results.append(normalized_row)
|
||||
|
||||
# Replace the original results with normalized ones
|
||||
chunk_results = normalized_results
|
||||
|
||||
except Exception as results_error:
|
||||
@@ -856,7 +786,6 @@ class DatabricksQueryTool(BaseTool):
|
||||
if "SUCCEEDED" in state_value:
|
||||
return "Query executed successfully (no results to display)"
|
||||
|
||||
# Format and return results
|
||||
return self._format_results(chunk_results) # type: ignore[arg-type]
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -37,11 +37,9 @@ class FileWriterTool(BaseTool):
|
||||
filepath = os.path.join(directory, filename)
|
||||
|
||||
# Prevent path traversal: the resolved path must be strictly inside
|
||||
# the resolved directory. This blocks ../sequences, absolute paths in
|
||||
# filename, and symlink escapes regardless of how directory is set.
|
||||
# is_relative_to() does a proper path-component comparison that is
|
||||
# safe on case-insensitive filesystems and avoids the "// " edge case
|
||||
# that plagues startswith(real_directory + os.sep).
|
||||
# We also reject the case where filepath resolves to the directory
|
||||
# itself, since that is not a valid file target.
|
||||
real_directory = Path(directory).resolve()
|
||||
|
||||
@@ -93,11 +93,9 @@ class FileCompressorTool(BaseTool):
|
||||
def _generate_output_path(input_path: str, format: str) -> str:
|
||||
"""Generates output path based on input path and format."""
|
||||
if os.path.isfile(input_path):
|
||||
base_name = os.path.splitext(os.path.basename(input_path))[
|
||||
0
|
||||
] # Remove extension
|
||||
base_name = os.path.splitext(os.path.basename(input_path))[0]
|
||||
else:
|
||||
base_name = os.path.basename(os.path.normpath(input_path)) # Directory name
|
||||
base_name = os.path.basename(os.path.normpath(input_path))
|
||||
return os.path.join(os.getcwd(), f"{base_name}.{format}")
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -57,7 +57,7 @@ class FirecrawlScrapeWebsiteTool(BaseTool):
|
||||
"only_main_content": True,
|
||||
"include_tags": [],
|
||||
"exclude_tags": [],
|
||||
"max_age": 172800000, # 2 days cache
|
||||
"max_age": 172800000,
|
||||
"headers": {},
|
||||
"wait_for": 0,
|
||||
"mobile": False,
|
||||
|
||||
@@ -67,7 +67,7 @@ class InvokeCrewAIAutomationTool(BaseTool):
|
||||
|
||||
crew_api_url: str
|
||||
crew_bearer_token: str
|
||||
max_polling_time: int = 10 * 60 # 10 minutes
|
||||
max_polling_time: int = 10 * 60
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -88,12 +88,9 @@ class InvokeCrewAIAutomationTool(BaseTool):
|
||||
max_polling_time: Maximum time in seconds to wait for task completion (default: 600 seconds = 10 minutes)
|
||||
crew_inputs: Optional dictionary defining custom input schema fields
|
||||
"""
|
||||
# Create dynamic args_schema if custom inputs provided
|
||||
if crew_inputs:
|
||||
# Start with the base prompt field
|
||||
fields = {}
|
||||
|
||||
# Add custom fields
|
||||
for field_name, field_def in crew_inputs.items():
|
||||
if isinstance(field_def, tuple):
|
||||
fields[field_name] = field_def
|
||||
@@ -101,12 +98,10 @@ class InvokeCrewAIAutomationTool(BaseTool):
|
||||
# Assume it's a Field object, extract type from annotation if available
|
||||
fields[field_name] = (str, field_def)
|
||||
|
||||
# Create dynamic model
|
||||
args_schema = create_model("DynamicInvokeCrewAIAutomationInput", **fields) # type: ignore[call-overload]
|
||||
else:
|
||||
args_schema = InvokeCrewAIAutomationInput
|
||||
|
||||
# Initialize the parent class with proper field values
|
||||
super().__init__(
|
||||
name=crew_name,
|
||||
description=crew_description,
|
||||
@@ -162,7 +157,6 @@ class InvokeCrewAIAutomationTool(BaseTool):
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
|
||||
# Start the crew
|
||||
response = self._kickoff_crew(inputs=kwargs)
|
||||
kickoff_id: str | None = response.get("kickoff_id")
|
||||
|
||||
@@ -178,7 +172,7 @@ class InvokeCrewAIAutomationTool(BaseTool):
|
||||
if status_response.get("state", "").lower() == "failed":
|
||||
return f"Error: Crew task failed. Response: {status_response}"
|
||||
except Exception as e:
|
||||
if i == self.max_polling_time - 1: # Last attempt
|
||||
if i == self.max_polling_time - 1:
|
||||
return f"Error: Failed to get crew status after {self.max_polling_time} attempts. Last error: {e}"
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
@@ -91,7 +91,6 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
if params:
|
||||
payload["params"] = params
|
||||
|
||||
# Log the full payload for debugging
|
||||
logger.debug(f"MCP Request to {url}: {json.dumps(payload, indent=2)}")
|
||||
|
||||
try:
|
||||
@@ -99,7 +98,6 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
# Handle JSON-RPC error responses
|
||||
if "error" in result:
|
||||
error_msg = result["error"].get("message", "Unknown error")
|
||||
error_code = result["error"].get("code", -1)
|
||||
@@ -119,20 +117,16 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
def _run(self, **kwargs: Any) -> Any:
|
||||
"""Execute the Agent Handler tool with the given arguments."""
|
||||
try:
|
||||
# Log what we're about to send
|
||||
logger.info(f"Executing {self.tool_name} with arguments: {kwargs}")
|
||||
|
||||
# Make the tool call via MCP
|
||||
result = self._make_mcp_request(
|
||||
method="tools/call",
|
||||
params={"name": self.tool_name, "arguments": kwargs},
|
||||
)
|
||||
|
||||
# Extract the actual result from the MCP response
|
||||
if "result" in result and "content" in result["result"]:
|
||||
content = result["result"]["content"]
|
||||
if content and len(content) > 0:
|
||||
# Parse the text content (it's JSON-encoded)
|
||||
text_content = content[0].get("text", "")
|
||||
try:
|
||||
return json.loads(text_content)
|
||||
@@ -176,10 +170,8 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
... registered_user_id="91b2b905-e866-40c8-8be2-efe53827a0aa",
|
||||
... )
|
||||
"""
|
||||
# Create an empty args schema model (proper BaseModel subclass)
|
||||
empty_args_schema = create_model(f"{tool_name.replace('__', '_').title()}Args")
|
||||
|
||||
# Initialize session and get tool schema
|
||||
instance = cls(
|
||||
name=tool_name,
|
||||
description=f"Execute {tool_name} via Agent Handler",
|
||||
@@ -191,7 +183,6 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Try to fetch the actual tool schema from Agent Handler
|
||||
try:
|
||||
result = instance._make_mcp_request(method="tools/list")
|
||||
if "result" in result and "tools" in result["result"]:
|
||||
@@ -222,7 +213,6 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
field_type: Any = Any
|
||||
field_default: Any = ...
|
||||
|
||||
# Map JSON schema types to Python types
|
||||
json_type = field_schema.get("type", "string")
|
||||
if json_type == "string":
|
||||
field_type = str
|
||||
@@ -237,7 +227,6 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
elif json_type == "object":
|
||||
field_type = dict[str, Any]
|
||||
|
||||
# Make field optional if not required
|
||||
if field_name not in required:
|
||||
field_type = field_type | None
|
||||
field_default = None
|
||||
@@ -303,7 +292,6 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
... tool_names=["linear__create_issue", "linear__get_issues"],
|
||||
... )
|
||||
"""
|
||||
# Create a temporary instance to fetch the tool list
|
||||
temp_instance = cls(
|
||||
name="temp",
|
||||
description="temp",
|
||||
@@ -315,7 +303,6 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
)
|
||||
|
||||
try:
|
||||
# Fetch available tools
|
||||
result = temp_instance._make_mcp_request(method="tools/list")
|
||||
|
||||
if "result" not in result or "tools" not in result["result"]:
|
||||
@@ -325,13 +312,11 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
|
||||
available_tools = result["result"]["tools"]
|
||||
|
||||
# Filter tools if specific names were requested
|
||||
if tool_names:
|
||||
available_tools = [
|
||||
t for t in available_tools if t.get("name") in tool_names
|
||||
]
|
||||
|
||||
# Check if all requested tools were found
|
||||
found_names = {t.get("name") for t in available_tools}
|
||||
missing_names = set(tool_names) - found_names
|
||||
if missing_names:
|
||||
@@ -339,7 +324,6 @@ class MergeAgentHandlerTool(BaseTool):
|
||||
f"The following tools were not found in the Tool Pack: {missing_names}"
|
||||
)
|
||||
|
||||
# Create tool instances
|
||||
tools = []
|
||||
for tool_schema in available_tools:
|
||||
tool_name = tool_schema.get("name")
|
||||
|
||||
@@ -260,7 +260,6 @@ class MongoDBVectorSearchTool(BaseTool):
|
||||
)
|
||||
]
|
||||
operations = [ReplaceOne({"_id": doc["_id"]}, doc, upsert=True) for doc in docs]
|
||||
# insert the documents in MongoDB Atlas
|
||||
result = self._coll.bulk_write(operations)
|
||||
if result.upserted_ids is None:
|
||||
raise ValueError("No documents were inserted.")
|
||||
@@ -277,7 +276,6 @@ class MongoDBVectorSearchTool(BaseTool):
|
||||
include_embeddings = query_config.include_embeddings
|
||||
post_filter_pipeline = query_config.post_filter_pipeline
|
||||
|
||||
# Create the embedding for the query
|
||||
query_vector = self._embed_texts([query])[0]
|
||||
|
||||
# Atlas Vector Search, potentially with filter
|
||||
@@ -296,7 +294,6 @@ class MongoDBVectorSearchTool(BaseTool):
|
||||
{"$set": {"score": {"$meta": "vectorSearchScore"}}},
|
||||
]
|
||||
|
||||
# Remove embeddings unless requested
|
||||
if not include_embeddings:
|
||||
pipeline.append({"$project": {self.embedding_key: 0}})
|
||||
|
||||
@@ -308,7 +305,6 @@ class MongoDBVectorSearchTool(BaseTool):
|
||||
cursor = self._coll.aggregate(pipeline) # type: ignore[arg-type]
|
||||
docs = []
|
||||
|
||||
# Format
|
||||
for doc in cursor:
|
||||
docs.append(doc) # noqa: PERF402
|
||||
return json_util.dumps(docs)
|
||||
|
||||
@@ -8,7 +8,6 @@ os.environ["OPENAI_API_KEY"] = "Your Key"
|
||||
|
||||
multion_browse_tool = MultiOnTool(api_key="Your Key")
|
||||
|
||||
# Create a new agent
|
||||
Browser = Agent(
|
||||
role="Browser Agent",
|
||||
goal="control web browsers using natural language ",
|
||||
@@ -17,7 +16,6 @@ Browser = Agent(
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
# Define tasks
|
||||
browse = Task(
|
||||
description="Summarize the top 3 trending AI News headlines",
|
||||
expected_output="A summary of the top 3 trending AI News headlines",
|
||||
|
||||
@@ -80,7 +80,6 @@ _AS_PAREN_RE = re.compile(r"\bAS\s*\(", re.IGNORECASE)
|
||||
|
||||
def _iter_as_paren_matches(stmt: str) -> Iterator[re.Match[str]]:
|
||||
"""Yield regex matches for ``AS\\s*(`` outside of string literals."""
|
||||
# Build a set of character positions that are inside string literals.
|
||||
in_string: set[int] = set()
|
||||
i = 0
|
||||
while i < len(stmt):
|
||||
@@ -124,7 +123,6 @@ def _skip_string_literal(stmt: str, pos: int) -> int:
|
||||
i = pos + 1
|
||||
while i < len(stmt):
|
||||
if stmt[i] == quote_char:
|
||||
# Check for escaped quote ('')
|
||||
if i + 1 < len(stmt) and stmt[i + 1] == quote_char:
|
||||
i += 2
|
||||
continue
|
||||
@@ -290,9 +288,7 @@ class NL2SQLTool(BaseTool):
|
||||
self.tables = tables
|
||||
self.columns = data
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Query validation
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _validate_query(self, sql_query: str) -> None:
|
||||
"""Raise ValueError if *sql_query* is not permitted under the current config.
|
||||
@@ -323,7 +319,6 @@ class NL2SQLTool(BaseTool):
|
||||
|
||||
# EXPLAIN ANALYZE / EXPLAIN ANALYSE actually *executes* the underlying
|
||||
# query. Resolve the real command so write operations are caught.
|
||||
# Handles both space-separated ("EXPLAIN ANALYZE DELETE …") and
|
||||
# parenthesized ("EXPLAIN (ANALYZE) DELETE …", "EXPLAIN (ANALYZE, VERBOSE) DELETE …").
|
||||
# EXPLAIN ANALYZE actually executes the underlying query — resolve the
|
||||
# real command so write operations are caught.
|
||||
@@ -332,10 +327,8 @@ class NL2SQLTool(BaseTool):
|
||||
if resolved:
|
||||
command = resolved
|
||||
|
||||
# WITH starts a CTE. Read-only CTEs are fine; writable CTEs
|
||||
# (e.g. WITH d AS (DELETE …) SELECT …) must be blocked in read-only mode.
|
||||
if command == "WITH":
|
||||
# Check for write commands inside CTE bodies.
|
||||
write_found = _detect_writable_cte(stmt)
|
||||
if write_found:
|
||||
found = write_found
|
||||
@@ -352,7 +345,6 @@ class NL2SQLTool(BaseTool):
|
||||
)
|
||||
return
|
||||
|
||||
# Check the main query after the CTE definitions.
|
||||
main_query = _extract_main_query_after_cte(stmt)
|
||||
if main_query:
|
||||
main_cmd = main_query.split()[0].upper().rstrip(";")
|
||||
@@ -404,9 +396,7 @@ class NL2SQLTool(BaseTool):
|
||||
first_token = stripped.split()[0] if stripped.split() else ""
|
||||
return first_token.upper().rstrip(";")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schema introspection helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _fetch_available_tables(self) -> list[dict[str, Any]] | str:
|
||||
return self.execute_sql(
|
||||
@@ -428,9 +418,7 @@ class NL2SQLTool(BaseTool):
|
||||
params={"table_name": table_name},
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Core execution
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _run(self, sql_query: str) -> list[dict[str, Any]] | str:
|
||||
try:
|
||||
@@ -497,7 +485,6 @@ class NL2SQLTool(BaseTool):
|
||||
try:
|
||||
result = session.execute(text(sql_query), params or {})
|
||||
|
||||
# Only commit when the operation actually mutates state
|
||||
if self.allow_dml and is_write:
|
||||
session.commit()
|
||||
|
||||
|
||||
@@ -107,7 +107,6 @@ class OxylabsAmazonProductScraperTool(BaseTool):
|
||||
username, password = self._get_credentials_from_env()
|
||||
|
||||
if OXYLABS_AVAILABLE:
|
||||
# import RealtimeClient to make it accessible for the current scope
|
||||
from oxylabs import RealtimeClient
|
||||
|
||||
kwargs["oxylabs_api"] = RealtimeClient(
|
||||
|
||||
@@ -109,7 +109,6 @@ class OxylabsAmazonSearchScraperTool(BaseTool):
|
||||
username, password = self._get_credentials_from_env()
|
||||
|
||||
if OXYLABS_AVAILABLE:
|
||||
# import RealtimeClient to make it accessible for the current scope
|
||||
from oxylabs import RealtimeClient
|
||||
|
||||
kwargs["oxylabs_api"] = RealtimeClient(
|
||||
|
||||
@@ -112,7 +112,6 @@ class OxylabsGoogleSearchScraperTool(BaseTool):
|
||||
username, password = self._get_credentials_from_env()
|
||||
|
||||
if OXYLABS_AVAILABLE:
|
||||
# import RealtimeClient to make it accessible for the current scope
|
||||
from oxylabs import RealtimeClient
|
||||
|
||||
kwargs["oxylabs_api"] = RealtimeClient(
|
||||
|
||||
@@ -103,7 +103,6 @@ class OxylabsUniversalScraperTool(BaseTool):
|
||||
username, password = self._get_credentials_from_env()
|
||||
|
||||
if OXYLABS_AVAILABLE:
|
||||
# import RealtimeClient to make it accessible for the current scope
|
||||
from oxylabs import RealtimeClient
|
||||
|
||||
kwargs["oxylabs_api"] = RealtimeClient(
|
||||
|
||||
@@ -11,7 +11,6 @@ from patronus_local_evaluator_tool import ( # type: ignore[import-not-found]
|
||||
)
|
||||
|
||||
|
||||
# Test the PatronusLocalEvaluatorTool where agent uses the local evaluator
|
||||
client = Client()
|
||||
|
||||
|
||||
@@ -41,7 +40,6 @@ patronus_eval_tool = PatronusLocalEvaluatorTool(
|
||||
evaluated_model_gold_answer="example label",
|
||||
)
|
||||
|
||||
# Create a new agent
|
||||
coding_agent = Agent(
|
||||
role="Coding Agent",
|
||||
goal="Generate high quality code and verify that the output is code by using Patronus AI's evaluation tool.",
|
||||
@@ -50,7 +48,6 @@ coding_agent = Agent(
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
# Define tasks
|
||||
generate_code = Task(
|
||||
description="Create a simple program to generate the first N numbers in the Fibonacci sequence. Select the most appropriate evaluator and criteria for evaluating your output.",
|
||||
expected_output="Program that generates the first N numbers in the Fibonacci sequence.",
|
||||
|
||||
@@ -119,7 +119,6 @@ class PatronusEvalTool(BaseTool):
|
||||
evaluated_model_retrieved_context: str | None,
|
||||
evaluators: list[dict[str, str]],
|
||||
) -> Any:
|
||||
# Assert correct format of evaluators
|
||||
evals = []
|
||||
for ev in evaluators:
|
||||
evals.append( # noqa: PERF401
|
||||
|
||||
@@ -103,7 +103,6 @@ class PatronusLocalEvaluatorTool(BaseTool):
|
||||
|
||||
|
||||
try:
|
||||
# Only rebuild if the class hasn't been initialized yet
|
||||
if not hasattr(PatronusLocalEvaluatorTool, "_model_rebuilt"):
|
||||
PatronusLocalEvaluatorTool.model_rebuild()
|
||||
PatronusLocalEvaluatorTool._model_rebuilt = True # type: ignore[attr-defined]
|
||||
|
||||
@@ -43,7 +43,6 @@ class QdrantVectorSearchTool(BaseTool):
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
# --- Metadata ---
|
||||
name: str = "QdrantVectorSearchTool"
|
||||
description: str = "Search Qdrant vector DB for relevant documents."
|
||||
args_schema: type[BaseModel] = QdrantToolSchema
|
||||
@@ -68,7 +67,6 @@ class QdrantVectorSearchTool(BaseTool):
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _setup_qdrant(self) -> QdrantVectorSearchTool:
|
||||
# Import the qdrant_package if it's a string
|
||||
if isinstance(self.qdrant_package, str):
|
||||
self.qdrant_package = importlib.import_module(self.qdrant_package)
|
||||
|
||||
|
||||
@@ -125,7 +125,6 @@ class ScrapegraphScrapeTool(BaseTool):
|
||||
if user_prompt is not None:
|
||||
self.user_prompt = user_prompt
|
||||
|
||||
# Configure logging only if enabled
|
||||
if self.enable_logging:
|
||||
sgai_logger.set_logging(level="INFO")
|
||||
|
||||
@@ -170,11 +169,9 @@ class ScrapegraphScrapeTool(BaseTool):
|
||||
if not website_url:
|
||||
raise ValueError("website_url is required")
|
||||
|
||||
# Validate URL format
|
||||
self._validate_url(website_url)
|
||||
|
||||
try:
|
||||
# Make the SmartScraper request
|
||||
if self._client is None:
|
||||
raise RuntimeError("Client not initialized")
|
||||
return self._client.smartscraper(
|
||||
|
||||
@@ -192,7 +192,6 @@ class SeleniumScrapingTool(BaseTool):
|
||||
if not url:
|
||||
raise ValueError("URL cannot be empty")
|
||||
|
||||
# Validate URL format
|
||||
if not re.match(r"^https?://", url):
|
||||
raise ValueError("URL must start with http:// or https://")
|
||||
|
||||
|
||||
@@ -49,16 +49,13 @@ class SerperScrapeWebsiteTool(BaseTool):
|
||||
# Serper API endpoint
|
||||
api_url = "https://scrape.serper.dev"
|
||||
|
||||
# Get API key from environment variable for security
|
||||
api_key = os.getenv("SERPER_API_KEY")
|
||||
|
||||
# Prepare the payload
|
||||
payload = json.dumps({"url": url, "includeMarkdown": include_markdown})
|
||||
|
||||
# Set headers
|
||||
headers = {"X-API-KEY": api_key or "", "Content-Type": "application/json"}
|
||||
|
||||
# Make the API request
|
||||
response = requests.post(
|
||||
api_url,
|
||||
headers=headers,
|
||||
@@ -66,11 +63,9 @@ class SerperScrapeWebsiteTool(BaseTool):
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
# Check if request was successful
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
|
||||
# Extract the scraped content
|
||||
if "text" in result:
|
||||
return str(result["text"])
|
||||
return f"Successfully scraped {url}, but no text content found in response: {response.text}"
|
||||
|
||||
@@ -61,7 +61,6 @@ class SerplyJobSearchTool(RagTool):
|
||||
elif search_query is not None:
|
||||
query_payload["q"] = search_query
|
||||
|
||||
# build the url
|
||||
url = f"{self.request_url}{urlencode(query_payload)}"
|
||||
|
||||
response = requests.request("GET", url, headers=self.headers, timeout=30)
|
||||
|
||||
@@ -53,7 +53,6 @@ class SerplyNewsSearchTool(BaseTool):
|
||||
self,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
# build query parameters
|
||||
query_payload = {}
|
||||
|
||||
if "query" in kwargs:
|
||||
@@ -61,7 +60,6 @@ class SerplyNewsSearchTool(BaseTool):
|
||||
elif "search_query" in kwargs:
|
||||
query_payload["q"] = kwargs["search_query"]
|
||||
|
||||
# build the url
|
||||
url = f"{self.search_url}{urlencode(query_payload)}"
|
||||
|
||||
response = requests.request(
|
||||
|
||||
@@ -64,7 +64,6 @@ class SerplyScholarSearchTool(BaseTool):
|
||||
elif "search_query" in kwargs:
|
||||
query_payload["q"] = kwargs["search_query"]
|
||||
|
||||
# build the url
|
||||
url = f"{self.search_url}{urlencode(query_payload)}"
|
||||
|
||||
response = requests.request(
|
||||
|
||||
@@ -58,7 +58,6 @@ class SerplyWebSearchTool(BaseTool):
|
||||
self.device_type = device_type
|
||||
self.proxy_location = proxy_location
|
||||
|
||||
# build query parameters
|
||||
self.query_payload = {
|
||||
"num": limit,
|
||||
"gl": proxy_location.upper(),
|
||||
@@ -80,7 +79,6 @@ class SerplyWebSearchTool(BaseTool):
|
||||
elif "search_query" in kwargs:
|
||||
self.query_payload["q"] = kwargs["search_query"] # type: ignore[index]
|
||||
|
||||
# build the url
|
||||
url = f"{self.search_url}{urlencode(self.query_payload)}" # type: ignore[arg-type]
|
||||
|
||||
response = requests.request(
|
||||
|
||||
@@ -123,7 +123,6 @@ class SingleStoreSearchTool(BaseTool):
|
||||
def __init__(
|
||||
self,
|
||||
tables: list[str] | None = None,
|
||||
# Basic connection parameters
|
||||
host: str | None = None,
|
||||
user: str | None = None,
|
||||
password: str | None = None,
|
||||
@@ -147,7 +146,6 @@ class SingleStoreSearchTool(BaseTool):
|
||||
conv: dict[int, Callable[..., Any]] | None = None,
|
||||
credential_type: str | None = None,
|
||||
autocommit: bool | None = None,
|
||||
# Result formatting options
|
||||
results_type: str | None = None,
|
||||
buffered: bool | None = None,
|
||||
results_format: str | None = None,
|
||||
@@ -210,13 +208,10 @@ class SingleStoreSearchTool(BaseTool):
|
||||
"`singlestore` package not found, please run `uv add crewai-tools[singlestore]`"
|
||||
)
|
||||
|
||||
# Set the data type for the parent class
|
||||
kwargs["data_type"] = "singlestore"
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Build connection arguments dictionary with sensible defaults
|
||||
self.connection_args = {
|
||||
# Basic connection parameters
|
||||
"host": host,
|
||||
"user": user,
|
||||
"password": password,
|
||||
@@ -240,7 +235,6 @@ class SingleStoreSearchTool(BaseTool):
|
||||
"conv": conv or {},
|
||||
"credential_type": credential_type,
|
||||
"autocommit": autocommit,
|
||||
# Result formatting
|
||||
"results_type": results_type,
|
||||
"buffered": buffered,
|
||||
"results_format": results_format,
|
||||
@@ -266,13 +260,11 @@ class SingleStoreSearchTool(BaseTool):
|
||||
):
|
||||
self.connection_args["conn_attrs"] = dict()
|
||||
|
||||
# Add tool identification to connection attributes
|
||||
self.connection_args["conn_attrs"]["_connector_name"] = (
|
||||
"crewAI SingleStore Tool"
|
||||
)
|
||||
self.connection_args["conn_attrs"]["_connector_version"] = "1.0"
|
||||
|
||||
# Initialize connection pool for efficient connection management
|
||||
self.connection_pool = QueuePool(
|
||||
creator=self._create_connection,
|
||||
pool_size=pool_size or 5,
|
||||
@@ -280,7 +272,6 @@ class SingleStoreSearchTool(BaseTool):
|
||||
timeout=timeout or 30.0,
|
||||
)
|
||||
|
||||
# Validate database schema and initialize table information
|
||||
self._initialize_tables(tables)
|
||||
|
||||
def _initialize_tables(self, tables: list[str]) -> None:
|
||||
@@ -295,22 +286,18 @@ class SingleStoreSearchTool(BaseTool):
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
# Get all existing tables in the database
|
||||
cursor.execute("SHOW TABLES")
|
||||
existing_tables = {table[0] for table in cursor.fetchall()}
|
||||
|
||||
# Validate that the database has tables
|
||||
if not existing_tables or len(existing_tables) == 0:
|
||||
raise ValueError(
|
||||
"No tables found in the database. "
|
||||
"Please ensure the database is initialized with the required tables."
|
||||
)
|
||||
|
||||
# Use all tables if none specified
|
||||
if not tables or len(tables) == 0:
|
||||
tables = list(existing_tables)
|
||||
|
||||
# Build table definitions for description
|
||||
table_definitions = []
|
||||
for table in tables:
|
||||
if table not in existing_tables:
|
||||
@@ -319,7 +306,6 @@ class SingleStoreSearchTool(BaseTool):
|
||||
f"Please ensure the table is created."
|
||||
)
|
||||
|
||||
# Get column information for each table
|
||||
cursor.execute(f"SHOW COLUMNS FROM {table}")
|
||||
columns = cursor.fetchall()
|
||||
column_info = ", ".join(f"{row[0]} {row[1]}" for row in columns)
|
||||
@@ -328,7 +314,6 @@ class SingleStoreSearchTool(BaseTool):
|
||||
# Ensure the connection is returned to the pool
|
||||
conn.close()
|
||||
|
||||
# Update the tool description with actual table information
|
||||
self.description = (
|
||||
f"A tool that can be used to semantic search a query from a SingleStore "
|
||||
f"database's {', '.join(table_definitions)} table(s) content."
|
||||
@@ -379,11 +364,9 @@ class SingleStoreSearchTool(BaseTool):
|
||||
Returns:
|
||||
tuple: (is_valid: bool, message: str)
|
||||
"""
|
||||
# Check if the input is a string
|
||||
if not isinstance(search_query, str):
|
||||
return False, "Search query must be a string."
|
||||
|
||||
# Remove leading/trailing whitespace and convert to lowercase for checking
|
||||
query_lower = search_query.strip().lower()
|
||||
|
||||
# Allow only SELECT and SHOW statements
|
||||
@@ -405,25 +388,20 @@ class SingleStoreSearchTool(BaseTool):
|
||||
Returns:
|
||||
str: Formatted search results or error message
|
||||
"""
|
||||
# Validate the query before execution
|
||||
valid, message = self._validate_query(search_query)
|
||||
if not valid:
|
||||
return f"Invalid search query: {message}"
|
||||
|
||||
# Execute the query using a connection from the pool
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
try:
|
||||
# Execute the validated search query
|
||||
cursor.execute(search_query)
|
||||
results = cursor.fetchall()
|
||||
|
||||
# Handle empty results
|
||||
if not results:
|
||||
return "No results found."
|
||||
|
||||
# Format the results for readable output
|
||||
formatted_results = "\n".join(
|
||||
[", ".join([str(item) for item in row]) for row in results]
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Import types for type checking only
|
||||
from snowflake.connector.connection import (
|
||||
SnowflakeConnection,
|
||||
)
|
||||
@@ -29,7 +28,6 @@ try:
|
||||
except ImportError:
|
||||
SNOWFLAKE_AVAILABLE = False
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Cache for query results
|
||||
@@ -257,7 +255,6 @@ class SnowflakeSearchTool(BaseTool):
|
||||
) -> Any:
|
||||
"""Execute the search query."""
|
||||
try:
|
||||
# Override database/schema if provided
|
||||
if database:
|
||||
await self._execute_query(f"USE DATABASE {database}")
|
||||
if snowflake_schema:
|
||||
@@ -284,7 +281,6 @@ class SnowflakeSearchTool(BaseTool):
|
||||
|
||||
|
||||
try:
|
||||
# Only rebuild if the class hasn't been initialized yet
|
||||
if not hasattr(SnowflakeSearchTool, "_model_rebuilt"):
|
||||
SnowflakeSearchTool.model_rebuild()
|
||||
SnowflakeSearchTool._model_rebuilt = True
|
||||
|
||||
@@ -28,23 +28,19 @@ from crewai_tools import StagehandTool
|
||||
_printer = Printer()
|
||||
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Get API keys from environment variables
|
||||
# You can set these in your shell or in a .env file
|
||||
browserbase_api_key = os.environ.get("BROWSERBASE_API_KEY")
|
||||
browserbase_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
|
||||
model_api_key = os.environ.get("OPENAI_API_KEY") # or OPENAI_API_KEY
|
||||
model_api_key = os.environ.get("OPENAI_API_KEY")
|
||||
|
||||
# Initialize the StagehandTool with your credentials and use context manager
|
||||
with StagehandTool(
|
||||
api_key=browserbase_api_key, # New parameter naming
|
||||
project_id=browserbase_project_id, # New parameter naming
|
||||
api_key=browserbase_api_key,
|
||||
project_id=browserbase_project_id,
|
||||
model_api_key=model_api_key,
|
||||
model_name=AvailableModel.GPT_4O, # Using the enum from schemas
|
||||
model_name=AvailableModel.GPT_4O,
|
||||
) as stagehand_tool:
|
||||
# Create a web researcher agent with the StagehandTool
|
||||
researcher = Agent(
|
||||
role="Web Researcher",
|
||||
goal="Find and extract information from websites using different Stagehand primitives",
|
||||
@@ -74,7 +70,6 @@ with StagehandTool(
|
||||
tools=[stagehand_tool],
|
||||
)
|
||||
|
||||
# Define a research task that demonstrates all three primitives
|
||||
research_task = Task(
|
||||
description=(
|
||||
"Demonstrate Stagehand capabilities by performing the following steps:\n"
|
||||
@@ -104,7 +99,6 @@ with StagehandTool(
|
||||
agent=researcher,
|
||||
)
|
||||
|
||||
# Set up the crew
|
||||
crew = Crew(
|
||||
agents=[researcher],
|
||||
tasks=[research_task], # You can switch this to web_research_task if you prefer
|
||||
@@ -112,7 +106,6 @@ with StagehandTool(
|
||||
process=Process.sequential,
|
||||
)
|
||||
|
||||
# Run the crew and get the result
|
||||
result = crew.kickoff()
|
||||
|
||||
_printer.print("\n==== RESULTS ====\n", color="cyan")
|
||||
|
||||
@@ -11,7 +11,6 @@ from crewai.tools import BaseTool, EnvVar
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# Define a flag to track whether stagehand is available
|
||||
_HAS_STAGEHAND = False
|
||||
|
||||
try:
|
||||
@@ -37,7 +36,6 @@ except ImportError:
|
||||
ExtractOptions = Any
|
||||
ObserveOptions = Any
|
||||
|
||||
# Mock configure_logging function
|
||||
def configure_logging(
|
||||
level: str | None = None,
|
||||
remove_logger_name: bool | None = None,
|
||||
@@ -45,7 +43,6 @@ except ImportError:
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
# Define only what's needed for class defaults
|
||||
class AvailableModel: # type: ignore[no-redef]
|
||||
CLAUDE_3_7_SONNET_LATEST = "anthropic.claude-3-7-sonnet-20240607"
|
||||
|
||||
@@ -203,7 +200,6 @@ class StagehandTool(BaseTool):
|
||||
self._testing = _testing
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Set up logger
|
||||
import logging
|
||||
|
||||
self._logger = logging.getLogger(__name__)
|
||||
@@ -231,7 +227,6 @@ class StagehandTool(BaseTool):
|
||||
|
||||
self._session_id = session_id
|
||||
|
||||
# Configure logging based on verbosity level
|
||||
if not self._testing:
|
||||
log_level = {1: "INFO", 2: "WARNING", 3: "DEBUG"}.get(self.verbose, "ERROR")
|
||||
configure_logging(
|
||||
@@ -263,7 +258,6 @@ class StagehandTool(BaseTool):
|
||||
|
||||
def _get_model_api_key(self) -> str | None:
|
||||
"""Get the appropriate API key based on the model being used."""
|
||||
# Check model type and get appropriate key
|
||||
model_str = str(self.model_name)
|
||||
if "gpt" in model_str.lower():
|
||||
return self.model_api_key or os.getenv("OPENAI_API_KEY")
|
||||
@@ -280,10 +274,9 @@ class StagehandTool(BaseTool):
|
||||
|
||||
async def _setup_stagehand(self, session_id: str | None = None) -> tuple[Any, Any]:
|
||||
"""Initialize Stagehand if not already set up."""
|
||||
# If we're in testing mode, return mock objects
|
||||
if self._testing:
|
||||
if not self._stagehand:
|
||||
# Create mock objects for testing
|
||||
|
||||
class MockPage:
|
||||
async def act(self, options: Any) -> Any:
|
||||
mock_result = type("MockResult", (), {})()
|
||||
@@ -331,7 +324,6 @@ class StagehandTool(BaseTool):
|
||||
|
||||
# Normal initialization for non-testing mode
|
||||
if not self._stagehand:
|
||||
# Get the appropriate API key based on model type
|
||||
model_api_key = self._get_model_api_key()
|
||||
|
||||
if not model_api_key:
|
||||
@@ -339,7 +331,6 @@ class StagehandTool(BaseTool):
|
||||
"No appropriate API key found for model. Please set OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_API_KEY"
|
||||
)
|
||||
|
||||
# Build the StagehandConfig with proper parameter names
|
||||
config = StagehandConfig(
|
||||
env="BROWSERBASE",
|
||||
apiKey=self.api_key, # Browserbase API key (camelCase)
|
||||
@@ -356,10 +347,8 @@ class StagehandTool(BaseTool):
|
||||
browserbaseSessionID=session_id or self._session_id,
|
||||
)
|
||||
|
||||
# Initialize Stagehand with config
|
||||
self._stagehand = Stagehand(config=config) # type: ignore[call-arg]
|
||||
|
||||
# Initialize the Stagehand instance
|
||||
await self._stagehand.init()
|
||||
self._page = self._stagehand.page
|
||||
self._session_id = self._stagehand.session_id
|
||||
@@ -368,7 +357,6 @@ class StagehandTool(BaseTool):
|
||||
|
||||
def _extract_steps(self, instruction: str) -> list[str]:
|
||||
"""Extract individual steps from multi-step instructions."""
|
||||
# Check for numbered steps (Step 1:, Step 2:, etc.)
|
||||
if re.search(r"Step \d+:", instruction, re.IGNORECASE):
|
||||
steps = re.findall(
|
||||
r"Step \d+:\s*([^;]+?)(?=Step \d+:|$)",
|
||||
@@ -376,14 +364,12 @@ class StagehandTool(BaseTool):
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
return [step.strip() for step in steps if step.strip()]
|
||||
# Check for semicolon-separated instructions
|
||||
if ";" in instruction:
|
||||
return [step.strip() for step in instruction.split(";") if step.strip()]
|
||||
return [instruction]
|
||||
|
||||
def _simplify_instruction(self, instruction: str) -> str:
|
||||
"""Simplify complex instructions to basic actions."""
|
||||
# Extract the core action from complex instructions
|
||||
instruction_lower = instruction.lower()
|
||||
|
||||
if "search" in instruction_lower and "click" in instruction_lower:
|
||||
@@ -392,7 +378,6 @@ class StagehandTool(BaseTool):
|
||||
return "click on the search input field"
|
||||
return "search for content on the page"
|
||||
if "click" in instruction_lower:
|
||||
# Extract what to click
|
||||
if "button" in instruction_lower:
|
||||
return "click the button"
|
||||
if "link" in instruction_lower:
|
||||
@@ -402,7 +387,7 @@ class StagehandTool(BaseTool):
|
||||
return "click on the element"
|
||||
if "type" in instruction_lower or "enter" in instruction_lower:
|
||||
return "type in the input field"
|
||||
return instruction # Return as-is if can't simplify
|
||||
return instruction
|
||||
|
||||
async def _async_run(
|
||||
self,
|
||||
@@ -411,7 +396,6 @@ class StagehandTool(BaseTool):
|
||||
command_type: str = "act",
|
||||
) -> StagehandResult:
|
||||
"""Override _async_run with improved atomic action handling."""
|
||||
# Handle missing instruction based on command type
|
||||
if not instruction:
|
||||
if command_type == "navigate" and url:
|
||||
instruction = f"Navigate to {url}"
|
||||
@@ -439,7 +423,6 @@ class StagehandTool(BaseTool):
|
||||
f"Executing {command_type} with instruction: {instruction}"
|
||||
)
|
||||
|
||||
# Get the API key to pass to model operations
|
||||
model_api_key = self._get_model_api_key()
|
||||
model_client_options: dict[str, Any] = {"apiKey": model_api_key}
|
||||
|
||||
@@ -451,9 +434,7 @@ class StagehandTool(BaseTool):
|
||||
# Small delay to ensure page is fully loaded
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Process according to command type
|
||||
if command_type.lower() == "act":
|
||||
# Extract steps from complex instructions
|
||||
steps = self._extract_steps(instruction)
|
||||
self._logger.info(f"Extracted {len(steps)} steps: {steps}")
|
||||
|
||||
@@ -462,7 +443,6 @@ class StagehandTool(BaseTool):
|
||||
self._logger.info(f"Executing step {i + 1}/{len(steps)}: {step}")
|
||||
|
||||
try:
|
||||
# Create act options with API key for each step
|
||||
from stagehand.schemas import ActOptions
|
||||
|
||||
act_options = ActOptions(
|
||||
@@ -483,7 +463,6 @@ class StagehandTool(BaseTool):
|
||||
error_msg = f"Step failed: {step_error}"
|
||||
self._logger.warning(f"Step {i + 1} failed: {error_msg}")
|
||||
|
||||
# Try with simplified instruction
|
||||
try:
|
||||
simplified = self._simplify_instruction(step)
|
||||
if simplified != step:
|
||||
@@ -501,13 +480,11 @@ class StagehandTool(BaseTool):
|
||||
result = await page.act(act_options)
|
||||
results.append(result.model_dump())
|
||||
else:
|
||||
# If we can't simplify or retry fails, record the error
|
||||
results.append({"error": error_msg, "step": step})
|
||||
except Exception as retry_error:
|
||||
self._logger.error(f"Retry also failed: {retry_error}")
|
||||
results.append({"error": str(retry_error), "step": step})
|
||||
|
||||
# Return combined results
|
||||
if len(results) == 1:
|
||||
# Single step, return as-is
|
||||
if "error" in results[0]:
|
||||
@@ -537,7 +514,6 @@ class StagehandTool(BaseTool):
|
||||
)
|
||||
|
||||
if command_type.lower() == "extract":
|
||||
# Create extract options with API key
|
||||
from stagehand.schemas import ExtractOptions
|
||||
|
||||
extract_options = ExtractOptions(
|
||||
@@ -545,7 +521,7 @@ class StagehandTool(BaseTool):
|
||||
modelName=self.model_name,
|
||||
domSettleTimeoutMs=self.dom_settle_timeout_ms,
|
||||
useTextExtract=True,
|
||||
modelClientOptions=model_client_options, # Add API key here
|
||||
modelClientOptions=model_client_options,
|
||||
)
|
||||
|
||||
result = await page.extract(extract_options)
|
||||
@@ -553,7 +529,6 @@ class StagehandTool(BaseTool):
|
||||
return self._format_result(True, result.model_dump())
|
||||
|
||||
if command_type.lower() == "observe":
|
||||
# Create observe options with API key
|
||||
from stagehand.schemas import ObserveOptions
|
||||
|
||||
observe_options = ObserveOptions(
|
||||
@@ -561,12 +536,11 @@ class StagehandTool(BaseTool):
|
||||
modelName=self.model_name,
|
||||
onlyVisible=True,
|
||||
domSettleTimeoutMs=self.dom_settle_timeout_ms,
|
||||
modelClientOptions=model_client_options, # Add API key here
|
||||
modelClientOptions=model_client_options,
|
||||
)
|
||||
|
||||
observe_results = await page.observe(observe_options)
|
||||
|
||||
# Format the observation results
|
||||
formatted_results: list[dict[str, Any]] = []
|
||||
for i, obs_result in enumerate(observe_results):
|
||||
formatted_results.append(
|
||||
@@ -616,7 +590,6 @@ class StagehandTool(BaseTool):
|
||||
Returns:
|
||||
The result of the browser automation task
|
||||
"""
|
||||
# Handle missing instruction based on command type
|
||||
if not instruction:
|
||||
if command_type == "navigate" and url:
|
||||
instruction = f"Navigate to {url}"
|
||||
@@ -626,7 +599,6 @@ class StagehandTool(BaseTool):
|
||||
instruction = "Extract information from the page"
|
||||
else:
|
||||
instruction = "Perform the requested action"
|
||||
# Create an event loop if we're not already in one
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
@@ -647,7 +619,6 @@ class StagehandTool(BaseTool):
|
||||
self._async_run(instruction, url, command_type)
|
||||
)
|
||||
|
||||
# Format the result for output
|
||||
if result.success:
|
||||
if command_type.lower() == "act":
|
||||
if isinstance(result.data, dict) and "steps" in result.data:
|
||||
@@ -696,7 +667,6 @@ class StagehandTool(BaseTool):
|
||||
|
||||
async def _async_close(self) -> None:
|
||||
"""Asynchronously clean up Stagehand resources."""
|
||||
# Skip for test mode
|
||||
if self._testing:
|
||||
self._stagehand = None
|
||||
self._page = None
|
||||
@@ -710,7 +680,6 @@ class StagehandTool(BaseTool):
|
||||
|
||||
def close(self) -> None:
|
||||
"""Clean up Stagehand resources."""
|
||||
# Skip actual closing for testing mode
|
||||
if self._testing:
|
||||
self._stagehand = None
|
||||
self._page = None
|
||||
@@ -741,7 +710,6 @@ class StagehandTool(BaseTool):
|
||||
else:
|
||||
close_method()
|
||||
except Exception: # noqa: S110
|
||||
# Log but don't raise - we're cleaning up
|
||||
pass
|
||||
|
||||
self._stagehand = None
|
||||
|
||||
@@ -25,7 +25,6 @@ class ImagePromptSchema(BaseModel):
|
||||
if not path.exists():
|
||||
raise ValueError(f"Image file does not exist: {v}")
|
||||
|
||||
# Validate supported formats
|
||||
valid_extensions = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||||
if path.suffix.lower() not in valid_extensions:
|
||||
raise ValueError(
|
||||
|
||||
@@ -137,7 +137,6 @@ def test_context_manager_with_filtered_tools(echo_server_script):
|
||||
assert len(tools) == 1
|
||||
assert tools[0].name == "echo_tool"
|
||||
assert tools[0].run(text="hello") == "Echo: hello"
|
||||
# Check that calc_tool is not present
|
||||
with pytest.raises(IndexError):
|
||||
_ = tools[1]
|
||||
with pytest.raises(KeyError):
|
||||
@@ -152,7 +151,6 @@ def test_context_manager_sse_with_filtered_tools(echo_sse_server):
|
||||
assert len(tools) == 1
|
||||
assert tools[0].name == "calc_tool"
|
||||
assert tools[0].run(a=10, b=5) == "15"
|
||||
# Check that echo_tool is not present
|
||||
with pytest.raises(IndexError):
|
||||
_ = tools[1]
|
||||
with pytest.raises(KeyError):
|
||||
|
||||
@@ -10,7 +10,6 @@ def test_creating_a_tool_using_annotation():
|
||||
"""Clear description for what this tool is useful for, you agent will need this information to use it."""
|
||||
return question
|
||||
|
||||
# Assert all the right attributes were defined
|
||||
assert my_tool.name == "Name of my tool"
|
||||
assert (
|
||||
my_tool.description
|
||||
@@ -48,7 +47,6 @@ def test_creating_a_tool_using_baseclass():
|
||||
return question
|
||||
|
||||
my_tool = MyCustomTool()
|
||||
# Assert all the right attributes were defined
|
||||
assert my_tool.name == "Name of my tool"
|
||||
assert (
|
||||
my_tool.description
|
||||
@@ -87,7 +85,6 @@ def test_setting_cache_function():
|
||||
return question
|
||||
|
||||
my_tool = MyCustomTool()
|
||||
# Assert all the right attributes were defined
|
||||
assert not my_tool.cache_function()
|
||||
|
||||
|
||||
@@ -100,5 +97,4 @@ def test_default_cache_function_is_true():
|
||||
return question
|
||||
|
||||
my_tool = MyCustomTool()
|
||||
# Assert all the right attributes were defined
|
||||
assert my_tool.cache_function()
|
||||
|
||||
@@ -6,18 +6,15 @@ from crewai_tools import FileReadTool
|
||||
|
||||
def test_file_read_tool_constructor():
|
||||
"""Test FileReadTool initialization with file_path."""
|
||||
# Create a temporary test file
|
||||
test_file = "/tmp/test_file.txt"
|
||||
test_content = "Hello, World!"
|
||||
with open(test_file, "w") as f:
|
||||
f.write(test_content)
|
||||
|
||||
# Test initialization with file_path
|
||||
tool = FileReadTool(file_path=test_file)
|
||||
assert tool.file_path == test_file
|
||||
assert "test_file.txt" in tool.description
|
||||
|
||||
# Clean up
|
||||
os.remove(test_file)
|
||||
|
||||
|
||||
@@ -28,7 +25,6 @@ def test_file_read_tool_run():
|
||||
|
||||
# Use mock_open to mock file operations
|
||||
with patch("builtins.open", mock_open(read_data=test_content)):
|
||||
# Test reading file with runtime file_path
|
||||
tool = FileReadTool()
|
||||
result = tool._run(file_path=test_file)
|
||||
assert result == test_content
|
||||
@@ -36,16 +32,13 @@ def test_file_read_tool_run():
|
||||
|
||||
def test_file_read_tool_error_handling():
|
||||
"""Test FileReadTool error handling."""
|
||||
# Test missing file path
|
||||
tool = FileReadTool()
|
||||
result = tool._run()
|
||||
assert "Error: No file path provided" in result
|
||||
|
||||
# Test non-existent file
|
||||
result = tool._run(file_path="/nonexistent/file.txt")
|
||||
assert "Error: File not found at path:" in result
|
||||
|
||||
# Test permission error
|
||||
with patch("builtins.open", side_effect=PermissionError()):
|
||||
result = tool._run(file_path="/tmp/no_permission.txt")
|
||||
assert "Error: Permission denied" in result
|
||||
@@ -58,7 +51,6 @@ def test_file_read_tool_constructor_and_run():
|
||||
content1 = "File 1 content"
|
||||
content2 = "File 2 content"
|
||||
|
||||
# First test with content1
|
||||
with patch("builtins.open", mock_open(read_data=content1)):
|
||||
tool = FileReadTool(file_path=test_file1)
|
||||
result = tool._run()
|
||||
@@ -90,7 +82,6 @@ def test_file_read_tool_chunk_reading():
|
||||
with patch("builtins.open", mock_open(read_data=file_content)):
|
||||
tool = FileReadTool()
|
||||
|
||||
# Test reading a specific chunk (lines 3-5)
|
||||
result = tool._run(file_path=test_file, start_line=3, line_count=3)
|
||||
expected = "".join(lines[2:5]) # Lines are 0-indexed in the array
|
||||
assert result == expected
|
||||
@@ -120,7 +111,6 @@ def test_file_read_tool_chunk_error_handling():
|
||||
with patch("builtins.open", mock_open(read_data=file_content)):
|
||||
tool = FileReadTool()
|
||||
|
||||
# Test start_line exceeding file length
|
||||
result = tool._run(file_path=test_file, start_line=10)
|
||||
assert "Error: Start line 10 exceeds the number of lines in the file" in result
|
||||
|
||||
@@ -139,12 +129,10 @@ def test_file_read_tool_zero_or_negative_start_line():
|
||||
with patch("builtins.open", mock_open(read_data=file_content)):
|
||||
tool = FileReadTool()
|
||||
|
||||
# Test with start_line = None
|
||||
result = tool._run(file_path=test_file, start_line=None)
|
||||
expected = "".join(lines) # Should read the entire file
|
||||
assert result == expected
|
||||
|
||||
# Test with start_line = 0
|
||||
result = tool._run(file_path=test_file, start_line=0)
|
||||
expected = "".join(lines) # Should read the entire file
|
||||
assert result == expected
|
||||
@@ -154,7 +142,6 @@ def test_file_read_tool_zero_or_negative_start_line():
|
||||
expected = "".join(lines[0:3]) # Should read first 3 lines
|
||||
assert result == expected
|
||||
|
||||
# Test with negative start_line
|
||||
result = tool._run(file_path=test_file, start_line=-5)
|
||||
expected = "".join(lines) # Should read the entire file
|
||||
assert result == expected
|
||||
|
||||
@@ -14,7 +14,7 @@ class TestDOCXLoader:
|
||||
mock_doc.paragraphs = [
|
||||
Mock(text="First paragraph"),
|
||||
Mock(text="Second paragraph"),
|
||||
Mock(text=" "), # Blank paragraph
|
||||
Mock(text=" "),
|
||||
]
|
||||
mock_doc.tables = []
|
||||
mock_docx_class.return_value = mock_doc
|
||||
|
||||
@@ -65,24 +65,20 @@ class TestEmbeddingService:
|
||||
"""Test getting default API keys from environment."""
|
||||
service = EmbeddingService.__new__(EmbeddingService) # Create without __init__
|
||||
|
||||
# Test with environment variable set
|
||||
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-openai-key"}):
|
||||
api_key = service._get_default_api_key("openai")
|
||||
assert api_key == "test-openai-key"
|
||||
|
||||
# Test with no environment variable
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
api_key = service._get_default_api_key("openai")
|
||||
assert api_key is None
|
||||
|
||||
# Test unknown provider
|
||||
api_key = service._get_default_api_key("unknown-provider")
|
||||
assert api_key is None
|
||||
|
||||
@patch('crewai.rag.embeddings.factory.build_embedder')
|
||||
def test_initialization_success(self, mock_build_embedder):
|
||||
"""Test successful initialization."""
|
||||
# Mock the embedding function
|
||||
mock_embedding_function = Mock()
|
||||
mock_build_embedder.return_value = mock_embedding_function
|
||||
|
||||
@@ -97,7 +93,6 @@ class TestEmbeddingService:
|
||||
assert service.config.api_key == "test-key"
|
||||
assert service._embedding_function == mock_embedding_function
|
||||
|
||||
# Verify build_embedder was called with correct config
|
||||
mock_build_embedder.assert_called_once()
|
||||
call_args = mock_build_embedder.call_args[0][0]
|
||||
assert call_args["provider"] == "openai"
|
||||
@@ -115,7 +110,6 @@ class TestEmbeddingService:
|
||||
@patch('crewai.rag.embeddings.factory.build_embedder')
|
||||
def test_embed_text_success(self, mock_build_embedder):
|
||||
"""Test successful text embedding."""
|
||||
# Mock the embedding function
|
||||
mock_embedding_function = Mock()
|
||||
mock_embedding_function.return_value = [[0.1, 0.2, 0.3]]
|
||||
mock_build_embedder.return_value = mock_embedding_function
|
||||
@@ -147,7 +141,6 @@ class TestEmbeddingService:
|
||||
@patch('crewai.rag.embeddings.factory.build_embedder')
|
||||
def test_embed_batch_success(self, mock_build_embedder):
|
||||
"""Test successful batch embedding."""
|
||||
# Mock the embedding function
|
||||
mock_embedding_function = Mock()
|
||||
mock_embedding_function.return_value = [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]
|
||||
mock_build_embedder.return_value = mock_embedding_function
|
||||
@@ -182,7 +175,6 @@ class TestEmbeddingService:
|
||||
@patch('crewai.rag.embeddings.factory.build_embedder')
|
||||
def test_validate_connection(self, mock_build_embedder):
|
||||
"""Test connection validation."""
|
||||
# Mock successful embedding
|
||||
mock_embedding_function = Mock()
|
||||
mock_embedding_function.return_value = [[0.1, 0.2, 0.3]]
|
||||
mock_build_embedder.return_value = mock_embedding_function
|
||||
@@ -191,14 +183,12 @@ class TestEmbeddingService:
|
||||
|
||||
assert service.validate_connection() is True
|
||||
|
||||
# Mock failed embedding
|
||||
mock_embedding_function.side_effect = Exception("Connection failed")
|
||||
assert service.validate_connection() is False
|
||||
|
||||
@patch('crewai.rag.embeddings.factory.build_embedder')
|
||||
def test_get_service_info(self, mock_build_embedder):
|
||||
"""Test getting service information."""
|
||||
# Mock the embedding function
|
||||
mock_embedding_function = Mock()
|
||||
mock_embedding_function.return_value = [[0.1, 0.2, 0.3]]
|
||||
mock_build_embedder.return_value = mock_embedding_function
|
||||
@@ -277,7 +267,6 @@ class TestProviderConfigurations:
|
||||
extra_config={"dimensions": 1024}
|
||||
)
|
||||
|
||||
# Check the configuration passed to build_embedder
|
||||
call_args = mock_build_embedder.call_args[0][0]
|
||||
assert call_args["provider"] == "openai"
|
||||
assert call_args["config"]["api_key"] == "test-key"
|
||||
@@ -298,7 +287,6 @@ class TestProviderConfigurations:
|
||||
extra_config={"input_type": "document"}
|
||||
)
|
||||
|
||||
# Check the configuration passed to build_embedder
|
||||
call_args = mock_build_embedder.call_args[0][0]
|
||||
assert call_args["provider"] == "voyageai"
|
||||
assert call_args["config"]["api_key"] == "test-key"
|
||||
@@ -318,7 +306,6 @@ class TestProviderConfigurations:
|
||||
api_key="test-key"
|
||||
)
|
||||
|
||||
# Check the configuration passed to build_embedder
|
||||
call_args = mock_build_embedder.call_args[0][0]
|
||||
assert call_args["provider"] == "cohere"
|
||||
assert call_args["config"]["api_key"] == "test-key"
|
||||
@@ -335,7 +322,6 @@ class TestProviderConfigurations:
|
||||
api_key="test-key"
|
||||
)
|
||||
|
||||
# Check the configuration passed to build_embedder
|
||||
call_args = mock_build_embedder.call_args[0][0]
|
||||
assert call_args["provider"] == "google-generativeai"
|
||||
assert call_args["config"]["api_key"] == "test-key"
|
||||
|
||||
@@ -126,9 +126,6 @@ class TestJSONLoader:
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
# ------------------------------
|
||||
# URL-based tests
|
||||
# ------------------------------
|
||||
|
||||
@patch("requests.get")
|
||||
def test_url_response_valid_json(self, mock_get):
|
||||
|
||||
@@ -45,7 +45,6 @@ class MockTool(BaseTool):
|
||||
)
|
||||
|
||||
|
||||
# --- Intermediate base class (like RagTool, BraveSearchToolBase) ---
|
||||
class MockIntermediateBase(BaseTool):
|
||||
"""Simulates an intermediate tool base class (e.g. RagTool, BraveSearchToolBase)."""
|
||||
|
||||
|
||||
@@ -51,9 +51,6 @@ def _mock_response(
|
||||
return resp
|
||||
|
||||
|
||||
# Fixtures
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _brave_env_and_rate_limit():
|
||||
"""Set BRAVE_API_KEY for every test. Rate limiting is per-instance (each tool starts with a fresh clock)."""
|
||||
@@ -81,8 +78,6 @@ def video_tool():
|
||||
return BraveVideoSearchTool()
|
||||
|
||||
|
||||
# Initialization
|
||||
|
||||
ALL_TOOL_CLASSES = [
|
||||
BraveWebSearchTool,
|
||||
BraveImageSearchTool,
|
||||
@@ -343,7 +338,6 @@ def test_refine_request_payload_passes_multiple_goggles_as_multiple_params(web_t
|
||||
|
||||
|
||||
# Null-like / empty value stripping
|
||||
#
|
||||
# crewAI's ensure_all_properties_required (pydantic_schema_utils.py) marks
|
||||
# every schema property as required for OpenAI strict-mode compatibility.
|
||||
# Because optional Brave API parameters look required to the LLM, it fills
|
||||
|
||||
@@ -20,7 +20,6 @@ class TestBrightDataSearchTool(unittest.TestCase):
|
||||
mock_response.text = "mock response text"
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
# Define search input
|
||||
input_data = {
|
||||
"query": "latest AI news",
|
||||
"search_engine": "google",
|
||||
@@ -46,7 +45,6 @@ class TestBrightDataSearchTool(unittest.TestCase):
|
||||
self.assertIn("Error", result)
|
||||
|
||||
def tearDown(self):
|
||||
# Clean up env vars
|
||||
pass
|
||||
|
||||
|
||||
|
||||