Compare commits

...

21 Commits

Author SHA1 Message Date
Greyson LaLonde
8bfdb188f7 feat: bump versions to 1.10.1a1 2026-02-27 09:44:47 -05:00
João Moura
1bdb9496a3 refactor: update step callback methods to support asynchronous invocation (#4633)
* refactor: update step callback methods to support asynchronous invocation

- Replaced synchronous step callback invocations with asynchronous counterparts in the CrewAgentExecutor class.
- Introduced a new async method _ainvoke_step_callback to handle step callbacks in an async context, improving responsiveness and performance in asynchronous workflows.

* chore: bump version to 1.10.1b1 across multiple files

- Updated version strings from 1.10.1b to 1.10.1b1 in various project files including pyproject.toml and __init__.py files.
- Adjusted dependency specifications to reflect the new version in relevant templates and modules.
2026-02-27 07:35:03 -03:00
Joao Moura
979aa26c3d bump new alpha version 2026-02-27 01:43:33 -08:00
João Moura
514c082882 refactor: implement lazy loading for heavy dependencies in Memory module (#4632)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
- Introduced lazy imports for the Memory and EncodingFlow classes to optimize import time and reduce initial load, particularly beneficial for deployment scenarios like Celery pre-fork.
- Updated the Memory class to include new configuration options for aggregation queries, enhancing its functionality.
- Adjusted the __getattr__ method in both the crewai and memory modules to support lazy loading of specified attributes.
2026-02-27 03:20:02 -03:00
Greyson LaLonde
c9e8068578 docs: update changelog and version for v1.10.0 2026-02-26 19:14:25 -05:00
Greyson LaLonde
df2778f08b fix: make branch for release notes
Some checks failed
Build uv cache / build-cache (3.10) (push) Waiting to run
Build uv cache / build-cache (3.11) (push) Waiting to run
Build uv cache / build-cache (3.12) (push) Waiting to run
Build uv cache / build-cache (3.13) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
2026-02-26 18:49:13 -05:00
Greyson LaLonde
d8fea2518d feat: bump versions to 1.10.0
* feat: bump versions to 1.10.0

* chore: update tool specifications

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
2026-02-26 18:31:14 -05:00
Lucas Gomide
d259150d8d Enhance MCP tool resolution and related events (#4580)
* feat: enhance MCP tool resolution

* feat: emit event when MCP configuration fails

* feat: emit event when MCP tool execution has failed

* style: resolve linter issues

* refactor: use clear and natural mcp tool name resolution

* test: fix broken tests

* fix: resolve MCP connection leaks, slug validation, duplicate connections, and httpx exception handling

---------

Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com>
Co-authored-by: Greyson LaLonde <greyson@crewai.com>
2026-02-26 13:59:30 -08:00
Greyson LaLonde
c4a328c9d5 fix: validate tool kwargs even when empty to prevent cryptic TypeError (#4611) 2026-02-26 16:18:03 -05:00
Greyson LaLonde
373abbb6b7 fix: add dict overload to build_embedder and type default embedder 2026-02-26 16:04:28 -05:00
João Moura
86d3ee022d feat: update lancedb version and add lance-namespace packages
* chore(deps): update lancedb version and add lance-namespace packages

- Updated lancedb dependency version from 0.4.0 to 0.29.2 in multiple files.
- Added new packages: lance-namespace and lance-namespace-urllib3-client with version 0.5.2, including their dependencies and installation details.
- Enhanced MemoryTUI to display a limit on entries and improved the LanceDBStorage class with automatic background compaction and index creation for better performance.

* linter

* refactor: update memory recall limit and formatting in Agent class

- Reduced the memory recall limit from 10 to 5 in multiple locations within the Agent class.
- Updated the memory formatting to use a new `format` method in the MemoryMatch class for improved readability and metadata inclusion.

* refactor: enhance memory handling with read-only support

- Updated memory-related classes and methods to support read-only functionality, allowing for silent no-ops when attempting to remember data in read-only mode.
- Modified the LiteAgent and CrewAgentExecutorMixin classes to check for read-only status before saving memories.
- Adjusted MemorySlice and Memory classes to reflect changes in behavior when read-only is enabled.
- Updated tests to verify that memory operations behave correctly under read-only conditions.

* test: set mock memory to read-write in unit tests

- Updated unit tests in test_unified_memory.py to set mock_memory._read_only to False, ensuring that memory operations can be tested in a writable state.

* fix test

* fix: preserve falsy metadata values and fix remember() return type

---------

Co-authored-by: lorenzejay <lorenzejaytech@gmail.com>
Co-authored-by: Greyson LaLonde <greyson@crewai.com>
2026-02-26 15:05:10 -05:00
Lucas Gomide
09e3b81ca3 fix: preserve null types in tool parameter schemas for LLM (#4579)
* fix: preserve null types in tool parameter schemas for LLM

Tool parameter schemas were stripping null from optional fields via
generate_model_description, forcing the LLM to provide non-null values
for fields.
Adds strip_null_types parameter to generate_model_description and passes False when generating tool
schemas, so optional fields keep anyOf: [{type: T}, {type: null}]

* Update lib/crewai/src/crewai/utilities/pydantic_schema_utils.py

Co-authored-by: Gabe Milani <gabriel@crewai.com>

---------

Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com>
Co-authored-by: Gabe Milani <gabriel@crewai.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
2026-02-26 11:51:34 -05:00
Heitor Carvalho
b6d8ce5c55 docs: add litellm dependency note for non-native LLM providers (#4600)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
2026-02-26 10:57:37 -03:00
Greyson LaLonde
b371f97a2f fix: map output_pydantic/output_json to native structured output
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* fix: map output_pydantic/output_json to native structured output

* test: add crew+tools+structured output integration test for Gemini

* fix: re-record stale cassette for test_crew_testing_function

* fix: re-record remaining stale cassettes for native structured output

* fix: enable native structured output for lite agent and fix mypy errors
2026-02-25 17:13:34 -05:00
dependabot[bot]
017189db78 chore(deps): bump nltk in the security-updates group across 1 directory (#4598)
Bumps the security-updates group with 1 update in the / directory: [nltk](https://github.com/nltk/nltk).


Updates `nltk` from 3.9.2 to 3.9.3
- [Changelog](https://github.com/nltk/nltk/blob/develop/ChangeLog)
- [Commits](https://github.com/nltk/nltk/compare/3.9.2...3.9.3)

---
updated-dependencies:
- dependency-name: nltk
  dependency-version: 3.9.3
  dependency-type: indirect
  dependency-group: security-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 15:37:21 -06:00
dependabot[bot]
02d911494f chore(deps): bump cryptography (#4506)
Bumps the security-updates group with 1 update in the / directory: [cryptography](https://github.com/pyca/cryptography).


Updates `cryptography` from 46.0.4 to 46.0.5
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.4...46.0.5)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.5
  dependency-type: indirect
  dependency-group: security-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-25 15:04:07 -06:00
João Moura
8102d0a6ca feat: enhance JSON argument parsing and validation in CrewAgentExecutor and BaseTool
* feat: enhance JSON argument parsing and validation in CrewAgentExecutor and BaseTool

- Added error handling for malformed JSON tool arguments in CrewAgentExecutor, providing descriptive error messages.
- Implemented schema validation for tool arguments in BaseTool, ensuring that invalid arguments raise appropriate exceptions.
- Introduced tests to verify correct behavior for both valid and invalid JSON inputs, enhancing robustness of tool execution.

* refactor: improve argument validation in BaseTool

- Introduced a new private method  to handle argument validation for tools, enhancing code clarity and reusability.
- Updated the  method to utilize the new validation method, ensuring consistent error handling for invalid arguments.
- Enhanced exception handling to specifically catch , providing clearer error messages for tool argument validation failures.

* feat: introduce parse_tool_call_args for improved argument parsing

- Added a new utility function, parse_tool_call_args, to handle parsing of tool call arguments from JSON strings or dictionaries, enhancing error handling for malformed JSON inputs.
- Updated CrewAgentExecutor and AgentExecutor to utilize the new parsing function, streamlining argument validation and improving clarity in error reporting.
- Introduced unit tests for parse_tool_call_args to ensure robust functionality and correct handling of various input scenarios.

* feat: add keyword argument validation in BaseTool and Tool classes

- Introduced a new method `_validate_kwargs` in BaseTool to validate keyword arguments against the defined schema, ensuring proper argument handling.
- Updated the `run` and `arun` methods in both BaseTool and Tool classes to utilize the new validation method, improving error handling and robustness.
- Added comprehensive tests for asynchronous execution in `TestBaseToolArunValidation` to verify correct behavior for valid and invalid keyword arguments.

* Potential fix for pull request finding 'Syntax error'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

---------

Co-authored-by: lorenzejay <lorenzejaytech@gmail.com>
Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-02-25 13:13:31 -05:00
Greyson LaLonde
ee374d01de chore: add versioning logic for devtools 2026-02-25 12:13:00 -05:00
Greyson LaLonde
9914e51199 feat: add versioned docs
starting with 1.10.0
2026-02-25 11:05:31 -05:00
nicoferdi96
2dbb83ae31 Private package registry (#4583)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
adding reference and explaination for package registry

Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
2026-02-24 19:37:17 +01:00
Mike Plachta
7377e1aa26 fix: bedrock region was always set to "us-east-1" not respecting the env var. (#4582)
* fix: bedrock region was always set to "us-east-1" not respecting the env
var.

code had AWS_REGION_NAME referenced, but not used, unified to
AWS_DEFAULT_REGION as per documentation

* DRY code improvement and fix caught by tests.

* Supporting litellm configuration
2026-02-24 09:59:01 -08:00
102 changed files with 9443 additions and 4242 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}"}'

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,56 @@ description: "Product updates, improvements, and bug fixes for CrewAI"
icon: "clock"
mode: "wide"
---
<Update label="Feb 26, 2026">
## v1.10.0
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.10.0)
## What's Changed
### Features
- Enhance MCP tool resolution and related events
- Update lancedb version and add lance-namespace packages
- Enhance JSON argument parsing and validation in CrewAgentExecutor and BaseTool
- Migrate CLI HTTP client from requests to httpx
- Add versioned documentation
- Add yanked detection for version notes
- Implement user input handling in Flows
- Enhance HITL self-loop functionality in human feedback integration tests
- Add started_event_id and set in eventbus
- Auto update tools.specs
### Bug Fixes
- Validate tool kwargs even when empty to prevent cryptic TypeError
- Preserve null types in tool parameter schemas for LLM
- Map output_pydantic/output_json to native structured output
- Ensure callbacks are ran/awaited if promise
- Capture method name in exception context
- Preserve enum type in router result; improve types
- Fix cyclic flows silently breaking when persistence ID is passed in inputs
- Correct CLI flag format from --skip-provider to --skip_provider
- Ensure OpenAI tool call stream is finalized
- Resolve complex schema $ref pointers in MCP tools
- Enforce additionalProperties=false in schemas
- Reject reserved script names for crew folders
- Resolve race condition in guardrail event emission test
### Documentation
- Add litellm dependency note for non-native LLM providers
- Clarify NL2SQL security model and hardening guidance
- Add 96 missing actions across 9 integrations
### Refactoring
- Refactor crew to provider
- Extract HITL to provider pattern
- Improve hook typing and registration
## Contributors
@dependabot[bot], @github-actions[bot], @github-code-quality[bot], @greysonlalonde, @heitorado, @hobostay, @joaomdmoura, @johnvan7, @jonathansampson, @lorenzejay, @lucasgomide, @mattatcha, @mplachta, @nicoferdi96, @theCyberTech, @thiagomoretto, @vinibrsl
</Update>
<Update label="Jan 26, 2026">
## v1.9.0

View File

@@ -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>

View File

@@ -177,6 +177,11 @@ You need to push your crew to a GitHub repository. If you haven't created a crew
![Set Environment Variables](/images/enterprise/set-env-variables.png)
</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">

View File

@@ -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.

View 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>

View File

@@ -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:

View File

@@ -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:

View File

@@ -4,6 +4,56 @@ description: "CrewAI의 제품 업데이트, 개선 사항 및 버그 수정"
icon: "clock"
mode: "wide"
---
<Update label="2026년 2월 26일">
## v1.10.0
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.10.0)
## 변경 사항
### 기능
- MCP 도구 해상도 및 관련 이벤트 개선
- lancedb 버전 업데이트 및 lance-namespace 패키지 추가
- CrewAgentExecutor 및 BaseTool에서 JSON 인수 파싱 및 검증 개선
- CLI HTTP 클라이언트를 requests에서 httpx로 마이그레이션
- 버전화된 문서 추가
- 버전 노트에 대한 yanked 감지 추가
- Flows에서 사용자 입력 처리 구현
- 인간 피드백 통합 테스트에서 HITL 자기 루프 기능 개선
- eventbus에 started_event_id 추가 및 설정
- tools.specs 자동 업데이트
### 버그 수정
- 빈 경우에도 도구 kwargs를 검증하여 모호한 TypeError 방지
- LLM을 위한 도구 매개변수 스키마에서 null 타입 유지
- output_pydantic/output_json을 네이티브 구조화된 출력으로 매핑
- 약속이 있는 경우 콜백이 실행/대기되도록 보장
- 예외 컨텍스트에서 메서드 이름 캡처
- 라우터 결과에서 enum 타입 유지; 타입 개선
- 입력으로 지속성 ID가 전달될 때 조용히 깨지는 순환 흐름 수정
- CLI 플래그 형식을 --skip-provider에서 --skip_provider로 수정
- OpenAI 도구 호출 스트림이 완료되도록 보장
- MCP 도구에서 복잡한 스키마 $ref 포인터 해결
- 스키마에서 additionalProperties=false 강제 적용
- 크루 폴더에 대해 예약된 스크립트 이름 거부
- 가드레일 이벤트 방출 테스트에서 경쟁 조건 해결
### 문서
- 비네이티브 LLM 공급자를 위한 litellm 종속성 노트 추가
- NL2SQL 보안 모델 및 강화 지침 명확화
- 9개 통합에서 96개의 누락된 작업 추가
### 리팩토링
- crew를 provider로 리팩토링
- HITL을 provider 패턴으로 추출
- 훅 타이핑 및 등록 개선
## 기여자
@dependabot[bot], @github-actions[bot], @github-code-quality[bot], @greysonlalonde, @heitorado, @hobostay, @joaomdmoura, @johnvan7, @jonathansampson, @lorenzejay, @lucasgomide, @mattatcha, @mplachta, @nicoferdi96, @theCyberTech, @thiagomoretto, @vinibrsl
</Update>
<Update label="2026년 1월 26일">
## v1.9.0

View File

@@ -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>

View File

@@ -176,6 +176,11 @@ Crew를 GitHub 저장소에 푸시해야 합니다. 아직 Crew를 만들지 않
![Set Environment Variables](/images/enterprise/set-env-variables.png)
</Frame>
<Info>
프라이빗 Python 패키지를 사용하시나요? 여기에 레지스트리 자격 증명도 추가해야 합니다.
필요한 변수는 [프라이빗 패키지 레지스트리](/ko/enterprise/guides/private-package-registry)를 참조하세요.
</Info>
</Step>
<Step title="Crew 배포하기">

View File

@@ -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>
구성 문제를 조기에 발견하기 위해 배포 전에 동일한 환경 변수로
로컬에서 프로젝트를 테스트하세요.

View 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>

View File

@@ -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을 사용하려면 여러 가지 방법이 있습니다:

View File

@@ -35,7 +35,7 @@ crewai login
아직 설치하지 않았다면 CLI 도구와 함께 CrewAI를 설치하세요:
```bash
uv add crewai[tools]
uv add 'crewai[tools]'
```
그런 다음 CrewAI AMP 계정으로 CLI를 인증하세요:

View File

@@ -4,6 +4,56 @@ description: "Atualizações de produto, melhorias e correções do CrewAI"
icon: "clock"
mode: "wide"
---
<Update label="26 fev 2026">
## v1.10.0
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.10.0)
## O que Mudou
### Recursos
- Aprimorar a resolução da ferramenta MCP e eventos relacionados
- Atualizar a versão do lancedb e adicionar pacotes lance-namespace
- Aprimorar a análise e validação de argumentos JSON no CrewAgentExecutor e BaseTool
- Migrar o cliente HTTP da CLI de requests para httpx
- Adicionar documentação versionada
- Adicionar detecção de versões removidas para notas de versão
- Implementar tratamento de entrada do usuário em Flows
- Aprimorar a funcionalidade de auto-loop HITL nos testes de integração de feedback humano
- Adicionar started_event_id e definir no eventbus
- Atualizar automaticamente tools.specs
### Correções de Bugs
- Validar kwargs da ferramenta mesmo quando vazios para evitar TypeError crípticos
- Preservar tipos nulos nos esquemas de parâmetros da ferramenta para LLM
- Mapear output_pydantic/output_json para saída estruturada nativa
- Garantir que callbacks sejam executados/aguardados se forem promessas
- Capturar o nome do método no contexto da exceção
- Preservar tipo enum no resultado do roteador; melhorar tipos
- Corrigir fluxos cíclicos que quebram silenciosamente quando o ID de persistência é passado nas entradas
- Corrigir o formato da flag da CLI de --skip-provider para --skip_provider
- Garantir que o fluxo de chamada da ferramenta OpenAI seja finalizado
- Resolver ponteiros $ref de esquema complexos nas ferramentas MCP
- Impor additionalProperties=false nos esquemas
- Rejeitar nomes de scripts reservados para pastas de equipe
- Resolver condição de corrida no teste de emissão de eventos de guardrail
### Documentação
- Adicionar nota de dependência litellm para provedores de LLM não nativos
- Esclarecer o modelo de segurança NL2SQL e orientações de fortalecimento
- Adicionar 96 ações ausentes em 9 integrações
### Refatoração
- Refatorar crew para provider
- Extrair HITL para padrão de provider
- Melhorar tipagem e registro de hooks
## Contribuidores
@dependabot[bot], @github-actions[bot], @github-code-quality[bot], @greysonlalonde, @heitorado, @hobostay, @joaomdmoura, @johnvan7, @jonathansampson, @lorenzejay, @lucasgomide, @mattatcha, @mplachta, @nicoferdi96, @theCyberTech, @thiagomoretto, @vinibrsl
</Update>
<Update label="26 jan 2026">
## v1.9.0

View File

@@ -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>

View File

@@ -176,6 +176,11 @@ Você precisa enviar seu crew para um repositório do GitHub. Caso ainda não te
![Definir Variáveis de Ambiente](/images/enterprise/set-env-variables.png)
</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">

View File

@@ -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.

View 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>

View File

@@ -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:

View File

@@ -152,4 +152,4 @@ __all__ = [
"wrap_file_source",
]
__version__ = "1.9.3"
__version__ = "1.10.1a1"

View File

@@ -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.1a1",
"tiktoken~=0.8.0",
"beautifulsoup4~=4.13.4",
"python-docx~=1.2.0",

View File

@@ -291,4 +291,4 @@ __all__ = [
"ZapierActionTools",
]
__version__ = "1.9.3"
__version__ = "1.10.1a1"

View File

@@ -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": [
{

View File

@@ -42,7 +42,7 @@ dependencies = [
"mcp~=1.26.0",
"uv~=0.9.13",
"aiosqlite~=0.21.0",
"lancedb>=0.4.0",
"lancedb>=0.29.2",
]
[project.urls]
@@ -53,7 +53,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
[project.optional-dependencies]
tools = [
"crewai-tools==1.9.3",
"crewai-tools==1.10.1a1",
]
embeddings = [
"tiktoken~=0.8.0"

View File

@@ -10,7 +10,6 @@ from crewai.flow.flow import Flow
from crewai.knowledge.knowledge import Knowledge
from crewai.llm import LLM
from crewai.llms.base_llm import BaseLLM
from crewai.memory.unified_memory import Memory
from crewai.process import Process
from crewai.task import Task
from crewai.tasks.llm_guardrail import LLMGuardrail
@@ -41,7 +40,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
_suppress_pydantic_deprecation_warnings()
__version__ = "1.9.3"
__version__ = "1.10.1a1"
_telemetry_submitted = False
@@ -72,6 +71,25 @@ def _track_install_async() -> None:
_track_install_async()
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
"Memory": ("crewai.memory.unified_memory", "Memory"),
}
def __getattr__(name: str) -> Any:
"""Lazily import heavy modules (e.g. Memory → lancedb) on first access."""
if name in _LAZY_IMPORTS:
module_path, attr = _LAZY_IMPORTS[name]
import importlib
mod = importlib.import_module(module_path)
val = getattr(mod, attr)
globals()[name] = val
return val
raise AttributeError(f"module 'crewai' has no attribute {name!r}")
__all__ = [
"LLM",
"Agent",

View File

@@ -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)

View File

@@ -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:

View File

@@ -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')}"

View File

@@ -50,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,
)
@@ -894,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 []:
@@ -1262,7 +1259,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
formatted_answer, tool_result
)
self._invoke_step_callback(formatted_answer) # type: ignore[arg-type]
await self._ainvoke_step_callback(formatted_answer) # type: ignore[arg-type]
self._append_message(formatted_answer.text) # type: ignore[union-attr]
except OutputParserError as e:
@@ -1377,7 +1374,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
output=answer,
text=answer,
)
self._invoke_step_callback(formatted_answer)
await self._ainvoke_step_callback(formatted_answer)
self._append_message(answer) # Save final answer to messages
self._show_logs(formatted_answer)
return formatted_answer
@@ -1389,7 +1386,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
output=answer,
text=output_json,
)
self._invoke_step_callback(formatted_answer)
await self._ainvoke_step_callback(formatted_answer)
self._append_message(output_json)
self._show_logs(formatted_answer)
return formatted_answer
@@ -1400,7 +1397,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
output=str(answer),
text=str(answer),
)
self._invoke_step_callback(formatted_answer)
await self._ainvoke_step_callback(formatted_answer)
self._append_message(str(answer)) # Save final answer to messages
self._show_logs(formatted_answer)
return formatted_answer
@@ -1494,7 +1491,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
def _invoke_step_callback(
self, formatted_answer: AgentAction | AgentFinish
) -> None:
"""Invoke step callback.
"""Invoke step callback (sync context).
Args:
formatted_answer: Current agent response.
@@ -1504,6 +1501,19 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
if inspect.iscoroutine(cb_result):
asyncio.run(cb_result)
async def _ainvoke_step_callback(
self, formatted_answer: AgentAction | AgentFinish
) -> None:
"""Invoke step callback (async context).
Args:
formatted_answer: Current agent response.
"""
if self.step_callback:
cb_result = self.step_callback(formatted_answer)
if inspect.iscoroutine(cb_result):
await cb_result
def _append_message(
self, text: str, role: Literal["user", "assistant", "system"] = "assistant"
) -> None:

View File

@@ -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": [

View File

@@ -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. "

View File

@@ -190,6 +190,15 @@ class PlusAPI:
timeout=30,
)
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")

View File

@@ -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.1a1"
]
[project.scripts]

View File

@@ -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.1a1"
]
[project.scripts]

View File

@@ -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.1a1"
]
[tool.crewai]

View File

@@ -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",

View File

@@ -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

View File

@@ -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
)

View File

@@ -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"

View File

@@ -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,

View File

@@ -66,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,
)
@@ -848,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"

View File

@@ -16,7 +16,7 @@ from collections.abc import (
Sequence,
ValuesView,
)
from concurrent.futures import Future
from concurrent.futures import Future, ThreadPoolExecutor
import copy
import enum
import inspect
@@ -1739,7 +1739,12 @@ class Flow(Generic[T], metaclass=FlowMeta):
async def _run_flow() -> Any:
return await self.kickoff_async(inputs, input_files)
return asyncio.run(_run_flow())
try:
asyncio.get_running_loop()
with ThreadPoolExecutor(max_workers=1) as pool:
return pool.submit(asyncio.run, _run_flow()).result()
except RuntimeError:
return asyncio.run(_run_flow())
async def kickoff_async(
self,

View File

@@ -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...",

View File

@@ -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: "

View File

@@ -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

View File

@@ -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(

View File

@@ -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",

View File

@@ -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.

View 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,
)

View File

@@ -1,6 +1,14 @@
"""Memory module: unified Memory with LLM analysis and pluggable storage."""
"""Memory module: unified Memory with LLM analysis and pluggable storage.
Heavy dependencies are lazily imported so that
``import crewai`` does not initialise at runtime — critical for
Celery pre-fork and similar deployment patterns.
"""
from __future__ import annotations
from typing import Any
from crewai.memory.encoding_flow import EncodingFlow
from crewai.memory.memory_scope import MemoryScope, MemorySlice
from crewai.memory.types import (
MemoryMatch,
@@ -10,7 +18,24 @@ from crewai.memory.types import (
embed_text,
embed_texts,
)
from crewai.memory.unified_memory import Memory
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
"Memory": ("crewai.memory.unified_memory", "Memory"),
"EncodingFlow": ("crewai.memory.encoding_flow", "EncodingFlow"),
}
def __getattr__(name: str) -> Any:
"""Lazily import Memory / EncodingFlow to avoid pulling in lancedb at import time."""
if name in _LAZY_IMPORTS:
import importlib
module_path, attr = _LAZY_IMPORTS[name]
mod = importlib.import_module(module_path)
val = getattr(mod, attr)
globals()[name] = val
return val
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = [

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 (
@@ -21,7 +21,6 @@ from crewai.llms.base_llm import BaseLLM
from crewai.memory.analyze import extract_memories_from_content
from crewai.memory.recall_flow import RecallFlow
from crewai.memory.storage.backend import StorageBackend
from crewai.memory.storage.lancedb_storage import LanceDBStorage
from crewai.memory.types import (
MemoryConfig,
MemoryMatch,
@@ -30,13 +29,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 +94,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 +117,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,14 +142,15 @@ 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).
if storage == "lancedb":
self._storage = LanceDBStorage()
elif isinstance(storage, str):
self._storage = LanceDBStorage(path=storage)
if isinstance(storage, str):
from crewai.memory.storage.lancedb_storage import LanceDBStorage
self._storage = LanceDBStorage() if storage == "lancedb" else LanceDBStorage(path=storage)
else:
self._storage = storage
@@ -160,12 +173,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 +198,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 +333,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 +351,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 +374,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 +444,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 +596,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 +768,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 +812,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,

View File

@@ -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.

View File

@@ -586,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,
@@ -687,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,

View File

@@ -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

View File

@@ -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__)

View File

@@ -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

View File

@@ -168,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)
@@ -1146,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,

View File

@@ -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"

View File

@@ -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

View File

@@ -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."]

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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.

View 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

View File

@@ -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

View File

@@ -218,14 +218,15 @@ def test_memory_slice_recall(tmp_path: Path, mock_embedder: MagicMock) -> None:
assert isinstance(matches, list)
def test_memory_slice_remember_raises_when_read_only(tmp_path: Path, mock_embedder: MagicMock) -> None:
def test_memory_slice_remember_is_noop_when_read_only(tmp_path: Path, mock_embedder: MagicMock) -> None:
from crewai.memory.unified_memory import Memory
from crewai.memory.memory_scope import MemorySlice
mem = Memory(storage=str(tmp_path / "db7"), llm=MagicMock(), embedder=mock_embedder)
sl = MemorySlice(mem, ["/a"], read_only=True)
with pytest.raises(PermissionError):
sl.remember("x", scope="/a")
result = sl.remember("x", scope="/a")
assert result is None
assert mem.list_records() == []
# --- Flow memory ---
@@ -318,6 +319,7 @@ def test_executor_save_to_memory_calls_extract_then_remember_per_item() -> None:
from crewai.agents.parser import AgentFinish
mock_memory = MagicMock()
mock_memory._read_only = False
mock_memory.extract_memories.return_value = ["Fact A.", "Fact B."]
mock_agent = MagicMock()
@@ -358,6 +360,7 @@ def test_executor_save_to_memory_skips_delegation_output() -> None:
from crewai.utilities.string_utils import sanitize_tool_name
mock_memory = MagicMock()
mock_memory._read_only = False
mock_agent = MagicMock()
mock_agent.memory = mock_memory
mock_agent._logger = MagicMock()

View File

@@ -759,11 +759,11 @@ def test_custom_converter_cls():
crew = Crew(agents=[scorer], tasks=[task])
with patch.object(
ScoreConverter, "to_pydantic", return_value=ScoreOutput(score=5)
) as mock_to_pydantic:
crew.kickoff()
mock_to_pydantic.assert_called_once()
# With native structured output, the LLM returns a BaseModel directly,
# so the converter is bypassed. Verify the output is valid instead.
result = crew.kickoff()
assert isinstance(result.pydantic, ScoreOutput)
assert isinstance(result.pydantic.score, int)
@pytest.mark.vcr()

View File

@@ -3,6 +3,8 @@ from typing import Callable
from unittest.mock import patch
import pytest
from pydantic import BaseModel, Field
from crewai.agent import Agent
from crewai.crew import Crew
from crewai.task import Task
@@ -230,3 +232,218 @@ def test_max_usage_count_is_respected():
crew.kickoff()
assert tool.max_usage_count == 5
assert tool.current_usage_count == 5
# =============================================================================
# Schema Validation in run() Tests
# =============================================================================
class CodeExecutorInput(BaseModel):
code: str = Field(description="The code to execute")
language: str = Field(default="python", description="Programming language")
class CodeExecutorTool(BaseTool):
name: str = "code_executor"
description: str = "Execute code snippets"
args_schema: type[BaseModel] = CodeExecutorInput
def _run(self, code: str, language: str = "python") -> str:
return f"Executed {language}: {code}"
class TestBaseToolRunValidation:
"""Tests for args_schema validation in BaseTool.run()."""
def test_run_with_valid_kwargs_passes_validation(self) -> None:
"""Valid keyword arguments should pass schema validation and execute."""
t = CodeExecutorTool()
result = t.run(code="print('hello')")
assert result == "Executed python: print('hello')"
def test_run_with_all_kwargs_passes_validation(self) -> None:
"""All keyword arguments including optional ones should pass."""
t = CodeExecutorTool()
result = t.run(code="console.log('hi')", language="javascript")
assert result == "Executed javascript: console.log('hi')"
def test_run_with_no_args_raises_validation_error(self) -> None:
"""Calling run() with no arguments should raise a clear ValueError,
not a cryptic TypeError about missing positional arguments (GH-4611)."""
t = CodeExecutorTool()
with pytest.raises(ValueError, match="validation failed"):
t.run()
def test_run_with_missing_required_kwarg_raises(self) -> None:
"""Missing required kwargs should raise ValueError from schema validation."""
t = CodeExecutorTool()
with pytest.raises(ValueError, match="validation failed"):
t.run(language="python")
def test_run_with_wrong_field_name_raises(self) -> None:
"""Kwargs not matching any schema field should trigger validation error
for missing required fields."""
t = CodeExecutorTool()
with pytest.raises(ValueError, match="validation failed"):
t.run(wrong_arg="value")
def test_run_with_positional_args_skips_validation(self) -> None:
"""Positional-arg calls should bypass schema validation (backwards compat)."""
class SimpleTool(BaseTool):
name: str = "simple"
description: str = "A simple tool"
def _run(self, question: str) -> str:
return question
t = SimpleTool()
result = t.run("What is life?")
assert result == "What is life?"
def test_run_strips_extra_kwargs_from_llm(self) -> None:
"""Extra kwargs not in the schema should be silently stripped,
preventing unexpected-keyword crashes in _run."""
t = CodeExecutorTool()
result = t.run(code="1+1", extra_hallucinated_field="junk")
assert result == "Executed python: 1+1"
def test_run_increments_usage_after_validation(self) -> None:
"""Usage count should still increment after validated execution."""
t = CodeExecutorTool()
assert t.current_usage_count == 0
t.run(code="x = 1")
assert t.current_usage_count == 1
def test_run_does_not_increment_usage_on_validation_error(self) -> None:
"""Usage count should NOT increment when validation fails."""
t = CodeExecutorTool()
assert t.current_usage_count == 0
with pytest.raises(ValueError):
t.run(wrong="bad")
assert t.current_usage_count == 0
class TestToolDecoratorRunValidation:
"""Tests for args_schema validation in Tool.run() (decorator-based tools)."""
def test_decorator_tool_run_validates_kwargs(self) -> None:
"""Decorator-created tools should also validate kwargs against schema."""
@tool("execute_code")
def execute_code(code: str, language: str = "python") -> str:
"""Execute a code snippet."""
return f"Executed {language}: {code}"
result = execute_code.run(code="x = 1")
assert result == "Executed python: x = 1"
def test_decorator_tool_run_rejects_missing_required(self) -> None:
"""Decorator tools should reject missing required args via validation."""
@tool("execute_code")
def execute_code(code: str) -> str:
"""Execute a code snippet."""
return f"Executed: {code}"
with pytest.raises(ValueError, match="validation failed"):
execute_code.run(wrong_arg="value")
def test_decorator_tool_positional_args_still_work(self) -> None:
"""Positional args to decorator tools should bypass validation."""
@tool("greet")
def greet(name: str) -> str:
"""Greet someone."""
return f"Hello, {name}!"
result = greet.run("World")
assert result == "Hello, World!"
# =============================================================================
# Async arun() Schema Validation Tests
# =============================================================================
class AsyncCodeExecutorTool(BaseTool):
name: str = "async_code_executor"
description: str = "Execute code snippets asynchronously"
args_schema: type[BaseModel] = CodeExecutorInput
async def _arun(self, code: str, language: str = "python") -> str:
return f"Async executed {language}: {code}"
def _run(self, code: str, language: str = "python") -> str:
return f"Executed {language}: {code}"
class TestBaseToolArunValidation:
"""Tests for args_schema validation in BaseTool.arun()."""
@pytest.mark.asyncio
async def test_arun_with_valid_kwargs_passes_validation(self) -> None:
"""Valid keyword arguments should pass schema validation in arun."""
t = AsyncCodeExecutorTool()
result = await t.arun(code="print('hello')")
assert result == "Async executed python: print('hello')"
@pytest.mark.asyncio
async def test_arun_with_no_args_raises_validation_error(self) -> None:
"""Calling arun() with no arguments should raise a clear ValueError (GH-4611)."""
t = AsyncCodeExecutorTool()
with pytest.raises(ValueError, match="validation failed"):
await t.arun()
@pytest.mark.asyncio
async def test_arun_with_missing_required_kwarg_raises(self) -> None:
"""Missing required kwargs should raise ValueError in arun."""
t = AsyncCodeExecutorTool()
with pytest.raises(ValueError, match="validation failed"):
await t.arun(language="python")
@pytest.mark.asyncio
async def test_arun_with_wrong_field_name_raises(self) -> None:
"""Kwargs not matching schema fields should trigger validation error in arun."""
t = AsyncCodeExecutorTool()
with pytest.raises(ValueError, match="validation failed"):
await t.arun(wrong_arg="value")
@pytest.mark.asyncio
async def test_arun_strips_extra_kwargs(self) -> None:
"""Extra kwargs not in the schema should be stripped in arun."""
t = AsyncCodeExecutorTool()
result = await t.arun(code="1+1", extra_field="junk")
assert result == "Async executed python: 1+1"
@pytest.mark.asyncio
async def test_arun_does_not_increment_usage_on_validation_error(self) -> None:
"""Usage count should NOT increment when arun validation fails."""
t = AsyncCodeExecutorTool()
assert t.current_usage_count == 0
with pytest.raises(ValueError):
await t.arun(wrong="bad")
assert t.current_usage_count == 0
class TestToolDecoratorArunValidation:
"""Tests for args_schema validation in Tool.arun() (decorator-based async tools)."""
@pytest.mark.asyncio
async def test_async_decorator_tool_arun_validates_kwargs(self) -> None:
"""Async decorator tools should validate kwargs in arun."""
@tool("async_execute")
async def async_execute(code: str, language: str = "python") -> str:
"""Execute code asynchronously."""
return f"Async {language}: {code}"
result = await async_execute.arun(code="x = 1")
assert result == "Async python: x = 1"
@pytest.mark.asyncio
async def test_async_decorator_tool_arun_rejects_missing_required(self) -> None:
"""Async decorator tools should reject missing required args in arun."""
@tool("async_execute")
async def async_execute(code: str) -> str:
"""Execute code asynchronously."""
return f"Async: {code}"
with pytest.raises(ValueError, match="validation failed"):
await async_execute.arun(wrong_arg="value")

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
import asyncio
from typing import Any
from typing import Any, Literal, Optional
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@@ -17,6 +17,7 @@ from crewai.utilities.agent_utils import (
_format_messages_for_summary,
_split_messages_into_chunks,
convert_tools_to_openai_schema,
parse_tool_call_args,
summarize_messages,
)
@@ -234,6 +235,79 @@ def _make_mock_i18n() -> MagicMock:
}.get(key, "")
return mock_i18n
class MCPStyleInput(BaseModel):
"""Input schema mimicking an MCP tool with optional fields."""
query: str = Field(description="Search query")
filter_type: Optional[Literal["internal", "user"]] = Field(
default=None, description="Filter type"
)
page_id: Optional[str] = Field(
default=None, description="Page UUID"
)
class MCPStyleTool(BaseTool):
"""A tool mimicking MCP tool schemas with optional fields."""
name: str = "mcp_search"
description: str = "Search with optional filters"
args_schema: type[BaseModel] = MCPStyleInput
def _run(self, **kwargs: Any) -> str:
return "result"
class TestOptionalFieldsPreserveNull:
"""Tests that optional tool fields preserve null in the schema."""
def test_optional_string_allows_null(self) -> None:
"""Optional[str] fields should include null in the schema so the LLM
can send null instead of being forced to guess a value."""
tools = [MCPStyleTool()]
schemas, _ = convert_tools_to_openai_schema(tools)
params = schemas[0]["function"]["parameters"]
page_id_prop = params["properties"]["page_id"]
assert "anyOf" in page_id_prop
type_options = [opt.get("type") for opt in page_id_prop["anyOf"]]
assert "string" in type_options
assert "null" in type_options
def test_optional_literal_allows_null(self) -> None:
"""Optional[Literal[...]] fields should include null."""
tools = [MCPStyleTool()]
schemas, _ = convert_tools_to_openai_schema(tools)
params = schemas[0]["function"]["parameters"]
filter_prop = params["properties"]["filter_type"]
assert "anyOf" in filter_prop
has_null = any(opt.get("type") == "null" for opt in filter_prop["anyOf"])
assert has_null
def test_required_field_stays_non_null(self) -> None:
"""Required fields without Optional should NOT have null."""
tools = [MCPStyleTool()]
schemas, _ = convert_tools_to_openai_schema(tools)
params = schemas[0]["function"]["parameters"]
query_prop = params["properties"]["query"]
assert query_prop.get("type") == "string"
assert "anyOf" not in query_prop
def test_all_fields_in_required_for_strict_mode(self) -> None:
"""All fields (including optional) must be in required for strict mode."""
tools = [MCPStyleTool()]
schemas, _ = convert_tools_to_openai_schema(tools)
params = schemas[0]["function"]["parameters"]
assert "query" in params["required"]
assert "filter_type" in params["required"]
assert "page_id" in params["required"]
class TestSummarizeMessages:
"""Tests for summarize_messages function."""
@@ -922,3 +996,56 @@ class TestParallelSummarizationVCR:
assert summary_msg["role"] == "user"
assert "files" in summary_msg
assert "report.pdf" in summary_msg["files"]
class TestParseToolCallArgs:
"""Unit tests for parse_tool_call_args."""
def test_valid_json_string_returns_dict(self) -> None:
args_dict, error = parse_tool_call_args('{"code": "print(1)"}', "run_code", "call_1")
assert error is None
assert args_dict == {"code": "print(1)"}
def test_malformed_json_returns_error_dict(self) -> None:
args_dict, error = parse_tool_call_args('{"code": "print("hi")"}', "run_code", "call_1")
assert args_dict is None
assert error is not None
assert error["call_id"] == "call_1"
assert error["func_name"] == "run_code"
assert error["from_cache"] is False
assert "Failed to parse tool arguments as JSON" in error["result"]
assert "run_code" in error["result"]
def test_malformed_json_preserves_original_tool(self) -> None:
mock_tool = object()
_, error = parse_tool_call_args("{bad}", "my_tool", "call_2", original_tool=mock_tool)
assert error is not None
assert error["original_tool"] is mock_tool
def test_malformed_json_original_tool_defaults_to_none(self) -> None:
_, error = parse_tool_call_args("{bad}", "my_tool", "call_3")
assert error is not None
assert error["original_tool"] is None
def test_dict_input_returned_directly(self) -> None:
func_args = {"code": "x = 42"}
args_dict, error = parse_tool_call_args(func_args, "run_code", "call_4")
assert error is None
assert args_dict == {"code": "x = 42"}
def test_empty_dict_input_returned_directly(self) -> None:
args_dict, error = parse_tool_call_args({}, "run_code", "call_5")
assert error is None
assert args_dict == {}
def test_valid_json_with_nested_values(self) -> None:
args_dict, error = parse_tool_call_args(
'{"query": "hello", "options": {"limit": 10}}', "search", "call_6"
)
assert error is None
assert args_dict == {"query": "hello", "options": {"limit": 10}}
def test_error_result_has_correct_keys(self) -> None:
_, error = parse_tool_call_args("{bad json}", "tool", "call_7")
assert error is not None
assert set(error.keys()) == {"call_id", "func_name", "result", "from_cache", "original_tool"}

View File

@@ -81,7 +81,7 @@ def test_create_llm_from_env_with_unaccepted_attributes() -> None:
"OPENAI_API_KEY": "fake-key",
"AWS_ACCESS_KEY_ID": "fake-access-key",
"AWS_SECRET_ACCESS_KEY": "fake-secret-key",
"AWS_REGION_NAME": "us-west-2",
"AWS_DEFAULT_REGION": "us-west-2",
},
):
llm = create_llm(llm_value=None)
@@ -89,7 +89,7 @@ def test_create_llm_from_env_with_unaccepted_attributes() -> None:
assert llm.model == "gpt-3.5-turbo"
assert not hasattr(llm, "AWS_ACCESS_KEY_ID")
assert not hasattr(llm, "AWS_SECRET_ACCESS_KEY")
assert not hasattr(llm, "AWS_REGION_NAME")
assert not hasattr(llm, "AWS_DEFAULT_REGION")
def test_create_llm_with_partial_attributes() -> None:

View File

@@ -0,0 +1,884 @@
"""Tests for pydantic_schema_utils module.
Covers:
- create_model_from_schema: type mapping, required/optional, enums, formats,
nested objects, arrays, unions, allOf, $ref, model_name, enrich_descriptions
- Schema transformation helpers: resolve_refs, force_additional_properties_false,
strip_unsupported_formats, ensure_type_in_schemas, convert_oneof_to_anyof,
ensure_all_properties_required, strip_null_from_types, build_rich_field_description
- End-to-end MCP tool schema conversion
"""
from __future__ import annotations
import datetime
from copy import deepcopy
from typing import Any
import pytest
from pydantic import BaseModel
from crewai.utilities.pydantic_schema_utils import (
build_rich_field_description,
convert_oneof_to_anyof,
create_model_from_schema,
ensure_all_properties_required,
ensure_type_in_schemas,
force_additional_properties_false,
resolve_refs,
strip_null_from_types,
strip_unsupported_formats,
)
class TestSimpleTypes:
def test_string_field(self) -> None:
schema = {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
}
Model = create_model_from_schema(schema)
obj = Model(name="Alice")
assert obj.name == "Alice"
def test_integer_field(self) -> None:
schema = {
"type": "object",
"properties": {"count": {"type": "integer"}},
"required": ["count"],
}
Model = create_model_from_schema(schema)
obj = Model(count=42)
assert obj.count == 42
def test_number_field(self) -> None:
schema = {
"type": "object",
"properties": {"score": {"type": "number"}},
"required": ["score"],
}
Model = create_model_from_schema(schema)
obj = Model(score=3.14)
assert obj.score == pytest.approx(3.14)
def test_boolean_field(self) -> None:
schema = {
"type": "object",
"properties": {"active": {"type": "boolean"}},
"required": ["active"],
}
Model = create_model_from_schema(schema)
assert Model(active=True).active is True
def test_null_field(self) -> None:
schema = {
"type": "object",
"properties": {"value": {"type": "null"}},
"required": ["value"],
}
Model = create_model_from_schema(schema)
obj = Model(value=None)
assert obj.value is None
class TestRequiredOptional:
def test_required_field_has_no_default(self) -> None:
schema = {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
}
Model = create_model_from_schema(schema)
with pytest.raises(Exception):
Model()
def test_optional_field_defaults_to_none(self) -> None:
schema = {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": [],
}
Model = create_model_from_schema(schema)
obj = Model()
assert obj.name is None
def test_mixed_required_optional(self) -> None:
schema = {
"type": "object",
"properties": {
"id": {"type": "integer"},
"label": {"type": "string"},
},
"required": ["id"],
}
Model = create_model_from_schema(schema)
obj = Model(id=1)
assert obj.id == 1
assert obj.label is None
class TestEnumLiteral:
def test_string_enum(self) -> None:
schema = {
"type": "object",
"properties": {
"color": {"type": "string", "enum": ["red", "green", "blue"]},
},
"required": ["color"],
}
Model = create_model_from_schema(schema)
obj = Model(color="red")
assert obj.color == "red"
def test_string_enum_rejects_invalid(self) -> None:
schema = {
"type": "object",
"properties": {
"color": {"type": "string", "enum": ["red", "green", "blue"]},
},
"required": ["color"],
}
Model = create_model_from_schema(schema)
with pytest.raises(Exception):
Model(color="yellow")
def test_const_value(self) -> None:
schema = {
"type": "object",
"properties": {
"kind": {"const": "fixed"},
},
"required": ["kind"],
}
Model = create_model_from_schema(schema)
obj = Model(kind="fixed")
assert obj.kind == "fixed"
class TestFormatMapping:
def test_date_format(self) -> None:
schema = {
"type": "object",
"properties": {
"birthday": {"type": "string", "format": "date"},
},
"required": ["birthday"],
}
Model = create_model_from_schema(schema)
obj = Model(birthday=datetime.date(2000, 1, 15))
assert obj.birthday == datetime.date(2000, 1, 15)
def test_datetime_format(self) -> None:
schema = {
"type": "object",
"properties": {
"created_at": {"type": "string", "format": "date-time"},
},
"required": ["created_at"],
}
Model = create_model_from_schema(schema)
dt = datetime.datetime(2025, 6, 1, 12, 0, 0)
obj = Model(created_at=dt)
assert obj.created_at == dt
def test_time_format(self) -> None:
schema = {
"type": "object",
"properties": {
"alarm": {"type": "string", "format": "time"},
},
"required": ["alarm"],
}
Model = create_model_from_schema(schema)
t = datetime.time(8, 30)
obj = Model(alarm=t)
assert obj.alarm == t
class TestNestedObjects:
def test_nested_object_creates_model(self) -> None:
schema = {
"type": "object",
"properties": {
"address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
},
"required": ["street", "city"],
},
},
"required": ["address"],
}
Model = create_model_from_schema(schema)
obj = Model(address={"street": "123 Main", "city": "Springfield"})
assert obj.address.street == "123 Main"
assert obj.address.city == "Springfield"
def test_object_without_properties_returns_dict(self) -> None:
schema = {
"type": "object",
"properties": {
"metadata": {"type": "object"},
},
"required": ["metadata"],
}
Model = create_model_from_schema(schema)
obj = Model(metadata={"key": "value"})
assert obj.metadata == {"key": "value"}
class TestTypedArrays:
def test_array_of_strings(self) -> None:
schema = {
"type": "object",
"properties": {
"tags": {"type": "array", "items": {"type": "string"}},
},
"required": ["tags"],
}
Model = create_model_from_schema(schema)
obj = Model(tags=["a", "b", "c"])
assert obj.tags == ["a", "b", "c"]
def test_array_of_objects(self) -> None:
schema = {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {"id": {"type": "integer"}},
"required": ["id"],
},
},
},
"required": ["items"],
}
Model = create_model_from_schema(schema)
obj = Model(items=[{"id": 1}, {"id": 2}])
assert len(obj.items) == 2
assert obj.items[0].id == 1
def test_untyped_array(self) -> None:
schema = {
"type": "object",
"properties": {"data": {"type": "array"}},
"required": ["data"],
}
Model = create_model_from_schema(schema)
obj = Model(data=[1, "two", 3.0])
assert obj.data == [1, "two", 3.0]
class TestUnionTypes:
def test_anyof_string_or_integer(self) -> None:
schema = {
"type": "object",
"properties": {
"value": {
"anyOf": [{"type": "string"}, {"type": "integer"}],
},
},
"required": ["value"],
}
Model = create_model_from_schema(schema)
assert Model(value="hello").value == "hello"
assert Model(value=42).value == 42
def test_oneof(self) -> None:
schema = {
"type": "object",
"properties": {
"value": {
"oneOf": [{"type": "string"}, {"type": "number"}],
},
},
"required": ["value"],
}
Model = create_model_from_schema(schema)
assert Model(value="hello").value == "hello"
assert Model(value=3.14).value == pytest.approx(3.14)
class TestAllOfMerging:
def test_allof_merges_properties(self) -> None:
schema = {
"type": "object",
"allOf": [
{
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
},
{
"type": "object",
"properties": {"age": {"type": "integer"}},
"required": ["age"],
},
],
}
Model = create_model_from_schema(schema)
obj = Model(name="Alice", age=30)
assert obj.name == "Alice"
assert obj.age == 30
def test_single_allof(self) -> None:
schema = {
"type": "object",
"properties": {
"item": {
"allOf": [
{
"type": "object",
"properties": {"id": {"type": "integer"}},
"required": ["id"],
}
]
}
},
"required": ["item"],
}
Model = create_model_from_schema(schema)
obj = Model(item={"id": 1})
assert obj.item.id == 1
# ---------------------------------------------------------------------------
# $ref resolution
# ---------------------------------------------------------------------------
class TestRefResolution:
def test_ref_in_property(self) -> None:
schema = {
"type": "object",
"properties": {
"item": {"$ref": "#/$defs/Item"},
},
"required": ["item"],
"$defs": {
"Item": {
"type": "object",
"title": "Item",
"properties": {"name": {"type": "string"}},
"required": ["name"],
},
},
}
Model = create_model_from_schema(schema)
obj = Model(item={"name": "Widget"})
assert obj.item.name == "Widget"
# ---------------------------------------------------------------------------
# model_name parameter
# ---------------------------------------------------------------------------
class TestModelName:
def test_model_name_override(self) -> None:
schema = {
"type": "object",
"title": "OriginalName",
"properties": {"x": {"type": "integer"}},
"required": ["x"],
}
Model = create_model_from_schema(schema, model_name="CustomSchema")
assert Model.__name__ == "CustomSchema"
def test_model_name_fallback_to_title(self) -> None:
schema = {
"type": "object",
"title": "FromTitle",
"properties": {"x": {"type": "integer"}},
"required": ["x"],
}
Model = create_model_from_schema(schema)
assert Model.__name__ == "FromTitle"
def test_model_name_fallback_to_dynamic(self) -> None:
schema = {
"type": "object",
"properties": {"x": {"type": "integer"}},
"required": ["x"],
}
Model = create_model_from_schema(schema)
assert Model.__name__ == "DynamicModel"
# ---------------------------------------------------------------------------
# enrich_descriptions
# ---------------------------------------------------------------------------
class TestEnrichDescriptions:
def test_enriched_description_includes_constraints(self) -> None:
schema = {
"type": "object",
"properties": {
"score": {
"type": "integer",
"description": "The score value",
"minimum": 0,
"maximum": 100,
},
},
"required": ["score"],
}
Model = create_model_from_schema(schema, enrich_descriptions=True)
field_info = Model.model_fields["score"]
assert "Minimum: 0" in field_info.description
assert "Maximum: 100" in field_info.description
assert "The score value" in field_info.description
def test_default_does_not_enrich(self) -> None:
schema = {
"type": "object",
"properties": {
"score": {
"type": "integer",
"description": "The score value",
"minimum": 0,
},
},
"required": ["score"],
}
Model = create_model_from_schema(schema, enrich_descriptions=False)
field_info = Model.model_fields["score"]
assert field_info.description == "The score value"
def test_enriched_description_propagates_to_nested(self) -> None:
schema = {
"type": "object",
"properties": {
"config": {
"type": "object",
"properties": {
"level": {
"type": "integer",
"description": "Level",
"minimum": 1,
"maximum": 10,
},
},
"required": ["level"],
},
},
"required": ["config"],
}
Model = create_model_from_schema(schema, enrich_descriptions=True)
nested_model = Model.model_fields["config"].annotation
nested_field = nested_model.model_fields["level"]
assert "Minimum: 1" in nested_field.description
assert "Maximum: 10" in nested_field.description
# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------
class TestEdgeCases:
def test_empty_properties(self) -> None:
schema = {"type": "object", "properties": {}, "required": []}
Model = create_model_from_schema(schema)
obj = Model()
assert obj is not None
def test_no_properties_key(self) -> None:
schema = {"type": "object"}
Model = create_model_from_schema(schema)
obj = Model()
assert obj is not None
def test_unknown_type_raises(self) -> None:
schema = {
"type": "object",
"properties": {
"weird": {"type": "hyperspace"},
},
"required": ["weird"],
}
with pytest.raises(ValueError, match="Unsupported JSON schema type"):
create_model_from_schema(schema)
# ---------------------------------------------------------------------------
# build_rich_field_description
# ---------------------------------------------------------------------------
class TestBuildRichFieldDescription:
def test_description_only(self) -> None:
assert build_rich_field_description({"description": "A name"}) == "A name"
def test_empty_schema(self) -> None:
assert build_rich_field_description({}) == ""
def test_format(self) -> None:
desc = build_rich_field_description({"format": "date-time"})
assert "Format: date-time" in desc
def test_enum(self) -> None:
desc = build_rich_field_description({"enum": ["a", "b"]})
assert "Allowed values:" in desc
assert "'a'" in desc
assert "'b'" in desc
def test_pattern(self) -> None:
desc = build_rich_field_description({"pattern": "^[a-z]+$"})
assert "Pattern: ^[a-z]+$" in desc
def test_min_max(self) -> None:
desc = build_rich_field_description({"minimum": 0, "maximum": 100})
assert "Minimum: 0" in desc
assert "Maximum: 100" in desc
def test_min_max_length(self) -> None:
desc = build_rich_field_description({"minLength": 1, "maxLength": 255})
assert "Min length: 1" in desc
assert "Max length: 255" in desc
def test_examples(self) -> None:
desc = build_rich_field_description({"examples": ["foo", "bar", "baz", "extra"]})
assert "Examples:" in desc
assert "'foo'" in desc
assert "'baz'" in desc
# Only first 3 shown
assert "'extra'" not in desc
def test_combined_constraints(self) -> None:
desc = build_rich_field_description({
"description": "A score",
"minimum": 0,
"maximum": 10,
"format": "int32",
})
assert desc.startswith("A score")
assert "Minimum: 0" in desc
assert "Maximum: 10" in desc
assert "Format: int32" in desc
# ---------------------------------------------------------------------------
# Schema transformation functions
# ---------------------------------------------------------------------------
class TestResolveRefs:
def test_basic_ref_resolution(self) -> None:
schema = {
"type": "object",
"properties": {"item": {"$ref": "#/$defs/Item"}},
"$defs": {
"Item": {"type": "object", "properties": {"id": {"type": "integer"}}},
},
}
resolved = resolve_refs(schema)
assert "$ref" not in resolved["properties"]["item"]
assert resolved["properties"]["item"]["type"] == "object"
def test_nested_ref_resolution(self) -> None:
schema = {
"type": "object",
"properties": {"wrapper": {"$ref": "#/$defs/Wrapper"}},
"$defs": {
"Wrapper": {
"type": "object",
"properties": {"inner": {"$ref": "#/$defs/Inner"}},
},
"Inner": {"type": "string"},
},
}
resolved = resolve_refs(schema)
wrapper = resolved["properties"]["wrapper"]
assert wrapper["properties"]["inner"]["type"] == "string"
def test_missing_ref_raises(self) -> None:
schema = {
"properties": {"x": {"$ref": "#/$defs/Missing"}},
"$defs": {},
}
with pytest.raises(KeyError, match="Missing"):
resolve_refs(schema)
def test_no_refs_unchanged(self) -> None:
schema = {
"type": "object",
"properties": {"name": {"type": "string"}},
}
resolved = resolve_refs(schema)
assert resolved == schema
class TestForceAdditionalPropertiesFalse:
def test_adds_to_object(self) -> None:
schema = {"type": "object", "properties": {"x": {"type": "integer"}}}
result = force_additional_properties_false(deepcopy(schema))
assert result["additionalProperties"] is False
def test_adds_empty_properties_and_required(self) -> None:
schema = {"type": "object"}
result = force_additional_properties_false(deepcopy(schema))
assert result["properties"] == {}
assert result["required"] == []
def test_recursive_nested_objects(self) -> None:
schema = {
"type": "object",
"properties": {
"child": {
"type": "object",
"properties": {"id": {"type": "integer"}},
},
},
}
result = force_additional_properties_false(deepcopy(schema))
assert result["additionalProperties"] is False
assert result["properties"]["child"]["additionalProperties"] is False
def test_does_not_affect_non_objects(self) -> None:
schema = {"type": "string"}
result = force_additional_properties_false(deepcopy(schema))
assert "additionalProperties" not in result
class TestStripUnsupportedFormats:
def test_removes_email_format(self) -> None:
schema = {"type": "string", "format": "email"}
result = strip_unsupported_formats(deepcopy(schema))
assert "format" not in result
def test_keeps_date_time(self) -> None:
schema = {"type": "string", "format": "date-time"}
result = strip_unsupported_formats(deepcopy(schema))
assert result["format"] == "date-time"
def test_keeps_date(self) -> None:
schema = {"type": "string", "format": "date"}
result = strip_unsupported_formats(deepcopy(schema))
assert result["format"] == "date"
def test_removes_uri_format(self) -> None:
schema = {"type": "string", "format": "uri"}
result = strip_unsupported_formats(deepcopy(schema))
assert "format" not in result
def test_recursive(self) -> None:
schema = {
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"created": {"type": "string", "format": "date-time"},
},
}
result = strip_unsupported_formats(deepcopy(schema))
assert "format" not in result["properties"]["email"]
assert result["properties"]["created"]["format"] == "date-time"
class TestEnsureTypeInSchemas:
def test_empty_schema_in_anyof_gets_type(self) -> None:
schema = {"anyOf": [{}, {"type": "string"}]}
result = ensure_type_in_schemas(deepcopy(schema))
assert result["anyOf"][0] == {"type": "object"}
def test_empty_schema_in_oneof_gets_type(self) -> None:
schema = {"oneOf": [{}, {"type": "integer"}]}
result = ensure_type_in_schemas(deepcopy(schema))
assert result["oneOf"][0] == {"type": "object"}
def test_non_empty_unchanged(self) -> None:
schema = {"anyOf": [{"type": "string"}, {"type": "integer"}]}
result = ensure_type_in_schemas(deepcopy(schema))
assert result == schema
class TestConvertOneofToAnyof:
def test_converts_top_level(self) -> None:
schema = {"oneOf": [{"type": "string"}, {"type": "integer"}]}
result = convert_oneof_to_anyof(deepcopy(schema))
assert "oneOf" not in result
assert "anyOf" in result
assert len(result["anyOf"]) == 2
def test_converts_nested(self) -> None:
schema = {
"type": "object",
"properties": {
"value": {"oneOf": [{"type": "string"}, {"type": "number"}]},
},
}
result = convert_oneof_to_anyof(deepcopy(schema))
assert "anyOf" in result["properties"]["value"]
assert "oneOf" not in result["properties"]["value"]
class TestEnsureAllPropertiesRequired:
def test_makes_all_required(self) -> None:
schema = {
"type": "object",
"properties": {"a": {"type": "string"}, "b": {"type": "integer"}},
"required": ["a"],
}
result = ensure_all_properties_required(deepcopy(schema))
assert set(result["required"]) == {"a", "b"}
def test_recursive(self) -> None:
schema = {
"type": "object",
"properties": {
"child": {
"type": "object",
"properties": {"x": {"type": "integer"}, "y": {"type": "integer"}},
"required": [],
},
},
}
result = ensure_all_properties_required(deepcopy(schema))
assert set(result["properties"]["child"]["required"]) == {"x", "y"}
class TestStripNullFromTypes:
def test_strips_null_from_anyof(self) -> None:
schema = {
"anyOf": [{"type": "string"}, {"type": "null"}],
}
result = strip_null_from_types(deepcopy(schema))
assert "anyOf" not in result
assert result["type"] == "string"
def test_strips_null_from_type_array(self) -> None:
schema = {"type": ["string", "null"]}
result = strip_null_from_types(deepcopy(schema))
assert result["type"] == "string"
def test_multiple_non_null_in_anyof(self) -> None:
schema = {
"anyOf": [{"type": "string"}, {"type": "integer"}, {"type": "null"}],
}
result = strip_null_from_types(deepcopy(schema))
assert len(result["anyOf"]) == 2
def test_no_null_unchanged(self) -> None:
schema = {"type": "string"}
result = strip_null_from_types(deepcopy(schema))
assert result == schema
class TestEndToEndMCPSchema:
"""Realistic MCP tool schema exercising multiple features simultaneously."""
MCP_SCHEMA: dict[str, Any] = {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query",
"minLength": 1,
"maxLength": 500,
},
"max_results": {
"type": "integer",
"description": "Maximum results",
"minimum": 1,
"maximum": 100,
},
"format": {
"type": "string",
"enum": ["json", "csv", "xml"],
"description": "Output format",
},
"filters": {
"type": "object",
"properties": {
"date_from": {"type": "string", "format": "date"},
"date_to": {"type": "string", "format": "date"},
"categories": {
"type": "array",
"items": {"type": "string"},
},
},
"required": ["date_from"],
},
"sort_order": {
"anyOf": [{"type": "string"}, {"type": "null"}],
},
},
"required": ["query", "format", "filters"],
}
def test_model_creation(self) -> None:
Model = create_model_from_schema(self.MCP_SCHEMA)
assert Model is not None
assert issubclass(Model, BaseModel)
def test_valid_input_accepted(self) -> None:
Model = create_model_from_schema(self.MCP_SCHEMA)
obj = Model(
query="test search",
format="json",
filters={"date_from": "2025-01-01"},
)
assert obj.query == "test search"
assert obj.format == "json"
def test_invalid_enum_rejected(self) -> None:
Model = create_model_from_schema(self.MCP_SCHEMA)
with pytest.raises(Exception):
Model(
query="test",
format="yaml",
filters={"date_from": "2025-01-01"},
)
def test_model_name_for_mcp_tool(self) -> None:
Model = create_model_from_schema(
self.MCP_SCHEMA, model_name="search_toolSchema"
)
assert Model.__name__ == "search_toolSchema"
def test_enriched_descriptions_for_mcp(self) -> None:
Model = create_model_from_schema(
self.MCP_SCHEMA, enrich_descriptions=True
)
query_field = Model.model_fields["query"]
assert "Min length: 1" in query_field.description
assert "Max length: 500" in query_field.description
max_results_field = Model.model_fields["max_results"]
assert "Minimum: 1" in max_results_field.description
assert "Maximum: 100" in max_results_field.description
format_field = Model.model_fields["format"]
assert "Allowed values:" in format_field.description
def test_optional_fields_accept_none(self) -> None:
Model = create_model_from_schema(self.MCP_SCHEMA)
obj = Model(
query="test",
format="csv",
filters={"date_from": "2025-01-01"},
max_results=None,
sort_order=None,
)
assert obj.max_results is None
assert obj.sort_order is None
def test_nested_filters_validated(self) -> None:
Model = create_model_from_schema(self.MCP_SCHEMA)
obj = Model(
query="test",
format="xml",
filters={
"date_from": "2025-01-01",
"date_to": "2025-12-31",
"categories": ["news", "tech"],
},
)
assert obj.filters.date_from == datetime.date(2025, 1, 1)
assert obj.filters.categories == ["news", "tech"]

View File

@@ -1,3 +1,3 @@
"""CrewAI development tools."""
__version__ = "1.9.3"
__version__ = "1.10.1a1"

View File

@@ -14,7 +14,7 @@ from rich.markdown import Markdown
from rich.panel import Panel
from rich.prompt import Confirm
from crewai_devtools.prompts import RELEASE_NOTES_PROMPT
from crewai_devtools.prompts import RELEASE_NOTES_PROMPT, TRANSLATE_RELEASE_NOTES_PROMPT
load_dotenv()
@@ -191,6 +191,248 @@ def update_pyproject_dependencies(file_path: Path, new_version: str) -> bool:
return False
def add_docs_version(docs_json_path: Path, version: str) -> bool:
"""Add a new version to the Mintlify docs.json versioning config.
Copies the current default version's tabs into a new version entry,
sets the new version as default, and marks the previous default as
non-default. Operates on all languages.
Args:
docs_json_path: Path to docs/docs.json.
version: Version string (e.g., "1.10.1b1").
Returns:
True if docs.json was updated, False otherwise.
"""
import json
if not docs_json_path.exists():
return False
data = json.loads(docs_json_path.read_text())
version_label = f"v{version}"
updated = False
for lang in data.get("navigation", {}).get("languages", []):
versions = lang.get("versions", [])
if not versions:
continue
# Skip if this version already exists for this language
if any(v.get("version") == version_label for v in versions):
continue
# Find the current default and copy its tabs
default_version = next(
(v for v in versions if v.get("default")),
versions[0],
)
new_version = {
"version": version_label,
"default": True,
"tabs": default_version.get("tabs", []),
}
# Remove default flag from old default
default_version.pop("default", None)
# Insert new version at the beginning
versions.insert(0, new_version)
updated = True
if not updated:
return False
docs_json_path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
return True
_PT_BR_MONTHS = {
1: "jan",
2: "fev",
3: "mar",
4: "abr",
5: "mai",
6: "jun",
7: "jul",
8: "ago",
9: "set",
10: "out",
11: "nov",
12: "dez",
}
_CHANGELOG_LOCALES: dict[str, dict[str, str]] = {
"en": {
"link_text": "View release on GitHub",
"language_name": "English",
},
"pt-BR": {
"link_text": "Ver release no GitHub",
"language_name": "Brazilian Portuguese",
},
"ko": {
"link_text": "GitHub 릴리스 보기",
"language_name": "Korean",
},
}
def translate_release_notes(
release_notes: str,
lang: str,
client: OpenAI,
) -> str:
"""Translate release notes into the target language using OpenAI.
Args:
release_notes: English release notes markdown.
lang: Language code (e.g., "pt-BR", "ko").
client: OpenAI client instance.
Returns:
Translated release notes, or original on failure.
"""
locale_cfg = _CHANGELOG_LOCALES.get(lang)
if not locale_cfg:
return release_notes
language_name = locale_cfg["language_name"]
prompt = TRANSLATE_RELEASE_NOTES_PROMPT.substitute(
language=language_name,
release_notes=release_notes,
)
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"You are a professional translator. Translate technical documentation into {language_name}.",
},
{"role": "user", "content": prompt},
],
temperature=0.3,
)
return response.choices[0].message.content or release_notes
except Exception as e:
console.print(
f"[yellow]Warning:[/yellow] Could not translate to {language_name}: {e}"
)
return release_notes
def _format_changelog_date(lang: str) -> str:
"""Format today's date for a changelog entry in the given language."""
from datetime import datetime
now = datetime.now()
if lang == "ko":
return f"{now.year}{now.month}{now.day}"
if lang == "pt-BR":
return f"{now.day:02d} {_PT_BR_MONTHS[now.month]} {now.year}"
return now.strftime("%b %d, %Y")
def update_changelog(
changelog_path: Path,
version: str,
release_notes: str,
lang: str = "en",
) -> bool:
"""Prepend a new release entry to a docs changelog file.
Args:
changelog_path: Path to the changelog.mdx file.
version: Version string (e.g., "1.9.3").
release_notes: Markdown release notes content.
lang: Language code for localized date/link text.
Returns:
True if changelog was updated, False otherwise.
"""
if not changelog_path.exists():
return False
locale_cfg = _CHANGELOG_LOCALES.get(lang, _CHANGELOG_LOCALES["en"])
date_label = _format_changelog_date(lang)
link_text = locale_cfg["link_text"]
# Indent each non-empty line with 2 spaces to match <Update> block format
indented_lines = []
for line in release_notes.splitlines():
if line.strip():
indented_lines.append(f" {line}")
else:
indented_lines.append("")
indented_notes = "\n".join(indented_lines)
entry = (
f'<Update label="{date_label}">\n'
f" ## v{version}\n"
f"\n"
f" [{link_text}]"
f"(https://github.com/crewAIInc/crewAI/releases/tag/{version})\n"
f"\n"
f"{indented_notes}\n"
f"\n"
f"</Update>"
)
content = changelog_path.read_text()
# Insert after the frontmatter closing ---
parts = content.split("---", 2)
if len(parts) >= 3:
new_content = (
parts[0]
+ "---"
+ parts[1]
+ "---\n"
+ entry
+ "\n\n"
+ parts[2].lstrip("\n")
)
else:
new_content = entry + "\n\n" + content
changelog_path.write_text(new_content)
return True
def update_template_dependencies(templates_dir: Path, new_version: str) -> list[Path]:
"""Update crewai dependency versions in CLI template pyproject.toml files.
Handles both pinned (==) and minimum (>=) version specifiers,
as well as extras like [tools].
Args:
templates_dir: Path to the CLI templates directory.
new_version: New version string.
Returns:
List of paths that were updated.
"""
import re
updated = []
for pyproject in templates_dir.rglob("pyproject.toml"):
content = pyproject.read_text()
new_content = re.sub(
r'"crewai(\[tools\])?(==|>=)[^"]*"',
lambda m: f'"crewai{(m.group(1) or "")!s}=={new_version}"',
content,
)
if new_content != content:
pyproject.write_text(new_content)
updated.append(pyproject)
return updated
def find_version_files(base_path: Path) -> list[Path]:
"""Find all __init__.py files that contain __version__.
@@ -394,6 +636,22 @@ def bump(version: str, dry_run: bool, no_push: bool, no_commit: bool) -> None:
"[yellow]Warning:[/yellow] No __version__ attributes found to update"
)
# Update CLI template pyproject.toml files
templates_dir = lib_dir / "crewai" / "src" / "crewai" / "cli" / "templates"
if templates_dir.exists():
if dry_run:
for tpl in templates_dir.rglob("pyproject.toml"):
console.print(
f"[dim][DRY RUN][/dim] Would update template: {tpl.relative_to(cwd)}"
)
else:
tpl_updated = update_template_dependencies(templates_dir, version)
for tpl in tpl_updated:
console.print(
f"[green]✓[/green] Updated template: {tpl.relative_to(cwd)}"
)
updated_files.append(tpl)
if not dry_run:
console.print("\nSyncing workspace...")
run_command(["uv", "sync"])
@@ -575,9 +833,9 @@ def tag(dry_run: bool, no_edit: bool) -> None:
github_contributors = get_github_contributors(commit_range)
if commits.strip():
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
if commits.strip():
contributors_section = ""
if github_contributors:
contributors_section = f"\n\n## Contributors\n\n{', '.join([f'@{u}' for u in github_contributors])}"
@@ -588,7 +846,7 @@ def tag(dry_run: bool, no_edit: bool) -> None:
contributors_section=contributors_section,
)
response = client.chat.completions.create(
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
@@ -643,6 +901,143 @@ def tag(dry_run: bool, no_edit: bool) -> None:
"\n[green]✓[/green] Using generated release notes without editing"
)
is_prerelease = any(
indicator in version.lower()
for indicator in ["a", "b", "rc", "alpha", "beta", "dev"]
)
# Update docs: changelogs + version switcher
docs_json_path = cwd / "docs" / "docs.json"
changelog_langs = ["en", "pt-BR", "ko"]
if not dry_run:
docs_files_staged = []
for lang in changelog_langs:
cl_path = cwd / "docs" / lang / "changelog.mdx"
if lang == "en":
notes_for_lang = release_notes
else:
console.print(f"[dim]Translating release notes to {lang}...[/dim]")
notes_for_lang = translate_release_notes(
release_notes, lang, openai_client
)
if update_changelog(cl_path, version, notes_for_lang, lang=lang):
console.print(
f"[green]✓[/green] Updated {cl_path.relative_to(cwd)}"
)
docs_files_staged.append(str(cl_path))
else:
console.print(
f"[yellow]Warning:[/yellow] Changelog not found at {cl_path.relative_to(cwd)}"
)
if not is_prerelease:
if add_docs_version(docs_json_path, version):
console.print(
f"[green]✓[/green] Added v{version} to docs version switcher"
)
docs_files_staged.append(str(docs_json_path))
else:
console.print(
f"[yellow]Warning:[/yellow] docs.json not found at {docs_json_path.relative_to(cwd)}"
)
if docs_files_staged:
docs_branch = f"docs/changelog-v{version}"
run_command(["git", "checkout", "-b", docs_branch])
for f in docs_files_staged:
run_command(["git", "add", f])
run_command(
[
"git",
"commit",
"-m",
f"docs: update changelog and version for v{version}",
]
)
console.print("[green]✓[/green] Committed docs updates")
run_command(["git", "push", "-u", "origin", docs_branch])
console.print(f"[green]✓[/green] Pushed branch {docs_branch}")
run_command(
[
"gh",
"pr",
"create",
"--base",
"main",
"--title",
f"docs: update changelog and version for v{version}",
"--body",
"",
]
)
console.print("[green]✓[/green] Created docs PR")
run_command(
[
"gh",
"pr",
"merge",
docs_branch,
"--squash",
"--auto",
"--delete-branch",
]
)
console.print("[green]✓[/green] Enabled auto-merge on docs PR")
import time
console.print("[cyan]Waiting for PR checks to pass and merge...[/cyan]")
while True:
time.sleep(10)
try:
state = run_command(
[
"gh",
"pr",
"view",
docs_branch,
"--json",
"state",
"--jq",
".state",
]
)
except subprocess.CalledProcessError:
state = ""
if state == "MERGED":
break
console.print("[dim]Still waiting for PR to merge...[/dim]")
console.print("[green]✓[/green] Docs PR merged")
run_command(["git", "checkout", "main"])
run_command(["git", "pull"])
console.print("[green]✓[/green] main branch updated with docs changes")
else:
for lang in changelog_langs:
cl_path = cwd / "docs" / lang / "changelog.mdx"
translated = " (translated)" if lang != "en" else ""
console.print(
f"[dim][DRY RUN][/dim] Would update {cl_path.relative_to(cwd)}{translated}"
)
if not is_prerelease:
console.print(
f"[dim][DRY RUN][/dim] Would add v{version} to docs version switcher"
)
else:
console.print(
"[dim][DRY RUN][/dim] Skipping docs version (pre-release)"
)
console.print(
f"[dim][DRY RUN][/dim] Would create branch docs/changelog-v{version}, PR, and merge"
)
if not dry_run:
with console.status(f"[cyan]Creating tag {tag_name}..."):
try:
@@ -660,11 +1055,6 @@ def tag(dry_run: bool, no_edit: bool) -> None:
sys.exit(1)
console.print(f"[green]✓[/green] Pushed tag {tag_name}")
is_prerelease = any(
indicator in version.lower()
for indicator in ["a", "b", "rc", "alpha", "beta", "dev"]
)
with console.status("[cyan]Creating GitHub Release..."):
try:
gh_cmd = [

Some files were not shown because too many files have changed in this diff Show More