mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-25 20:32:36 +00:00
Compare commits
8 Commits
1.7.1
...
devin/1766
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47c698dc86 | ||
|
|
be70a04153 | ||
|
|
0c359f4df8 | ||
|
|
fe288dbe73 | ||
|
|
dc63bc2319 | ||
|
|
8d0effafec | ||
|
|
1cdbe79b34 | ||
|
|
84328d9311 |
23
.github/workflows/publish.yml
vendored
23
.github/workflows/publish.yml
vendored
@@ -1,9 +1,14 @@
|
|||||||
name: Publish to PyPI
|
name: Publish to PyPI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
repository_dispatch:
|
||||||
types: [ published ]
|
types: [deployment-tests-passed]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
description: 'Release tag to publish'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -12,7 +17,21 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
|
- name: Determine release tag
|
||||||
|
id: release
|
||||||
|
run: |
|
||||||
|
# Priority: workflow_dispatch input > repository_dispatch payload > default branch
|
||||||
|
if [ -n "${{ inputs.release_tag }}" ]; then
|
||||||
|
echo "tag=${{ inputs.release_tag }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [ -n "${{ github.event.client_payload.release_tag }}" ]; then
|
||||||
|
echo "tag=${{ github.event.client_payload.release_tag }}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "tag=" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.release.outputs.tag || github.ref }}
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
|||||||
18
.github/workflows/trigger-deployment-tests.yml
vendored
Normal file
18
.github/workflows/trigger-deployment-tests.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: Trigger Deployment Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger:
|
||||||
|
name: Trigger deployment tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Trigger deployment tests
|
||||||
|
uses: peter-evans/repository-dispatch@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CREWAI_DEPLOYMENTS_PAT }}
|
||||||
|
repository: ${{ secrets.CREWAI_DEPLOYMENTS_REPOSITORY }}
|
||||||
|
event-type: crewai-release
|
||||||
|
client-payload: '{"release_tag": "${{ github.event.release.tag_name }}", "release_name": "${{ github.event.release.name }}"}'
|
||||||
@@ -24,4 +24,10 @@ repos:
|
|||||||
rev: 0.9.3
|
rev: 0.9.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: uv-lock
|
- id: uv-lock
|
||||||
|
- repo: https://github.com/commitizen-tools/commitizen
|
||||||
|
rev: v4.10.1
|
||||||
|
hooks:
|
||||||
|
- id: commitizen
|
||||||
|
- id: commitizen-branch
|
||||||
|
stages: [ pre-push ]
|
||||||
|
|
||||||
|
|||||||
@@ -16,16 +16,17 @@ Welcome to the CrewAI AOP API reference. This API allows you to programmatically
|
|||||||
Navigate to your crew's detail page in the CrewAI AOP dashboard and copy your Bearer Token from the Status tab.
|
Navigate to your crew's detail page in the CrewAI AOP dashboard and copy your Bearer Token from the Status tab.
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="Discover Required Inputs">
|
<Step title="Discover Required Inputs">
|
||||||
Use the `GET /inputs` endpoint to see what parameters your crew expects.
|
Use the `GET /inputs` endpoint to see what parameters your crew expects.
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="Start a Crew Execution">
|
<Step title="Start a Crew Execution">
|
||||||
Call `POST /kickoff` with your inputs to start the crew execution and receive a `kickoff_id`.
|
Call `POST /kickoff` with your inputs to start the crew execution and receive
|
||||||
</Step>
|
a `kickoff_id`.
|
||||||
|
</Step>
|
||||||
|
|
||||||
<Step title="Monitor Progress">
|
<Step title="Monitor Progress">
|
||||||
Use `GET /status/{kickoff_id}` to check execution status and retrieve results.
|
Use `GET /{kickoff_id}/status` to check execution status and retrieve results.
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
@@ -40,13 +41,14 @@ curl -H "Authorization: Bearer YOUR_CREW_TOKEN" \
|
|||||||
|
|
||||||
### Token Types
|
### Token Types
|
||||||
|
|
||||||
| Token Type | Scope | Use Case |
|
| Token Type | Scope | Use Case |
|
||||||
|:-----------|:--------|:----------|
|
| :-------------------- | :------------------------ | :----------------------------------------------------------- |
|
||||||
| **Bearer Token** | Organization-level access | Full crew operations, ideal for server-to-server integration |
|
| **Bearer Token** | Organization-level access | Full crew operations, ideal for server-to-server integration |
|
||||||
| **User Bearer Token** | User-scoped access | Limited permissions, suitable for user-specific operations |
|
| **User Bearer Token** | User-scoped access | Limited permissions, suitable for user-specific operations |
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
You can find both token types in the Status tab of your crew's detail page in the CrewAI AOP dashboard.
|
You can find both token types in the Status tab of your crew's detail page in
|
||||||
|
the CrewAI AOP dashboard.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
## Base URL
|
## Base URL
|
||||||
@@ -63,29 +65,33 @@ Replace `your-crew-name` with your actual crew's URL from the dashboard.
|
|||||||
|
|
||||||
1. **Discovery**: Call `GET /inputs` to understand what your crew needs
|
1. **Discovery**: Call `GET /inputs` to understand what your crew needs
|
||||||
2. **Execution**: Submit inputs via `POST /kickoff` to start processing
|
2. **Execution**: Submit inputs via `POST /kickoff` to start processing
|
||||||
3. **Monitoring**: Poll `GET /status/{kickoff_id}` until completion
|
3. **Monitoring**: Poll `GET /{kickoff_id}/status` until completion
|
||||||
4. **Results**: Extract the final output from the completed response
|
4. **Results**: Extract the final output from the completed response
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
The API uses standard HTTP status codes:
|
The API uses standard HTTP status codes:
|
||||||
|
|
||||||
| Code | Meaning |
|
| Code | Meaning |
|
||||||
|------|:--------|
|
| ----- | :----------------------------------------- |
|
||||||
| `200` | Success |
|
| `200` | Success |
|
||||||
| `400` | Bad Request - Invalid input format |
|
| `400` | Bad Request - Invalid input format |
|
||||||
| `401` | Unauthorized - Invalid bearer token |
|
| `401` | Unauthorized - Invalid bearer token |
|
||||||
| `404` | Not Found - Resource doesn't exist |
|
| `404` | Not Found - Resource doesn't exist |
|
||||||
| `422` | Validation Error - Missing required inputs |
|
| `422` | Validation Error - Missing required inputs |
|
||||||
| `500` | Server Error - Contact support |
|
| `500` | Server Error - Contact support |
|
||||||
|
|
||||||
## Interactive Testing
|
## Interactive Testing
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
**Why no "Send" button?** Since each CrewAI AOP user has their own unique crew URL, we use **reference mode** instead of an interactive playground to avoid confusion. This shows you exactly what the requests should look like without non-functional send buttons.
|
**Why no "Send" button?** Since each CrewAI AOP user has their own unique crew
|
||||||
|
URL, we use **reference mode** instead of an interactive playground to avoid
|
||||||
|
confusion. This shows you exactly what the requests should look like without
|
||||||
|
non-functional send buttons.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
Each endpoint page shows you:
|
Each endpoint page shows you:
|
||||||
|
|
||||||
- ✅ **Exact request format** with all parameters
|
- ✅ **Exact request format** with all parameters
|
||||||
- ✅ **Response examples** for success and error cases
|
- ✅ **Response examples** for success and error cases
|
||||||
- ✅ **Code samples** in multiple languages (cURL, Python, JavaScript, etc.)
|
- ✅ **Code samples** in multiple languages (cURL, Python, JavaScript, etc.)
|
||||||
@@ -103,6 +109,7 @@ Each endpoint page shows you:
|
|||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|
||||||
**Example workflow:**
|
**Example workflow:**
|
||||||
|
|
||||||
1. **Copy this cURL example** from any endpoint page
|
1. **Copy this cURL example** from any endpoint page
|
||||||
2. **Replace `your-actual-crew-name.crewai.com`** with your real crew URL
|
2. **Replace `your-actual-crew-name.crewai.com`** with your real crew URL
|
||||||
3. **Replace the Bearer token** with your real token from the dashboard
|
3. **Replace the Bearer token** with your real token from the dashboard
|
||||||
@@ -111,10 +118,18 @@ Each endpoint page shows you:
|
|||||||
## Need Help?
|
## Need Help?
|
||||||
|
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card title="Enterprise Support" icon="headset" href="mailto:support@crewai.com">
|
<Card
|
||||||
|
title="Enterprise Support"
|
||||||
|
icon="headset"
|
||||||
|
href="mailto:support@crewai.com"
|
||||||
|
>
|
||||||
Get help with API integration and troubleshooting
|
Get help with API integration and troubleshooting
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Enterprise Dashboard" icon="chart-line" href="https://app.crewai.com">
|
<Card
|
||||||
|
title="Enterprise Dashboard"
|
||||||
|
icon="chart-line"
|
||||||
|
href="https://app.crewai.com"
|
||||||
|
>
|
||||||
Manage your crews and view execution logs
|
Manage your crews and view execution logs
|
||||||
</Card>
|
</Card>
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "GET /status/{kickoff_id}"
|
title: "GET /{kickoff_id}/status"
|
||||||
description: "Get execution status"
|
description: "Get execution status"
|
||||||
openapi: "/enterprise-api.en.yaml GET /status/{kickoff_id}"
|
openapi: "/enterprise-api.en.yaml GET /{kickoff_id}/status"
|
||||||
mode: "wide"
|
mode: "wide"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ info:
|
|||||||
|
|
||||||
1. **Discover inputs** using `GET /inputs`
|
1. **Discover inputs** using `GET /inputs`
|
||||||
2. **Start execution** using `POST /kickoff`
|
2. **Start execution** using `POST /kickoff`
|
||||||
3. **Monitor progress** using `GET /status/{kickoff_id}`
|
3. **Monitor progress** using `GET /{kickoff_id}/status`
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
contact:
|
contact:
|
||||||
name: CrewAI Support
|
name: CrewAI Support
|
||||||
@@ -63,7 +63,7 @@ paths:
|
|||||||
Use this endpoint to discover what inputs you need to provide when starting a crew execution.
|
Use this endpoint to discover what inputs you need to provide when starting a crew execution.
|
||||||
operationId: getRequiredInputs
|
operationId: getRequiredInputs
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Successfully retrieved required inputs
|
description: Successfully retrieved required inputs
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -84,13 +84,21 @@ paths:
|
|||||||
outreach_crew:
|
outreach_crew:
|
||||||
summary: Outreach crew inputs
|
summary: Outreach crew inputs
|
||||||
value:
|
value:
|
||||||
inputs: ["name", "title", "company", "industry", "our_product", "linkedin_url"]
|
inputs:
|
||||||
'401':
|
[
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
"name",
|
||||||
'404':
|
"title",
|
||||||
$ref: '#/components/responses/NotFoundError'
|
"company",
|
||||||
'500':
|
"industry",
|
||||||
$ref: '#/components/responses/ServerError'
|
"our_product",
|
||||||
|
"linkedin_url",
|
||||||
|
]
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
|
"404":
|
||||||
|
$ref: "#/components/responses/NotFoundError"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/kickoff:
|
/kickoff:
|
||||||
post:
|
post:
|
||||||
@@ -170,7 +178,7 @@ paths:
|
|||||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Crew execution started successfully
|
description: Crew execution started successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -182,24 +190,24 @@ paths:
|
|||||||
format: uuid
|
format: uuid
|
||||||
description: Unique identifier for tracking this execution
|
description: Unique identifier for tracking this execution
|
||||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||||
'400':
|
"400":
|
||||||
description: Invalid request body or missing required inputs
|
description: Invalid request body or missing required inputs
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'422':
|
"422":
|
||||||
description: Validation error - ensure all required inputs are provided
|
description: Validation error - ensure all required inputs are provided
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: "#/components/schemas/ValidationError"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/status/{kickoff_id}:
|
/{kickoff_id}/status:
|
||||||
get:
|
get:
|
||||||
summary: Get Execution Status
|
summary: Get Execution Status
|
||||||
description: |
|
description: |
|
||||||
@@ -222,15 +230,15 @@ paths:
|
|||||||
format: uuid
|
format: uuid
|
||||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Successfully retrieved execution status
|
description: Successfully retrieved execution status
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/ExecutionRunning'
|
- $ref: "#/components/schemas/ExecutionRunning"
|
||||||
- $ref: '#/components/schemas/ExecutionCompleted'
|
- $ref: "#/components/schemas/ExecutionCompleted"
|
||||||
- $ref: '#/components/schemas/ExecutionError'
|
- $ref: "#/components/schemas/ExecutionError"
|
||||||
examples:
|
examples:
|
||||||
running:
|
running:
|
||||||
summary: Execution in progress
|
summary: Execution in progress
|
||||||
@@ -262,19 +270,19 @@ paths:
|
|||||||
status: "error"
|
status: "error"
|
||||||
error: "Task execution failed: Invalid API key for external service"
|
error: "Task execution failed: Invalid API key for external service"
|
||||||
execution_time: 23.1
|
execution_time: 23.1
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'404':
|
"404":
|
||||||
description: Kickoff ID not found
|
description: Kickoff ID not found
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Execution not found"
|
error: "Execution not found"
|
||||||
message: "No execution found with ID: abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
message: "No execution found with ID: abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/resume:
|
/resume:
|
||||||
post:
|
post:
|
||||||
@@ -354,7 +362,7 @@ paths:
|
|||||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Execution resumed successfully
|
description: Execution resumed successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -381,28 +389,28 @@ paths:
|
|||||||
value:
|
value:
|
||||||
status: "retrying"
|
status: "retrying"
|
||||||
message: "Task will be retried with your feedback"
|
message: "Task will be retried with your feedback"
|
||||||
'400':
|
"400":
|
||||||
description: Invalid request body or execution not in pending state
|
description: Invalid request body or execution not in pending state
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Invalid Request"
|
error: "Invalid Request"
|
||||||
message: "Execution is not in pending human input state"
|
message: "Execution is not in pending human input state"
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'404':
|
"404":
|
||||||
description: Execution ID or Task ID not found
|
description: Execution ID or Task ID not found
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Not Found"
|
error: "Not Found"
|
||||||
message: "Execution ID not found"
|
message: "Execution ID not found"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
components:
|
components:
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
@@ -458,7 +466,7 @@ components:
|
|||||||
tasks:
|
tasks:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/TaskResult'
|
$ref: "#/components/schemas/TaskResult"
|
||||||
execution_time:
|
execution_time:
|
||||||
type: number
|
type: number
|
||||||
description: Total execution time in seconds
|
description: Total execution time in seconds
|
||||||
@@ -536,7 +544,7 @@ components:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Unauthorized"
|
error: "Unauthorized"
|
||||||
message: "Invalid or missing bearer token"
|
message: "Invalid or missing bearer token"
|
||||||
@@ -546,7 +554,7 @@ components:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Not Found"
|
error: "Not Found"
|
||||||
message: "The requested resource was not found"
|
message: "The requested resource was not found"
|
||||||
@@ -556,7 +564,7 @@ components:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Internal Server Error"
|
error: "Internal Server Error"
|
||||||
message: "An unexpected error occurred"
|
message: "An unexpected error occurred"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ info:
|
|||||||
|
|
||||||
1. **Discover inputs** using `GET /inputs`
|
1. **Discover inputs** using `GET /inputs`
|
||||||
2. **Start execution** using `POST /kickoff`
|
2. **Start execution** using `POST /kickoff`
|
||||||
3. **Monitor progress** using `GET /status/{kickoff_id}`
|
3. **Monitor progress** using `GET /{kickoff_id}/status`
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
contact:
|
contact:
|
||||||
name: CrewAI Support
|
name: CrewAI Support
|
||||||
@@ -63,7 +63,7 @@ paths:
|
|||||||
Use this endpoint to discover what inputs you need to provide when starting a crew execution.
|
Use this endpoint to discover what inputs you need to provide when starting a crew execution.
|
||||||
operationId: getRequiredInputs
|
operationId: getRequiredInputs
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Successfully retrieved required inputs
|
description: Successfully retrieved required inputs
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -84,13 +84,21 @@ paths:
|
|||||||
outreach_crew:
|
outreach_crew:
|
||||||
summary: Outreach crew inputs
|
summary: Outreach crew inputs
|
||||||
value:
|
value:
|
||||||
inputs: ["name", "title", "company", "industry", "our_product", "linkedin_url"]
|
inputs:
|
||||||
'401':
|
[
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
"name",
|
||||||
'404':
|
"title",
|
||||||
$ref: '#/components/responses/NotFoundError'
|
"company",
|
||||||
'500':
|
"industry",
|
||||||
$ref: '#/components/responses/ServerError'
|
"our_product",
|
||||||
|
"linkedin_url",
|
||||||
|
]
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
|
"404":
|
||||||
|
$ref: "#/components/responses/NotFoundError"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/kickoff:
|
/kickoff:
|
||||||
post:
|
post:
|
||||||
@@ -170,7 +178,7 @@ paths:
|
|||||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Crew execution started successfully
|
description: Crew execution started successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -182,24 +190,24 @@ paths:
|
|||||||
format: uuid
|
format: uuid
|
||||||
description: Unique identifier for tracking this execution
|
description: Unique identifier for tracking this execution
|
||||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||||
'400':
|
"400":
|
||||||
description: Invalid request body or missing required inputs
|
description: Invalid request body or missing required inputs
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'422':
|
"422":
|
||||||
description: Validation error - ensure all required inputs are provided
|
description: Validation error - ensure all required inputs are provided
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: "#/components/schemas/ValidationError"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/status/{kickoff_id}:
|
/{kickoff_id}/status:
|
||||||
get:
|
get:
|
||||||
summary: Get Execution Status
|
summary: Get Execution Status
|
||||||
description: |
|
description: |
|
||||||
@@ -222,15 +230,15 @@ paths:
|
|||||||
format: uuid
|
format: uuid
|
||||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Successfully retrieved execution status
|
description: Successfully retrieved execution status
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/ExecutionRunning'
|
- $ref: "#/components/schemas/ExecutionRunning"
|
||||||
- $ref: '#/components/schemas/ExecutionCompleted'
|
- $ref: "#/components/schemas/ExecutionCompleted"
|
||||||
- $ref: '#/components/schemas/ExecutionError'
|
- $ref: "#/components/schemas/ExecutionError"
|
||||||
examples:
|
examples:
|
||||||
running:
|
running:
|
||||||
summary: Execution in progress
|
summary: Execution in progress
|
||||||
@@ -262,19 +270,19 @@ paths:
|
|||||||
status: "error"
|
status: "error"
|
||||||
error: "Task execution failed: Invalid API key for external service"
|
error: "Task execution failed: Invalid API key for external service"
|
||||||
execution_time: 23.1
|
execution_time: 23.1
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'404':
|
"404":
|
||||||
description: Kickoff ID not found
|
description: Kickoff ID not found
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Execution not found"
|
error: "Execution not found"
|
||||||
message: "No execution found with ID: abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
message: "No execution found with ID: abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/resume:
|
/resume:
|
||||||
post:
|
post:
|
||||||
@@ -354,7 +362,7 @@ paths:
|
|||||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Execution resumed successfully
|
description: Execution resumed successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -381,28 +389,28 @@ paths:
|
|||||||
value:
|
value:
|
||||||
status: "retrying"
|
status: "retrying"
|
||||||
message: "Task will be retried with your feedback"
|
message: "Task will be retried with your feedback"
|
||||||
'400':
|
"400":
|
||||||
description: Invalid request body or execution not in pending state
|
description: Invalid request body or execution not in pending state
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Invalid Request"
|
error: "Invalid Request"
|
||||||
message: "Execution is not in pending human input state"
|
message: "Execution is not in pending human input state"
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'404':
|
"404":
|
||||||
description: Execution ID or Task ID not found
|
description: Execution ID or Task ID not found
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Not Found"
|
error: "Not Found"
|
||||||
message: "Execution ID not found"
|
message: "Execution ID not found"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
components:
|
components:
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
@@ -458,7 +466,7 @@ components:
|
|||||||
tasks:
|
tasks:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/TaskResult'
|
$ref: "#/components/schemas/TaskResult"
|
||||||
execution_time:
|
execution_time:
|
||||||
type: number
|
type: number
|
||||||
description: Total execution time in seconds
|
description: Total execution time in seconds
|
||||||
@@ -536,7 +544,7 @@ components:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Unauthorized"
|
error: "Unauthorized"
|
||||||
message: "Invalid or missing bearer token"
|
message: "Invalid or missing bearer token"
|
||||||
@@ -546,7 +554,7 @@ components:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Not Found"
|
error: "Not Found"
|
||||||
message: "The requested resource was not found"
|
message: "The requested resource was not found"
|
||||||
@@ -556,7 +564,7 @@ components:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Internal Server Error"
|
error: "Internal Server Error"
|
||||||
message: "An unexpected error occurred"
|
message: "An unexpected error occurred"
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ paths:
|
|||||||
'500':
|
'500':
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: '#/components/responses/ServerError'
|
||||||
|
|
||||||
/status/{kickoff_id}:
|
/{kickoff_id}/status:
|
||||||
get:
|
get:
|
||||||
summary: 실행 상태 조회
|
summary: 실행 상태 조회
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ info:
|
|||||||
|
|
||||||
1. **Descubra os inputs** usando `GET /inputs`
|
1. **Descubra os inputs** usando `GET /inputs`
|
||||||
2. **Inicie a execução** usando `POST /kickoff`
|
2. **Inicie a execução** usando `POST /kickoff`
|
||||||
3. **Monitore o progresso** usando `GET /status/{kickoff_id}`
|
3. **Monitore o progresso** usando `GET /{kickoff_id}/status`
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
contact:
|
contact:
|
||||||
name: CrewAI Suporte
|
name: CrewAI Suporte
|
||||||
@@ -56,7 +56,7 @@ paths:
|
|||||||
Retorna a lista de parâmetros de entrada que sua crew espera.
|
Retorna a lista de parâmetros de entrada que sua crew espera.
|
||||||
operationId: getRequiredInputs
|
operationId: getRequiredInputs
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Inputs requeridos obtidos com sucesso
|
description: Inputs requeridos obtidos com sucesso
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -69,12 +69,12 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
description: Nomes dos parâmetros de entrada
|
description: Nomes dos parâmetros de entrada
|
||||||
example: ["budget", "interests", "duration", "age"]
|
example: ["budget", "interests", "duration", "age"]
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'404':
|
"404":
|
||||||
$ref: '#/components/responses/NotFoundError'
|
$ref: "#/components/responses/NotFoundError"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/kickoff:
|
/kickoff:
|
||||||
post:
|
post:
|
||||||
@@ -104,7 +104,7 @@ paths:
|
|||||||
age: "35"
|
age: "35"
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Execução iniciada com sucesso
|
description: Execução iniciada com sucesso
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -115,12 +115,12 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/status/{kickoff_id}:
|
/{kickoff_id}/status:
|
||||||
get:
|
get:
|
||||||
summary: Obter Status da Execução
|
summary: Obter Status da Execução
|
||||||
description: |
|
description: |
|
||||||
@@ -136,25 +136,25 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Status recuperado com sucesso
|
description: Status recuperado com sucesso
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/ExecutionRunning'
|
- $ref: "#/components/schemas/ExecutionRunning"
|
||||||
- $ref: '#/components/schemas/ExecutionCompleted'
|
- $ref: "#/components/schemas/ExecutionCompleted"
|
||||||
- $ref: '#/components/schemas/ExecutionError'
|
- $ref: "#/components/schemas/ExecutionError"
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'404':
|
"404":
|
||||||
description: Kickoff ID não encontrado
|
description: Kickoff ID não encontrado
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
/resume:
|
/resume:
|
||||||
post:
|
post:
|
||||||
@@ -234,7 +234,7 @@ paths:
|
|||||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Execution resumed successfully
|
description: Execution resumed successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@@ -261,28 +261,28 @@ paths:
|
|||||||
value:
|
value:
|
||||||
status: "retrying"
|
status: "retrying"
|
||||||
message: "Task will be retried with your feedback"
|
message: "Task will be retried with your feedback"
|
||||||
'400':
|
"400":
|
||||||
description: Invalid request body or execution not in pending state
|
description: Invalid request body or execution not in pending state
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Invalid Request"
|
error: "Invalid Request"
|
||||||
message: "Execution is not in pending human input state"
|
message: "Execution is not in pending human input state"
|
||||||
'401':
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: "#/components/responses/UnauthorizedError"
|
||||||
'404':
|
"404":
|
||||||
description: Execution ID or Task ID not found
|
description: Execution ID or Task ID not found
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
example:
|
example:
|
||||||
error: "Not Found"
|
error: "Not Found"
|
||||||
message: "Execution ID not found"
|
message: "Execution ID not found"
|
||||||
'500':
|
"500":
|
||||||
$ref: '#/components/responses/ServerError'
|
$ref: "#/components/responses/ServerError"
|
||||||
|
|
||||||
components:
|
components:
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
@@ -324,7 +324,7 @@ components:
|
|||||||
tasks:
|
tasks:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/TaskResult'
|
$ref: "#/components/schemas/TaskResult"
|
||||||
execution_time:
|
execution_time:
|
||||||
type: number
|
type: number
|
||||||
|
|
||||||
@@ -380,16 +380,16 @@ components:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
NotFoundError:
|
NotFoundError:
|
||||||
description: Recurso não encontrado
|
description: Recurso não encontrado
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
ServerError:
|
ServerError:
|
||||||
description: Erro interno do servidor
|
description: Erro interno do servidor
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: "#/components/schemas/Error"
|
||||||
|
|||||||
@@ -16,16 +16,17 @@ CrewAI 엔터프라이즈 API 참고 자료에 오신 것을 환영합니다.
|
|||||||
CrewAI AOP 대시보드에서 자신의 crew 상세 페이지로 이동하여 Status 탭에서 Bearer Token을 복사하세요.
|
CrewAI AOP 대시보드에서 자신의 crew 상세 페이지로 이동하여 Status 탭에서 Bearer Token을 복사하세요.
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="필수 입력값 확인하기">
|
<Step title="필수 입력값 확인하기">
|
||||||
`GET /inputs` 엔드포인트를 사용하여 crew가 기대하는 파라미터를 확인하세요.
|
`GET /inputs` 엔드포인트를 사용하여 crew가 기대하는 파라미터를 확인하세요.
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="Crew 실행 시작하기">
|
<Step title="Crew 실행 시작하기">
|
||||||
입력값과 함께 `POST /kickoff`를 호출하여 crew 실행을 시작하고 `kickoff_id`를 받으세요.
|
입력값과 함께 `POST /kickoff`를 호출하여 crew 실행을 시작하고 `kickoff_id`를
|
||||||
</Step>
|
받으세요.
|
||||||
|
</Step>
|
||||||
|
|
||||||
<Step title="진행 상황 모니터링">
|
<Step title="진행 상황 모니터링">
|
||||||
`GET /status/{kickoff_id}`를 사용하여 실행 상태를 확인하고 결과를 조회하세요.
|
`GET /{kickoff_id}/status`를 사용하여 실행 상태를 확인하고 결과를 조회하세요.
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
@@ -40,13 +41,14 @@ curl -H "Authorization: Bearer YOUR_CREW_TOKEN" \
|
|||||||
|
|
||||||
### 토큰 유형
|
### 토큰 유형
|
||||||
|
|
||||||
| 토큰 유형 | 범위 | 사용 사례 |
|
| 토큰 유형 | 범위 | 사용 사례 |
|
||||||
|:-----------|:--------|:----------|
|
| :-------------------- | :--------------- | :------------------------------------ |
|
||||||
| **Bearer Token** | 조직 단위 접근 | 전체 crew 운영, 서버 간 통합에 이상적 |
|
| **Bearer Token** | 조직 단위 접근 | 전체 crew 운영, 서버 간 통합에 이상적 |
|
||||||
| **User Bearer Token** | 사용자 범위 접근 | 제한된 권한, 사용자별 작업에 적합 |
|
| **User Bearer Token** | 사용자 범위 접근 | 제한된 권한, 사용자별 작업에 적합 |
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
두 토큰 유형 모두 CrewAI AOP 대시보드의 crew 상세 페이지 Status 탭에서 확인할 수 있습니다.
|
두 토큰 유형 모두 CrewAI AOP 대시보드의 crew 상세 페이지 Status 탭에서 확인할
|
||||||
|
수 있습니다.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
## 기본 URL
|
## 기본 URL
|
||||||
@@ -63,29 +65,33 @@ https://your-crew-name.crewai.com
|
|||||||
|
|
||||||
1. **탐색**: `GET /inputs`를 호출하여 crew가 필요한 것을 파악합니다.
|
1. **탐색**: `GET /inputs`를 호출하여 crew가 필요한 것을 파악합니다.
|
||||||
2. **실행**: `POST /kickoff`를 통해 입력값을 제출하여 처리를 시작합니다.
|
2. **실행**: `POST /kickoff`를 통해 입력값을 제출하여 처리를 시작합니다.
|
||||||
3. **모니터링**: 완료될 때까지 `GET /status/{kickoff_id}`를 주기적으로 조회합니다.
|
3. **모니터링**: 완료될 때까지 `GET /{kickoff_id}/status`를 주기적으로 조회합니다.
|
||||||
4. **결과**: 완료된 응답에서 최종 출력을 추출합니다.
|
4. **결과**: 완료된 응답에서 최종 출력을 추출합니다.
|
||||||
|
|
||||||
## 오류 처리
|
## 오류 처리
|
||||||
|
|
||||||
API는 표준 HTTP 상태 코드를 사용합니다:
|
API는 표준 HTTP 상태 코드를 사용합니다:
|
||||||
|
|
||||||
| 코드 | 의미 |
|
| 코드 | 의미 |
|
||||||
|------|:--------|
|
| ----- | :------------------------------------ |
|
||||||
| `200` | 성공 |
|
| `200` | 성공 |
|
||||||
| `400` | 잘못된 요청 - 잘못된 입력 형식 |
|
| `400` | 잘못된 요청 - 잘못된 입력 형식 |
|
||||||
| `401` | 인증 실패 - 잘못된 베어러 토큰 |
|
| `401` | 인증 실패 - 잘못된 베어러 토큰 |
|
||||||
| `404` | 찾을 수 없음 - 리소스가 존재하지 않음 |
|
| `404` | 찾을 수 없음 - 리소스가 존재하지 않음 |
|
||||||
| `422` | 유효성 검사 오류 - 필수 입력 누락 |
|
| `422` | 유효성 검사 오류 - 필수 입력 누락 |
|
||||||
| `500` | 서버 오류 - 지원팀에 문의하십시오 |
|
| `500` | 서버 오류 - 지원팀에 문의하십시오 |
|
||||||
|
|
||||||
## 인터랙티브 테스트
|
## 인터랙티브 테스트
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
**왜 "전송" 버튼이 없나요?** 각 CrewAI AOP 사용자는 고유한 crew URL을 가지므로, 혼동을 피하기 위해 인터랙티브 플레이그라운드 대신 **참조 모드**를 사용합니다. 이를 통해 비작동 전송 버튼 없이 요청이 어떻게 생겼는지 정확히 보여줍니다.
|
**왜 "전송" 버튼이 없나요?** 각 CrewAI AOP 사용자는 고유한 crew URL을
|
||||||
|
가지므로, 혼동을 피하기 위해 인터랙티브 플레이그라운드 대신 **참조 모드**를
|
||||||
|
사용합니다. 이를 통해 비작동 전송 버튼 없이 요청이 어떻게 생겼는지 정확히
|
||||||
|
보여줍니다.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
각 엔드포인트 페이지에서는 다음을 확인할 수 있습니다:
|
각 엔드포인트 페이지에서는 다음을 확인할 수 있습니다:
|
||||||
|
|
||||||
- ✅ 모든 파라미터가 포함된 **정확한 요청 형식**
|
- ✅ 모든 파라미터가 포함된 **정확한 요청 형식**
|
||||||
- ✅ 성공 및 오류 사례에 대한 **응답 예시**
|
- ✅ 성공 및 오류 사례에 대한 **응답 예시**
|
||||||
- ✅ 여러 언어(cURL, Python, JavaScript 등)로 제공되는 **코드 샘플**
|
- ✅ 여러 언어(cURL, Python, JavaScript 등)로 제공되는 **코드 샘플**
|
||||||
@@ -103,6 +109,7 @@ API는 표준 HTTP 상태 코드를 사용합니다:
|
|||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|
||||||
**예시 작업 흐름:**
|
**예시 작업 흐름:**
|
||||||
|
|
||||||
1. **cURL 예제를 복사**합니다 (엔드포인트 페이지에서)
|
1. **cURL 예제를 복사**합니다 (엔드포인트 페이지에서)
|
||||||
2. **`your-actual-crew-name.crewai.com`**을(를) 실제 crew URL로 교체합니다
|
2. **`your-actual-crew-name.crewai.com`**을(를) 실제 crew URL로 교체합니다
|
||||||
3. **Bearer 토큰을** 대시보드에서 복사한 실제 토큰으로 교체합니다
|
3. **Bearer 토큰을** 대시보드에서 복사한 실제 토큰으로 교체합니다
|
||||||
@@ -111,10 +118,18 @@ API는 표준 HTTP 상태 코드를 사용합니다:
|
|||||||
## 도움이 필요하신가요?
|
## 도움이 필요하신가요?
|
||||||
|
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card title="Enterprise Support" icon="headset" href="mailto:support@crewai.com">
|
<Card
|
||||||
|
title="Enterprise Support"
|
||||||
|
icon="headset"
|
||||||
|
href="mailto:support@crewai.com"
|
||||||
|
>
|
||||||
API 통합 및 문제 해결에 대한 지원을 받으세요
|
API 통합 및 문제 해결에 대한 지원을 받으세요
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Enterprise Dashboard" icon="chart-line" href="https://app.crewai.com">
|
<Card
|
||||||
|
title="Enterprise Dashboard"
|
||||||
|
icon="chart-line"
|
||||||
|
href="https://app.crewai.com"
|
||||||
|
>
|
||||||
crew를 관리하고 실행 로그를 확인하세요
|
crew를 관리하고 실행 로그를 확인하세요
|
||||||
</Card>
|
</Card>
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "GET /status/{kickoff_id}"
|
title: "GET /{kickoff_id}/status"
|
||||||
description: "실행 상태 조회"
|
description: "실행 상태 조회"
|
||||||
openapi: "/enterprise-api.ko.yaml GET /status/{kickoff_id}"
|
openapi: "/enterprise-api.ko.yaml GET /{kickoff_id}/status"
|
||||||
mode: "wide"
|
mode: "wide"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,16 +16,17 @@ Bem-vindo à referência da API do CrewAI AOP. Esta API permite que você intera
|
|||||||
Navegue até a página de detalhes do seu crew no painel do CrewAI AOP e copie seu Bearer Token na aba Status.
|
Navegue até a página de detalhes do seu crew no painel do CrewAI AOP e copie seu Bearer Token na aba Status.
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="Descubra os Inputs Necessários">
|
<Step title="Descubra os Inputs Necessários">
|
||||||
Use o endpoint `GET /inputs` para ver quais parâmetros seu crew espera.
|
Use o endpoint `GET /inputs` para ver quais parâmetros seu crew espera.
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="Inicie uma Execução de Crew">
|
<Step title="Inicie uma Execução de Crew">
|
||||||
Chame `POST /kickoff` com seus inputs para iniciar a execução do crew e receber um `kickoff_id`.
|
Chame `POST /kickoff` com seus inputs para iniciar a execução do crew e
|
||||||
</Step>
|
receber um `kickoff_id`.
|
||||||
|
</Step>
|
||||||
|
|
||||||
<Step title="Monitore o Progresso">
|
<Step title="Monitore o Progresso">
|
||||||
Use `GET /status/{kickoff_id}` para checar o status da execução e recuperar os resultados.
|
Use `GET /{kickoff_id}/status` para checar o status da execução e recuperar os resultados.
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
@@ -40,13 +41,14 @@ curl -H "Authorization: Bearer YOUR_CREW_TOKEN" \
|
|||||||
|
|
||||||
### Tipos de Token
|
### Tipos de Token
|
||||||
|
|
||||||
| Tipo de Token | Escopo | Caso de Uso |
|
| Tipo de Token | Escopo | Caso de Uso |
|
||||||
|:--------------------|:------------------------|:---------------------------------------------------------|
|
| :-------------------- | :----------------------------- | :------------------------------------------------------------------- |
|
||||||
| **Bearer Token** | Acesso em nível de organização | Operações completas de crew, ideal para integração server-to-server |
|
| **Bearer Token** | Acesso em nível de organização | Operações completas de crew, ideal para integração server-to-server |
|
||||||
| **User Bearer Token** | Acesso com escopo de usuário | Permissões limitadas, adequado para operações específicas de usuário |
|
| **User Bearer Token** | Acesso com escopo de usuário | Permissões limitadas, adequado para operações específicas de usuário |
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Você pode encontrar ambos os tipos de token na aba Status da página de detalhes do seu crew no painel do CrewAI AOP.
|
Você pode encontrar ambos os tipos de token na aba Status da página de
|
||||||
|
detalhes do seu crew no painel do CrewAI AOP.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
## URL Base
|
## URL Base
|
||||||
@@ -63,29 +65,33 @@ Substitua `your-crew-name` pela URL real do seu crew no painel.
|
|||||||
|
|
||||||
1. **Descoberta**: Chame `GET /inputs` para entender o que seu crew precisa
|
1. **Descoberta**: Chame `GET /inputs` para entender o que seu crew precisa
|
||||||
2. **Execução**: Envie os inputs via `POST /kickoff` para iniciar o processamento
|
2. **Execução**: Envie os inputs via `POST /kickoff` para iniciar o processamento
|
||||||
3. **Monitoramento**: Faça polling em `GET /status/{kickoff_id}` até a conclusão
|
3. **Monitoramento**: Faça polling em `GET /{kickoff_id}/status` até a conclusão
|
||||||
4. **Resultados**: Extraia o output final da resposta concluída
|
4. **Resultados**: Extraia o output final da resposta concluída
|
||||||
|
|
||||||
## Tratamento de Erros
|
## Tratamento de Erros
|
||||||
|
|
||||||
A API utiliza códigos de status HTTP padrão:
|
A API utiliza códigos de status HTTP padrão:
|
||||||
|
|
||||||
| Código | Significado |
|
| Código | Significado |
|
||||||
|--------|:--------------------------------------|
|
| ------ | :----------------------------------------------- |
|
||||||
| `200` | Sucesso |
|
| `200` | Sucesso |
|
||||||
| `400` | Requisição Inválida - Formato de input inválido |
|
| `400` | Requisição Inválida - Formato de input inválido |
|
||||||
| `401` | Não Autorizado - Bearer token inválido |
|
| `401` | Não Autorizado - Bearer token inválido |
|
||||||
| `404` | Não Encontrado - Recurso não existe |
|
| `404` | Não Encontrado - Recurso não existe |
|
||||||
| `422` | Erro de Validação - Inputs obrigatórios ausentes |
|
| `422` | Erro de Validação - Inputs obrigatórios ausentes |
|
||||||
| `500` | Erro no Servidor - Contate o suporte |
|
| `500` | Erro no Servidor - Contate o suporte |
|
||||||
|
|
||||||
## Testes Interativos
|
## Testes Interativos
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
**Por que não há botão "Enviar"?** Como cada usuário do CrewAI AOP possui sua própria URL de crew, utilizamos o **modo referência** em vez de um playground interativo para evitar confusão. Isso mostra exatamente como as requisições devem ser feitas, sem botões de envio não funcionais.
|
**Por que não há botão "Enviar"?** Como cada usuário do CrewAI AOP possui sua
|
||||||
|
própria URL de crew, utilizamos o **modo referência** em vez de um playground
|
||||||
|
interativo para evitar confusão. Isso mostra exatamente como as requisições
|
||||||
|
devem ser feitas, sem botões de envio não funcionais.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
Cada página de endpoint mostra para você:
|
Cada página de endpoint mostra para você:
|
||||||
|
|
||||||
- ✅ **Formato exato da requisição** com todos os parâmetros
|
- ✅ **Formato exato da requisição** com todos os parâmetros
|
||||||
- ✅ **Exemplos de resposta** para casos de sucesso e erro
|
- ✅ **Exemplos de resposta** para casos de sucesso e erro
|
||||||
- ✅ **Exemplos de código** em várias linguagens (cURL, Python, JavaScript, etc.)
|
- ✅ **Exemplos de código** em várias linguagens (cURL, Python, JavaScript, etc.)
|
||||||
@@ -103,6 +109,7 @@ Cada página de endpoint mostra para você:
|
|||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|
||||||
**Exemplo de fluxo:**
|
**Exemplo de fluxo:**
|
||||||
|
|
||||||
1. **Copie este exemplo cURL** de qualquer página de endpoint
|
1. **Copie este exemplo cURL** de qualquer página de endpoint
|
||||||
2. **Substitua `your-actual-crew-name.crewai.com`** pela URL real do seu crew
|
2. **Substitua `your-actual-crew-name.crewai.com`** pela URL real do seu crew
|
||||||
3. **Substitua o Bearer token** pelo seu token real do painel
|
3. **Substitua o Bearer token** pelo seu token real do painel
|
||||||
@@ -111,10 +118,18 @@ Cada página de endpoint mostra para você:
|
|||||||
## Precisa de Ajuda?
|
## Precisa de Ajuda?
|
||||||
|
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card title="Suporte Enterprise" icon="headset" href="mailto:support@crewai.com">
|
<Card
|
||||||
|
title="Suporte Enterprise"
|
||||||
|
icon="headset"
|
||||||
|
href="mailto:support@crewai.com"
|
||||||
|
>
|
||||||
Obtenha ajuda com integração da API e resolução de problemas
|
Obtenha ajuda com integração da API e resolução de problemas
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Painel Enterprise" icon="chart-line" href="https://app.crewai.com">
|
<Card
|
||||||
|
title="Painel Enterprise"
|
||||||
|
icon="chart-line"
|
||||||
|
href="https://app.crewai.com"
|
||||||
|
>
|
||||||
Gerencie seus crews e visualize logs de execução
|
Gerencie seus crews e visualize logs de execução
|
||||||
</Card>
|
</Card>
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "GET /status/{kickoff_id}"
|
title: "GET /{kickoff_id}/status"
|
||||||
description: "Obter o status da execução"
|
description: "Obter o status da execução"
|
||||||
openapi: "/enterprise-api.pt-BR.yaml GET /status/{kickoff_id}"
|
openapi: "/enterprise-api.pt-BR.yaml GET /{kickoff_id}/status"
|
||||||
mode: "wide"
|
mode: "wide"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ dependencies = [
|
|||||||
"pytube~=15.0.0",
|
"pytube~=15.0.0",
|
||||||
"requests~=2.32.5",
|
"requests~=2.32.5",
|
||||||
"docker~=7.1.0",
|
"docker~=7.1.0",
|
||||||
"crewai==1.7.1",
|
"crewai==1.7.2",
|
||||||
"lancedb~=0.5.4",
|
"lancedb~=0.5.4",
|
||||||
"tiktoken~=0.8.0",
|
"tiktoken~=0.8.0",
|
||||||
"beautifulsoup4~=4.13.4",
|
"beautifulsoup4~=4.13.4",
|
||||||
|
|||||||
@@ -291,4 +291,4 @@ __all__ = [
|
|||||||
"ZapierActionTools",
|
"ZapierActionTools",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.7.1"
|
__version__ = "1.7.2"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""Crewai Enterprise Tools."""
|
"""Crewai Enterprise Tools."""
|
||||||
|
import os
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from typing import Any, Optional, Union, cast, get_origin
|
from typing import Any, Optional, Union, cast, get_origin
|
||||||
@@ -432,7 +432,11 @@ class CrewAIPlatformActionTool(BaseTool):
|
|||||||
payload = cleaned_kwargs
|
payload = cleaned_kwargs
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
url=api_url, headers=headers, json=payload, timeout=60
|
url=api_url,
|
||||||
|
headers=headers,
|
||||||
|
json=payload,
|
||||||
|
timeout=60,
|
||||||
|
verify=os.environ.get("CREWAI_FACTORY", "false").lower() != "true",
|
||||||
)
|
)
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
import os
|
||||||
from crewai.tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ class CrewaiPlatformToolBuilder:
|
|||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=30,
|
timeout=30,
|
||||||
params={"apps": ",".join(self._apps)},
|
params={"apps": ",".join(self._apps)},
|
||||||
|
verify=os.environ.get("CREWAI_FACTORY", "false").lower() != "true",
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from typing import Union, get_args, get_origin
|
from typing import Union, get_args, get_origin
|
||||||
|
from unittest.mock import patch, Mock
|
||||||
|
import os
|
||||||
|
|
||||||
from crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool import (
|
from crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool import (
|
||||||
CrewAIPlatformActionTool,
|
CrewAIPlatformActionTool,
|
||||||
@@ -249,3 +251,109 @@ class TestSchemaProcessing:
|
|||||||
result_type = tool._process_schema_type(test_schema, "TestFieldAllOfMixed")
|
result_type = tool._process_schema_type(test_schema, "TestFieldAllOfMixed")
|
||||||
|
|
||||||
assert result_type is str
|
assert result_type is str
|
||||||
|
|
||||||
|
class TestCrewAIPlatformActionToolVerify:
|
||||||
|
"""Test suite for SSL verification behavior based on CREWAI_FACTORY environment variable"""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.action_schema = {
|
||||||
|
"function": {
|
||||||
|
"name": "test_action",
|
||||||
|
"parameters": {
|
||||||
|
"properties": {
|
||||||
|
"test_param": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Test parameter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_test_tool(self):
|
||||||
|
return CrewAIPlatformActionTool(
|
||||||
|
description="Test action tool",
|
||||||
|
action_name="test_action",
|
||||||
|
action_schema=self.action_schema
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"}, clear=True)
|
||||||
|
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post")
|
||||||
|
def test_run_with_ssl_verification_default(self, mock_post):
|
||||||
|
"""Test that _run uses SSL verification by default when CREWAI_FACTORY is not set"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.ok = True
|
||||||
|
mock_response.json.return_value = {"result": "success"}
|
||||||
|
mock_post.return_value = mock_response
|
||||||
|
|
||||||
|
tool = self.create_test_tool()
|
||||||
|
tool._run(test_param="test_value")
|
||||||
|
|
||||||
|
mock_post.assert_called_once()
|
||||||
|
call_args = mock_post.call_args
|
||||||
|
assert call_args.kwargs["verify"] is True
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "false"}, clear=True)
|
||||||
|
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post")
|
||||||
|
def test_run_with_ssl_verification_factory_false(self, mock_post):
|
||||||
|
"""Test that _run uses SSL verification when CREWAI_FACTORY is 'false'"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.ok = True
|
||||||
|
mock_response.json.return_value = {"result": "success"}
|
||||||
|
mock_post.return_value = mock_response
|
||||||
|
|
||||||
|
tool = self.create_test_tool()
|
||||||
|
tool._run(test_param="test_value")
|
||||||
|
|
||||||
|
mock_post.assert_called_once()
|
||||||
|
call_args = mock_post.call_args
|
||||||
|
assert call_args.kwargs["verify"] is True
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "FALSE"}, clear=True)
|
||||||
|
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post")
|
||||||
|
def test_run_with_ssl_verification_factory_false_uppercase(self, mock_post):
|
||||||
|
"""Test that _run uses SSL verification when CREWAI_FACTORY is 'FALSE' (case-insensitive)"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.ok = True
|
||||||
|
mock_response.json.return_value = {"result": "success"}
|
||||||
|
mock_post.return_value = mock_response
|
||||||
|
|
||||||
|
tool = self.create_test_tool()
|
||||||
|
tool._run(test_param="test_value")
|
||||||
|
|
||||||
|
mock_post.assert_called_once()
|
||||||
|
call_args = mock_post.call_args
|
||||||
|
assert call_args.kwargs["verify"] is True
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "true"}, clear=True)
|
||||||
|
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post")
|
||||||
|
def test_run_without_ssl_verification_factory_true(self, mock_post):
|
||||||
|
"""Test that _run disables SSL verification when CREWAI_FACTORY is 'true'"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.ok = True
|
||||||
|
mock_response.json.return_value = {"result": "success"}
|
||||||
|
mock_post.return_value = mock_response
|
||||||
|
|
||||||
|
tool = self.create_test_tool()
|
||||||
|
tool._run(test_param="test_value")
|
||||||
|
|
||||||
|
mock_post.assert_called_once()
|
||||||
|
call_args = mock_post.call_args
|
||||||
|
assert call_args.kwargs["verify"] is False
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "TRUE"}, clear=True)
|
||||||
|
@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post")
|
||||||
|
def test_run_without_ssl_verification_factory_true_uppercase(self, mock_post):
|
||||||
|
"""Test that _run disables SSL verification when CREWAI_FACTORY is 'TRUE' (case-insensitive)"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.ok = True
|
||||||
|
mock_response.json.return_value = {"result": "success"}
|
||||||
|
mock_post.return_value = mock_response
|
||||||
|
|
||||||
|
tool = self.create_test_tool()
|
||||||
|
tool._run(test_param="test_value")
|
||||||
|
|
||||||
|
mock_post.assert_called_once()
|
||||||
|
call_args = mock_post.call_args
|
||||||
|
assert call_args.kwargs["verify"] is False
|
||||||
|
|||||||
@@ -258,3 +258,98 @@ class TestCrewaiPlatformToolBuilder(unittest.TestCase):
|
|||||||
assert "simple_string" in description_text
|
assert "simple_string" in description_text
|
||||||
assert "nested_object" in description_text
|
assert "nested_object" in description_text
|
||||||
assert "array_prop" in description_text
|
assert "array_prop" in description_text
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestCrewaiPlatformToolBuilderVerify(unittest.TestCase):
|
||||||
|
"""Test suite for SSL verification behavior in CrewaiPlatformToolBuilder"""
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"}, clear=True)
|
||||||
|
@patch(
|
||||||
|
"crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get"
|
||||||
|
)
|
||||||
|
def test_fetch_actions_with_ssl_verification_default(self, mock_get):
|
||||||
|
"""Test that _fetch_actions uses SSL verification by default when CREWAI_FACTORY is not set"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_response.json.return_value = {"actions": {}}
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
builder = CrewaiPlatformToolBuilder(apps=["github"])
|
||||||
|
builder._fetch_actions()
|
||||||
|
|
||||||
|
mock_get.assert_called_once()
|
||||||
|
call_args = mock_get.call_args
|
||||||
|
assert call_args.kwargs["verify"] is True
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "false"}, clear=True)
|
||||||
|
@patch(
|
||||||
|
"crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get"
|
||||||
|
)
|
||||||
|
def test_fetch_actions_with_ssl_verification_factory_false(self, mock_get):
|
||||||
|
"""Test that _fetch_actions uses SSL verification when CREWAI_FACTORY is 'false'"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_response.json.return_value = {"actions": {}}
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
builder = CrewaiPlatformToolBuilder(apps=["github"])
|
||||||
|
builder._fetch_actions()
|
||||||
|
|
||||||
|
mock_get.assert_called_once()
|
||||||
|
call_args = mock_get.call_args
|
||||||
|
assert call_args.kwargs["verify"] is True
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "FALSE"}, clear=True)
|
||||||
|
@patch(
|
||||||
|
"crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get"
|
||||||
|
)
|
||||||
|
def test_fetch_actions_with_ssl_verification_factory_false_uppercase(self, mock_get):
|
||||||
|
"""Test that _fetch_actions uses SSL verification when CREWAI_FACTORY is 'FALSE' (case-insensitive)"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_response.json.return_value = {"actions": {}}
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
builder = CrewaiPlatformToolBuilder(apps=["github"])
|
||||||
|
builder._fetch_actions()
|
||||||
|
|
||||||
|
mock_get.assert_called_once()
|
||||||
|
call_args = mock_get.call_args
|
||||||
|
assert call_args.kwargs["verify"] is True
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "true"}, clear=True)
|
||||||
|
@patch(
|
||||||
|
"crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get"
|
||||||
|
)
|
||||||
|
def test_fetch_actions_without_ssl_verification_factory_true(self, mock_get):
|
||||||
|
"""Test that _fetch_actions disables SSL verification when CREWAI_FACTORY is 'true'"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_response.json.return_value = {"actions": {}}
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
builder = CrewaiPlatformToolBuilder(apps=["github"])
|
||||||
|
builder._fetch_actions()
|
||||||
|
|
||||||
|
mock_get.assert_called_once()
|
||||||
|
call_args = mock_get.call_args
|
||||||
|
assert call_args.kwargs["verify"] is False
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "TRUE"}, clear=True)
|
||||||
|
@patch(
|
||||||
|
"crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get"
|
||||||
|
)
|
||||||
|
def test_fetch_actions_without_ssl_verification_factory_true_uppercase(self, mock_get):
|
||||||
|
"""Test that _fetch_actions disables SSL verification when CREWAI_FACTORY is 'TRUE' (case-insensitive)"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.raise_for_status.return_value = None
|
||||||
|
mock_response.json.return_value = {"actions": {}}
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
builder = CrewaiPlatformToolBuilder(apps=["github"])
|
||||||
|
builder._fetch_actions()
|
||||||
|
|
||||||
|
mock_get.assert_called_once()
|
||||||
|
call_args = mock_get.call_args
|
||||||
|
assert call_args.kwargs["verify"] is False
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
|
|||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
tools = [
|
tools = [
|
||||||
"crewai-tools==1.7.1",
|
"crewai-tools==1.7.2",
|
||||||
]
|
]
|
||||||
embeddings = [
|
embeddings = [
|
||||||
"tiktoken~=0.8.0"
|
"tiktoken~=0.8.0"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
|
|||||||
|
|
||||||
_suppress_pydantic_deprecation_warnings()
|
_suppress_pydantic_deprecation_warnings()
|
||||||
|
|
||||||
__version__ = "1.7.1"
|
__version__ = "1.7.2"
|
||||||
_telemetry_submitted = False
|
_telemetry_submitted = False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -439,6 +439,12 @@ class Agent(BaseAgent):
|
|||||||
def _execute_with_timeout(self, task_prompt: str, task: Task, timeout: int) -> Any:
|
def _execute_with_timeout(self, task_prompt: str, task: Task, timeout: int) -> Any:
|
||||||
"""Execute a task with a timeout.
|
"""Execute a task with a timeout.
|
||||||
|
|
||||||
|
This method uses cooperative cancellation to ensure clean thread cleanup.
|
||||||
|
When a timeout occurs:
|
||||||
|
1. The executor's deadline is set, causing the worker to check and exit
|
||||||
|
2. The executor is shut down with wait=False to return control promptly
|
||||||
|
3. The worker thread will exit cleanly when it checks the deadline
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
task_prompt: The prompt to send to the agent.
|
task_prompt: The prompt to send to the agent.
|
||||||
task: The task being executed.
|
task: The task being executed.
|
||||||
@@ -453,7 +459,11 @@ class Agent(BaseAgent):
|
|||||||
"""
|
"""
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
|
||||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
if self.agent_executor:
|
||||||
|
self.agent_executor.set_execution_deadline(timeout)
|
||||||
|
|
||||||
|
executor = concurrent.futures.ThreadPoolExecutor(thread_name_prefix="crewai_task")
|
||||||
|
try:
|
||||||
future = executor.submit(
|
future = executor.submit(
|
||||||
self._execute_without_timeout, task_prompt=task_prompt, task=task
|
self._execute_without_timeout, task_prompt=task_prompt, task=task
|
||||||
)
|
)
|
||||||
@@ -468,6 +478,10 @@ class Agent(BaseAgent):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
future.cancel()
|
future.cancel()
|
||||||
raise RuntimeError(f"Task execution failed: {e!s}") from e
|
raise RuntimeError(f"Task execution failed: {e!s}") from e
|
||||||
|
finally:
|
||||||
|
if self.agent_executor:
|
||||||
|
self.agent_executor.clear_execution_deadline()
|
||||||
|
executor.shutdown(wait=False)
|
||||||
|
|
||||||
def _execute_without_timeout(self, task_prompt: str, task: Task) -> Any:
|
def _execute_without_timeout(self, task_prompt: str, task: Task) -> Any:
|
||||||
"""Execute a task without a timeout.
|
"""Execute a task without a timeout.
|
||||||
@@ -647,6 +661,12 @@ class Agent(BaseAgent):
|
|||||||
) -> Any:
|
) -> Any:
|
||||||
"""Execute a task with a timeout asynchronously.
|
"""Execute a task with a timeout asynchronously.
|
||||||
|
|
||||||
|
This method uses cooperative cancellation to ensure clean task cleanup.
|
||||||
|
When a timeout occurs:
|
||||||
|
1. The executor's deadline is set, causing the worker to check and exit
|
||||||
|
2. asyncio.wait_for cancels the coroutine
|
||||||
|
3. The worker will exit cleanly when it checks the deadline
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
task_prompt: The prompt to send to the agent.
|
task_prompt: The prompt to send to the agent.
|
||||||
task: The task being executed.
|
task: The task being executed.
|
||||||
@@ -659,6 +679,9 @@ class Agent(BaseAgent):
|
|||||||
TimeoutError: If execution exceeds the timeout.
|
TimeoutError: If execution exceeds the timeout.
|
||||||
RuntimeError: If execution fails for other reasons.
|
RuntimeError: If execution fails for other reasons.
|
||||||
"""
|
"""
|
||||||
|
if self.agent_executor:
|
||||||
|
self.agent_executor.set_execution_deadline(timeout)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return await asyncio.wait_for(
|
return await asyncio.wait_for(
|
||||||
self._aexecute_without_timeout(task_prompt, task),
|
self._aexecute_without_timeout(task_prompt, task),
|
||||||
@@ -669,6 +692,9 @@ class Agent(BaseAgent):
|
|||||||
f"Task '{task.description}' execution timed out after {timeout} seconds. "
|
f"Task '{task.description}' execution timed out after {timeout} seconds. "
|
||||||
"Consider increasing max_execution_time or optimizing the task."
|
"Consider increasing max_execution_time or optimizing the task."
|
||||||
) from e
|
) from e
|
||||||
|
finally:
|
||||||
|
if self.agent_executor:
|
||||||
|
self.agent_executor.clear_execution_deadline()
|
||||||
|
|
||||||
async def _aexecute_without_timeout(self, task_prompt: str, task: Task) -> Any:
|
async def _aexecute_without_timeout(self, task_prompt: str, task: Task) -> Any:
|
||||||
"""Execute a task without a timeout asynchronously.
|
"""Execute a task without a timeout asynchronously.
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
self.messages: list[LLMMessage] = []
|
self.messages: list[LLMMessage] = []
|
||||||
self.iterations = 0
|
self.iterations = 0
|
||||||
self.log_error_after = 3
|
self.log_error_after = 3
|
||||||
|
self._execution_deadline: float | None = None
|
||||||
self.before_llm_call_hooks: list[Callable[..., Any]] = []
|
self.before_llm_call_hooks: list[Callable[..., Any]] = []
|
||||||
self.after_llm_call_hooks: list[Callable[..., Any]] = []
|
self.after_llm_call_hooks: list[Callable[..., Any]] = []
|
||||||
self.before_llm_call_hooks.extend(get_before_llm_call_hooks())
|
self.before_llm_call_hooks.extend(get_before_llm_call_hooks())
|
||||||
@@ -162,6 +163,36 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
"""
|
"""
|
||||||
return self.llm.supports_stop_words() if self.llm else False
|
return self.llm.supports_stop_words() if self.llm else False
|
||||||
|
|
||||||
|
def set_execution_deadline(self, timeout_seconds: int | float) -> None:
|
||||||
|
"""Set the execution deadline for cooperative timeout.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout_seconds: Maximum execution time in seconds.
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
self._execution_deadline = time.monotonic() + timeout_seconds
|
||||||
|
|
||||||
|
def clear_execution_deadline(self) -> None:
|
||||||
|
"""Clear the execution deadline."""
|
||||||
|
self._execution_deadline = None
|
||||||
|
|
||||||
|
def _check_execution_deadline(self) -> None:
|
||||||
|
"""Check if the execution deadline has been exceeded.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TimeoutError: If the deadline has been exceeded.
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
if self._execution_deadline is not None:
|
||||||
|
if time.monotonic() >= self._execution_deadline:
|
||||||
|
task_desc = self.task.description if self.task else "Unknown task"
|
||||||
|
raise TimeoutError(
|
||||||
|
f"Task '{task_desc}' execution timed out. "
|
||||||
|
"Consider increasing max_execution_time or optimizing the task."
|
||||||
|
)
|
||||||
|
|
||||||
def invoke(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
def invoke(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Execute the agent with given inputs.
|
"""Execute the agent with given inputs.
|
||||||
|
|
||||||
@@ -217,6 +248,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
formatted_answer = None
|
formatted_answer = None
|
||||||
while not isinstance(formatted_answer, AgentFinish):
|
while not isinstance(formatted_answer, AgentFinish):
|
||||||
try:
|
try:
|
||||||
|
self._check_execution_deadline()
|
||||||
|
|
||||||
if has_reached_max_iterations(self.iterations, self.max_iter):
|
if has_reached_max_iterations(self.iterations, self.max_iter):
|
||||||
formatted_answer = handle_max_iterations_exceeded(
|
formatted_answer = handle_max_iterations_exceeded(
|
||||||
formatted_answer,
|
formatted_answer,
|
||||||
@@ -371,6 +404,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
formatted_answer = None
|
formatted_answer = None
|
||||||
while not isinstance(formatted_answer, AgentFinish):
|
while not isinstance(formatted_answer, AgentFinish):
|
||||||
try:
|
try:
|
||||||
|
self._check_execution_deadline()
|
||||||
|
|
||||||
if has_reached_max_iterations(self.iterations, self.max_iter):
|
if has_reached_max_iterations(self.iterations, self.max_iter):
|
||||||
formatted_answer = handle_max_iterations_exceeded(
|
formatted_answer = handle_max_iterations_exceeded(
|
||||||
formatted_answer,
|
formatted_answer,
|
||||||
|
|||||||
@@ -149,7 +149,9 @@ class AuthenticationCommand:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if token_data["error"] not in ("authorization_pending", "slow_down"):
|
if token_data["error"] not in ("authorization_pending", "slow_down"):
|
||||||
raise requests.HTTPError(token_data["error_description"])
|
raise requests.HTTPError(
|
||||||
|
token_data.get("error_description") or token_data.get("error")
|
||||||
|
)
|
||||||
|
|
||||||
time.sleep(device_code_data["interval"])
|
time.sleep(device_code_data["interval"])
|
||||||
attempts += 1
|
attempts += 1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from crewai.cli.config import Settings
|
from crewai.cli.config import Settings
|
||||||
@@ -33,9 +33,7 @@ class PlusAPI:
|
|||||||
if settings.org_uuid:
|
if settings.org_uuid:
|
||||||
self.headers["X-Crewai-Organization-Id"] = settings.org_uuid
|
self.headers["X-Crewai-Organization-Id"] = settings.org_uuid
|
||||||
|
|
||||||
self.base_url = (
|
self.base_url = os.getenv("CREWAI_PLUS_URL") or str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL
|
||||||
str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL
|
|
||||||
)
|
|
||||||
|
|
||||||
def _make_request(
|
def _make_request(
|
||||||
self, method: str, endpoint: str, **kwargs: Any
|
self, method: str, endpoint: str, **kwargs: Any
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
|||||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||||
requires-python = ">=3.10,<3.14"
|
requires-python = ">=3.10,<3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]==1.7.1"
|
"crewai[tools]==1.7.2"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
|||||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||||
requires-python = ">=3.10,<3.14"
|
requires-python = ">=3.10,<3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]==1.7.1"
|
"crewai[tools]==1.7.2"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from rich.console import Console
|
|||||||
from crewai.cli import git
|
from crewai.cli import git
|
||||||
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
||||||
from crewai.cli.config import Settings
|
from crewai.cli.config import Settings
|
||||||
|
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
|
||||||
from crewai.cli.utils import (
|
from crewai.cli.utils import (
|
||||||
build_env_with_tool_repository_credentials,
|
build_env_with_tool_repository_credentials,
|
||||||
extract_available_exports,
|
extract_available_exports,
|
||||||
@@ -131,10 +132,13 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
self._validate_response(publish_response)
|
self._validate_response(publish_response)
|
||||||
|
|
||||||
published_handle = publish_response.json()["handle"]
|
published_handle = publish_response.json()["handle"]
|
||||||
|
settings = Settings()
|
||||||
|
base_url = settings.enterprise_base_url or DEFAULT_CREWAI_ENTERPRISE_URL
|
||||||
|
|
||||||
console.print(
|
console.print(
|
||||||
f"Successfully published `{published_handle}` ({project_version}).\n\n"
|
f"Successfully published `{published_handle}` ({project_version}).\n\n"
|
||||||
+ "⚠️ Security checks are running in the background. Your tool will be available once these are complete.\n"
|
+ "⚠️ Security checks are running in the background. Your tool will be available once these are complete.\n"
|
||||||
+ f"You can monitor the status or access your tool here:\nhttps://app.crewai.com/crewai_plus/tools/{published_handle}",
|
+ f"You can monitor the status or access your tool here:\n{base_url}/crewai_plus/tools/{published_handle}",
|
||||||
style="bold green",
|
style="bold green",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from rich.console import Console
|
|||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
|
||||||
from crewai.cli.authentication.token import AuthError, get_auth_token
|
from crewai.cli.authentication.token import AuthError, get_auth_token
|
||||||
|
from crewai.cli.config import Settings
|
||||||
|
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
|
||||||
from crewai.cli.plus_api import PlusAPI
|
from crewai.cli.plus_api import PlusAPI
|
||||||
from crewai.cli.version import get_crewai_version
|
from crewai.cli.version import get_crewai_version
|
||||||
from crewai.events.listeners.tracing.types import TraceEvent
|
from crewai.events.listeners.tracing.types import TraceEvent
|
||||||
@@ -16,7 +18,6 @@ from crewai.events.listeners.tracing.utils import (
|
|||||||
is_tracing_enabled_in_context,
|
is_tracing_enabled_in_context,
|
||||||
should_auto_collect_first_time_traces,
|
should_auto_collect_first_time_traces,
|
||||||
)
|
)
|
||||||
from crewai.utilities.constants import CREWAI_BASE_URL
|
|
||||||
|
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
@@ -326,10 +327,12 @@ class TraceBatchManager:
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
access_code = response.json().get("access_code", None)
|
access_code = response.json().get("access_code", None)
|
||||||
console = Console()
|
console = Console()
|
||||||
|
settings = Settings()
|
||||||
|
base_url = settings.enterprise_base_url or DEFAULT_CREWAI_ENTERPRISE_URL
|
||||||
return_link = (
|
return_link = (
|
||||||
f"{CREWAI_BASE_URL}/crewai_plus/trace_batches/{self.trace_batch_id}"
|
f"{base_url}/crewai_plus/trace_batches/{self.trace_batch_id}"
|
||||||
if not self.is_current_batch_ephemeral and access_code is None
|
if not self.is_current_batch_ephemeral and access_code is None
|
||||||
else f"{CREWAI_BASE_URL}/crewai_plus/ephemeral_trace_batches/{self.trace_batch_id}?access_code={access_code}"
|
else f"{base_url}/crewai_plus/ephemeral_trace_batches/{self.trace_batch_id}?access_code={access_code}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.is_current_batch_ephemeral:
|
if self.is_current_batch_ephemeral:
|
||||||
|
|||||||
@@ -30,4 +30,3 @@ NOT_SPECIFIED: Final[
|
|||||||
"allows us to distinguish between 'not passed at all' and 'explicitly passed None' or '[]'.",
|
"allows us to distinguish between 'not passed at all' and 'explicitly passed None' or '[]'.",
|
||||||
]
|
]
|
||||||
] = _NotSpecified()
|
] = _NotSpecified()
|
||||||
CREWAI_BASE_URL: Final[str] = "https://app.crewai.com"
|
|
||||||
|
|||||||
313
lib/crewai/tests/agents/test_agent_timeout.py
Normal file
313
lib/crewai/tests/agents/test_agent_timeout.py
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
"""Test Agent timeout handling and cooperative cancellation."""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from crewai import Agent, Task
|
||||||
|
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||||
|
|
||||||
|
|
||||||
|
class TestExecutorDeadline:
|
||||||
|
"""Tests for CrewAgentExecutor deadline functionality."""
|
||||||
|
|
||||||
|
def test_set_execution_deadline(self):
|
||||||
|
"""Test that set_execution_deadline sets the deadline correctly."""
|
||||||
|
executor = MagicMock(spec=CrewAgentExecutor)
|
||||||
|
executor._execution_deadline = None
|
||||||
|
|
||||||
|
CrewAgentExecutor.set_execution_deadline(executor, 5)
|
||||||
|
|
||||||
|
assert executor._execution_deadline is not None
|
||||||
|
assert executor._execution_deadline > time.monotonic()
|
||||||
|
|
||||||
|
def test_clear_execution_deadline(self):
|
||||||
|
"""Test that clear_execution_deadline clears the deadline."""
|
||||||
|
executor = MagicMock(spec=CrewAgentExecutor)
|
||||||
|
executor._execution_deadline = time.monotonic() + 100
|
||||||
|
|
||||||
|
CrewAgentExecutor.clear_execution_deadline(executor)
|
||||||
|
|
||||||
|
assert executor._execution_deadline is None
|
||||||
|
|
||||||
|
def test_check_execution_deadline_not_exceeded(self):
|
||||||
|
"""Test that _check_execution_deadline does not raise when deadline not exceeded."""
|
||||||
|
executor = MagicMock(spec=CrewAgentExecutor)
|
||||||
|
executor._execution_deadline = time.monotonic() + 100
|
||||||
|
executor.task = MagicMock()
|
||||||
|
executor.task.description = "Test task"
|
||||||
|
|
||||||
|
CrewAgentExecutor._check_execution_deadline(executor)
|
||||||
|
|
||||||
|
def test_check_execution_deadline_exceeded(self):
|
||||||
|
"""Test that _check_execution_deadline raises TimeoutError when deadline exceeded."""
|
||||||
|
executor = MagicMock(spec=CrewAgentExecutor)
|
||||||
|
executor._execution_deadline = time.monotonic() - 1
|
||||||
|
executor.task = MagicMock()
|
||||||
|
executor.task.description = "Test task"
|
||||||
|
|
||||||
|
with pytest.raises(TimeoutError) as exc_info:
|
||||||
|
CrewAgentExecutor._check_execution_deadline(executor)
|
||||||
|
|
||||||
|
assert "Test task" in str(exc_info.value)
|
||||||
|
assert "timed out" in str(exc_info.value)
|
||||||
|
|
||||||
|
def test_check_execution_deadline_no_deadline_set(self):
|
||||||
|
"""Test that _check_execution_deadline does nothing when no deadline is set."""
|
||||||
|
executor = MagicMock(spec=CrewAgentExecutor)
|
||||||
|
executor._execution_deadline = None
|
||||||
|
executor.task = MagicMock()
|
||||||
|
executor.task.description = "Test task"
|
||||||
|
|
||||||
|
CrewAgentExecutor._check_execution_deadline(executor)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAgentTimeoutBehavior:
|
||||||
|
"""Tests for Agent timeout behavior."""
|
||||||
|
|
||||||
|
def test_execute_with_timeout_sets_deadline(self):
|
||||||
|
"""Test that _execute_with_timeout sets the deadline on the executor."""
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
max_execution_time=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_executor = MagicMock()
|
||||||
|
mock_executor.invoke.return_value = {"output": "test output"}
|
||||||
|
agent.agent_executor = mock_executor
|
||||||
|
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(agent, "_execute_without_timeout", return_value="test output"):
|
||||||
|
agent._execute_with_timeout("test prompt", task, 5)
|
||||||
|
|
||||||
|
mock_executor.set_execution_deadline.assert_called_once_with(5)
|
||||||
|
mock_executor.clear_execution_deadline.assert_called_once()
|
||||||
|
|
||||||
|
def test_execute_with_timeout_clears_deadline_on_success(self):
|
||||||
|
"""Test that _execute_with_timeout clears the deadline after successful execution."""
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
max_execution_time=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_executor = MagicMock()
|
||||||
|
agent.agent_executor = mock_executor
|
||||||
|
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(agent, "_execute_without_timeout", return_value="test output"):
|
||||||
|
result = agent._execute_with_timeout("test prompt", task, 5)
|
||||||
|
|
||||||
|
assert result == "test output"
|
||||||
|
mock_executor.clear_execution_deadline.assert_called_once()
|
||||||
|
|
||||||
|
def test_execute_with_timeout_clears_deadline_on_timeout(self):
|
||||||
|
"""Test that _execute_with_timeout clears the deadline even when timeout occurs."""
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
max_execution_time=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_executor = MagicMock()
|
||||||
|
agent.agent_executor = mock_executor
|
||||||
|
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
def slow_execution(*args, **kwargs):
|
||||||
|
time.sleep(5)
|
||||||
|
return "test output"
|
||||||
|
|
||||||
|
with patch.object(agent, "_execute_without_timeout", side_effect=slow_execution):
|
||||||
|
with pytest.raises(TimeoutError):
|
||||||
|
agent._execute_with_timeout("test prompt", task, 1)
|
||||||
|
|
||||||
|
mock_executor.clear_execution_deadline.assert_called_once()
|
||||||
|
|
||||||
|
def test_execute_with_timeout_raises_timeout_error(self):
|
||||||
|
"""Test that _execute_with_timeout raises TimeoutError when execution exceeds timeout."""
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
max_execution_time=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_executor = MagicMock()
|
||||||
|
agent.agent_executor = mock_executor
|
||||||
|
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
def slow_execution(*args, **kwargs):
|
||||||
|
time.sleep(5)
|
||||||
|
return "test output"
|
||||||
|
|
||||||
|
with patch.object(agent, "_execute_without_timeout", side_effect=slow_execution):
|
||||||
|
with pytest.raises(TimeoutError) as exc_info:
|
||||||
|
agent._execute_with_timeout("test prompt", task, 1)
|
||||||
|
|
||||||
|
assert "Test task" in str(exc_info.value)
|
||||||
|
assert "timed out" in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCooperativeCancellation:
|
||||||
|
"""Tests for cooperative cancellation behavior."""
|
||||||
|
|
||||||
|
def test_timeout_returns_control_promptly(self):
|
||||||
|
"""Test that timeout returns control to caller promptly (within reasonable bounds)."""
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
max_execution_time=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_executor = MagicMock()
|
||||||
|
agent.agent_executor = mock_executor
|
||||||
|
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
execution_started = threading.Event()
|
||||||
|
|
||||||
|
def slow_execution(*args, **kwargs):
|
||||||
|
execution_started.set()
|
||||||
|
time.sleep(10)
|
||||||
|
return "test output"
|
||||||
|
|
||||||
|
with patch.object(agent, "_execute_without_timeout", side_effect=slow_execution):
|
||||||
|
start_time = time.monotonic()
|
||||||
|
with pytest.raises(TimeoutError):
|
||||||
|
agent._execute_with_timeout("test prompt", task, 1)
|
||||||
|
elapsed_time = time.monotonic() - start_time
|
||||||
|
|
||||||
|
assert elapsed_time < 3, f"Timeout should return control within 3 seconds, took {elapsed_time:.2f}s"
|
||||||
|
|
||||||
|
def test_executor_deadline_checked_in_invoke_loop(self):
|
||||||
|
"""Test that the executor checks the deadline in the invoke loop."""
|
||||||
|
mock_llm = MagicMock()
|
||||||
|
mock_llm.supports_stop_words.return_value = False
|
||||||
|
mock_llm.call.return_value = "Final Answer: test"
|
||||||
|
|
||||||
|
mock_task = MagicMock()
|
||||||
|
mock_task.description = "Test task"
|
||||||
|
|
||||||
|
mock_crew = MagicMock()
|
||||||
|
mock_crew.verbose = False
|
||||||
|
|
||||||
|
mock_agent = MagicMock()
|
||||||
|
mock_agent.verbose = False
|
||||||
|
mock_agent.role = "Test Agent"
|
||||||
|
|
||||||
|
executor = CrewAgentExecutor(
|
||||||
|
llm=mock_llm,
|
||||||
|
task=mock_task,
|
||||||
|
crew=mock_crew,
|
||||||
|
agent=mock_agent,
|
||||||
|
prompt={"prompt": "test"},
|
||||||
|
max_iter=10,
|
||||||
|
tools=[],
|
||||||
|
tools_names="",
|
||||||
|
stop_words=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
executor.set_execution_deadline(0.001)
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
with pytest.raises(TimeoutError) as exc_info:
|
||||||
|
executor.invoke({"input": "test", "tool_names": "", "tools": ""})
|
||||||
|
|
||||||
|
assert "timed out" in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAsyncTimeoutBehavior:
|
||||||
|
"""Tests for async timeout behavior."""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_aexecute_with_timeout_sets_deadline(self):
|
||||||
|
"""Test that _aexecute_with_timeout sets the deadline on the executor."""
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
max_execution_time=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_executor = MagicMock()
|
||||||
|
mock_executor.ainvoke = MagicMock(return_value={"output": "test output"})
|
||||||
|
agent.agent_executor = mock_executor
|
||||||
|
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def mock_aexecute(*args, **kwargs):
|
||||||
|
return "test output"
|
||||||
|
|
||||||
|
with patch.object(agent, "_aexecute_without_timeout", side_effect=mock_aexecute):
|
||||||
|
await agent._aexecute_with_timeout("test prompt", task, 5)
|
||||||
|
|
||||||
|
mock_executor.set_execution_deadline.assert_called_once_with(5)
|
||||||
|
mock_executor.clear_execution_deadline.assert_called_once()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_aexecute_with_timeout_clears_deadline_on_timeout(self):
|
||||||
|
"""Test that _aexecute_with_timeout clears the deadline even when timeout occurs."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
max_execution_time=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_executor = MagicMock()
|
||||||
|
agent.agent_executor = mock_executor
|
||||||
|
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def slow_execution(*args, **kwargs):
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
return "test output"
|
||||||
|
|
||||||
|
with patch.object(agent, "_aexecute_without_timeout", side_effect=slow_execution):
|
||||||
|
with pytest.raises(TimeoutError):
|
||||||
|
await agent._aexecute_with_timeout("test prompt", task, 1)
|
||||||
|
|
||||||
|
mock_executor.clear_execution_deadline.assert_called_once()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import ANY, MagicMock, patch
|
from unittest.mock import ANY, MagicMock, patch
|
||||||
|
|
||||||
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
|
|
||||||
from crewai.cli.plus_api import PlusAPI
|
from crewai.cli.plus_api import PlusAPI
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class TestPlusAPI(unittest.TestCase):
|
|||||||
):
|
):
|
||||||
mock_make_request.assert_called_once_with(
|
mock_make_request.assert_called_once_with(
|
||||||
method,
|
method,
|
||||||
f"{DEFAULT_CREWAI_ENTERPRISE_URL}{endpoint}",
|
f"{os.getenv('CREWAI_PLUS_URL')}{endpoint}",
|
||||||
headers={
|
headers={
|
||||||
"Authorization": ANY,
|
"Authorization": ANY,
|
||||||
"Content-Type": ANY,
|
"Content-Type": ANY,
|
||||||
@@ -53,7 +53,7 @@ class TestPlusAPI(unittest.TestCase):
|
|||||||
):
|
):
|
||||||
mock_settings = MagicMock()
|
mock_settings = MagicMock()
|
||||||
mock_settings.org_uuid = self.org_uuid
|
mock_settings.org_uuid = self.org_uuid
|
||||||
mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL
|
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
|
||||||
mock_settings_class.return_value = mock_settings
|
mock_settings_class.return_value = mock_settings
|
||||||
# re-initialize Client
|
# re-initialize Client
|
||||||
self.api = PlusAPI(self.api_key)
|
self.api = PlusAPI(self.api_key)
|
||||||
@@ -84,7 +84,7 @@ class TestPlusAPI(unittest.TestCase):
|
|||||||
def test_get_agent_with_org_uuid(self, mock_make_request, mock_settings_class):
|
def test_get_agent_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||||
mock_settings = MagicMock()
|
mock_settings = MagicMock()
|
||||||
mock_settings.org_uuid = self.org_uuid
|
mock_settings.org_uuid = self.org_uuid
|
||||||
mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL
|
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
|
||||||
mock_settings_class.return_value = mock_settings
|
mock_settings_class.return_value = mock_settings
|
||||||
# re-initialize Client
|
# re-initialize Client
|
||||||
self.api = PlusAPI(self.api_key)
|
self.api = PlusAPI(self.api_key)
|
||||||
@@ -115,7 +115,7 @@ class TestPlusAPI(unittest.TestCase):
|
|||||||
def test_get_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
|
def test_get_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||||
mock_settings = MagicMock()
|
mock_settings = MagicMock()
|
||||||
mock_settings.org_uuid = self.org_uuid
|
mock_settings.org_uuid = self.org_uuid
|
||||||
mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL
|
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
|
||||||
mock_settings_class.return_value = mock_settings
|
mock_settings_class.return_value = mock_settings
|
||||||
# re-initialize Client
|
# re-initialize Client
|
||||||
self.api = PlusAPI(self.api_key)
|
self.api = PlusAPI(self.api_key)
|
||||||
@@ -163,7 +163,7 @@ class TestPlusAPI(unittest.TestCase):
|
|||||||
def test_publish_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
|
def test_publish_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||||
mock_settings = MagicMock()
|
mock_settings = MagicMock()
|
||||||
mock_settings.org_uuid = self.org_uuid
|
mock_settings.org_uuid = self.org_uuid
|
||||||
mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL
|
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
|
||||||
mock_settings_class.return_value = mock_settings
|
mock_settings_class.return_value = mock_settings
|
||||||
# re-initialize Client
|
# re-initialize Client
|
||||||
self.api = PlusAPI(self.api_key)
|
self.api = PlusAPI(self.api_key)
|
||||||
@@ -320,6 +320,7 @@ class TestPlusAPI(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@patch("crewai.cli.plus_api.Settings")
|
@patch("crewai.cli.plus_api.Settings")
|
||||||
|
@patch.dict(os.environ, {"CREWAI_PLUS_URL": ""})
|
||||||
def test_custom_base_url(self, mock_settings_class):
|
def test_custom_base_url(self, mock_settings_class):
|
||||||
mock_settings = MagicMock()
|
mock_settings = MagicMock()
|
||||||
mock_settings.enterprise_base_url = "https://custom-url.com/api"
|
mock_settings.enterprise_base_url = "https://custom-url.com/api"
|
||||||
@@ -329,3 +330,11 @@ class TestPlusAPI(unittest.TestCase):
|
|||||||
custom_api.base_url,
|
custom_api.base_url,
|
||||||
"https://custom-url.com/api",
|
"https://custom-url.com/api",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@patch.dict(os.environ, {"CREWAI_PLUS_URL": "https://custom-url-from-env.com"})
|
||||||
|
def test_custom_base_url_from_env(self):
|
||||||
|
custom_api = PlusAPI("test_key")
|
||||||
|
self.assertEqual(
|
||||||
|
custom_api.base_url,
|
||||||
|
"https://custom-url-from-env.com",
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""CrewAI development tools."""
|
"""CrewAI development tools."""
|
||||||
|
|
||||||
__version__ = "1.7.1"
|
__version__ = "1.7.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user