mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-02-27 08:18:16 +00:00
Compare commits
18 Commits
lg-isolate
...
1.10.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df2778f08b | ||
|
|
d8fea2518d | ||
|
|
d259150d8d | ||
|
|
c4a328c9d5 | ||
|
|
373abbb6b7 | ||
|
|
86d3ee022d | ||
|
|
09e3b81ca3 | ||
|
|
b6d8ce5c55 | ||
|
|
b371f97a2f | ||
|
|
017189db78 | ||
|
|
02d911494f | ||
|
|
8102d0a6ca | ||
|
|
ee374d01de | ||
|
|
9914e51199 | ||
|
|
2dbb83ae31 | ||
|
|
7377e1aa26 | ||
|
|
51754899a2 | ||
|
|
71b4f8402a |
@@ -21,7 +21,6 @@ OPENROUTER_API_KEY=fake-openrouter-key
|
||||
AWS_ACCESS_KEY_ID=fake-aws-access-key
|
||||
AWS_SECRET_ACCESS_KEY=fake-aws-secret-key
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_REGION_NAME=us-east-1
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Azure OpenAI Configuration
|
||||
|
||||
5
.github/workflows/publish.yml
vendored
5
.github/workflows/publish.yml
vendored
@@ -1,8 +1,6 @@
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [deployment-tests-passed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
@@ -20,11 +18,8 @@ jobs:
|
||||
- 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
|
||||
|
||||
18
.github/workflows/trigger-deployment-tests.yml
vendored
18
.github/workflows/trigger-deployment-tests.yml
vendored
@@ -1,18 +0,0 @@
|
||||
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 }}"}'
|
||||
2299
docs/docs.json
2299
docs/docs.json
File diff suppressed because it is too large
Load Diff
@@ -106,6 +106,15 @@ There are different places in CrewAI code where you can specify the model to use
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Info>
|
||||
CrewAI provides native SDK integrations for OpenAI, Anthropic, Google (Gemini API), Azure, and AWS Bedrock — no extra install needed beyond the provider-specific extras (e.g. `uv add "crewai[openai]"`).
|
||||
|
||||
All other providers are powered by **LiteLLM**. If you plan to use any of them, add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Info>
|
||||
|
||||
## Provider Configuration Examples
|
||||
|
||||
CrewAI supports a multitude of LLM providers, each offering unique features, authentication methods, and model capabilities.
|
||||
@@ -275,6 +284,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
| `meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | 128k | 4028 | Text, Image | Text |
|
||||
| `meta_llama/Llama-3.3-70B-Instruct` | 128k | 4028 | Text | Text |
|
||||
| `meta_llama/Llama-3.3-8B-Instruct` | 128k | 4028 | Text | Text |
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Anthropic">
|
||||
@@ -470,7 +484,7 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
To get an Express mode API key:
|
||||
- New Google Cloud users: Get an [express mode API key](https://cloud.google.com/vertex-ai/generative-ai/docs/start/quickstart?usertype=apikey)
|
||||
- Existing Google Cloud users: Get a [Google Cloud API key bound to a service account](https://cloud.google.com/docs/authentication/api-keys)
|
||||
|
||||
|
||||
For more details, see the [Vertex AI Express mode documentation](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/start/quickstart?usertype=apikey).
|
||||
</Info>
|
||||
|
||||
@@ -571,6 +585,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
| gemini-1.5-flash | 1M tokens | Balanced multimodal model, good for most tasks |
|
||||
| gemini-1.5-flash-8B | 1M tokens | Fastest, most cost-efficient, good for high-frequency tasks |
|
||||
| gemini-1.5-pro | 2M tokens | Best performing, wide variety of reasoning tasks including logical reasoning, coding, and creative collaboration |
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Azure">
|
||||
@@ -652,6 +671,7 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
# Optional
|
||||
AWS_SESSION_TOKEN=<your-session-token> # For temporary credentials
|
||||
AWS_DEFAULT_REGION=<your-region> # Defaults to us-east-1
|
||||
AWS_REGION_NAME=<your-region> # Alternative configuration for backwards compatibility with LiteLLM. Defaults to us-east-1
|
||||
```
|
||||
|
||||
**Basic Usage:**
|
||||
@@ -695,6 +715,7 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
- `AWS_SECRET_ACCESS_KEY`: AWS secret key (required)
|
||||
- `AWS_SESSION_TOKEN`: AWS session token for temporary credentials (optional)
|
||||
- `AWS_DEFAULT_REGION`: AWS region (defaults to `us-east-1`)
|
||||
- `AWS_REGION_NAME`: AWS region (defaults to `us-east-1`). Alternative configuration for backwards compatibility with LiteLLM
|
||||
|
||||
**Features:**
|
||||
- Native tool calling support via Converse API
|
||||
@@ -764,6 +785,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
model="sagemaker/<my-endpoint>"
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Mistral">
|
||||
@@ -779,6 +805,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
temperature=0.7
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Nvidia NIM">
|
||||
@@ -865,6 +896,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
| rakuten/rakutenai-7b-instruct | 1,024 tokens | Advanced state-of-the-art LLM with language understanding, superior reasoning, and text generation. |
|
||||
| rakuten/rakutenai-7b-chat | 1,024 tokens | Advanced state-of-the-art LLM with language understanding, superior reasoning, and text generation. |
|
||||
| baichuan-inc/baichuan2-13b-chat | 4,096 tokens | Support Chinese and English chat, coding, math, instruction following, solving quizzes |
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Local NVIDIA NIM Deployed using WSL2">
|
||||
@@ -905,6 +941,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Groq">
|
||||
@@ -926,6 +967,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
| Llama 3.1 70B/8B | 131,072 tokens | High-performance, large context tasks |
|
||||
| Llama 3.2 Series | 8,192 tokens | General-purpose tasks |
|
||||
| Mixtral 8x7B | 32,768 tokens | Balanced performance and context |
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="IBM watsonx.ai">
|
||||
@@ -948,6 +994,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
base_url="https://api.watsonx.ai/v1"
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Ollama (Local LLMs)">
|
||||
@@ -961,6 +1012,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
base_url="http://localhost:11434"
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Fireworks AI">
|
||||
@@ -976,6 +1032,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
temperature=0.7
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Perplexity AI">
|
||||
@@ -991,6 +1052,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
base_url="https://api.perplexity.ai/"
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Hugging Face">
|
||||
@@ -1005,6 +1071,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
model="huggingface/meta-llama/Meta-Llama-3.1-8B-Instruct"
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="SambaNova">
|
||||
@@ -1028,6 +1099,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
| Llama 3.2 Series | 8,192 tokens | General-purpose, multimodal tasks |
|
||||
| Llama 3.3 70B | Up to 131,072 tokens | High-performance and output quality |
|
||||
| Qwen2 familly | 8,192 tokens | High-performance and output quality |
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Cerebras">
|
||||
@@ -1053,6 +1129,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
- Good balance of speed and quality
|
||||
- Support for long context windows
|
||||
</Info>
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Open Router">
|
||||
@@ -1075,6 +1156,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
- openrouter/deepseek/deepseek-r1
|
||||
- openrouter/deepseek/deepseek-chat
|
||||
</Info>
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Nebius AI Studio">
|
||||
@@ -1097,6 +1183,11 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
- Competitive pricing
|
||||
- Good balance of speed and quality
|
||||
</Info>
|
||||
|
||||
**Note:** This provider uses LiteLLM. Add it as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -177,6 +177,11 @@ You need to push your crew to a GitHub repository. If you haven't created a crew
|
||||

|
||||
</Frame>
|
||||
|
||||
<Info>
|
||||
Using private Python packages? You'll need to add your registry credentials here too.
|
||||
See [Private Package Registries](/en/enterprise/guides/private-package-registry) for the required variables.
|
||||
</Info>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Deploy Your Crew">
|
||||
|
||||
@@ -256,6 +256,12 @@ Before deployment, ensure you have:
|
||||
1. **LLM API keys** ready (OpenAI, Anthropic, Google, etc.)
|
||||
2. **Tool API keys** if using external tools (Serper, etc.)
|
||||
|
||||
<Info>
|
||||
If your project depends on packages from a **private PyPI registry**, you'll also need to configure
|
||||
registry authentication credentials as environment variables. See the
|
||||
[Private Package Registries](/en/enterprise/guides/private-package-registry) guide for details.
|
||||
</Info>
|
||||
|
||||
<Tip>
|
||||
Test your project locally with the same environment variables before deploying
|
||||
to catch configuration issues early.
|
||||
|
||||
263
docs/en/enterprise/guides/private-package-registry.mdx
Normal file
263
docs/en/enterprise/guides/private-package-registry.mdx
Normal file
@@ -0,0 +1,263 @@
|
||||
---
|
||||
title: "Private Package Registries"
|
||||
description: "Install private Python packages from authenticated PyPI registries in CrewAI AMP"
|
||||
icon: "lock"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Note>
|
||||
This guide covers how to configure your CrewAI project to install Python packages
|
||||
from private PyPI registries (Azure DevOps Artifacts, GitHub Packages, GitLab, AWS CodeArtifact, etc.)
|
||||
when deploying to CrewAI AMP.
|
||||
</Note>
|
||||
|
||||
## When You Need This
|
||||
|
||||
If your project depends on internal or proprietary Python packages hosted on a private registry
|
||||
rather than the public PyPI, you'll need to:
|
||||
|
||||
1. Tell UV **where** to find the package (an index URL)
|
||||
2. Tell UV **which** packages come from that index (a source mapping)
|
||||
3. Provide **credentials** so UV can authenticate during install
|
||||
|
||||
CrewAI AMP uses [UV](https://docs.astral.sh/uv/) for dependency resolution and installation.
|
||||
UV supports authenticated private registries through `pyproject.toml` configuration combined
|
||||
with environment variables for credentials.
|
||||
|
||||
## Step 1: Configure pyproject.toml
|
||||
|
||||
Three pieces work together in your `pyproject.toml`:
|
||||
|
||||
### 1a. Declare the dependency
|
||||
|
||||
Add the private package to your `[project.dependencies]` like any other dependency:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.100.1,<1.0.0",
|
||||
"my-private-package>=1.2.0",
|
||||
]
|
||||
```
|
||||
|
||||
### 1b. Define the index
|
||||
|
||||
Register your private registry as a named index under `[[tool.uv.index]]`:
|
||||
|
||||
```toml
|
||||
[[tool.uv.index]]
|
||||
name = "my-private-registry"
|
||||
url = "https://pkgs.dev.azure.com/my-org/_packaging/my-feed/pypi/simple/"
|
||||
explicit = true
|
||||
```
|
||||
|
||||
<Info>
|
||||
The `name` field is important — UV uses it to construct the environment variable names
|
||||
for authentication (see [Step 2](#step-2-set-authentication-credentials) below).
|
||||
|
||||
Setting `explicit = true` means UV won't search this index for every package — only the
|
||||
ones you explicitly map to it in `[tool.uv.sources]`. This avoids unnecessary queries
|
||||
against your private registry and protects against dependency confusion attacks.
|
||||
</Info>
|
||||
|
||||
### 1c. Map the package to the index
|
||||
|
||||
Tell UV which packages should be resolved from your private index using `[tool.uv.sources]`:
|
||||
|
||||
```toml
|
||||
[tool.uv.sources]
|
||||
my-private-package = { index = "my-private-registry" }
|
||||
```
|
||||
|
||||
### Complete example
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "my-crew-project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.100.1,<1.0.0",
|
||||
"my-private-package>=1.2.0",
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
type = "crew"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "my-private-registry"
|
||||
url = "https://pkgs.dev.azure.com/my-org/_packaging/my-feed/pypi/simple/"
|
||||
explicit = true
|
||||
|
||||
[tool.uv.sources]
|
||||
my-private-package = { index = "my-private-registry" }
|
||||
```
|
||||
|
||||
After updating `pyproject.toml`, regenerate your lock file:
|
||||
|
||||
```bash
|
||||
uv lock
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Always commit the updated `uv.lock` along with your `pyproject.toml` changes.
|
||||
The lock file is required for deployment — see [Prepare for Deployment](/en/enterprise/guides/prepare-for-deployment).
|
||||
</Warning>
|
||||
|
||||
## Step 2: Set Authentication Credentials
|
||||
|
||||
UV authenticates against private indexes using environment variables that follow a naming convention
|
||||
based on the index name you defined in `pyproject.toml`:
|
||||
|
||||
```
|
||||
UV_INDEX_{UPPER_NAME}_USERNAME
|
||||
UV_INDEX_{UPPER_NAME}_PASSWORD
|
||||
```
|
||||
|
||||
Where `{UPPER_NAME}` is your index name converted to **uppercase** with **hyphens replaced by underscores**.
|
||||
|
||||
For example, an index named `my-private-registry` uses:
|
||||
|
||||
| Variable | Value |
|
||||
|----------|-------|
|
||||
| `UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME` | Your registry username or token name |
|
||||
| `UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD` | Your registry password or token/PAT |
|
||||
|
||||
<Warning>
|
||||
These environment variables **must** be added via the CrewAI AMP **Environment Variables** settings —
|
||||
either globally or at the deployment level. They cannot be set in `.env` files or hardcoded in your project.
|
||||
|
||||
See [Setting Environment Variables in AMP](#setting-environment-variables-in-amp) below.
|
||||
</Warning>
|
||||
|
||||
## Registry Provider Reference
|
||||
|
||||
The table below shows the index URL format and credential values for common registry providers.
|
||||
Replace placeholder values with your actual organization and feed details.
|
||||
|
||||
| Provider | Index URL | Username | Password |
|
||||
|----------|-----------|----------|----------|
|
||||
| **Azure DevOps Artifacts** | `https://pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/` | Any non-empty string (e.g. `token`) | Personal Access Token (PAT) with Packaging Read scope |
|
||||
| **GitHub Packages** | `https://pypi.pkg.github.com/{owner}/simple/` | GitHub username | Personal Access Token (classic) with `read:packages` scope |
|
||||
| **GitLab Package Registry** | `https://gitlab.com/api/v4/projects/{project_id}/packages/pypi/simple/` | `__token__` | Project or Personal Access Token with `read_api` scope |
|
||||
| **AWS CodeArtifact** | Use the URL from `aws codeartifact get-repository-endpoint` | `aws` | Token from `aws codeartifact get-authorization-token` |
|
||||
| **Google Artifact Registry** | `https://{region}-python.pkg.dev/{project}/{repo}/simple/` | `_json_key_base64` | Base64-encoded service account key |
|
||||
| **JFrog Artifactory** | `https://{instance}.jfrog.io/artifactory/api/pypi/{repo}/simple/` | Username or email | API key or identity token |
|
||||
| **Self-hosted (devpi, Nexus, etc.)** | Your registry's simple API URL | Registry username | Registry password |
|
||||
|
||||
<Tip>
|
||||
For **AWS CodeArtifact**, the authorization token expires periodically.
|
||||
You'll need to refresh the `UV_INDEX_*_PASSWORD` value when it expires.
|
||||
Consider automating this in your CI/CD pipeline.
|
||||
</Tip>
|
||||
|
||||
## Setting Environment Variables in AMP
|
||||
|
||||
Private registry credentials must be configured as environment variables in CrewAI AMP.
|
||||
You have two options:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Web Interface">
|
||||
1. Log in to [CrewAI AMP](https://app.crewai.com)
|
||||
2. Navigate to your automation
|
||||
3. Open the **Environment Variables** tab
|
||||
4. Add each variable (`UV_INDEX_*_USERNAME` and `UV_INDEX_*_PASSWORD`) with its value
|
||||
|
||||
See the [Deploy to AMP — Set Environment Variables](/en/enterprise/guides/deploy-to-amp#set-environment-variables) step for details.
|
||||
</Tab>
|
||||
<Tab title="CLI Deployment">
|
||||
Add the variables to your local `.env` file before running `crewai deploy create`.
|
||||
The CLI will securely transfer them to the platform:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
OPENAI_API_KEY=sk-...
|
||||
UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME=token
|
||||
UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD=your-pat-here
|
||||
```
|
||||
|
||||
```bash
|
||||
crewai deploy create
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
**Never** commit credentials to your repository. Use AMP environment variables for all secrets.
|
||||
The `.env` file should be listed in `.gitignore`.
|
||||
</Warning>
|
||||
|
||||
To update credentials on an existing deployment, see [Update Your Crew — Environment Variables](/en/enterprise/guides/update-crew).
|
||||
|
||||
## How It All Fits Together
|
||||
|
||||
When CrewAI AMP builds your automation, the resolution flow works like this:
|
||||
|
||||
<Steps>
|
||||
<Step title="Build starts">
|
||||
AMP pulls your repository and reads `pyproject.toml` and `uv.lock`.
|
||||
</Step>
|
||||
<Step title="UV resolves dependencies">
|
||||
UV reads `[tool.uv.sources]` to determine which index each package should come from.
|
||||
</Step>
|
||||
<Step title="UV authenticates">
|
||||
For each private index, UV looks up `UV_INDEX_{NAME}_USERNAME` and `UV_INDEX_{NAME}_PASSWORD`
|
||||
from the environment variables you configured in AMP.
|
||||
</Step>
|
||||
<Step title="Packages install">
|
||||
UV downloads and installs all packages — both public (from PyPI) and private (from your registry).
|
||||
</Step>
|
||||
<Step title="Automation runs">
|
||||
Your crew or flow starts with all dependencies available.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Authentication Errors During Build
|
||||
|
||||
**Symptom**: Build fails with `401 Unauthorized` or `403 Forbidden` when resolving a private package.
|
||||
|
||||
**Check**:
|
||||
- The `UV_INDEX_*` environment variable names match your index name exactly (uppercased, hyphens → underscores)
|
||||
- Credentials are set in AMP environment variables, not just in a local `.env`
|
||||
- Your token/PAT has the required read permissions for the package feed
|
||||
- The token hasn't expired (especially relevant for AWS CodeArtifact)
|
||||
|
||||
### Package Not Found
|
||||
|
||||
**Symptom**: `No matching distribution found for my-private-package`.
|
||||
|
||||
**Check**:
|
||||
- The index URL in `pyproject.toml` ends with `/simple/`
|
||||
- The `[tool.uv.sources]` entry maps the correct package name to the correct index name
|
||||
- The package is actually published to your private registry
|
||||
- Run `uv lock` locally with the same credentials to verify resolution works
|
||||
|
||||
### Lock File Conflicts
|
||||
|
||||
**Symptom**: `uv lock` fails or produces unexpected results after adding a private index.
|
||||
|
||||
**Solution**: Set the credentials locally and regenerate:
|
||||
|
||||
```bash
|
||||
export UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME=token
|
||||
export UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD=your-pat
|
||||
uv lock
|
||||
```
|
||||
|
||||
Then commit the updated `uv.lock`.
|
||||
|
||||
## Related Guides
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Prepare for Deployment" icon="clipboard-check" href="/en/enterprise/guides/prepare-for-deployment">
|
||||
Verify project structure and dependencies before deploying.
|
||||
</Card>
|
||||
<Card title="Deploy to AMP" icon="rocket" href="/en/enterprise/guides/deploy-to-amp">
|
||||
Deploy your crew or flow and configure environment variables.
|
||||
</Card>
|
||||
<Card title="Update Your Crew" icon="arrows-rotate" href="/en/enterprise/guides/update-crew">
|
||||
Update environment variables and push changes to a running deployment.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -7,7 +7,7 @@ mode: "wide"
|
||||
|
||||
## Connect CrewAI to LLMs
|
||||
|
||||
CrewAI uses LiteLLM to connect to a wide variety of Language Models (LLMs). This integration provides extensive versatility, allowing you to use models from numerous providers with a simple, unified interface.
|
||||
CrewAI connects to LLMs through native SDK integrations for the most popular providers (OpenAI, Anthropic, Google Gemini, Azure, and AWS Bedrock), and uses LiteLLM as a flexible fallback for all other providers.
|
||||
|
||||
<Note>
|
||||
By default, CrewAI uses the `gpt-4o-mini` model. This is determined by the `OPENAI_MODEL_NAME` environment variable, which defaults to "gpt-4o-mini" if not set.
|
||||
@@ -41,6 +41,14 @@ LiteLLM supports a wide range of providers, including but not limited to:
|
||||
|
||||
For a complete and up-to-date list of supported providers, please refer to the [LiteLLM Providers documentation](https://docs.litellm.ai/docs/providers).
|
||||
|
||||
<Info>
|
||||
To use any provider not covered by a native integration, add LiteLLM as a dependency to your project:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
Native providers (OpenAI, Anthropic, Google Gemini, Azure, AWS Bedrock) use their own SDK extras — see the [Provider Configuration Examples](/en/concepts/llms#provider-configuration-examples).
|
||||
</Info>
|
||||
|
||||
## Changing the LLM
|
||||
|
||||
To use a different LLM with your CrewAI agents, you have several options:
|
||||
|
||||
@@ -35,7 +35,7 @@ Visit [app.crewai.com](https://app.crewai.com) and create your free account. Thi
|
||||
If you haven't already, install CrewAI with the CLI tools:
|
||||
|
||||
```bash
|
||||
uv add crewai[tools]
|
||||
uv add 'crewai[tools]'
|
||||
```
|
||||
|
||||
Then authenticate your CLI with your CrewAI AMP account:
|
||||
|
||||
@@ -105,6 +105,15 @@ CrewAI 코드 내에는 사용할 모델을 지정할 수 있는 여러 위치
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Info>
|
||||
CrewAI는 OpenAI, Anthropic, Google (Gemini API), Azure, AWS Bedrock에 대해 네이티브 SDK 통합을 제공합니다 — 제공자별 extras(예: `uv add "crewai[openai]"`) 외에 추가 설치가 필요하지 않습니다.
|
||||
|
||||
그 외 모든 제공자는 **LiteLLM**을 통해 지원됩니다. 이를 사용하려면 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Info>
|
||||
|
||||
## 공급자 구성 예시
|
||||
|
||||
CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양한 LLM 공급자를 지원합니다.
|
||||
@@ -214,6 +223,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
| `meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | 128k | 4028 | 텍스트, 이미지 | 텍스트 |
|
||||
| `meta_llama/Llama-3.3-70B-Instruct` | 128k | 4028 | 텍스트 | 텍스트 |
|
||||
| `meta_llama/Llama-3.3-8B-Instruct` | 128k | 4028 | 텍스트 | 텍스트 |
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Anthropic">
|
||||
@@ -354,6 +368,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
| gemini-1.5-flash | 1M 토큰 | 밸런스 잡힌 멀티모달 모델, 대부분의 작업에 적합 |
|
||||
| gemini-1.5-flash-8B | 1M 토큰 | 가장 빠르고, 비용 효율적, 고빈도 작업에 적합 |
|
||||
| gemini-1.5-pro | 2M 토큰 | 최고의 성능, 논리적 추론, 코딩, 창의적 협업 등 다양한 추론 작업에 적합 |
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Azure">
|
||||
@@ -439,6 +458,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
model="sagemaker/<my-endpoint>"
|
||||
)
|
||||
```
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Mistral">
|
||||
@@ -454,6 +478,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
temperature=0.7
|
||||
)
|
||||
```
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Nvidia NIM">
|
||||
@@ -540,6 +569,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
| rakuten/rakutenai-7b-instruct | 1,024 토큰 | 언어 이해, 추론, 텍스트 생성이 탁월한 최첨단 LLM |
|
||||
| rakuten/rakutenai-7b-chat | 1,024 토큰 | 언어 이해, 추론, 텍스트 생성이 탁월한 최첨단 LLM |
|
||||
| baichuan-inc/baichuan2-13b-chat | 4,096 토큰 | 중국어 및 영어 대화, 코딩, 수학, 지시 따르기, 퀴즈 풀이 지원 |
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Local NVIDIA NIM Deployed using WSL2">
|
||||
@@ -580,6 +614,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Groq">
|
||||
@@ -601,6 +640,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
| Llama 3.1 70B/8B| 131,072 토큰 | 고성능, 대용량 문맥 작업 |
|
||||
| Llama 3.2 Series| 8,192 토큰 | 범용 작업 |
|
||||
| Mixtral 8x7B | 32,768 토큰 | 성능과 문맥의 균형 |
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="IBM watsonx.ai">
|
||||
@@ -623,6 +667,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
base_url="https://api.watsonx.ai/v1"
|
||||
)
|
||||
```
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Ollama (Local LLMs)">
|
||||
@@ -636,6 +685,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
base_url="http://localhost:11434"
|
||||
)
|
||||
```
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Fireworks AI">
|
||||
@@ -651,6 +705,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
temperature=0.7
|
||||
)
|
||||
```
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Perplexity AI">
|
||||
@@ -666,6 +725,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
base_url="https://api.perplexity.ai/"
|
||||
)
|
||||
```
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Hugging Face">
|
||||
@@ -680,6 +744,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
model="huggingface/meta-llama/Meta-Llama-3.1-8B-Instruct"
|
||||
)
|
||||
```
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="SambaNova">
|
||||
@@ -703,6 +772,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
| Llama 3.2 Series| 8,192 토큰 | 범용, 멀티모달 작업 |
|
||||
| Llama 3.3 70B | 최대 131,072 토큰 | 고성능, 높은 출력 품질 |
|
||||
| Qwen2 familly | 8,192 토큰 | 고성능, 높은 출력 품질 |
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Cerebras">
|
||||
@@ -728,6 +802,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
- 속도와 품질의 우수한 밸런스
|
||||
- 긴 컨텍스트 윈도우 지원
|
||||
</Info>
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Open Router">
|
||||
@@ -750,6 +829,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
- openrouter/deepseek/deepseek-r1
|
||||
- openrouter/deepseek/deepseek-chat
|
||||
</Info>
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Nebius AI Studio">
|
||||
@@ -772,6 +856,11 @@ CrewAI는 고유한 기능, 인증 방법, 모델 역량을 제공하는 다양
|
||||
- 경쟁력 있는 가격
|
||||
- 속도와 품질의 우수한 밸런스
|
||||
</Info>
|
||||
|
||||
**참고:** 이 제공자는 LiteLLM을 사용합니다. 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -176,6 +176,11 @@ Crew를 GitHub 저장소에 푸시해야 합니다. 아직 Crew를 만들지 않
|
||||

|
||||
</Frame>
|
||||
|
||||
<Info>
|
||||
프라이빗 Python 패키지를 사용하시나요? 여기에 레지스트리 자격 증명도 추가해야 합니다.
|
||||
필요한 변수는 [프라이빗 패키지 레지스트리](/ko/enterprise/guides/private-package-registry)를 참조하세요.
|
||||
</Info>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Crew 배포하기">
|
||||
|
||||
@@ -256,6 +256,12 @@ Crews와 Flows 모두 `src/project_name/main.py`에 진입점이 있습니다:
|
||||
1. **LLM API 키** (OpenAI, Anthropic, Google 등)
|
||||
2. **도구 API 키** - 외부 도구를 사용하는 경우 (Serper 등)
|
||||
|
||||
<Info>
|
||||
프로젝트가 **프라이빗 PyPI 레지스트리**의 패키지에 의존하는 경우, 레지스트리 인증 자격 증명도
|
||||
환경 변수로 구성해야 합니다. 자세한 내용은
|
||||
[프라이빗 패키지 레지스트리](/ko/enterprise/guides/private-package-registry) 가이드를 참조하세요.
|
||||
</Info>
|
||||
|
||||
<Tip>
|
||||
구성 문제를 조기에 발견하기 위해 배포 전에 동일한 환경 변수로
|
||||
로컬에서 프로젝트를 테스트하세요.
|
||||
|
||||
261
docs/ko/enterprise/guides/private-package-registry.mdx
Normal file
261
docs/ko/enterprise/guides/private-package-registry.mdx
Normal file
@@ -0,0 +1,261 @@
|
||||
---
|
||||
title: "프라이빗 패키지 레지스트리"
|
||||
description: "CrewAI AMP에서 인증된 PyPI 레지스트리의 프라이빗 Python 패키지 설치하기"
|
||||
icon: "lock"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Note>
|
||||
이 가이드는 CrewAI AMP에 배포할 때 프라이빗 PyPI 레지스트리(Azure DevOps Artifacts, GitHub Packages,
|
||||
GitLab, AWS CodeArtifact 등)에서 Python 패키지를 설치하도록 CrewAI 프로젝트를 구성하는 방법을 다룹니다.
|
||||
</Note>
|
||||
|
||||
## 이 가이드가 필요한 경우
|
||||
|
||||
프로젝트가 공개 PyPI가 아닌 프라이빗 레지스트리에 호스팅된 내부 또는 독점 Python 패키지에
|
||||
의존하는 경우, 다음을 수행해야 합니다:
|
||||
|
||||
1. UV에 패키지를 **어디서** 찾을지 알려줍니다 (index URL)
|
||||
2. UV에 **어떤** 패키지가 해당 index에서 오는지 알려줍니다 (source 매핑)
|
||||
3. UV가 설치 중에 인증할 수 있도록 **자격 증명**을 제공합니다
|
||||
|
||||
CrewAI AMP는 의존성 해결 및 설치에 [UV](https://docs.astral.sh/uv/)를 사용합니다.
|
||||
UV는 `pyproject.toml` 구성과 자격 증명용 환경 변수를 결합하여 인증된 프라이빗 레지스트리를 지원합니다.
|
||||
|
||||
## 1단계: pyproject.toml 구성
|
||||
|
||||
`pyproject.toml`에서 세 가지 요소가 함께 작동합니다:
|
||||
|
||||
### 1a. 의존성 선언
|
||||
|
||||
프라이빗 패키지를 다른 의존성과 마찬가지로 `[project.dependencies]`에 추가합니다:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.100.1,<1.0.0",
|
||||
"my-private-package>=1.2.0",
|
||||
]
|
||||
```
|
||||
|
||||
### 1b. index 정의
|
||||
|
||||
프라이빗 레지스트리를 `[[tool.uv.index]]` 아래에 명명된 index로 등록합니다:
|
||||
|
||||
```toml
|
||||
[[tool.uv.index]]
|
||||
name = "my-private-registry"
|
||||
url = "https://pkgs.dev.azure.com/my-org/_packaging/my-feed/pypi/simple/"
|
||||
explicit = true
|
||||
```
|
||||
|
||||
<Info>
|
||||
`name` 필드는 중요합니다 — UV는 이를 사용하여 인증을 위한 환경 변수 이름을
|
||||
구성합니다 (아래 [2단계](#2단계-인증-자격-증명-설정)를 참조하세요).
|
||||
|
||||
`explicit = true`를 설정하면 UV가 모든 패키지에 대해 이 index를 검색하지 않습니다 —
|
||||
`[tool.uv.sources]`에서 명시적으로 매핑한 패키지만 검색합니다. 이렇게 하면 프라이빗
|
||||
레지스트리에 대한 불필요한 쿼리를 방지하고 의존성 혼동 공격을 차단할 수 있습니다.
|
||||
</Info>
|
||||
|
||||
### 1c. 패키지를 index에 매핑
|
||||
|
||||
`[tool.uv.sources]`를 사용하여 프라이빗 index에서 해결해야 할 패키지를 UV에 알려줍니다:
|
||||
|
||||
```toml
|
||||
[tool.uv.sources]
|
||||
my-private-package = { index = "my-private-registry" }
|
||||
```
|
||||
|
||||
### 전체 예시
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "my-crew-project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.100.1,<1.0.0",
|
||||
"my-private-package>=1.2.0",
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
type = "crew"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "my-private-registry"
|
||||
url = "https://pkgs.dev.azure.com/my-org/_packaging/my-feed/pypi/simple/"
|
||||
explicit = true
|
||||
|
||||
[tool.uv.sources]
|
||||
my-private-package = { index = "my-private-registry" }
|
||||
```
|
||||
|
||||
`pyproject.toml`을 업데이트한 후 lock 파일을 다시 생성합니다:
|
||||
|
||||
```bash
|
||||
uv lock
|
||||
```
|
||||
|
||||
<Warning>
|
||||
업데이트된 `uv.lock`을 항상 `pyproject.toml` 변경 사항과 함께 커밋하세요.
|
||||
lock 파일은 배포에 필수입니다 — [배포 준비하기](/ko/enterprise/guides/prepare-for-deployment)를 참조하세요.
|
||||
</Warning>
|
||||
|
||||
## 2단계: 인증 자격 증명 설정
|
||||
|
||||
UV는 `pyproject.toml`에서 정의한 index 이름을 기반으로 한 명명 규칙을 따르는
|
||||
환경 변수를 사용하여 프라이빗 index에 인증합니다:
|
||||
|
||||
```
|
||||
UV_INDEX_{UPPER_NAME}_USERNAME
|
||||
UV_INDEX_{UPPER_NAME}_PASSWORD
|
||||
```
|
||||
|
||||
여기서 `{UPPER_NAME}`은 index 이름을 **대문자**로 변환하고 **하이픈을 언더스코어로 대체**한 것입니다.
|
||||
|
||||
예를 들어, `my-private-registry`라는 이름의 index는 다음을 사용합니다:
|
||||
|
||||
| 변수 | 값 |
|
||||
|------|-----|
|
||||
| `UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME` | 레지스트리 사용자 이름 또는 토큰 이름 |
|
||||
| `UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD` | 레지스트리 비밀번호 또는 토큰/PAT |
|
||||
|
||||
<Warning>
|
||||
이 환경 변수는 CrewAI AMP **환경 변수** 설정을 통해 **반드시** 추가해야 합니다 —
|
||||
전역적으로 또는 배포 수준에서. `.env` 파일에 설정하거나 프로젝트에 하드코딩할 수 없습니다.
|
||||
|
||||
아래 [AMP에서 환경 변수 설정](#amp에서-환경-변수-설정)을 참조하세요.
|
||||
</Warning>
|
||||
|
||||
## 레지스트리 제공업체 참조
|
||||
|
||||
아래 표는 일반적인 레지스트리 제공업체의 index URL 형식과 자격 증명 값을 보여줍니다.
|
||||
자리 표시자 값을 실제 조직 및 피드 세부 정보로 대체하세요.
|
||||
|
||||
| 제공업체 | Index URL | 사용자 이름 | 비밀번호 |
|
||||
|---------|-----------|-----------|---------|
|
||||
| **Azure DevOps Artifacts** | `https://pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/` | 비어 있지 않은 임의의 문자열 (예: `token`) | Packaging Read 범위의 Personal Access Token (PAT) |
|
||||
| **GitHub Packages** | `https://pypi.pkg.github.com/{owner}/simple/` | GitHub 사용자 이름 | `read:packages` 범위의 Personal Access Token (classic) |
|
||||
| **GitLab Package Registry** | `https://gitlab.com/api/v4/projects/{project_id}/packages/pypi/simple/` | `__token__` | `read_api` 범위의 Project 또는 Personal Access Token |
|
||||
| **AWS CodeArtifact** | `aws codeartifact get-repository-endpoint`의 URL 사용 | `aws` | `aws codeartifact get-authorization-token`의 토큰 |
|
||||
| **Google Artifact Registry** | `https://{region}-python.pkg.dev/{project}/{repo}/simple/` | `_json_key_base64` | Base64로 인코딩된 서비스 계정 키 |
|
||||
| **JFrog Artifactory** | `https://{instance}.jfrog.io/artifactory/api/pypi/{repo}/simple/` | 사용자 이름 또는 이메일 | API 키 또는 ID 토큰 |
|
||||
| **자체 호스팅 (devpi, Nexus 등)** | 레지스트리의 simple API URL | 레지스트리 사용자 이름 | 레지스트리 비밀번호 |
|
||||
|
||||
<Tip>
|
||||
**AWS CodeArtifact**의 경우 인증 토큰이 주기적으로 만료됩니다.
|
||||
만료되면 `UV_INDEX_*_PASSWORD` 값을 갱신해야 합니다.
|
||||
CI/CD 파이프라인에서 이를 자동화하는 것을 고려하세요.
|
||||
</Tip>
|
||||
|
||||
## AMP에서 환경 변수 설정
|
||||
|
||||
프라이빗 레지스트리 자격 증명은 CrewAI AMP에서 환경 변수로 구성해야 합니다.
|
||||
두 가지 옵션이 있습니다:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="웹 인터페이스">
|
||||
1. [CrewAI AMP](https://app.crewai.com)에 로그인합니다
|
||||
2. 자동화로 이동합니다
|
||||
3. **Environment Variables** 탭을 엽니다
|
||||
4. 각 변수 (`UV_INDEX_*_USERNAME` 및 `UV_INDEX_*_PASSWORD`)에 값을 추가합니다
|
||||
|
||||
자세한 내용은 [AMP에 배포하기 — 환경 변수 설정하기](/ko/enterprise/guides/deploy-to-amp#환경-변수-설정하기) 단계를 참조하세요.
|
||||
</Tab>
|
||||
<Tab title="CLI 배포">
|
||||
`crewai deploy create`를 실행하기 전에 로컬 `.env` 파일에 변수를 추가합니다.
|
||||
CLI가 이를 안전하게 플랫폼으로 전송합니다:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
OPENAI_API_KEY=sk-...
|
||||
UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME=token
|
||||
UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD=your-pat-here
|
||||
```
|
||||
|
||||
```bash
|
||||
crewai deploy create
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
자격 증명을 저장소에 **절대** 커밋하지 마세요. 모든 비밀 정보에는 AMP 환경 변수를 사용하세요.
|
||||
`.env` 파일은 `.gitignore`에 포함되어야 합니다.
|
||||
</Warning>
|
||||
|
||||
기존 배포의 자격 증명을 업데이트하려면 [Crew 업데이트하기 — 환경 변수](/ko/enterprise/guides/update-crew)를 참조하세요.
|
||||
|
||||
## 전체 동작 흐름
|
||||
|
||||
CrewAI AMP가 자동화를 빌드할 때, 해결 흐름은 다음과 같이 작동합니다:
|
||||
|
||||
<Steps>
|
||||
<Step title="빌드 시작">
|
||||
AMP가 저장소를 가져오고 `pyproject.toml`과 `uv.lock`을 읽습니다.
|
||||
</Step>
|
||||
<Step title="UV가 의존성 해결">
|
||||
UV가 `[tool.uv.sources]`를 읽어 각 패키지가 어떤 index에서 와야 하는지 결정합니다.
|
||||
</Step>
|
||||
<Step title="UV가 인증">
|
||||
각 프라이빗 index에 대해 UV가 AMP에서 구성한 환경 변수에서
|
||||
`UV_INDEX_{NAME}_USERNAME`과 `UV_INDEX_{NAME}_PASSWORD`를 조회합니다.
|
||||
</Step>
|
||||
<Step title="패키지 설치">
|
||||
UV가 공개(PyPI) 및 프라이빗(레지스트리) 패키지를 모두 다운로드하고 설치합니다.
|
||||
</Step>
|
||||
<Step title="자동화 실행">
|
||||
모든 의존성이 사용 가능한 상태에서 crew 또는 flow가 시작됩니다.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## 문제 해결
|
||||
|
||||
### 빌드 중 인증 오류
|
||||
|
||||
**증상**: 프라이빗 패키지를 해결할 때 `401 Unauthorized` 또는 `403 Forbidden`으로 빌드가 실패합니다.
|
||||
|
||||
**확인사항**:
|
||||
- `UV_INDEX_*` 환경 변수 이름이 index 이름과 정확히 일치하는지 확인합니다 (대문자, 하이픈 -> 언더스코어)
|
||||
- 자격 증명이 로컬 `.env`뿐만 아니라 AMP 환경 변수에 설정되어 있는지 확인합니다
|
||||
- 토큰/PAT에 패키지 피드에 필요한 읽기 권한이 있는지 확인합니다
|
||||
- 토큰이 만료되지 않았는지 확인합니다 (특히 AWS CodeArtifact의 경우)
|
||||
|
||||
### 패키지를 찾을 수 없음
|
||||
|
||||
**증상**: `No matching distribution found for my-private-package`.
|
||||
|
||||
**확인사항**:
|
||||
- `pyproject.toml`의 index URL이 `/simple/`로 끝나는지 확인합니다
|
||||
- `[tool.uv.sources]` 항목이 올바른 패키지 이름을 올바른 index 이름에 매핑하는지 확인합니다
|
||||
- 패키지가 실제로 프라이빗 레지스트리에 게시되어 있는지 확인합니다
|
||||
- 동일한 자격 증명으로 로컬에서 `uv lock`을 실행하여 해결이 작동하는지 확인합니다
|
||||
|
||||
### Lock 파일 충돌
|
||||
|
||||
**증상**: 프라이빗 index를 추가한 후 `uv lock`이 실패하거나 예상치 못한 결과를 생성합니다.
|
||||
|
||||
**해결책**: 로컬에서 자격 증명을 설정하고 다시 생성합니다:
|
||||
|
||||
```bash
|
||||
export UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME=token
|
||||
export UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD=your-pat
|
||||
uv lock
|
||||
```
|
||||
|
||||
그런 다음 업데이트된 `uv.lock`을 커밋합니다.
|
||||
|
||||
## 관련 가이드
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="배포 준비하기" icon="clipboard-check" href="/ko/enterprise/guides/prepare-for-deployment">
|
||||
배포 전에 프로젝트 구조와 의존성을 확인합니다.
|
||||
</Card>
|
||||
<Card title="AMP에 배포하기" icon="rocket" href="/ko/enterprise/guides/deploy-to-amp">
|
||||
crew 또는 flow를 배포하고 환경 변수를 구성합니다.
|
||||
</Card>
|
||||
<Card title="Crew 업데이트하기" icon="arrows-rotate" href="/ko/enterprise/guides/update-crew">
|
||||
환경 변수를 업데이트하고 실행 중인 배포에 변경 사항을 푸시합니다.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -7,7 +7,7 @@ mode: "wide"
|
||||
|
||||
## CrewAI를 LLM에 연결하기
|
||||
|
||||
CrewAI는 LiteLLM을 사용하여 다양한 언어 모델(LLM)에 연결합니다. 이 통합은 높은 다양성을 제공하여, 여러 공급자의 모델을 간단하고 통합된 인터페이스로 사용할 수 있게 해줍니다.
|
||||
CrewAI는 가장 인기 있는 제공자(OpenAI, Anthropic, Google Gemini, Azure, AWS Bedrock)에 대해 네이티브 SDK 통합을 통해 LLM에 연결하며, 그 외 모든 제공자에 대해서는 LiteLLM을 유연한 폴백으로 사용합니다.
|
||||
|
||||
<Note>
|
||||
기본적으로 CrewAI는 `gpt-4o-mini` 모델을 사용합니다. 이는 `OPENAI_MODEL_NAME` 환경 변수에 의해 결정되며, 설정되지 않은 경우 기본값은 "gpt-4o-mini"입니다.
|
||||
@@ -41,6 +41,14 @@ LiteLLM은 다음을 포함하되 이에 국한되지 않는 다양한 프로바
|
||||
|
||||
지원되는 프로바이더의 전체 및 최신 목록은 [LiteLLM 프로바이더 문서](https://docs.litellm.ai/docs/providers)를 참조하세요.
|
||||
|
||||
<Info>
|
||||
네이티브 통합에서 지원하지 않는 제공자를 사용하려면 LiteLLM을 프로젝트에 의존성으로 추가하세요:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
네이티브 제공자(OpenAI, Anthropic, Google Gemini, Azure, AWS Bedrock)는 자체 SDK extras를 사용합니다 — [공급자 구성 예시](/ko/concepts/llms#공급자-구성-예시)를 참조하세요.
|
||||
</Info>
|
||||
|
||||
## LLM 변경하기
|
||||
|
||||
CrewAI agent에서 다른 LLM을 사용하려면 여러 가지 방법이 있습니다:
|
||||
|
||||
@@ -35,7 +35,7 @@ crewai login
|
||||
아직 설치하지 않았다면 CLI 도구와 함께 CrewAI를 설치하세요:
|
||||
|
||||
```bash
|
||||
uv add crewai[tools]
|
||||
uv add 'crewai[tools]'
|
||||
```
|
||||
|
||||
그런 다음 CrewAI AMP 계정으로 CLI를 인증하세요:
|
||||
|
||||
@@ -105,6 +105,15 @@ Existem diferentes locais no código do CrewAI onde você pode especificar o mod
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Info>
|
||||
O CrewAI oferece integrações nativas via SDK para OpenAI, Anthropic, Google (Gemini API), Azure e AWS Bedrock — sem necessidade de instalação extra além dos extras específicos do provedor (ex.: `uv add "crewai[openai]"`).
|
||||
|
||||
Todos os outros provedores são alimentados pelo **LiteLLM**. Se você planeja usar algum deles, adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Info>
|
||||
|
||||
## Exemplos de Configuração de Provedores
|
||||
|
||||
O CrewAI suporta uma grande variedade de provedores de LLM, cada um com recursos, métodos de autenticação e capacidades de modelo únicos.
|
||||
@@ -214,6 +223,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
| `meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | 128k | 4028 | Texto, Imagem | Texto |
|
||||
| `meta_llama/Llama-3.3-70B-Instruct` | 128k | 4028 | Texto | Texto |
|
||||
| `meta_llama/Llama-3.3-8B-Instruct` | 128k | 4028 | Texto | Texto |
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Anthropic">
|
||||
@@ -354,6 +368,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
| gemini-1.5-flash | 1M tokens | Modelo multimodal equilibrado, bom para maioria das tarefas |
|
||||
| gemini-1.5-flash-8B | 1M tokens | Mais rápido, mais eficiente em custo, adequado para tarefas de alta frequência |
|
||||
| gemini-1.5-pro | 2M tokens | Melhor desempenho para uma ampla variedade de tarefas de raciocínio, incluindo lógica, codificação e colaboração criativa |
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Azure">
|
||||
@@ -438,6 +457,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
model="sagemaker/<my-endpoint>"
|
||||
)
|
||||
```
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Mistral">
|
||||
@@ -453,6 +477,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
temperature=0.7
|
||||
)
|
||||
```
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Nvidia NIM">
|
||||
@@ -539,6 +568,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
| rakuten/rakutenai-7b-instruct | 1.024 tokens | LLM topo de linha, compreensão, raciocínio e geração textual.|
|
||||
| rakuten/rakutenai-7b-chat | 1.024 tokens | LLM topo de linha, compreensão, raciocínio e geração textual.|
|
||||
| baichuan-inc/baichuan2-13b-chat | 4.096 tokens | Suporte a chat em chinês/inglês, programação, matemática, seguir instruções, resolver quizzes.|
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Local NVIDIA NIM Deployed using WSL2">
|
||||
@@ -579,6 +613,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Groq">
|
||||
@@ -600,6 +639,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
| Llama 3.1 70B/8B | 131.072 tokens | Alta performance e tarefas de contexto grande|
|
||||
| Llama 3.2 Série | 8.192 tokens | Tarefas gerais |
|
||||
| Mixtral 8x7B | 32.768 tokens | Equilíbrio entre performance e contexto |
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="IBM watsonx.ai">
|
||||
@@ -622,6 +666,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
base_url="https://api.watsonx.ai/v1"
|
||||
)
|
||||
```
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Ollama (LLMs Locais)">
|
||||
@@ -635,6 +684,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
base_url="http://localhost:11434"
|
||||
)
|
||||
```
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Fireworks AI">
|
||||
@@ -650,6 +704,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
temperature=0.7
|
||||
)
|
||||
```
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Perplexity AI">
|
||||
@@ -665,6 +724,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
base_url="https://api.perplexity.ai/"
|
||||
)
|
||||
```
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Hugging Face">
|
||||
@@ -679,6 +743,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
model="huggingface/meta-llama/Meta-Llama-3.1-8B-Instruct"
|
||||
)
|
||||
```
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="SambaNova">
|
||||
@@ -702,6 +771,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
| Llama 3.2 Série | 8.192 tokens | Tarefas gerais e multimodais |
|
||||
| Llama 3.3 70B | Até 131.072 tokens | Desempenho e qualidade de saída elevada |
|
||||
| Família Qwen2 | 8.192 tokens | Desempenho e qualidade de saída elevada |
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Cerebras">
|
||||
@@ -727,6 +801,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
- Equilíbrio entre velocidade e qualidade
|
||||
- Suporte a longas janelas de contexto
|
||||
</Info>
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Open Router">
|
||||
@@ -749,6 +828,11 @@ Nesta seção, você encontrará exemplos detalhados que ajudam a selecionar, co
|
||||
- openrouter/deepseek/deepseek-r1
|
||||
- openrouter/deepseek/deepseek-chat
|
||||
</Info>
|
||||
|
||||
**Nota:** Este provedor usa o LiteLLM. Adicione-o como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -176,6 +176,11 @@ Você precisa enviar seu crew para um repositório do GitHub. Caso ainda não te
|
||||

|
||||
</Frame>
|
||||
|
||||
<Info>
|
||||
Usando pacotes Python privados? Você também precisará adicionar suas credenciais de registro aqui.
|
||||
Consulte [Registros de Pacotes Privados](/pt-BR/enterprise/guides/private-package-registry) para as variáveis necessárias.
|
||||
</Info>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Implante Seu Crew">
|
||||
|
||||
@@ -256,6 +256,12 @@ Antes da implantação, certifique-se de ter:
|
||||
1. **Chaves de API de LLM** prontas (OpenAI, Anthropic, Google, etc.)
|
||||
2. **Chaves de API de ferramentas** se estiver usando ferramentas externas (Serper, etc.)
|
||||
|
||||
<Info>
|
||||
Se seu projeto depende de pacotes de um **registro PyPI privado**, você também precisará configurar
|
||||
credenciais de autenticação do registro como variáveis de ambiente. Consulte o guia
|
||||
[Registros de Pacotes Privados](/pt-BR/enterprise/guides/private-package-registry) para mais detalhes.
|
||||
</Info>
|
||||
|
||||
<Tip>
|
||||
Teste seu projeto localmente com as mesmas variáveis de ambiente antes de implantar
|
||||
para detectar problemas de configuração antecipadamente.
|
||||
|
||||
263
docs/pt-BR/enterprise/guides/private-package-registry.mdx
Normal file
263
docs/pt-BR/enterprise/guides/private-package-registry.mdx
Normal file
@@ -0,0 +1,263 @@
|
||||
---
|
||||
title: "Registros de Pacotes Privados"
|
||||
description: "Instale pacotes Python privados de registros PyPI autenticados no CrewAI AMP"
|
||||
icon: "lock"
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Este guia aborda como configurar seu projeto CrewAI para instalar pacotes Python
|
||||
de registros PyPI privados (Azure DevOps Artifacts, GitHub Packages, GitLab, AWS CodeArtifact, etc.)
|
||||
ao implantar no CrewAI AMP.
|
||||
</Note>
|
||||
|
||||
## Quando Você Precisa Disso
|
||||
|
||||
Se seu projeto depende de pacotes Python internos ou proprietários hospedados em um registro privado
|
||||
em vez do PyPI público, você precisará:
|
||||
|
||||
1. Informar ao UV **onde** encontrar o pacote (uma URL de index)
|
||||
2. Informar ao UV **quais** pacotes vêm desse index (um mapeamento de source)
|
||||
3. Fornecer **credenciais** para que o UV possa autenticar durante a instalação
|
||||
|
||||
O CrewAI AMP usa [UV](https://docs.astral.sh/uv/) para resolução e instalação de dependências.
|
||||
O UV suporta registros privados autenticados por meio da configuração do `pyproject.toml` combinada
|
||||
com variáveis de ambiente para credenciais.
|
||||
|
||||
## Passo 1: Configurar o pyproject.toml
|
||||
|
||||
Três elementos trabalham juntos no seu `pyproject.toml`:
|
||||
|
||||
### 1a. Declarar a dependência
|
||||
|
||||
Adicione o pacote privado ao seu `[project.dependencies]` como qualquer outra dependência:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.100.1,<1.0.0",
|
||||
"my-private-package>=1.2.0",
|
||||
]
|
||||
```
|
||||
|
||||
### 1b. Definir o index
|
||||
|
||||
Registre seu registro privado como um index nomeado em `[[tool.uv.index]]`:
|
||||
|
||||
```toml
|
||||
[[tool.uv.index]]
|
||||
name = "my-private-registry"
|
||||
url = "https://pkgs.dev.azure.com/my-org/_packaging/my-feed/pypi/simple/"
|
||||
explicit = true
|
||||
```
|
||||
|
||||
<Info>
|
||||
O campo `name` é importante — o UV o utiliza para construir os nomes das variáveis de ambiente
|
||||
para autenticação (veja o [Passo 2](#passo-2-configurar-credenciais-de-autenticação) abaixo).
|
||||
|
||||
Definir `explicit = true` significa que o UV não consultará esse index para todos os pacotes — apenas
|
||||
os que você mapear explicitamente em `[tool.uv.sources]`. Isso evita consultas desnecessárias
|
||||
ao seu registro privado e protege contra ataques de confusão de dependências.
|
||||
</Info>
|
||||
|
||||
### 1c. Mapear o pacote para o index
|
||||
|
||||
Informe ao UV quais pacotes devem ser resolvidos a partir do seu index privado usando `[tool.uv.sources]`:
|
||||
|
||||
```toml
|
||||
[tool.uv.sources]
|
||||
my-private-package = { index = "my-private-registry" }
|
||||
```
|
||||
|
||||
### Exemplo completo
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "my-crew-project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.100.1,<1.0.0",
|
||||
"my-private-package>=1.2.0",
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
type = "crew"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "my-private-registry"
|
||||
url = "https://pkgs.dev.azure.com/my-org/_packaging/my-feed/pypi/simple/"
|
||||
explicit = true
|
||||
|
||||
[tool.uv.sources]
|
||||
my-private-package = { index = "my-private-registry" }
|
||||
```
|
||||
|
||||
Após atualizar o `pyproject.toml`, regenere seu arquivo lock:
|
||||
|
||||
```bash
|
||||
uv lock
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Sempre faça commit do `uv.lock` atualizado junto com as alterações no `pyproject.toml`.
|
||||
O arquivo lock é obrigatório para implantação — veja [Preparar para Implantação](/pt-BR/enterprise/guides/prepare-for-deployment).
|
||||
</Warning>
|
||||
|
||||
## Passo 2: Configurar Credenciais de Autenticação
|
||||
|
||||
O UV autentica em indexes privados usando variáveis de ambiente que seguem uma convenção de nomenclatura
|
||||
baseada no nome do index que você definiu no `pyproject.toml`:
|
||||
|
||||
```
|
||||
UV_INDEX_{UPPER_NAME}_USERNAME
|
||||
UV_INDEX_{UPPER_NAME}_PASSWORD
|
||||
```
|
||||
|
||||
Onde `{UPPER_NAME}` é o nome do seu index convertido para **maiúsculas** com **hifens substituídos por underscores**.
|
||||
|
||||
Por exemplo, um index chamado `my-private-registry` usa:
|
||||
|
||||
| Variável | Valor |
|
||||
|----------|-------|
|
||||
| `UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME` | Seu nome de usuário ou nome do token do registro |
|
||||
| `UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD` | Sua senha ou token/PAT do registro |
|
||||
|
||||
<Warning>
|
||||
Essas variáveis de ambiente **devem** ser adicionadas pelas configurações de **Variáveis de Ambiente** do CrewAI AMP —
|
||||
globalmente ou no nível da implantação. Elas não podem ser definidas em arquivos `.env` ou codificadas no seu projeto.
|
||||
|
||||
Veja [Configurar Variáveis de Ambiente no AMP](#configurar-variáveis-de-ambiente-no-amp) abaixo.
|
||||
</Warning>
|
||||
|
||||
## Referência de Provedores de Registro
|
||||
|
||||
A tabela abaixo mostra o formato da URL de index e os valores de credenciais para provedores de registro comuns.
|
||||
Substitua os valores de exemplo pelos detalhes reais da sua organização e feed.
|
||||
|
||||
| Provedor | URL do Index | Usuário | Senha |
|
||||
|----------|-------------|---------|-------|
|
||||
| **Azure DevOps Artifacts** | `https://pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/` | Qualquer string não vazia (ex: `token`) | Personal Access Token (PAT) com escopo Packaging Read |
|
||||
| **GitHub Packages** | `https://pypi.pkg.github.com/{owner}/simple/` | Nome de usuário do GitHub | Personal Access Token (classic) com escopo `read:packages` |
|
||||
| **GitLab Package Registry** | `https://gitlab.com/api/v4/projects/{project_id}/packages/pypi/simple/` | `__token__` | Project ou Personal Access Token com escopo `read_api` |
|
||||
| **AWS CodeArtifact** | Use a URL de `aws codeartifact get-repository-endpoint` | `aws` | Token de `aws codeartifact get-authorization-token` |
|
||||
| **Google Artifact Registry** | `https://{region}-python.pkg.dev/{project}/{repo}/simple/` | `_json_key_base64` | Chave de conta de serviço codificada em Base64 |
|
||||
| **JFrog Artifactory** | `https://{instance}.jfrog.io/artifactory/api/pypi/{repo}/simple/` | Nome de usuário ou email | Chave API ou token de identidade |
|
||||
| **Auto-hospedado (devpi, Nexus, etc.)** | URL da API simple do seu registro | Nome de usuário do registro | Senha do registro |
|
||||
|
||||
<Tip>
|
||||
Para **AWS CodeArtifact**, o token de autorização expira periodicamente.
|
||||
Você precisará atualizar o valor de `UV_INDEX_*_PASSWORD` quando ele expirar.
|
||||
Considere automatizar isso no seu pipeline de CI/CD.
|
||||
</Tip>
|
||||
|
||||
## Configurar Variáveis de Ambiente no AMP
|
||||
|
||||
As credenciais do registro privado devem ser configuradas como variáveis de ambiente no CrewAI AMP.
|
||||
Você tem duas opções:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Interface Web">
|
||||
1. Faça login no [CrewAI AMP](https://app.crewai.com)
|
||||
2. Navegue até sua automação
|
||||
3. Abra a aba **Environment Variables**
|
||||
4. Adicione cada variável (`UV_INDEX_*_USERNAME` e `UV_INDEX_*_PASSWORD`) com seu valor
|
||||
|
||||
Veja o passo [Deploy para AMP — Definir Variáveis de Ambiente](/pt-BR/enterprise/guides/deploy-to-amp#definir-as-variáveis-de-ambiente) para detalhes.
|
||||
</Tab>
|
||||
<Tab title="Implantação via CLI">
|
||||
Adicione as variáveis ao seu arquivo `.env` local antes de executar `crewai deploy create`.
|
||||
A CLI as transferirá com segurança para a plataforma:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
OPENAI_API_KEY=sk-...
|
||||
UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME=token
|
||||
UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD=your-pat-here
|
||||
```
|
||||
|
||||
```bash
|
||||
crewai deploy create
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
**Nunca** faça commit de credenciais no seu repositório. Use variáveis de ambiente do AMP para todos os segredos.
|
||||
O arquivo `.env` deve estar listado no `.gitignore`.
|
||||
</Warning>
|
||||
|
||||
Para atualizar credenciais em uma implantação existente, veja [Atualizar Seu Crew — Variáveis de Ambiente](/pt-BR/enterprise/guides/update-crew).
|
||||
|
||||
## Como Tudo se Conecta
|
||||
|
||||
Quando o CrewAI AMP faz o build da sua automação, o fluxo de resolução funciona assim:
|
||||
|
||||
<Steps>
|
||||
<Step title="Build inicia">
|
||||
O AMP busca seu repositório e lê o `pyproject.toml` e o `uv.lock`.
|
||||
</Step>
|
||||
<Step title="UV resolve dependências">
|
||||
O UV lê `[tool.uv.sources]` para determinar de qual index cada pacote deve vir.
|
||||
</Step>
|
||||
<Step title="UV autentica">
|
||||
Para cada index privado, o UV busca `UV_INDEX_{NAME}_USERNAME` e `UV_INDEX_{NAME}_PASSWORD`
|
||||
nas variáveis de ambiente que você configurou no AMP.
|
||||
</Step>
|
||||
<Step title="Pacotes são instalados">
|
||||
O UV baixa e instala todos os pacotes — tanto públicos (do PyPI) quanto privados (do seu registro).
|
||||
</Step>
|
||||
<Step title="Automação executa">
|
||||
Seu crew ou flow inicia com todas as dependências disponíveis.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### Erros de Autenticação Durante o Build
|
||||
|
||||
**Sintoma**: Build falha com `401 Unauthorized` ou `403 Forbidden` ao resolver um pacote privado.
|
||||
|
||||
**Verifique**:
|
||||
- Os nomes das variáveis de ambiente `UV_INDEX_*` correspondem exatamente ao nome do seu index (maiúsculas, hifens -> underscores)
|
||||
- As credenciais estão definidas nas variáveis de ambiente do AMP, não apenas em um `.env` local
|
||||
- Seu token/PAT tem as permissões de leitura necessárias para o feed de pacotes
|
||||
- O token não expirou (especialmente relevante para AWS CodeArtifact)
|
||||
|
||||
### Pacote Não Encontrado
|
||||
|
||||
**Sintoma**: `No matching distribution found for my-private-package`.
|
||||
|
||||
**Verifique**:
|
||||
- A URL do index no `pyproject.toml` termina com `/simple/`
|
||||
- A entrada `[tool.uv.sources]` mapeia o nome correto do pacote para o nome correto do index
|
||||
- O pacote está realmente publicado no seu registro privado
|
||||
- Execute `uv lock` localmente com as mesmas credenciais para verificar se a resolução funciona
|
||||
|
||||
### Conflitos no Arquivo Lock
|
||||
|
||||
**Sintoma**: `uv lock` falha ou produz resultados inesperados após adicionar um index privado.
|
||||
|
||||
**Solução**: Defina as credenciais localmente e regenere:
|
||||
|
||||
```bash
|
||||
export UV_INDEX_MY_PRIVATE_REGISTRY_USERNAME=token
|
||||
export UV_INDEX_MY_PRIVATE_REGISTRY_PASSWORD=your-pat
|
||||
uv lock
|
||||
```
|
||||
|
||||
Em seguida, faça commit do `uv.lock` atualizado.
|
||||
|
||||
## Guias Relacionados
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Preparar para Implantação" icon="clipboard-check" href="/pt-BR/enterprise/guides/prepare-for-deployment">
|
||||
Verifique a estrutura do projeto e as dependências antes de implantar.
|
||||
</Card>
|
||||
<Card title="Deploy para AMP" icon="rocket" href="/pt-BR/enterprise/guides/deploy-to-amp">
|
||||
Implante seu crew ou flow e configure variáveis de ambiente.
|
||||
</Card>
|
||||
<Card title="Atualizar Seu Crew" icon="arrows-rotate" href="/pt-BR/enterprise/guides/update-crew">
|
||||
Atualize variáveis de ambiente e envie alterações para uma implantação em execução.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -7,7 +7,7 @@ mode: "wide"
|
||||
|
||||
## Conecte o CrewAI a LLMs
|
||||
|
||||
O CrewAI utiliza o LiteLLM para conectar-se a uma grande variedade de Modelos de Linguagem (LLMs). Essa integração proporciona grande versatilidade, permitindo que você utilize modelos de inúmeros provedores por meio de uma interface simples e unificada.
|
||||
O CrewAI conecta-se a LLMs por meio de integrações nativas via SDK para os provedores mais populares (OpenAI, Anthropic, Google Gemini, Azure e AWS Bedrock), e usa o LiteLLM como alternativa flexível para todos os demais provedores.
|
||||
|
||||
<Note>
|
||||
Por padrão, o CrewAI usa o modelo `gpt-4o-mini`. Isso é determinado pela variável de ambiente `OPENAI_MODEL_NAME`, que tem como padrão "gpt-4o-mini" se não for definida.
|
||||
@@ -40,6 +40,14 @@ O LiteLLM oferece suporte a uma ampla gama de provedores, incluindo, mas não se
|
||||
|
||||
Para uma lista completa e sempre atualizada dos provedores suportados, consulte a [documentação de Provedores do LiteLLM](https://docs.litellm.ai/docs/providers).
|
||||
|
||||
<Info>
|
||||
Para usar qualquer provedor não coberto por uma integração nativa, adicione o LiteLLM como dependência ao seu projeto:
|
||||
```bash
|
||||
uv add 'crewai[litellm]'
|
||||
```
|
||||
Provedores nativos (OpenAI, Anthropic, Google Gemini, Azure, AWS Bedrock) usam seus próprios extras de SDK — consulte os [Exemplos de Configuração de Provedores](/pt-BR/concepts/llms#exemplos-de-configuração-de-provedores).
|
||||
</Info>
|
||||
|
||||
## Alterando a LLM
|
||||
|
||||
Para utilizar uma LLM diferente com seus agentes CrewAI, você tem várias opções:
|
||||
|
||||
@@ -152,4 +152,4 @@ __all__ = [
|
||||
"wrap_file_source",
|
||||
]
|
||||
|
||||
__version__ = "1.9.3"
|
||||
__version__ = "1.10.0"
|
||||
|
||||
@@ -8,12 +8,10 @@ authors = [
|
||||
]
|
||||
requires-python = ">=3.10, <3.14"
|
||||
dependencies = [
|
||||
"lancedb~=0.5.4",
|
||||
"pytube~=15.0.0",
|
||||
"requests~=2.32.5",
|
||||
"docker~=7.1.0",
|
||||
"crewai==1.9.3",
|
||||
"lancedb~=0.5.4",
|
||||
"crewai==1.10.0",
|
||||
"tiktoken~=0.8.0",
|
||||
"beautifulsoup4~=4.13.4",
|
||||
"python-docx~=1.2.0",
|
||||
|
||||
@@ -291,4 +291,4 @@ __all__ = [
|
||||
"ZapierActionTools",
|
||||
]
|
||||
|
||||
__version__ = "1.9.3"
|
||||
__version__ = "1.10.0"
|
||||
|
||||
@@ -20117,18 +20117,6 @@
|
||||
"humanized_name": "Web Automation Tool",
|
||||
"init_params_schema": {
|
||||
"$defs": {
|
||||
"AvailableModel": {
|
||||
"enum": [
|
||||
"gpt-4o",
|
||||
"gpt-4o-mini",
|
||||
"claude-3-5-sonnet-latest",
|
||||
"claude-3-7-sonnet-latest",
|
||||
"computer-use-preview",
|
||||
"gemini-2.0-flash"
|
||||
],
|
||||
"title": "AvailableModel",
|
||||
"type": "string"
|
||||
},
|
||||
"EnvVar": {
|
||||
"properties": {
|
||||
"default": {
|
||||
@@ -20206,17 +20194,6 @@
|
||||
"default": null,
|
||||
"title": "Model Api Key"
|
||||
},
|
||||
"model_name": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/AvailableModel"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "claude-3-7-sonnet-latest"
|
||||
},
|
||||
"project_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -38,10 +38,11 @@ dependencies = [
|
||||
"json5~=0.10.0",
|
||||
"portalocker~=2.7.0",
|
||||
"pydantic-settings~=2.10.1",
|
||||
"httpx~=0.28.1",
|
||||
"mcp~=1.26.0",
|
||||
"uv~=0.9.13",
|
||||
"aiosqlite~=0.21.0",
|
||||
"lancedb>=0.4.0",
|
||||
"lancedb>=0.29.2",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -52,7 +53,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = [
|
||||
"crewai-tools==1.9.3",
|
||||
"crewai-tools==1.10.0",
|
||||
]
|
||||
embeddings = [
|
||||
"tiktoken~=0.8.0"
|
||||
|
||||
@@ -41,7 +41,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
|
||||
|
||||
_suppress_pydantic_deprecation_warnings()
|
||||
|
||||
__version__ = "1.9.3"
|
||||
__version__ = "1.10.0"
|
||||
_telemetry_submitted = False
|
||||
|
||||
|
||||
|
||||
@@ -8,11 +8,9 @@ import time
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Final,
|
||||
Literal,
|
||||
cast,
|
||||
)
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
@@ -61,16 +59,8 @@ from crewai.knowledge.knowledge import Knowledge
|
||||
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||
from crewai.lite_agent_output import LiteAgentOutput
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.mcp import (
|
||||
MCPClient,
|
||||
MCPServerConfig,
|
||||
MCPServerHTTP,
|
||||
MCPServerSSE,
|
||||
MCPServerStdio,
|
||||
)
|
||||
from crewai.mcp.transports.http import HTTPTransport
|
||||
from crewai.mcp.transports.sse import SSETransport
|
||||
from crewai.mcp.transports.stdio import StdioTransport
|
||||
from crewai.mcp import MCPServerConfig
|
||||
from crewai.mcp.tool_resolver import MCPToolResolver
|
||||
from crewai.rag.embeddings.types import EmbedderConfig
|
||||
from crewai.security.fingerprint import Fingerprint
|
||||
from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||
@@ -111,18 +101,8 @@ if TYPE_CHECKING:
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
# MCP Connection timeout constants (in seconds)
|
||||
MCP_CONNECTION_TIMEOUT: Final[int] = 10
|
||||
MCP_TOOL_EXECUTION_TIMEOUT: Final[int] = 30
|
||||
MCP_DISCOVERY_TIMEOUT: Final[int] = 15
|
||||
MCP_MAX_RETRIES: Final[int] = 3
|
||||
|
||||
_passthrough_exceptions: tuple[type[Exception], ...] = ()
|
||||
|
||||
# Simple in-memory cache for MCP tool schemas (duration: 5 minutes)
|
||||
_mcp_schema_cache: dict[str, Any] = {}
|
||||
_cache_ttl: Final[int] = 300 # 5 minutes
|
||||
|
||||
|
||||
class Agent(BaseAgent):
|
||||
"""Represents an agent in a system.
|
||||
@@ -154,7 +134,7 @@ class Agent(BaseAgent):
|
||||
model_config = ConfigDict()
|
||||
|
||||
_times_executed: int = PrivateAttr(default=0)
|
||||
_mcp_clients: list[Any] = PrivateAttr(default_factory=list)
|
||||
_mcp_resolver: MCPToolResolver | None = PrivateAttr(default=None)
|
||||
_last_messages: list[LLMMessage] = PrivateAttr(default_factory=list)
|
||||
max_execution_time: int | None = Field(
|
||||
default=None,
|
||||
@@ -384,10 +364,10 @@ class Agent(BaseAgent):
|
||||
)
|
||||
if unified_memory is not None:
|
||||
query = task.description
|
||||
matches = unified_memory.recall(query, limit=10)
|
||||
matches = unified_memory.recall(query, limit=5)
|
||||
if matches:
|
||||
memory = "Relevant memories:\n" + "\n".join(
|
||||
f"- {m.record.content}" for m in matches
|
||||
m.format() for m in matches
|
||||
)
|
||||
if memory.strip() != "":
|
||||
task_prompt += self.i18n.slice("memory").format(memory=memory)
|
||||
@@ -622,10 +602,10 @@ class Agent(BaseAgent):
|
||||
)
|
||||
if unified_memory is not None:
|
||||
query = task.description
|
||||
matches = unified_memory.recall(query, limit=10)
|
||||
matches = unified_memory.recall(query, limit=5)
|
||||
if matches:
|
||||
memory = "Relevant memories:\n" + "\n".join(
|
||||
f"- {m.record.content}" for m in matches
|
||||
m.format() for m in matches
|
||||
)
|
||||
if memory.strip() != "":
|
||||
task_prompt += self.i18n.slice("memory").format(memory=memory)
|
||||
@@ -864,7 +844,11 @@ class Agent(BaseAgent):
|
||||
respect_context_window=self.respect_context_window,
|
||||
request_within_rpm_limit=rpm_limit_fn,
|
||||
callbacks=[TokenCalcHandler(self._token_process)],
|
||||
response_model=task.response_model if task else None,
|
||||
response_model=(
|
||||
task.response_model or task.output_pydantic or task.output_json
|
||||
)
|
||||
if task
|
||||
else None,
|
||||
)
|
||||
|
||||
def _update_executor_parameters(
|
||||
@@ -893,7 +877,11 @@ class Agent(BaseAgent):
|
||||
self.agent_executor.stop = stop_words
|
||||
self.agent_executor.tools_names = get_tool_names(tools)
|
||||
self.agent_executor.tools_description = render_text_description_and_args(tools)
|
||||
self.agent_executor.response_model = task.response_model if task else None
|
||||
self.agent_executor.response_model = (
|
||||
(task.response_model or task.output_pydantic or task.output_json)
|
||||
if task
|
||||
else None
|
||||
)
|
||||
|
||||
self.agent_executor.tools_handler = self.tools_handler
|
||||
self.agent_executor.request_within_rpm_limit = rpm_limit_fn
|
||||
@@ -926,544 +914,17 @@ class Agent(BaseAgent):
|
||||
def get_mcp_tools(self, mcps: list[str | MCPServerConfig]) -> list[BaseTool]:
|
||||
"""Convert MCP server references/configs to CrewAI tools.
|
||||
|
||||
Supports both string references (backwards compatible) and structured
|
||||
configuration objects (MCPServerStdio, MCPServerHTTP, MCPServerSSE).
|
||||
|
||||
Args:
|
||||
mcps: List of MCP server references (strings) or configurations.
|
||||
|
||||
Returns:
|
||||
List of BaseTool instances from MCP servers.
|
||||
Delegates to :class:`~crewai.mcp.tool_resolver.MCPToolResolver`.
|
||||
"""
|
||||
all_tools = []
|
||||
clients = []
|
||||
|
||||
for mcp_config in mcps:
|
||||
if isinstance(mcp_config, str):
|
||||
tools = self._get_mcp_tools_from_string(mcp_config)
|
||||
else:
|
||||
tools, client = self._get_native_mcp_tools(mcp_config)
|
||||
if client:
|
||||
clients.append(client)
|
||||
|
||||
all_tools.extend(tools)
|
||||
|
||||
# Store clients for cleanup
|
||||
self._mcp_clients.extend(clients)
|
||||
return all_tools
|
||||
self._cleanup_mcp_clients()
|
||||
self._mcp_resolver = MCPToolResolver(agent=self, logger=self._logger)
|
||||
return self._mcp_resolver.resolve(mcps)
|
||||
|
||||
def _cleanup_mcp_clients(self) -> None:
|
||||
"""Cleanup MCP client connections after task execution."""
|
||||
if not self._mcp_clients:
|
||||
return
|
||||
|
||||
async def _disconnect_all() -> None:
|
||||
for client in self._mcp_clients:
|
||||
if client and hasattr(client, "connected") and client.connected:
|
||||
await client.disconnect()
|
||||
|
||||
try:
|
||||
asyncio.run(_disconnect_all())
|
||||
except Exception as e:
|
||||
self._logger.log("error", f"Error during MCP client cleanup: {e}")
|
||||
finally:
|
||||
self._mcp_clients.clear()
|
||||
|
||||
def _get_mcp_tools_from_string(self, mcp_ref: str) -> list[BaseTool]:
|
||||
"""Get tools from legacy string-based MCP references.
|
||||
|
||||
This method maintains backwards compatibility with string-based
|
||||
MCP references (https://... and crewai-amp:...).
|
||||
|
||||
Args:
|
||||
mcp_ref: String reference to MCP server.
|
||||
|
||||
Returns:
|
||||
List of BaseTool instances.
|
||||
"""
|
||||
if mcp_ref.startswith("crewai-amp:"):
|
||||
return self._get_amp_mcp_tools(mcp_ref)
|
||||
if mcp_ref.startswith("https://"):
|
||||
return self._get_external_mcp_tools(mcp_ref)
|
||||
return []
|
||||
|
||||
def _get_external_mcp_tools(self, mcp_ref: str) -> list[BaseTool]:
|
||||
"""Get tools from external HTTPS MCP server with graceful error handling."""
|
||||
from crewai.tools.mcp_tool_wrapper import MCPToolWrapper
|
||||
|
||||
# Parse server URL and optional tool name
|
||||
if "#" in mcp_ref:
|
||||
server_url, specific_tool = mcp_ref.split("#", 1)
|
||||
else:
|
||||
server_url, specific_tool = mcp_ref, None
|
||||
|
||||
server_params = {"url": server_url}
|
||||
server_name = self._extract_server_name(server_url)
|
||||
|
||||
try:
|
||||
# Get tool schemas with timeout and error handling
|
||||
tool_schemas = self._get_mcp_tool_schemas(server_params)
|
||||
|
||||
if not tool_schemas:
|
||||
self._logger.log(
|
||||
"warning", f"No tools discovered from MCP server: {server_url}"
|
||||
)
|
||||
return []
|
||||
|
||||
tools = []
|
||||
for tool_name, schema in tool_schemas.items():
|
||||
# Skip if specific tool requested and this isn't it
|
||||
if specific_tool and tool_name != specific_tool:
|
||||
continue
|
||||
|
||||
try:
|
||||
wrapper = MCPToolWrapper(
|
||||
mcp_server_params=server_params,
|
||||
tool_name=tool_name,
|
||||
tool_schema=schema,
|
||||
server_name=server_name,
|
||||
)
|
||||
tools.append(wrapper)
|
||||
except Exception as e:
|
||||
self._logger.log(
|
||||
"warning",
|
||||
f"Failed to create MCP tool wrapper for {tool_name}: {e}",
|
||||
)
|
||||
continue
|
||||
|
||||
if specific_tool and not tools:
|
||||
self._logger.log(
|
||||
"warning",
|
||||
f"Specific tool '{specific_tool}' not found on MCP server: {server_url}",
|
||||
)
|
||||
|
||||
return cast(list[BaseTool], tools)
|
||||
|
||||
except Exception as e:
|
||||
self._logger.log(
|
||||
"warning", f"Failed to connect to MCP server {server_url}: {e}"
|
||||
)
|
||||
return []
|
||||
|
||||
def _get_native_mcp_tools(
|
||||
self, mcp_config: MCPServerConfig
|
||||
) -> tuple[list[BaseTool], Any | None]:
|
||||
"""Get tools from MCP server using structured configuration.
|
||||
|
||||
This method creates an MCP client based on the configuration type,
|
||||
connects to the server, discovers tools, applies filtering, and
|
||||
returns wrapped tools along with the client instance for cleanup.
|
||||
|
||||
Args:
|
||||
mcp_config: MCP server configuration (MCPServerStdio, MCPServerHTTP, or MCPServerSSE).
|
||||
|
||||
Returns:
|
||||
Tuple of (list of BaseTool instances, MCPClient instance for cleanup).
|
||||
"""
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.mcp_native_tool import MCPNativeTool
|
||||
|
||||
transport: StdioTransport | HTTPTransport | SSETransport
|
||||
if isinstance(mcp_config, MCPServerStdio):
|
||||
transport = StdioTransport(
|
||||
command=mcp_config.command,
|
||||
args=mcp_config.args,
|
||||
env=mcp_config.env,
|
||||
)
|
||||
server_name = f"{mcp_config.command}_{'_'.join(mcp_config.args)}"
|
||||
elif isinstance(mcp_config, MCPServerHTTP):
|
||||
transport = HTTPTransport(
|
||||
url=mcp_config.url,
|
||||
headers=mcp_config.headers,
|
||||
streamable=mcp_config.streamable,
|
||||
)
|
||||
server_name = self._extract_server_name(mcp_config.url)
|
||||
elif isinstance(mcp_config, MCPServerSSE):
|
||||
transport = SSETransport(
|
||||
url=mcp_config.url,
|
||||
headers=mcp_config.headers,
|
||||
)
|
||||
server_name = self._extract_server_name(mcp_config.url)
|
||||
else:
|
||||
raise ValueError(f"Unsupported MCP server config type: {type(mcp_config)}")
|
||||
|
||||
client = MCPClient(
|
||||
transport=transport,
|
||||
cache_tools_list=mcp_config.cache_tools_list,
|
||||
)
|
||||
|
||||
async def _setup_client_and_list_tools() -> list[dict[str, Any]]:
|
||||
"""Async helper to connect and list tools in same event loop."""
|
||||
|
||||
try:
|
||||
if not client.connected:
|
||||
await client.connect()
|
||||
|
||||
tools_list = await client.list_tools()
|
||||
|
||||
try:
|
||||
await client.disconnect()
|
||||
# Small delay to allow background tasks to finish cleanup
|
||||
# This helps prevent "cancel scope in different task" errors
|
||||
# when asyncio.run() closes the event loop
|
||||
await asyncio.sleep(0.1)
|
||||
except Exception as e:
|
||||
self._logger.log("error", f"Error during disconnect: {e}")
|
||||
|
||||
return tools_list
|
||||
except Exception as e:
|
||||
if client.connected:
|
||||
await client.disconnect()
|
||||
await asyncio.sleep(0.1)
|
||||
raise RuntimeError(
|
||||
f"Error during setup client and list tools: {e}"
|
||||
) from e
|
||||
|
||||
try:
|
||||
try:
|
||||
asyncio.get_running_loop()
|
||||
import concurrent.futures
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future = executor.submit(
|
||||
asyncio.run, _setup_client_and_list_tools()
|
||||
)
|
||||
tools_list = future.result()
|
||||
except RuntimeError:
|
||||
try:
|
||||
tools_list = asyncio.run(_setup_client_and_list_tools())
|
||||
except RuntimeError as e:
|
||||
error_msg = str(e).lower()
|
||||
if "cancel scope" in error_msg or "task" in error_msg:
|
||||
raise ConnectionError(
|
||||
"MCP connection failed due to event loop cleanup issues. "
|
||||
"This may be due to authentication errors or server unavailability."
|
||||
) from e
|
||||
except asyncio.CancelledError as e:
|
||||
raise ConnectionError(
|
||||
"MCP connection was cancelled. This may indicate an authentication "
|
||||
"error or server unavailability."
|
||||
) from e
|
||||
|
||||
if mcp_config.tool_filter:
|
||||
filtered_tools = []
|
||||
for tool in tools_list:
|
||||
if callable(mcp_config.tool_filter):
|
||||
try:
|
||||
from crewai.mcp.filters import ToolFilterContext
|
||||
|
||||
context = ToolFilterContext(
|
||||
agent=self,
|
||||
server_name=server_name,
|
||||
run_context=None,
|
||||
)
|
||||
if mcp_config.tool_filter(context, tool): # type: ignore[call-arg, arg-type]
|
||||
filtered_tools.append(tool)
|
||||
except (TypeError, AttributeError):
|
||||
if mcp_config.tool_filter(tool): # type: ignore[call-arg, arg-type]
|
||||
filtered_tools.append(tool)
|
||||
else:
|
||||
# Not callable - include tool
|
||||
filtered_tools.append(tool)
|
||||
tools_list = filtered_tools
|
||||
|
||||
tools = []
|
||||
for tool_def in tools_list:
|
||||
tool_name = tool_def.get("name", "")
|
||||
if not tool_name:
|
||||
continue
|
||||
|
||||
# Convert inputSchema to Pydantic model if present
|
||||
args_schema = None
|
||||
if tool_def.get("inputSchema"):
|
||||
args_schema = self._json_schema_to_pydantic(
|
||||
tool_name, tool_def["inputSchema"]
|
||||
)
|
||||
|
||||
tool_schema = {
|
||||
"description": tool_def.get("description", ""),
|
||||
"args_schema": args_schema,
|
||||
}
|
||||
|
||||
try:
|
||||
native_tool = MCPNativeTool(
|
||||
mcp_client=client,
|
||||
tool_name=tool_name,
|
||||
tool_schema=tool_schema,
|
||||
server_name=server_name,
|
||||
)
|
||||
tools.append(native_tool)
|
||||
except Exception as e:
|
||||
self._logger.log("error", f"Failed to create native MCP tool: {e}")
|
||||
continue
|
||||
|
||||
return cast(list[BaseTool], tools), client
|
||||
except Exception as e:
|
||||
if client.connected:
|
||||
asyncio.run(client.disconnect())
|
||||
|
||||
raise RuntimeError(f"Failed to get native MCP tools: {e}") from e
|
||||
|
||||
def _get_amp_mcp_tools(self, amp_ref: str) -> list[BaseTool]:
|
||||
"""Get tools from CrewAI AMP MCP marketplace."""
|
||||
# Parse: "crewai-amp:mcp-name" or "crewai-amp:mcp-name#tool_name"
|
||||
amp_part = amp_ref.replace("crewai-amp:", "")
|
||||
if "#" in amp_part:
|
||||
mcp_name, specific_tool = amp_part.split("#", 1)
|
||||
else:
|
||||
mcp_name, specific_tool = amp_part, None
|
||||
|
||||
# Call AMP API to get MCP server URLs
|
||||
mcp_servers = self._fetch_amp_mcp_servers(mcp_name)
|
||||
|
||||
tools = []
|
||||
for server_config in mcp_servers:
|
||||
server_ref = server_config["url"]
|
||||
if specific_tool:
|
||||
server_ref += f"#{specific_tool}"
|
||||
server_tools = self._get_external_mcp_tools(server_ref)
|
||||
tools.extend(server_tools)
|
||||
|
||||
return tools
|
||||
|
||||
@staticmethod
|
||||
def _extract_server_name(server_url: str) -> str:
|
||||
"""Extract clean server name from URL for tool prefixing."""
|
||||
|
||||
parsed = urlparse(server_url)
|
||||
domain = parsed.netloc.replace(".", "_")
|
||||
path = parsed.path.replace("/", "_").strip("_")
|
||||
return f"{domain}_{path}" if path else domain
|
||||
|
||||
def _get_mcp_tool_schemas(
|
||||
self, server_params: dict[str, Any]
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Get tool schemas from MCP server for wrapper creation with caching."""
|
||||
server_url = server_params["url"]
|
||||
|
||||
# Check cache first
|
||||
cache_key = server_url
|
||||
current_time = time.time()
|
||||
|
||||
if cache_key in _mcp_schema_cache:
|
||||
cached_data, cache_time = _mcp_schema_cache[cache_key]
|
||||
if current_time - cache_time < _cache_ttl:
|
||||
self._logger.log(
|
||||
"debug", f"Using cached MCP tool schemas for {server_url}"
|
||||
)
|
||||
return cached_data # type: ignore[no-any-return]
|
||||
|
||||
try:
|
||||
schemas = asyncio.run(self._get_mcp_tool_schemas_async(server_params))
|
||||
|
||||
# Cache successful results
|
||||
_mcp_schema_cache[cache_key] = (schemas, current_time)
|
||||
|
||||
return schemas
|
||||
except Exception as e:
|
||||
# Log warning but don't raise - this allows graceful degradation
|
||||
self._logger.log(
|
||||
"warning", f"Failed to get MCP tool schemas from {server_url}: {e}"
|
||||
)
|
||||
return {}
|
||||
|
||||
async def _get_mcp_tool_schemas_async(
|
||||
self, server_params: dict[str, Any]
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Async implementation of MCP tool schema retrieval with timeouts and retries."""
|
||||
server_url = server_params["url"]
|
||||
return await self._retry_mcp_discovery(
|
||||
self._discover_mcp_tools_with_timeout, server_url
|
||||
)
|
||||
|
||||
async def _retry_mcp_discovery(
|
||||
self, operation_func: Any, server_url: str
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Retry MCP discovery operation with exponential backoff, avoiding try-except in loop."""
|
||||
last_error = None
|
||||
|
||||
for attempt in range(MCP_MAX_RETRIES):
|
||||
# Execute single attempt outside try-except loop structure
|
||||
result, error, should_retry = await self._attempt_mcp_discovery(
|
||||
operation_func, server_url
|
||||
)
|
||||
|
||||
# Success case - return immediately
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
# Non-retryable error - raise immediately
|
||||
if not should_retry:
|
||||
raise RuntimeError(error)
|
||||
|
||||
# Retryable error - continue with backoff
|
||||
last_error = error
|
||||
if attempt < MCP_MAX_RETRIES - 1:
|
||||
wait_time = 2**attempt # Exponential backoff
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
raise RuntimeError(
|
||||
f"Failed to discover MCP tools after {MCP_MAX_RETRIES} attempts: {last_error}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def _attempt_mcp_discovery(
|
||||
operation_func: Any, server_url: str
|
||||
) -> tuple[dict[str, dict[str, Any]] | None, str, bool]:
|
||||
"""Attempt single MCP discovery operation and return (result, error_message, should_retry)."""
|
||||
try:
|
||||
result = await operation_func(server_url)
|
||||
return result, "", False
|
||||
|
||||
except ImportError:
|
||||
return (
|
||||
None,
|
||||
"MCP library not available. Please install with: pip install mcp",
|
||||
False,
|
||||
)
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
return (
|
||||
None,
|
||||
f"MCP discovery timed out after {MCP_DISCOVERY_TIMEOUT} seconds",
|
||||
True,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_str = str(e).lower()
|
||||
|
||||
# Classify errors as retryable or non-retryable
|
||||
if "authentication" in error_str or "unauthorized" in error_str:
|
||||
return None, f"Authentication failed for MCP server: {e!s}", False
|
||||
if "connection" in error_str or "network" in error_str:
|
||||
return None, f"Network connection failed: {e!s}", True
|
||||
if "json" in error_str or "parsing" in error_str:
|
||||
return None, f"Server response parsing error: {e!s}", True
|
||||
return None, f"MCP discovery error: {e!s}", False
|
||||
|
||||
async def _discover_mcp_tools_with_timeout(
|
||||
self, server_url: str
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Discover MCP tools with timeout wrapper."""
|
||||
return await asyncio.wait_for(
|
||||
self._discover_mcp_tools(server_url), timeout=MCP_DISCOVERY_TIMEOUT
|
||||
)
|
||||
|
||||
async def _discover_mcp_tools(self, server_url: str) -> dict[str, dict[str, Any]]:
|
||||
"""Discover tools from MCP server with proper timeout handling."""
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
|
||||
async with streamablehttp_client(server_url) as (read, write, _):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize the connection with timeout
|
||||
await asyncio.wait_for(
|
||||
session.initialize(), timeout=MCP_CONNECTION_TIMEOUT
|
||||
)
|
||||
|
||||
# List available tools with timeout
|
||||
tools_result = await asyncio.wait_for(
|
||||
session.list_tools(),
|
||||
timeout=MCP_DISCOVERY_TIMEOUT - MCP_CONNECTION_TIMEOUT,
|
||||
)
|
||||
|
||||
schemas = {}
|
||||
for tool in tools_result.tools:
|
||||
args_schema = None
|
||||
if hasattr(tool, "inputSchema") and tool.inputSchema:
|
||||
args_schema = self._json_schema_to_pydantic(
|
||||
sanitize_tool_name(tool.name), tool.inputSchema
|
||||
)
|
||||
|
||||
schemas[sanitize_tool_name(tool.name)] = {
|
||||
"description": getattr(tool, "description", ""),
|
||||
"args_schema": args_schema,
|
||||
}
|
||||
return schemas
|
||||
|
||||
def _json_schema_to_pydantic(
|
||||
self, tool_name: str, json_schema: dict[str, Any]
|
||||
) -> type:
|
||||
"""Convert JSON Schema to Pydantic model for tool arguments.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool (used for model naming)
|
||||
json_schema: JSON Schema dict with 'properties', 'required', etc.
|
||||
|
||||
Returns:
|
||||
Pydantic BaseModel class
|
||||
"""
|
||||
from pydantic import Field, create_model
|
||||
|
||||
properties = json_schema.get("properties", {})
|
||||
required_fields = json_schema.get("required", [])
|
||||
|
||||
field_definitions: dict[str, Any] = {}
|
||||
|
||||
for field_name, field_schema in properties.items():
|
||||
field_type = self._json_type_to_python(field_schema)
|
||||
field_description = field_schema.get("description", "")
|
||||
|
||||
is_required = field_name in required_fields
|
||||
|
||||
if is_required:
|
||||
field_definitions[field_name] = (
|
||||
field_type,
|
||||
Field(..., description=field_description),
|
||||
)
|
||||
else:
|
||||
field_definitions[field_name] = (
|
||||
field_type | None,
|
||||
Field(default=None, description=field_description),
|
||||
)
|
||||
|
||||
model_name = f"{tool_name.replace('-', '_').replace(' ', '_')}Schema"
|
||||
return create_model(model_name, **field_definitions) # type: ignore[no-any-return]
|
||||
|
||||
def _json_type_to_python(self, field_schema: dict[str, Any]) -> type:
|
||||
"""Convert JSON Schema type to Python type.
|
||||
|
||||
Args:
|
||||
field_schema: JSON Schema field definition
|
||||
|
||||
Returns:
|
||||
Python type
|
||||
"""
|
||||
|
||||
json_type = field_schema.get("type")
|
||||
|
||||
if "anyOf" in field_schema:
|
||||
types: list[type] = []
|
||||
for option in field_schema["anyOf"]:
|
||||
if "const" in option:
|
||||
types.append(str)
|
||||
else:
|
||||
types.append(self._json_type_to_python(option))
|
||||
unique_types = list(set(types))
|
||||
if len(unique_types) > 1:
|
||||
result: Any = unique_types[0]
|
||||
for t in unique_types[1:]:
|
||||
result = result | t
|
||||
return result # type: ignore[no-any-return]
|
||||
return unique_types[0]
|
||||
|
||||
type_mapping: dict[str | None, type] = {
|
||||
"string": str,
|
||||
"number": float,
|
||||
"integer": int,
|
||||
"boolean": bool,
|
||||
"array": list,
|
||||
"object": dict,
|
||||
}
|
||||
|
||||
return type_mapping.get(json_type, Any)
|
||||
|
||||
@staticmethod
|
||||
def _fetch_amp_mcp_servers(mcp_name: str) -> list[dict[str, Any]]:
|
||||
"""Fetch MCP server configurations from CrewAI AMP API."""
|
||||
# TODO: Implement AMP API call to "integrations/mcps" endpoint
|
||||
# Should return list of server configs with URLs
|
||||
return []
|
||||
if self._mcp_resolver is not None:
|
||||
self._mcp_resolver.cleanup()
|
||||
self._mcp_resolver = None
|
||||
|
||||
@staticmethod
|
||||
def get_multimodal_tools() -> Sequence[BaseTool]:
|
||||
@@ -1712,7 +1173,8 @@ class Agent(BaseAgent):
|
||||
|
||||
existing_names = {sanitize_tool_name(t.name) for t in raw_tools}
|
||||
raw_tools.extend(
|
||||
mt for mt in create_memory_tools(agent_memory)
|
||||
mt
|
||||
for mt in create_memory_tools(agent_memory)
|
||||
if sanitize_tool_name(mt.name) not in existing_names
|
||||
)
|
||||
|
||||
@@ -1802,11 +1264,11 @@ class Agent(BaseAgent):
|
||||
),
|
||||
)
|
||||
start_time = time.time()
|
||||
matches = agent_memory.recall(formatted_messages, limit=10)
|
||||
matches = agent_memory.recall(formatted_messages, limit=5)
|
||||
memory_block = ""
|
||||
if matches:
|
||||
memory_block = "Relevant memories:\n" + "\n".join(
|
||||
f"- {m.record.content}" for m in matches
|
||||
m.format() for m in matches
|
||||
)
|
||||
if memory_block:
|
||||
formatted_messages += "\n\n" + self.i18n.slice("memory").format(
|
||||
@@ -1937,14 +1399,15 @@ class Agent(BaseAgent):
|
||||
if isinstance(messages, str):
|
||||
input_str = messages
|
||||
else:
|
||||
input_str = "\n".join(
|
||||
str(msg.get("content", "")) for msg in messages if msg.get("content")
|
||||
) or "User request"
|
||||
raw = (
|
||||
f"Input: {input_str}\n"
|
||||
f"Agent: {self.role}\n"
|
||||
f"Result: {output_text}"
|
||||
)
|
||||
input_str = (
|
||||
"\n".join(
|
||||
str(msg.get("content", ""))
|
||||
for msg in messages
|
||||
if msg.get("content")
|
||||
)
|
||||
or "User request"
|
||||
)
|
||||
raw = f"Input: {input_str}\nAgent: {self.role}\nResult: {output_text}"
|
||||
extracted = agent_memory.extract_memories(raw)
|
||||
if extracted:
|
||||
agent_memory.remember_many(extracted)
|
||||
|
||||
@@ -4,7 +4,8 @@ from abc import ABC, abstractmethod
|
||||
from collections.abc import Callable
|
||||
from copy import copy as shallow_copy
|
||||
from hashlib import md5
|
||||
from typing import Any, Literal
|
||||
import re
|
||||
from typing import Any, Final, Literal
|
||||
import uuid
|
||||
|
||||
from pydantic import (
|
||||
@@ -36,6 +37,11 @@ from crewai.utilities.rpm_controller import RPMController
|
||||
from crewai.utilities.string_utils import interpolate_only
|
||||
|
||||
|
||||
_SLUG_RE: Final[re.Pattern[str]] = re.compile(
|
||||
r"^(?:crewai-amp:)?[a-zA-Z0-9][a-zA-Z0-9_-]*(?:#\w+)?$"
|
||||
)
|
||||
|
||||
|
||||
PlatformApp = Literal[
|
||||
"asana",
|
||||
"box",
|
||||
@@ -197,7 +203,7 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
|
||||
)
|
||||
mcps: list[str | MCPServerConfig] | None = Field(
|
||||
default=None,
|
||||
description="List of MCP server references. Supports 'https://server.com/path' for external servers and 'crewai-amp:mcp-name' for AMP marketplace. Use '#tool_name' suffix for specific tools.",
|
||||
description="List of MCP server references. Supports 'https://server.com/path' for external servers and bare slugs like 'notion' for connected MCP integrations. Use '#tool_name' suffix for specific tools.",
|
||||
)
|
||||
memory: Any = Field(
|
||||
default=None,
|
||||
@@ -276,14 +282,16 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
|
||||
validated_mcps: list[str | MCPServerConfig] = []
|
||||
for mcp in mcps:
|
||||
if isinstance(mcp, str):
|
||||
if mcp.startswith(("https://", "crewai-amp:")):
|
||||
if mcp.startswith("https://"):
|
||||
validated_mcps.append(mcp)
|
||||
elif _SLUG_RE.match(mcp):
|
||||
validated_mcps.append(mcp)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid MCP reference: {mcp}. "
|
||||
"String references must start with 'https://' or 'crewai-amp:'"
|
||||
f"Invalid MCP reference: {mcp!r}. "
|
||||
"String references must be an 'https://' URL or a valid "
|
||||
"slug (e.g. 'notion', 'notion#search', 'crewai-amp:notion')."
|
||||
)
|
||||
|
||||
elif isinstance(mcp, (MCPServerConfig)):
|
||||
validated_mcps.append(mcp)
|
||||
else:
|
||||
|
||||
@@ -30,7 +30,7 @@ class CrewAgentExecutorMixin:
|
||||
memory = getattr(self.agent, "memory", None) or (
|
||||
getattr(self.crew, "_memory", None) if self.crew else None
|
||||
)
|
||||
if memory is None or not self.task:
|
||||
if memory is None or not self.task or getattr(memory, "_read_only", False):
|
||||
return
|
||||
if (
|
||||
f"Action: {sanitize_tool_name('Delegate work to coworker')}"
|
||||
|
||||
@@ -6,8 +6,10 @@ and memory management.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
import inspect
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, Literal, cast
|
||||
|
||||
@@ -48,6 +50,7 @@ from crewai.utilities.agent_utils import (
|
||||
handle_unknown_error,
|
||||
has_reached_max_iterations,
|
||||
is_context_length_exceeded,
|
||||
parse_tool_call_args,
|
||||
process_llm_response,
|
||||
track_delegation_if_needed,
|
||||
)
|
||||
@@ -736,7 +739,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
] = []
|
||||
for call_id, func_name, func_args in parsed_calls:
|
||||
original_tool = original_tools_by_name.get(func_name)
|
||||
execution_plan.append((call_id, func_name, func_args, original_tool))
|
||||
execution_plan.append(
|
||||
(call_id, func_name, func_args, original_tool)
|
||||
)
|
||||
|
||||
self._append_assistant_tool_calls_message(
|
||||
[
|
||||
@@ -746,7 +751,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
)
|
||||
|
||||
max_workers = min(8, len(execution_plan))
|
||||
ordered_results: list[dict[str, Any] | None] = [None] * len(execution_plan)
|
||||
ordered_results: list[dict[str, Any] | None] = [None] * len(
|
||||
execution_plan
|
||||
)
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as pool:
|
||||
futures = {
|
||||
pool.submit(
|
||||
@@ -803,7 +810,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
return tool_finish
|
||||
|
||||
reasoning_prompt = self._i18n.slice("post_tool_reasoning")
|
||||
reasoning_message: LLMMessage = {
|
||||
reasoning_message = {
|
||||
"role": "user",
|
||||
"content": reasoning_prompt,
|
||||
}
|
||||
@@ -888,13 +895,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
ToolUsageStartedEvent,
|
||||
)
|
||||
|
||||
if isinstance(func_args, str):
|
||||
try:
|
||||
args_dict = json.loads(func_args)
|
||||
except json.JSONDecodeError:
|
||||
args_dict = {}
|
||||
else:
|
||||
args_dict = func_args
|
||||
args_dict, parse_error = parse_tool_call_args(func_args, func_name, call_id, original_tool)
|
||||
if parse_error is not None:
|
||||
return parse_error
|
||||
|
||||
if original_tool is None:
|
||||
for tool in self.original_tools or []:
|
||||
@@ -908,9 +911,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
elif (
|
||||
should_execute
|
||||
and original_tool
|
||||
and getattr(original_tool, "max_usage_count", None) is not None
|
||||
and getattr(original_tool, "current_usage_count", 0)
|
||||
>= original_tool.max_usage_count
|
||||
and (max_count := getattr(original_tool, "max_usage_count", None))
|
||||
is not None
|
||||
and getattr(original_tool, "current_usage_count", 0) >= max_count
|
||||
):
|
||||
max_usage_reached = True
|
||||
|
||||
@@ -989,13 +992,17 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
and hasattr(original_tool, "cache_function")
|
||||
and callable(original_tool.cache_function)
|
||||
):
|
||||
should_cache = original_tool.cache_function(args_dict, raw_result)
|
||||
should_cache = original_tool.cache_function(
|
||||
args_dict, raw_result
|
||||
)
|
||||
if should_cache:
|
||||
self.tools_handler.cache.add(
|
||||
tool=func_name, input=input_str, output=raw_result
|
||||
)
|
||||
|
||||
result = str(raw_result) if not isinstance(raw_result, str) else raw_result
|
||||
result = (
|
||||
str(raw_result) if not isinstance(raw_result, str) else raw_result
|
||||
)
|
||||
except Exception as e:
|
||||
result = f"Error executing tool: {e}"
|
||||
if self.task:
|
||||
@@ -1490,7 +1497,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
formatted_answer: Current agent response.
|
||||
"""
|
||||
if self.step_callback:
|
||||
self.step_callback(formatted_answer)
|
||||
cb_result = self.step_callback(formatted_answer)
|
||||
if inspect.iscoroutine(cb_result):
|
||||
asyncio.run(cb_result)
|
||||
|
||||
def _append_message(
|
||||
self, text: str, role: Literal["user", "assistant", "system"] = "assistant"
|
||||
|
||||
@@ -2,8 +2,8 @@ import time
|
||||
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||
import webbrowser
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel, Field
|
||||
import requests
|
||||
from rich.console import Console
|
||||
|
||||
from crewai.cli.authentication.utils import validate_jwt_token
|
||||
@@ -98,7 +98,7 @@ class AuthenticationCommand:
|
||||
"scope": " ".join(self.oauth2_provider.get_oauth_scopes()),
|
||||
"audience": self.oauth2_provider.get_audience(),
|
||||
}
|
||||
response = requests.post(
|
||||
response = httpx.post(
|
||||
url=self.oauth2_provider.get_authorize_url(),
|
||||
data=device_code_payload,
|
||||
timeout=20,
|
||||
@@ -130,7 +130,7 @@ class AuthenticationCommand:
|
||||
|
||||
attempts = 0
|
||||
while True and attempts < 10:
|
||||
response = requests.post(
|
||||
response = httpx.post(
|
||||
self.oauth2_provider.get_token_url(), data=token_payload, timeout=30
|
||||
)
|
||||
token_data = response.json()
|
||||
@@ -149,7 +149,7 @@ class AuthenticationCommand:
|
||||
return
|
||||
|
||||
if token_data["error"] not in ("authorization_pending", "slow_down"):
|
||||
raise requests.HTTPError(
|
||||
raise httpx.HTTPError(
|
||||
token_data.get("error_description") or token_data.get("error")
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import requests
|
||||
from requests.exceptions import JSONDecodeError
|
||||
import json
|
||||
|
||||
import httpx
|
||||
from rich.console import Console
|
||||
|
||||
from crewai.cli.authentication.token import get_auth_token
|
||||
@@ -30,16 +31,16 @@ class PlusAPIMixin:
|
||||
console.print("Run 'crewai login' to sign up/login.", style="bold green")
|
||||
raise SystemExit from None
|
||||
|
||||
def _validate_response(self, response: requests.Response) -> None:
|
||||
def _validate_response(self, response: httpx.Response) -> None:
|
||||
"""
|
||||
Handle and display error messages from API responses.
|
||||
|
||||
Args:
|
||||
response (requests.Response): The response from the Plus API
|
||||
response (httpx.Response): The response from the Plus API
|
||||
"""
|
||||
try:
|
||||
json_response = response.json()
|
||||
except (JSONDecodeError, ValueError):
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
console.print(
|
||||
"Failed to parse response from Enterprise API failed. Details:",
|
||||
style="bold red",
|
||||
@@ -62,7 +63,7 @@ class PlusAPIMixin:
|
||||
)
|
||||
raise SystemExit
|
||||
|
||||
if not response.ok:
|
||||
if not response.is_success:
|
||||
console.print(
|
||||
"Request to Enterprise API failed. Details:", style="bold red"
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ ENV_VARS: dict[str, list[dict[str, Any]]] = {
|
||||
},
|
||||
{
|
||||
"prompt": "Enter your AWS Region Name (press Enter to skip)",
|
||||
"key_name": "AWS_REGION_NAME",
|
||||
"key_name": "AWS_DEFAULT_REGION",
|
||||
},
|
||||
],
|
||||
"azure": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
from typing import Any, cast
|
||||
|
||||
import requests
|
||||
from requests.exceptions import JSONDecodeError, RequestException
|
||||
import httpx
|
||||
from rich.console import Console
|
||||
|
||||
from crewai.cli.authentication.main import Oauth2Settings, ProviderFactory
|
||||
@@ -47,12 +47,12 @@ class EnterpriseConfigureCommand(BaseCommand):
|
||||
"User-Agent": f"CrewAI-CLI/{get_crewai_version()}",
|
||||
"X-Crewai-Version": get_crewai_version(),
|
||||
}
|
||||
response = requests.get(oauth_endpoint, timeout=30, headers=headers)
|
||||
response = httpx.get(oauth_endpoint, timeout=30, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
try:
|
||||
oauth_config = response.json()
|
||||
except JSONDecodeError as e:
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON response from {oauth_endpoint}") from e
|
||||
|
||||
self._validate_oauth_config(oauth_config)
|
||||
@@ -62,7 +62,7 @@ class EnterpriseConfigureCommand(BaseCommand):
|
||||
)
|
||||
return cast(dict[str, Any], oauth_config)
|
||||
|
||||
except RequestException as e:
|
||||
except httpx.HTTPError as e:
|
||||
raise ValueError(f"Failed to connect to enterprise URL: {e!s}") from e
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error fetching OAuth2 configuration: {e!s}") from e
|
||||
|
||||
@@ -290,13 +290,20 @@ class MemoryTUI(App[None]):
|
||||
if self._memory is None:
|
||||
panel.update(self._init_error or "No memory loaded.")
|
||||
return
|
||||
display_limit = 1000
|
||||
info = self._memory.info(path)
|
||||
self._last_scope_info = info
|
||||
self._entries = self._memory.list_records(scope=path, limit=200)
|
||||
self._entries = self._memory.list_records(scope=path, limit=display_limit)
|
||||
panel.update(_format_scope_info(info))
|
||||
panel.border_title = "Detail"
|
||||
entry_list = self.query_one("#entry-list", OptionList)
|
||||
entry_list.border_title = f"Entries ({len(self._entries)})"
|
||||
capped = info.record_count > display_limit
|
||||
count_label = (
|
||||
f"Entries (showing {display_limit} of {info.record_count} — display limit)"
|
||||
if capped
|
||||
else f"Entries ({len(self._entries)})"
|
||||
)
|
||||
entry_list.border_title = count_label
|
||||
self._populate_entry_list()
|
||||
|
||||
def on_option_list_option_highlighted(
|
||||
@@ -376,6 +383,11 @@ class MemoryTUI(App[None]):
|
||||
return
|
||||
|
||||
info_lines: list[str] = []
|
||||
info_lines.append(
|
||||
"[dim italic]Searched the full dataset"
|
||||
+ (f" within [bold]{scope}[/]" if scope else "")
|
||||
+ " using the recall flow (semantic + recency + importance).[/]\n"
|
||||
)
|
||||
if not self._custom_embedder:
|
||||
info_lines.append(
|
||||
"[dim italic]Note: Using default OpenAI embedder. "
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from requests import HTTPError
|
||||
from httpx import HTTPStatusError
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
@@ -10,11 +10,11 @@ console = Console()
|
||||
|
||||
|
||||
class OrganizationCommand(BaseCommand, PlusAPIMixin):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
BaseCommand.__init__(self)
|
||||
PlusAPIMixin.__init__(self, telemetry=self._telemetry)
|
||||
|
||||
def list(self):
|
||||
def list(self) -> None:
|
||||
try:
|
||||
response = self.plus_api_client.get_organizations()
|
||||
response.raise_for_status()
|
||||
@@ -33,7 +33,7 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
|
||||
table.add_row(org["name"], org["uuid"])
|
||||
|
||||
console.print(table)
|
||||
except HTTPError as e:
|
||||
except HTTPStatusError as e:
|
||||
if e.response.status_code == 401:
|
||||
console.print(
|
||||
"You are not logged in to any organization. Use 'crewai login' to login.",
|
||||
@@ -50,7 +50,7 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
|
||||
)
|
||||
raise SystemExit(1) from e
|
||||
|
||||
def switch(self, org_id):
|
||||
def switch(self, org_id: str) -> None:
|
||||
try:
|
||||
response = self.plus_api_client.get_organizations()
|
||||
response.raise_for_status()
|
||||
@@ -72,7 +72,7 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
|
||||
f"Successfully switched to {org['name']} ({org['uuid']})",
|
||||
style="bold green",
|
||||
)
|
||||
except HTTPError as e:
|
||||
except HTTPStatusError as e:
|
||||
if e.response.status_code == 401:
|
||||
console.print(
|
||||
"You are not logged in to any organization. Use 'crewai login' to login.",
|
||||
@@ -87,7 +87,7 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
|
||||
console.print(f"Failed to switch organization: {e!s}", style="bold red")
|
||||
raise SystemExit(1) from e
|
||||
|
||||
def current(self):
|
||||
def current(self) -> None:
|
||||
settings = Settings()
|
||||
if settings.org_uuid:
|
||||
console.print(
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import Any
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import httpx
|
||||
import requests
|
||||
|
||||
from crewai.cli.config import Settings
|
||||
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
|
||||
@@ -43,16 +42,16 @@ class PlusAPI:
|
||||
|
||||
def _make_request(
|
||||
self, method: str, endpoint: str, **kwargs: Any
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
url = urljoin(self.base_url, endpoint)
|
||||
session = requests.Session()
|
||||
session.trust_env = False
|
||||
return session.request(method, url, headers=self.headers, **kwargs)
|
||||
verify = kwargs.pop("verify", True)
|
||||
with httpx.Client(trust_env=False, verify=verify) as client:
|
||||
return client.request(method, url, headers=self.headers, **kwargs)
|
||||
|
||||
def login_to_tool_repository(self) -> requests.Response:
|
||||
def login_to_tool_repository(self) -> httpx.Response:
|
||||
return self._make_request("POST", f"{self.TOOLS_RESOURCE}/login")
|
||||
|
||||
def get_tool(self, handle: str) -> requests.Response:
|
||||
def get_tool(self, handle: str) -> httpx.Response:
|
||||
return self._make_request("GET", f"{self.TOOLS_RESOURCE}/{handle}")
|
||||
|
||||
async def get_agent(self, handle: str) -> httpx.Response:
|
||||
@@ -68,7 +67,7 @@ class PlusAPI:
|
||||
description: str | None,
|
||||
encoded_file: str,
|
||||
available_exports: list[dict[str, Any]] | None = None,
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
params = {
|
||||
"handle": handle,
|
||||
"public": is_public,
|
||||
@@ -79,54 +78,52 @@ class PlusAPI:
|
||||
}
|
||||
return self._make_request("POST", f"{self.TOOLS_RESOURCE}", json=params)
|
||||
|
||||
def deploy_by_name(self, project_name: str) -> requests.Response:
|
||||
def deploy_by_name(self, project_name: str) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"POST", f"{self.CREWS_RESOURCE}/by-name/{project_name}/deploy"
|
||||
)
|
||||
|
||||
def deploy_by_uuid(self, uuid: str) -> requests.Response:
|
||||
def deploy_by_uuid(self, uuid: str) -> httpx.Response:
|
||||
return self._make_request("POST", f"{self.CREWS_RESOURCE}/{uuid}/deploy")
|
||||
|
||||
def crew_status_by_name(self, project_name: str) -> requests.Response:
|
||||
def crew_status_by_name(self, project_name: str) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"GET", f"{self.CREWS_RESOURCE}/by-name/{project_name}/status"
|
||||
)
|
||||
|
||||
def crew_status_by_uuid(self, uuid: str) -> requests.Response:
|
||||
def crew_status_by_uuid(self, uuid: str) -> httpx.Response:
|
||||
return self._make_request("GET", f"{self.CREWS_RESOURCE}/{uuid}/status")
|
||||
|
||||
def crew_by_name(
|
||||
self, project_name: str, log_type: str = "deployment"
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"GET", f"{self.CREWS_RESOURCE}/by-name/{project_name}/logs/{log_type}"
|
||||
)
|
||||
|
||||
def crew_by_uuid(
|
||||
self, uuid: str, log_type: str = "deployment"
|
||||
) -> requests.Response:
|
||||
def crew_by_uuid(self, uuid: str, log_type: str = "deployment") -> httpx.Response:
|
||||
return self._make_request(
|
||||
"GET", f"{self.CREWS_RESOURCE}/{uuid}/logs/{log_type}"
|
||||
)
|
||||
|
||||
def delete_crew_by_name(self, project_name: str) -> requests.Response:
|
||||
def delete_crew_by_name(self, project_name: str) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"DELETE", f"{self.CREWS_RESOURCE}/by-name/{project_name}"
|
||||
)
|
||||
|
||||
def delete_crew_by_uuid(self, uuid: str) -> requests.Response:
|
||||
def delete_crew_by_uuid(self, uuid: str) -> httpx.Response:
|
||||
return self._make_request("DELETE", f"{self.CREWS_RESOURCE}/{uuid}")
|
||||
|
||||
def list_crews(self) -> requests.Response:
|
||||
def list_crews(self) -> httpx.Response:
|
||||
return self._make_request("GET", self.CREWS_RESOURCE)
|
||||
|
||||
def create_crew(self, payload: dict[str, Any]) -> requests.Response:
|
||||
def create_crew(self, payload: dict[str, Any]) -> httpx.Response:
|
||||
return self._make_request("POST", self.CREWS_RESOURCE, json=payload)
|
||||
|
||||
def get_organizations(self) -> requests.Response:
|
||||
def get_organizations(self) -> httpx.Response:
|
||||
return self._make_request("GET", self.ORGANIZATIONS_RESOURCE)
|
||||
|
||||
def initialize_trace_batch(self, payload: dict[str, Any]) -> requests.Response:
|
||||
def initialize_trace_batch(self, payload: dict[str, Any]) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"POST",
|
||||
f"{self.TRACING_RESOURCE}/batches",
|
||||
@@ -136,7 +133,7 @@ class PlusAPI:
|
||||
|
||||
def initialize_ephemeral_trace_batch(
|
||||
self, payload: dict[str, Any]
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"POST",
|
||||
f"{self.EPHEMERAL_TRACING_RESOURCE}/batches",
|
||||
@@ -145,7 +142,7 @@ class PlusAPI:
|
||||
|
||||
def send_trace_events(
|
||||
self, trace_batch_id: str, payload: dict[str, Any]
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"POST",
|
||||
f"{self.TRACING_RESOURCE}/batches/{trace_batch_id}/events",
|
||||
@@ -155,7 +152,7 @@ class PlusAPI:
|
||||
|
||||
def send_ephemeral_trace_events(
|
||||
self, trace_batch_id: str, payload: dict[str, Any]
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"POST",
|
||||
f"{self.EPHEMERAL_TRACING_RESOURCE}/batches/{trace_batch_id}/events",
|
||||
@@ -165,7 +162,7 @@ class PlusAPI:
|
||||
|
||||
def finalize_trace_batch(
|
||||
self, trace_batch_id: str, payload: dict[str, Any]
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"PATCH",
|
||||
f"{self.TRACING_RESOURCE}/batches/{trace_batch_id}/finalize",
|
||||
@@ -175,7 +172,7 @@ class PlusAPI:
|
||||
|
||||
def finalize_ephemeral_trace_batch(
|
||||
self, trace_batch_id: str, payload: dict[str, Any]
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"PATCH",
|
||||
f"{self.EPHEMERAL_TRACING_RESOURCE}/batches/{trace_batch_id}/finalize",
|
||||
@@ -185,7 +182,7 @@ class PlusAPI:
|
||||
|
||||
def mark_trace_batch_as_failed(
|
||||
self, trace_batch_id: str, error_message: str
|
||||
) -> requests.Response:
|
||||
) -> httpx.Response:
|
||||
return self._make_request(
|
||||
"PATCH",
|
||||
f"{self.TRACING_RESOURCE}/batches/{trace_batch_id}",
|
||||
@@ -193,13 +190,20 @@ class PlusAPI:
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
def get_triggers(self) -> requests.Response:
|
||||
def get_mcp_configs(self, slugs: list[str]) -> httpx.Response:
|
||||
"""Get MCP server configurations for the given slugs."""
|
||||
return self._make_request(
|
||||
"GET",
|
||||
f"{self.INTEGRATIONS_RESOURCE}/mcp_configs",
|
||||
params={"slugs": ",".join(slugs)},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
def get_triggers(self) -> httpx.Response:
|
||||
"""Get all available triggers from integrations."""
|
||||
return self._make_request("GET", f"{self.INTEGRATIONS_RESOURCE}/apps")
|
||||
|
||||
def get_trigger_payload(
|
||||
self, app_slug: str, trigger_slug: str
|
||||
) -> requests.Response:
|
||||
def get_trigger_payload(self, app_slug: str, trigger_slug: str) -> httpx.Response:
|
||||
"""Get sample payload for a specific trigger."""
|
||||
return self._make_request(
|
||||
"GET", f"{self.INTEGRATIONS_RESOURCE}/{app_slug}/{trigger_slug}/payload"
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Any
|
||||
|
||||
import certifi
|
||||
import click
|
||||
import requests
|
||||
import httpx
|
||||
|
||||
from crewai.cli.constants import JSON_URL, MODELS, PROVIDERS
|
||||
|
||||
@@ -165,20 +165,20 @@ def fetch_provider_data(cache_file: Path) -> dict[str, Any] | None:
|
||||
ssl_config = os.environ["SSL_CERT_FILE"] = certifi.where()
|
||||
|
||||
try:
|
||||
response = requests.get(JSON_URL, stream=True, timeout=60, verify=ssl_config)
|
||||
response.raise_for_status()
|
||||
data = download_data(response)
|
||||
with open(cache_file, "w") as f:
|
||||
json.dump(data, f)
|
||||
return data
|
||||
except requests.RequestException as e:
|
||||
with httpx.stream("GET", JSON_URL, timeout=60, verify=ssl_config) as response:
|
||||
response.raise_for_status()
|
||||
data = download_data(response)
|
||||
with open(cache_file, "w") as f:
|
||||
json.dump(data, f)
|
||||
return data
|
||||
except httpx.HTTPError as e:
|
||||
click.secho(f"Error fetching provider data: {e}", fg="red")
|
||||
except json.JSONDecodeError:
|
||||
click.secho("Error parsing provider data. Invalid JSON format.", fg="red")
|
||||
return None
|
||||
|
||||
|
||||
def download_data(response: requests.Response) -> dict[str, Any]:
|
||||
def download_data(response: httpx.Response) -> dict[str, Any]:
|
||||
"""Downloads data from a given HTTP response and returns the JSON content.
|
||||
|
||||
Args:
|
||||
@@ -194,7 +194,7 @@ def download_data(response: requests.Response) -> dict[str, Any]:
|
||||
with click.progressbar(
|
||||
length=total_size, label="Downloading", show_pos=True
|
||||
) as bar:
|
||||
for chunk in response.iter_content(block_size):
|
||||
for chunk in response.iter_bytes(block_size):
|
||||
if chunk:
|
||||
data_chunks.append(chunk)
|
||||
bar.update(len(chunk))
|
||||
|
||||
@@ -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.9.3"
|
||||
"crewai[tools]==1.10.0"
|
||||
]
|
||||
|
||||
[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.9.3"
|
||||
"crewai[tools]==1.10.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.203.1"
|
||||
"crewai[tools]==1.10.0"
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
|
||||
@@ -63,6 +63,7 @@ from crewai.events.types.logging_events import (
|
||||
AgentLogsStartedEvent,
|
||||
)
|
||||
from crewai.events.types.mcp_events import (
|
||||
MCPConfigFetchFailedEvent,
|
||||
MCPConnectionCompletedEvent,
|
||||
MCPConnectionFailedEvent,
|
||||
MCPConnectionStartedEvent,
|
||||
@@ -165,6 +166,7 @@ __all__ = [
|
||||
"LiteAgentExecutionCompletedEvent",
|
||||
"LiteAgentExecutionErrorEvent",
|
||||
"LiteAgentExecutionStartedEvent",
|
||||
"MCPConfigFetchFailedEvent",
|
||||
"MCPConnectionCompletedEvent",
|
||||
"MCPConnectionFailedEvent",
|
||||
"MCPConnectionStartedEvent",
|
||||
|
||||
@@ -68,6 +68,7 @@ from crewai.events.types.logging_events import (
|
||||
AgentLogsStartedEvent,
|
||||
)
|
||||
from crewai.events.types.mcp_events import (
|
||||
MCPConfigFetchFailedEvent,
|
||||
MCPConnectionCompletedEvent,
|
||||
MCPConnectionFailedEvent,
|
||||
MCPConnectionStartedEvent,
|
||||
@@ -665,6 +666,16 @@ class EventListener(BaseEventListener):
|
||||
event.error_type,
|
||||
)
|
||||
|
||||
@crewai_event_bus.on(MCPConfigFetchFailedEvent)
|
||||
def on_mcp_config_fetch_failed(
|
||||
_: Any, event: MCPConfigFetchFailedEvent
|
||||
) -> None:
|
||||
self.formatter.handle_mcp_config_fetch_failed(
|
||||
event.slug,
|
||||
event.error,
|
||||
event.error_type,
|
||||
)
|
||||
|
||||
@crewai_event_bus.on(MCPToolExecutionStartedEvent)
|
||||
def on_mcp_tool_execution_started(
|
||||
_: Any, event: MCPToolExecutionStartedEvent
|
||||
|
||||
@@ -67,6 +67,7 @@ from crewai.events.types.llm_guardrail_events import (
|
||||
LLMGuardrailStartedEvent,
|
||||
)
|
||||
from crewai.events.types.mcp_events import (
|
||||
MCPConfigFetchFailedEvent,
|
||||
MCPConnectionCompletedEvent,
|
||||
MCPConnectionFailedEvent,
|
||||
MCPConnectionStartedEvent,
|
||||
@@ -181,4 +182,5 @@ EventTypes = (
|
||||
| MCPToolExecutionStartedEvent
|
||||
| MCPToolExecutionCompletedEvent
|
||||
| MCPToolExecutionFailedEvent
|
||||
| MCPConfigFetchFailedEvent
|
||||
)
|
||||
|
||||
@@ -83,3 +83,16 @@ class MCPToolExecutionFailedEvent(MCPEvent):
|
||||
error_type: str | None = None # "timeout", "validation", "server_error", etc.
|
||||
started_at: datetime | None = None
|
||||
failed_at: datetime | None = None
|
||||
|
||||
|
||||
class MCPConfigFetchFailedEvent(BaseEvent):
|
||||
"""Event emitted when fetching an AMP MCP server config fails.
|
||||
|
||||
This covers cases where the slug is not connected, the API call
|
||||
failed, or native MCP resolution failed after config was fetched.
|
||||
"""
|
||||
|
||||
type: str = "mcp_config_fetch_failed"
|
||||
slug: str
|
||||
error: str
|
||||
error_type: str | None = None # "not_connected", "api_error", "connection_failed"
|
||||
|
||||
@@ -1512,6 +1512,34 @@ To enable tracing, do any one of these:
|
||||
self.print(panel)
|
||||
self.print()
|
||||
|
||||
def handle_mcp_config_fetch_failed(
|
||||
self,
|
||||
slug: str,
|
||||
error: str = "",
|
||||
error_type: str | None = None,
|
||||
) -> None:
|
||||
"""Handle MCP config fetch failed event (AMP resolution failures)."""
|
||||
if not self.verbose:
|
||||
return
|
||||
|
||||
content = Text()
|
||||
content.append("MCP Config Fetch Failed\n\n", style="red bold")
|
||||
content.append("Server: ", style="white")
|
||||
content.append(f"{slug}\n", style="red")
|
||||
|
||||
if error_type:
|
||||
content.append("Error Type: ", style="white")
|
||||
content.append(f"{error_type}\n", style="red")
|
||||
|
||||
if error:
|
||||
content.append("\nError: ", style="white bold")
|
||||
error_preview = error[:500] + "..." if len(error) > 500 else error
|
||||
content.append(f"{error_preview}\n", style="red")
|
||||
|
||||
panel = self.create_panel(content, "❌ MCP Config Failed", "red")
|
||||
self.print(panel)
|
||||
self.print()
|
||||
|
||||
def handle_mcp_tool_execution_started(
|
||||
self,
|
||||
server_name: str,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable, Coroutine
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from datetime import datetime
|
||||
import inspect
|
||||
import json
|
||||
import threading
|
||||
from typing import TYPE_CHECKING, Any, Literal, cast
|
||||
@@ -64,6 +66,7 @@ from crewai.utilities.agent_utils import (
|
||||
has_reached_max_iterations,
|
||||
is_context_length_exceeded,
|
||||
is_inside_event_loop,
|
||||
parse_tool_call_args,
|
||||
process_llm_response,
|
||||
track_delegation_if_needed,
|
||||
)
|
||||
@@ -778,7 +781,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
|
||||
from_cache = cast(bool, execution_result["from_cache"])
|
||||
original_tool = execution_result["original_tool"]
|
||||
|
||||
tool_message: LLMMessage = {
|
||||
tool_message = {
|
||||
"role": "tool",
|
||||
"tool_call_id": call_id,
|
||||
"name": func_name,
|
||||
@@ -846,13 +849,9 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
|
||||
call_id, func_name, func_args = info
|
||||
|
||||
# Parse arguments
|
||||
if isinstance(func_args, str):
|
||||
try:
|
||||
args_dict = json.loads(func_args)
|
||||
except json.JSONDecodeError:
|
||||
args_dict = {}
|
||||
else:
|
||||
args_dict = func_args
|
||||
args_dict, parse_error = parse_tool_call_args(func_args, func_name, call_id)
|
||||
if parse_error is not None:
|
||||
return parse_error
|
||||
|
||||
# Get agent_key for event tracking
|
||||
agent_key = getattr(self.agent, "key", "unknown") if self.agent else "unknown"
|
||||
@@ -1358,7 +1357,9 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
|
||||
formatted_answer: Current agent response.
|
||||
"""
|
||||
if self.step_callback:
|
||||
self.step_callback(formatted_answer)
|
||||
cb_result = self.step_callback(formatted_answer)
|
||||
if inspect.iscoroutine(cb_result):
|
||||
asyncio.run(cb_result)
|
||||
|
||||
def _append_message_to_state(
|
||||
self, text: str, role: Literal["user", "assistant", "system"] = "assistant"
|
||||
|
||||
@@ -2,10 +2,10 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
import time
|
||||
from functools import wraps
|
||||
import inspect
|
||||
import json
|
||||
import time
|
||||
from types import MethodType
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
@@ -49,15 +49,20 @@ from crewai.events.types.agent_events import (
|
||||
LiteAgentExecutionErrorEvent,
|
||||
LiteAgentExecutionStartedEvent,
|
||||
)
|
||||
from crewai.events.types.logging_events import AgentLogsExecutionEvent
|
||||
from crewai.events.types.memory_events import (
|
||||
MemoryRetrievalCompletedEvent,
|
||||
MemoryRetrievalFailedEvent,
|
||||
MemoryRetrievalStartedEvent,
|
||||
)
|
||||
from crewai.events.types.logging_events import AgentLogsExecutionEvent
|
||||
from crewai.flow.flow_trackable import FlowTrackable
|
||||
from crewai.hooks.llm_hooks import get_after_llm_call_hooks, get_before_llm_call_hooks
|
||||
from crewai.hooks.types import AfterLLMCallHookType, BeforeLLMCallHookType
|
||||
from crewai.hooks.types import (
|
||||
AfterLLMCallHookCallable,
|
||||
AfterLLMCallHookType,
|
||||
BeforeLLMCallHookCallable,
|
||||
BeforeLLMCallHookType,
|
||||
)
|
||||
from crewai.lite_agent_output import LiteAgentOutput
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
@@ -270,11 +275,11 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
_guardrail: GuardrailCallable | None = PrivateAttr(default=None)
|
||||
_guardrail_retry_count: int = PrivateAttr(default=0)
|
||||
_callbacks: list[TokenCalcHandler] = PrivateAttr(default_factory=list)
|
||||
_before_llm_call_hooks: list[BeforeLLMCallHookType] = PrivateAttr(
|
||||
default_factory=get_before_llm_call_hooks
|
||||
_before_llm_call_hooks: list[BeforeLLMCallHookType | BeforeLLMCallHookCallable] = (
|
||||
PrivateAttr(default_factory=get_before_llm_call_hooks)
|
||||
)
|
||||
_after_llm_call_hooks: list[AfterLLMCallHookType] = PrivateAttr(
|
||||
default_factory=get_after_llm_call_hooks
|
||||
_after_llm_call_hooks: list[AfterLLMCallHookType | AfterLLMCallHookCallable] = (
|
||||
PrivateAttr(default_factory=get_after_llm_call_hooks)
|
||||
)
|
||||
_memory: Any = PrivateAttr(default=None)
|
||||
|
||||
@@ -440,12 +445,16 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
return self.role
|
||||
|
||||
@property
|
||||
def before_llm_call_hooks(self) -> list[BeforeLLMCallHookType]:
|
||||
def before_llm_call_hooks(
|
||||
self,
|
||||
) -> list[BeforeLLMCallHookType | BeforeLLMCallHookCallable]:
|
||||
"""Get the before_llm_call hooks for this agent."""
|
||||
return self._before_llm_call_hooks
|
||||
|
||||
@property
|
||||
def after_llm_call_hooks(self) -> list[AfterLLMCallHookType]:
|
||||
def after_llm_call_hooks(
|
||||
self,
|
||||
) -> list[AfterLLMCallHookType | AfterLLMCallHookCallable]:
|
||||
"""Get the after_llm_call hooks for this agent."""
|
||||
return self._after_llm_call_hooks
|
||||
|
||||
@@ -482,11 +491,12 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
# Inject memory tools once if memory is configured (mirrors Agent._prepare_kickoff)
|
||||
if self._memory is not None:
|
||||
from crewai.tools.memory_tools import create_memory_tools
|
||||
from crewai.utilities.agent_utils import sanitize_tool_name
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
existing_names = {sanitize_tool_name(t.name) for t in self._parsed_tools}
|
||||
memory_tools = [
|
||||
mt for mt in create_memory_tools(self._memory)
|
||||
mt
|
||||
for mt in create_memory_tools(self._memory)
|
||||
if sanitize_tool_name(mt.name) not in existing_names
|
||||
]
|
||||
if memory_tools:
|
||||
@@ -565,9 +575,10 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
if memory_block:
|
||||
formatted = self.i18n.slice("memory").format(memory=memory_block)
|
||||
if self._messages and self._messages[0].get("role") == "system":
|
||||
self._messages[0]["content"] = (
|
||||
self._messages[0].get("content", "") + "\n\n" + formatted
|
||||
)
|
||||
existing_content = self._messages[0].get("content", "")
|
||||
if not isinstance(existing_content, str):
|
||||
existing_content = ""
|
||||
self._messages[0]["content"] = existing_content + "\n\n" + formatted
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
event=MemoryRetrievalCompletedEvent(
|
||||
@@ -588,16 +599,12 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
)
|
||||
|
||||
def _save_to_memory(self, output_text: str) -> None:
|
||||
"""Extract discrete memories from the run and remember each. No-op if _memory is None."""
|
||||
if self._memory is None:
|
||||
"""Extract discrete memories from the run and remember each. No-op if _memory is None or read-only."""
|
||||
if self._memory is None or getattr(self._memory, "_read_only", False):
|
||||
return
|
||||
input_str = self._get_last_user_content() or "User request"
|
||||
try:
|
||||
raw = (
|
||||
f"Input: {input_str}\n"
|
||||
f"Agent: {self.role}\n"
|
||||
f"Result: {output_text}"
|
||||
)
|
||||
raw = f"Input: {input_str}\nAgent: {self.role}\nResult: {output_text}"
|
||||
extracted = self._memory.extract_memories(raw)
|
||||
if extracted:
|
||||
self._memory.remember_many(extracted, agent_role=self.role)
|
||||
@@ -622,13 +629,20 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
)
|
||||
|
||||
# Execute the agent using invoke loop
|
||||
agent_finish = self._invoke_loop()
|
||||
active_response_format = response_format or self.response_format
|
||||
agent_finish = self._invoke_loop(response_model=active_response_format)
|
||||
if self._memory is not None:
|
||||
self._save_to_memory(agent_finish.output)
|
||||
output_text = (
|
||||
agent_finish.output.model_dump_json()
|
||||
if isinstance(agent_finish.output, BaseModel)
|
||||
else agent_finish.output
|
||||
)
|
||||
self._save_to_memory(output_text)
|
||||
formatted_result: BaseModel | None = None
|
||||
|
||||
active_response_format = response_format or self.response_format
|
||||
if active_response_format:
|
||||
if isinstance(agent_finish.output, BaseModel):
|
||||
formatted_result = agent_finish.output
|
||||
elif active_response_format:
|
||||
try:
|
||||
model_schema = generate_model_description(active_response_format)
|
||||
schema = json.dumps(model_schema, indent=2)
|
||||
@@ -660,8 +674,13 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
usage_metrics = self._token_process.get_summary()
|
||||
|
||||
# Create output
|
||||
raw_output = (
|
||||
agent_finish.output.model_dump_json()
|
||||
if isinstance(agent_finish.output, BaseModel)
|
||||
else agent_finish.output
|
||||
)
|
||||
output = LiteAgentOutput(
|
||||
raw=agent_finish.output,
|
||||
raw=raw_output,
|
||||
pydantic=formatted_result,
|
||||
agent_role=self.role,
|
||||
usage_metrics=usage_metrics.model_dump() if usage_metrics else None,
|
||||
@@ -838,10 +857,15 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
|
||||
return formatted_messages
|
||||
|
||||
def _invoke_loop(self) -> AgentFinish:
|
||||
def _invoke_loop(
|
||||
self, response_model: type[BaseModel] | None = None
|
||||
) -> AgentFinish:
|
||||
"""
|
||||
Run the agent's thought process until it reaches a conclusion or max iterations.
|
||||
|
||||
Args:
|
||||
response_model: Optional Pydantic model for native structured output.
|
||||
|
||||
Returns:
|
||||
AgentFinish: The final result of the agent execution.
|
||||
"""
|
||||
@@ -870,12 +894,19 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
printer=self._printer,
|
||||
from_agent=self,
|
||||
executor_context=self,
|
||||
response_model=response_model,
|
||||
verbose=self.verbose,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
if isinstance(answer, BaseModel):
|
||||
formatted_answer = AgentFinish(
|
||||
thought="", output=answer, text=answer.model_dump_json()
|
||||
)
|
||||
break
|
||||
|
||||
formatted_answer = process_llm_response(
|
||||
cast(str, answer), self.use_stop_words
|
||||
)
|
||||
@@ -901,7 +932,7 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
)
|
||||
|
||||
self._append_message(formatted_answer.text, role="assistant")
|
||||
except OutputParserError as e: # noqa: PERF203
|
||||
except OutputParserError as e:
|
||||
if self.verbose:
|
||||
self._printer.print(
|
||||
content="Failed to parse LLM output. Retrying...",
|
||||
|
||||
@@ -427,7 +427,7 @@ class LLM(BaseLLM):
|
||||
f"installed.\n\n"
|
||||
f"To fix this, either:\n"
|
||||
f" 1. Install LiteLLM for broad model support: "
|
||||
f"uv add litellm\n"
|
||||
f"uv add 'crewai[litellm]'\n"
|
||||
f"or\n"
|
||||
f"pip install litellm\n\n"
|
||||
f"For more details, see: "
|
||||
|
||||
@@ -234,7 +234,7 @@ class BedrockCompletion(BaseLLM):
|
||||
aws_access_key_id: str | None = None,
|
||||
aws_secret_access_key: str | None = None,
|
||||
aws_session_token: str | None = None,
|
||||
region_name: str = "us-east-1",
|
||||
region_name: str | None = None,
|
||||
temperature: float | None = None,
|
||||
max_tokens: int | None = None,
|
||||
top_p: float | None = None,
|
||||
@@ -287,15 +287,6 @@ class BedrockCompletion(BaseLLM):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Initialize Bedrock client with proper configuration
|
||||
session = Session(
|
||||
aws_access_key_id=aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID"),
|
||||
aws_secret_access_key=aws_secret_access_key
|
||||
or os.getenv("AWS_SECRET_ACCESS_KEY"),
|
||||
aws_session_token=aws_session_token or os.getenv("AWS_SESSION_TOKEN"),
|
||||
region_name=region_name,
|
||||
)
|
||||
|
||||
# Configure client with timeouts and retries following AWS best practices
|
||||
config = Config(
|
||||
read_timeout=300,
|
||||
@@ -306,8 +297,12 @@ class BedrockCompletion(BaseLLM):
|
||||
tcp_keepalive=True,
|
||||
)
|
||||
|
||||
self.client = session.client("bedrock-runtime", config=config)
|
||||
self.region_name = region_name
|
||||
self.region_name = (
|
||||
region_name
|
||||
or os.getenv("AWS_DEFAULT_REGION")
|
||||
or os.getenv("AWS_REGION_NAME")
|
||||
or "us-east-1"
|
||||
)
|
||||
|
||||
self.aws_access_key_id = aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID")
|
||||
self.aws_secret_access_key = aws_secret_access_key or os.getenv(
|
||||
@@ -315,6 +310,16 @@ class BedrockCompletion(BaseLLM):
|
||||
)
|
||||
self.aws_session_token = aws_session_token or os.getenv("AWS_SESSION_TOKEN")
|
||||
|
||||
# Initialize Bedrock client with proper configuration
|
||||
session = Session(
|
||||
aws_access_key_id=self.aws_access_key_id,
|
||||
aws_secret_access_key=self.aws_secret_access_key,
|
||||
aws_session_token=self.aws_session_token,
|
||||
region_name=self.region_name,
|
||||
)
|
||||
|
||||
self.client = session.client("bedrock-runtime", config=config)
|
||||
|
||||
self._async_exit_stack = AsyncExitStack() if AIOBOTOCORE_AVAILABLE else None
|
||||
self._async_client_initialized = False
|
||||
|
||||
|
||||
@@ -894,7 +894,7 @@ class GeminiCompletion(BaseLLM):
|
||||
content = self._extract_text_from_response(response)
|
||||
|
||||
effective_response_model = None if self.tools else response_model
|
||||
if not effective_response_model:
|
||||
if not response_model:
|
||||
content = self._apply_stop_words(content)
|
||||
|
||||
return self._finalize_completion_response(
|
||||
|
||||
@@ -18,6 +18,7 @@ from crewai.mcp.filters import (
|
||||
create_dynamic_tool_filter,
|
||||
create_static_tool_filter,
|
||||
)
|
||||
from crewai.mcp.tool_resolver import MCPToolResolver
|
||||
from crewai.mcp.transports.base import BaseTransport, TransportType
|
||||
|
||||
|
||||
@@ -28,6 +29,7 @@ __all__ = [
|
||||
"MCPServerHTTP",
|
||||
"MCPServerSSE",
|
||||
"MCPServerStdio",
|
||||
"MCPToolResolver",
|
||||
"StaticToolFilter",
|
||||
"ToolFilter",
|
||||
"ToolFilterContext",
|
||||
|
||||
@@ -6,7 +6,7 @@ from contextlib import AsyncExitStack
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import time
|
||||
from typing import Any
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
@@ -34,6 +34,13 @@ from crewai.mcp.transports.stdio import StdioTransport
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
class _MCPToolResult(NamedTuple):
|
||||
"""Internal result from an MCP tool call, carrying the ``isError`` flag."""
|
||||
|
||||
content: str
|
||||
is_error: bool
|
||||
|
||||
|
||||
# MCP Connection timeout constants (in seconds)
|
||||
MCP_CONNECTION_TIMEOUT = 30 # Increased for slow servers
|
||||
MCP_TOOL_EXECUTION_TIMEOUT = 30
|
||||
@@ -420,6 +427,7 @@ class MCPClient:
|
||||
return [
|
||||
{
|
||||
"name": sanitize_tool_name(tool.name),
|
||||
"original_name": tool.name,
|
||||
"description": getattr(tool, "description", ""),
|
||||
"inputSchema": getattr(tool, "inputSchema", {}),
|
||||
}
|
||||
@@ -461,29 +469,46 @@ class MCPClient:
|
||||
)
|
||||
|
||||
try:
|
||||
result = await self._retry_operation(
|
||||
tool_result: _MCPToolResult = await self._retry_operation(
|
||||
lambda: self._call_tool_impl(tool_name, cleaned_arguments),
|
||||
timeout=self.execution_timeout,
|
||||
)
|
||||
|
||||
completed_at = datetime.now()
|
||||
execution_duration_ms = (completed_at - started_at).total_seconds() * 1000
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
MCPToolExecutionCompletedEvent(
|
||||
server_name=server_name,
|
||||
server_url=server_url,
|
||||
transport_type=transport_type,
|
||||
tool_name=tool_name,
|
||||
tool_args=cleaned_arguments,
|
||||
result=result,
|
||||
started_at=started_at,
|
||||
completed_at=completed_at,
|
||||
execution_duration_ms=execution_duration_ms,
|
||||
),
|
||||
)
|
||||
finished_at = datetime.now()
|
||||
execution_duration_ms = (finished_at - started_at).total_seconds() * 1000
|
||||
|
||||
return result
|
||||
if tool_result.is_error:
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
MCPToolExecutionFailedEvent(
|
||||
server_name=server_name,
|
||||
server_url=server_url,
|
||||
transport_type=transport_type,
|
||||
tool_name=tool_name,
|
||||
tool_args=cleaned_arguments,
|
||||
error=tool_result.content,
|
||||
error_type="tool_error",
|
||||
started_at=started_at,
|
||||
failed_at=finished_at,
|
||||
),
|
||||
)
|
||||
else:
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
MCPToolExecutionCompletedEvent(
|
||||
server_name=server_name,
|
||||
server_url=server_url,
|
||||
transport_type=transport_type,
|
||||
tool_name=tool_name,
|
||||
tool_args=cleaned_arguments,
|
||||
result=tool_result.content,
|
||||
started_at=started_at,
|
||||
completed_at=finished_at,
|
||||
execution_duration_ms=execution_duration_ms,
|
||||
),
|
||||
)
|
||||
|
||||
return tool_result.content
|
||||
except Exception as e:
|
||||
failed_at = datetime.now()
|
||||
error_type = (
|
||||
@@ -564,23 +589,27 @@ class MCPClient:
|
||||
|
||||
return cleaned
|
||||
|
||||
async def _call_tool_impl(self, tool_name: str, arguments: dict[str, Any]) -> Any:
|
||||
async def _call_tool_impl(
|
||||
self, tool_name: str, arguments: dict[str, Any]
|
||||
) -> _MCPToolResult:
|
||||
"""Internal implementation of call_tool."""
|
||||
result = await asyncio.wait_for(
|
||||
self.session.call_tool(tool_name, arguments),
|
||||
timeout=self.execution_timeout,
|
||||
)
|
||||
|
||||
is_error = getattr(result, "isError", False) or False
|
||||
|
||||
# Extract result content
|
||||
if hasattr(result, "content") and result.content:
|
||||
if isinstance(result.content, list) and len(result.content) > 0:
|
||||
content_item = result.content[0]
|
||||
if hasattr(content_item, "text"):
|
||||
return str(content_item.text)
|
||||
return str(content_item)
|
||||
return str(result.content)
|
||||
return _MCPToolResult(str(content_item.text), is_error)
|
||||
return _MCPToolResult(str(content_item), is_error)
|
||||
return _MCPToolResult(str(result.content), is_error)
|
||||
|
||||
return str(result)
|
||||
return _MCPToolResult(str(result), is_error)
|
||||
|
||||
async def list_prompts(self) -> list[dict[str, Any]]:
|
||||
"""List available prompts from MCP server.
|
||||
|
||||
592
lib/crewai/src/crewai/mcp/tool_resolver.py
Normal file
592
lib/crewai/src/crewai/mcp/tool_resolver.py
Normal file
@@ -0,0 +1,592 @@
|
||||
"""MCP tool resolution for CrewAI agents.
|
||||
|
||||
This module extracts all MCP-related tool resolution logic from the Agent class
|
||||
into a standalone MCPToolResolver. It handles three flavours of MCP reference:
|
||||
|
||||
1. Native configs: MCPServerStdio / MCPServerHTTP / MCPServerSSE objects.
|
||||
2. HTTPS URLs: e.g. "https://mcp.example.com/api"
|
||||
3. AMP references: e.g. "notion" or "notion#search" (legacy "crewai-amp:" prefix also works)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, Final, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from crewai.mcp.client import MCPClient
|
||||
from crewai.mcp.config import (
|
||||
MCPServerConfig,
|
||||
MCPServerHTTP,
|
||||
MCPServerSSE,
|
||||
MCPServerStdio,
|
||||
)
|
||||
from crewai.mcp.transports.http import HTTPTransport
|
||||
from crewai.mcp.transports.sse import SSETransport
|
||||
from crewai.mcp.transports.stdio import StdioTransport
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.logger import Logger
|
||||
|
||||
MCP_CONNECTION_TIMEOUT: Final[int] = 10
|
||||
MCP_TOOL_EXECUTION_TIMEOUT: Final[int] = 30
|
||||
MCP_DISCOVERY_TIMEOUT: Final[int] = 15
|
||||
MCP_MAX_RETRIES: Final[int] = 3
|
||||
|
||||
_mcp_schema_cache: dict[str, Any] = {}
|
||||
_cache_ttl: Final[int] = 300 # 5 minutes
|
||||
|
||||
|
||||
class MCPToolResolver:
|
||||
"""Resolves MCP server references / configs into CrewAI ``BaseTool`` instances.
|
||||
|
||||
Typical lifecycle::
|
||||
|
||||
resolver = MCPToolResolver(agent=my_agent, logger=my_agent._logger)
|
||||
tools = resolver.resolve(my_agent.mcps)
|
||||
# … agent executes tasks using *tools* …
|
||||
resolver.cleanup()
|
||||
|
||||
The resolver owns the MCP client connections it creates and is responsible
|
||||
for tearing them down via :meth:`cleanup`.
|
||||
"""
|
||||
|
||||
def __init__(self, agent: Any, logger: Logger) -> None:
|
||||
self._agent = agent
|
||||
self._logger = logger
|
||||
self._clients: list[Any] = []
|
||||
|
||||
@property
|
||||
def clients(self) -> list[Any]:
|
||||
return list(self._clients)
|
||||
|
||||
def resolve(self, mcps: list[str | MCPServerConfig]) -> list[BaseTool]:
|
||||
"""Convert MCP server references/configs to CrewAI tools."""
|
||||
all_tools: list[BaseTool] = []
|
||||
amp_refs: list[tuple[str, str | None]] = []
|
||||
|
||||
for mcp_config in mcps:
|
||||
if isinstance(mcp_config, str) and mcp_config.startswith("https://"):
|
||||
all_tools.extend(self._resolve_external(mcp_config))
|
||||
elif isinstance(mcp_config, str):
|
||||
amp_refs.append(self._parse_amp_ref(mcp_config))
|
||||
else:
|
||||
tools, client = self._resolve_native(mcp_config)
|
||||
all_tools.extend(tools)
|
||||
if client:
|
||||
self._clients.append(client)
|
||||
|
||||
if amp_refs:
|
||||
tools, clients = self._resolve_amp(amp_refs)
|
||||
all_tools.extend(tools)
|
||||
self._clients.extend(clients)
|
||||
|
||||
return all_tools
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""Disconnect all MCP client connections."""
|
||||
if not self._clients:
|
||||
return
|
||||
|
||||
async def _disconnect_all() -> None:
|
||||
for client in self._clients:
|
||||
if client and hasattr(client, "connected") and client.connected:
|
||||
await client.disconnect()
|
||||
|
||||
try:
|
||||
asyncio.run(_disconnect_all())
|
||||
except Exception as e:
|
||||
self._logger.log("error", f"Error during MCP client cleanup: {e}")
|
||||
finally:
|
||||
self._clients.clear()
|
||||
|
||||
@staticmethod
|
||||
def _parse_amp_ref(mcp_config: str) -> tuple[str, str | None]:
|
||||
"""Parse an AMP reference into *(slug, optional tool name)*.
|
||||
|
||||
Accepts both bare slugs (``"notion"``, ``"notion#search"``) and the
|
||||
legacy ``"crewai-amp:notion"`` form.
|
||||
"""
|
||||
bare = mcp_config.removeprefix("crewai-amp:")
|
||||
slug, _, specific_tool = bare.partition("#")
|
||||
return slug, specific_tool or None
|
||||
|
||||
def _resolve_amp(
|
||||
self, amp_refs: list[tuple[str, str | None]]
|
||||
) -> tuple[list[BaseTool], list[Any]]:
|
||||
"""Fetch AMP configs in bulk and return their tools and clients.
|
||||
|
||||
Resolves each unique slug only once (single connection per server),
|
||||
then applies per-ref tool filters to select specific tools.
|
||||
"""
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.mcp_events import MCPConfigFetchFailedEvent
|
||||
|
||||
unique_slugs = list(dict.fromkeys(slug for slug, _ in amp_refs))
|
||||
amp_configs_map = self._fetch_amp_mcp_configs(unique_slugs)
|
||||
|
||||
all_tools: list[BaseTool] = []
|
||||
all_clients: list[Any] = []
|
||||
|
||||
resolved_cache: dict[str, tuple[list[BaseTool], Any | None]] = {}
|
||||
|
||||
for slug in unique_slugs:
|
||||
config_dict = amp_configs_map.get(slug)
|
||||
if not config_dict:
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
MCPConfigFetchFailedEvent(
|
||||
slug=slug,
|
||||
error=f"Config for '{slug}' not found. Make sure it is connected in your account.",
|
||||
error_type="not_connected",
|
||||
),
|
||||
)
|
||||
continue
|
||||
|
||||
mcp_server_config = self._build_mcp_config_from_dict(config_dict)
|
||||
|
||||
try:
|
||||
tools, client = self._resolve_native(mcp_server_config)
|
||||
resolved_cache[slug] = (tools, client)
|
||||
if client:
|
||||
all_clients.append(client)
|
||||
except Exception as e:
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
MCPConfigFetchFailedEvent(
|
||||
slug=slug,
|
||||
error=str(e),
|
||||
error_type="connection_failed",
|
||||
),
|
||||
)
|
||||
|
||||
for slug, specific_tool in amp_refs:
|
||||
cached = resolved_cache.get(slug)
|
||||
if not cached:
|
||||
continue
|
||||
|
||||
slug_tools, _ = cached
|
||||
if specific_tool:
|
||||
all_tools.extend(
|
||||
t for t in slug_tools if t.name.endswith(f"_{specific_tool}")
|
||||
)
|
||||
else:
|
||||
all_tools.extend(slug_tools)
|
||||
|
||||
return all_tools, all_clients
|
||||
|
||||
def _fetch_amp_mcp_configs(self, slugs: list[str]) -> dict[str, dict[str, Any]]:
|
||||
"""Fetch MCP server configurations via CrewAI+ API.
|
||||
|
||||
Sends a GET request to the CrewAI+ mcps/configs endpoint with
|
||||
comma-separated slugs. CrewAI+ proxies the request to crewai-oauth.
|
||||
|
||||
API-level failures return ``{}``; individual slugs will then
|
||||
surface as ``MCPConfigFetchFailedEvent`` in :meth:`_resolve_amp`.
|
||||
"""
|
||||
import httpx
|
||||
|
||||
try:
|
||||
from crewai_tools.tools.crewai_platform_tools.misc import (
|
||||
get_platform_integration_token,
|
||||
)
|
||||
|
||||
from crewai.cli.plus_api import PlusAPI
|
||||
|
||||
plus_api = PlusAPI(api_key=get_platform_integration_token())
|
||||
response = plus_api.get_mcp_configs(slugs)
|
||||
|
||||
if response.status_code == 200:
|
||||
configs: dict[str, dict[str, Any]] = response.json().get("configs", {})
|
||||
return configs
|
||||
|
||||
self._logger.log(
|
||||
"debug",
|
||||
f"Failed to fetch MCP configs: HTTP {response.status_code}",
|
||||
)
|
||||
return {}
|
||||
|
||||
except httpx.HTTPError as e:
|
||||
self._logger.log("debug", f"Failed to fetch MCP configs: {e}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
self._logger.log("debug", f"Cannot fetch AMP MCP configs: {e}")
|
||||
return {}
|
||||
|
||||
def _resolve_external(self, mcp_ref: str) -> list[BaseTool]:
|
||||
"""Resolve an HTTPS MCP server URL into tools."""
|
||||
from crewai.tools.mcp_tool_wrapper import MCPToolWrapper
|
||||
|
||||
if "#" in mcp_ref:
|
||||
server_url, specific_tool = mcp_ref.split("#", 1)
|
||||
else:
|
||||
server_url, specific_tool = mcp_ref, None
|
||||
|
||||
server_params = {"url": server_url}
|
||||
server_name = self._extract_server_name(server_url)
|
||||
|
||||
try:
|
||||
tool_schemas = self._get_mcp_tool_schemas(server_params)
|
||||
|
||||
if not tool_schemas:
|
||||
self._logger.log(
|
||||
"warning", f"No tools discovered from MCP server: {server_url}"
|
||||
)
|
||||
return []
|
||||
|
||||
tools = []
|
||||
for tool_name, schema in tool_schemas.items():
|
||||
if specific_tool and tool_name != specific_tool:
|
||||
continue
|
||||
|
||||
try:
|
||||
wrapper = MCPToolWrapper(
|
||||
mcp_server_params=server_params,
|
||||
tool_name=tool_name,
|
||||
tool_schema=schema,
|
||||
server_name=server_name,
|
||||
)
|
||||
tools.append(wrapper)
|
||||
except Exception as e:
|
||||
self._logger.log(
|
||||
"warning",
|
||||
f"Failed to create MCP tool wrapper for {tool_name}: {e}",
|
||||
)
|
||||
continue
|
||||
|
||||
if specific_tool and not tools:
|
||||
self._logger.log(
|
||||
"warning",
|
||||
f"Specific tool '{specific_tool}' not found on MCP server: {server_url}",
|
||||
)
|
||||
|
||||
return cast(list[BaseTool], tools)
|
||||
|
||||
except Exception as e:
|
||||
self._logger.log(
|
||||
"warning", f"Failed to connect to MCP server {server_url}: {e}"
|
||||
)
|
||||
return []
|
||||
|
||||
def _resolve_native(
|
||||
self, mcp_config: MCPServerConfig
|
||||
) -> tuple[list[BaseTool], Any | None]:
|
||||
"""Resolve an ``MCPServerConfig`` into tools, returning the client for cleanup."""
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.mcp_native_tool import MCPNativeTool
|
||||
|
||||
transport: StdioTransport | HTTPTransport | SSETransport
|
||||
if isinstance(mcp_config, MCPServerStdio):
|
||||
transport = StdioTransport(
|
||||
command=mcp_config.command,
|
||||
args=mcp_config.args,
|
||||
env=mcp_config.env,
|
||||
)
|
||||
server_name = f"{mcp_config.command}_{'_'.join(mcp_config.args)}"
|
||||
elif isinstance(mcp_config, MCPServerHTTP):
|
||||
transport = HTTPTransport(
|
||||
url=mcp_config.url,
|
||||
headers=mcp_config.headers,
|
||||
streamable=mcp_config.streamable,
|
||||
)
|
||||
server_name = self._extract_server_name(mcp_config.url)
|
||||
elif isinstance(mcp_config, MCPServerSSE):
|
||||
transport = SSETransport(
|
||||
url=mcp_config.url,
|
||||
headers=mcp_config.headers,
|
||||
)
|
||||
server_name = self._extract_server_name(mcp_config.url)
|
||||
else:
|
||||
raise ValueError(f"Unsupported MCP server config type: {type(mcp_config)}")
|
||||
|
||||
client = MCPClient(
|
||||
transport=transport,
|
||||
cache_tools_list=mcp_config.cache_tools_list,
|
||||
)
|
||||
|
||||
async def _setup_client_and_list_tools() -> list[dict[str, Any]]:
|
||||
try:
|
||||
if not client.connected:
|
||||
await client.connect()
|
||||
|
||||
tools_list = await client.list_tools()
|
||||
|
||||
try:
|
||||
await client.disconnect()
|
||||
await asyncio.sleep(0.1)
|
||||
except Exception as e:
|
||||
self._logger.log("error", f"Error during disconnect: {e}")
|
||||
|
||||
return tools_list
|
||||
except Exception as e:
|
||||
if client.connected:
|
||||
await client.disconnect()
|
||||
await asyncio.sleep(0.1)
|
||||
raise RuntimeError(
|
||||
f"Error during setup client and list tools: {e}"
|
||||
) from e
|
||||
|
||||
try:
|
||||
try:
|
||||
asyncio.get_running_loop()
|
||||
import concurrent.futures
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future = executor.submit(
|
||||
asyncio.run, _setup_client_and_list_tools()
|
||||
)
|
||||
tools_list = future.result()
|
||||
except RuntimeError:
|
||||
try:
|
||||
tools_list = asyncio.run(_setup_client_and_list_tools())
|
||||
except RuntimeError as e:
|
||||
error_msg = str(e).lower()
|
||||
if "cancel scope" in error_msg or "task" in error_msg:
|
||||
raise ConnectionError(
|
||||
"MCP connection failed due to event loop cleanup issues. "
|
||||
"This may be due to authentication errors or server unavailability."
|
||||
) from e
|
||||
except asyncio.CancelledError as e:
|
||||
raise ConnectionError(
|
||||
"MCP connection was cancelled. This may indicate an authentication "
|
||||
"error or server unavailability."
|
||||
) from e
|
||||
|
||||
if mcp_config.tool_filter:
|
||||
filtered_tools = []
|
||||
for tool in tools_list:
|
||||
if callable(mcp_config.tool_filter):
|
||||
try:
|
||||
from crewai.mcp.filters import ToolFilterContext
|
||||
|
||||
context = ToolFilterContext(
|
||||
agent=self._agent,
|
||||
server_name=server_name,
|
||||
run_context=None,
|
||||
)
|
||||
if mcp_config.tool_filter(context, tool): # type: ignore[call-arg, arg-type]
|
||||
filtered_tools.append(tool)
|
||||
except (TypeError, AttributeError):
|
||||
if mcp_config.tool_filter(tool): # type: ignore[call-arg, arg-type]
|
||||
filtered_tools.append(tool)
|
||||
else:
|
||||
filtered_tools.append(tool)
|
||||
tools_list = filtered_tools
|
||||
|
||||
tools = []
|
||||
for tool_def in tools_list:
|
||||
tool_name = tool_def.get("name", "")
|
||||
original_tool_name = tool_def.get("original_name", tool_name)
|
||||
if not tool_name:
|
||||
continue
|
||||
|
||||
args_schema = None
|
||||
if tool_def.get("inputSchema"):
|
||||
args_schema = self._json_schema_to_pydantic(
|
||||
tool_name, tool_def["inputSchema"]
|
||||
)
|
||||
|
||||
tool_schema = {
|
||||
"description": tool_def.get("description", ""),
|
||||
"args_schema": args_schema,
|
||||
}
|
||||
|
||||
try:
|
||||
native_tool = MCPNativeTool(
|
||||
mcp_client=client,
|
||||
tool_name=tool_name,
|
||||
tool_schema=tool_schema,
|
||||
server_name=server_name,
|
||||
original_tool_name=original_tool_name,
|
||||
)
|
||||
tools.append(native_tool)
|
||||
except Exception as e:
|
||||
self._logger.log("error", f"Failed to create native MCP tool: {e}")
|
||||
continue
|
||||
|
||||
return cast(list[BaseTool], tools), client
|
||||
except Exception as e:
|
||||
if client.connected:
|
||||
asyncio.run(client.disconnect())
|
||||
|
||||
raise RuntimeError(f"Failed to get native MCP tools: {e}") from e
|
||||
|
||||
@staticmethod
|
||||
def _build_mcp_config_from_dict(
|
||||
config_dict: dict[str, Any],
|
||||
) -> MCPServerConfig:
|
||||
"""Convert a config dict from crewai-oauth into an MCPServerConfig."""
|
||||
config_type = config_dict.get("type", "http")
|
||||
|
||||
if config_type == "sse":
|
||||
return MCPServerSSE(
|
||||
url=config_dict["url"],
|
||||
headers=config_dict.get("headers"),
|
||||
cache_tools_list=config_dict.get("cache_tools_list", False),
|
||||
)
|
||||
|
||||
return MCPServerHTTP(
|
||||
url=config_dict["url"],
|
||||
headers=config_dict.get("headers"),
|
||||
streamable=config_dict.get("streamable", True),
|
||||
cache_tools_list=config_dict.get("cache_tools_list", False),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _extract_server_name(server_url: str) -> str:
|
||||
"""Extract clean server name from URL for tool prefixing."""
|
||||
parsed = urlparse(server_url)
|
||||
domain = parsed.netloc.replace(".", "_")
|
||||
path = parsed.path.replace("/", "_").strip("_")
|
||||
return f"{domain}_{path}" if path else domain
|
||||
|
||||
def _get_mcp_tool_schemas(
|
||||
self, server_params: dict[str, Any]
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Get tool schemas from MCP server with caching."""
|
||||
server_url = server_params["url"]
|
||||
|
||||
cache_key = server_url
|
||||
current_time = time.time()
|
||||
|
||||
if cache_key in _mcp_schema_cache:
|
||||
cached_data, cache_time = _mcp_schema_cache[cache_key]
|
||||
if current_time - cache_time < _cache_ttl:
|
||||
self._logger.log(
|
||||
"debug", f"Using cached MCP tool schemas for {server_url}"
|
||||
)
|
||||
return cached_data # type: ignore[no-any-return]
|
||||
|
||||
try:
|
||||
schemas = asyncio.run(self._get_mcp_tool_schemas_async(server_params))
|
||||
_mcp_schema_cache[cache_key] = (schemas, current_time)
|
||||
return schemas
|
||||
except Exception as e:
|
||||
self._logger.log(
|
||||
"warning", f"Failed to get MCP tool schemas from {server_url}: {e}"
|
||||
)
|
||||
return {}
|
||||
|
||||
async def _get_mcp_tool_schemas_async(
|
||||
self, server_params: dict[str, Any]
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Async implementation of MCP tool schema retrieval."""
|
||||
server_url = server_params["url"]
|
||||
return await self._retry_mcp_discovery(
|
||||
self._discover_mcp_tools_with_timeout, server_url
|
||||
)
|
||||
|
||||
async def _retry_mcp_discovery(
|
||||
self, operation_func: Any, server_url: str
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Retry MCP discovery with exponential backoff."""
|
||||
last_error = None
|
||||
|
||||
for attempt in range(MCP_MAX_RETRIES):
|
||||
result, error, should_retry = await self._attempt_mcp_discovery(
|
||||
operation_func, server_url
|
||||
)
|
||||
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
if not should_retry:
|
||||
raise RuntimeError(error)
|
||||
|
||||
last_error = error
|
||||
if attempt < MCP_MAX_RETRIES - 1:
|
||||
wait_time = 2**attempt
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
raise RuntimeError(
|
||||
f"Failed to discover MCP tools after {MCP_MAX_RETRIES} attempts: {last_error}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def _attempt_mcp_discovery(
|
||||
operation_func: Any, server_url: str
|
||||
) -> tuple[dict[str, dict[str, Any]] | None, str, bool]:
|
||||
"""Attempt single MCP discovery; returns *(result, error_message, should_retry)*."""
|
||||
try:
|
||||
result = await operation_func(server_url)
|
||||
return result, "", False
|
||||
|
||||
except ImportError:
|
||||
return (
|
||||
None,
|
||||
"MCP library not available. Please install with: pip install mcp",
|
||||
False,
|
||||
)
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
return (
|
||||
None,
|
||||
f"MCP discovery timed out after {MCP_DISCOVERY_TIMEOUT} seconds",
|
||||
True,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_str = str(e).lower()
|
||||
|
||||
if "authentication" in error_str or "unauthorized" in error_str:
|
||||
return None, f"Authentication failed for MCP server: {e!s}", False
|
||||
if "connection" in error_str or "network" in error_str:
|
||||
return None, f"Network connection failed: {e!s}", True
|
||||
if "json" in error_str or "parsing" in error_str:
|
||||
return None, f"Server response parsing error: {e!s}", True
|
||||
return None, f"MCP discovery error: {e!s}", False
|
||||
|
||||
async def _discover_mcp_tools_with_timeout(
|
||||
self, server_url: str
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Discover MCP tools with timeout wrapper."""
|
||||
return await asyncio.wait_for(
|
||||
self._discover_mcp_tools(server_url), timeout=MCP_DISCOVERY_TIMEOUT
|
||||
)
|
||||
|
||||
async def _discover_mcp_tools(self, server_url: str) -> dict[str, dict[str, Any]]:
|
||||
"""Discover tools from an MCP server (HTTPS / streamable-HTTP path)."""
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
async with streamablehttp_client(server_url) as (read, write, _):
|
||||
async with ClientSession(read, write) as session:
|
||||
await asyncio.wait_for(
|
||||
session.initialize(), timeout=MCP_CONNECTION_TIMEOUT
|
||||
)
|
||||
|
||||
tools_result = await asyncio.wait_for(
|
||||
session.list_tools(),
|
||||
timeout=MCP_DISCOVERY_TIMEOUT - MCP_CONNECTION_TIMEOUT,
|
||||
)
|
||||
|
||||
schemas = {}
|
||||
for tool in tools_result.tools:
|
||||
args_schema = None
|
||||
if hasattr(tool, "inputSchema") and tool.inputSchema:
|
||||
args_schema = self._json_schema_to_pydantic(
|
||||
sanitize_tool_name(tool.name), tool.inputSchema
|
||||
)
|
||||
|
||||
schemas[sanitize_tool_name(tool.name)] = {
|
||||
"description": getattr(tool, "description", ""),
|
||||
"args_schema": args_schema,
|
||||
}
|
||||
return schemas
|
||||
|
||||
@staticmethod
|
||||
def _json_schema_to_pydantic(tool_name: str, json_schema: dict[str, Any]) -> type:
|
||||
"""Convert JSON Schema to a Pydantic model for tool arguments."""
|
||||
from crewai.utilities.pydantic_schema_utils import create_model_from_schema
|
||||
|
||||
model_name = f"{tool_name.replace('-', '_').replace(' ', '_')}Schema"
|
||||
return create_model_from_schema(
|
||||
json_schema,
|
||||
model_name=model_name,
|
||||
enrich_descriptions=True,
|
||||
)
|
||||
@@ -145,7 +145,7 @@ class MemoryScope:
|
||||
|
||||
|
||||
class MemorySlice:
|
||||
"""View over multiple scopes: recall searches all, remember requires explicit scope unless read_only."""
|
||||
"""View over multiple scopes: recall searches all, remember is a no-op when read_only."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -160,7 +160,7 @@ class MemorySlice:
|
||||
memory: The underlying Memory instance.
|
||||
scopes: List of scope paths to include.
|
||||
categories: Optional category filter for recall.
|
||||
read_only: If True, remember() raises PermissionError.
|
||||
read_only: If True, remember() is a silent no-op.
|
||||
"""
|
||||
self._memory = memory
|
||||
self._scopes = [s.rstrip("/") or "/" for s in scopes]
|
||||
@@ -176,10 +176,10 @@ class MemorySlice:
|
||||
importance: float | None = None,
|
||||
source: str | None = None,
|
||||
private: bool = False,
|
||||
) -> MemoryRecord:
|
||||
"""Remember into an explicit scope. Required when read_only=False."""
|
||||
) -> MemoryRecord | None:
|
||||
"""Remember into an explicit scope. No-op when read_only=True."""
|
||||
if self._read_only:
|
||||
raise PermissionError("This MemorySlice is read-only")
|
||||
return None
|
||||
return self._memory.remember(
|
||||
content,
|
||||
scope=scope,
|
||||
|
||||
@@ -53,6 +53,7 @@ class LanceDBStorage:
|
||||
path: str | Path | None = None,
|
||||
table_name: str = "memories",
|
||||
vector_dim: int | None = None,
|
||||
compact_every: int = 100,
|
||||
) -> None:
|
||||
"""Initialize LanceDB storage.
|
||||
|
||||
@@ -64,6 +65,10 @@ class LanceDBStorage:
|
||||
vector_dim: Dimensionality of the embedding vector. When ``None``
|
||||
(default), the dimension is auto-detected from the existing
|
||||
table schema or from the first saved embedding.
|
||||
compact_every: Number of ``save()`` calls between automatic
|
||||
background compactions. Each ``save()`` creates one new
|
||||
fragment file; compaction merges them, keeping query
|
||||
performance consistent. Set to 0 to disable.
|
||||
"""
|
||||
if path is None:
|
||||
storage_dir = os.environ.get("CREWAI_STORAGE_DIR")
|
||||
@@ -78,6 +83,22 @@ class LanceDBStorage:
|
||||
self._table_name = table_name
|
||||
self._db = lancedb.connect(str(self._path))
|
||||
|
||||
# On macOS and Linux the default per-process open-file limit is 256.
|
||||
# A LanceDB table stores one file per fragment (one fragment per save()
|
||||
# call by default). With hundreds of fragments, a single full-table
|
||||
# scan opens all of them simultaneously, exhausting the limit.
|
||||
# Raise it proactively so scans on large tables never hit OS error 24.
|
||||
try:
|
||||
import resource
|
||||
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
if soft < 4096:
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, (min(hard, 4096), hard))
|
||||
except Exception: # noqa: S110
|
||||
pass # Windows or already at the max hard limit — safe to ignore
|
||||
|
||||
self._compact_every = compact_every
|
||||
self._save_count = 0
|
||||
|
||||
# Get or create a shared write lock for this database path.
|
||||
resolved = str(self._path.resolve())
|
||||
with LanceDBStorage._path_locks_guard:
|
||||
@@ -91,6 +112,11 @@ class LanceDBStorage:
|
||||
try:
|
||||
self._table: lancedb.table.Table | None = self._db.open_table(self._table_name)
|
||||
self._vector_dim: int = self._infer_dim_from_table(self._table)
|
||||
# Best-effort: create the scope index if it doesn't exist yet.
|
||||
self._ensure_scope_index()
|
||||
# Compact in the background if the table has accumulated many
|
||||
# fragments from previous runs (each save() creates one).
|
||||
self._compact_if_needed()
|
||||
except Exception:
|
||||
self._table = None
|
||||
self._vector_dim = vector_dim or 0 # 0 = not yet known
|
||||
@@ -178,6 +204,56 @@ class LanceDBStorage:
|
||||
table.delete("id = '__schema_placeholder__'")
|
||||
return table
|
||||
|
||||
def _ensure_scope_index(self) -> None:
|
||||
"""Create a BTREE scalar index on the ``scope`` column if not present.
|
||||
|
||||
A scalar index lets LanceDB skip a full table scan when filtering by
|
||||
scope prefix, which is the hot path for ``list_records``,
|
||||
``get_scope_info``, and ``list_scopes``. The call is best-effort:
|
||||
if the table is empty or the index already exists the exception is
|
||||
swallowed silently.
|
||||
"""
|
||||
if self._table is None:
|
||||
return
|
||||
try:
|
||||
self._table.create_scalar_index("scope", index_type="BTREE", replace=False)
|
||||
except Exception: # noqa: S110
|
||||
pass # index already exists, table empty, or unsupported version
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Automatic background compaction
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _compact_if_needed(self) -> None:
|
||||
"""Spawn a background compaction on startup.
|
||||
|
||||
Called whenever an existing table is opened so that fragments
|
||||
accumulated in previous sessions are silently merged before the
|
||||
first query. ``optimize()`` returns quickly when the table is
|
||||
already compact, so the cost is negligible in the common case.
|
||||
"""
|
||||
if self._table is None or self._compact_every <= 0:
|
||||
return
|
||||
self._compact_async()
|
||||
|
||||
def _compact_async(self) -> None:
|
||||
"""Fire-and-forget: compact the table in a daemon background thread."""
|
||||
threading.Thread(
|
||||
target=self._compact_safe,
|
||||
daemon=True,
|
||||
name="lancedb-compact",
|
||||
).start()
|
||||
|
||||
def _compact_safe(self) -> None:
|
||||
"""Run ``table.optimize()`` in a background thread, absorbing errors."""
|
||||
try:
|
||||
if self._table is not None:
|
||||
self._table.optimize()
|
||||
# Refresh the scope index so new fragments are covered.
|
||||
self._ensure_scope_index()
|
||||
except Exception:
|
||||
_logger.debug("LanceDB background compaction failed", exc_info=True)
|
||||
|
||||
def _ensure_table(self, vector_dim: int | None = None) -> lancedb.table.Table:
|
||||
"""Return the table, creating it lazily if needed.
|
||||
|
||||
@@ -239,6 +315,7 @@ class LanceDBStorage:
|
||||
if r.embedding and len(r.embedding) > 0:
|
||||
dim = len(r.embedding)
|
||||
break
|
||||
is_new_table = self._table is None
|
||||
with self._write_lock:
|
||||
self._ensure_table(vector_dim=dim)
|
||||
rows = [self._record_to_row(r) for r in records]
|
||||
@@ -246,6 +323,13 @@ class LanceDBStorage:
|
||||
if r["vector"] is None or len(r["vector"]) != self._vector_dim:
|
||||
r["vector"] = [0.0] * self._vector_dim
|
||||
self._retry_write("add", rows)
|
||||
# Create the scope index on the first save so it covers the initial dataset.
|
||||
if is_new_table:
|
||||
self._ensure_scope_index()
|
||||
# Auto-compact every N saves so fragment files don't pile up.
|
||||
self._save_count += 1
|
||||
if self._compact_every > 0 and self._save_count % self._compact_every == 0:
|
||||
self._compact_async()
|
||||
|
||||
def update(self, record: MemoryRecord) -> None:
|
||||
"""Update a record by ID. Preserves created_at, updates last_accessed."""
|
||||
@@ -261,6 +345,10 @@ class LanceDBStorage:
|
||||
def touch_records(self, record_ids: list[str]) -> None:
|
||||
"""Update last_accessed to now for the given record IDs.
|
||||
|
||||
Uses a single batch ``table.update()`` call instead of N
|
||||
delete-and-re-add cycles, which is both faster and avoids
|
||||
unnecessary write amplification.
|
||||
|
||||
Args:
|
||||
record_ids: IDs of records to touch.
|
||||
"""
|
||||
@@ -268,25 +356,20 @@ class LanceDBStorage:
|
||||
return
|
||||
with self._write_lock:
|
||||
now = datetime.utcnow().isoformat()
|
||||
for rid in record_ids:
|
||||
safe_id = str(rid).replace("'", "''")
|
||||
rows = (
|
||||
self._table.search([0.0] * self._vector_dim)
|
||||
.where(f"id = '{safe_id}'")
|
||||
.limit(1)
|
||||
.to_list()
|
||||
)
|
||||
if rows:
|
||||
rows[0]["last_accessed"] = now
|
||||
self._retry_write("delete", f"id = '{safe_id}'")
|
||||
self._retry_write("add", [rows[0]])
|
||||
safe_ids = [str(rid).replace("'", "''") for rid in record_ids]
|
||||
ids_expr = ", ".join(f"'{rid}'" for rid in safe_ids)
|
||||
self._retry_write(
|
||||
"update",
|
||||
where=f"id IN ({ids_expr})",
|
||||
values={"last_accessed": now},
|
||||
)
|
||||
|
||||
def get_record(self, record_id: str) -> MemoryRecord | None:
|
||||
"""Return a single record by ID, or None if not found."""
|
||||
if self._table is None:
|
||||
return None
|
||||
safe_id = str(record_id).replace("'", "''")
|
||||
rows = self._table.search([0.0] * self._vector_dim).where(f"id = '{safe_id}'").limit(1).to_list()
|
||||
rows = self._table.search().where(f"id = '{safe_id}'").limit(1).to_list()
|
||||
if not rows:
|
||||
return None
|
||||
return self._row_to_record(rows[0])
|
||||
@@ -374,13 +457,31 @@ class LanceDBStorage:
|
||||
self._retry_write("delete", where_expr)
|
||||
return before - self._table.count_rows()
|
||||
|
||||
def _scan_rows(self, scope_prefix: str | None = None, limit: int = _SCAN_ROWS_LIMIT) -> list[dict[str, Any]]:
|
||||
"""Scan rows optionally filtered by scope prefix."""
|
||||
def _scan_rows(
|
||||
self,
|
||||
scope_prefix: str | None = None,
|
||||
limit: int = _SCAN_ROWS_LIMIT,
|
||||
columns: list[str] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Scan rows optionally filtered by scope prefix.
|
||||
|
||||
Uses a full table scan (no vector query) so the limit is applied after
|
||||
the scope filter, not to ANN candidates before filtering.
|
||||
|
||||
Args:
|
||||
scope_prefix: Optional scope path prefix to filter by.
|
||||
limit: Maximum number of rows to return (applied after filtering).
|
||||
columns: Optional list of column names to fetch. Pass only the
|
||||
columns you need for metadata operations to avoid reading the
|
||||
heavy ``vector`` column unnecessarily.
|
||||
"""
|
||||
if self._table is None:
|
||||
return []
|
||||
q = self._table.search([0.0] * self._vector_dim)
|
||||
q = self._table.search()
|
||||
if scope_prefix is not None and scope_prefix.strip("/"):
|
||||
q = q.where(f"scope LIKE '{scope_prefix.rstrip('/')}%'")
|
||||
if columns is not None:
|
||||
q = q.select(columns)
|
||||
return q.limit(limit).to_list()
|
||||
|
||||
def list_records(
|
||||
@@ -406,7 +507,10 @@ class LanceDBStorage:
|
||||
prefix = scope if scope != "/" else ""
|
||||
if prefix and not prefix.startswith("/"):
|
||||
prefix = "/" + prefix
|
||||
rows = self._scan_rows(prefix or None)
|
||||
rows = self._scan_rows(
|
||||
prefix or None,
|
||||
columns=["scope", "categories_str", "created_at"],
|
||||
)
|
||||
if not rows:
|
||||
return ScopeInfo(
|
||||
path=scope or "/",
|
||||
@@ -453,7 +557,7 @@ class LanceDBStorage:
|
||||
def list_scopes(self, parent: str = "/") -> list[str]:
|
||||
parent = parent.rstrip("/") or ""
|
||||
prefix = (parent + "/") if parent else "/"
|
||||
rows = self._scan_rows(prefix if prefix != "/" else None)
|
||||
rows = self._scan_rows(prefix if prefix != "/" else None, columns=["scope"])
|
||||
children: set[str] = set()
|
||||
for row in rows:
|
||||
sc = str(row.get("scope", ""))
|
||||
@@ -465,7 +569,7 @@ class LanceDBStorage:
|
||||
return sorted(children)
|
||||
|
||||
def list_categories(self, scope_prefix: str | None = None) -> dict[str, int]:
|
||||
rows = self._scan_rows(scope_prefix)
|
||||
rows = self._scan_rows(scope_prefix, columns=["categories_str"])
|
||||
counts: dict[str, int] = {}
|
||||
for row in rows:
|
||||
cat_str = row.get("categories_str") or "[]"
|
||||
@@ -498,6 +602,21 @@ class LanceDBStorage:
|
||||
if prefix:
|
||||
self._table.delete(f"scope >= '{prefix}' AND scope < '{prefix}/\uFFFF'")
|
||||
|
||||
def optimize(self) -> None:
|
||||
"""Compact the table synchronously and refresh the scope index.
|
||||
|
||||
Under normal usage this is called automatically in the background
|
||||
(every ``compact_every`` saves and on startup when the table is
|
||||
fragmented). Call this explicitly only when you need the compaction
|
||||
to be complete before the next operation — for example immediately
|
||||
after a large bulk import, before a latency-sensitive recall.
|
||||
It is a no-op if the table does not exist.
|
||||
"""
|
||||
if self._table is None:
|
||||
return
|
||||
self._table.optimize()
|
||||
self._ensure_scope_index()
|
||||
|
||||
async def asave(self, records: list[MemoryRecord]) -> None:
|
||||
self.save(records)
|
||||
|
||||
|
||||
@@ -87,6 +87,22 @@ class MemoryMatch(BaseModel):
|
||||
description="Information the system looked for but could not find.",
|
||||
)
|
||||
|
||||
def format(self) -> str:
|
||||
"""Format this match as a human-readable string including metadata.
|
||||
|
||||
Returns:
|
||||
A multi-line string with score, content, categories, and non-empty
|
||||
metadata fields.
|
||||
"""
|
||||
lines = [f"- (score={self.score:.2f}) {self.record.content}"]
|
||||
if self.record.categories:
|
||||
lines.append(f" categories: {', '.join(self.record.categories)}")
|
||||
if self.record.metadata:
|
||||
for key, value in self.record.metadata.items():
|
||||
if value is not None:
|
||||
lines.append(f" {key}: {value}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class ScopeInfo(BaseModel):
|
||||
"""Information about a scope in the memory hierarchy."""
|
||||
@@ -291,7 +307,7 @@ def embed_text(embedder: Any, text: str) -> list[float]:
|
||||
return []
|
||||
first = result[0]
|
||||
if hasattr(first, "tolist"):
|
||||
return first.tolist()
|
||||
return list(first.tolist())
|
||||
if isinstance(first, list):
|
||||
return [float(x) for x in first]
|
||||
return list(first)
|
||||
|
||||
@@ -6,7 +6,7 @@ from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Literal
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.memory_events import (
|
||||
@@ -30,13 +30,20 @@ from crewai.memory.types import (
|
||||
compute_composite_score,
|
||||
embed_text,
|
||||
)
|
||||
from crewai.rag.embeddings.factory import build_embedder
|
||||
from crewai.rag.embeddings.providers.openai.types import OpenAIProviderSpec
|
||||
|
||||
|
||||
def _default_embedder() -> Any:
|
||||
if TYPE_CHECKING:
|
||||
from chromadb.utils.embedding_functions.openai_embedding_function import (
|
||||
OpenAIEmbeddingFunction,
|
||||
)
|
||||
|
||||
|
||||
def _default_embedder() -> OpenAIEmbeddingFunction:
|
||||
"""Build default OpenAI embedder for memory."""
|
||||
from crewai.rag.embeddings.factory import build_embedder
|
||||
|
||||
return build_embedder({"provider": "openai", "config": {}})
|
||||
spec: OpenAIProviderSpec = {"provider": "openai", "config": {}}
|
||||
return build_embedder(spec)
|
||||
|
||||
|
||||
class Memory:
|
||||
@@ -88,6 +95,10 @@ class Memory:
|
||||
# Queries shorter than this skip LLM analysis (saving ~1-3s).
|
||||
# Longer queries (full task descriptions) benefit from LLM distillation.
|
||||
query_analysis_threshold: int = 200,
|
||||
# When True, all write operations (remember, remember_many) are silently
|
||||
# skipped. Useful for sharing a read-only view of memory across agents
|
||||
# without any of them persisting new memories.
|
||||
read_only: bool = False,
|
||||
) -> None:
|
||||
"""Initialize Memory.
|
||||
|
||||
@@ -107,7 +118,9 @@ class Memory:
|
||||
complex_query_threshold: For complex queries, explore deeper below this confidence.
|
||||
exploration_budget: Number of LLM-driven exploration rounds during deep recall.
|
||||
query_analysis_threshold: Queries shorter than this skip LLM analysis during deep recall.
|
||||
read_only: If True, remember() and remember_many() are silent no-ops.
|
||||
"""
|
||||
self._read_only = read_only
|
||||
self._config = MemoryConfig(
|
||||
recency_weight=recency_weight,
|
||||
semantic_weight=semantic_weight,
|
||||
@@ -130,10 +143,13 @@ class Memory:
|
||||
self._llm_instance: BaseLLM | None = None if isinstance(llm, str) else llm
|
||||
self._embedder_config: Any = embedder
|
||||
self._embedder_instance: Any = (
|
||||
embedder if (embedder is not None and not isinstance(embedder, dict)) else None
|
||||
embedder
|
||||
if (embedder is not None and not isinstance(embedder, dict))
|
||||
else None
|
||||
)
|
||||
|
||||
# Storage is initialized eagerly (local, no API key needed).
|
||||
self._storage: StorageBackend
|
||||
if storage == "lancedb":
|
||||
self._storage = LanceDBStorage()
|
||||
elif isinstance(storage, str):
|
||||
@@ -160,12 +176,17 @@ class Memory:
|
||||
from crewai.llm import LLM
|
||||
|
||||
try:
|
||||
self._llm_instance = LLM(model=self._llm_config)
|
||||
model_name = (
|
||||
self._llm_config
|
||||
if isinstance(self._llm_config, str)
|
||||
else str(self._llm_config)
|
||||
)
|
||||
self._llm_instance = LLM(model=model_name)
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
f"Memory requires an LLM for analysis but initialization failed: {e}\n\n"
|
||||
"To fix this, do one of the following:\n"
|
||||
' - Set OPENAI_API_KEY for the default model (gpt-4o-mini)\n'
|
||||
" - Set OPENAI_API_KEY for the default model (gpt-4o-mini)\n"
|
||||
' - Pass a different model: Memory(llm="anthropic/claude-3-haiku-20240307")\n'
|
||||
' - Pass any LLM instance: Memory(llm=LLM(model="your-model"))\n'
|
||||
" - To skip LLM analysis, pass all fields explicitly to remember()\n"
|
||||
@@ -180,8 +201,6 @@ class Memory:
|
||||
if self._embedder_instance is None:
|
||||
try:
|
||||
if isinstance(self._embedder_config, dict):
|
||||
from crewai.rag.embeddings.factory import build_embedder
|
||||
|
||||
self._embedder_instance = build_embedder(self._embedder_config)
|
||||
else:
|
||||
self._embedder_instance = _default_embedder()
|
||||
@@ -317,7 +336,7 @@ class Memory:
|
||||
source: str | None = None,
|
||||
private: bool = False,
|
||||
agent_role: str | None = None,
|
||||
) -> MemoryRecord:
|
||||
) -> MemoryRecord | None:
|
||||
"""Store a single item in memory (synchronous).
|
||||
|
||||
Routes through the same serialized save pool as ``remember_many``
|
||||
@@ -335,11 +354,13 @@ class Memory:
|
||||
agent_role: Optional agent role for event metadata.
|
||||
|
||||
Returns:
|
||||
The created MemoryRecord.
|
||||
The created MemoryRecord, or None if this memory is read-only.
|
||||
|
||||
Raises:
|
||||
Exception: On save failure (events emitted).
|
||||
"""
|
||||
if self._read_only:
|
||||
return None
|
||||
_source_type = "unified_memory"
|
||||
try:
|
||||
crewai_event_bus.emit(
|
||||
@@ -356,7 +377,13 @@ class Memory:
|
||||
# then immediately wait for the result.
|
||||
future = self._submit_save(
|
||||
self._encode_batch,
|
||||
[content], scope, categories, metadata, importance, source, private,
|
||||
[content],
|
||||
scope,
|
||||
categories,
|
||||
metadata,
|
||||
importance,
|
||||
source,
|
||||
private,
|
||||
)
|
||||
records = future.result()
|
||||
record = records[0] if records else None
|
||||
@@ -420,13 +447,19 @@ class Memory:
|
||||
Returns:
|
||||
Empty list (records are not available until the background save completes).
|
||||
"""
|
||||
if not contents:
|
||||
if not contents or self._read_only:
|
||||
return []
|
||||
|
||||
self._submit_save(
|
||||
self._background_encode_batch,
|
||||
contents, scope, categories, metadata,
|
||||
importance, source, private, agent_role,
|
||||
contents,
|
||||
scope,
|
||||
categories,
|
||||
metadata,
|
||||
importance,
|
||||
source,
|
||||
private,
|
||||
agent_role,
|
||||
)
|
||||
return []
|
||||
|
||||
@@ -566,14 +599,13 @@ class Memory:
|
||||
# Privacy filter
|
||||
if not include_private:
|
||||
raw = [
|
||||
(r, s) for r, s in raw
|
||||
(r, s)
|
||||
for r, s in raw
|
||||
if not r.private or r.source == source
|
||||
]
|
||||
results = []
|
||||
for r, s in raw:
|
||||
composite, reasons = compute_composite_score(
|
||||
r, s, self._config
|
||||
)
|
||||
composite, reasons = compute_composite_score(r, s, self._config)
|
||||
results.append(
|
||||
MemoryMatch(
|
||||
record=r,
|
||||
@@ -739,7 +771,9 @@ class Memory:
|
||||
limit: Maximum number of records to return.
|
||||
offset: Number of records to skip (for pagination).
|
||||
"""
|
||||
return self._storage.list_records(scope_prefix=scope, limit=limit, offset=offset)
|
||||
return self._storage.list_records(
|
||||
scope_prefix=scope, limit=limit, offset=offset
|
||||
)
|
||||
|
||||
def info(self, path: str = "/") -> ScopeInfo:
|
||||
"""Return scope info for path."""
|
||||
@@ -781,7 +815,7 @@ class Memory:
|
||||
importance: float | None = None,
|
||||
source: str | None = None,
|
||||
private: bool = False,
|
||||
) -> MemoryRecord:
|
||||
) -> MemoryRecord | None:
|
||||
"""Async remember: delegates to sync for now."""
|
||||
return self.remember(
|
||||
content,
|
||||
|
||||
@@ -216,6 +216,10 @@ def build_embedder_from_dict(
|
||||
def build_embedder_from_dict(spec: ONNXProviderSpec) -> ONNXMiniLM_L6_V2: ...
|
||||
|
||||
|
||||
@overload
|
||||
def build_embedder_from_dict(spec: dict[str, Any]) -> EmbeddingFunction[Any]: ...
|
||||
|
||||
|
||||
def build_embedder_from_dict(spec): # type: ignore[no-untyped-def]
|
||||
"""Build an embedding function instance from a dictionary specification.
|
||||
|
||||
@@ -341,6 +345,10 @@ def build_embedder(spec: Text2VecProviderSpec) -> Text2VecEmbeddingFunction: ...
|
||||
def build_embedder(spec: ONNXProviderSpec) -> ONNXMiniLM_L6_V2: ...
|
||||
|
||||
|
||||
@overload
|
||||
def build_embedder(spec: dict[str, Any]) -> EmbeddingFunction[Any]: ...
|
||||
|
||||
|
||||
def build_embedder(spec): # type: ignore[no-untyped-def]
|
||||
"""Build an embedding function from either a provider spec or a provider instance.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from concurrent.futures import Future
|
||||
from copy import copy as shallow_copy
|
||||
import datetime
|
||||
@@ -585,16 +586,29 @@ class Task(BaseModel):
|
||||
|
||||
self._post_agent_execution(agent)
|
||||
|
||||
if not self._guardrails and not self._guardrail:
|
||||
if isinstance(result, BaseModel):
|
||||
raw = result.model_dump_json()
|
||||
if self.output_pydantic:
|
||||
pydantic_output = result
|
||||
json_output = None
|
||||
elif self.output_json:
|
||||
pydantic_output = None
|
||||
json_output = result.model_dump()
|
||||
else:
|
||||
pydantic_output = None
|
||||
json_output = None
|
||||
elif not self._guardrails and not self._guardrail:
|
||||
raw = result
|
||||
pydantic_output, json_output = self._export_output(result)
|
||||
else:
|
||||
raw = result
|
||||
pydantic_output, json_output = None, None
|
||||
|
||||
task_output = TaskOutput(
|
||||
name=self.name or self.description,
|
||||
description=self.description,
|
||||
expected_output=self.expected_output,
|
||||
raw=result,
|
||||
raw=raw,
|
||||
pydantic=pydantic_output,
|
||||
json_dict=json_output,
|
||||
agent=agent.role,
|
||||
@@ -624,11 +638,15 @@ class Task(BaseModel):
|
||||
self.end_time = datetime.datetime.now()
|
||||
|
||||
if self.callback:
|
||||
self.callback(self.output)
|
||||
cb_result = self.callback(self.output)
|
||||
if inspect.isawaitable(cb_result):
|
||||
await cb_result
|
||||
|
||||
crew = self.agent.crew # type: ignore[union-attr]
|
||||
if crew and crew.task_callback and crew.task_callback != self.callback:
|
||||
crew.task_callback(self.output)
|
||||
cb_result = crew.task_callback(self.output)
|
||||
if inspect.isawaitable(cb_result):
|
||||
await cb_result
|
||||
|
||||
if self.output_file:
|
||||
content = (
|
||||
@@ -682,16 +700,29 @@ class Task(BaseModel):
|
||||
|
||||
self._post_agent_execution(agent)
|
||||
|
||||
if not self._guardrails and not self._guardrail:
|
||||
if isinstance(result, BaseModel):
|
||||
raw = result.model_dump_json()
|
||||
if self.output_pydantic:
|
||||
pydantic_output = result
|
||||
json_output = None
|
||||
elif self.output_json:
|
||||
pydantic_output = None
|
||||
json_output = result.model_dump()
|
||||
else:
|
||||
pydantic_output = None
|
||||
json_output = None
|
||||
elif not self._guardrails and not self._guardrail:
|
||||
raw = result
|
||||
pydantic_output, json_output = self._export_output(result)
|
||||
else:
|
||||
raw = result
|
||||
pydantic_output, json_output = None, None
|
||||
|
||||
task_output = TaskOutput(
|
||||
name=self.name or self.description,
|
||||
description=self.description,
|
||||
expected_output=self.expected_output,
|
||||
raw=result,
|
||||
raw=raw,
|
||||
pydantic=pydantic_output,
|
||||
json_dict=json_output,
|
||||
agent=agent.role,
|
||||
@@ -722,11 +753,15 @@ class Task(BaseModel):
|
||||
self.end_time = datetime.datetime.now()
|
||||
|
||||
if self.callback:
|
||||
self.callback(self.output)
|
||||
cb_result = self.callback(self.output)
|
||||
if inspect.iscoroutine(cb_result):
|
||||
asyncio.run(cb_result)
|
||||
|
||||
crew = self.agent.crew # type: ignore[union-attr]
|
||||
if crew and crew.task_callback and crew.task_callback != self.callback:
|
||||
crew.task_callback(self.output)
|
||||
cb_result = crew.task_callback(self.output)
|
||||
if inspect.iscoroutine(cb_result):
|
||||
asyncio.run(cb_result)
|
||||
|
||||
if self.output_file:
|
||||
content = (
|
||||
|
||||
@@ -150,14 +150,38 @@ class BaseTool(BaseModel, ABC):
|
||||
|
||||
super().model_post_init(__context)
|
||||
|
||||
def _validate_kwargs(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Validate keyword arguments against args_schema if present.
|
||||
|
||||
Args:
|
||||
kwargs: The keyword arguments to validate.
|
||||
|
||||
Returns:
|
||||
Validated (and possibly coerced) keyword arguments.
|
||||
|
||||
Raises:
|
||||
ValueError: If validation against args_schema fails.
|
||||
"""
|
||||
if self.args_schema is not None and self.args_schema.model_fields:
|
||||
try:
|
||||
validated = self.args_schema.model_validate(kwargs)
|
||||
return validated.model_dump()
|
||||
except Exception as e:
|
||||
raise ValueError(
|
||||
f"Tool '{self.name}' arguments validation failed: {e}"
|
||||
) from e
|
||||
return kwargs
|
||||
|
||||
def run(
|
||||
self,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
if not args:
|
||||
kwargs = self._validate_kwargs(kwargs)
|
||||
|
||||
result = self._run(*args, **kwargs)
|
||||
|
||||
# If _run is async, we safely run it
|
||||
if asyncio.iscoroutine(result):
|
||||
result = asyncio.run(result)
|
||||
|
||||
@@ -179,6 +203,8 @@ class BaseTool(BaseModel, ABC):
|
||||
Returns:
|
||||
The result of the tool execution.
|
||||
"""
|
||||
if not args:
|
||||
kwargs = self._validate_kwargs(kwargs)
|
||||
result = await self._arun(*args, **kwargs)
|
||||
self.current_usage_count += 1
|
||||
return result
|
||||
@@ -331,6 +357,9 @@ class Tool(BaseTool, Generic[P, R]):
|
||||
Returns:
|
||||
The result of the tool execution.
|
||||
"""
|
||||
if not args:
|
||||
kwargs = self._validate_kwargs(kwargs) # type: ignore[assignment]
|
||||
|
||||
result = self.func(*args, **kwargs)
|
||||
|
||||
if asyncio.iscoroutine(result):
|
||||
@@ -361,6 +390,8 @@ class Tool(BaseTool, Generic[P, R]):
|
||||
Returns:
|
||||
The result of the tool execution.
|
||||
"""
|
||||
if not args:
|
||||
kwargs = self._validate_kwargs(kwargs) # type: ignore[assignment]
|
||||
result = await self._arun(*args, **kwargs)
|
||||
self.current_usage_count += 1
|
||||
return result
|
||||
|
||||
@@ -27,14 +27,16 @@ class MCPNativeTool(BaseTool):
|
||||
tool_name: str,
|
||||
tool_schema: dict[str, Any],
|
||||
server_name: str,
|
||||
original_tool_name: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize native MCP tool.
|
||||
|
||||
Args:
|
||||
mcp_client: MCPClient instance with active session.
|
||||
tool_name: Original name of the tool on the MCP server.
|
||||
tool_name: Name of the tool (may be prefixed).
|
||||
tool_schema: Schema information for the tool.
|
||||
server_name: Name of the MCP server for prefixing.
|
||||
original_tool_name: Original name of the tool on the MCP server.
|
||||
"""
|
||||
# Create tool name with server prefix to avoid conflicts
|
||||
prefixed_name = f"{server_name}_{tool_name}"
|
||||
@@ -57,7 +59,7 @@ class MCPNativeTool(BaseTool):
|
||||
|
||||
# Set instance attributes after super().__init__
|
||||
self._mcp_client = mcp_client
|
||||
self._original_tool_name = tool_name
|
||||
self._original_tool_name = original_tool_name or tool_name
|
||||
self._server_name = server_name
|
||||
# self._logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -20,14 +20,6 @@ class RecallMemorySchema(BaseModel):
|
||||
"or multiple items to search for several things at once."
|
||||
),
|
||||
)
|
||||
scope: str | None = Field(
|
||||
default=None,
|
||||
description="Optional scope to narrow the search (e.g. /project/alpha)",
|
||||
)
|
||||
depth: str = Field(
|
||||
default="shallow",
|
||||
description="'shallow' for fast vector search, 'deep' for LLM-analyzed retrieval",
|
||||
)
|
||||
|
||||
|
||||
class RecallMemoryTool(BaseTool):
|
||||
@@ -41,32 +33,27 @@ class RecallMemoryTool(BaseTool):
|
||||
def _run(
|
||||
self,
|
||||
queries: list[str] | str,
|
||||
scope: str | None = None,
|
||||
depth: str = "shallow",
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""Search memory for relevant information.
|
||||
|
||||
Args:
|
||||
queries: One or more search queries (string or list of strings).
|
||||
scope: Optional scope prefix to narrow the search.
|
||||
depth: "shallow" for fast vector search, "deep" for LLM-analyzed retrieval.
|
||||
|
||||
Returns:
|
||||
Formatted string of matching memories, or a message if none found.
|
||||
"""
|
||||
if isinstance(queries, str):
|
||||
queries = [queries]
|
||||
actual_depth = depth if depth in ("shallow", "deep") else "shallow"
|
||||
|
||||
all_lines: list[str] = []
|
||||
seen_ids: set[str] = set()
|
||||
for query in queries:
|
||||
matches = self.memory.recall(query, scope=scope, limit=5, depth=actual_depth)
|
||||
matches = self.memory.recall(query)
|
||||
for m in matches:
|
||||
if m.record.id not in seen_ids:
|
||||
seen_ids.add(m.record.id)
|
||||
all_lines.append(f"- (score={m.score:.2f}) {m.record.content}")
|
||||
all_lines.append(m.format())
|
||||
|
||||
if not all_lines:
|
||||
return "No relevant memories found."
|
||||
@@ -117,20 +104,28 @@ class RememberTool(BaseTool):
|
||||
def create_memory_tools(memory: Any) -> list[BaseTool]:
|
||||
"""Create Recall and Remember tools for the given memory instance.
|
||||
|
||||
When memory is read-only (``_read_only=True``), only the RecallMemoryTool
|
||||
is returned — the RememberTool is omitted so agents are never offered a
|
||||
save capability they cannot use.
|
||||
|
||||
Args:
|
||||
memory: A Memory, MemoryScope, or MemorySlice instance.
|
||||
|
||||
Returns:
|
||||
List containing a RecallMemoryTool and a RememberTool.
|
||||
List containing a RecallMemoryTool and, if not read-only, a RememberTool.
|
||||
"""
|
||||
i18n = get_i18n()
|
||||
return [
|
||||
tools: list[BaseTool] = [
|
||||
RecallMemoryTool(
|
||||
memory=memory,
|
||||
description=i18n.tools("recall_memory"),
|
||||
),
|
||||
RememberTool(
|
||||
memory=memory,
|
||||
description=i18n.tools("save_to_memory"),
|
||||
),
|
||||
]
|
||||
if not getattr(memory, "_read_only", False):
|
||||
tools.append(
|
||||
RememberTool(
|
||||
memory=memory,
|
||||
description=i18n.tools("save_to_memory"),
|
||||
)
|
||||
)
|
||||
return tools
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from collections.abc import Callable, Sequence
|
||||
import concurrent.futures
|
||||
import inspect
|
||||
import json
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any, Final, Literal, TypedDict
|
||||
@@ -167,7 +168,9 @@ def convert_tools_to_openai_schema(
|
||||
parameters: dict[str, Any] = {}
|
||||
if hasattr(tool, "args_schema") and tool.args_schema is not None:
|
||||
try:
|
||||
schema_output = generate_model_description(tool.args_schema)
|
||||
schema_output = generate_model_description(
|
||||
tool.args_schema, strip_null_types=False
|
||||
)
|
||||
parameters = schema_output.get("json_schema", {}).get("schema", {})
|
||||
# Remove title and description from schema root as they're redundant
|
||||
parameters.pop("title", None)
|
||||
@@ -501,7 +504,9 @@ def handle_agent_action_core(
|
||||
- TODO: Remove messages parameter and its usage.
|
||||
"""
|
||||
if step_callback:
|
||||
step_callback(tool_result)
|
||||
cb_result = step_callback(tool_result)
|
||||
if inspect.iscoroutine(cb_result):
|
||||
asyncio.run(cb_result)
|
||||
|
||||
formatted_answer.text += f"\nObservation: {tool_result.result}"
|
||||
formatted_answer.result = tool_result.result
|
||||
@@ -1143,6 +1148,36 @@ def extract_tool_call_info(
|
||||
return None
|
||||
|
||||
|
||||
def parse_tool_call_args(
|
||||
func_args: dict[str, Any] | str,
|
||||
func_name: str,
|
||||
call_id: str,
|
||||
original_tool: Any = None,
|
||||
) -> tuple[dict[str, Any], None] | tuple[None, dict[str, Any]]:
|
||||
"""Parse tool call arguments from a JSON string or dict.
|
||||
|
||||
Returns:
|
||||
``(args_dict, None)`` on success, or ``(None, error_result)`` on
|
||||
JSON parse failure where ``error_result`` is a ready-to-return dict
|
||||
with the same shape as ``_execute_single_native_tool_call`` return values.
|
||||
"""
|
||||
if isinstance(func_args, str):
|
||||
try:
|
||||
return json.loads(func_args), None
|
||||
except json.JSONDecodeError as e:
|
||||
return None, {
|
||||
"call_id": call_id,
|
||||
"func_name": func_name,
|
||||
"result": (
|
||||
f"Error: Failed to parse tool arguments as JSON: {e}. "
|
||||
f"Please provide valid JSON arguments for the '{func_name}' tool."
|
||||
),
|
||||
"from_cache": False,
|
||||
"original_tool": original_tool,
|
||||
}
|
||||
return func_args, None
|
||||
|
||||
|
||||
def _setup_before_llm_call_hooks(
|
||||
executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None,
|
||||
printer: Printer,
|
||||
|
||||
@@ -69,7 +69,7 @@ def create_llm(
|
||||
UNACCEPTED_ATTRIBUTES: Final[list[str]] = [
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
"AWS_REGION_NAME",
|
||||
"AWS_DEFAULT_REGION",
|
||||
]
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ def _llm_via_environment_or_fallback() -> LLM | None:
|
||||
unaccepted_attributes = [
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
"AWS_REGION_NAME",
|
||||
"AWS_DEFAULT_REGION",
|
||||
]
|
||||
set_provider = model_name.partition("/")[0] if "/" in model_name else "openai"
|
||||
|
||||
|
||||
@@ -417,7 +417,11 @@ def strip_null_from_types(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
return schema
|
||||
|
||||
|
||||
def generate_model_description(model: type[BaseModel]) -> ModelDescription:
|
||||
def generate_model_description(
|
||||
model: type[BaseModel],
|
||||
*,
|
||||
strip_null_types: bool = True,
|
||||
) -> ModelDescription:
|
||||
"""Generate JSON schema description of a Pydantic model.
|
||||
|
||||
This function takes a Pydantic model class and returns its JSON schema,
|
||||
@@ -426,6 +430,9 @@ def generate_model_description(model: type[BaseModel]) -> ModelDescription:
|
||||
|
||||
Args:
|
||||
model: A Pydantic model class.
|
||||
strip_null_types: When ``True`` (default), remove ``null`` from
|
||||
``anyOf`` / ``type`` arrays. Set to ``False`` to allow sending ``null`` for
|
||||
optional fields.
|
||||
|
||||
Returns:
|
||||
A ModelDescription with JSON schema representation of the model.
|
||||
@@ -442,7 +449,9 @@ def generate_model_description(model: type[BaseModel]) -> ModelDescription:
|
||||
json_schema = fix_discriminator_mappings(json_schema)
|
||||
json_schema = convert_oneof_to_anyof(json_schema)
|
||||
json_schema = ensure_all_properties_required(json_schema)
|
||||
json_schema = strip_null_from_types(json_schema)
|
||||
|
||||
if strip_null_types:
|
||||
json_schema = strip_null_from_types(json_schema)
|
||||
|
||||
return {
|
||||
"type": "json_schema",
|
||||
@@ -482,10 +491,66 @@ FORMAT_TYPE_MAP: dict[str, type[Any]] = {
|
||||
}
|
||||
|
||||
|
||||
def build_rich_field_description(prop_schema: dict[str, Any]) -> str:
|
||||
"""Build a comprehensive field description including constraints.
|
||||
|
||||
Embeds format, enum, pattern, min/max, and example constraints into the
|
||||
description text so that LLMs can understand tool parameter requirements
|
||||
without inspecting the raw JSON Schema.
|
||||
|
||||
Args:
|
||||
prop_schema: Property schema with description and constraints.
|
||||
|
||||
Returns:
|
||||
Enhanced description with format, enum, and other constraints.
|
||||
"""
|
||||
parts: list[str] = []
|
||||
|
||||
description = prop_schema.get("description", "")
|
||||
if description:
|
||||
parts.append(description)
|
||||
|
||||
format_type = prop_schema.get("format")
|
||||
if format_type:
|
||||
parts.append(f"Format: {format_type}")
|
||||
|
||||
enum_values = prop_schema.get("enum")
|
||||
if enum_values:
|
||||
enum_str = ", ".join(repr(v) for v in enum_values)
|
||||
parts.append(f"Allowed values: [{enum_str}]")
|
||||
|
||||
pattern = prop_schema.get("pattern")
|
||||
if pattern:
|
||||
parts.append(f"Pattern: {pattern}")
|
||||
|
||||
minimum = prop_schema.get("minimum")
|
||||
maximum = prop_schema.get("maximum")
|
||||
if minimum is not None:
|
||||
parts.append(f"Minimum: {minimum}")
|
||||
if maximum is not None:
|
||||
parts.append(f"Maximum: {maximum}")
|
||||
|
||||
min_length = prop_schema.get("minLength")
|
||||
max_length = prop_schema.get("maxLength")
|
||||
if min_length is not None:
|
||||
parts.append(f"Min length: {min_length}")
|
||||
if max_length is not None:
|
||||
parts.append(f"Max length: {max_length}")
|
||||
|
||||
examples = prop_schema.get("examples")
|
||||
if examples:
|
||||
examples_str = ", ".join(repr(e) for e in examples[:3])
|
||||
parts.append(f"Examples: {examples_str}")
|
||||
|
||||
return ". ".join(parts) if parts else ""
|
||||
|
||||
|
||||
def create_model_from_schema( # type: ignore[no-any-unimported]
|
||||
json_schema: dict[str, Any],
|
||||
*,
|
||||
root_schema: dict[str, Any] | None = None,
|
||||
model_name: str | None = None,
|
||||
enrich_descriptions: bool = False,
|
||||
__config__: ConfigDict | None = None,
|
||||
__base__: type[BaseModel] | None = None,
|
||||
__module__: str = __name__,
|
||||
@@ -503,6 +568,13 @@ def create_model_from_schema( # type: ignore[no-any-unimported]
|
||||
json_schema: A dictionary representing the JSON schema.
|
||||
root_schema: The root schema containing $defs. If not provided, the
|
||||
current schema is treated as the root schema.
|
||||
model_name: Override for the model name. If not provided, the schema
|
||||
``title`` field is used, falling back to ``"DynamicModel"``.
|
||||
enrich_descriptions: When True, augment field descriptions with
|
||||
constraint info (format, enum, pattern, min/max, examples) via
|
||||
:func:`build_rich_field_description`. Useful for LLM-facing tool
|
||||
schemas where constraints in the description help the model
|
||||
understand parameter requirements.
|
||||
__config__: Pydantic configuration for the generated model.
|
||||
__base__: Base class for the generated model. Defaults to BaseModel.
|
||||
__module__: Module name for the generated model class.
|
||||
@@ -539,10 +611,14 @@ def create_model_from_schema( # type: ignore[no-any-unimported]
|
||||
if "title" not in json_schema and "title" in (root_schema or {}):
|
||||
json_schema["title"] = (root_schema or {}).get("title")
|
||||
|
||||
model_name = json_schema.get("title") or "DynamicModel"
|
||||
effective_name = model_name or json_schema.get("title") or "DynamicModel"
|
||||
field_definitions = {
|
||||
name: _json_schema_to_pydantic_field(
|
||||
name, prop, json_schema.get("required", []), effective_root
|
||||
name,
|
||||
prop,
|
||||
json_schema.get("required", []),
|
||||
effective_root,
|
||||
enrich_descriptions=enrich_descriptions,
|
||||
)
|
||||
for name, prop in (json_schema.get("properties", {}) or {}).items()
|
||||
}
|
||||
@@ -550,7 +626,7 @@ def create_model_from_schema( # type: ignore[no-any-unimported]
|
||||
effective_config = __config__ or ConfigDict(extra="forbid")
|
||||
|
||||
return create_model_base(
|
||||
model_name,
|
||||
effective_name,
|
||||
__config__=effective_config,
|
||||
__base__=__base__,
|
||||
__module__=__module__,
|
||||
@@ -565,6 +641,8 @@ def _json_schema_to_pydantic_field(
|
||||
json_schema: dict[str, Any],
|
||||
required: list[str],
|
||||
root_schema: dict[str, Any],
|
||||
*,
|
||||
enrich_descriptions: bool = False,
|
||||
) -> Any:
|
||||
"""Convert a JSON schema property to a Pydantic field definition.
|
||||
|
||||
@@ -573,20 +651,29 @@ def _json_schema_to_pydantic_field(
|
||||
json_schema: The JSON schema for this field.
|
||||
required: List of required field names.
|
||||
root_schema: The root schema for resolving $ref.
|
||||
enrich_descriptions: When True, embed constraints in the description.
|
||||
|
||||
Returns:
|
||||
A tuple of (type, Field) for use with create_model.
|
||||
"""
|
||||
type_ = _json_schema_to_pydantic_type(json_schema, root_schema, name_=name.title())
|
||||
description = json_schema.get("description")
|
||||
examples = json_schema.get("examples")
|
||||
type_ = _json_schema_to_pydantic_type(
|
||||
json_schema, root_schema, name_=name.title(), enrich_descriptions=enrich_descriptions
|
||||
)
|
||||
is_required = name in required
|
||||
|
||||
field_params: dict[str, Any] = {}
|
||||
schema_extra: dict[str, Any] = {}
|
||||
|
||||
if description:
|
||||
field_params["description"] = description
|
||||
if enrich_descriptions:
|
||||
rich_desc = build_rich_field_description(json_schema)
|
||||
if rich_desc:
|
||||
field_params["description"] = rich_desc
|
||||
else:
|
||||
description = json_schema.get("description")
|
||||
if description:
|
||||
field_params["description"] = description
|
||||
|
||||
examples = json_schema.get("examples")
|
||||
if examples:
|
||||
schema_extra["examples"] = examples
|
||||
|
||||
@@ -702,6 +789,7 @@ def _json_schema_to_pydantic_type(
|
||||
root_schema: dict[str, Any],
|
||||
*,
|
||||
name_: str | None = None,
|
||||
enrich_descriptions: bool = False,
|
||||
) -> Any:
|
||||
"""Convert a JSON schema to a Python/Pydantic type.
|
||||
|
||||
@@ -709,6 +797,7 @@ def _json_schema_to_pydantic_type(
|
||||
json_schema: The JSON schema to convert.
|
||||
root_schema: The root schema for resolving $ref.
|
||||
name_: Optional name for nested models.
|
||||
enrich_descriptions: Propagated to nested model creation.
|
||||
|
||||
Returns:
|
||||
A Python type corresponding to the JSON schema.
|
||||
@@ -716,7 +805,9 @@ def _json_schema_to_pydantic_type(
|
||||
ref = json_schema.get("$ref")
|
||||
if ref:
|
||||
ref_schema = _resolve_ref(ref, root_schema)
|
||||
return _json_schema_to_pydantic_type(ref_schema, root_schema, name_=name_)
|
||||
return _json_schema_to_pydantic_type(
|
||||
ref_schema, root_schema, name_=name_, enrich_descriptions=enrich_descriptions
|
||||
)
|
||||
|
||||
enum_values = json_schema.get("enum")
|
||||
if enum_values:
|
||||
@@ -731,7 +822,10 @@ def _json_schema_to_pydantic_type(
|
||||
if any_of_schemas:
|
||||
any_of_types = [
|
||||
_json_schema_to_pydantic_type(
|
||||
schema, root_schema, name_=f"{name_ or 'Union'}Option{i}"
|
||||
schema,
|
||||
root_schema,
|
||||
name_=f"{name_ or 'Union'}Option{i}",
|
||||
enrich_descriptions=enrich_descriptions,
|
||||
)
|
||||
for i, schema in enumerate(any_of_schemas)
|
||||
]
|
||||
@@ -741,10 +835,14 @@ def _json_schema_to_pydantic_type(
|
||||
if all_of_schemas:
|
||||
if len(all_of_schemas) == 1:
|
||||
return _json_schema_to_pydantic_type(
|
||||
all_of_schemas[0], root_schema, name_=name_
|
||||
all_of_schemas[0], root_schema, name_=name_,
|
||||
enrich_descriptions=enrich_descriptions,
|
||||
)
|
||||
merged = _merge_all_of_schemas(all_of_schemas, root_schema)
|
||||
return _json_schema_to_pydantic_type(merged, root_schema, name_=name_)
|
||||
return _json_schema_to_pydantic_type(
|
||||
merged, root_schema, name_=name_,
|
||||
enrich_descriptions=enrich_descriptions,
|
||||
)
|
||||
|
||||
type_ = json_schema.get("type")
|
||||
|
||||
@@ -760,7 +858,8 @@ def _json_schema_to_pydantic_type(
|
||||
items_schema = json_schema.get("items")
|
||||
if items_schema:
|
||||
item_type = _json_schema_to_pydantic_type(
|
||||
items_schema, root_schema, name_=name_
|
||||
items_schema, root_schema, name_=name_,
|
||||
enrich_descriptions=enrich_descriptions,
|
||||
)
|
||||
return list[item_type] # type: ignore[valid-type]
|
||||
return list
|
||||
@@ -770,7 +869,10 @@ def _json_schema_to_pydantic_type(
|
||||
json_schema_ = json_schema.copy()
|
||||
if json_schema_.get("title") is None:
|
||||
json_schema_["title"] = name_ or "DynamicModel"
|
||||
return create_model_from_schema(json_schema_, root_schema=root_schema)
|
||||
return create_model_from_schema(
|
||||
json_schema_, root_schema=root_schema,
|
||||
enrich_descriptions=enrich_descriptions,
|
||||
)
|
||||
return dict
|
||||
if type_ == "null":
|
||||
return None
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -291,6 +291,46 @@ class TestAsyncAgentExecutor:
|
||||
assert max_concurrent > 1, f"Expected concurrent execution, max concurrent was {max_concurrent}"
|
||||
|
||||
|
||||
class TestInvokeStepCallback:
|
||||
"""Tests for _invoke_step_callback with sync and async callbacks."""
|
||||
|
||||
def test_invoke_step_callback_with_sync_callback(
|
||||
self, executor: CrewAgentExecutor
|
||||
) -> None:
|
||||
"""Test that a sync step callback is called normally."""
|
||||
callback = Mock()
|
||||
executor.step_callback = callback
|
||||
answer = AgentFinish(thought="thinking", output="test", text="final")
|
||||
|
||||
executor._invoke_step_callback(answer)
|
||||
|
||||
callback.assert_called_once_with(answer)
|
||||
|
||||
def test_invoke_step_callback_with_async_callback(
|
||||
self, executor: CrewAgentExecutor
|
||||
) -> None:
|
||||
"""Test that an async step callback is awaited via asyncio.run."""
|
||||
async_callback = AsyncMock()
|
||||
executor.step_callback = async_callback
|
||||
answer = AgentFinish(thought="thinking", output="test", text="final")
|
||||
|
||||
with patch("crewai.agents.crew_agent_executor.asyncio.run") as mock_run:
|
||||
executor._invoke_step_callback(answer)
|
||||
|
||||
async_callback.assert_called_once_with(answer)
|
||||
mock_run.assert_called_once()
|
||||
|
||||
def test_invoke_step_callback_with_none(
|
||||
self, executor: CrewAgentExecutor
|
||||
) -> None:
|
||||
"""Test that no error is raised when step_callback is None."""
|
||||
executor.step_callback = None
|
||||
answer = AgentFinish(thought="thinking", output="test", text="final")
|
||||
|
||||
# Should not raise
|
||||
executor._invoke_step_callback(answer)
|
||||
|
||||
|
||||
class TestAsyncLLMResponseHelper:
|
||||
"""Tests for aget_llm_response helper function."""
|
||||
|
||||
|
||||
@@ -659,7 +659,7 @@ def test_agent_kickoff_with_platform_tools(mock_get, mock_post):
|
||||
|
||||
|
||||
@patch.dict("os.environ", {"EXA_API_KEY": "test_exa_key"})
|
||||
@patch("crewai.agent.Agent._get_external_mcp_tools")
|
||||
@patch("crewai.agent.Agent.get_mcp_tools")
|
||||
@pytest.mark.vcr()
|
||||
def test_agent_kickoff_with_mcp_tools(mock_get_mcp_tools):
|
||||
"""Test that Agent.kickoff() properly integrates MCP tools with LiteAgent"""
|
||||
@@ -691,7 +691,7 @@ def test_agent_kickoff_with_mcp_tools(mock_get_mcp_tools):
|
||||
assert result.raw is not None
|
||||
|
||||
# Verify MCP tools were retrieved
|
||||
mock_get_mcp_tools.assert_called_once_with("https://mcp.exa.ai/mcp?api_key=test_exa_key&profile=research")
|
||||
mock_get_mcp_tools.assert_called_once_with(["https://mcp.exa.ai/mcp?api_key=test_exa_key&profile=research"])
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -1136,6 +1136,7 @@ def test_lite_agent_memory_instance_recall_and_save_called():
|
||||
successful_requests=1,
|
||||
)
|
||||
mock_memory = Mock()
|
||||
mock_memory._read_only = False
|
||||
mock_memory.recall.return_value = []
|
||||
mock_memory.extract_memories.return_value = ["Fact one.", "Fact two."]
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import os
|
||||
import threading
|
||||
import time
|
||||
from collections import Counter
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -1129,3 +1129,150 @@ class TestMaxUsageCountWithNativeToolCalling:
|
||||
# Verify the requested calls occurred while keeping usage bounded.
|
||||
assert tool.current_usage_count >= 2
|
||||
assert tool.current_usage_count <= tool.max_usage_count
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# JSON Parse Error Handling Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestNativeToolCallingJsonParseError:
|
||||
"""Tests that malformed JSON tool arguments produce clear errors
|
||||
instead of silently dropping all arguments."""
|
||||
|
||||
def _make_executor(self, tools: list[BaseTool]) -> "CrewAgentExecutor":
|
||||
"""Create a minimal CrewAgentExecutor with mocked dependencies."""
|
||||
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||
from crewai.tools.base_tool import to_langchain
|
||||
|
||||
structured_tools = to_langchain(tools)
|
||||
mock_agent = Mock()
|
||||
mock_agent.key = "test_agent"
|
||||
mock_agent.role = "tester"
|
||||
mock_agent.verbose = False
|
||||
mock_agent.fingerprint = None
|
||||
mock_agent.tools_results = []
|
||||
|
||||
mock_task = Mock()
|
||||
mock_task.name = "test"
|
||||
mock_task.description = "test"
|
||||
mock_task.id = "test-id"
|
||||
|
||||
executor = object.__new__(CrewAgentExecutor)
|
||||
executor.agent = mock_agent
|
||||
executor.task = mock_task
|
||||
executor.crew = Mock()
|
||||
executor.tools = structured_tools
|
||||
executor.original_tools = tools
|
||||
executor.tools_handler = None
|
||||
executor._printer = Mock()
|
||||
executor.messages = []
|
||||
|
||||
return executor
|
||||
|
||||
def test_malformed_json_returns_parse_error(self) -> None:
|
||||
"""Malformed JSON args must return a descriptive error, not silently become {}."""
|
||||
|
||||
class CodeTool(BaseTool):
|
||||
name: str = "execute_code"
|
||||
description: str = "Run code"
|
||||
|
||||
def _run(self, code: str) -> str:
|
||||
return f"ran: {code}"
|
||||
|
||||
tool = CodeTool()
|
||||
executor = self._make_executor([tool])
|
||||
|
||||
from crewai.utilities.agent_utils import convert_tools_to_openai_schema
|
||||
_, available_functions = convert_tools_to_openai_schema([tool])
|
||||
|
||||
malformed_json = '{"code": "print("hello")"}'
|
||||
|
||||
result = executor._execute_single_native_tool_call(
|
||||
call_id="call_123",
|
||||
func_name="execute_code",
|
||||
func_args=malformed_json,
|
||||
available_functions=available_functions,
|
||||
)
|
||||
|
||||
assert "Failed to parse tool arguments as JSON" in result["result"]
|
||||
assert tool.current_usage_count == 0
|
||||
|
||||
def test_valid_json_still_executes_normally(self) -> None:
|
||||
"""Valid JSON args should execute the tool as before."""
|
||||
|
||||
class CodeTool(BaseTool):
|
||||
name: str = "execute_code"
|
||||
description: str = "Run code"
|
||||
|
||||
def _run(self, code: str) -> str:
|
||||
return f"ran: {code}"
|
||||
|
||||
tool = CodeTool()
|
||||
executor = self._make_executor([tool])
|
||||
|
||||
from crewai.utilities.agent_utils import convert_tools_to_openai_schema
|
||||
_, available_functions = convert_tools_to_openai_schema([tool])
|
||||
|
||||
valid_json = '{"code": "print(1)"}'
|
||||
|
||||
result = executor._execute_single_native_tool_call(
|
||||
call_id="call_456",
|
||||
func_name="execute_code",
|
||||
func_args=valid_json,
|
||||
available_functions=available_functions,
|
||||
)
|
||||
|
||||
assert result["result"] == "ran: print(1)"
|
||||
|
||||
def test_dict_args_bypass_json_parsing(self) -> None:
|
||||
"""When func_args is already a dict, no JSON parsing occurs."""
|
||||
|
||||
class CodeTool(BaseTool):
|
||||
name: str = "execute_code"
|
||||
description: str = "Run code"
|
||||
|
||||
def _run(self, code: str) -> str:
|
||||
return f"ran: {code}"
|
||||
|
||||
tool = CodeTool()
|
||||
executor = self._make_executor([tool])
|
||||
|
||||
from crewai.utilities.agent_utils import convert_tools_to_openai_schema
|
||||
_, available_functions = convert_tools_to_openai_schema([tool])
|
||||
|
||||
result = executor._execute_single_native_tool_call(
|
||||
call_id="call_789",
|
||||
func_name="execute_code",
|
||||
func_args={"code": "x = 42"},
|
||||
available_functions=available_functions,
|
||||
)
|
||||
|
||||
assert result["result"] == "ran: x = 42"
|
||||
|
||||
def test_schema_validation_catches_missing_args_on_native_path(self) -> None:
|
||||
"""The native function calling path should now enforce args_schema,
|
||||
catching missing required fields before _run is called."""
|
||||
|
||||
class StrictTool(BaseTool):
|
||||
name: str = "strict_tool"
|
||||
description: str = "A tool with required args"
|
||||
|
||||
def _run(self, code: str, language: str) -> str:
|
||||
return f"{language}: {code}"
|
||||
|
||||
tool = StrictTool()
|
||||
executor = self._make_executor([tool])
|
||||
|
||||
from crewai.utilities.agent_utils import convert_tools_to_openai_schema
|
||||
_, available_functions = convert_tools_to_openai_schema([tool])
|
||||
|
||||
result = executor._execute_single_native_tool_call(
|
||||
call_id="call_schema",
|
||||
func_name="strict_tool",
|
||||
func_args={"code": "print(1)"},
|
||||
available_functions=available_functions,
|
||||
)
|
||||
|
||||
assert "Error" in result["result"]
|
||||
assert "validation failed" in result["result"].lower() or "missing" in result["result"].lower()
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"contents": [{"parts": [{"text": "\nCurrent Task: Calculate 15 + 27 using
|
||||
your add_numbers tool. Report the result.\n\nThis is the expected criteria for
|
||||
your final answer: A structured calculation result\nyou MUST return the actual
|
||||
complete content as the final answer, not a summary.\nFormat your final answer
|
||||
according to the following OpenAPI schema: {\n \"properties\": {\n \"operation\":
|
||||
{\n \"description\": \"The mathematical operation performed\",\n \"title\":
|
||||
\"Operation\",\n \"type\": \"string\"\n },\n \"result\": {\n \"description\":
|
||||
\"The result of the calculation\",\n \"title\": \"Result\",\n \"type\":
|
||||
\"integer\"\n },\n \"explanation\": {\n \"description\": \"Brief
|
||||
explanation of the calculation\",\n \"title\": \"Explanation\",\n \"type\":
|
||||
\"string\"\n }\n },\n \"required\": [\n \"operation\",\n \"result\",\n \"explanation\"\n ],\n \"title\":
|
||||
\"CalculationResult\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nIMPORTANT: Preserve the original content exactly as-is. Do NOT rewrite,
|
||||
paraphrase, or modify the meaning of the content. Only structure it to match
|
||||
the schema format.\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python."}], "role": "user"}], "systemInstruction": {"parts": [{"text":
|
||||
"You are Calculator. You are a calculator assistant that uses tools to compute
|
||||
results.\nYour personal goal is: Perform calculations using available tools"}],
|
||||
"role": "user"}, "tools": [{"functionDeclarations": [{"description": "Add two
|
||||
numbers together and return the sum.", "name": "add_numbers", "parameters_json_schema":
|
||||
{"properties": {"a": {"title": "A", "type": "integer"}, "b": {"title": "B",
|
||||
"type": "integer"}}, "required": ["a", "b"], "type": "object", "additionalProperties":
|
||||
false}}, {"description": "Use this tool to provide your final structured response.
|
||||
Call this tool when you have gathered all necessary information and are ready
|
||||
to provide the final answer in the required format.", "name": "structured_output",
|
||||
"parameters_json_schema": {"properties": {"operation": {"description": "The
|
||||
mathematical operation performed", "title": "Operation", "type": "string"},
|
||||
"result": {"description": "The result of the calculation", "title": "Result",
|
||||
"type": "integer"}, "explanation": {"description": "Brief explanation of the
|
||||
calculation", "title": "Explanation", "type": "string"}}, "required": ["operation",
|
||||
"result", "explanation"], "title": "CalculationResult", "type": "object", "additionalProperties":
|
||||
false, "propertyOrdering": ["operation", "result", "explanation"]}}]}], "generationConfig":
|
||||
{"stopSequences": ["\nObservation:"]}}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- '*/*'
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '2763'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- generativelanguage.googleapis.com
|
||||
x-goog-api-client:
|
||||
- google-genai-sdk/1.49.0 gl-python/3.13.12
|
||||
x-goog-api-key:
|
||||
- X-GOOG-API-KEY-XXX
|
||||
method: POST
|
||||
uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:generateContent
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\":
|
||||
[\n {\n \"functionCall\": {\n \"name\": \"add_numbers\",\n
|
||||
\ \"args\": {\n \"a\": 15,\n \"b\":
|
||||
27\n }\n }\n }\n ],\n \"role\":
|
||||
\"model\"\n },\n \"finishReason\": \"STOP\",\n \"avgLogprobs\":
|
||||
4.3579145442760951e-06\n }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\":
|
||||
377,\n \"candidatesTokenCount\": 7,\n \"totalTokenCount\": 384,\n \"promptTokensDetails\":
|
||||
[\n {\n \"modality\": \"TEXT\",\n \"tokenCount\": 377\n
|
||||
\ }\n ],\n \"candidatesTokensDetails\": [\n {\n \"modality\":
|
||||
\"TEXT\",\n \"tokenCount\": 7\n }\n ]\n },\n \"modelVersion\":
|
||||
\"gemini-2.0-flash-001\",\n \"responseId\": \"vVefaYDSOouXjMcPicLCsQY\"\n}\n"
|
||||
headers:
|
||||
Alt-Svc:
|
||||
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
|
||||
Content-Type:
|
||||
- application/json; charset=UTF-8
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 20:12:46 GMT
|
||||
Server:
|
||||
- scaffolding on HTTPServer2
|
||||
Server-Timing:
|
||||
- gfet4t7; dur=718
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin
|
||||
- X-Origin
|
||||
- Referer
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
X-Frame-Options:
|
||||
- X-FRAME-OPTIONS-XXX
|
||||
X-XSS-Protection:
|
||||
- '0'
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"contents": [{"parts": [{"text": "\nCurrent Task: Calculate 15 + 27 using
|
||||
your add_numbers tool. Report the result.\n\nThis is the expected criteria for
|
||||
your final answer: A structured calculation result\nyou MUST return the actual
|
||||
complete content as the final answer, not a summary.\nFormat your final answer
|
||||
according to the following OpenAPI schema: {\n \"properties\": {\n \"operation\":
|
||||
{\n \"description\": \"The mathematical operation performed\",\n \"title\":
|
||||
\"Operation\",\n \"type\": \"string\"\n },\n \"result\": {\n \"description\":
|
||||
\"The result of the calculation\",\n \"title\": \"Result\",\n \"type\":
|
||||
\"integer\"\n },\n \"explanation\": {\n \"description\": \"Brief
|
||||
explanation of the calculation\",\n \"title\": \"Explanation\",\n \"type\":
|
||||
\"string\"\n }\n },\n \"required\": [\n \"operation\",\n \"result\",\n \"explanation\"\n ],\n \"title\":
|
||||
\"CalculationResult\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nIMPORTANT: Preserve the original content exactly as-is. Do NOT rewrite,
|
||||
paraphrase, or modify the meaning of the content. Only structure it to match
|
||||
the schema format.\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python."}], "role": "user"}, {"parts": [{"functionCall": {"args": {"a":
|
||||
15, "b": 27}, "name": "add_numbers"}}], "role": "model"}, {"parts": [{"functionResponse":
|
||||
{"name": "add_numbers", "response": {"result": 42}}}], "role": "user"}, {"parts":
|
||||
[{"text": "Analyze the tool result. If requirements are met, provide the Final
|
||||
Answer. Otherwise, call the next tool. Deliver only the answer without meta-commentary."}],
|
||||
"role": "user"}], "systemInstruction": {"parts": [{"text": "You are Calculator.
|
||||
You are a calculator assistant that uses tools to compute results.\nYour personal
|
||||
goal is: Perform calculations using available tools"}], "role": "user"}, "tools":
|
||||
[{"functionDeclarations": [{"description": "Add two numbers together and return
|
||||
the sum.", "name": "add_numbers", "parameters_json_schema": {"properties": {"a":
|
||||
{"title": "A", "type": "integer"}, "b": {"title": "B", "type": "integer"}},
|
||||
"required": ["a", "b"], "type": "object", "additionalProperties": false}}, {"description":
|
||||
"Use this tool to provide your final structured response. Call this tool when
|
||||
you have gathered all necessary information and are ready to provide the final
|
||||
answer in the required format.", "name": "structured_output", "parameters_json_schema":
|
||||
{"properties": {"operation": {"description": "The mathematical operation performed",
|
||||
"title": "Operation", "type": "string"}, "result": {"description": "The result
|
||||
of the calculation", "title": "Result", "type": "integer"}, "explanation": {"description":
|
||||
"Brief explanation of the calculation", "title": "Explanation", "type": "string"}},
|
||||
"required": ["operation", "result", "explanation"], "title": "CalculationResult",
|
||||
"type": "object", "additionalProperties": false, "propertyOrdering": ["operation",
|
||||
"result", "explanation"]}}]}], "generationConfig": {"stopSequences": ["\nObservation:"]}}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- '*/*'
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '3166'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- generativelanguage.googleapis.com
|
||||
x-goog-api-client:
|
||||
- google-genai-sdk/1.49.0 gl-python/3.13.12
|
||||
x-goog-api-key:
|
||||
- X-GOOG-API-KEY-XXX
|
||||
method: POST
|
||||
uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-001:generateContent
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\":
|
||||
[\n {\n \"functionCall\": {\n \"name\": \"structured_output\",\n
|
||||
\ \"args\": {\n \"result\": 42,\n \"explanation\":
|
||||
\"15 + 27 = 42\",\n \"operation\": \"addition\"\n }\n
|
||||
\ }\n }\n ],\n \"role\": \"model\"\n },\n
|
||||
\ \"finishReason\": \"STOP\",\n \"avgLogprobs\": -0.07498827245500353\n
|
||||
\ }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\": 421,\n \"candidatesTokenCount\":
|
||||
18,\n \"totalTokenCount\": 439,\n \"promptTokensDetails\": [\n {\n
|
||||
\ \"modality\": \"TEXT\",\n \"tokenCount\": 421\n }\n ],\n
|
||||
\ \"candidatesTokensDetails\": [\n {\n \"modality\": \"TEXT\",\n
|
||||
\ \"tokenCount\": 18\n }\n ]\n },\n \"modelVersion\": \"gemini-2.0-flash-001\",\n
|
||||
\ \"responseId\": \"vlefac7bJb6TjMcPzYWh0Ag\"\n}\n"
|
||||
headers:
|
||||
Alt-Svc:
|
||||
- h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
|
||||
Content-Type:
|
||||
- application/json; charset=UTF-8
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 20:12:47 GMT
|
||||
Server:
|
||||
- scaffolding on HTTPServer2
|
||||
Server-Timing:
|
||||
- gfet4t7; dur=774
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin
|
||||
- X-Origin
|
||||
- Referer
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
X-Frame-Options:
|
||||
- X-FRAME-OPTIONS-XXX
|
||||
X-XSS-Protection:
|
||||
- '0'
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,97 +1,120 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1394'
|
||||
- '1421'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0OJQX3eMkY3pcrZz7iSh2HHTPF\",\n \"object\": \"chat.completion\",\n \"created\": 1762380656,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal Answer: {\\\"score\\\":4}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\": 18,\n \"total_tokens\": 312,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDDzfvCsU0fZWdxFwjGh6dmaEheAW\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044427,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
276,\n \"completion_tokens\": 5,\n \"total_tokens\": 281,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 22:10:56 GMT
|
||||
- Wed, 25 Feb 2026 18:33:48 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 22:40:56 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '770'
|
||||
- '552'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '796'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '200000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '199687'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 93ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,189 +1,121 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test Backstory\nYour personal goal is: Test Goal\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: Gather information about available books on the First World War\n\nThis is the expected criteria for your final answer: A list of available books on the First World War\nyou MUST return the actual complete content as the final answer, not a summary.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"]}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Test Agent. Test Backstory\nYour
|
||||
personal goal is: Test Goal"},{"role":"user","content":"\nCurrent Task: Gather
|
||||
information about available books on the First World War\n\nThis is the expected
|
||||
criteria for your final answer: A list of available books on the First World
|
||||
War\nyou MUST return the actual complete content as the final answer, not a
|
||||
summary.\n\nProvide your complete response:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '903'
|
||||
- '465'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.68.2
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.68.2
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- '600.0'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-BReRV6HdeL9wUgmKwfAZfVjuGdpAo\",\n \"object\": \"chat.completion\",\n \"created\": 1745930017,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal Answer: Here is a comprehensive list of available books on the First World War:\\n\\n1. **\\\"The Sleepwalkers: How Europe Went to War in 1914\\\" by Christopher Clark** \\n This book delves into the complex factors that led to the outbreak of the war, offering insights into the political and social dynamics of early 20th century Europe.\\n\\n2. **\\\"A World Undone: The Story of the Great War, 1914 to 1918\\\" by G.J. Meyer** \\n Meyer's expansive narrative covers the entire war with a focus on both military strategies and the human experiences endured by soldiers and civilians alike.\\n\\n3. **\\\"All Quiet on the Western Front\\\" by Erich Maria\
|
||||
\ Remarque** \\n A poignant novel that captures the resilience and trauma experienced by German soldiers during World War I, based on the author's own experiences.\\n\\n4. **\\\"The First World War\\\" by John Keegan** \\n Keegan provides a detailed military history of the war, featuring insights on battles, strategies, and the overall impact on global affairs.\\n\\n5. **\\\"Goodbye to All That\\\" by Robert Graves** \\n This autobiography recounts the author's experiences as a soldier during the war, offering a personal and critical perspective on the conflicts and the post-war era.\\n\\n6. **\\\"Catastrophe 1914: Europe Goes to War\\\" by Max Hastings** \\n Hastings chronicles the events leading up to World War I and the early battles, detailing the war's initial impact on European societies.\\n\\n7. **\\\"The War That Ended Peace: The Road to 1914\\\" by Margaret MacMillan** \\n MacMillan explores the political and historical factors that contributed to the outbreak\
|
||||
\ of war, emphasizing the decisions made by leaders across Europe.\\n\\n8. **\\\"The First World War: A Complete History\\\" by Martin Gilbert** \\n This complete history takes readers through the entirety of the war, from its causes to its aftermath, using a wide range of sources.\\n\\n9. **\\\"1914: The Year the World Ended\\\" by Paul Ham** \\n Ham focuses on the pivotal year of 1914 and the early war's devastation, analyzing its long-lasting effects on the world.\\n\\n10. **\\\"War Horse\\\" by Michael Morpurgo** \\n This children's novel tells the story of a horse and his experiences during the war, highlighting the bond between animals and humans amidst the chaos.\\n\\nEach of these books offers unique perspectives and rich details about the First World War, making them valuable resources for anyone interested in this pivotal period in history.\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\"\
|
||||
: \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 170,\n \"completion_tokens\": 534,\n \"total_tokens\": 704,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_dbaca60df0\"\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDGA6ArRnT0S8ME2I1R4x9Mo4JyGJ\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772052762,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Here is a list of available books on
|
||||
the First World War:\\n\\n1. \\\"The Guns of August\\\" by Barbara W. Tuchman\\n2.
|
||||
\\\"A World Undone: The Story of the Great War, 1914 to 1918\\\" by G.J. Meyer\\n3.
|
||||
\\\"The First World War\\\" by John Keegan\\n4. \\\"The Sleepwalkers: How
|
||||
Europe Went to War in 1914\\\" by Christopher Clark\\n5. \\\"To End All Wars:
|
||||
A Story of Loyalty and Rebellion, 1914-1918\\\" by Adam Hochschild\\n6. \\\"World
|
||||
War I: The Definitive Visual History\\\" by R.G. Grant\\n7. \\\"Catastrophe
|
||||
1914: Europe Goes to War\\\" by Max Hastings\\n8. \\\"The Great War and Modern
|
||||
Memory\\\" by Paul Fussell\\n9. \\\"Paris 1919: Six Months That Changed the
|
||||
World\\\" by Margaret MacMillan\\n10. \\\"The Pity of War: Explaining World
|
||||
War I\\\" by Niall Ferguson\\n\\nIf you need further details on any of these
|
||||
titles, feel free to ask.\",\n \"refusal\": null,\n \"annotations\":
|
||||
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 84,\n \"completion_tokens\":
|
||||
230,\n \"total_tokens\": 314,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 937ed42dee2e621f-GRU
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 29 Apr 2025 12:33:48 GMT
|
||||
- Wed, 25 Feb 2026 20:52:46 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=mLRCnpdB3n_6medIZWHnUu8MNRGZsD6riaRhN47PK74-1745930028-1.0.1.1-M2lDM1_V9hNCK0MZrBnFalF3lndC3JkS8zhDOGww_LmOrgdpU9fZLpNZUmyinCQOnlCjDjDYJUECM82ffT1anqBiO1NoDeNp91EPKiK7s.8; path=/; expires=Tue, 29-Apr-25 13:03:48 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=eTrj_ZhCx2XuylS5vYROwUlPrJBwOyrbS2Ki.msl45E-1745930028010-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '10856'
|
||||
- '3250'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '30000'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '150000000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '29999'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '149999807'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 2ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_bc2d62d8325b2bdd3e98544a66389132
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are Guardrail Agent. You are a expert at validating the output of a task. By providing effective feedback if the output is not valid.\nYour personal goal is: Validate the output of the task\n\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!\nIMPORTANT: Your final answer MUST contain all the information requested in the following format: {\n \"valid\": bool,\n \"feedback\": str | None\n}\n\nIMPORTANT: Ensure the final output does not include any code block markers like ```json or ```python."}, {"role": "user", "content": "\n Ensure the following task result complies with the given guardrail.\n\n Task result:\n Here is a comprehensive list of available books on
|
||||
the First World War:\n\n1. **\"The Sleepwalkers: How Europe Went to War in 1914\" by Christopher Clark** \n This book delves into the complex factors that led to the outbreak of the war, offering insights into the political and social dynamics of early 20th century Europe.\n\n2. **\"A World Undone: The Story of the Great War, 1914 to 1918\" by G.J. Meyer** \n Meyer''s expansive narrative covers the entire war with a focus on both military strategies and the human experiences endured by soldiers and civilians alike.\n\n3. **\"All Quiet on the Western Front\" by Erich Maria Remarque** \n A poignant novel that captures the resilience and trauma experienced by German soldiers during World War I, based on the author''s own experiences.\n\n4. **\"The First World War\" by John Keegan** \n Keegan provides a detailed military history of the war, featuring insights on battles, strategies, and the overall impact on global affairs.\n\n5. **\"Goodbye to All That\" by Robert Graves** \n This
|
||||
autobiography recounts the author''s experiences as a soldier during the war, offering a personal and critical perspective on the conflicts and the post-war era.\n\n6. **\"Catastrophe 1914: Europe Goes to War\" by Max Hastings** \n Hastings chronicles the events leading up to World War I and the early battles, detailing the war''s initial impact on European societies.\n\n7. **\"The War That Ended Peace: The Road to 1914\" by Margaret MacMillan** \n MacMillan explores the political and historical factors that contributed to the outbreak of war, emphasizing the decisions made by leaders across Europe.\n\n8. **\"The First World War: A Complete History\" by Martin Gilbert** \n This complete history takes readers through the entirety of the war, from its causes to its aftermath, using a wide range of sources.\n\n9. **\"1914: The Year the World Ended\" by Paul Ham** \n Ham focuses on the pivotal year of 1914 and the early war''s devastation, analyzing its long-lasting effects
|
||||
on the world.\n\n10. **\"War Horse\" by Michael Morpurgo** \n This children''s novel tells the story of a horse and his experiences during the war, highlighting the bond between animals and humans amidst the chaos.\n\nEach of these books offers unique perspectives and rich details about the First World War, making them valuable resources for anyone interested in this pivotal period in history.\n\n Guardrail:\n Ensure the authors are from Italy\n \n Your task:\n - Confirm if the Task result complies with the guardrail.\n - If not, provide clear feedback explaining what is wrong (e.g., by how much it violates the rule, or what specific part fails).\n - Focus only on identifying issues \u2014 do not propose corrections.\n - If the Task result complies with the guardrail, saying that is valid\n "}], "model": "gpt-4o-mini", "stop": ["\nObservation:"]}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '3917'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=mLRCnpdB3n_6medIZWHnUu8MNRGZsD6riaRhN47PK74-1745930028-1.0.1.1-M2lDM1_V9hNCK0MZrBnFalF3lndC3JkS8zhDOGww_LmOrgdpU9fZLpNZUmyinCQOnlCjDjDYJUECM82ffT1anqBiO1NoDeNp91EPKiK7s.8; _cfuvid=eTrj_ZhCx2XuylS5vYROwUlPrJBwOyrbS2Ki.msl45E-1745930028010-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.68.2
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.68.2
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-read-timeout:
|
||||
- '600.0'
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-BReTBRCAvSDG5VMdtF9ZjByy7lqSJ\",\n \"object\": \"chat.completion\",\n \"created\": 1745930121,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer \\nFinal Answer: {\\n \\\"valid\\\": false,\\n \\\"feedback\\\": \\\"None of the authors listed in the task result are from Italy. All the authors mentioned are from other countries, such as Germany, the UK, and the US.\\\"\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 797,\n \"completion_tokens\": 60,\n \"total_tokens\": 857,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"\
|
||||
audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_0392822090\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 937ed6bd68faa435-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 29 Apr 2025 12:35:23 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '1138'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '30000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '29999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '149999072'
|
||||
x-ratelimit-reset-requests:
|
||||
- 2ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_2ba1be014a5974ba354aff564e26516a
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"trace_id": "4ced1ade-0d34-4d28-a47d-61011b1f3582", "execution_type": "crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null, "crew_name": "crew", "flow_name": null, "crewai_version": "1.2.1", "privacy_level": "standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count": 0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-10-31T07:25:08.937105+00:00"}, "ephemeral_trace_id": "4ced1ade-0d34-4d28-a47d-61011b1f3582"}'
|
||||
body: '{"trace_id": "4ced1ade-0d34-4d28-a47d-61011b1f3582", "execution_type":
|
||||
"crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null,
|
||||
"crew_name": "crew", "flow_name": null, "crewai_version": "1.2.1", "privacy_level":
|
||||
"standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count":
|
||||
0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-10-31T07:25:08.937105+00:00"},
|
||||
"ephemeral_trace_id": "4ced1ade-0d34-4d28-a47d-61011b1f3582"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate, zstd
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
@@ -13,11 +16,13 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- CrewAI-CLI/1.2.1
|
||||
- X-USER-AGENT-XXX
|
||||
X-Crewai-Organization-Id:
|
||||
- 73c2b193-f579-422c-84c7-76a39a1da77f
|
||||
X-Crewai-Version:
|
||||
- 1.2.1
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
method: POST
|
||||
uri: https://app.crewai.com/crewai_plus/api/v1/tracing/ephemeral/batches
|
||||
response:
|
||||
@@ -35,46 +40,60 @@ interactions:
|
||||
cache-control:
|
||||
- no-store
|
||||
content-security-policy:
|
||||
- 'default-src ''self'' *.app.crewai.com app.crewai.com; script-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts https://www.gstatic.com https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com https://js-na1.hs-scripts.com https://js.hubspot.com http://js-na1.hs-scripts.com https://bat.bing.com https://cdn.amplitude.com https://cdn.segment.com https://d1d3n03t5zntha.cloudfront.net/ https://descriptusercontent.com https://edge.fullstory.com https://googleads.g.doubleclick.net https://js.hs-analytics.net https://js.hs-banner.com https://js.hsadspixel.net https://js.hscollectedforms.net
|
||||
https://js.usemessages.com https://snap.licdn.com https://static.cloudflareinsights.com https://static.reo.dev https://www.google-analytics.com https://share.descript.com/; style-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts; img-src ''self'' data: *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://dashboard.tools.crewai.com https://cdn.jsdelivr.net https://forms.hsforms.com https://track.hubspot.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://www.google.com https://www.google.com.br; font-src ''self'' data: *.app.crewai.com app.crewai.com; connect-src ''self'' *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://connect.useparagon.com/ https://zeus.useparagon.com/* https://*.useparagon.com/* https://run.pstmn.io https://connect.tools.crewai.com/ https://*.sentry.io https://www.google-analytics.com https://edge.fullstory.com https://rs.fullstory.com https://api.hubspot.com
|
||||
https://forms.hscollectedforms.net https://api.hubapi.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://google.com/pagead/form-data/16713662509 https://google.com/ccm/form-data/16713662509 https://www.google.com/ccm/collect https://worker-actionkit.tools.crewai.com https://api.reo.dev; frame-src ''self'' *.app.crewai.com app.crewai.com https://connect.useparagon.com/ https://zeus.tools.crewai.com https://zeus.useparagon.com/* https://connect.tools.crewai.com/ https://docs.google.com https://drive.google.com https://slides.google.com https://accounts.google.com https://*.google.com https://app.hubspot.com/ https://td.doubleclick.net https://www.googletagmanager.com/ https://www.youtube.com https://share.descript.com'
|
||||
- CSP-FILTERED
|
||||
etag:
|
||||
- W/"684f9dff2cfefa325ac69ea38dba2309"
|
||||
- ETAG-XXX
|
||||
expires:
|
||||
- '0'
|
||||
permissions-policy:
|
||||
- camera=(), microphone=(self), geolocation=()
|
||||
- PERMISSIONS-POLICY-XXX
|
||||
pragma:
|
||||
- no-cache
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
- REFERRER-POLICY-XXX
|
||||
strict-transport-security:
|
||||
- max-age=63072000; includeSubDomains
|
||||
- STS-XXX
|
||||
vary:
|
||||
- Accept
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
x-frame-options:
|
||||
- SAMEORIGIN
|
||||
- X-FRAME-OPTIONS-XXX
|
||||
x-permitted-cross-domain-policies:
|
||||
- none
|
||||
- X-PERMITTED-XXX
|
||||
x-request-id:
|
||||
- 630cda16-c991-4ed0-b534-16c03eb2ffca
|
||||
- X-REQUEST-ID-XXX
|
||||
x-runtime:
|
||||
- '0.072382'
|
||||
- X-RUNTIME-XXX
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
- X-XSS-PROTECTION-XXX
|
||||
status:
|
||||
code: 201
|
||||
message: Created
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer contains only the content in the following format: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nEnsure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo
|
||||
give my best complete final answer to the task respond using the exact following
|
||||
format:\n\nThought: I now can give a great answer\nFinal Answer: Your final
|
||||
answer must be the great and the most complete as possible, it must be outcome
|
||||
described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nEnsure your final answer contains only
|
||||
the content in the following format: {\n \"properties\": {\n \"score\":
|
||||
{\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\":
|
||||
[\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nEnsure the final output does not include any code block markers
|
||||
like ```json or ```python.\n\nBegin! This is VERY important to you, use the
|
||||
tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
@@ -83,20 +102,18 @@ interactions:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
@@ -107,10 +124,21 @@ interactions:
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CWdnRkRPYTVe5JfVO7aC1cdVfqIdd\",\n \"object\": \"chat.completion\",\n \"created\": 1761895509,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\n{\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 281,\n \"completion_tokens\": 19,\n \"total_tokens\": 300,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-CWdnRkRPYTVe5JfVO7aC1cdVfqIdd\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1761895509,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\n{\\n
|
||||
\ \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\":
|
||||
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 281,\n \"completion_tokens\":
|
||||
19,\n \"total_tokens\": 300,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 99716ab4788dea35-FCO
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
@@ -120,26 +148,25 @@ interactions:
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=S.q8_0ONHDHBHNOJdMZHwJDue9lKhWQHpKuP2lsspx4-1761895510-1.0.1.1-QUDxMm9SVfRT2R188bLcvxUd6SXIBmZgnz3D35UF95nNg8zX5Gzdg2OmU.uo29rqaGatjupcLPNMyhfOqeoyhNQ28Zz1ESSQLq0y70x3IvM; path=/; expires=Fri, 31-Oct-25 07:55:10 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=TvP4GePeQO8E5c_xWNGzJb84f940MFRG_lZ_0hWAc5M-1761895510432-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- SET-COOKIE-XXX
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '569'
|
||||
openai-project:
|
||||
- proj_xitITlrFeen7zjNSzML82h9x
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
@@ -149,40 +176,119 @@ interactions:
|
||||
x-ratelimit-limit-project-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-limit-requests:
|
||||
- '30000'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '150000000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-project-tokens:
|
||||
- '149999700'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '29999'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '149999700'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-project-tokens:
|
||||
- 0s
|
||||
x-ratelimit-reset-requests:
|
||||
- 2ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_393e029e99d54ab0b4e7c69c5cba099f
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"events": [{"event_id": "ea607d3f-c9ff-4aa8-babb-a84eb6d16663", "timestamp": "2025-10-31T07:25:08.935640+00:00", "type": "crew_kickoff_started", "event_data": {"timestamp": "2025-10-31T07:25:08.935640+00:00", "type": "crew_kickoff_started", "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null, "task_id": null, "task_name": null, "agent_id": null, "agent_role": null, "crew_name": "crew", "crew": null, "inputs": null}}, {"event_id": "8e792d78-fe9c-4601-a7b4-7b105fa8fb40", "timestamp": "2025-10-31T07:25:08.937816+00:00", "type": "task_started", "event_data": {"task_description": "Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''", "expected_output": "The score of the title.", "task_name": "Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''", "context": "", "agent_role": "Scorer", "task_id": "677cf2dd-96a9-4eac-9140-0ecaba9609f7"}}, {"event_id": "a2fcdfee-a395-4dc8-99b8-ba3d8d843a70",
|
||||
"timestamp": "2025-10-31T07:25:08.938816+00:00", "type": "agent_execution_started", "event_data": {"agent_role": "Scorer", "agent_goal": "Score the title", "agent_backstory": "You''re an expert scorer, specialized in scoring titles."}}, {"event_id": "b0ba7582-6ea0-4b66-a64a-0a1e38d57502", "timestamp": "2025-10-31T07:25:08.938996+00:00", "type": "llm_call_started", "event_data": {"timestamp": "2025-10-31T07:25:08.938996+00:00", "type": "llm_call_started", "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null, "task_id": "677cf2dd-96a9-4eac-9140-0ecaba9609f7", "task_name": "Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''", "agent_id": "8d6e3481-36fa-4fca-9665-977e6d76a969", "agent_role": "Scorer", "from_task": null, "from_agent": null, "model": "gpt-4.1-mini", "messages": [{"role": "system", "content": "You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score
|
||||
the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer contains only the content in the following format: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nEnsure the final output does not include any
|
||||
code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], "tools": null, "callbacks": ["<crewai.utilities.token_counter_callback.TokenCalcHandler object at 0x11da36000>"], "available_functions": null}}, {"event_id": "ab6b168b-d954-494f-ae58-d9ef7a1941dc", "timestamp": "2025-10-31T07:25:10.466669+00:00", "type": "llm_call_completed", "event_data": {"timestamp": "2025-10-31T07:25:10.466669+00:00", "type": "llm_call_completed", "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null, "task_id": "677cf2dd-96a9-4eac-9140-0ecaba9609f7", "task_name": "Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''", "agent_id": "8d6e3481-36fa-4fca-9665-977e6d76a969", "agent_role": "Scorer", "from_task": null, "from_agent": null, "messages": [{"role": "system", "content": "You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer contains only the content in the following format: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nEnsure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], "response": "Thought: I now can give a great answer\n{\n \"score\": 4\n}", "call_type": "<LLMCallType.LLM_CALL: ''llm_call''>", "model": "gpt-4.1-mini"}}, {"event_id": "0b8a17b6-e7d2-464d-a969-56dd705a40ef", "timestamp": "2025-10-31T07:25:10.466933+00:00", "type": "agent_execution_completed", "event_data": {"agent_role": "Scorer", "agent_goal": "Score the title", "agent_backstory": "You''re an expert scorer, specialized in scoring titles."}}, {"event_id": "b835b8e7-992b-4364-9ff8-25c81203ef77", "timestamp": "2025-10-31T07:25:10.467175+00:00", "type": "task_completed", "event_data": {"task_description": "Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''", "task_name": "Give me an integer score
|
||||
between 1-5 for the following title: ''The impact of AI in the future of work''", "task_id": "677cf2dd-96a9-4eac-9140-0ecaba9609f7", "output_raw": "Thought: I now can give a great answer\n{\n \"score\": 4\n}", "output_format": "OutputFormat.PYDANTIC", "agent_role": "Scorer"}}, {"event_id": "a9973b74-9ca6-46c3-b219-0b11ffa9e210", "timestamp": "2025-10-31T07:25:10.469421+00:00", "type": "crew_kickoff_completed", "event_data": {"timestamp": "2025-10-31T07:25:10.469421+00:00", "type": "crew_kickoff_completed", "source_fingerprint": null, "source_type": null, "fingerprint_metadata": null, "task_id": null, "task_name": null, "agent_id": null, "agent_role": null, "crew_name": "crew", "crew": null, "output": {"description": "Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''", "name": "Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''", "expected_output": "The score of the title.",
|
||||
"summary": "Give me an integer score between 1-5 for the following...", "raw": "Thought: I now can give a great answer\n{\n \"score\": 4\n}", "pydantic": {}, "json_dict": null, "agent": "Scorer", "output_format": "pydantic"}, "total_tokens": 300}}], "batch_metadata": {"events_count": 8, "batch_sequence": 1, "is_final_batch": false}}'
|
||||
body: '{"events": [{"event_id": "ea607d3f-c9ff-4aa8-babb-a84eb6d16663", "timestamp":
|
||||
"2025-10-31T07:25:08.935640+00:00", "type": "crew_kickoff_started", "event_data":
|
||||
{"timestamp": "2025-10-31T07:25:08.935640+00:00", "type": "crew_kickoff_started",
|
||||
"source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
|
||||
"task_id": null, "task_name": null, "agent_id": null, "agent_role": null, "crew_name":
|
||||
"crew", "crew": null, "inputs": null}}, {"event_id": "8e792d78-fe9c-4601-a7b4-7b105fa8fb40",
|
||||
"timestamp": "2025-10-31T07:25:08.937816+00:00", "type": "task_started", "event_data":
|
||||
{"task_description": "Give me an integer score between 1-5 for the following
|
||||
title: ''The impact of AI in the future of work''", "expected_output": "The
|
||||
score of the title.", "task_name": "Give me an integer score between 1-5 for
|
||||
the following title: ''The impact of AI in the future of work''", "context":
|
||||
"", "agent_role": "Scorer", "task_id": "677cf2dd-96a9-4eac-9140-0ecaba9609f7"}},
|
||||
{"event_id": "a2fcdfee-a395-4dc8-99b8-ba3d8d843a70", "timestamp": "2025-10-31T07:25:08.938816+00:00",
|
||||
"type": "agent_execution_started", "event_data": {"agent_role": "Scorer", "agent_goal":
|
||||
"Score the title", "agent_backstory": "You''re an expert scorer, specialized
|
||||
in scoring titles."}}, {"event_id": "b0ba7582-6ea0-4b66-a64a-0a1e38d57502",
|
||||
"timestamp": "2025-10-31T07:25:08.938996+00:00", "type": "llm_call_started",
|
||||
"event_data": {"timestamp": "2025-10-31T07:25:08.938996+00:00", "type": "llm_call_started",
|
||||
"source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
|
||||
"task_id": "677cf2dd-96a9-4eac-9140-0ecaba9609f7", "task_name": "Give me an
|
||||
integer score between 1-5 for the following title: ''The impact of AI in the
|
||||
future of work''", "agent_id": "8d6e3481-36fa-4fca-9665-977e6d76a969", "agent_role":
|
||||
"Scorer", "from_task": null, "from_agent": null, "model": "gpt-4.1-mini", "messages":
|
||||
[{"role": "system", "content": "You are Scorer. You''re an expert scorer, specialized
|
||||
in scoring titles.\nYour personal goal is: Score the title\nTo give my best
|
||||
complete final answer to the task respond using the exact following format:\n\nThought:
|
||||
I now can give a great answer\nFinal Answer: Your final answer must be the great
|
||||
and the most complete as possible, it must be outcome described.\n\nI MUST use
|
||||
these formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nEnsure your final answer contains only
|
||||
the content in the following format: {\n \"properties\": {\n \"score\":
|
||||
{\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\":
|
||||
[\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nEnsure the final output does not include any code block markers
|
||||
like ```json or ```python.\n\nBegin! This is VERY important to you, use the
|
||||
tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],
|
||||
"tools": null, "callbacks": ["<crewai.utilities.token_counter_callback.TokenCalcHandler
|
||||
object at 0x11da36000>"], "available_functions": null}}, {"event_id": "ab6b168b-d954-494f-ae58-d9ef7a1941dc",
|
||||
"timestamp": "2025-10-31T07:25:10.466669+00:00", "type": "llm_call_completed",
|
||||
"event_data": {"timestamp": "2025-10-31T07:25:10.466669+00:00", "type": "llm_call_completed",
|
||||
"source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
|
||||
"task_id": "677cf2dd-96a9-4eac-9140-0ecaba9609f7", "task_name": "Give me an
|
||||
integer score between 1-5 for the following title: ''The impact of AI in the
|
||||
future of work''", "agent_id": "8d6e3481-36fa-4fca-9665-977e6d76a969", "agent_role":
|
||||
"Scorer", "from_task": null, "from_agent": null, "messages": [{"role": "system",
|
||||
"content": "You are Scorer. You''re an expert scorer, specialized in scoring
|
||||
titles.\nYour personal goal is: Score the title\nTo give my best complete final
|
||||
answer to the task respond using the exact following format:\n\nThought: I now
|
||||
can give a great answer\nFinal Answer: Your final answer must be the great and
|
||||
the most complete as possible, it must be outcome described.\n\nI MUST use these
|
||||
formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task:
|
||||
Give me an integer score between 1-5 for the following title: ''The impact of
|
||||
AI in the future of work''\n\nThis is the expected criteria for your final answer:
|
||||
The score of the title.\nyou MUST return the actual complete content as the
|
||||
final answer, not a summary.\nEnsure your final answer contains only the content
|
||||
in the following format: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nEnsure
|
||||
the final output does not include any code block markers like ```json or ```python.\n\nBegin!
|
||||
This is VERY important to you, use the tools available and give your best Final
|
||||
Answer, your job depends on it!\n\nThought:"}], "response": "Thought: I now
|
||||
can give a great answer\n{\n \"score\": 4\n}", "call_type": "<LLMCallType.LLM_CALL:
|
||||
''llm_call''>", "model": "gpt-4.1-mini"}}, {"event_id": "0b8a17b6-e7d2-464d-a969-56dd705a40ef",
|
||||
"timestamp": "2025-10-31T07:25:10.466933+00:00", "type": "agent_execution_completed",
|
||||
"event_data": {"agent_role": "Scorer", "agent_goal": "Score the title", "agent_backstory":
|
||||
"You''re an expert scorer, specialized in scoring titles."}}, {"event_id": "b835b8e7-992b-4364-9ff8-25c81203ef77",
|
||||
"timestamp": "2025-10-31T07:25:10.467175+00:00", "type": "task_completed", "event_data":
|
||||
{"task_description": "Give me an integer score between 1-5 for the following
|
||||
title: ''The impact of AI in the future of work''", "task_name": "Give me an
|
||||
integer score between 1-5 for the following title: ''The impact of AI in the
|
||||
future of work''", "task_id": "677cf2dd-96a9-4eac-9140-0ecaba9609f7", "output_raw":
|
||||
"Thought: I now can give a great answer\n{\n \"score\": 4\n}", "output_format":
|
||||
"OutputFormat.PYDANTIC", "agent_role": "Scorer"}}, {"event_id": "a9973b74-9ca6-46c3-b219-0b11ffa9e210",
|
||||
"timestamp": "2025-10-31T07:25:10.469421+00:00", "type": "crew_kickoff_completed",
|
||||
"event_data": {"timestamp": "2025-10-31T07:25:10.469421+00:00", "type": "crew_kickoff_completed",
|
||||
"source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
|
||||
"task_id": null, "task_name": null, "agent_id": null, "agent_role": null, "crew_name":
|
||||
"crew", "crew": null, "output": {"description": "Give me an integer score between
|
||||
1-5 for the following title: ''The impact of AI in the future of work''", "name":
|
||||
"Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''", "expected_output": "The score of the title.",
|
||||
"summary": "Give me an integer score between 1-5 for the following...", "raw":
|
||||
"Thought: I now can give a great answer\n{\n \"score\": 4\n}", "pydantic":
|
||||
{}, "json_dict": null, "agent": "Scorer", "output_format": "pydantic"}, "total_tokens":
|
||||
300}}], "batch_metadata": {"events_count": 8, "batch_sequence": 1, "is_final_batch":
|
||||
false}}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate, zstd
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
@@ -190,11 +296,13 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- CrewAI-CLI/1.2.1
|
||||
- X-USER-AGENT-XXX
|
||||
X-Crewai-Organization-Id:
|
||||
- 73c2b193-f579-422c-84c7-76a39a1da77f
|
||||
X-Crewai-Version:
|
||||
- 1.2.1
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
method: POST
|
||||
uri: https://app.crewai.com/crewai_plus/api/v1/tracing/ephemeral/batches/4ced1ade-0d34-4d28-a47d-61011b1f3582/events
|
||||
response:
|
||||
@@ -212,35 +320,33 @@ interactions:
|
||||
cache-control:
|
||||
- no-store
|
||||
content-security-policy:
|
||||
- 'default-src ''self'' *.app.crewai.com app.crewai.com; script-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts https://www.gstatic.com https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com https://js-na1.hs-scripts.com https://js.hubspot.com http://js-na1.hs-scripts.com https://bat.bing.com https://cdn.amplitude.com https://cdn.segment.com https://d1d3n03t5zntha.cloudfront.net/ https://descriptusercontent.com https://edge.fullstory.com https://googleads.g.doubleclick.net https://js.hs-analytics.net https://js.hs-banner.com https://js.hsadspixel.net https://js.hscollectedforms.net
|
||||
https://js.usemessages.com https://snap.licdn.com https://static.cloudflareinsights.com https://static.reo.dev https://www.google-analytics.com https://share.descript.com/; style-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts; img-src ''self'' data: *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://dashboard.tools.crewai.com https://cdn.jsdelivr.net https://forms.hsforms.com https://track.hubspot.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://www.google.com https://www.google.com.br; font-src ''self'' data: *.app.crewai.com app.crewai.com; connect-src ''self'' *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://connect.useparagon.com/ https://zeus.useparagon.com/* https://*.useparagon.com/* https://run.pstmn.io https://connect.tools.crewai.com/ https://*.sentry.io https://www.google-analytics.com https://edge.fullstory.com https://rs.fullstory.com https://api.hubspot.com
|
||||
https://forms.hscollectedforms.net https://api.hubapi.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://google.com/pagead/form-data/16713662509 https://google.com/ccm/form-data/16713662509 https://www.google.com/ccm/collect https://worker-actionkit.tools.crewai.com https://api.reo.dev; frame-src ''self'' *.app.crewai.com app.crewai.com https://connect.useparagon.com/ https://zeus.tools.crewai.com https://zeus.useparagon.com/* https://connect.tools.crewai.com/ https://docs.google.com https://drive.google.com https://slides.google.com https://accounts.google.com https://*.google.com https://app.hubspot.com/ https://td.doubleclick.net https://www.googletagmanager.com/ https://www.youtube.com https://share.descript.com'
|
||||
- CSP-FILTERED
|
||||
etag:
|
||||
- W/"be223998b84365d3a863f942c880adfb"
|
||||
- ETAG-XXX
|
||||
expires:
|
||||
- '0'
|
||||
permissions-policy:
|
||||
- camera=(), microphone=(self), geolocation=()
|
||||
- PERMISSIONS-POLICY-XXX
|
||||
pragma:
|
||||
- no-cache
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
- REFERRER-POLICY-XXX
|
||||
strict-transport-security:
|
||||
- max-age=63072000; includeSubDomains
|
||||
- STS-XXX
|
||||
vary:
|
||||
- Accept
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
x-frame-options:
|
||||
- SAMEORIGIN
|
||||
- X-FRAME-OPTIONS-XXX
|
||||
x-permitted-cross-domain-policies:
|
||||
- none
|
||||
- X-PERMITTED-XXX
|
||||
x-request-id:
|
||||
- 9c19d6df-9190-4764-afed-f3444939d2e4
|
||||
- X-REQUEST-ID-XXX
|
||||
x-runtime:
|
||||
- '0.123911'
|
||||
- X-RUNTIME-XXX
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
- X-XSS-PROTECTION-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
@@ -249,8 +355,6 @@ interactions:
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate, zstd
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
@@ -258,11 +362,13 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- CrewAI-CLI/1.2.1
|
||||
- X-USER-AGENT-XXX
|
||||
X-Crewai-Organization-Id:
|
||||
- 73c2b193-f579-422c-84c7-76a39a1da77f
|
||||
X-Crewai-Version:
|
||||
- 1.2.1
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
method: PATCH
|
||||
uri: https://app.crewai.com/crewai_plus/api/v1/tracing/ephemeral/batches/4ced1ade-0d34-4d28-a47d-61011b1f3582/finalize
|
||||
response:
|
||||
@@ -280,35 +386,167 @@ interactions:
|
||||
cache-control:
|
||||
- no-store
|
||||
content-security-policy:
|
||||
- 'default-src ''self'' *.app.crewai.com app.crewai.com; script-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts https://www.gstatic.com https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com https://js-na1.hs-scripts.com https://js.hubspot.com http://js-na1.hs-scripts.com https://bat.bing.com https://cdn.amplitude.com https://cdn.segment.com https://d1d3n03t5zntha.cloudfront.net/ https://descriptusercontent.com https://edge.fullstory.com https://googleads.g.doubleclick.net https://js.hs-analytics.net https://js.hs-banner.com https://js.hsadspixel.net https://js.hscollectedforms.net
|
||||
https://js.usemessages.com https://snap.licdn.com https://static.cloudflareinsights.com https://static.reo.dev https://www.google-analytics.com https://share.descript.com/; style-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts; img-src ''self'' data: *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://dashboard.tools.crewai.com https://cdn.jsdelivr.net https://forms.hsforms.com https://track.hubspot.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://www.google.com https://www.google.com.br; font-src ''self'' data: *.app.crewai.com app.crewai.com; connect-src ''self'' *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://connect.useparagon.com/ https://zeus.useparagon.com/* https://*.useparagon.com/* https://run.pstmn.io https://connect.tools.crewai.com/ https://*.sentry.io https://www.google-analytics.com https://edge.fullstory.com https://rs.fullstory.com https://api.hubspot.com
|
||||
https://forms.hscollectedforms.net https://api.hubapi.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://google.com/pagead/form-data/16713662509 https://google.com/ccm/form-data/16713662509 https://www.google.com/ccm/collect https://worker-actionkit.tools.crewai.com https://api.reo.dev; frame-src ''self'' *.app.crewai.com app.crewai.com https://connect.useparagon.com/ https://zeus.tools.crewai.com https://zeus.useparagon.com/* https://connect.tools.crewai.com/ https://docs.google.com https://drive.google.com https://slides.google.com https://accounts.google.com https://*.google.com https://app.hubspot.com/ https://td.doubleclick.net https://www.googletagmanager.com/ https://www.youtube.com https://share.descript.com'
|
||||
- CSP-FILTERED
|
||||
etag:
|
||||
- W/"bff97e21bd1971750dcfdb102fba9dcd"
|
||||
- ETAG-XXX
|
||||
expires:
|
||||
- '0'
|
||||
permissions-policy:
|
||||
- camera=(), microphone=(self), geolocation=()
|
||||
- PERMISSIONS-POLICY-XXX
|
||||
pragma:
|
||||
- no-cache
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
- REFERRER-POLICY-XXX
|
||||
strict-transport-security:
|
||||
- max-age=63072000; includeSubDomains
|
||||
- STS-XXX
|
||||
vary:
|
||||
- Accept
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
x-frame-options:
|
||||
- SAMEORIGIN
|
||||
- X-FRAME-OPTIONS-XXX
|
||||
x-permitted-cross-domain-policies:
|
||||
- none
|
||||
- X-PERMITTED-XXX
|
||||
x-request-id:
|
||||
- 2b6cd38d-78fa-4676-94ff-80e3bcf48a03
|
||||
- X-REQUEST-ID-XXX
|
||||
x-runtime:
|
||||
- '0.064858'
|
||||
- X-RUNTIME-XXX
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
- X-XSS-PROTECTION-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"},{"role":"system","content":"You are Scorer. You''re
|
||||
an expert scorer, specialized in scoring titles.\nYour personal goal is: Score
|
||||
the title"},{"role":"user","content":"\nCurrent Task: Give me an integer score
|
||||
between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis
|
||||
is the expected criteria for your final answer: The score of the title.\nyou
|
||||
MUST return the actual complete content as the final answer, not a summary.\nFormat
|
||||
your final answer according to the following OpenAPI schema: {\n \"properties\":
|
||||
{\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\":
|
||||
[\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nIMPORTANT: Preserve the original content exactly as-is. Do NOT rewrite,
|
||||
paraphrase, or modify the meaning of the content. Only structure it to match
|
||||
the schema format.\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python.\n\nProvide your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '2541'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-DDE0D15NvBLDvn8Wy68ZscARhqMaX\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044461,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
513,\n \"completion_tokens\": 5,\n \"total_tokens\": 518,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 18:34:21 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '477'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -426,4 +426,121 @@ interactions:
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"user","content":"Thought: I now can give a great
|
||||
answer\nFinal Answer: I would assign a score of 4 to the title \"The impact
|
||||
of AI in the future of work.\" The title is very relevant and timely, as artificial
|
||||
intelligence is a major transformative force affecting the labor market and
|
||||
employment trends. It is clear and concise, effectively highlighting the focus
|
||||
on AI''s influence on the future of work. However, while it is engaging and
|
||||
implies substantial potential impact, it could be slightly more specific or
|
||||
dynamic to reach an excellent level. Overall, it meets very good standards for
|
||||
potential impact, engagement, relevance, and clarity."}],"model":"gpt-4o","tool_choice":{"type":"function","function":{"name":"ScoreOutput"}},"tools":[{"type":"function","function":{"name":"ScoreOutput","description":"Correctly
|
||||
extracted `ScoreOutput` with all the required parameters with correct types","parameters":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"type":"object"}}}]}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1034'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-DDE0G4tjiC8Je3BD8xhWMey7kZF66\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044464,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
|
||||
\ \"id\": \"call_x95I7UxdCvFccZ87imExKzu9\",\n \"type\":
|
||||
\"function\",\n \"function\": {\n \"name\": \"ScoreOutput\",\n
|
||||
\ \"arguments\": \"{\\\"score\\\":4}\"\n }\n }\n
|
||||
\ ],\n \"refusal\": null,\n \"annotations\": []\n },\n
|
||||
\ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n
|
||||
\ \"usage\": {\n \"prompt_tokens\": 188,\n \"completion_tokens\": 5,\n
|
||||
\ \"total_tokens\": 193,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_64dfa806c7\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 18:34:24 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '385'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
|
||||
@@ -1,98 +1,120 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1394'
|
||||
- '1421'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0UpOvDuMqlqYkt9WW8lQSkyatz\",\n \"object\": \"chat.completion\",\n \"created\": 1762380662,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\": 22,\n \"total_tokens\": 316,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\
|
||||
\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDE5QUOVeJDiOh6TuObUjh32f7Q0g\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044784,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
276,\n \"completion_tokens\": 5,\n \"total_tokens\": 281,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 22:11:02 GMT
|
||||
- Wed, 25 Feb 2026 18:39:44 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 22:41:02 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '864'
|
||||
- '303'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '3087'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '200000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '199687'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 93ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -427,4 +427,122 @@ interactions:
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"user","content":"Thought: The title \"The impact
|
||||
of AI in the future of work\" is highly relevant given the widespread and ongoing
|
||||
discussions about AI''s role in transforming workplaces globally. It is clear
|
||||
and concise, directly indicating the subject and scope, which helps the reader
|
||||
understand what to expect. In terms of engagement, it has strong potential to
|
||||
attract interest from professionals, researchers, and the general public curious
|
||||
about how AI will shape jobs and employment trends. Although it is somewhat
|
||||
broad and could be more specific to a particular aspect of work or type of AI,
|
||||
it remains focused enough to be effective as a general overview title.\n\nFinal
|
||||
Answer: 4"}],"model":"gpt-4o","tool_choice":{"type":"function","function":{"name":"ScoreOutput"}},"tools":[{"type":"function","function":{"name":"ScoreOutput","description":"Correctly
|
||||
extracted `ScoreOutput` with all the required parameters with correct types","parameters":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"type":"object"}}}]}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1077'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-DDE0FPRrXCbAAssWcvT9wUojN8yPa\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044463,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
|
||||
\ \"id\": \"call_237IZJqLGcX4N5MZYEd6Wz2n\",\n \"type\":
|
||||
\"function\",\n \"function\": {\n \"name\": \"ScoreOutput\",\n
|
||||
\ \"arguments\": \"{\\\"score\\\":4}\"\n }\n }\n
|
||||
\ ],\n \"refusal\": null,\n \"annotations\": []\n },\n
|
||||
\ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n
|
||||
\ \"usage\": {\n \"prompt_tokens\": 191,\n \"completion_tokens\": 5,\n
|
||||
\ \"total_tokens\": 196,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_64dfa806c7\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 18:34:23 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '365'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo
|
||||
give my best complete final answer to the task respond using the exact following
|
||||
format:\n\nThought: I now can give a great answer\nFinal Answer: Your final
|
||||
answer must be the great and the most complete as possible, it must be outcome
|
||||
described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nEnsure your final answer strictly adheres
|
||||
to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nBegin!
|
||||
This is VERY important to you, use the tools available and give your best Final
|
||||
Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
@@ -15,20 +32,18 @@ interactions:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
@@ -39,11 +54,21 @@ interactions:
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0P4wugCaRcXw9kmLG3BAMBmkA0\",\n \"object\": \"chat.completion\",\n \"created\": 1762380657,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\": 22,\n \"total_tokens\": 316,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\
|
||||
\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0P4wugCaRcXw9kmLG3BAMBmkA0\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1762380657,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\":
|
||||
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\":
|
||||
22,\n \"total_tokens\": 316,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
@@ -53,26 +78,25 @@ interactions:
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 22:40:57 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- SET-COOKIE-XXX
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '537'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
@@ -80,19 +104,153 @@ interactions:
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '200000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '199687'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 93ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"},{"role":"system","content":"You are Scorer. You''re
|
||||
an expert scorer, specialized in scoring titles.\nYour personal goal is: Score
|
||||
the title"},{"role":"user","content":"\nCurrent Task: Give me an integer score
|
||||
between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis
|
||||
is the expected criteria for your final answer: The score of the title.\nyou
|
||||
MUST return the actual complete content as the final answer, not a summary.\nFormat
|
||||
your final answer according to the following OpenAPI schema: {\n \"properties\":
|
||||
{\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\":
|
||||
[\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nIMPORTANT: Preserve the original content exactly as-is. Do NOT rewrite,
|
||||
paraphrase, or modify the meaning of the content. Only structure it to match
|
||||
the schema format.\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python.\n\nProvide your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '2541'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-DDDzz40VXTe9AsmG5ZSlL0IufvYKz\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044447,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
513,\n \"completion_tokens\": 5,\n \"total_tokens\": 518,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 18:34:07 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '426'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -1,194 +1,254 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1394'
|
||||
- '1421'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0M3aPReBrUikkn7QiHFyZG8ETn\",\n \"object\": \"chat.completion\",\n \"created\": 1762380654,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\": 22,\n \"total_tokens\": 316,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\
|
||||
\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDE5OBoRr3j1NGXkef0waj9TCBmLb\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044782,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
276,\n \"completion_tokens\": 5,\n \"total_tokens\": 281,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 22:10:54 GMT
|
||||
- Wed, 25 Feb 2026 18:39:42 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 22:40:54 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '730'
|
||||
- '435'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '754'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '200000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '199687'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 93ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Given the score the title ''The impact of AI in the future of work'' got, give me an integer score between 1-5 for the following title: ''Return of the Jedi''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\":
|
||||
[\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nThis is the context you''re working with:\n{\n \"score\": 4\n}\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"},{"role":"assistant","content":"{\"score\":4}"},{"role":"system","content":"You
|
||||
are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal
|
||||
goal is: Score the title"},{"role":"user","content":"\nCurrent Task: Given the
|
||||
score the title ''The impact of AI in the future of work'' got, give me an integer
|
||||
score between 1-5 for the following title: ''Return of the Jedi''\n\nThis is
|
||||
the expected criteria for your final answer: The score of the title.\nyou MUST
|
||||
return the actual complete content as the final answer, not a summary.\nFormat
|
||||
your final answer according to the following OpenAPI schema: {\n \"properties\":
|
||||
{\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\":
|
||||
[\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nIMPORTANT: Preserve the original content exactly as-is. Do NOT rewrite,
|
||||
paraphrase, or modify the meaning of the content. Only structure it to match
|
||||
the schema format.\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python.\n\nThis is the context you''re working with:\n{\"score\":4}\n\nProvide
|
||||
your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1512'
|
||||
- '2699'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=REDACTED; _cfuvid=REDACTED
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0MEYp1MebCu2eCMBqCwXtNYTbD\",\n \"object\": \"chat.completion\",\n \"created\": 1762380654,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal Answer: {\\n \\\"score\\\": 3\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 324,\n \"completion_tokens\": 22,\n \"total_tokens\": 346,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\
|
||||
\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDE5OEawexwaazoOAgn4QD9W8roe6\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044782,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":3}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
552,\n \"completion_tokens\": 5,\n \"total_tokens\": 557,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 22:10:55 GMT
|
||||
- Wed, 25 Feb 2026 18:39:43 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '983'
|
||||
- '309'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '1002'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '200000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '199659'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 102ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -10,28 +10,29 @@ interactions:
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nEnsure your final answer strictly adheres
|
||||
to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nThis is
|
||||
VERY important to you, your job depends on it!"}],"model":"gpt-4o","tool_choice":"auto","tools":[{"type":"function","function":{"name":"Delegate_work_to_coworker","description":"Delegate
|
||||
does not include any code block markers like ```json or ```python."}],"model":"gpt-4o","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false,"tool_choice":"auto","tools":[{"type":"function","function":{"name":"delegate_work_to_coworker","description":"Delegate
|
||||
a specific task to one of the following coworkers: Scorer\nThe input to this
|
||||
tool should be the coworker, the task you want them to do, and ALL necessary
|
||||
context to execute the task, they know nothing about the task, so share absolutely
|
||||
everything you know, don''t reference things but instead explain them.","parameters":{"properties":{"task":{"description":"The
|
||||
everything you know, don''t reference things but instead explain them.","strict":true,"parameters":{"properties":{"task":{"description":"The
|
||||
task to delegate","title":"Task","type":"string"},"context":{"description":"The
|
||||
context for the task","title":"Context","type":"string"},"coworker":{"description":"The
|
||||
role/name of the coworker to delegate to","title":"Coworker","type":"string"}},"required":["task","context","coworker"],"type":"object"}}},{"type":"function","function":{"name":"Ask_question_to_coworker","description":"Ask
|
||||
role/name of the coworker to delegate to","title":"Coworker","type":"string"}},"required":["task","context","coworker"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"ask_question_to_coworker","description":"Ask
|
||||
a specific question to one of the following coworkers: Scorer\nThe input to
|
||||
this tool should be the coworker, the question you have for them, and ALL necessary
|
||||
context to ask the question properly, they know nothing about the question,
|
||||
so share absolutely everything you know, don''t reference things but instead
|
||||
explain them.","parameters":{"properties":{"question":{"description":"The question
|
||||
to ask","title":"Question","type":"string"},"context":{"description":"The context
|
||||
for the question","title":"Context","type":"string"},"coworker":{"description":"The
|
||||
role/name of the coworker to ask","title":"Coworker","type":"string"}},"required":["question","context","coworker"],"type":"object"}}}]}'
|
||||
explain them.","strict":true,"parameters":{"properties":{"question":{"description":"The
|
||||
question to ask","title":"Question","type":"string"},"context":{"description":"The
|
||||
context for the question","title":"Context","type":"string"},"coworker":{"description":"The
|
||||
role/name of the coworker to ask","title":"Coworker","type":"string"}},"required":["question","context","coworker"],"type":"object","additionalProperties":false}}}]}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
@@ -44,7 +45,7 @@ interactions:
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '2959'
|
||||
- '3415'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
@@ -53,6 +54,8 @@ interactions:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
@@ -66,31 +69,33 @@ interactions:
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.3
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-D0u1dSPVqe5art2HXWibsPOp3SOti\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1769107733,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
string: "{\n \"id\": \"chatcmpl-DDG9wKD6IRmnAwBS1tw4NMVccsPnZ\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772052752,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
|
||||
\ \"id\": \"call_AEHe6pv1NqguBRA5q9CHVSn3\",\n \"type\":
|
||||
\"function\",\n \"function\": {\n \"name\": \"Delegate_work_to_coworker\",\n
|
||||
\ \"arguments\": \"{\\\"task\\\":\\\"Provide an integer score
|
||||
between 1-5 for the title 'The impact of AI in the future of work'. The score
|
||||
should reflect how engaging, relevant, and thought-provoking the title is.\\\",\\\"context\\\":\\\"You
|
||||
need to evaluate how well the title 'The impact of AI in the future of work'
|
||||
meets the criteria of being engaging, relevant, and thought-provoking in the
|
||||
context of emerging technologies and their implications on future work environments.\\\",\\\"coworker\\\":\\\"Scorer\\\"}\"\n
|
||||
\ }\n }\n ],\n \"refusal\": null,\n \"annotations\":
|
||||
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 562,\n \"completion_tokens\":
|
||||
111,\n \"total_tokens\": 673,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
\ \"id\": \"call_VzfUuCi89kzEC9gJgiMCz5B2\",\n \"type\":
|
||||
\"function\",\n \"function\": {\n \"name\": \"delegate_work_to_coworker\",\n
|
||||
\ \"arguments\": \"{\\\"task\\\":\\\"Evaluate the title 'The impact
|
||||
of AI in the future of work' and give an integer score between 1-5 based on
|
||||
how compelling or effective the title is.\\\",\\\"context\\\":\\\"You are
|
||||
asked to evaluate a title 'The impact of AI in the future of work' and provide
|
||||
an integer score between 1-5. The criteria for evaluation include how informative,
|
||||
engaging, relevant, and clear the title is. Additionally, consider how the
|
||||
title may attract the intended audience's interest and its potential impact
|
||||
on readers.\\\",\\\"coworker\\\":\\\"Scorer\\\"}\"\n }\n }\n
|
||||
\ ],\n \"refusal\": null,\n \"annotations\": []\n },\n
|
||||
\ \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n }\n
|
||||
\ ],\n \"usage\": {\n \"prompt_tokens\": 613,\n \"completion_tokens\":
|
||||
127,\n \"total_tokens\": 740,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_deacdd5f6f\"\n}\n"
|
||||
\"default\",\n \"system_fingerprint\": \"fp_64dfa806c7\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
@@ -99,11 +104,9 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 22 Jan 2026 18:48:56 GMT
|
||||
- Wed, 25 Feb 2026 20:52:34 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- SET-COOKIE-XXX
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
@@ -119,146 +122,13 @@ interactions:
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '3849'
|
||||
- '2259'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '3973'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo
|
||||
give my best complete final answer to the task respond using the exact following
|
||||
format:\n\nThought: I now can give a great answer\nFinal Answer: Your final
|
||||
answer must be the great and the most complete as possible, it must be outcome
|
||||
described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent
|
||||
Task: Provide an integer score between 1-5 for the title ''The impact of AI
|
||||
in the future of work''. The score should reflect how engaging, relevant, and
|
||||
thought-provoking the title is.\n\nThis is the expected criteria for your final
|
||||
answer: Your best answer to your coworker asking you this, accounting for the
|
||||
context shared.\nyou MUST return the actual complete content as the final answer,
|
||||
not a summary.\n\nThis is the context you''re working with:\nYou need to evaluate
|
||||
how well the title ''The impact of AI in the future of work'' meets the criteria
|
||||
of being engaging, relevant, and thought-provoking in the context of emerging
|
||||
technologies and their implications on future work environments.\n\nBegin! This
|
||||
is VERY important to you, use the tools available and give your best Final Answer,
|
||||
your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1348'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.3
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-D0u1hKGQrrJVYOcW1tAlQMgAjcaDX\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1769107737,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: The title 'The impact of AI in the future of work' is highly relevant
|
||||
given the current and growing significance of artificial intelligence in transforming
|
||||
work environments across industries. It is engaging because AI's influence
|
||||
on future employment is a topic of widespread interest and concern, prompting
|
||||
readers to explore its implications. Furthermore, it is thought-provoking
|
||||
as it invites consideration of both the opportunities and challenges AI presents
|
||||
for the workforce, including changes in job roles, skills, and economic structures.
|
||||
However, the title could be more captivating or specific to heighten curiosity
|
||||
and emphasize particular aspects of AI's impact. Overall, it effectively meets
|
||||
the criteria but could be slightly enhanced for maximum engagement. Considering
|
||||
all factors, I would score it a 4 out of 5.\",\n \"refusal\": null,\n
|
||||
\ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\":
|
||||
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 264,\n \"completion_tokens\":
|
||||
160,\n \"total_tokens\": 424,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_376a7ccef1\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 22 Jan 2026 18:49:00 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '3273'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '3299'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
@@ -289,47 +159,29 @@ interactions:
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nEnsure your final answer strictly adheres
|
||||
to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nThis is
|
||||
VERY important to you, your job depends on it!"},{"role":"assistant","content":null,"tool_calls":[{"id":"call_AEHe6pv1NqguBRA5q9CHVSn3","type":"function","function":{"name":"Delegate_work_to_coworker","arguments":"{\"task\":\"Provide
|
||||
an integer score between 1-5 for the title ''The impact of AI in the future
|
||||
of work''. The score should reflect how engaging, relevant, and thought-provoking
|
||||
the title is.\",\"context\":\"You need to evaluate how well the title ''The
|
||||
impact of AI in the future of work'' meets the criteria of being engaging, relevant,
|
||||
and thought-provoking in the context of emerging technologies and their implications
|
||||
on future work environments.\",\"coworker\":\"Scorer\"}"}}]},{"role":"tool","tool_call_id":"call_AEHe6pv1NqguBRA5q9CHVSn3","content":"The
|
||||
title ''The impact of AI in the future of work'' is highly relevant given the
|
||||
current and growing significance of artificial intelligence in transforming
|
||||
work environments across industries. It is engaging because AI''s influence
|
||||
on future employment is a topic of widespread interest and concern, prompting
|
||||
readers to explore its implications. Furthermore, it is thought-provoking as
|
||||
it invites consideration of both the opportunities and challenges AI presents
|
||||
for the workforce, including changes in job roles, skills, and economic structures.
|
||||
However, the title could be more captivating or specific to heighten curiosity
|
||||
and emphasize particular aspects of AI''s impact. Overall, it effectively meets
|
||||
the criteria but could be slightly enhanced for maximum engagement. Considering
|
||||
all factors, I would score it a 4 out of 5."},{"role":"user","content":"Analyze
|
||||
the tool result. If requirements are met, provide the Final Answer. Otherwise,
|
||||
call the next tool. Deliver only the answer without meta-commentary."}],"model":"gpt-4o","tool_choice":"auto","tools":[{"type":"function","function":{"name":"Delegate_work_to_coworker","description":"Delegate
|
||||
does not include any code block markers like ```json or ```python."}],"model":"gpt-4o","tool_choice":"auto","tools":[{"type":"function","function":{"name":"delegate_work_to_coworker","description":"Delegate
|
||||
a specific task to one of the following coworkers: Scorer\nThe input to this
|
||||
tool should be the coworker, the task you want them to do, and ALL necessary
|
||||
context to execute the task, they know nothing about the task, so share absolutely
|
||||
everything you know, don''t reference things but instead explain them.","parameters":{"properties":{"task":{"description":"The
|
||||
everything you know, don''t reference things but instead explain them.","strict":true,"parameters":{"properties":{"task":{"description":"The
|
||||
task to delegate","title":"Task","type":"string"},"context":{"description":"The
|
||||
context for the task","title":"Context","type":"string"},"coworker":{"description":"The
|
||||
role/name of the coworker to delegate to","title":"Coworker","type":"string"}},"required":["task","context","coworker"],"type":"object"}}},{"type":"function","function":{"name":"Ask_question_to_coworker","description":"Ask
|
||||
role/name of the coworker to delegate to","title":"Coworker","type":"string"}},"required":["task","context","coworker"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"ask_question_to_coworker","description":"Ask
|
||||
a specific question to one of the following coworkers: Scorer\nThe input to
|
||||
this tool should be the coworker, the question you have for them, and ALL necessary
|
||||
context to ask the question properly, they know nothing about the question,
|
||||
so share absolutely everything you know, don''t reference things but instead
|
||||
explain them.","parameters":{"properties":{"question":{"description":"The question
|
||||
to ask","title":"Question","type":"string"},"context":{"description":"The context
|
||||
for the question","title":"Context","type":"string"},"coworker":{"description":"The
|
||||
role/name of the coworker to ask","title":"Coworker","type":"string"}},"required":["question","context","coworker"],"type":"object"}}}]}'
|
||||
explain them.","strict":true,"parameters":{"properties":{"question":{"description":"The
|
||||
question to ask","title":"Question","type":"string"},"context":{"description":"The
|
||||
context for the question","title":"Context","type":"string"},"coworker":{"description":"The
|
||||
role/name of the coworker to ask","title":"Coworker","type":"string"}},"required":["question","context","coworker"],"type":"object","additionalProperties":false}}}]}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
@@ -342,7 +194,7 @@ interactions:
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '4694'
|
||||
- '3151'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
@@ -366,22 +218,31 @@ interactions:
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.3
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-D0u1kZrAEdxxk1GHhh8iEvvddrv5C\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1769107740,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
string: "{\n \"id\": \"chatcmpl-DDG9zJ5ZtuBIJLBxuTBqV4pYyaAf3\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772052755,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
868,\n \"completion_tokens\": 6,\n \"total_tokens\": 874,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
|
||||
\ \"id\": \"call_IdyahKEb4Ez9fWTlL0SWNU97\",\n \"type\":
|
||||
\"function\",\n \"function\": {\n \"name\": \"ask_question_to_coworker\",\n
|
||||
\ \"arguments\": \"{\\\"question\\\":\\\"What score would you
|
||||
give between 1-5 to the following title: 'The impact of AI in the future of
|
||||
work' and why?\\\",\\\"context\\\":\\\"Your task is to evaluate the title
|
||||
based on its ability to intrigue, its clarity, and relevance. You need to
|
||||
provide an integer score between 1 and 5 for this title, considering these
|
||||
aspects.\\\",\\\"coworker\\\":\\\"Scorer\\\"}\"\n }\n }\n
|
||||
\ ],\n \"refusal\": null,\n \"annotations\": []\n },\n
|
||||
\ \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n }\n
|
||||
\ ],\n \"usage\": {\n \"prompt_tokens\": 581,\n \"completion_tokens\":
|
||||
97,\n \"total_tokens\": 678,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_deacdd5f6f\"\n}\n"
|
||||
\"default\",\n \"system_fingerprint\": \"fp_18e61aa3bc\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
@@ -390,7 +251,7 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 22 Jan 2026 18:49:00 GMT
|
||||
- Wed, 25 Feb 2026 20:52:36 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
@@ -408,13 +269,299 @@ interactions:
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '480'
|
||||
- '1686'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: What score would you give between 1-5 to the following title: ''The impact
|
||||
of AI in the future of work'' and why?\n\nThis is the expected criteria for
|
||||
your final answer: Your best answer to your coworker asking you this, accounting
|
||||
for the context shared.\nyou MUST return the actual complete content as the
|
||||
final answer, not a summary.\n\nThis is the context you''re working with:\nYour
|
||||
task is to evaluate the title based on its ability to intrigue, its clarity,
|
||||
and relevance. You need to provide an integer score between 1 and 5 for this
|
||||
title, considering these aspects.\n\nProvide your complete response:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '831'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-DDGA1eLxVsUvh5Ptopxsrctx3s8fF\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772052757,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"I would give the title \\\"The impact
|
||||
of AI in the future of work\\\" a score of 4 out of 5.\\n\\nHere's why:\\n\\n-
|
||||
**Clarity:** The title is clear and straightforward; it immediately tells
|
||||
the reader that the focus is on how AI will influence the work landscape going
|
||||
forward. There is no ambiguity about the subject matter.\\n\\n- **Relevance:**
|
||||
The topic is highly relevant in today's context, as AI technologies are rapidly
|
||||
transforming industries and workplace dynamics. This makes the title timely
|
||||
and likely to attract interest from professionals, academics, and anyone curious
|
||||
about technological impacts on employment.\\n\\n- **Intrigue:** While the
|
||||
title is clear and relevant, it lacks a bit of punch or uniqueness that might
|
||||
make it stand out more. It's somewhat generic\u2014many articles use similar
|
||||
phrasing. Adding an element that hints at specific insights or a fresh perspective
|
||||
could increase intrigue.\\n\\nOverall, the title effectively conveys the subject
|
||||
and relevance but could be slightly improved with more compelling language
|
||||
to boost interest.\",\n \"refusal\": null,\n \"annotations\":
|
||||
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 164,\n \"completion_tokens\":
|
||||
198,\n \"total_tokens\": 362,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 20:52:41 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '4344'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: "{\"messages\":[{\"role\":\"system\",\"content\":\"You are Crew Manager.
|
||||
You are a seasoned manager with a knack for getting the best out of your team.\\nYou
|
||||
are also known for your ability to delegate work to the right people, and to
|
||||
ask the right questions to get the best out of your team.\\nEven though you
|
||||
don't perform tasks by yourself, you have a lot of experience in the field,
|
||||
which allows you to properly evaluate the work of your team members.\\nYour
|
||||
personal goal is: Manage the team to complete the task in the best way possible.\"},{\"role\":\"user\",\"content\":\"\\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: 'The impact
|
||||
of AI in the future of work'\\n\\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\\nFormat your final answer according to
|
||||
the following OpenAPI schema: {\\n \\\"properties\\\": {\\n \\\"score\\\":
|
||||
{\\n \\\"title\\\": \\\"Score\\\",\\n \\\"type\\\": \\\"integer\\\"\\n
|
||||
\ }\\n },\\n \\\"required\\\": [\\n \\\"score\\\"\\n ],\\n \\\"title\\\":
|
||||
\\\"ScoreOutput\\\",\\n \\\"type\\\": \\\"object\\\",\\n \\\"additionalProperties\\\":
|
||||
false\\n}\\n\\nIMPORTANT: Preserve the original content exactly as-is. Do NOT
|
||||
rewrite, paraphrase, or modify the meaning of the content. Only structure it
|
||||
to match the schema format.\\n\\nDo not include the OpenAPI schema in the final
|
||||
output. Ensure the final output does not include any code block markers like
|
||||
```json or ```python.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_IdyahKEb4Ez9fWTlL0SWNU97\",\"type\":\"function\",\"function\":{\"name\":\"ask_question_to_coworker\",\"arguments\":\"{\\\"question\\\":\\\"What
|
||||
score would you give between 1-5 to the following title: 'The impact of AI in
|
||||
the future of work' and why?\\\",\\\"context\\\":\\\"Your task is to evaluate
|
||||
the title based on its ability to intrigue, its clarity, and relevance. You
|
||||
need to provide an integer score between 1 and 5 for this title, considering
|
||||
these aspects.\\\",\\\"coworker\\\":\\\"Scorer\\\"}\"}}]},{\"role\":\"tool\",\"tool_call_id\":\"call_IdyahKEb4Ez9fWTlL0SWNU97\",\"name\":\"ask_question_to_coworker\",\"content\":\"I
|
||||
would give the title \\\"The impact of AI in the future of work\\\" a score
|
||||
of 4 out of 5.\\n\\nHere's why:\\n\\n- **Clarity:** The title is clear and straightforward;
|
||||
it immediately tells the reader that the focus is on how AI will influence the
|
||||
work landscape going forward. There is no ambiguity about the subject matter.\\n\\n-
|
||||
**Relevance:** The topic is highly relevant in today's context, as AI technologies
|
||||
are rapidly transforming industries and workplace dynamics. This makes the title
|
||||
timely and likely to attract interest from professionals, academics, and anyone
|
||||
curious about technological impacts on employment.\\n\\n- **Intrigue:** While
|
||||
the title is clear and relevant, it lacks a bit of punch or uniqueness that
|
||||
might make it stand out more. It's somewhat generic\u2014many articles use similar
|
||||
phrasing. Adding an element that hints at specific insights or a fresh perspective
|
||||
could increase intrigue.\\n\\nOverall, the title effectively conveys the subject
|
||||
and relevance but could be slightly improved with more compelling language to
|
||||
boost interest.\"},{\"role\":\"user\",\"content\":\"Analyze the tool result.
|
||||
If requirements are met, provide the Final Answer. Otherwise, call the next
|
||||
tool. Deliver only the answer without meta-commentary.\"}],\"model\":\"gpt-4o\",\"response_format\":{\"type\":\"json_schema\",\"json_schema\":{\"schema\":{\"properties\":{\"score\":{\"title\":\"Score\",\"type\":\"integer\"}},\"required\":[\"score\"],\"title\":\"ScoreOutput\",\"type\":\"object\",\"additionalProperties\":false},\"name\":\"ScoreOutput\",\"strict\":true}},\"stream\":false,\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"delegate_work_to_coworker\",\"description\":\"Delegate
|
||||
a specific task to one of the following coworkers: Scorer\\nThe input to this
|
||||
tool should be the coworker, the task you want them to do, and ALL necessary
|
||||
context to execute the task, they know nothing about the task, so share absolutely
|
||||
everything you know, don't reference things but instead explain them.\",\"strict\":true,\"parameters\":{\"properties\":{\"task\":{\"description\":\"The
|
||||
task to delegate\",\"title\":\"Task\",\"type\":\"string\"},\"context\":{\"description\":\"The
|
||||
context for the task\",\"title\":\"Context\",\"type\":\"string\"},\"coworker\":{\"description\":\"The
|
||||
role/name of the coworker to delegate to\",\"title\":\"Coworker\",\"type\":\"string\"}},\"required\":[\"task\",\"context\",\"coworker\"],\"type\":\"object\",\"additionalProperties\":false}}},{\"type\":\"function\",\"function\":{\"name\":\"ask_question_to_coworker\",\"description\":\"Ask
|
||||
a specific question to one of the following coworkers: Scorer\\nThe input to
|
||||
this tool should be the coworker, the question you have for them, and ALL necessary
|
||||
context to ask the question properly, they know nothing about the question,
|
||||
so share absolutely everything you know, don't reference things but instead
|
||||
explain them.\",\"strict\":true,\"parameters\":{\"properties\":{\"question\":{\"description\":\"The
|
||||
question to ask\",\"title\":\"Question\",\"type\":\"string\"},\"context\":{\"description\":\"The
|
||||
context for the question\",\"title\":\"Context\",\"type\":\"string\"},\"coworker\":{\"description\":\"The
|
||||
role/name of the coworker to ask\",\"title\":\"Coworker\",\"type\":\"string\"}},\"required\":[\"question\",\"context\",\"coworker\"],\"type\":\"object\",\"additionalProperties\":false}}}]}"
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '5297'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-DDGA5qDbleuzKoN7uVs5MFOC6X5DG\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772052761,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
956,\n \"completion_tokens\": 10,\n \"total_tokens\": 966,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_64dfa806c7\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 20:52:42 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '508'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '503'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
|
||||
@@ -1,98 +1,120 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1394'
|
||||
- '1421'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0ICsr8nVjoOoVFpnOLUh71LgfJ\",\n \"object\": \"chat.completion\",\n \"created\": 1762380650,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\": 22,\n \"total_tokens\": 316,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\
|
||||
\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDDxMk9AEzSz8xZnza3XoSeijSI5R\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044284,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
276,\n \"completion_tokens\": 5,\n \"total_tokens\": 281,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 22:10:50 GMT
|
||||
- Wed, 25 Feb 2026 18:31:25 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 22:40:50 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '482'
|
||||
- '385'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '495'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '200000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '199687'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 93ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -1,192 +1,254 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4o"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"}],"model":"gpt-4o","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1388'
|
||||
- '1415'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0Jd7eOJXIC6Yc1xB0F6Ve3KK1M\",\n \"object\": \"chat.completion\",\n \"created\": 1762380651,\n \"model\": \"gpt-4o-2024-08-06\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"I now can give a great answer\\nFinal Answer: {\\\"score\\\": 4}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\": 17,\n \"total_tokens\": 311,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_65564d8ba5\"\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDDxNULmWtIUe1SAGHcArDXYSifV8\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044285,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
276,\n \"completion_tokens\": 5,\n \"total_tokens\": 281,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_9e0d253e63\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 22:10:52 GMT
|
||||
- Wed, 25 Feb 2026 18:31:26 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 22:40:52 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '1337'
|
||||
- '364'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '1487'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29687'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 626ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Given the score the title ''The impact of AI in the future of work'' got, give me an integer score between 1-5 for the following title: ''Return of the Jedi'', you MUST give it a score, use your best judgment\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nThis is the context you''re working with:\n{\"score\": 4}\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4o"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"},{"role":"assistant","content":"{\"score\":4}"},{"role":"system","content":"You
|
||||
are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal
|
||||
goal is: Score the title"},{"role":"user","content":"\nCurrent Task: Given the
|
||||
score the title ''The impact of AI in the future of work'' got, give me an integer
|
||||
score between 1-5 for the following title: ''Return of the Jedi'', you MUST
|
||||
give it a score, use your best judgment\n\nThis is the expected criteria for
|
||||
your final answer: The score of the title.\nyou MUST return the actual complete
|
||||
content as the final answer, not a summary.\nFormat your final answer according
|
||||
to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nThis is
|
||||
the context you''re working with:\n{\"score\":4}\n\nProvide your complete response:"}],"model":"gpt-4o","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1550'
|
||||
- '2743'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=REDACTED; _cfuvid=REDACTED
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0KidOU2tphhqhW69ygSBSubHBQ\",\n \"object\": \"chat.completion\",\n \"created\": 1762380652,\n \"model\": \"gpt-4o-2024-08-06\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"I now can give a great answer\\nFinal Answer: {\\\"score\\\": 5}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 333,\n \"completion_tokens\": 17,\n \"total_tokens\": 350,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_a788c5aef0\"\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDDxOIf7hV4pRmOxmlsA7bO8L2z5w\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044286,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":5}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
564,\n \"completion_tokens\": 5,\n \"total_tokens\": 569,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_9e0d253e63\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 22:10:53 GMT
|
||||
- Wed, 25 Feb 2026 18:31:27 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '1009'
|
||||
- '393'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '1106'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29647'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 706ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"trace_id": "00000000-0000-0000-0000-000000000000", "execution_type": "crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null, "crew_name": "crew", "flow_name": null, "crewai_version": "1.3.0", "privacy_level": "standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count": 0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-11-05T22:10:38.307164+00:00"}, "ephemeral_trace_id": "00000000-0000-0000-0000-000000000000"}'
|
||||
body: '{"trace_id": "00000000-0000-0000-0000-000000000000", "execution_type":
|
||||
"crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null,
|
||||
"crew_name": "crew", "flow_name": null, "crewai_version": "1.3.0", "privacy_level":
|
||||
"standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count":
|
||||
0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-11-05T22:10:38.307164+00:00"},
|
||||
"ephemeral_trace_id": "00000000-0000-0000-0000-000000000000"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate, zstd
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
@@ -13,14 +16,18 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- CrewAI-CLI/1.3.0
|
||||
- X-USER-AGENT-XXX
|
||||
X-Crewai-Version:
|
||||
- 1.3.0
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
method: POST
|
||||
uri: https://app.crewai.com/crewai_plus/api/v1/tracing/ephemeral/batches
|
||||
response:
|
||||
body:
|
||||
string: '{"id": "00000000-0000-0000-0000-000000000000","ephemeral_trace_id": "00000000-0000-0000-0000-000000000000","execution_type":"crew","crew_name":"crew","flow_name":null,"status":"running","duration_ms":null,"crewai_version":"1.3.0","total_events":0,"execution_context":{"crew_fingerprint":null,"crew_name":"crew","flow_name":null,"crewai_version":"1.3.0","privacy_level":"standard"},"created_at":"2025-11-05T22:10:38.904Z","updated_at":"2025-11-05T22:10:38.904Z","access_code": "TRACE-0000000000","user_identifier":null}'
|
||||
string: '{"id": "00000000-0000-0000-0000-000000000000","ephemeral_trace_id":
|
||||
"00000000-0000-0000-0000-000000000000","execution_type":"crew","crew_name":"crew","flow_name":null,"status":"running","duration_ms":null,"crewai_version":"1.3.0","total_events":0,"execution_context":{"crew_fingerprint":null,"crew_name":"crew","flow_name":null,"crewai_version":"1.3.0","privacy_level":"standard"},"created_at":"2025-11-05T22:10:38.904Z","updated_at":"2025-11-05T22:10:38.904Z","access_code":
|
||||
"TRACE-0000000000","user_identifier":null}'
|
||||
headers:
|
||||
Connection:
|
||||
- keep-alive
|
||||
@@ -33,46 +40,61 @@ interactions:
|
||||
cache-control:
|
||||
- no-store
|
||||
content-security-policy:
|
||||
- 'default-src ''self'' *.app.crewai.com app.crewai.com; script-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts https://www.gstatic.com https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com https://js-na1.hs-scripts.com https://js.hubspot.com http://js-na1.hs-scripts.com https://bat.bing.com https://cdn.amplitude.com https://cdn.segment.com https://d1d3n03t5zntha.cloudfront.net/ https://descriptusercontent.com https://edge.fullstory.com https://googleads.g.doubleclick.net https://js.hs-analytics.net https://js.hs-banner.com https://js.hsadspixel.net https://js.hscollectedforms.net
|
||||
https://js.usemessages.com https://snap.licdn.com https://static.cloudflareinsights.com https://static.reo.dev https://www.google-analytics.com https://share.descript.com/; style-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts; img-src ''self'' data: *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://dashboard.tools.crewai.com https://cdn.jsdelivr.net https://forms.hsforms.com https://track.hubspot.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://www.google.com https://www.google.com.br; font-src ''self'' data: *.app.crewai.com app.crewai.com; connect-src ''self'' *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://connect.useparagon.com/ https://zeus.useparagon.com/* https://*.useparagon.com/* https://run.pstmn.io https://connect.tools.crewai.com/ https://*.sentry.io https://www.google-analytics.com https://edge.fullstory.com https://rs.fullstory.com https://api.hubspot.com
|
||||
https://forms.hscollectedforms.net https://api.hubapi.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://google.com/pagead/form-data/16713662509 https://google.com/ccm/form-data/16713662509 https://www.google.com/ccm/collect https://worker-actionkit.tools.crewai.com https://api.reo.dev; frame-src ''self'' *.app.crewai.com app.crewai.com https://connect.useparagon.com/ https://zeus.tools.crewai.com https://zeus.useparagon.com/* https://connect.tools.crewai.com/ https://docs.google.com https://drive.google.com https://slides.google.com https://accounts.google.com https://*.google.com https://app.hubspot.com/ https://td.doubleclick.net https://www.googletagmanager.com/ https://www.youtube.com https://share.descript.com'
|
||||
- CSP-FILTERED
|
||||
etag:
|
||||
- W/"06db9ad73130a1da388846e83fc98135"
|
||||
- ETAG-XXX
|
||||
expires:
|
||||
- '0'
|
||||
permissions-policy:
|
||||
- camera=(), microphone=(self), geolocation=()
|
||||
- PERMISSIONS-POLICY-XXX
|
||||
pragma:
|
||||
- no-cache
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
- REFERRER-POLICY-XXX
|
||||
strict-transport-security:
|
||||
- max-age=63072000; includeSubDomains
|
||||
- STS-XXX
|
||||
vary:
|
||||
- Accept
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
x-frame-options:
|
||||
- SAMEORIGIN
|
||||
- X-FRAME-OPTIONS-XXX
|
||||
x-permitted-cross-domain-policies:
|
||||
- none
|
||||
- X-PERMITTED-XXX
|
||||
x-request-id:
|
||||
- 34f34729-198e-482e-8c87-163a997bc3f4
|
||||
- X-REQUEST-ID-XXX
|
||||
x-runtime:
|
||||
- '0.239932'
|
||||
- X-RUNTIME-XXX
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
- X-XSS-PROTECTION-XXX
|
||||
status:
|
||||
code: 201
|
||||
message: Created
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo
|
||||
give my best complete final answer to the task respond using the exact following
|
||||
format:\n\nThought: I now can give a great answer\nFinal Answer: Your final
|
||||
answer must be the great and the most complete as possible, it must be outcome
|
||||
described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nEnsure your final answer strictly adheres
|
||||
to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nBegin!
|
||||
This is VERY important to you, use the tools available and give your best Final
|
||||
Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
@@ -81,20 +103,18 @@ interactions:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
@@ -105,11 +125,21 @@ interactions:
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0656gofDPbkHnqVBtb4a5cX4I0\",\n \"object\": \"chat.completion\",\n \"created\": 1762380638,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\": 22,\n \"total_tokens\": 316,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\
|
||||
\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0656gofDPbkHnqVBtb4a5cX4I0\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1762380638,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\":
|
||||
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\":
|
||||
22,\n \"total_tokens\": 316,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
@@ -119,26 +149,25 @@ interactions:
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 22:40:39 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- SET-COOKIE-XXX
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '491'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
@@ -146,19 +175,153 @@ interactions:
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '200000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '199687'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 93ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"},{"role":"system","content":"You are Scorer. You''re
|
||||
an expert scorer, specialized in scoring titles.\nYour personal goal is: Score
|
||||
the title"},{"role":"user","content":"\nCurrent Task: Give me an integer score
|
||||
between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis
|
||||
is the expected criteria for your final answer: The score of the title.\nyou
|
||||
MUST return the actual complete content as the final answer, not a summary.\nFormat
|
||||
your final answer according to the following OpenAPI schema: {\n \"properties\":
|
||||
{\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\":
|
||||
[\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nIMPORTANT: Preserve the original content exactly as-is. Do NOT rewrite,
|
||||
paraphrase, or modify the meaning of the content. Only structure it to match
|
||||
the schema format.\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python.\n\nProvide your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '2541'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-DDDzycCKiyLb7UfPI2tKGyQAw8LGi\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044446,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
513,\n \"completion_tokens\": 5,\n \"total_tokens\": 518,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 18:34:07 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '497'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -1,106 +1,110 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are Scorer. You''re an
|
||||
expert scorer, specialized in scoring titles.\nYour personal goal is: Score
|
||||
the title\nTo give my best complete final answer to the task use the exact following
|
||||
format:\n\nThought: I now can give a great answer\nFinal Answer: Your final
|
||||
answer must be the great and the most complete as possible, it must be outcome
|
||||
described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user",
|
||||
"content": "\nCurrent Task: Give me an integer score between 1-5 for the following
|
||||
title: ''The impact of AI in the future of work''\n\nThis is the expect criteria
|
||||
for your final answer: The score of the title.\nyou MUST return the actual complete
|
||||
content as the final answer, not a summary.\n\nBegin! This is VERY important
|
||||
to you, use the tools available and give your best Final Answer, your job depends
|
||||
on it!\n\nThought:"}], "model": "gpt-4o"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\n\nProvide your complete response:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '915'
|
||||
- '522'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-AB7gKOb785BSjHMwGUL7QpXJHDfmJ\",\n \"object\"\
|
||||
: \"chat.completion\",\n \"created\": 1727214500,\n \"model\": \"gpt-4o-2024-05-13\"\
|
||||
,\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \
|
||||
\ \"role\": \"assistant\",\n \"content\": \"Thought: I now can\
|
||||
\ give a great answer\\nFinal Answer: 4\",\n \"refusal\": null\n \
|
||||
\ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n \
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 186,\n \"completion_tokens\"\
|
||||
: 15,\n \"total_tokens\": 201,\n \"completion_tokens_details\": {\n\
|
||||
\ \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"\
|
||||
fp_52a7f40b0b\"\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-DDG9vqGZskrNpGfY0XnTHvzJGDu5u\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772052751,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"4\",\n \"refusal\": null,\n
|
||||
\ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\":
|
||||
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 101,\n \"completion_tokens\":
|
||||
1,\n \"total_tokens\": 102,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85fa63ed091cf3-GRU
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:48:21 GMT
|
||||
- Wed, 25 Feb 2026 20:52:32 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '199'
|
||||
- '276'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999781'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_93411fed8e9bb5607df0dbc5d178f2cb
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Give me an integer score between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis is the expected criteria for your final answer: The score of the title.\nyou MUST return the actual complete content as the final answer, not a summary.\nEnsure your final answer strictly adheres to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo not include the OpenAPI schema in the final output. Ensure the final output does not include any code block markers like ```json or ```python.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo
|
||||
give my best complete final answer to the task respond using the exact following
|
||||
format:\n\nThought: I now can give a great answer\nFinal Answer: Your final
|
||||
answer must be the great and the most complete as possible, it must be outcome
|
||||
described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nEnsure your final answer strictly adheres
|
||||
to the following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nBegin!
|
||||
This is VERY important to you, use the tools available and give your best Final
|
||||
Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
- ACCEPT-ENCODING-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
@@ -15,20 +32,18 @@ interactions:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
@@ -39,11 +54,25 @@ interactions:
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0PI2q4kRtIkqoIwCl9TVmZiD0o\",\n \"object\": \"chat.completion\",\n \"created\": 1762380657,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: The title \\\"The impact of AI in the future of work\\\" is clear, relevant, and concise. It accurately reflects a significant and current topic that is likely to attract interest. However, it could be more specific about the type of impact or scope to make it more compelling. Overall, it is a strong and effective title.\\n\\nFinal Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\": 80,\n \"total_tokens\": 374,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \
|
||||
\ \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\n}\n"
|
||||
string: "{\n \"id\": \"chatcmpl-CYg0PI2q4kRtIkqoIwCl9TVmZiD0o\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1762380657,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: The title \\\"The impact of
|
||||
AI in the future of work\\\" is clear, relevant, and concise. It accurately
|
||||
reflects a significant and current topic that is likely to attract interest.
|
||||
However, it could be more specific about the type of impact or scope to make
|
||||
it more compelling. Overall, it is a strong and effective title.\\n\\nFinal
|
||||
Answer: {\\n \\\"score\\\": 4\\n}\",\n \"refusal\": null,\n \"annotations\":
|
||||
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 294,\n \"completion_tokens\":
|
||||
80,\n \"total_tokens\": 374,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_4c2851f862\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- REDACTED-RAY
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
@@ -53,26 +82,25 @@ interactions:
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 22:40:59 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- SET-COOKIE-XXX
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '1476'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
@@ -80,29 +108,32 @@ interactions:
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '500'
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- '200000'
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- '499'
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '199687'
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- 120ms
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- 93ms
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- req_REDACTED
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"trace_id": "c682f49d-bb6b-49d9-84b7-06e1881d37cd", "execution_type": "crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null, "crew_name": "crew", "flow_name": null, "crewai_version": "1.4.1", "privacy_level": "standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count": 0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-11-15T21:20:09.431751+00:00"}, "ephemeral_trace_id": "c682f49d-bb6b-49d9-84b7-06e1881d37cd"}'
|
||||
body: '{"trace_id": "c682f49d-bb6b-49d9-84b7-06e1881d37cd", "execution_type":
|
||||
"crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null,
|
||||
"crew_name": "crew", "flow_name": null, "crewai_version": "1.4.1", "privacy_level":
|
||||
"standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count":
|
||||
0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-11-15T21:20:09.431751+00:00"},
|
||||
"ephemeral_trace_id": "c682f49d-bb6b-49d9-84b7-06e1881d37cd"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
@@ -110,11 +141,13 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- CrewAI-CLI/1.4.1
|
||||
- X-USER-AGENT-XXX
|
||||
X-Crewai-Organization-Id:
|
||||
- 73c2b193-f579-422c-84c7-76a39a1da77f
|
||||
X-Crewai-Version:
|
||||
- 1.4.1
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
method: POST
|
||||
uri: https://app.crewai.com/crewai_plus/api/v1/tracing/ephemeral/batches
|
||||
response:
|
||||
@@ -132,36 +165,168 @@ interactions:
|
||||
cache-control:
|
||||
- no-store
|
||||
content-security-policy:
|
||||
- 'default-src ''self'' *.app.crewai.com app.crewai.com; script-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts https://www.gstatic.com https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com https://js-na1.hs-scripts.com https://js.hubspot.com http://js-na1.hs-scripts.com https://bat.bing.com https://cdn.amplitude.com https://cdn.segment.com https://d1d3n03t5zntha.cloudfront.net/ https://descriptusercontent.com https://edge.fullstory.com https://googleads.g.doubleclick.net https://js.hs-analytics.net https://js.hs-banner.com https://js.hsadspixel.net https://js.hscollectedforms.net
|
||||
https://js.usemessages.com https://snap.licdn.com https://static.cloudflareinsights.com https://static.reo.dev https://www.google-analytics.com https://share.descript.com/; style-src ''self'' ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts; img-src ''self'' data: *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://dashboard.tools.crewai.com https://cdn.jsdelivr.net https://forms.hsforms.com https://track.hubspot.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://www.google.com https://www.google.com.br; font-src ''self'' data: *.app.crewai.com app.crewai.com; connect-src ''self'' *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://connect.useparagon.com/ https://zeus.useparagon.com/* https://*.useparagon.com/* https://run.pstmn.io https://connect.tools.crewai.com/ https://*.sentry.io https://www.google-analytics.com https://edge.fullstory.com https://rs.fullstory.com https://api.hubspot.com
|
||||
https://forms.hscollectedforms.net https://api.hubapi.com https://px.ads.linkedin.com https://px4.ads.linkedin.com https://google.com/pagead/form-data/16713662509 https://google.com/ccm/form-data/16713662509 https://www.google.com/ccm/collect https://worker-actionkit.tools.crewai.com https://api.reo.dev; frame-src ''self'' *.app.crewai.com app.crewai.com https://connect.useparagon.com/ https://zeus.tools.crewai.com https://zeus.useparagon.com/* https://connect.tools.crewai.com/ https://docs.google.com https://drive.google.com https://slides.google.com https://accounts.google.com https://*.google.com https://app.hubspot.com/ https://td.doubleclick.net https://www.googletagmanager.com/ https://www.youtube.com https://share.descript.com'
|
||||
- CSP-FILTERED
|
||||
etag:
|
||||
- W/"e8d1e903c8c6ec2f765163c0c03bed79"
|
||||
- ETAG-XXX
|
||||
expires:
|
||||
- '0'
|
||||
permissions-policy:
|
||||
- camera=(), microphone=(self), geolocation=()
|
||||
- PERMISSIONS-POLICY-XXX
|
||||
pragma:
|
||||
- no-cache
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
- REFERRER-POLICY-XXX
|
||||
strict-transport-security:
|
||||
- max-age=63072000; includeSubDomains
|
||||
- STS-XXX
|
||||
vary:
|
||||
- Accept
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
- X-CONTENT-TYPE-XXX
|
||||
x-frame-options:
|
||||
- SAMEORIGIN
|
||||
- X-FRAME-OPTIONS-XXX
|
||||
x-permitted-cross-domain-policies:
|
||||
- none
|
||||
- X-PERMITTED-XXX
|
||||
x-request-id:
|
||||
- 5ea5f513-c359-4a92-a84a-08ad44d9857b
|
||||
- X-REQUEST-ID-XXX
|
||||
x-runtime:
|
||||
- '0.044665'
|
||||
- X-RUNTIME-XXX
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
- X-XSS-PROTECTION-XXX
|
||||
status:
|
||||
code: 201
|
||||
message: Created
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nFormat your final answer according to the
|
||||
following OpenAPI schema: {\n \"properties\": {\n \"score\": {\n \"title\":
|
||||
\"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\": [\n \"score\"\n ],\n \"title\":
|
||||
\"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\": false\n}\n\nIMPORTANT:
|
||||
Preserve the original content exactly as-is. Do NOT rewrite, paraphrase, or
|
||||
modify the meaning of the content. Only structure it to match the schema format.\n\nDo
|
||||
not include the OpenAPI schema in the final output. Ensure the final output
|
||||
does not include any code block markers like ```json or ```python.\n\nProvide
|
||||
your complete response:"},{"role":"system","content":"You are Scorer. You''re
|
||||
an expert scorer, specialized in scoring titles.\nYour personal goal is: Score
|
||||
the title"},{"role":"user","content":"\nCurrent Task: Give me an integer score
|
||||
between 1-5 for the following title: ''The impact of AI in the future of work''\n\nThis
|
||||
is the expected criteria for your final answer: The score of the title.\nyou
|
||||
MUST return the actual complete content as the final answer, not a summary.\nFormat
|
||||
your final answer according to the following OpenAPI schema: {\n \"properties\":
|
||||
{\n \"score\": {\n \"title\": \"Score\",\n \"type\": \"integer\"\n }\n },\n \"required\":
|
||||
[\n \"score\"\n ],\n \"title\": \"ScoreOutput\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n}\n\nIMPORTANT: Preserve the original content exactly as-is. Do NOT rewrite,
|
||||
paraphrase, or modify the meaning of the content. Only structure it to match
|
||||
the schema format.\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python.\n\nProvide your complete response:"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '2541'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-DDE0GSLDtGruDzwtl2bwlAXUmvmHG\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772044464,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"score\\\":4}\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
513,\n \"completion_tokens\": 5,\n \"total_tokens\": 518,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a391f2cee0\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 25 Feb 2026 18:34:25 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '530'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
|
||||
@@ -3,11 +3,7 @@ interactions:
|
||||
body: "{\"messages\":[{\"role\":\"system\",\"content\":\"You are Guardrail Agent.
|
||||
You are a expert at validating the output of a task. By providing effective
|
||||
feedback if the output is not valid.\\nYour personal goal is: Validate the output
|
||||
of the task\\nTo give my best complete final answer to the task respond using
|
||||
the exact following format:\\n\\nThought: I now can give a great answer\\nFinal
|
||||
Answer: Your final answer must be the great and the most complete as possible,
|
||||
it must be outcome described.\\n\\nI MUST use these formats, my job depends
|
||||
on it!\"},{\"role\":\"user\",\"content\":\"\\nCurrent Task: \\n Ensure
|
||||
of the task\"},{\"role\":\"user\",\"content\":\"\\nCurrent Task: \\n Ensure
|
||||
the following task result complies with the given guardrail.\\n\\n Task
|
||||
result:\\n \\n Lorem Ipsum is simply dummy text of the printing
|
||||
and typesetting industry. Lorem Ipsum has been the industry's standard dummy
|
||||
@@ -17,8 +13,9 @@ interactions:
|
||||
what is wrong (e.g., by how much it violates the rule, or what specific part
|
||||
fails).\\n - Focus only on identifying issues \u2014 do not propose corrections.\\n
|
||||
\ - If the Task result complies with the guardrail, saying that is valid\\n
|
||||
\ \\n\\nBegin! This is VERY important to you, use the tools available
|
||||
and give your best Final Answer, your job depends on it!\\n\\nThought:\"}],\"model\":\"gpt-4o\"}"
|
||||
\ \\n\\nProvide your complete response:\"}],\"model\":\"gpt-4o\",\"response_format\":{\"type\":\"json_schema\",\"json_schema\":{\"schema\":{\"properties\":{\"valid\":{\"description\":\"Whether
|
||||
the task output complies with the guardrail\",\"title\":\"Valid\",\"type\":\"boolean\"},\"feedback\":{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"A
|
||||
feedback about the task output if it is not valid\",\"title\":\"Feedback\"}},\"required\":[\"valid\",\"feedback\"],\"title\":\"LLMGuardrailResult\",\"type\":\"object\",\"additionalProperties\":false},\"name\":\"LLMGuardrailResult\",\"strict\":true}},\"stream\":false}"
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
@@ -31,7 +28,7 @@ interactions:
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1467'
|
||||
- '1567'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
@@ -40,142 +37,6 @@ interactions:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.3
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-Cy7yHRYTZi8yzRbcODnKr92keLKCb\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1768446357,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"The task result provided has more than
|
||||
10 words. I will count the words to verify this.\\n\\nThe task result is the
|
||||
following text:\\n\\\"Lorem Ipsum is simply dummy text of the printing and
|
||||
typesetting industry. Lorem Ipsum has been the industry's standard dummy text
|
||||
ever\\\"\\n\\nCounting the words:\\n\\n1. Lorem \\n2. Ipsum \\n3. is \\n4.
|
||||
simply \\n5. dummy \\n6. text \\n7. of \\n8. the \\n9. printing \\n10. and
|
||||
\\n11. typesetting \\n12. industry. \\n13. Lorem \\n14. Ipsum \\n15. has \\n16.
|
||||
been \\n17. the \\n18. industry's \\n19. standard \\n20. dummy \\n21. text
|
||||
\\n22. ever\\n\\nThe total word count is 22.\\n\\nThought: I now can give
|
||||
a great answer\\nFinal Answer: The task result does not comply with the guardrail.
|
||||
It contains 22 words, which exceeds the limit of 10 words.\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
285,\n \"completion_tokens\": 195,\n \"total_tokens\": 480,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_deacdd5f6f\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 15 Jan 2026 03:05:59 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- SET-COOKIE-XXX
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
content-length:
|
||||
- '1557'
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '2130'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '2147'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"Ensure your final answer strictly
|
||||
adheres to the following OpenAPI schema: {\n \"type\": \"json_schema\",\n \"json_schema\":
|
||||
{\n \"name\": \"LLMGuardrailResult\",\n \"strict\": true,\n \"schema\":
|
||||
{\n \"properties\": {\n \"valid\": {\n \"description\":
|
||||
\"Whether the task output complies with the guardrail\",\n \"title\":
|
||||
\"Valid\",\n \"type\": \"boolean\"\n },\n \"feedback\":
|
||||
{\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\":
|
||||
\"null\"\n }\n ],\n \"default\": null,\n \"description\":
|
||||
\"A feedback about the task output if it is not valid\",\n \"title\":
|
||||
\"Feedback\"\n }\n },\n \"required\": [\n \"valid\",\n \"feedback\"\n ],\n \"title\":
|
||||
\"LLMGuardrailResult\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n }\n }\n}\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python."},{"role":"user","content":"The task result does not comply with
|
||||
the guardrail. It contains 22 words, which exceeds the limit of 10 words."}],"model":"gpt-4o","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"valid":{"description":"Whether
|
||||
the task output complies with the guardrail","title":"Valid","type":"boolean"},"feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A
|
||||
feedback about the task output if it is not valid","title":"Feedback"}},"required":["valid","feedback"],"title":"LLMGuardrailResult","type":"object","additionalProperties":false},"name":"LLMGuardrailResult","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1835'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
@@ -191,23 +52,24 @@ interactions:
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.3
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-Cy7yJiPCk4fXuogyT5e8XeGRLCSf8\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1768446359,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
string: "{\n \"id\": \"chatcmpl-DDGANa7LCEtvfCZsEly4mNksTjCX3\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772052779,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"valid\\\":false,\\\"feedback\\\":\\\"The
|
||||
task output exceeds the word limit of 10 words by containing 22 words.\\\"}\",\n
|
||||
\ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\":
|
||||
null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
363,\n \"completion_tokens\": 25,\n \"total_tokens\": 388,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
task result contains more than 10 words. Specifically, it has 20 words, which
|
||||
exceeds the guardrail limit by 10 words.\\\"}\",\n \"refusal\": null,\n
|
||||
\ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\":
|
||||
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 290,\n \"completion_tokens\":
|
||||
37,\n \"total_tokens\": 327,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a0e9480a2f\"\n}\n"
|
||||
\"default\",\n \"system_fingerprint\": \"fp_64dfa806c7\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
@@ -216,7 +78,7 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 15 Jan 2026 03:05:59 GMT
|
||||
- Wed, 25 Feb 2026 20:53:00 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
@@ -231,18 +93,16 @@ interactions:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
content-length:
|
||||
- '913'
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '488'
|
||||
- '1108'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '507'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
@@ -266,11 +126,7 @@ interactions:
|
||||
body: "{\"messages\":[{\"role\":\"system\",\"content\":\"You are Guardrail Agent.
|
||||
You are a expert at validating the output of a task. By providing effective
|
||||
feedback if the output is not valid.\\nYour personal goal is: Validate the output
|
||||
of the task\\nTo give my best complete final answer to the task respond using
|
||||
the exact following format:\\n\\nThought: I now can give a great answer\\nFinal
|
||||
Answer: Your final answer must be the great and the most complete as possible,
|
||||
it must be outcome described.\\n\\nI MUST use these formats, my job depends
|
||||
on it!\"},{\"role\":\"user\",\"content\":\"\\nCurrent Task: \\n Ensure
|
||||
of the task\"},{\"role\":\"user\",\"content\":\"\\nCurrent Task: \\n Ensure
|
||||
the following task result complies with the given guardrail.\\n\\n Task
|
||||
result:\\n \\n Lorem Ipsum is simply dummy text of the printing
|
||||
and typesetting industry. Lorem Ipsum has been the industry's standard dummy
|
||||
@@ -280,8 +136,9 @@ interactions:
|
||||
explaining what is wrong (e.g., by how much it violates the rule, or what specific
|
||||
part fails).\\n - Focus only on identifying issues \u2014 do not propose
|
||||
corrections.\\n - If the Task result complies with the guardrail, saying
|
||||
that is valid\\n \\n\\nBegin! This is VERY important to you, use the
|
||||
tools available and give your best Final Answer, your job depends on it!\\n\\nThought:\"}],\"model\":\"gpt-4o\"}"
|
||||
that is valid\\n \\n\\nProvide your complete response:\"}],\"model\":\"gpt-4o\",\"response_format\":{\"type\":\"json_schema\",\"json_schema\":{\"schema\":{\"properties\":{\"valid\":{\"description\":\"Whether
|
||||
the task output complies with the guardrail\",\"title\":\"Valid\",\"type\":\"boolean\"},\"feedback\":{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"null\"}],\"description\":\"A
|
||||
feedback about the task output if it is not valid\",\"title\":\"Feedback\"}},\"required\":[\"valid\",\"feedback\"],\"title\":\"LLMGuardrailResult\",\"type\":\"object\",\"additionalProperties\":false},\"name\":\"LLMGuardrailResult\",\"strict\":true}},\"stream\":false}"
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
@@ -294,7 +151,7 @@ interactions:
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1468'
|
||||
- '1568'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
@@ -303,144 +160,6 @@ interactions:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- X-STAINLESS-OS-XXX
|
||||
x-stainless-package-version:
|
||||
- 1.83.0
|
||||
x-stainless-read-timeout:
|
||||
- X-STAINLESS-READ-TIMEOUT-XXX
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.3
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-Cy7yKa0rmi2YoTLpyXt9hjeLt2rTI\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1768446360,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"First, I'll count the number of words
|
||||
in the Task result to ensure it complies with the guardrail. \\n\\nThe Task
|
||||
result is: \\\"Lorem Ipsum is simply dummy text of the printing and typesetting
|
||||
industry. Lorem Ipsum has been the industry's standard dummy text ever.\\\"\\n\\nBy
|
||||
counting the words: \\n1. Lorem\\n2. Ipsum\\n3. is\\n4. simply\\n5. dummy\\n6.
|
||||
text\\n7. of\\n8. the\\n9. printing\\n10. and\\n11. typesetting\\n12. industry\\n13.
|
||||
Lorem\\n14. Ipsum\\n15. has\\n16. been\\n17. the\\n18. industry's\\n19. standard\\n20.
|
||||
dummy\\n21. text\\n22. ever\\n\\nThere are 22 words total in the Task result.\\n\\nI
|
||||
need to verify if the count of 22 words is less than the guardrail limit of
|
||||
500 words.\\n\\nThought: I now can give a great answer\\nFinal Answer: The
|
||||
Task result complies with the guardrail as it contains 22 words, which is
|
||||
less than the 500-word limit. Therefore, the output is valid.\",\n \"refusal\":
|
||||
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
285,\n \"completion_tokens\": 227,\n \"total_tokens\": 512,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_deacdd5f6f\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 15 Jan 2026 03:06:02 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- SET-COOKIE-XXX
|
||||
Strict-Transport-Security:
|
||||
- STS-XXX
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- X-CONTENT-TYPE-XXX
|
||||
access-control-expose-headers:
|
||||
- ACCESS-CONTROL-XXX
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
content-length:
|
||||
- '1668'
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '2502'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '2522'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- X-RATELIMIT-LIMIT-REQUESTS-XXX
|
||||
x-ratelimit-limit-tokens:
|
||||
- X-RATELIMIT-LIMIT-TOKENS-XXX
|
||||
x-ratelimit-remaining-requests:
|
||||
- X-RATELIMIT-REMAINING-REQUESTS-XXX
|
||||
x-ratelimit-remaining-tokens:
|
||||
- X-RATELIMIT-REMAINING-TOKENS-XXX
|
||||
x-ratelimit-reset-requests:
|
||||
- X-RATELIMIT-RESET-REQUESTS-XXX
|
||||
x-ratelimit-reset-tokens:
|
||||
- X-RATELIMIT-RESET-TOKENS-XXX
|
||||
x-request-id:
|
||||
- X-REQUEST-ID-XXX
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"Ensure your final answer strictly
|
||||
adheres to the following OpenAPI schema: {\n \"type\": \"json_schema\",\n \"json_schema\":
|
||||
{\n \"name\": \"LLMGuardrailResult\",\n \"strict\": true,\n \"schema\":
|
||||
{\n \"properties\": {\n \"valid\": {\n \"description\":
|
||||
\"Whether the task output complies with the guardrail\",\n \"title\":
|
||||
\"Valid\",\n \"type\": \"boolean\"\n },\n \"feedback\":
|
||||
{\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\":
|
||||
\"null\"\n }\n ],\n \"default\": null,\n \"description\":
|
||||
\"A feedback about the task output if it is not valid\",\n \"title\":
|
||||
\"Feedback\"\n }\n },\n \"required\": [\n \"valid\",\n \"feedback\"\n ],\n \"title\":
|
||||
\"LLMGuardrailResult\",\n \"type\": \"object\",\n \"additionalProperties\":
|
||||
false\n }\n }\n}\n\nDo not include the OpenAPI schema in the final output.
|
||||
Ensure the final output does not include any code block markers like ```json
|
||||
or ```python."},{"role":"user","content":"The Task result complies with the
|
||||
guardrail as it contains 22 words, which is less than the 500-word limit. Therefore,
|
||||
the output is valid."}],"model":"gpt-4o","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"valid":{"description":"Whether
|
||||
the task output complies with the guardrail","title":"Valid","type":"boolean"},"feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A
|
||||
feedback about the task output if it is not valid","title":"Feedback"}},"required":["valid","feedback"],"title":"LLMGuardrailResult","type":"object","additionalProperties":false},"name":"LLMGuardrailResult","strict":true}},"stream":false}'
|
||||
headers:
|
||||
User-Agent:
|
||||
- X-USER-AGENT-XXX
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- ACCEPT-ENCODING-XXX
|
||||
authorization:
|
||||
- AUTHORIZATION-XXX
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1864'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- COOKIE-XXX
|
||||
host:
|
||||
- api.openai.com
|
||||
x-stainless-arch:
|
||||
- X-STAINLESS-ARCH-XXX
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-helper-method:
|
||||
- beta.chat.completions.parse
|
||||
x-stainless-lang:
|
||||
@@ -456,22 +175,22 @@ interactions:
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.13.3
|
||||
- 3.13.12
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: "{\n \"id\": \"chatcmpl-Cy7yMAjNYSCz2foZPEcSVCuapzF8y\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1768446362,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
string: "{\n \"id\": \"chatcmpl-DDGAO7HbV6K3Iy0lQA058TOzTDoVa\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1772052780,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"{\\\"valid\\\":true,\\\"feedback\\\":null}\",\n
|
||||
\ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\":
|
||||
null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
369,\n \"completion_tokens\": 9,\n \"total_tokens\": 378,\n \"prompt_tokens_details\":
|
||||
290,\n \"completion_tokens\": 9,\n \"total_tokens\": 299,\n \"prompt_tokens_details\":
|
||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||
\"default\",\n \"system_fingerprint\": \"fp_a0e9480a2f\"\n}\n"
|
||||
\"default\",\n \"system_fingerprint\": \"fp_1d6b4c17c3\"\n}\n"
|
||||
headers:
|
||||
CF-RAY:
|
||||
- CF-RAY-XXX
|
||||
@@ -480,7 +199,7 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 15 Jan 2026 03:06:03 GMT
|
||||
- Wed, 25 Feb 2026 20:53:01 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
@@ -495,18 +214,16 @@ interactions:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
content-length:
|
||||
- '837'
|
||||
openai-organization:
|
||||
- OPENAI-ORG-XXX
|
||||
openai-processing-ms:
|
||||
- '413'
|
||||
- '386'
|
||||
openai-project:
|
||||
- OPENAI-PROJECT-XXX
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '650'
|
||||
set-cookie:
|
||||
- SET-COOKIE-XXX
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@ from datetime import datetime, timedelta
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
import httpx
|
||||
from crewai.cli.authentication.main import AuthenticationCommand
|
||||
from crewai.cli.constants import (
|
||||
CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE,
|
||||
@@ -220,7 +220,7 @@ class TestAuthenticationCommand:
|
||||
]
|
||||
mock_console_print.assert_has_calls(expected_calls)
|
||||
|
||||
@patch("requests.post")
|
||||
@patch("crewai.cli.authentication.main.httpx.post")
|
||||
def test_get_device_code(self, mock_post):
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {
|
||||
@@ -256,7 +256,7 @@ class TestAuthenticationCommand:
|
||||
"verification_uri_complete": "https://example.com/auth",
|
||||
}
|
||||
|
||||
@patch("requests.post")
|
||||
@patch("crewai.cli.authentication.main.httpx.post")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
def test_poll_for_token_success(self, mock_console_print, mock_post):
|
||||
mock_response_success = MagicMock()
|
||||
@@ -305,7 +305,7 @@ class TestAuthenticationCommand:
|
||||
]
|
||||
mock_console_print.assert_has_calls(expected_calls)
|
||||
|
||||
@patch("requests.post")
|
||||
@patch("crewai.cli.authentication.main.httpx.post")
|
||||
@patch("crewai.cli.authentication.main.console.print")
|
||||
def test_poll_for_token_timeout(self, mock_console_print, mock_post):
|
||||
mock_response_pending = MagicMock()
|
||||
@@ -324,7 +324,7 @@ class TestAuthenticationCommand:
|
||||
"Timeout: Failed to get the token. Please try again.", style="bold red"
|
||||
)
|
||||
|
||||
@patch("requests.post")
|
||||
@patch("crewai.cli.authentication.main.httpx.post")
|
||||
def test_poll_for_token_error(self, mock_post):
|
||||
"""Test the method to poll for token (error path)."""
|
||||
# Setup mock to return error
|
||||
@@ -338,5 +338,5 @@ class TestAuthenticationCommand:
|
||||
|
||||
device_code_data = {"device_code": "test_device_code", "interval": 1}
|
||||
|
||||
with pytest.raises(requests.HTTPError):
|
||||
with pytest.raises(httpx.HTTPError):
|
||||
self.auth_command._poll_for_token(device_code_data)
|
||||
|
||||
@@ -4,10 +4,11 @@ from io import StringIO
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
import json
|
||||
|
||||
import httpx
|
||||
from crewai.cli.deploy.main import DeployCommand
|
||||
from crewai.cli.utils import parse_toml
|
||||
from requests.exceptions import JSONDecodeError
|
||||
|
||||
|
||||
class TestDeployCommand(unittest.TestCase):
|
||||
@@ -37,18 +38,18 @@ class TestDeployCommand(unittest.TestCase):
|
||||
DeployCommand()
|
||||
|
||||
def test_validate_response_successful_response(self):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.json.return_value = {"message": "Success"}
|
||||
mock_response.status_code = 200
|
||||
mock_response.ok = True
|
||||
mock_response.is_success = True
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
self.deploy_command._validate_response(mock_response)
|
||||
assert fake_out.getvalue() == ""
|
||||
|
||||
def test_validate_response_json_decode_error(self):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response.json.side_effect = JSONDecodeError("Decode error", "", 0)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.json.side_effect = json.JSONDecodeError("Decode error", "", 0)
|
||||
mock_response.status_code = 500
|
||||
mock_response.content = b"Invalid JSON"
|
||||
|
||||
@@ -64,13 +65,13 @@ class TestDeployCommand(unittest.TestCase):
|
||||
assert "Response:\nInvalid JSON" in output
|
||||
|
||||
def test_validate_response_422_error(self):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.json.return_value = {
|
||||
"field1": ["Error message 1"],
|
||||
"field2": ["Error message 2"],
|
||||
}
|
||||
mock_response.status_code = 422
|
||||
mock_response.ok = False
|
||||
mock_response.is_success = False
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
with pytest.raises(SystemExit):
|
||||
@@ -84,10 +85,10 @@ class TestDeployCommand(unittest.TestCase):
|
||||
assert "Field2 Error message 2" in output
|
||||
|
||||
def test_validate_response_other_error(self):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.json.return_value = {"error": "Something went wrong"}
|
||||
mock_response.status_code = 500
|
||||
mock_response.ok = False
|
||||
mock_response.is_success = False
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
with pytest.raises(SystemExit):
|
||||
|
||||
@@ -3,8 +3,9 @@ import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import requests
|
||||
from requests.exceptions import JSONDecodeError
|
||||
import json
|
||||
|
||||
import httpx
|
||||
|
||||
from crewai.cli.enterprise.main import EnterpriseConfigureCommand
|
||||
from crewai.cli.settings.main import SettingsCommand
|
||||
@@ -25,7 +26,7 @@ class TestEnterpriseConfigureCommand(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.test_dir)
|
||||
|
||||
@patch('crewai.cli.enterprise.main.requests.get')
|
||||
@patch('crewai.cli.enterprise.main.httpx.get')
|
||||
@patch('crewai.cli.enterprise.main.get_crewai_version')
|
||||
def test_successful_configuration(self, mock_get_version, mock_requests_get):
|
||||
mock_get_version.return_value = "1.0.0"
|
||||
@@ -73,19 +74,23 @@ class TestEnterpriseConfigureCommand(unittest.TestCase):
|
||||
self.assertEqual(call_args[0], key)
|
||||
self.assertEqual(call_args[1], value)
|
||||
|
||||
@patch('crewai.cli.enterprise.main.requests.get')
|
||||
@patch('crewai.cli.enterprise.main.httpx.get')
|
||||
@patch('crewai.cli.enterprise.main.get_crewai_version')
|
||||
def test_http_error_handling(self, mock_get_version, mock_requests_get):
|
||||
mock_get_version.return_value = "1.0.0"
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found")
|
||||
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
|
||||
"404 Not Found",
|
||||
request=httpx.Request("GET", "http://test"),
|
||||
response=httpx.Response(404),
|
||||
)
|
||||
mock_requests_get.return_value = mock_response
|
||||
|
||||
with self.assertRaises(SystemExit):
|
||||
self.enterprise_command.configure("https://enterprise.example.com")
|
||||
|
||||
@patch('crewai.cli.enterprise.main.requests.get')
|
||||
@patch('crewai.cli.enterprise.main.httpx.get')
|
||||
@patch('crewai.cli.enterprise.main.get_crewai_version')
|
||||
def test_invalid_json_response(self, mock_get_version, mock_requests_get):
|
||||
mock_get_version.return_value = "1.0.0"
|
||||
@@ -93,13 +98,13 @@ class TestEnterpriseConfigureCommand(unittest.TestCase):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.raise_for_status.return_value = None
|
||||
mock_response.json.side_effect = JSONDecodeError("Invalid JSON", "", 0)
|
||||
mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0)
|
||||
mock_requests_get.return_value = mock_response
|
||||
|
||||
with self.assertRaises(SystemExit):
|
||||
self.enterprise_command.configure("https://enterprise.example.com")
|
||||
|
||||
@patch('crewai.cli.enterprise.main.requests.get')
|
||||
@patch('crewai.cli.enterprise.main.httpx.get')
|
||||
@patch('crewai.cli.enterprise.main.get_crewai_version')
|
||||
def test_missing_required_fields(self, mock_get_version, mock_requests_get):
|
||||
mock_get_version.return_value = "1.0.0"
|
||||
@@ -115,7 +120,7 @@ class TestEnterpriseConfigureCommand(unittest.TestCase):
|
||||
with self.assertRaises(SystemExit):
|
||||
self.enterprise_command.configure("https://enterprise.example.com")
|
||||
|
||||
@patch('crewai.cli.enterprise.main.requests.get')
|
||||
@patch('crewai.cli.enterprise.main.httpx.get')
|
||||
@patch('crewai.cli.enterprise.main.get_crewai_version')
|
||||
def test_settings_update_error(self, mock_get_version, mock_requests_get):
|
||||
mock_get_version.return_value = "1.0.0"
|
||||
|
||||
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch, call
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
import requests
|
||||
import httpx
|
||||
|
||||
from crewai.cli.organization.main import OrganizationCommand
|
||||
from crewai.cli.cli import org_list, switch, current
|
||||
@@ -115,7 +115,7 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
def test_list_organizations_api_error(self, mock_console):
|
||||
self.org_command.plus_api_client = MagicMock()
|
||||
self.org_command.plus_api_client.get_organizations.side_effect = (
|
||||
requests.exceptions.RequestException("API Error")
|
||||
httpx.HTTPError("API Error")
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
@@ -201,8 +201,10 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
def test_list_organizations_unauthorized(self, mock_console):
|
||||
mock_response = MagicMock()
|
||||
mock_http_error = requests.exceptions.HTTPError(
|
||||
"401 Client Error: Unauthorized", response=MagicMock(status_code=401)
|
||||
mock_http_error = httpx.HTTPStatusError(
|
||||
"401 Client Error: Unauthorized",
|
||||
request=httpx.Request("GET", "http://test"),
|
||||
response=httpx.Response(401),
|
||||
)
|
||||
|
||||
mock_response.raise_for_status.side_effect = mock_http_error
|
||||
@@ -219,8 +221,10 @@ class TestOrganizationCommand(unittest.TestCase):
|
||||
@patch("crewai.cli.organization.main.console")
|
||||
def test_switch_organization_unauthorized(self, mock_console):
|
||||
mock_response = MagicMock()
|
||||
mock_http_error = requests.exceptions.HTTPError(
|
||||
"401 Client Error: Unauthorized", response=MagicMock(status_code=401)
|
||||
mock_http_error = httpx.HTTPStatusError(
|
||||
"401 Client Error: Unauthorized",
|
||||
request=httpx.Request("GET", "http://test"),
|
||||
response=httpx.Response(401),
|
||||
)
|
||||
|
||||
mock_response.raise_for_status.side_effect = mock_http_error
|
||||
|
||||
@@ -33,9 +33,9 @@ class TestPlusAPI(unittest.TestCase):
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
def assert_request_with_org_id(
|
||||
self, mock_make_request, method: str, endpoint: str, **kwargs
|
||||
self, mock_client_instance, method: str, endpoint: str, **kwargs
|
||||
):
|
||||
mock_make_request.assert_called_once_with(
|
||||
mock_client_instance.request.assert_called_once_with(
|
||||
method,
|
||||
f"{os.getenv('CREWAI_PLUS_URL')}{endpoint}",
|
||||
headers={
|
||||
@@ -49,24 +49,25 @@ class TestPlusAPI(unittest.TestCase):
|
||||
)
|
||||
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
@patch("requests.Session.request")
|
||||
@patch("crewai.cli.plus_api.httpx.Client")
|
||||
def test_login_to_tool_repository_with_org_uuid(
|
||||
self, mock_make_request, mock_settings_class
|
||||
self, mock_client_class, mock_settings_class
|
||||
):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.org_uuid = self.org_uuid
|
||||
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)
|
||||
|
||||
mock_client_instance = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_make_request.return_value = mock_response
|
||||
mock_client_instance.request.return_value = mock_response
|
||||
mock_client_class.return_value.__enter__.return_value = mock_client_instance
|
||||
|
||||
response = self.api.login_to_tool_repository()
|
||||
|
||||
self.assert_request_with_org_id(
|
||||
mock_make_request, "POST", "/crewai_plus/api/v1/tools/login"
|
||||
mock_client_instance, "POST", "/crewai_plus/api/v1/tools/login"
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@@ -82,23 +83,23 @@ class TestPlusAPI(unittest.TestCase):
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||
@patch("crewai.cli.plus_api.httpx.Client")
|
||||
def test_get_tool_with_org_uuid(self, mock_client_class, mock_settings_class):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.org_uuid = self.org_uuid
|
||||
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)
|
||||
|
||||
# Set up mock response
|
||||
mock_client_instance = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_make_request.return_value = mock_response
|
||||
mock_client_instance.request.return_value = mock_response
|
||||
mock_client_class.return_value.__enter__.return_value = mock_client_instance
|
||||
|
||||
response = self.api.get_tool("test_tool_handle")
|
||||
|
||||
self.assert_request_with_org_id(
|
||||
mock_make_request, "GET", "/crewai_plus/api/v1/tools/test_tool_handle"
|
||||
mock_client_instance, "GET", "/crewai_plus/api/v1/tools/test_tool_handle"
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@@ -130,18 +131,18 @@ class TestPlusAPI(unittest.TestCase):
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@patch("crewai.cli.plus_api.Settings")
|
||||
@patch("requests.Session.request")
|
||||
def test_publish_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
|
||||
@patch("crewai.cli.plus_api.httpx.Client")
|
||||
def test_publish_tool_with_org_uuid(self, mock_client_class, mock_settings_class):
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.org_uuid = self.org_uuid
|
||||
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)
|
||||
|
||||
# Set up mock response
|
||||
mock_client_instance = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_make_request.return_value = mock_response
|
||||
mock_client_instance.request.return_value = mock_response
|
||||
mock_client_class.return_value.__enter__.return_value = mock_client_instance
|
||||
|
||||
handle = "test_tool_handle"
|
||||
public = True
|
||||
@@ -153,7 +154,6 @@ class TestPlusAPI(unittest.TestCase):
|
||||
handle, public, version, description, encoded_file
|
||||
)
|
||||
|
||||
# Expected params including organization_uuid
|
||||
expected_params = {
|
||||
"handle": handle,
|
||||
"public": public,
|
||||
@@ -164,7 +164,7 @@ class TestPlusAPI(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assert_request_with_org_id(
|
||||
mock_make_request, "POST", "/crewai_plus/api/v1/tools", json=expected_params
|
||||
mock_client_instance, "POST", "/crewai_plus/api/v1/tools", json=expected_params
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@@ -195,20 +195,19 @@ class TestPlusAPI(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@patch("crewai.cli.plus_api.requests.Session")
|
||||
def test_make_request(self, mock_session):
|
||||
@patch("crewai.cli.plus_api.httpx.Client")
|
||||
def test_make_request(self, mock_client_class):
|
||||
mock_client_instance = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
|
||||
mock_session_instance = mock_session.return_value
|
||||
mock_session_instance.request.return_value = mock_response
|
||||
mock_client_instance.request.return_value = mock_response
|
||||
mock_client_class.return_value.__enter__.return_value = mock_client_instance
|
||||
|
||||
response = self.api._make_request("GET", "test_endpoint")
|
||||
|
||||
mock_session.assert_called_once()
|
||||
mock_session_instance.request.assert_called_once_with(
|
||||
mock_client_class.assert_called_once_with(trust_env=False, verify=True)
|
||||
mock_client_instance.request.assert_called_once_with(
|
||||
"GET", f"{self.api.base_url}/test_endpoint", headers=self.api.headers
|
||||
)
|
||||
mock_session_instance.trust_env = False
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI._make_request")
|
||||
|
||||
@@ -351,7 +351,7 @@ def test_publish_api_error(
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 500
|
||||
mock_response.json.return_value = {"error": "Internal Server Error"}
|
||||
mock_response.ok = False
|
||||
mock_response.is_success = False
|
||||
mock_publish.return_value = mock_response
|
||||
|
||||
with raises(SystemExit):
|
||||
|
||||
@@ -3,7 +3,7 @@ import subprocess
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import requests
|
||||
import httpx
|
||||
from crewai.cli.triggers.main import TriggersCommand
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class TestTriggersCommand(unittest.TestCase):
|
||||
|
||||
@patch("crewai.cli.triggers.main.console.print")
|
||||
def test_list_triggers_success(self, mock_console_print):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.status_code = 200
|
||||
mock_response.ok = True
|
||||
mock_response.json.return_value = {
|
||||
@@ -50,7 +50,7 @@ class TestTriggersCommand(unittest.TestCase):
|
||||
|
||||
@patch("crewai.cli.triggers.main.console.print")
|
||||
def test_list_triggers_no_apps(self, mock_console_print):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.status_code = 200
|
||||
mock_response.ok = True
|
||||
mock_response.json.return_value = {"apps": []}
|
||||
@@ -81,7 +81,7 @@ class TestTriggersCommand(unittest.TestCase):
|
||||
@patch("crewai.cli.triggers.main.console.print")
|
||||
@patch.object(TriggersCommand, "_run_crew_with_payload")
|
||||
def test_execute_with_trigger_success(self, mock_run_crew, mock_console_print):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.status_code = 200
|
||||
mock_response.ok = True
|
||||
mock_response.json.return_value = {
|
||||
@@ -99,7 +99,7 @@ class TestTriggersCommand(unittest.TestCase):
|
||||
|
||||
@patch("crewai.cli.triggers.main.console.print")
|
||||
def test_execute_with_trigger_not_found(self, mock_console_print):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.status_code = 404
|
||||
mock_response.json.return_value = {"error": "Trigger not found"}
|
||||
self.mock_client.get_trigger_payload.return_value = mock_response
|
||||
@@ -159,7 +159,7 @@ class TestTriggersCommand(unittest.TestCase):
|
||||
|
||||
@patch("crewai.cli.triggers.main.console.print")
|
||||
def test_execute_with_trigger_with_default_error_message(self, mock_console_print):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.status_code = 404
|
||||
mock_response.json.return_value = {}
|
||||
self.mock_client.get_trigger_payload.return_value = mock_response
|
||||
|
||||
@@ -437,17 +437,36 @@ def test_bedrock_aws_credentials_configuration():
|
||||
"""
|
||||
Test that AWS credentials configuration works properly
|
||||
"""
|
||||
aws_access_key_id = "test-access-key"
|
||||
aws_secret_access_key = "test-secret-key"
|
||||
aws_region_name = "us-east-1"
|
||||
|
||||
|
||||
# Test with environment variables
|
||||
with patch.dict(os.environ, {
|
||||
"AWS_ACCESS_KEY_ID": "test-access-key",
|
||||
"AWS_SECRET_ACCESS_KEY": "test-secret-key",
|
||||
"AWS_DEFAULT_REGION": "us-east-1"
|
||||
"AWS_ACCESS_KEY_ID": aws_access_key_id,
|
||||
"AWS_SECRET_ACCESS_KEY": aws_secret_access_key,
|
||||
"AWS_DEFAULT_REGION": aws_region_name
|
||||
}):
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
|
||||
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
assert isinstance(llm, BedrockCompletion)
|
||||
assert llm.region_name == "us-east-1"
|
||||
assert llm.region_name == aws_region_name
|
||||
assert llm.aws_access_key_id == aws_access_key_id
|
||||
assert llm.aws_secret_access_key == aws_secret_access_key
|
||||
|
||||
# Test with litellm environment variables
|
||||
with patch.dict(os.environ, {
|
||||
"AWS_ACCESS_KEY_ID": aws_access_key_id,
|
||||
"AWS_SECRET_ACCESS_KEY": aws_secret_access_key,
|
||||
"AWS_REGION_NAME": aws_region_name
|
||||
}):
|
||||
llm = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
|
||||
|
||||
from crewai.llms.providers.bedrock.completion import BedrockCompletion
|
||||
assert isinstance(llm, BedrockCompletion)
|
||||
assert llm.region_name == aws_region_name
|
||||
|
||||
# Test with explicit credentials
|
||||
llm_explicit = LLM(
|
||||
|
||||
@@ -957,6 +957,47 @@ def test_gemini_agent_kickoff_structured_output_with_tools():
|
||||
|
||||
|
||||
|
||||
@pytest.mark.vcr()
|
||||
def test_gemini_crew_structured_output_with_tools():
|
||||
"""
|
||||
Test that a crew with Gemini can use both tools and output_pydantic on a task.
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from crewai.tools import tool
|
||||
|
||||
class CalculationResult(BaseModel):
|
||||
operation: str = Field(description="The mathematical operation performed")
|
||||
result: int = Field(description="The result of the calculation")
|
||||
explanation: str = Field(description="Brief explanation of the calculation")
|
||||
|
||||
@tool
|
||||
def add_numbers(a: int, b: int) -> int:
|
||||
"""Add two numbers together and return the sum."""
|
||||
return a + b
|
||||
|
||||
agent = Agent(
|
||||
role="Calculator",
|
||||
goal="Perform calculations using available tools",
|
||||
backstory="You are a calculator assistant that uses tools to compute results.",
|
||||
llm=LLM(model="google/gemini-2.0-flash-001"),
|
||||
tools=[add_numbers],
|
||||
)
|
||||
|
||||
task = Task(
|
||||
description="Calculate 15 + 27 using your add_numbers tool. Report the result.",
|
||||
expected_output="A structured calculation result",
|
||||
output_pydantic=CalculationResult,
|
||||
agent=agent,
|
||||
)
|
||||
|
||||
crew = Crew(agents=[agent], tasks=[task])
|
||||
result = crew.kickoff()
|
||||
|
||||
assert result.pydantic is not None, "Expected pydantic output but got None"
|
||||
assert isinstance(result.pydantic, CalculationResult)
|
||||
assert result.pydantic.result == 42, f"Expected 42 but got {result.pydantic.result}"
|
||||
|
||||
|
||||
def test_gemini_stop_words_not_applied_to_structured_output():
|
||||
"""
|
||||
Test that stop words are NOT applied when response_model is provided.
|
||||
|
||||
373
lib/crewai/tests/mcp/test_amp_mcp.py
Normal file
373
lib/crewai/tests/mcp/test_amp_mcp.py
Normal file
@@ -0,0 +1,373 @@
|
||||
"""Tests for AMP MCP config fetching and tool resolution."""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.mcp.config import MCPServerHTTP, MCPServerSSE
|
||||
from crewai.mcp.tool_resolver import MCPToolResolver
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent():
|
||||
return Agent(
|
||||
role="Test Agent",
|
||||
goal="Test goal",
|
||||
backstory="Test backstory",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def resolver(agent):
|
||||
return MCPToolResolver(agent=agent, logger=agent._logger)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_tool_definitions():
|
||||
return [
|
||||
{
|
||||
"name": "search",
|
||||
"description": "Search tool",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query"}
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "create_page",
|
||||
"description": "Create a page",
|
||||
"inputSchema": {},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class TestBuildMCPConfigFromDict:
|
||||
def test_builds_http_config(self):
|
||||
config_dict = {
|
||||
"type": "http",
|
||||
"url": "https://mcp.example.com/api",
|
||||
"headers": {"Authorization": "Bearer token123"},
|
||||
"streamable": True,
|
||||
"cache_tools_list": False,
|
||||
}
|
||||
|
||||
result = MCPToolResolver._build_mcp_config_from_dict(config_dict)
|
||||
|
||||
assert isinstance(result, MCPServerHTTP)
|
||||
assert result.url == "https://mcp.example.com/api"
|
||||
assert result.headers == {"Authorization": "Bearer token123"}
|
||||
assert result.streamable is True
|
||||
assert result.cache_tools_list is False
|
||||
|
||||
def test_builds_sse_config(self):
|
||||
config_dict = {
|
||||
"type": "sse",
|
||||
"url": "https://mcp.example.com/sse",
|
||||
"headers": {"Authorization": "Bearer token123"},
|
||||
"cache_tools_list": True,
|
||||
}
|
||||
|
||||
result = MCPToolResolver._build_mcp_config_from_dict(config_dict)
|
||||
|
||||
assert isinstance(result, MCPServerSSE)
|
||||
assert result.url == "https://mcp.example.com/sse"
|
||||
assert result.headers == {"Authorization": "Bearer token123"}
|
||||
assert result.cache_tools_list is True
|
||||
|
||||
def test_defaults_to_http(self):
|
||||
config_dict = {
|
||||
"url": "https://mcp.example.com/api",
|
||||
}
|
||||
|
||||
result = MCPToolResolver._build_mcp_config_from_dict(config_dict)
|
||||
|
||||
assert isinstance(result, MCPServerHTTP)
|
||||
assert result.streamable is True
|
||||
|
||||
def test_http_defaults(self):
|
||||
config_dict = {
|
||||
"type": "http",
|
||||
"url": "https://mcp.example.com/api",
|
||||
}
|
||||
|
||||
result = MCPToolResolver._build_mcp_config_from_dict(config_dict)
|
||||
|
||||
assert result.headers is None
|
||||
assert result.streamable is True
|
||||
assert result.cache_tools_list is False
|
||||
|
||||
|
||||
class TestFetchAmpMCPConfigs:
|
||||
@patch("crewai.cli.plus_api.PlusAPI")
|
||||
@patch("crewai_tools.tools.crewai_platform_tools.misc.get_platform_integration_token", return_value="test-api-key")
|
||||
def test_fetches_configs_successfully(self, mock_get_token, mock_plus_api_class, resolver):
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"configs": {
|
||||
"notion": {
|
||||
"type": "sse",
|
||||
"url": "https://mcp.notion.so/sse",
|
||||
"headers": {"Authorization": "Bearer notion-token"},
|
||||
},
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.github.com/api",
|
||||
"headers": {"Authorization": "Bearer gh-token"},
|
||||
},
|
||||
},
|
||||
}
|
||||
mock_plus_api = MagicMock()
|
||||
mock_plus_api.get_mcp_configs.return_value = mock_response
|
||||
mock_plus_api_class.return_value = mock_plus_api
|
||||
|
||||
result = resolver._fetch_amp_mcp_configs(["notion", "github"])
|
||||
|
||||
assert "notion" in result
|
||||
assert "github" in result
|
||||
assert result["notion"]["url"] == "https://mcp.notion.so/sse"
|
||||
mock_plus_api_class.assert_called_once_with(api_key="test-api-key")
|
||||
mock_plus_api.get_mcp_configs.assert_called_once_with(["notion", "github"])
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI")
|
||||
@patch("crewai_tools.tools.crewai_platform_tools.misc.get_platform_integration_token", return_value="test-api-key")
|
||||
def test_omits_missing_slugs(self, mock_get_token, mock_plus_api_class, resolver):
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"configs": {"notion": {"type": "sse", "url": "https://mcp.notion.so/sse"}},
|
||||
}
|
||||
mock_plus_api = MagicMock()
|
||||
mock_plus_api.get_mcp_configs.return_value = mock_response
|
||||
mock_plus_api_class.return_value = mock_plus_api
|
||||
|
||||
result = resolver._fetch_amp_mcp_configs(["notion", "missing-server"])
|
||||
|
||||
assert "notion" in result
|
||||
assert "missing-server" not in result
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI")
|
||||
@patch("crewai_tools.tools.crewai_platform_tools.misc.get_platform_integration_token", return_value="test-api-key")
|
||||
def test_returns_empty_on_http_error(self, mock_get_token, mock_plus_api_class, resolver):
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 500
|
||||
mock_plus_api = MagicMock()
|
||||
mock_plus_api.get_mcp_configs.return_value = mock_response
|
||||
mock_plus_api_class.return_value = mock_plus_api
|
||||
|
||||
result = resolver._fetch_amp_mcp_configs(["notion"])
|
||||
|
||||
assert result == {}
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI")
|
||||
@patch("crewai_tools.tools.crewai_platform_tools.misc.get_platform_integration_token", return_value="test-api-key")
|
||||
def test_returns_empty_on_network_error(self, mock_get_token, mock_plus_api_class, resolver):
|
||||
import httpx
|
||||
|
||||
mock_plus_api = MagicMock()
|
||||
mock_plus_api.get_mcp_configs.side_effect = httpx.ConnectError("Connection refused")
|
||||
mock_plus_api_class.return_value = mock_plus_api
|
||||
|
||||
result = resolver._fetch_amp_mcp_configs(["notion"])
|
||||
|
||||
assert result == {}
|
||||
|
||||
@patch("crewai_tools.tools.crewai_platform_tools.misc.get_platform_integration_token", side_effect=Exception("No token"))
|
||||
def test_returns_empty_when_no_token(self, mock_get_token, resolver):
|
||||
result = resolver._fetch_amp_mcp_configs(["notion"])
|
||||
|
||||
assert result == {}
|
||||
|
||||
|
||||
class TestParseAmpRef:
|
||||
def test_bare_slug(self):
|
||||
slug, tool = MCPToolResolver._parse_amp_ref("notion")
|
||||
assert slug == "notion"
|
||||
assert tool is None
|
||||
|
||||
def test_bare_slug_with_tool(self):
|
||||
slug, tool = MCPToolResolver._parse_amp_ref("notion#search")
|
||||
assert slug == "notion"
|
||||
assert tool == "search"
|
||||
|
||||
def test_bare_slug_with_empty_tool(self):
|
||||
slug, tool = MCPToolResolver._parse_amp_ref("notion#")
|
||||
assert slug == "notion"
|
||||
assert tool is None
|
||||
|
||||
def test_legacy_prefix_slug(self):
|
||||
slug, tool = MCPToolResolver._parse_amp_ref("crewai-amp:notion")
|
||||
assert slug == "notion"
|
||||
assert tool is None
|
||||
|
||||
def test_legacy_prefix_with_tool(self):
|
||||
slug, tool = MCPToolResolver._parse_amp_ref("crewai-amp:notion#search")
|
||||
assert slug == "notion"
|
||||
assert tool == "search"
|
||||
|
||||
|
||||
class TestGetMCPToolsAmpIntegration:
|
||||
@patch("crewai.mcp.tool_resolver.MCPClient")
|
||||
@patch.object(MCPToolResolver, "_fetch_amp_mcp_configs")
|
||||
def test_single_request_for_multiple_amp_refs(
|
||||
self, mock_fetch, mock_client_class, agent, mock_tool_definitions
|
||||
):
|
||||
mock_fetch.return_value = {
|
||||
"notion": {
|
||||
"type": "sse",
|
||||
"url": "https://mcp.notion.so/sse",
|
||||
"headers": {"Authorization": "Bearer token"},
|
||||
},
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.github.com/api",
|
||||
"headers": {"Authorization": "Bearer gh-token"},
|
||||
"streamable": True,
|
||||
},
|
||||
}
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False
|
||||
mock_client.connect = AsyncMock()
|
||||
mock_client.disconnect = AsyncMock()
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
tools = agent.get_mcp_tools(["notion", "github"])
|
||||
|
||||
mock_fetch.assert_called_once_with(["notion", "github"])
|
||||
assert len(tools) == 4 # 2 tools per server
|
||||
|
||||
@patch("crewai.mcp.tool_resolver.MCPClient")
|
||||
@patch.object(MCPToolResolver, "_fetch_amp_mcp_configs")
|
||||
def test_tool_filter_with_hash_syntax(
|
||||
self, mock_fetch, mock_client_class, agent, mock_tool_definitions
|
||||
):
|
||||
mock_fetch.return_value = {
|
||||
"notion": {
|
||||
"type": "sse",
|
||||
"url": "https://mcp.notion.so/sse",
|
||||
"headers": {"Authorization": "Bearer token"},
|
||||
},
|
||||
}
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False
|
||||
mock_client.connect = AsyncMock()
|
||||
mock_client.disconnect = AsyncMock()
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
tools = agent.get_mcp_tools(["notion#search"])
|
||||
|
||||
mock_fetch.assert_called_once_with(["notion"])
|
||||
assert len(tools) == 1
|
||||
assert tools[0].name == "mcp_notion_so_sse_search"
|
||||
|
||||
@patch("crewai.mcp.tool_resolver.MCPClient")
|
||||
@patch.object(MCPToolResolver, "_fetch_amp_mcp_configs")
|
||||
def test_deduplicates_slugs(
|
||||
self, mock_fetch, mock_client_class, agent, mock_tool_definitions
|
||||
):
|
||||
mock_fetch.return_value = {
|
||||
"notion": {
|
||||
"type": "sse",
|
||||
"url": "https://mcp.notion.so/sse",
|
||||
"headers": {"Authorization": "Bearer token"},
|
||||
},
|
||||
}
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False
|
||||
mock_client.connect = AsyncMock()
|
||||
mock_client.disconnect = AsyncMock()
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
tools = agent.get_mcp_tools(["notion#search", "notion#create_page"])
|
||||
|
||||
mock_fetch.assert_called_once_with(["notion"])
|
||||
assert len(tools) == 2
|
||||
|
||||
@patch.object(MCPToolResolver, "_fetch_amp_mcp_configs")
|
||||
def test_skips_missing_configs_gracefully(self, mock_fetch, agent):
|
||||
mock_fetch.return_value = {}
|
||||
|
||||
tools = agent.get_mcp_tools(["missing-server"])
|
||||
|
||||
assert tools == []
|
||||
|
||||
@patch("crewai.mcp.tool_resolver.MCPClient")
|
||||
@patch.object(MCPToolResolver, "_fetch_amp_mcp_configs")
|
||||
def test_legacy_crewai_amp_prefix_still_works(
|
||||
self, mock_fetch, mock_client_class, agent, mock_tool_definitions
|
||||
):
|
||||
mock_fetch.return_value = {
|
||||
"notion": {
|
||||
"type": "sse",
|
||||
"url": "https://mcp.notion.so/sse",
|
||||
"headers": {"Authorization": "Bearer token"},
|
||||
},
|
||||
}
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False
|
||||
mock_client.connect = AsyncMock()
|
||||
mock_client.disconnect = AsyncMock()
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
tools = agent.get_mcp_tools(["crewai-amp:notion"])
|
||||
|
||||
mock_fetch.assert_called_once_with(["notion"])
|
||||
assert len(tools) == 2
|
||||
|
||||
@patch("crewai.mcp.tool_resolver.MCPClient")
|
||||
@patch.object(MCPToolResolver, "_fetch_amp_mcp_configs")
|
||||
@patch.object(MCPToolResolver, "_resolve_external")
|
||||
def test_non_amp_items_unaffected(
|
||||
self,
|
||||
mock_external,
|
||||
mock_fetch,
|
||||
mock_client_class,
|
||||
agent,
|
||||
mock_tool_definitions,
|
||||
):
|
||||
mock_fetch.return_value = {
|
||||
"notion": {
|
||||
"type": "sse",
|
||||
"url": "https://mcp.notion.so/sse",
|
||||
},
|
||||
}
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False
|
||||
mock_client.connect = AsyncMock()
|
||||
mock_client.disconnect = AsyncMock()
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
mock_external_tool = MagicMock(spec=BaseTool)
|
||||
mock_external.return_value = [mock_external_tool]
|
||||
|
||||
http_config = MCPServerHTTP(
|
||||
url="https://other.mcp.com/api",
|
||||
headers={"Authorization": "Bearer other"},
|
||||
)
|
||||
|
||||
tools = agent.get_mcp_tools(
|
||||
[
|
||||
"notion",
|
||||
"https://external.mcp.com/api",
|
||||
http_config,
|
||||
]
|
||||
)
|
||||
|
||||
mock_fetch.assert_called_once_with(["notion"])
|
||||
mock_external.assert_called_once_with("https://external.mcp.com/api")
|
||||
# 2 from notion + 1 from external + 2 from http_config
|
||||
assert len(tools) == 5
|
||||
@@ -1,5 +1,5 @@
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from crewai.agent.core import Agent
|
||||
@@ -46,7 +46,7 @@ def test_agent_with_stdio_mcp_config(mock_tool_definitions):
|
||||
)
|
||||
|
||||
|
||||
with patch("crewai.agent.core.MCPClient") as mock_client_class:
|
||||
with patch("crewai.mcp.tool_resolver.MCPClient") as mock_client_class:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False # Will trigger connect
|
||||
@@ -82,7 +82,7 @@ def test_agent_with_http_mcp_config(mock_tool_definitions):
|
||||
mcps=[http_config],
|
||||
)
|
||||
|
||||
with patch("crewai.agent.core.MCPClient") as mock_client_class:
|
||||
with patch("crewai.mcp.tool_resolver.MCPClient") as mock_client_class:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False # Will trigger connect
|
||||
@@ -117,7 +117,7 @@ def test_agent_with_sse_mcp_config(mock_tool_definitions):
|
||||
mcps=[sse_config],
|
||||
)
|
||||
|
||||
with patch("crewai.agent.core.MCPClient") as mock_client_class:
|
||||
with patch("crewai.mcp.tool_resolver.MCPClient") as mock_client_class:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False
|
||||
@@ -141,7 +141,7 @@ def test_mcp_tool_execution_in_sync_context(mock_tool_definitions):
|
||||
"""Test MCPNativeTool execution in synchronous context (normal crew execution)."""
|
||||
http_config = MCPServerHTTP(url="https://api.example.com/mcp")
|
||||
|
||||
with patch("crewai.agent.core.MCPClient") as mock_client_class:
|
||||
with patch("crewai.mcp.tool_resolver.MCPClient") as mock_client_class:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False
|
||||
@@ -173,7 +173,7 @@ async def test_mcp_tool_execution_in_async_context(mock_tool_definitions):
|
||||
"""Test MCPNativeTool execution in async context (e.g., from a Flow)."""
|
||||
http_config = MCPServerHTTP(url="https://api.example.com/mcp")
|
||||
|
||||
with patch("crewai.agent.core.MCPClient") as mock_client_class:
|
||||
with patch("crewai.mcp.tool_resolver.MCPClient") as mock_client_class:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.list_tools = AsyncMock(return_value=mock_tool_definitions)
|
||||
mock_client.connected = False
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user