mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-08 07:38:29 +00:00
Compare commits
9 Commits
devin/1765
...
devin/1766
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06c4974cb3 | ||
|
|
0c020991c4 | ||
|
|
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
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
repository_dispatch:
|
||||
types: [deployment-tests-passed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Release tag to publish'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -12,7 +17,21 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
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
|
||||
with:
|
||||
ref: ${{ steps.release.outputs.tag || github.ref }}
|
||||
|
||||
- name: Set up Python
|
||||
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
|
||||
hooks:
|
||||
- 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.
|
||||
</Step>
|
||||
|
||||
<Step title="Discover Required Inputs">
|
||||
Use the `GET /inputs` endpoint to see what parameters your crew expects.
|
||||
</Step>
|
||||
<Step title="Discover Required Inputs">
|
||||
Use the `GET /inputs` endpoint to see what parameters your crew expects.
|
||||
</Step>
|
||||
|
||||
<Step title="Start a Crew Execution">
|
||||
Call `POST /kickoff` with your inputs to start the crew execution and receive a `kickoff_id`.
|
||||
</Step>
|
||||
<Step title="Start a Crew Execution">
|
||||
Call `POST /kickoff` with your inputs to start the crew execution and receive
|
||||
a `kickoff_id`.
|
||||
</Step>
|
||||
|
||||
<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>
|
||||
</Steps>
|
||||
|
||||
@@ -40,13 +41,14 @@ curl -H "Authorization: Bearer YOUR_CREW_TOKEN" \
|
||||
|
||||
### Token Types
|
||||
|
||||
| Token Type | Scope | Use Case |
|
||||
|:-----------|:--------|:----------|
|
||||
| **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 |
|
||||
| Token Type | Scope | Use Case |
|
||||
| :-------------------- | :------------------------ | :----------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
<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>
|
||||
|
||||
## 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
|
||||
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
|
||||
|
||||
## Error Handling
|
||||
|
||||
The API uses standard HTTP status codes:
|
||||
|
||||
| Code | Meaning |
|
||||
|------|:--------|
|
||||
| `200` | Success |
|
||||
| `400` | Bad Request - Invalid input format |
|
||||
| `401` | Unauthorized - Invalid bearer token |
|
||||
| `404` | Not Found - Resource doesn't exist |
|
||||
| Code | Meaning |
|
||||
| ----- | :----------------------------------------- |
|
||||
| `200` | Success |
|
||||
| `400` | Bad Request - Invalid input format |
|
||||
| `401` | Unauthorized - Invalid bearer token |
|
||||
| `404` | Not Found - Resource doesn't exist |
|
||||
| `422` | Validation Error - Missing required inputs |
|
||||
| `500` | Server Error - Contact support |
|
||||
| `500` | Server Error - Contact support |
|
||||
|
||||
## Interactive Testing
|
||||
|
||||
<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>
|
||||
|
||||
Each endpoint page shows you:
|
||||
|
||||
- ✅ **Exact request format** with all parameters
|
||||
- ✅ **Response examples** for success and error cases
|
||||
- ✅ **Code samples** in multiple languages (cURL, Python, JavaScript, etc.)
|
||||
@@ -103,6 +109,7 @@ Each endpoint page shows you:
|
||||
</CardGroup>
|
||||
|
||||
**Example workflow:**
|
||||
|
||||
1. **Copy this cURL example** from any endpoint page
|
||||
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
|
||||
@@ -111,10 +118,18 @@ Each endpoint page shows you:
|
||||
## Need Help?
|
||||
|
||||
<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
|
||||
</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
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: "GET /status/{kickoff_id}"
|
||||
title: "GET /{kickoff_id}/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"
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -62,13 +62,13 @@ Test your Gmail trigger integration locally using the CrewAI CLI:
|
||||
crewai triggers list
|
||||
|
||||
# Simulate a Gmail trigger with realistic payload
|
||||
crewai triggers run gmail/new_email
|
||||
crewai triggers run gmail/new_email_received
|
||||
```
|
||||
|
||||
The `crewai triggers run` command will execute your crew with a complete Gmail payload, allowing you to test your parsing logic before deployment.
|
||||
|
||||
<Warning>
|
||||
Use `crewai triggers run gmail/new_email` (not `crewai run`) to simulate trigger execution during development. After deployment, your crew will automatically receive the trigger payload.
|
||||
Use `crewai triggers run gmail/new_email_received` (not `crewai run`) to simulate trigger execution during development. After deployment, your crew will automatically receive the trigger payload.
|
||||
</Warning>
|
||||
|
||||
## Monitoring Executions
|
||||
@@ -83,6 +83,6 @@ Track history and performance of triggered runs:
|
||||
|
||||
- Ensure Gmail is connected in Tools & Integrations
|
||||
- Verify the Gmail Trigger is enabled on the Triggers tab
|
||||
- Test locally with `crewai triggers run gmail/new_email` to see the exact payload structure
|
||||
- Test locally with `crewai triggers run gmail/new_email_received` to see the exact payload structure
|
||||
- Check the execution logs and confirm the payload is passed as `crewai_trigger_payload`
|
||||
- Remember: use `crewai triggers run` (not `crewai run`) to simulate trigger execution
|
||||
|
||||
@@ -35,7 +35,7 @@ info:
|
||||
|
||||
1. **Discover inputs** using `GET /inputs`
|
||||
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
|
||||
contact:
|
||||
name: CrewAI Support
|
||||
@@ -63,7 +63,7 @@ paths:
|
||||
Use this endpoint to discover what inputs you need to provide when starting a crew execution.
|
||||
operationId: getRequiredInputs
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Successfully retrieved required inputs
|
||||
content:
|
||||
application/json:
|
||||
@@ -84,13 +84,21 @@ paths:
|
||||
outreach_crew:
|
||||
summary: Outreach crew inputs
|
||||
value:
|
||||
inputs: ["name", "title", "company", "industry", "our_product", "linkedin_url"]
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFoundError'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
inputs:
|
||||
[
|
||||
"name",
|
||||
"title",
|
||||
"company",
|
||||
"industry",
|
||||
"our_product",
|
||||
"linkedin_url",
|
||||
]
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/kickoff:
|
||||
post:
|
||||
@@ -170,7 +178,7 @@ paths:
|
||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Crew execution started successfully
|
||||
content:
|
||||
application/json:
|
||||
@@ -182,24 +190,24 @@ paths:
|
||||
format: uuid
|
||||
description: Unique identifier for tracking this execution
|
||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||
'400':
|
||||
"400":
|
||||
description: Invalid request body or missing required inputs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'422':
|
||||
$ref: "#/components/schemas/Error"
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"422":
|
||||
description: Validation error - ensure all required inputs are provided
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
$ref: "#/components/schemas/ValidationError"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/status/{kickoff_id}:
|
||||
/{kickoff_id}/status:
|
||||
get:
|
||||
summary: Get Execution Status
|
||||
description: |
|
||||
@@ -222,15 +230,15 @@ paths:
|
||||
format: uuid
|
||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Successfully retrieved execution status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/ExecutionRunning'
|
||||
- $ref: '#/components/schemas/ExecutionCompleted'
|
||||
- $ref: '#/components/schemas/ExecutionError'
|
||||
- $ref: "#/components/schemas/ExecutionRunning"
|
||||
- $ref: "#/components/schemas/ExecutionCompleted"
|
||||
- $ref: "#/components/schemas/ExecutionError"
|
||||
examples:
|
||||
running:
|
||||
summary: Execution in progress
|
||||
@@ -262,19 +270,19 @@ paths:
|
||||
status: "error"
|
||||
error: "Task execution failed: Invalid API key for external service"
|
||||
execution_time: 23.1
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
description: Kickoff ID not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Execution not found"
|
||||
message: "No execution found with ID: abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/resume:
|
||||
post:
|
||||
@@ -354,7 +362,7 @@ paths:
|
||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Execution resumed successfully
|
||||
content:
|
||||
application/json:
|
||||
@@ -381,28 +389,28 @@ paths:
|
||||
value:
|
||||
status: "retrying"
|
||||
message: "Task will be retried with your feedback"
|
||||
'400':
|
||||
"400":
|
||||
description: Invalid request body or execution not in pending state
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Invalid Request"
|
||||
message: "Execution is not in pending human input state"
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
description: Execution ID or Task ID not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Not Found"
|
||||
message: "Execution ID not found"
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
@@ -458,7 +466,7 @@ components:
|
||||
tasks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TaskResult'
|
||||
$ref: "#/components/schemas/TaskResult"
|
||||
execution_time:
|
||||
type: number
|
||||
description: Total execution time in seconds
|
||||
@@ -536,7 +544,7 @@ components:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Unauthorized"
|
||||
message: "Invalid or missing bearer token"
|
||||
@@ -546,7 +554,7 @@ components:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Not Found"
|
||||
message: "The requested resource was not found"
|
||||
@@ -556,7 +564,7 @@ components:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Internal Server Error"
|
||||
message: "An unexpected error occurred"
|
||||
|
||||
@@ -35,7 +35,7 @@ info:
|
||||
|
||||
1. **Discover inputs** using `GET /inputs`
|
||||
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
|
||||
contact:
|
||||
name: CrewAI Support
|
||||
@@ -63,7 +63,7 @@ paths:
|
||||
Use this endpoint to discover what inputs you need to provide when starting a crew execution.
|
||||
operationId: getRequiredInputs
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Successfully retrieved required inputs
|
||||
content:
|
||||
application/json:
|
||||
@@ -84,13 +84,21 @@ paths:
|
||||
outreach_crew:
|
||||
summary: Outreach crew inputs
|
||||
value:
|
||||
inputs: ["name", "title", "company", "industry", "our_product", "linkedin_url"]
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFoundError'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
inputs:
|
||||
[
|
||||
"name",
|
||||
"title",
|
||||
"company",
|
||||
"industry",
|
||||
"our_product",
|
||||
"linkedin_url",
|
||||
]
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/kickoff:
|
||||
post:
|
||||
@@ -170,7 +178,7 @@ paths:
|
||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Crew execution started successfully
|
||||
content:
|
||||
application/json:
|
||||
@@ -182,24 +190,24 @@ paths:
|
||||
format: uuid
|
||||
description: Unique identifier for tracking this execution
|
||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||
'400':
|
||||
"400":
|
||||
description: Invalid request body or missing required inputs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'422':
|
||||
$ref: "#/components/schemas/Error"
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"422":
|
||||
description: Validation error - ensure all required inputs are provided
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
$ref: "#/components/schemas/ValidationError"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/status/{kickoff_id}:
|
||||
/{kickoff_id}/status:
|
||||
get:
|
||||
summary: Get Execution Status
|
||||
description: |
|
||||
@@ -222,15 +230,15 @@ paths:
|
||||
format: uuid
|
||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Successfully retrieved execution status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/ExecutionRunning'
|
||||
- $ref: '#/components/schemas/ExecutionCompleted'
|
||||
- $ref: '#/components/schemas/ExecutionError'
|
||||
- $ref: "#/components/schemas/ExecutionRunning"
|
||||
- $ref: "#/components/schemas/ExecutionCompleted"
|
||||
- $ref: "#/components/schemas/ExecutionError"
|
||||
examples:
|
||||
running:
|
||||
summary: Execution in progress
|
||||
@@ -262,19 +270,19 @@ paths:
|
||||
status: "error"
|
||||
error: "Task execution failed: Invalid API key for external service"
|
||||
execution_time: 23.1
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
description: Kickoff ID not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Execution not found"
|
||||
message: "No execution found with ID: abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/resume:
|
||||
post:
|
||||
@@ -354,7 +362,7 @@ paths:
|
||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Execution resumed successfully
|
||||
content:
|
||||
application/json:
|
||||
@@ -381,28 +389,28 @@ paths:
|
||||
value:
|
||||
status: "retrying"
|
||||
message: "Task will be retried with your feedback"
|
||||
'400':
|
||||
"400":
|
||||
description: Invalid request body or execution not in pending state
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Invalid Request"
|
||||
message: "Execution is not in pending human input state"
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
description: Execution ID or Task ID not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Not Found"
|
||||
message: "Execution ID not found"
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
@@ -458,7 +466,7 @@ components:
|
||||
tasks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TaskResult'
|
||||
$ref: "#/components/schemas/TaskResult"
|
||||
execution_time:
|
||||
type: number
|
||||
description: Total execution time in seconds
|
||||
@@ -536,7 +544,7 @@ components:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Unauthorized"
|
||||
message: "Invalid or missing bearer token"
|
||||
@@ -546,7 +554,7 @@ components:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Not Found"
|
||||
message: "The requested resource was not found"
|
||||
@@ -556,7 +564,7 @@ components:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Internal Server Error"
|
||||
message: "An unexpected error occurred"
|
||||
|
||||
@@ -84,7 +84,7 @@ paths:
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
|
||||
/status/{kickoff_id}:
|
||||
/{kickoff_id}/status:
|
||||
get:
|
||||
summary: 실행 상태 조회
|
||||
description: |
|
||||
|
||||
@@ -35,7 +35,7 @@ info:
|
||||
|
||||
1. **Descubra os inputs** usando `GET /inputs`
|
||||
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
|
||||
contact:
|
||||
name: CrewAI Suporte
|
||||
@@ -56,7 +56,7 @@ paths:
|
||||
Retorna a lista de parâmetros de entrada que sua crew espera.
|
||||
operationId: getRequiredInputs
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Inputs requeridos obtidos com sucesso
|
||||
content:
|
||||
application/json:
|
||||
@@ -69,12 +69,12 @@ paths:
|
||||
type: string
|
||||
description: Nomes dos parâmetros de entrada
|
||||
example: ["budget", "interests", "duration", "age"]
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFoundError'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFoundError"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/kickoff:
|
||||
post:
|
||||
@@ -104,7 +104,7 @@ paths:
|
||||
age: "35"
|
||||
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Execução iniciada com sucesso
|
||||
content:
|
||||
application/json:
|
||||
@@ -115,12 +115,12 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/status/{kickoff_id}:
|
||||
/{kickoff_id}/status:
|
||||
get:
|
||||
summary: Obter Status da Execução
|
||||
description: |
|
||||
@@ -136,25 +136,25 @@ paths:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Status recuperado com sucesso
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/ExecutionRunning'
|
||||
- $ref: '#/components/schemas/ExecutionCompleted'
|
||||
- $ref: '#/components/schemas/ExecutionError'
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
- $ref: "#/components/schemas/ExecutionRunning"
|
||||
- $ref: "#/components/schemas/ExecutionCompleted"
|
||||
- $ref: "#/components/schemas/ExecutionError"
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
description: Kickoff ID não encontrado
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
$ref: "#/components/schemas/Error"
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
/resume:
|
||||
post:
|
||||
@@ -234,7 +234,7 @@ paths:
|
||||
taskWebhookUrl: "https://api.example.com/webhooks/task"
|
||||
crewWebhookUrl: "https://api.example.com/webhooks/crew"
|
||||
responses:
|
||||
'200':
|
||||
"200":
|
||||
description: Execution resumed successfully
|
||||
content:
|
||||
application/json:
|
||||
@@ -261,28 +261,28 @@ paths:
|
||||
value:
|
||||
status: "retrying"
|
||||
message: "Task will be retried with your feedback"
|
||||
'400':
|
||||
"400":
|
||||
description: Invalid request body or execution not in pending state
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Invalid Request"
|
||||
message: "Execution is not in pending human input state"
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'404':
|
||||
"401":
|
||||
$ref: "#/components/responses/UnauthorizedError"
|
||||
"404":
|
||||
description: Execution ID or Task ID not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
example:
|
||||
error: "Not Found"
|
||||
message: "Execution ID not found"
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
"500":
|
||||
$ref: "#/components/responses/ServerError"
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
@@ -324,7 +324,7 @@ components:
|
||||
tasks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TaskResult'
|
||||
$ref: "#/components/schemas/TaskResult"
|
||||
execution_time:
|
||||
type: number
|
||||
|
||||
@@ -380,16 +380,16 @@ components:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
NotFoundError:
|
||||
description: Recurso não encontrado
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
ServerError:
|
||||
description: Erro interno do servidor
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
@@ -16,16 +16,17 @@ CrewAI 엔터프라이즈 API 참고 자료에 오신 것을 환영합니다.
|
||||
CrewAI AOP 대시보드에서 자신의 crew 상세 페이지로 이동하여 Status 탭에서 Bearer Token을 복사하세요.
|
||||
</Step>
|
||||
|
||||
<Step title="필수 입력값 확인하기">
|
||||
`GET /inputs` 엔드포인트를 사용하여 crew가 기대하는 파라미터를 확인하세요.
|
||||
</Step>
|
||||
<Step title="필수 입력값 확인하기">
|
||||
`GET /inputs` 엔드포인트를 사용하여 crew가 기대하는 파라미터를 확인하세요.
|
||||
</Step>
|
||||
|
||||
<Step title="Crew 실행 시작하기">
|
||||
입력값과 함께 `POST /kickoff`를 호출하여 crew 실행을 시작하고 `kickoff_id`를 받으세요.
|
||||
</Step>
|
||||
<Step title="Crew 실행 시작하기">
|
||||
입력값과 함께 `POST /kickoff`를 호출하여 crew 실행을 시작하고 `kickoff_id`를
|
||||
받으세요.
|
||||
</Step>
|
||||
|
||||
<Step title="진행 상황 모니터링">
|
||||
`GET /status/{kickoff_id}`를 사용하여 실행 상태를 확인하고 결과를 조회하세요.
|
||||
`GET /{kickoff_id}/status`를 사용하여 실행 상태를 확인하고 결과를 조회하세요.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
@@ -40,13 +41,14 @@ curl -H "Authorization: Bearer YOUR_CREW_TOKEN" \
|
||||
|
||||
### 토큰 유형
|
||||
|
||||
| 토큰 유형 | 범위 | 사용 사례 |
|
||||
|:-----------|:--------|:----------|
|
||||
| **Bearer Token** | 조직 단위 접근 | 전체 crew 운영, 서버 간 통합에 이상적 |
|
||||
| **User Bearer Token** | 사용자 범위 접근 | 제한된 권한, 사용자별 작업에 적합 |
|
||||
| 토큰 유형 | 범위 | 사용 사례 |
|
||||
| :-------------------- | :--------------- | :------------------------------------ |
|
||||
| **Bearer Token** | 조직 단위 접근 | 전체 crew 운영, 서버 간 통합에 이상적 |
|
||||
| **User Bearer Token** | 사용자 범위 접근 | 제한된 권한, 사용자별 작업에 적합 |
|
||||
|
||||
<Tip>
|
||||
두 토큰 유형 모두 CrewAI AOP 대시보드의 crew 상세 페이지 Status 탭에서 확인할 수 있습니다.
|
||||
두 토큰 유형 모두 CrewAI AOP 대시보드의 crew 상세 페이지 Status 탭에서 확인할
|
||||
수 있습니다.
|
||||
</Tip>
|
||||
|
||||
## 기본 URL
|
||||
@@ -63,29 +65,33 @@ https://your-crew-name.crewai.com
|
||||
|
||||
1. **탐색**: `GET /inputs`를 호출하여 crew가 필요한 것을 파악합니다.
|
||||
2. **실행**: `POST /kickoff`를 통해 입력값을 제출하여 처리를 시작합니다.
|
||||
3. **모니터링**: 완료될 때까지 `GET /status/{kickoff_id}`를 주기적으로 조회합니다.
|
||||
3. **모니터링**: 완료될 때까지 `GET /{kickoff_id}/status`를 주기적으로 조회합니다.
|
||||
4. **결과**: 완료된 응답에서 최종 출력을 추출합니다.
|
||||
|
||||
## 오류 처리
|
||||
|
||||
API는 표준 HTTP 상태 코드를 사용합니다:
|
||||
|
||||
| 코드 | 의미 |
|
||||
|------|:--------|
|
||||
| `200` | 성공 |
|
||||
| `400` | 잘못된 요청 - 잘못된 입력 형식 |
|
||||
| `401` | 인증 실패 - 잘못된 베어러 토큰 |
|
||||
| 코드 | 의미 |
|
||||
| ----- | :------------------------------------ |
|
||||
| `200` | 성공 |
|
||||
| `400` | 잘못된 요청 - 잘못된 입력 형식 |
|
||||
| `401` | 인증 실패 - 잘못된 베어러 토큰 |
|
||||
| `404` | 찾을 수 없음 - 리소스가 존재하지 않음 |
|
||||
| `422` | 유효성 검사 오류 - 필수 입력 누락 |
|
||||
| `500` | 서버 오류 - 지원팀에 문의하십시오 |
|
||||
| `422` | 유효성 검사 오류 - 필수 입력 누락 |
|
||||
| `500` | 서버 오류 - 지원팀에 문의하십시오 |
|
||||
|
||||
## 인터랙티브 테스트
|
||||
|
||||
<Info>
|
||||
**왜 "전송" 버튼이 없나요?** 각 CrewAI AOP 사용자는 고유한 crew URL을 가지므로, 혼동을 피하기 위해 인터랙티브 플레이그라운드 대신 **참조 모드**를 사용합니다. 이를 통해 비작동 전송 버튼 없이 요청이 어떻게 생겼는지 정확히 보여줍니다.
|
||||
**왜 "전송" 버튼이 없나요?** 각 CrewAI AOP 사용자는 고유한 crew URL을
|
||||
가지므로, 혼동을 피하기 위해 인터랙티브 플레이그라운드 대신 **참조 모드**를
|
||||
사용합니다. 이를 통해 비작동 전송 버튼 없이 요청이 어떻게 생겼는지 정확히
|
||||
보여줍니다.
|
||||
</Info>
|
||||
|
||||
각 엔드포인트 페이지에서는 다음을 확인할 수 있습니다:
|
||||
|
||||
- ✅ 모든 파라미터가 포함된 **정확한 요청 형식**
|
||||
- ✅ 성공 및 오류 사례에 대한 **응답 예시**
|
||||
- ✅ 여러 언어(cURL, Python, JavaScript 등)로 제공되는 **코드 샘플**
|
||||
@@ -103,6 +109,7 @@ API는 표준 HTTP 상태 코드를 사용합니다:
|
||||
</CardGroup>
|
||||
|
||||
**예시 작업 흐름:**
|
||||
|
||||
1. **cURL 예제를 복사**합니다 (엔드포인트 페이지에서)
|
||||
2. **`your-actual-crew-name.crewai.com`**을(를) 실제 crew URL로 교체합니다
|
||||
3. **Bearer 토큰을** 대시보드에서 복사한 실제 토큰으로 교체합니다
|
||||
@@ -111,10 +118,18 @@ API는 표준 HTTP 상태 코드를 사용합니다:
|
||||
## 도움이 필요하신가요?
|
||||
|
||||
<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 통합 및 문제 해결에 대한 지원을 받으세요
|
||||
</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를 관리하고 실행 로그를 확인하세요
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: "GET /status/{kickoff_id}"
|
||||
title: "GET /{kickoff_id}/status"
|
||||
description: "실행 상태 조회"
|
||||
openapi: "/enterprise-api.ko.yaml GET /status/{kickoff_id}"
|
||||
openapi: "/enterprise-api.ko.yaml GET /{kickoff_id}/status"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -62,13 +62,13 @@ CrewAI CLI를 사용하여 Gmail 트리거 통합을 로컬에서 테스트하
|
||||
crewai triggers list
|
||||
|
||||
# 실제 payload로 Gmail 트리거 시뮬레이션
|
||||
crewai triggers run gmail/new_email
|
||||
crewai triggers run gmail/new_email_received
|
||||
```
|
||||
|
||||
`crewai triggers run` 명령은 완전한 Gmail payload로 크루를 실행하여 배포 전에 파싱 로직을 테스트할 수 있게 해줍니다.
|
||||
|
||||
<Warning>
|
||||
개발 중에는 `crewai triggers run gmail/new_email`을 사용하세요 (`crewai run`이 아님). 배포 후에는 크루가 자동으로 트리거 payload를 받습니다.
|
||||
개발 중에는 `crewai triggers run gmail/new_email_received`을 사용하세요 (`crewai run`이 아님). 배포 후에는 크루가 자동으로 트리거 payload를 받습니다.
|
||||
</Warning>
|
||||
|
||||
## Monitoring Executions
|
||||
@@ -83,6 +83,6 @@ Track history and performance of triggered runs:
|
||||
|
||||
- Ensure Gmail is connected in Tools & Integrations
|
||||
- Verify the Gmail Trigger is enabled on the Triggers tab
|
||||
- `crewai triggers run gmail/new_email`로 로컬 테스트하여 정확한 payload 구조를 확인하세요
|
||||
- `crewai triggers run gmail/new_email_received`로 로컬 테스트하여 정확한 payload 구조를 확인하세요
|
||||
- Check the execution logs and confirm the payload is passed as `crewai_trigger_payload`
|
||||
- 주의: 트리거 실행을 시뮬레이션하려면 `crewai triggers run`을 사용하세요 (`crewai run`이 아님)
|
||||
|
||||
@@ -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.
|
||||
</Step>
|
||||
|
||||
<Step title="Descubra os Inputs Necessários">
|
||||
Use o endpoint `GET /inputs` para ver quais parâmetros seu crew espera.
|
||||
</Step>
|
||||
<Step title="Descubra os Inputs Necessários">
|
||||
Use o endpoint `GET /inputs` para ver quais parâmetros seu crew espera.
|
||||
</Step>
|
||||
|
||||
<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`.
|
||||
</Step>
|
||||
<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`.
|
||||
</Step>
|
||||
|
||||
<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>
|
||||
</Steps>
|
||||
|
||||
@@ -40,13 +41,14 @@ curl -H "Authorization: Bearer YOUR_CREW_TOKEN" \
|
||||
|
||||
### Tipos de Token
|
||||
|
||||
| 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 |
|
||||
| **User Bearer Token** | Acesso com escopo de usuário | Permissões limitadas, adequado para operações específicas de usuário |
|
||||
| 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 |
|
||||
| **User Bearer Token** | Acesso com escopo de usuário | Permissões limitadas, adequado para operações específicas de usuário |
|
||||
|
||||
<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>
|
||||
|
||||
## 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
|
||||
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
|
||||
|
||||
## Tratamento de Erros
|
||||
|
||||
A API utiliza códigos de status HTTP padrão:
|
||||
|
||||
| Código | Significado |
|
||||
|--------|:--------------------------------------|
|
||||
| `200` | Sucesso |
|
||||
| `400` | Requisição Inválida - Formato de input inválido |
|
||||
| `401` | Não Autorizado - Bearer token inválido |
|
||||
| `404` | Não Encontrado - Recurso não existe |
|
||||
| Código | Significado |
|
||||
| ------ | :----------------------------------------------- |
|
||||
| `200` | Sucesso |
|
||||
| `400` | Requisição Inválida - Formato de input inválido |
|
||||
| `401` | Não Autorizado - Bearer token inválido |
|
||||
| `404` | Não Encontrado - Recurso não existe |
|
||||
| `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
|
||||
|
||||
<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>
|
||||
|
||||
Cada página de endpoint mostra para você:
|
||||
|
||||
- ✅ **Formato exato da requisição** com todos os parâmetros
|
||||
- ✅ **Exemplos de resposta** para casos de sucesso e erro
|
||||
- ✅ **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>
|
||||
|
||||
**Exemplo de fluxo:**
|
||||
|
||||
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
|
||||
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?
|
||||
|
||||
<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
|
||||
</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
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: "GET /status/{kickoff_id}"
|
||||
title: "GET /{kickoff_id}/status"
|
||||
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"
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -62,13 +62,13 @@ Teste sua integração de trigger do Gmail localmente usando a CLI da CrewAI:
|
||||
crewai triggers list
|
||||
|
||||
# Simule um trigger do Gmail com payload realista
|
||||
crewai triggers run gmail/new_email
|
||||
crewai triggers run gmail/new_email_received
|
||||
```
|
||||
|
||||
O comando `crewai triggers run` executará sua crew com um payload completo do Gmail, permitindo que você teste sua lógica de parsing antes do deployment.
|
||||
|
||||
<Warning>
|
||||
Use `crewai triggers run gmail/new_email` (não `crewai run`) para simular execução de trigger durante o desenvolvimento. Após o deployment, sua crew receberá automaticamente o payload do trigger.
|
||||
Use `crewai triggers run gmail/new_email_received` (não `crewai run`) para simular execução de trigger durante o desenvolvimento. Após o deployment, sua crew receberá automaticamente o payload do trigger.
|
||||
</Warning>
|
||||
|
||||
## Monitoring Executions
|
||||
@@ -83,6 +83,6 @@ Track history and performance of triggered runs:
|
||||
|
||||
- Ensure Gmail is connected in Tools & Integrations
|
||||
- Verify the Gmail Trigger is enabled on the Triggers tab
|
||||
- Teste localmente com `crewai triggers run gmail/new_email` para ver a estrutura exata do payload
|
||||
- Teste localmente com `crewai triggers run gmail/new_email_received` para ver a estrutura exata do payload
|
||||
- Check the execution logs and confirm the payload is passed as `crewai_trigger_payload`
|
||||
- Lembre-se: use `crewai triggers run` (não `crewai run`) para simular execução de trigger
|
||||
|
||||
@@ -12,7 +12,7 @@ dependencies = [
|
||||
"pytube~=15.0.0",
|
||||
"requests~=2.32.5",
|
||||
"docker~=7.1.0",
|
||||
"crewai==1.7.1",
|
||||
"crewai==1.7.2",
|
||||
"lancedb~=0.5.4",
|
||||
"tiktoken~=0.8.0",
|
||||
"beautifulsoup4~=4.13.4",
|
||||
|
||||
@@ -291,4 +291,4 @@ __all__ = [
|
||||
"ZapierActionTools",
|
||||
]
|
||||
|
||||
__version__ = "1.7.1"
|
||||
__version__ = "1.7.2"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Crewai Enterprise Tools."""
|
||||
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Optional, Union, cast, get_origin
|
||||
@@ -432,7 +432,11 @@ class CrewAIPlatformActionTool(BaseTool):
|
||||
payload = cleaned_kwargs
|
||||
|
||||
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()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
import os
|
||||
from crewai.tools import BaseTool
|
||||
import requests
|
||||
|
||||
@@ -37,6 +37,7 @@ class CrewaiPlatformToolBuilder:
|
||||
headers=headers,
|
||||
timeout=30,
|
||||
params={"apps": ",".join(self._apps)},
|
||||
verify=os.environ.get("CREWAI_FACTORY", "false").lower() != "true",
|
||||
)
|
||||
response.raise_for_status()
|
||||
except Exception:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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 (
|
||||
CrewAIPlatformActionTool,
|
||||
@@ -249,3 +251,109 @@ class TestSchemaProcessing:
|
||||
result_type = tool._process_schema_type(test_schema, "TestFieldAllOfMixed")
|
||||
|
||||
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 "nested_object" 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]
|
||||
tools = [
|
||||
"crewai-tools==1.7.1",
|
||||
"crewai-tools==1.7.2",
|
||||
]
|
||||
embeddings = [
|
||||
"tiktoken~=0.8.0"
|
||||
|
||||
@@ -40,7 +40,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
|
||||
|
||||
_suppress_pydantic_deprecation_warnings()
|
||||
|
||||
__version__ = "1.7.1"
|
||||
__version__ = "1.7.2"
|
||||
_telemetry_submitted = False
|
||||
|
||||
|
||||
|
||||
@@ -149,7 +149,9 @@ class AuthenticationCommand:
|
||||
return
|
||||
|
||||
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"])
|
||||
attempts += 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Any
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import os
|
||||
import requests
|
||||
|
||||
from crewai.cli.config import Settings
|
||||
@@ -33,9 +33,7 @@ class PlusAPI:
|
||||
if settings.org_uuid:
|
||||
self.headers["X-Crewai-Organization-Id"] = settings.org_uuid
|
||||
|
||||
self.base_url = (
|
||||
str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
)
|
||||
self.base_url = os.getenv("CREWAI_PLUS_URL") or str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
|
||||
def _make_request(
|
||||
self, method: str, endpoint: str, **kwargs: Any
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.7.1"
|
||||
"crewai[tools]==1.7.2"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.7.1"
|
||||
"crewai[tools]==1.7.2"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -12,6 +12,7 @@ from rich.console import Console
|
||||
from crewai.cli import git
|
||||
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
||||
from crewai.cli.config import Settings
|
||||
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
from crewai.cli.utils import (
|
||||
build_env_with_tool_repository_credentials,
|
||||
extract_available_exports,
|
||||
@@ -131,10 +132,13 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
self._validate_response(publish_response)
|
||||
|
||||
published_handle = publish_response.json()["handle"]
|
||||
settings = Settings()
|
||||
base_url = settings.enterprise_base_url or DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
|
||||
console.print(
|
||||
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"
|
||||
+ 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",
|
||||
)
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
|
||||
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.version import get_crewai_version
|
||||
from crewai.events.listeners.tracing.types import TraceEvent
|
||||
@@ -16,7 +18,6 @@ from crewai.events.listeners.tracing.utils import (
|
||||
is_tracing_enabled_in_context,
|
||||
should_auto_collect_first_time_traces,
|
||||
)
|
||||
from crewai.utilities.constants import CREWAI_BASE_URL
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
@@ -326,10 +327,12 @@ class TraceBatchManager:
|
||||
if response.status_code == 200:
|
||||
access_code = response.json().get("access_code", None)
|
||||
console = Console()
|
||||
settings = Settings()
|
||||
base_url = settings.enterprise_base_url or DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
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
|
||||
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:
|
||||
|
||||
@@ -676,14 +676,13 @@ class LLM(BaseLLM):
|
||||
formatted_messages = self._format_messages_for_provider(messages)
|
||||
|
||||
# --- 2) Prepare the parameters for the completion call
|
||||
params = {
|
||||
params: dict[str, Any] = {
|
||||
"model": self.model,
|
||||
"messages": formatted_messages,
|
||||
"timeout": self.timeout,
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"n": self.n,
|
||||
"stop": self.stop,
|
||||
"max_tokens": self.max_tokens or self.max_completion_tokens,
|
||||
"presence_penalty": self.presence_penalty,
|
||||
"frequency_penalty": self.frequency_penalty,
|
||||
@@ -702,6 +701,12 @@ class LLM(BaseLLM):
|
||||
**self.additional_params,
|
||||
}
|
||||
|
||||
# Only include stop if it has values and is not in additional_drop_params
|
||||
# Some models (e.g., gpt-5.1) don't support the stop parameter at all
|
||||
drop_params = self.additional_params.get("additional_drop_params", [])
|
||||
if self.stop and "stop" not in drop_params:
|
||||
params["stop"] = self.stop
|
||||
|
||||
# Remove None values from params
|
||||
return {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
|
||||
@@ -583,47 +583,6 @@ class GeminiCompletion(BaseLLM):
|
||||
messages_for_event, content, from_agent
|
||||
)
|
||||
|
||||
def _format_function_call_as_action(
|
||||
self, function_name: str, function_args: dict[str, Any]
|
||||
) -> str:
|
||||
"""Format a function call as Action/Action Input text for CrewAI's parser.
|
||||
|
||||
When Gemini returns a function call but native tool execution is not available
|
||||
(i.e., available_functions is empty), this method formats the function call
|
||||
in the text format that CrewAI's agent parser expects.
|
||||
|
||||
Args:
|
||||
function_name: Name of the function to call
|
||||
function_args: Arguments for the function call
|
||||
|
||||
Returns:
|
||||
Formatted string in "Action: <name>\nAction Input: <args>" format
|
||||
"""
|
||||
import json
|
||||
|
||||
try:
|
||||
args_str = json.dumps(function_args, default=str)
|
||||
except (TypeError, ValueError):
|
||||
args_str = str(function_args)
|
||||
|
||||
return f"Action: {function_name}\nAction Input: {args_str}"
|
||||
|
||||
def _extract_text_from_parts(
|
||||
self, parts: list[Any]
|
||||
) -> str:
|
||||
"""Extract text content from response parts.
|
||||
|
||||
Args:
|
||||
parts: List of response parts from Gemini
|
||||
|
||||
Returns:
|
||||
Concatenated text content from all text parts
|
||||
"""
|
||||
text_parts = [
|
||||
part.text for part in parts if hasattr(part, "text") and part.text
|
||||
]
|
||||
return " ".join(text_parts).strip()
|
||||
|
||||
def _process_response_with_tools(
|
||||
self,
|
||||
response: GenerateContentResponse,
|
||||
@@ -646,8 +605,6 @@ class GeminiCompletion(BaseLLM):
|
||||
Returns:
|
||||
Final response content or function call result
|
||||
"""
|
||||
function_call_fallback = ""
|
||||
|
||||
if response.candidates and (self.tools or available_functions):
|
||||
candidate = response.candidates[0]
|
||||
if candidate.content and candidate.content.parts:
|
||||
@@ -662,35 +619,18 @@ class GeminiCompletion(BaseLLM):
|
||||
else {}
|
||||
)
|
||||
|
||||
if available_functions:
|
||||
result = self._handle_tool_execution(
|
||||
function_name=function_name,
|
||||
function_args=function_args,
|
||||
available_functions=available_functions,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
)
|
||||
result = self._handle_tool_execution(
|
||||
function_name=function_name,
|
||||
function_args=function_args,
|
||||
available_functions=available_functions or {},
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
)
|
||||
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
if not function_call_fallback:
|
||||
function_call_fallback = self._format_function_call_as_action(
|
||||
function_name, function_args
|
||||
)
|
||||
|
||||
content = ""
|
||||
if response.candidates:
|
||||
candidate = response.candidates[0]
|
||||
if candidate.content and candidate.content.parts:
|
||||
content = self._extract_text_from_parts(candidate.content.parts)
|
||||
|
||||
if not content:
|
||||
content = response.text or ""
|
||||
|
||||
if not content and function_call_fallback:
|
||||
content = function_call_fallback
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
content = response.text or ""
|
||||
content = self._apply_stop_words(content)
|
||||
|
||||
return self._finalize_completion_response(
|
||||
@@ -778,10 +718,8 @@ class GeminiCompletion(BaseLLM):
|
||||
"""
|
||||
self._track_token_usage_internal(usage_data)
|
||||
|
||||
function_call_fallback = ""
|
||||
|
||||
# Handle completed function calls
|
||||
if function_calls:
|
||||
if function_calls and available_functions:
|
||||
for call_data in function_calls.values():
|
||||
function_name = call_data["name"]
|
||||
function_args = call_data["args"]
|
||||
@@ -794,32 +732,20 @@ class GeminiCompletion(BaseLLM):
|
||||
if not isinstance(function_args, dict):
|
||||
function_args = {}
|
||||
|
||||
# Try to execute tool if available_functions provided
|
||||
if available_functions:
|
||||
result = self._handle_tool_execution(
|
||||
function_name=function_name,
|
||||
function_args=function_args,
|
||||
available_functions=available_functions,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
)
|
||||
# Execute tool
|
||||
result = self._handle_tool_execution(
|
||||
function_name=function_name,
|
||||
function_args=function_args,
|
||||
available_functions=available_functions,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
)
|
||||
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
# Store first function call as fallback if no text response
|
||||
if not function_call_fallback:
|
||||
function_call_fallback = self._format_function_call_as_action(
|
||||
function_name, function_args
|
||||
)
|
||||
|
||||
# Use function call fallback if no text response
|
||||
content = full_response
|
||||
if not content and function_call_fallback:
|
||||
content = function_call_fallback
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return self._finalize_completion_response(
|
||||
content=content,
|
||||
content=full_response,
|
||||
contents=contents,
|
||||
response_model=response_model,
|
||||
from_task=from_task,
|
||||
|
||||
@@ -30,4 +30,3 @@ NOT_SPECIFIED: Final[
|
||||
"allows us to distinguish between 'not passed at all' and 'explicitly passed None' or '[]'.",
|
||||
]
|
||||
] = _NotSpecified()
|
||||
CREWAI_BASE_URL: Final[str] = "https://app.crewai.com"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import ANY, MagicMock, patch
|
||||
|
||||
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
from crewai.cli.plus_api import PlusAPI
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestPlusAPI(unittest.TestCase):
|
||||
):
|
||||
mock_make_request.assert_called_once_with(
|
||||
method,
|
||||
f"{DEFAULT_CREWAI_ENTERPRISE_URL}{endpoint}",
|
||||
f"{os.getenv('CREWAI_PLUS_URL')}{endpoint}",
|
||||
headers={
|
||||
"Authorization": ANY,
|
||||
"Content-Type": ANY,
|
||||
@@ -53,7 +53,7 @@ class TestPlusAPI(unittest.TestCase):
|
||||
):
|
||||
mock_settings = MagicMock()
|
||||
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
|
||||
# re-initialize Client
|
||||
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):
|
||||
mock_settings = MagicMock()
|
||||
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
|
||||
# re-initialize Client
|
||||
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):
|
||||
mock_settings = MagicMock()
|
||||
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
|
||||
# re-initialize Client
|
||||
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):
|
||||
mock_settings = MagicMock()
|
||||
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
|
||||
# re-initialize Client
|
||||
self.api = PlusAPI(self.api_key)
|
||||
@@ -320,6 +320,7 @@ class TestPlusAPI(unittest.TestCase):
|
||||
)
|
||||
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
@patch.dict(os.environ, {"CREWAI_PLUS_URL": ""})
|
||||
def test_custom_base_url(self, mock_settings_class):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.enterprise_base_url = "https://custom-url.com/api"
|
||||
@@ -329,3 +330,11 @@ class TestPlusAPI(unittest.TestCase):
|
||||
custom_api.base_url,
|
||||
"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",
|
||||
)
|
||||
|
||||
@@ -728,283 +728,3 @@ def test_google_streaming_returns_usage_metrics():
|
||||
assert result.token_usage.prompt_tokens > 0
|
||||
assert result.token_usage.completion_tokens > 0
|
||||
assert result.token_usage.successful_requests >= 1
|
||||
|
||||
|
||||
def test_gemini_function_call_without_text_returns_action_format():
|
||||
"""
|
||||
Test that when Gemini returns a function call without text content,
|
||||
the response is formatted as Action/Action Input for CrewAI's parser.
|
||||
|
||||
This addresses GitHub issue #4093 where Gemini models would return
|
||||
"None or empty response" errors when the model returned only a function call.
|
||||
"""
|
||||
from unittest.mock import Mock, patch
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001")
|
||||
|
||||
with patch.object(completion.client.models, 'generate_content') as mock_generate:
|
||||
mock_function_call = Mock()
|
||||
mock_function_call.name = "search_tool"
|
||||
mock_function_call.args = {"query": "weather in Tokyo"}
|
||||
|
||||
mock_part = Mock()
|
||||
mock_part.function_call = mock_function_call
|
||||
mock_part.text = None
|
||||
|
||||
mock_content = Mock()
|
||||
mock_content.parts = [mock_part]
|
||||
|
||||
mock_candidate = Mock()
|
||||
mock_candidate.content = mock_content
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.candidates = [mock_candidate]
|
||||
mock_response.text = None
|
||||
mock_response.usage_metadata = Mock()
|
||||
mock_response.usage_metadata.prompt_token_count = 100
|
||||
mock_response.usage_metadata.candidates_token_count = 50
|
||||
mock_response.usage_metadata.total_token_count = 150
|
||||
|
||||
mock_generate.return_value = mock_response
|
||||
|
||||
# Pass tools to simulate real scenario where tools are configured
|
||||
# This sets self.tools in the completion object
|
||||
tools = [{"name": "search_tool", "description": "Search for information"}]
|
||||
messages = [{"role": "user", "content": "What's the weather in Tokyo?"}]
|
||||
result = completion.call(messages=messages, tools=tools)
|
||||
|
||||
assert result is not None
|
||||
assert result != ""
|
||||
assert "Action: search_tool" in result
|
||||
assert "Action Input:" in result
|
||||
assert "weather in Tokyo" in result
|
||||
|
||||
|
||||
def test_gemini_function_call_with_text_returns_text():
|
||||
"""
|
||||
Test that when Gemini returns both a function call and text content,
|
||||
the text content is returned (not the Action/Action Input format).
|
||||
"""
|
||||
from unittest.mock import Mock, patch
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001")
|
||||
|
||||
with patch.object(completion.client.models, 'generate_content') as mock_generate:
|
||||
mock_function_call = Mock()
|
||||
mock_function_call.name = "search_tool"
|
||||
mock_function_call.args = {"query": "weather"}
|
||||
|
||||
mock_function_part = Mock()
|
||||
mock_function_part.function_call = mock_function_call
|
||||
mock_function_part.text = None
|
||||
|
||||
mock_text_part = Mock()
|
||||
mock_text_part.function_call = None
|
||||
mock_text_part.text = "Let me search for the weather information."
|
||||
|
||||
mock_content = Mock()
|
||||
mock_content.parts = [mock_text_part, mock_function_part]
|
||||
|
||||
mock_candidate = Mock()
|
||||
mock_candidate.content = mock_content
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.candidates = [mock_candidate]
|
||||
mock_response.text = "Let me search for the weather information."
|
||||
mock_response.usage_metadata = Mock()
|
||||
mock_response.usage_metadata.prompt_token_count = 100
|
||||
mock_response.usage_metadata.candidates_token_count = 50
|
||||
mock_response.usage_metadata.total_token_count = 150
|
||||
|
||||
mock_generate.return_value = mock_response
|
||||
|
||||
# Pass tools to simulate real scenario where tools are configured
|
||||
tools = [{"name": "search_tool", "description": "Search for information"}]
|
||||
messages = [{"role": "user", "content": "What's the weather?"}]
|
||||
result = completion.call(messages=messages, tools=tools)
|
||||
|
||||
assert result is not None
|
||||
assert result != ""
|
||||
assert "Let me search for the weather information" in result
|
||||
assert "Action:" not in result
|
||||
|
||||
|
||||
def test_gemini_function_call_with_available_functions_executes_tool():
|
||||
"""
|
||||
Test that when available_functions is provided, the function call is executed
|
||||
instead of being formatted as Action/Action Input.
|
||||
"""
|
||||
from unittest.mock import Mock, patch
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001")
|
||||
|
||||
def mock_search_tool(query: str) -> str:
|
||||
return f"Search results for: {query}"
|
||||
|
||||
available_functions = {"search_tool": mock_search_tool}
|
||||
|
||||
with patch.object(completion.client.models, 'generate_content') as mock_generate:
|
||||
mock_function_call = Mock()
|
||||
mock_function_call.name = "search_tool"
|
||||
mock_function_call.args = {"query": "weather in Tokyo"}
|
||||
|
||||
mock_part = Mock()
|
||||
mock_part.function_call = mock_function_call
|
||||
mock_part.text = None
|
||||
|
||||
mock_content = Mock()
|
||||
mock_content.parts = [mock_part]
|
||||
|
||||
mock_candidate = Mock()
|
||||
mock_candidate.content = mock_content
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.candidates = [mock_candidate]
|
||||
mock_response.text = None
|
||||
mock_response.usage_metadata = Mock()
|
||||
mock_response.usage_metadata.prompt_token_count = 100
|
||||
mock_response.usage_metadata.candidates_token_count = 50
|
||||
mock_response.usage_metadata.total_token_count = 150
|
||||
|
||||
mock_generate.return_value = mock_response
|
||||
|
||||
# Pass tools to simulate real scenario where tools are configured
|
||||
tools = [{"name": "search_tool", "description": "Search for information"}]
|
||||
messages = [{"role": "user", "content": "What's the weather in Tokyo?"}]
|
||||
result = completion.call(
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
available_functions=available_functions
|
||||
)
|
||||
|
||||
assert result == "Search results for: weather in Tokyo"
|
||||
|
||||
|
||||
def test_gemini_streaming_function_call_without_text_returns_action_format():
|
||||
"""
|
||||
Test that streaming responses with function calls but no text content
|
||||
return Action/Action Input format.
|
||||
"""
|
||||
from unittest.mock import Mock, patch
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001", stream=True)
|
||||
|
||||
with patch.object(completion.client.models, 'generate_content_stream') as mock_stream:
|
||||
mock_function_call = Mock()
|
||||
mock_function_call.name = "calculator"
|
||||
mock_function_call.args = {"expression": "2 + 2"}
|
||||
|
||||
mock_part = Mock()
|
||||
mock_part.function_call = mock_function_call
|
||||
mock_part.text = None
|
||||
|
||||
mock_content = Mock()
|
||||
mock_content.parts = [mock_part]
|
||||
|
||||
mock_candidate = Mock()
|
||||
mock_candidate.content = mock_content
|
||||
|
||||
mock_chunk = Mock()
|
||||
mock_chunk.candidates = [mock_candidate]
|
||||
mock_chunk.text = None
|
||||
mock_chunk.usage_metadata = Mock()
|
||||
mock_chunk.usage_metadata.prompt_token_count = 50
|
||||
mock_chunk.usage_metadata.candidates_token_count = 25
|
||||
mock_chunk.usage_metadata.total_token_count = 75
|
||||
|
||||
mock_stream.return_value = [mock_chunk]
|
||||
|
||||
# Pass tools to simulate real scenario where tools are configured
|
||||
tools = [{"name": "calculator", "description": "Calculate expressions"}]
|
||||
messages = [{"role": "user", "content": "Calculate 2 + 2"}]
|
||||
result = completion.call(messages=messages, tools=tools)
|
||||
|
||||
assert result is not None
|
||||
assert result != ""
|
||||
assert "Action: calculator" in result
|
||||
assert "Action Input:" in result
|
||||
assert "2 + 2" in result
|
||||
|
||||
|
||||
def test_gemini_format_function_call_as_action():
|
||||
"""
|
||||
Test the _format_function_call_as_action helper method directly.
|
||||
"""
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001")
|
||||
|
||||
result = completion._format_function_call_as_action(
|
||||
function_name="search_tool",
|
||||
function_args={"query": "test query", "limit": 10}
|
||||
)
|
||||
|
||||
assert "Action: search_tool" in result
|
||||
assert "Action Input:" in result
|
||||
assert "test query" in result
|
||||
assert "10" in result
|
||||
|
||||
|
||||
def test_gemini_format_function_call_as_action_handles_special_types():
|
||||
"""
|
||||
Test that _format_function_call_as_action handles non-JSON-serializable types.
|
||||
"""
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
from datetime import datetime
|
||||
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001")
|
||||
|
||||
result = completion._format_function_call_as_action(
|
||||
function_name="date_tool",
|
||||
function_args={"date": datetime(2024, 1, 1), "value": 42}
|
||||
)
|
||||
|
||||
assert "Action: date_tool" in result
|
||||
assert "Action Input:" in result
|
||||
assert "42" in result
|
||||
|
||||
|
||||
def test_gemini_extract_text_from_parts():
|
||||
"""
|
||||
Test the _extract_text_from_parts helper method.
|
||||
"""
|
||||
from unittest.mock import Mock
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001")
|
||||
|
||||
mock_part1 = Mock()
|
||||
mock_part1.text = "Hello"
|
||||
|
||||
mock_part2 = Mock()
|
||||
mock_part2.text = "World"
|
||||
|
||||
mock_part3 = Mock()
|
||||
mock_part3.text = None
|
||||
|
||||
parts = [mock_part1, mock_part2, mock_part3]
|
||||
result = completion._extract_text_from_parts(parts)
|
||||
|
||||
assert result == "Hello World"
|
||||
|
||||
|
||||
def test_gemini_extract_text_from_parts_empty():
|
||||
"""
|
||||
Test _extract_text_from_parts with no text parts.
|
||||
"""
|
||||
from unittest.mock import Mock
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
completion = GeminiCompletion(model="gemini-2.0-flash-001")
|
||||
|
||||
mock_part = Mock()
|
||||
mock_part.text = None
|
||||
|
||||
parts = [mock_part]
|
||||
result = completion._extract_text_from_parts(parts)
|
||||
|
||||
assert result == ""
|
||||
|
||||
@@ -877,3 +877,62 @@ def test_validate_model_in_constants():
|
||||
LLM._validate_model_in_constants("anthropic.claude-future-v1:0", "bedrock")
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
def test_prepare_completion_params_excludes_empty_stop():
|
||||
"""Test that _prepare_completion_params excludes stop when it's empty.
|
||||
|
||||
This is a regression test for issue #4149 where models like gpt-5.1
|
||||
don't support the stop parameter at all, and passing an empty list
|
||||
would cause an error.
|
||||
"""
|
||||
llm = LLM(model="gpt-4o", is_litellm=True)
|
||||
# By default, stop is initialized to an empty list
|
||||
assert llm.stop == []
|
||||
|
||||
params = llm._prepare_completion_params("Hello")
|
||||
# stop should not be in params when it's empty
|
||||
assert "stop" not in params
|
||||
|
||||
|
||||
def test_prepare_completion_params_includes_stop_when_provided():
|
||||
"""Test that _prepare_completion_params includes stop when it has values."""
|
||||
llm = LLM(model="gpt-4o", stop=["Observation:"], is_litellm=True)
|
||||
assert llm.stop == ["Observation:"]
|
||||
|
||||
params = llm._prepare_completion_params("Hello")
|
||||
# stop should be in params when it has values
|
||||
assert "stop" in params
|
||||
assert params["stop"] == ["Observation:"]
|
||||
|
||||
|
||||
def test_prepare_completion_params_excludes_stop_when_in_drop_params():
|
||||
"""Test that _prepare_completion_params excludes stop when it's in additional_drop_params.
|
||||
|
||||
This ensures the retry logic works correctly when a model doesn't support stop.
|
||||
"""
|
||||
llm = LLM(
|
||||
model="gpt-4o",
|
||||
stop=["Observation:"],
|
||||
additional_drop_params=["stop"],
|
||||
is_litellm=True,
|
||||
)
|
||||
assert llm.stop == ["Observation:"]
|
||||
|
||||
params = llm._prepare_completion_params("Hello")
|
||||
# stop should not be in params when it's in additional_drop_params
|
||||
assert "stop" not in params
|
||||
|
||||
|
||||
def test_prepare_completion_params_excludes_stop_with_existing_drop_params():
|
||||
"""Test that stop is excluded when additional_drop_params already has other params."""
|
||||
llm = LLM(
|
||||
model="gpt-4o",
|
||||
stop=["Observation:"],
|
||||
additional_drop_params=["another_param", "stop"],
|
||||
is_litellm=True,
|
||||
)
|
||||
|
||||
params = llm._prepare_completion_params("Hello")
|
||||
# stop should not be in params
|
||||
assert "stop" not in params
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""CrewAI development tools."""
|
||||
|
||||
__version__ = "1.7.1"
|
||||
__version__ = "1.7.2"
|
||||
|
||||
Reference in New Issue
Block a user