From bcc3e358cb88dbac07aa65cd4bc6992a0eba54f1 Mon Sep 17 00:00:00 2001 From: Gil Feig Date: Thu, 20 Nov 2025 16:58:41 -0800 Subject: [PATCH 1/4] feat: Add Merge Agent Handler tool (#3911) * feat: Add Merge Agent Handler tool * Fix linting issues * Empty --- lib/crewai-tools/src/crewai_tools/__init__.py | 4 + .../src/crewai_tools/tools/__init__.py | 4 + .../tools/merge_agent_handler_tool/README.md | 231 +++++++++ .../merge_agent_handler_tool/__init__.py | 8 + .../merge_agent_handler_tool.py | 362 +++++++++++++ .../tools/merge_agent_handler_tool_test.py | 490 ++++++++++++++++++ 6 files changed, 1099 insertions(+) create mode 100644 lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/README.md create mode 100644 lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/__init__.py create mode 100644 lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/merge_agent_handler_tool.py create mode 100644 lib/crewai-tools/tests/tools/merge_agent_handler_tool_test.py diff --git a/lib/crewai-tools/src/crewai_tools/__init__.py b/lib/crewai-tools/src/crewai_tools/__init__.py index 22c9cd541..c14dc160a 100644 --- a/lib/crewai-tools/src/crewai_tools/__init__.py +++ b/lib/crewai-tools/src/crewai_tools/__init__.py @@ -90,6 +90,9 @@ from crewai_tools.tools.json_search_tool.json_search_tool import JSONSearchTool from crewai_tools.tools.linkup.linkup_search_tool import LinkupSearchTool from crewai_tools.tools.llamaindex_tool.llamaindex_tool import LlamaIndexTool from crewai_tools.tools.mdx_search_tool.mdx_search_tool import MDXSearchTool +from crewai_tools.tools.merge_agent_handler_tool.merge_agent_handler_tool import ( + MergeAgentHandlerTool, +) from crewai_tools.tools.mongodb_vector_search_tool.vector_search import ( MongoDBVectorSearchConfig, MongoDBVectorSearchTool, @@ -235,6 +238,7 @@ __all__ = [ "LlamaIndexTool", "MCPServerAdapter", "MDXSearchTool", + "MergeAgentHandlerTool", "MongoDBVectorSearchConfig", "MongoDBVectorSearchTool", "MultiOnTool", diff --git a/lib/crewai-tools/src/crewai_tools/tools/__init__.py b/lib/crewai-tools/src/crewai_tools/tools/__init__.py index 36806d281..51d32ddc2 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/__init__.py +++ b/lib/crewai-tools/src/crewai_tools/tools/__init__.py @@ -79,6 +79,9 @@ from crewai_tools.tools.json_search_tool.json_search_tool import JSONSearchTool from crewai_tools.tools.linkup.linkup_search_tool import LinkupSearchTool from crewai_tools.tools.llamaindex_tool.llamaindex_tool import LlamaIndexTool from crewai_tools.tools.mdx_search_tool.mdx_search_tool import MDXSearchTool +from crewai_tools.tools.merge_agent_handler_tool.merge_agent_handler_tool import ( + MergeAgentHandlerTool, +) from crewai_tools.tools.mongodb_vector_search_tool import ( MongoDBToolSchema, MongoDBVectorSearchConfig, @@ -218,6 +221,7 @@ __all__ = [ "LinkupSearchTool", "LlamaIndexTool", "MDXSearchTool", + "MergeAgentHandlerTool", "MongoDBToolSchema", "MongoDBVectorSearchConfig", "MongoDBVectorSearchTool", diff --git a/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/README.md b/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/README.md new file mode 100644 index 000000000..291ddcad4 --- /dev/null +++ b/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/README.md @@ -0,0 +1,231 @@ +# MergeAgentHandlerTool Documentation + +## Description + +This tool is a wrapper around the Merge Agent Handler platform and gives your agent access to third-party tools and integrations via the Model Context Protocol (MCP). Merge Agent Handler securely manages authentication, permissions, and monitoring of all tool interactions across platforms like Linear, Jira, Slack, GitHub, and many more. + +## Installation + +### Step 1: Set up a virtual environment (recommended) + +It's recommended to use a virtual environment to avoid conflicts with other packages: + +```shell +# Create a virtual environment +python3 -m venv venv + +# Activate the virtual environment +# On macOS/Linux: +source venv/bin/activate + +# On Windows: +# venv\Scripts\activate +``` + +### Step 2: Install CrewAI Tools + +To incorporate this tool into your project, install CrewAI with tools support: + +```shell +pip install 'crewai[tools]' +``` + +### Step 3: Set up your Agent Handler credentials + +You'll need to set up your Agent Handler API key. You can get your API key from the [Agent Handler dashboard](https://ah.merge.dev). + +```shell +# Set the API key in your current terminal session +export AGENT_HANDLER_API_KEY='your-api-key-here' + +# Or add it to your shell profile for persistence (e.g., ~/.bashrc, ~/.zshrc) +echo "export AGENT_HANDLER_API_KEY='your-api-key-here'" >> ~/.zshrc +source ~/.zshrc +``` + +**Alternative: Use a `.env` file** + +You can also use a `.env` file in your project directory: + +```shell +# Create a .env file +echo "AGENT_HANDLER_API_KEY=your-api-key-here" > .env + +# Load it in your Python script +from dotenv import load_dotenv +load_dotenv() +``` + +**Note**: Make sure to add `.env` to your `.gitignore` to avoid committing secrets! + +## Prerequisites + +Before using this tool, you need to: + +1. **Create a Tool Pack** in Agent Handler with the connectors and tools you want to use +2. **Register a User** who will be executing the tools +3. **Authenticate connectors** for the registered user (using Agent Handler Link) + +You can do this via the [Agent Handler dashboard](https://ah.merge.dev) or the [Agent Handler API](https://docs.ah.merge.dev). + +## Example Usage + +### Example 1: Using a specific tool + +The following example demonstrates how to initialize a specific tool and use it with a CrewAI agent: + +```python +from crewai_tools import MergeAgentHandlerTool +from crewai import Agent, Task + +# Initialize a specific tool +create_issue_tool = MergeAgentHandlerTool.from_tool_name( + tool_name="linear__create_issue", + tool_pack_id="134e0111-0f67-44f6-98f0-597000290bb3", + registered_user_id="91b2b905-e866-40c8-8be2-efe53827a0aa" +) + +# Define agent with the tool +project_manager = Agent( + role="Project Manager", + goal="Create and manage project tasks efficiently", + backstory=( + "You are an experienced project manager who tracks tasks " + "and issues across various project management tools." + ), + verbose=True, + tools=[create_issue_tool], +) + +# Execute task +task = Task( + description="Create a new issue in Linear titled 'Implement user authentication' with high priority", + agent=project_manager, + expected_output="Confirmation that the issue was created with its ID", +) + +task.execute() +``` + +### Example 2: Loading all tools from a Tool Pack + +You can load all tools from a Tool Pack at once: + +```python +from crewai_tools import MergeAgentHandlerTool +from crewai import Agent, Task + +# Load all tools from a Tool Pack +tools = MergeAgentHandlerTool.from_tool_pack( + tool_pack_id="134e0111-0f67-44f6-98f0-597000290bb3", + registered_user_id="91b2b905-e866-40c8-8be2-efe53827a0aa" +) + +# Define agent with all tools +support_agent = Agent( + role="Support Engineer", + goal="Handle customer support requests across multiple platforms", + backstory=( + "You are a skilled support engineer who can access customer " + "data and create tickets across various support tools." + ), + verbose=True, + tools=tools, +) +``` + +### Example 3: Loading specific tools from a Tool Pack + +You can also load only specific tools from a Tool Pack: + +```python +from crewai_tools import MergeAgentHandlerTool + +# Load only specific tools +tools = MergeAgentHandlerTool.from_tool_pack( + tool_pack_id="134e0111-0f67-44f6-98f0-597000290bb3", + registered_user_id="91b2b905-e866-40c8-8be2-efe53827a0aa", + tool_names=["linear__create_issue", "linear__get_issues", "slack__send_message"] +) +``` + +### Example 4: Using with local/staging environment + +For development, you can point to a different Agent Handler environment: + +```python +from crewai_tools import MergeAgentHandlerTool + +# Use with local or staging environment +tool = MergeAgentHandlerTool.from_tool_name( + tool_name="linear__create_issue", + tool_pack_id="your-tool-pack-id", + registered_user_id="your-user-id", + base_url="http://localhost:8000" # or your staging URL +) +``` + +## API Reference + +### Class Methods + +#### `from_tool_name()` + +Create a single tool instance for a specific tool. + +**Parameters:** +- `tool_name` (str): Name of the tool (e.g., "linear__create_issue") +- `tool_pack_id` (str): UUID of the Tool Pack +- `registered_user_id` (str): UUID or origin_id of the registered user +- `base_url` (str, optional): Base URL for Agent Handler API (defaults to "https://api.ah.merge.dev") + +**Returns:** `MergeAgentHandlerTool` instance + +#### `from_tool_pack()` + +Create multiple tool instances from a Tool Pack. + +**Parameters:** +- `tool_pack_id` (str): UUID of the Tool Pack +- `registered_user_id` (str): UUID or origin_id of the registered user +- `tool_names` (List[str], optional): List of specific tool names to load. If None, loads all tools. +- `base_url` (str, optional): Base URL for Agent Handler API (defaults to "https://api.ah.merge.dev") + +**Returns:** `List[MergeAgentHandlerTool]` instances + +## Available Connectors + +Merge Agent Handler supports 100+ integrations including: + +**Project Management:** Linear, Jira, Asana, Monday, ClickUp, Height, Shortcut + +**Communication:** Slack, Microsoft Teams, Discord + +**CRM:** Salesforce, HubSpot, Pipedrive + +**Development:** GitHub, GitLab, Bitbucket + +**Documentation:** Notion, Confluence, Google Docs + +**And many more...** + +For a complete list of available connectors and tools, visit the [Agent Handler documentation](https://docs.ah.merge.dev). + +## Authentication + +Agent Handler handles all authentication for you. Users authenticate to third-party services via Agent Handler Link, and the platform securely manages tokens and credentials. Your agents can then execute tools without worrying about authentication details. + +## Security + +All tool executions are: +- **Logged and monitored** for audit trails +- **Scanned for PII** to prevent sensitive data leaks +- **Rate limited** based on your plan +- **Permission-controlled** at the user and organization level + +## Support + +For questions or issues: +- 📚 [Documentation](https://docs.ah.merge.dev) +- 💬 [Discord Community](https://merge.dev/discord) +- 📧 [Support Email](mailto:support@merge.dev) diff --git a/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/__init__.py b/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/__init__.py new file mode 100644 index 000000000..414481220 --- /dev/null +++ b/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/__init__.py @@ -0,0 +1,8 @@ +"""Merge Agent Handler tool for CrewAI.""" + +from crewai_tools.tools.merge_agent_handler_tool.merge_agent_handler_tool import ( + MergeAgentHandlerTool, +) + + +__all__ = ["MergeAgentHandlerTool"] diff --git a/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/merge_agent_handler_tool.py b/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/merge_agent_handler_tool.py new file mode 100644 index 000000000..70077d0ee --- /dev/null +++ b/lib/crewai-tools/src/crewai_tools/tools/merge_agent_handler_tool/merge_agent_handler_tool.py @@ -0,0 +1,362 @@ +"""Merge Agent Handler tools wrapper for CrewAI.""" + +import json +import logging +from typing import Any +from uuid import uuid4 + +from crewai.tools import BaseTool, EnvVar +from pydantic import BaseModel, Field, create_model +import requests +import typing_extensions as te + + +logger = logging.getLogger(__name__) + + +class MergeAgentHandlerToolError(Exception): + """Base exception for Merge Agent Handler tool errors.""" + + + +class MergeAgentHandlerTool(BaseTool): + """ + Wrapper for Merge Agent Handler tools. + + This tool allows CrewAI agents to execute tools from Merge Agent Handler, + which provides secure access to third-party integrations via the Model Context Protocol (MCP). + + Agent Handler manages authentication, permissions, and monitoring of all tool interactions. + """ + + tool_pack_id: str = Field( + ..., description="UUID of the Agent Handler Tool Pack to use" + ) + registered_user_id: str = Field( + ..., description="UUID or origin_id of the registered user" + ) + tool_name: str = Field(..., description="Name of the specific tool to execute") + base_url: str = Field( + default="https://ah-api.merge.dev", + description="Base URL for Agent Handler API", + ) + session_id: str | None = Field( + default=None, description="MCP session ID (generated if not provided)" + ) + env_vars: list[EnvVar] = Field( + default_factory=lambda: [ + EnvVar( + name="AGENT_HANDLER_API_KEY", + description="Production API key for Agent Handler services", + required=True, + ), + ] + ) + + def model_post_init(self, __context: Any) -> None: + """Initialize session ID if not provided.""" + super().model_post_init(__context) + if self.session_id is None: + self.session_id = str(uuid4()) + + def _get_api_key(self) -> str: + """Get the API key from environment variables.""" + import os + + api_key = os.environ.get("AGENT_HANDLER_API_KEY") + if not api_key: + raise MergeAgentHandlerToolError( + "AGENT_HANDLER_API_KEY environment variable is required. " + "Set it with: export AGENT_HANDLER_API_KEY='your-key-here'" + ) + return api_key + + def _make_mcp_request( + self, method: str, params: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Make a JSON-RPC 2.0 MCP request to Agent Handler.""" + url = f"{self.base_url}/api/v1/tool-packs/{self.tool_pack_id}/registered-users/{self.registered_user_id}/mcp" + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._get_api_key()}", + "Mcp-Session-Id": self.session_id or str(uuid4()), + } + + payload: dict[str, Any] = { + "jsonrpc": "2.0", + "method": method, + "id": str(uuid4()), + } + + if params: + payload["params"] = params + + # Log the full payload for debugging + logger.debug(f"MCP Request to {url}: {json.dumps(payload, indent=2)}") + + try: + response = requests.post(url, json=payload, headers=headers, timeout=60) + response.raise_for_status() + result = response.json() + + # Handle JSON-RPC error responses + if "error" in result: + error_msg = result["error"].get("message", "Unknown error") + error_code = result["error"].get("code", -1) + logger.error( + f"Agent Handler API error (code {error_code}): {error_msg}" + ) + raise MergeAgentHandlerToolError(f"API Error: {error_msg}") + + return result + + except requests.exceptions.RequestException as e: + logger.error(f"Failed to call Agent Handler API: {e!s}") + raise MergeAgentHandlerToolError( + f"Failed to communicate with Agent Handler API: {e!s}" + ) from e + + def _run(self, **kwargs: Any) -> Any: + """Execute the Agent Handler tool with the given arguments.""" + try: + # Log what we're about to send + logger.info(f"Executing {self.tool_name} with arguments: {kwargs}") + + # Make the tool call via MCP + result = self._make_mcp_request( + method="tools/call", + params={"name": self.tool_name, "arguments": kwargs}, + ) + + # Extract the actual result from the MCP response + if "result" in result and "content" in result["result"]: + content = result["result"]["content"] + if content and len(content) > 0: + # Parse the text content (it's JSON-encoded) + text_content = content[0].get("text", "") + try: + return json.loads(text_content) + except json.JSONDecodeError: + return text_content + + return result + + except MergeAgentHandlerToolError: + raise + except Exception as e: + logger.error(f"Unexpected error executing tool {self.tool_name}: {e!s}") + raise MergeAgentHandlerToolError(f"Tool execution failed: {e!s}") from e + + @classmethod + def from_tool_name( + cls, + tool_name: str, + tool_pack_id: str, + registered_user_id: str, + base_url: str = "https://ah-api.merge.dev", + **kwargs: Any, + ) -> te.Self: + """ + Create a MergeAgentHandlerTool from a tool name. + + Args: + tool_name: Name of the tool (e.g., "linear__create_issue") + tool_pack_id: UUID of the Tool Pack + registered_user_id: UUID of the registered user + base_url: Base URL for Agent Handler API (defaults to production) + **kwargs: Additional arguments to pass to the tool + + Returns: + MergeAgentHandlerTool instance ready to use + + Example: + >>> tool = MergeAgentHandlerTool.from_tool_name( + ... tool_name="linear__create_issue", + ... tool_pack_id="134e0111-0f67-44f6-98f0-597000290bb3", + ... registered_user_id="91b2b905-e866-40c8-8be2-efe53827a0aa" + ... ) + """ + # Create an empty args schema model (proper BaseModel subclass) + empty_args_schema = create_model(f"{tool_name.replace('__', '_').title()}Args") + + # Initialize session and get tool schema + instance = cls( + name=tool_name, + description=f"Execute {tool_name} via Agent Handler", + tool_pack_id=tool_pack_id, + registered_user_id=registered_user_id, + tool_name=tool_name, + base_url=base_url, + args_schema=empty_args_schema, # Empty schema that properly inherits from BaseModel + **kwargs, + ) + + # Try to fetch the actual tool schema from Agent Handler + try: + result = instance._make_mcp_request(method="tools/list") + if "result" in result and "tools" in result["result"]: + tools = result["result"]["tools"] + tool_schema = next( + (t for t in tools if t.get("name") == tool_name), None + ) + + if tool_schema: + instance.description = tool_schema.get( + "description", instance.description + ) + + # Convert parameters schema to Pydantic model + if "parameters" in tool_schema: + try: + params = tool_schema["parameters"] + if params.get("type") == "object" and "properties" in params: + # Build field definitions for Pydantic + fields = {} + properties = params["properties"] + required = params.get("required", []) + + for field_name, field_schema in properties.items(): + field_type = Any # Default type + field_default = ... # Required by default + + # Map JSON schema types to Python types + json_type = field_schema.get("type", "string") + if json_type == "string": + field_type = str + elif json_type == "integer": + field_type = int + elif json_type == "number": + field_type = float + elif json_type == "boolean": + field_type = bool + elif json_type == "array": + field_type = list[Any] + elif json_type == "object": + field_type = dict[str, Any] + + # Make field optional if not required + if field_name not in required: + field_type = field_type | None + field_default = None + + field_description = field_schema.get("description") + if field_description: + fields[field_name] = ( + field_type, + Field( + default=field_default, + description=field_description, + ), + ) + else: + fields[field_name] = (field_type, field_default) + + # Create the Pydantic model + if fields: + args_schema = create_model( + f"{tool_name.replace('__', '_').title()}Args", + **fields, + ) + instance.args_schema = args_schema + + except Exception as e: + logger.warning( + f"Failed to create args schema for {tool_name}: {e!s}" + ) + + except Exception as e: + logger.warning( + f"Failed to fetch tool schema for {tool_name}, using defaults: {e!s}" + ) + + return instance + + @classmethod + def from_tool_pack( + cls, + tool_pack_id: str, + registered_user_id: str, + tool_names: list[str] | None = None, + base_url: str = "https://ah-api.merge.dev", + **kwargs: Any, + ) -> list[te.Self]: + """ + Create multiple MergeAgentHandlerTool instances from a Tool Pack. + + Args: + tool_pack_id: UUID of the Tool Pack + registered_user_id: UUID or origin_id of the registered user + tool_names: Optional list of specific tool names to load. If None, loads all tools. + base_url: Base URL for Agent Handler API (defaults to production) + **kwargs: Additional arguments to pass to each tool + + Returns: + List of MergeAgentHandlerTool instances + + Example: + >>> tools = MergeAgentHandlerTool.from_tool_pack( + ... tool_pack_id="134e0111-0f67-44f6-98f0-597000290bb3", + ... registered_user_id="91b2b905-e866-40c8-8be2-efe53827a0aa", + ... tool_names=["linear__create_issue", "linear__get_issues"] + ... ) + """ + # Create a temporary instance to fetch the tool list + temp_instance = cls( + name="temp", + description="temp", + tool_pack_id=tool_pack_id, + registered_user_id=registered_user_id, + tool_name="temp", + base_url=base_url, + args_schema=BaseModel, + ) + + try: + # Fetch available tools + result = temp_instance._make_mcp_request(method="tools/list") + + if "result" not in result or "tools" not in result["result"]: + raise MergeAgentHandlerToolError( + "Failed to fetch tools from Agent Handler Tool Pack" + ) + + available_tools = result["result"]["tools"] + + # Filter tools if specific names were requested + if tool_names: + available_tools = [ + t for t in available_tools if t.get("name") in tool_names + ] + + # Check if all requested tools were found + found_names = {t.get("name") for t in available_tools} + missing_names = set(tool_names) - found_names + if missing_names: + logger.warning( + f"The following tools were not found in the Tool Pack: {missing_names}" + ) + + # Create tool instances + tools = [] + for tool_schema in available_tools: + tool_name = tool_schema.get("name") + if not tool_name: + continue + + tool = cls.from_tool_name( + tool_name=tool_name, + tool_pack_id=tool_pack_id, + registered_user_id=registered_user_id, + base_url=base_url, + **kwargs, + ) + tools.append(tool) + + return tools + + except MergeAgentHandlerToolError: + raise + except Exception as e: + logger.error(f"Failed to create tools from Tool Pack: {e!s}") + raise MergeAgentHandlerToolError(f"Failed to load Tool Pack: {e!s}") from e diff --git a/lib/crewai-tools/tests/tools/merge_agent_handler_tool_test.py b/lib/crewai-tools/tests/tools/merge_agent_handler_tool_test.py new file mode 100644 index 000000000..17bdd79fb --- /dev/null +++ b/lib/crewai-tools/tests/tools/merge_agent_handler_tool_test.py @@ -0,0 +1,490 @@ +"""Tests for MergeAgentHandlerTool.""" + +import os +from unittest.mock import Mock, patch + +import pytest + +from crewai_tools import MergeAgentHandlerTool + + +@pytest.fixture(autouse=True) +def mock_agent_handler_api_key(): + """Mock the Agent Handler API key environment variable.""" + with patch.dict(os.environ, {"AGENT_HANDLER_API_KEY": "test_key"}): + yield + + +@pytest.fixture +def mock_tool_pack_response(): + """Mock response for tools/list MCP request.""" + return { + "jsonrpc": "2.0", + "id": "test-id", + "result": { + "tools": [ + { + "name": "linear__create_issue", + "description": "Creates a new issue in Linear", + "parameters": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The issue title", + }, + "description": { + "type": "string", + "description": "The issue description", + }, + "priority": { + "type": "integer", + "description": "Priority level (1-4)", + }, + }, + "required": ["title"], + }, + }, + { + "name": "linear__get_issues", + "description": "Get issues from Linear", + "parameters": { + "type": "object", + "properties": { + "filter": { + "type": "object", + "description": "Filter criteria", + } + }, + }, + }, + ] + }, + } + + +@pytest.fixture +def mock_tool_execute_response(): + """Mock response for tools/call MCP request.""" + return { + "jsonrpc": "2.0", + "id": "test-id", + "result": { + "content": [ + { + "type": "text", + "text": '{"success": true, "id": "ISS-123", "title": "Test Issue"}', + } + ] + }, + } + + +def test_tool_initialization(): + """Test basic tool initialization.""" + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + ) + + assert tool.name == "test_tool" + assert "Test tool" in tool.description # Description gets formatted by BaseTool + assert tool.tool_pack_id == "test-pack-id" + assert tool.registered_user_id == "test-user-id" + assert tool.tool_name == "linear__create_issue" + assert tool.base_url == "https://ah-api.merge.dev" + assert tool.session_id is not None + + +def test_tool_initialization_with_custom_base_url(): + """Test tool initialization with custom base URL.""" + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + base_url="http://localhost:8000", + ) + + assert tool.base_url == "http://localhost:8000" + + +def test_missing_api_key(): + """Test that missing API key raises appropriate error.""" + with patch.dict(os.environ, {}, clear=True): + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + ) + + with pytest.raises(Exception) as exc_info: + tool._get_api_key() + + assert "AGENT_HANDLER_API_KEY" in str(exc_info.value) + + +@patch("requests.post") +def test_mcp_request_success(mock_post, mock_tool_pack_response): + """Test successful MCP request.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_pack_response + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + ) + + result = tool._make_mcp_request(method="tools/list") + + assert "result" in result + assert "tools" in result["result"] + assert len(result["result"]["tools"]) == 2 + + +@patch("requests.post") +def test_mcp_request_error(mock_post): + """Test MCP request with error response.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "jsonrpc": "2.0", + "id": "test-id", + "error": {"code": -32601, "message": "Method not found"}, + } + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + ) + + with pytest.raises(Exception) as exc_info: + tool._make_mcp_request(method="invalid/method") + + assert "Method not found" in str(exc_info.value) + + +@patch("requests.post") +def test_mcp_request_http_error(mock_post): + """Test MCP request with HTTP error.""" + mock_post.side_effect = Exception("Connection error") + + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + ) + + with pytest.raises(Exception) as exc_info: + tool._make_mcp_request(method="tools/list") + + assert "Connection error" in str(exc_info.value) + + +@patch("requests.post") +def test_tool_execution(mock_post, mock_tool_execute_response): + """Test tool execution via _run method.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_execute_response + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + ) + + result = tool._run(title="Test Issue", description="Test description") + + assert result["success"] is True + assert result["id"] == "ISS-123" + assert result["title"] == "Test Issue" + + +@patch("requests.post") +def test_from_tool_name(mock_post, mock_tool_pack_response): + """Test creating tool from tool name.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_pack_response + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool.from_tool_name( + tool_name="linear__create_issue", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + ) + + assert tool.name == "linear__create_issue" + assert tool.description == "Creates a new issue in Linear" + assert tool.tool_name == "linear__create_issue" + + +@patch("requests.post") +def test_from_tool_name_with_custom_base_url(mock_post, mock_tool_pack_response): + """Test creating tool from tool name with custom base URL.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_pack_response + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool.from_tool_name( + tool_name="linear__create_issue", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + base_url="http://localhost:8000", + ) + + assert tool.base_url == "http://localhost:8000" + + +@patch("requests.post") +def test_from_tool_pack_all_tools(mock_post, mock_tool_pack_response): + """Test creating all tools from a Tool Pack.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_pack_response + mock_post.return_value = mock_response + + tools = MergeAgentHandlerTool.from_tool_pack( + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + ) + + assert len(tools) == 2 + assert tools[0].name == "linear__create_issue" + assert tools[1].name == "linear__get_issues" + + +@patch("requests.post") +def test_from_tool_pack_specific_tools(mock_post, mock_tool_pack_response): + """Test creating specific tools from a Tool Pack.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_pack_response + mock_post.return_value = mock_response + + tools = MergeAgentHandlerTool.from_tool_pack( + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_names=["linear__create_issue"], + ) + + assert len(tools) == 1 + assert tools[0].name == "linear__create_issue" + + +@patch("requests.post") +def test_from_tool_pack_with_custom_base_url(mock_post, mock_tool_pack_response): + """Test creating tools from Tool Pack with custom base URL.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_pack_response + mock_post.return_value = mock_response + + tools = MergeAgentHandlerTool.from_tool_pack( + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + base_url="http://localhost:8000", + ) + + assert len(tools) == 2 + assert all(tool.base_url == "http://localhost:8000" for tool in tools) + + +@patch("requests.post") +def test_tool_execution_with_text_response(mock_post): + """Test tool execution with plain text response.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "jsonrpc": "2.0", + "id": "test-id", + "result": {"content": [{"type": "text", "text": "Plain text result"}]}, + } + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + ) + + result = tool._run(title="Test") + + assert result == "Plain text result" + + +@patch("requests.post") +def test_mcp_request_builds_correct_url(mock_post, mock_tool_pack_response): + """Test that MCP request builds correct URL.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_pack_response + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-123", + registered_user_id="user-456", + tool_name="linear__create_issue", + base_url="https://ah-api.merge.dev", + ) + + tool._make_mcp_request(method="tools/list") + + expected_url = ( + "https://ah-api.merge.dev/api/v1/tool-packs/" + "test-pack-123/registered-users/user-456/mcp" + ) + mock_post.assert_called_once() + assert mock_post.call_args[0][0] == expected_url + + +@patch("requests.post") +def test_mcp_request_includes_correct_headers(mock_post, mock_tool_pack_response): + """Test that MCP request includes correct headers.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_tool_pack_response + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__create_issue", + ) + + tool._make_mcp_request(method="tools/list") + + mock_post.assert_called_once() + headers = mock_post.call_args.kwargs["headers"] + assert headers["Content-Type"] == "application/json" + assert headers["Authorization"] == "Bearer test_key" + assert "Mcp-Session-Id" in headers + + +@patch("requests.post") +def test_tool_parameters_are_passed_in_request(mock_post): + """Test that tool parameters are correctly included in the MCP request.""" + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "jsonrpc": "2.0", + "id": "test-id", + "result": {"content": [{"type": "text", "text": '{"success": true}'}]}, + } + mock_post.return_value = mock_response + + tool = MergeAgentHandlerTool( + name="test_tool", + description="Test tool", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + tool_name="linear__update_issue", + ) + + # Execute tool with specific parameters + tool._run(id="issue-123", title="New Title", priority=1) + + # Verify the request was made + mock_post.assert_called_once() + + # Get the JSON payload that was sent + payload = mock_post.call_args.kwargs["json"] + + # Verify MCP structure + assert payload["jsonrpc"] == "2.0" + assert payload["method"] == "tools/call" + assert "id" in payload + + # Verify parameters are in the request + assert "params" in payload + assert payload["params"]["name"] == "linear__update_issue" + assert "arguments" in payload["params"] + + # Verify the actual arguments were passed + arguments = payload["params"]["arguments"] + assert arguments["id"] == "issue-123" + assert arguments["title"] == "New Title" + assert arguments["priority"] == 1 + + +@patch("requests.post") +def test_tool_run_method_passes_parameters(mock_post, mock_tool_pack_response): + """Test that parameters are passed when using the .run() method (how CrewAI calls it).""" + # Mock the tools/list response + mock_response = Mock() + mock_response.status_code = 200 + + # First call: tools/list + # Second call: tools/call + mock_response.json.side_effect = [ + mock_tool_pack_response, # tools/list response + { + "jsonrpc": "2.0", + "id": "test-id", + "result": {"content": [{"type": "text", "text": '{"success": true, "id": "issue-123"}'}]}, + }, # tools/call response + ] + mock_post.return_value = mock_response + + # Create tool using from_tool_name (which fetches schema) + tool = MergeAgentHandlerTool.from_tool_name( + tool_name="linear__create_issue", + tool_pack_id="test-pack-id", + registered_user_id="test-user-id", + ) + + # Call using .run() method (this is how CrewAI invokes tools) + result = tool.run(title="Test Issue", description="Test description", priority=2) + + # Verify two calls were made: tools/list and tools/call + assert mock_post.call_count == 2 + + # Get the second call (tools/call) + second_call = mock_post.call_args_list[1] + payload = second_call.kwargs["json"] + + # Verify it's a tools/call request + assert payload["method"] == "tools/call" + assert payload["params"]["name"] == "linear__create_issue" + + # Verify parameters were passed + arguments = payload["params"]["arguments"] + assert arguments["title"] == "Test Issue" + assert arguments["description"] == "Test description" + assert arguments["priority"] == 2 + + # Verify result was returned + assert result["success"] is True + assert result["id"] == "issue-123" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From a559cedbd1c0d6100bbe5f5118635e353a0a4da1 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Mon, 24 Nov 2025 12:29:11 -0500 Subject: [PATCH 2/4] chore: ensure proper cassettes for agent tests * chore: ensure proper cassettes for agent tests * chore: tweak eval test to avoid race condition --- lib/crewai/tests/agents/test_agent.py | 37 +- .../test_agent_execute_task_with_tool.yaml | 434 ++-- .../test_agent_repeated_tool_usage.yaml | 1034 +++----- .../tests/cassettes/test_cache_hitting.yaml | 2220 +++++++++-------- .../evaluation/test_agent_evaluator.py | 22 +- 5 files changed, 1831 insertions(+), 1916 deletions(-) diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index 5bc9f3421..977db03db 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -307,27 +307,22 @@ def test_cache_hitting(): event_handled = True condition.notify() - with ( - patch.object(CacheHandler, "read") as read, - ): - read.return_value = "0" - task = Task( - description="What is 2 times 6? Ignore correctness and just return the result of the multiplication tool, you must use the tool.", - agent=agent, - expected_output="The number that is the result of the multiplication tool.", - ) - output = agent.execute_task(task) - assert output == "0" - read.assert_called_with( - tool="multiplier", input='{"first_number": 2, "second_number": 6}' - ) - with condition: - if not event_handled: - condition.wait(timeout=5) - assert event_handled, "Timeout waiting for tool usage event" - assert len(received_events) == 1 - assert isinstance(received_events[0], ToolUsageFinishedEvent) - assert received_events[0].from_cache + task = Task( + description="What is 2 times 6? Return only the result of the multiplication.", + agent=agent, + expected_output="The result of the multiplication.", + ) + output = agent.execute_task(task) + assert output == "12" + + with condition: + if not event_handled: + condition.wait(timeout=5) + assert event_handled, "Timeout waiting for tool usage event" + assert len(received_events) == 1 + assert isinstance(received_events[0], ToolUsageFinishedEvent) + assert received_events[0].from_cache + assert received_events[0].output == "12" @pytest.mark.vcr(filter_headers=["authorization"]) diff --git a/lib/crewai/tests/cassettes/test_agent_execute_task_with_tool.yaml b/lib/crewai/tests/cassettes/test_agent_execute_task_with_tool.yaml index a36161061..c60f1d852 100644 --- a/lib/crewai/tests/cassettes/test_agent_execute_task_with_tool.yaml +++ b/lib/crewai/tests/cassettes/test_agent_execute_task_with_tool.yaml @@ -1,23 +1,22 @@ interactions: - request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\nTool Name: dummy_tool\nTool Arguments: {''query'': {''description'': None, ''type'': ''str''}}\nTool Description: - Useful for when you need to get a dummy result for a query.\n\nUse the following - format:\n\nThought: you should always think about what to do\nAction: the action - to take, only one name of [dummy_tool], just the name, exactly as it''s written.\nAction - Input: the input to the action, just a simple python dictionary, enclosed in - curly braces, using \" to wrap keys and values.\nObservation: the result of - the action\n\nOnce all necessary information is gathered:\n\nThought: I now - know the final answer\nFinal Answer: the final answer to the original input - question"}, {"role": "user", "content": "\nCurrent Task: Use the dummy tool - to get a result for ''test query''\n\nThis is the expect criteria for your final - answer: The result from the dummy tool\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-3.5-turbo", "stop": ["\nObservation:"], - "stream": false}' + Useful for when you need to get a dummy result for a query.\n\nIMPORTANT: Use + the following format in your response:\n\n```\nThought: you should always think + about what to do\nAction: the action to take, only one name of [dummy_tool], + just the name, exactly as it''s written.\nAction Input: the input to the action, + just a simple JSON object, enclosed in curly braces, using \" to wrap keys and + values.\nObservation: the result of the action\n```\n\nOnce all necessary information + is gathered, return the following format:\n\n```\nThought: I now know the final + answer\nFinal Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: Use the dummy tool to get a result for ''test query''\n\nThis is the expected + criteria for your final answer: The result from the dummy tool\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-3.5-turbo"}' headers: accept: - application/json @@ -26,13 +25,13 @@ interactions: connection: - keep-alive content-length: - - '1363' + - '1381' content-type: - application/json host: - api.openai.com user-agent: - - OpenAI/Python 1.52.1 + - OpenAI/Python 1.109.1 x-stainless-arch: - arm64 x-stainless-async: @@ -42,35 +41,33 @@ interactions: x-stainless-os: - MacOS x-stainless-package-version: - - 1.52.1 - x-stainless-raw-response: - - 'true' + - 1.109.1 + x-stainless-read-timeout: + - '600' x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.7 + - 3.12.10 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AmjTkjHtNtJfKGo6wS35grXEzfoqv\",\n \"object\": - \"chat.completion\",\n \"created\": 1736177928,\n \"model\": \"gpt-3.5-turbo-0125\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I should use the dummy tool to get a - result for the 'test query'.\\n\\nAction: dummy_tool\\nAction Input: {\\\"query\\\": - \\\"test query\\\"}\",\n \"refusal\": null\n },\n \"logprobs\": - null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 271,\n \"completion_tokens\": 31,\n \"total_tokens\": 302,\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 \"system_fingerprint\": - null\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAA4xTwW4TMRC95ytGvvSSVGlDWthbqYSIECAQSFRstXK8s7tuvR5jj5uGKv+O7CTd + FAriYtnz5j2/8YwfRgBC16IAoTrJqndmctl8ff3tJsxWd29vLu/7d1eXnz4vfq7cVft+1ohxYtDy + BhXvWceKemeQNdktrDxKxqR6cn72YjqdzU/mGeipRpNorePJ7Hg+4eiXNJmenM53zI60wiAK+D4C + AHjIa/Joa7wXBUzH+0iPIcgWRfGYBCA8mRQRMgQdWFoW4wFUZBlttr2A0FE0NcSAwB1CHft+XTGR + ASZokUGCxxANQ0M+pxwxBoYfEf366Li0FyoVXBww9zFYWBe5gIdS5OxS5H2NQXntUkaKfCCLYygF + rx2mcykC+1JsNqX9uAzo7+RW/8veHWR3nQzgkaO3WIPcIf92WtovHcW24wIWYGkFt2lJiY220oC0 + YYW+tG/y6SKftvfudT31wytlH4fv6rGJQaa+2mjMASCtJc5l5I5e75DNYw8Ntc7TMvxGFY22OnSV + RxnIpn4FJicyuhkBXOdZiU/aL5yn3nHFdIv5utOXr7Z6YhjPAT2f7UAmlmaIz85Ox8/oVTWy1CYc + TJtQUnVYD9RhNGWsNR0Ao4Oq/3TznPa2cm3b/5EfAKXQMdaV81hr9bTiIc1j+r1/S3t85WxYpEnU + CivW6FMnamxkNNt/JcI6MPZVo22L3nmdP1fq5Ggz+gUAAP//AwDDsh2ZWwQAAA== headers: - CF-Cache-Status: - - DYNAMIC CF-RAY: - - 8fdccc13af387bb2-ATL + - 9a3a73adce2d43c2-EWR Connection: - keep-alive Content-Encoding: @@ -78,15 +75,17 @@ interactions: Content-Type: - application/json Date: - - Mon, 06 Jan 2025 15:38:48 GMT + - Mon, 24 Nov 2025 16:58:36 GMT Server: - cloudflare Set-Cookie: - - __cf_bm=PdbRW9vzO7559czIqn0xmXQjbN8_vV_J7k1DlkB4d_Y-1736177928-1.0.1.1-7yNcyljwqHI.TVflr9ZnkS705G.K5hgPbHpxRzcO3ZMFi5lHCBPs_KB5pFE043wYzPmDIHpn6fu6jIY9mlNoLQ; - path=/; expires=Mon, 06-Jan-25 16:08:48 GMT; domain=.api.openai.com; HttpOnly; + - __cf_bm=Xa8khOM9zEqqwwmzvZrdS.nMU9nW06e0gk4Xg8ga5BI-1764003516-1.0.1.1-mR_vAWrgEyaykpsxgHq76VhaNTOdAWeNJweR1bmH1wVJgzoE0fuSPEKZMJy9Uon.1KBTV3yJVxLvQ4PjPLuE30IUdwY9Lrfbz.Rhb6UVbwY; + path=/; expires=Mon, 24-Nov-25 17:28:36 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None - - _cfuvid=lOOz0FbrrPaRb4IFEeHNcj7QghHzxI1tTV2N0jD9icA-1736177928767-0.0.1.1-604800000; + - _cfuvid=GP8hWglm1PiEe8AjYsdeCiIUtkA7483Hr9Ws4AZWe5U-1764003516772-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload Transfer-Encoding: - chunked X-Content-Type-Options: @@ -95,14 +94,20 @@ interactions: - X-Request-ID alt-svc: - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC openai-organization: - - crewai-iuxna1 + - REDACTED openai-processing-ms: - - '444' + - '1413' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '1606' + x-openai-proxy-wasm: + - v0.1 x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: @@ -110,36 +115,52 @@ interactions: x-ratelimit-remaining-requests: - '9999' x-ratelimit-remaining-tokens: - - '49999686' + - '49999684' x-ratelimit-reset-requests: - 6ms x-ratelimit-reset-tokens: - 0s x-request-id: - - req_5b3e93f5d4e6ab8feef83dc26b6eb623 - http_version: HTTP/1.1 - status_code: 200 + - req_REDACTED + status: + code: 200 + message: OK - request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\nTool Name: dummy_tool\nTool Arguments: {''query'': {''description'': None, ''type'': ''str''}}\nTool Description: - Useful for when you need to get a dummy result for a query.\n\nUse the following - format:\n\nThought: you should always think about what to do\nAction: the action - to take, only one name of [dummy_tool], just the name, exactly as it''s written.\nAction - Input: the input to the action, just a simple python dictionary, enclosed in - curly braces, using \" to wrap keys and values.\nObservation: the result of - the action\n\nOnce all necessary information is gathered:\n\nThought: I now - know the final answer\nFinal Answer: the final answer to the original input - question"}, {"role": "user", "content": "\nCurrent Task: Use the dummy tool - to get a result for ''test query''\n\nThis is the expect criteria for your final - answer: The result from the dummy tool\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:"}, {"role": "assistant", "content": "I should use the dummy - tool to get a result for the ''test query''.\n\nAction: dummy_tool\nAction Input: - {\"query\": \"test query\"}\nObservation: Dummy result for: test query"}], "model": - "gpt-3.5-turbo", "stop": ["\nObservation:"], "stream": false}' + Useful for when you need to get a dummy result for a query.\n\nIMPORTANT: Use + the following format in your response:\n\n```\nThought: you should always think + about what to do\nAction: the action to take, only one name of [dummy_tool], + just the name, exactly as it''s written.\nAction Input: the input to the action, + just a simple JSON object, enclosed in curly braces, using \" to wrap keys and + values.\nObservation: the result of the action\n```\n\nOnce all necessary information + is gathered, return the following format:\n\n```\nThought: I now know the final + answer\nFinal Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: Use the dummy tool to get a result for ''test query''\n\nThis is the expected + criteria for your final answer: The result from the dummy tool\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:"},{"role":"assistant","content":"I should + use the dummy_tool to get a result for the ''test query''.\nAction: dummy_tool\nAction + Input: {\"query\": {\"description\": None, \"type\": \"str\"}}\nObservation: + \nI encountered an error while trying to use the tool. This was the error: Arguments + validation failed: 1 validation error for Dummy_Tool\nquery\n Input should + be a valid string [type=string_type, input_value={''description'': ''None'', + ''type'': ''str''}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.12/v/string_type.\n + Tool dummy_tool accepts these inputs: Tool Name: dummy_tool\nTool Arguments: + {''query'': {''description'': None, ''type'': ''str''}}\nTool Description: Useful + for when you need to get a dummy result for a query..\nMoving on then. I MUST + either use a tool (use one at time) OR give my best final answer not both at + the same time. When responding, I must use the following format:\n\n```\nThought: + you should always think about what to do\nAction: the action to take, should + be one of [dummy_tool]\nAction Input: the input to the action, dictionary enclosed + in curly braces\nObservation: the result of the action\n```\nThis Thought/Action/Action + Input/Result can repeat N times. Once I know the final answer, I must return + the following format:\n\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\n```"}],"model":"gpt-3.5-turbo"}' headers: accept: - application/json @@ -148,16 +169,16 @@ interactions: connection: - keep-alive content-length: - - '1574' + - '2841' content-type: - application/json cookie: - - __cf_bm=PdbRW9vzO7559czIqn0xmXQjbN8_vV_J7k1DlkB4d_Y-1736177928-1.0.1.1-7yNcyljwqHI.TVflr9ZnkS705G.K5hgPbHpxRzcO3ZMFi5lHCBPs_KB5pFE043wYzPmDIHpn6fu6jIY9mlNoLQ; - _cfuvid=lOOz0FbrrPaRb4IFEeHNcj7QghHzxI1tTV2N0jD9icA-1736177928767-0.0.1.1-604800000 + - __cf_bm=Xa8khOM9zEqqwwmzvZrdS.nMU9nW06e0gk4Xg8ga5BI-1764003516-1.0.1.1-mR_vAWrgEyaykpsxgHq76VhaNTOdAWeNJweR1bmH1wVJgzoE0fuSPEKZMJy9Uon.1KBTV3yJVxLvQ4PjPLuE30IUdwY9Lrfbz.Rhb6UVbwY; + _cfuvid=GP8hWglm1PiEe8AjYsdeCiIUtkA7483Hr9Ws4AZWe5U-1764003516772-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.52.1 + - OpenAI/Python 1.109.1 x-stainless-arch: - arm64 x-stainless-async: @@ -167,34 +188,34 @@ interactions: x-stainless-os: - MacOS x-stainless-package-version: - - 1.52.1 - x-stainless-raw-response: - - 'true' + - 1.109.1 + x-stainless-read-timeout: + - '600' x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.7 + - 3.12.10 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AmjTkjtDnt98YQ3k4y71C523EQM9p\",\n \"object\": - \"chat.completion\",\n \"created\": 1736177928,\n \"model\": \"gpt-3.5-turbo-0125\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Final Answer: Dummy result for: test - query\",\n \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": - \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 315,\n \"completion_tokens\": - 9,\n \"total_tokens\": 324,\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 \"system_fingerprint\": - null\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//pFPbahsxEH33Vwx6yYtt7LhO0n1LWgomlFKaFko3LLJ2dletdrSRRklN + 8L8HyZdd9wKFvgikM2cuOmeeRwBClyIDoRrJqu3M5E31+UaeL+ct335c3Ty8/frFLW5vF6G9dNfv + xTgy7Po7Kj6wpsq2nUHWlnawcigZY9b55cWr2WyxnF8loLUlmkirO54spssJB7e2k9n8fLlnNlYr + 9CKDbyMAgOd0xh6pxJ8ig9n48NKi97JGkR2DAISzJr4I6b32LInFuAeVJUZKbd81NtQNZ7CCJ20M + KOscKgZuEDR1gaGyrpUMkkpgt4HgNdUJLkPbbgq21oCspaZpTtcqzp4NoMMbrGKyDJ5z8RDQbXKR + QS4YPcP+vs3pw9qje5S7HDndNQgOfTAMlbNtXxRSUe0z+BSUQu+rYMwG7JqlJixB7sMOZOsS96wv + dzbNKRY4Dk/2CZQkqPUjgoQ6CgeS/BO6nN5pkgau0+0/ag4lcFgFL6MFKBgzACSR5fQFSfz7PbI9 + ym1s3Tm79r9QRaVJ+6ZwKL2lKK1n24mEbkcA98lW4cQponO27bhg+wNTuYvzva1E7+Qevbzag2xZ + mgHr9QE4yVeUyFIbPzCmUFI1WPbU3sUylNoOgNFg6t+7+VPu3eSa6n9J3wNKYcdYFp3DUqvTifsw + h3HR/xZ2/OXUsIgu1goL1uiiEiVWMpjdCgq/8YxtUWmq0XVOpz2MSo62oxcAAAD//wMA+UmELoYE + AAA= headers: - CF-Cache-Status: - - DYNAMIC CF-RAY: - - 8fdccc171b647bb2-ATL + - 9a3a73bbf9d943c2-EWR Connection: - keep-alive Content-Encoding: @@ -202,9 +223,11 @@ interactions: Content-Type: - application/json Date: - - Mon, 06 Jan 2025 15:38:49 GMT + - Mon, 24 Nov 2025 16:58:39 GMT Server: - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload Transfer-Encoding: - chunked X-Content-Type-Options: @@ -213,14 +236,20 @@ interactions: - X-Request-ID alt-svc: - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC openai-organization: - - crewai-iuxna1 + - REDACTED openai-processing-ms: - - '249' + - '1513' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '1753' + x-openai-proxy-wasm: + - v0.1 x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: @@ -228,103 +257,156 @@ interactions: x-ratelimit-remaining-requests: - '9999' x-ratelimit-remaining-tokens: - - '49999643' + - '49999334' x-ratelimit-reset-requests: - 6ms x-ratelimit-reset-tokens: - 0s x-request-id: - - req_cdc7b25a3877bb9a7cb7c6d2645ff447 - http_version: HTTP/1.1 - status_code: 200 + - req_REDACTED + status: + code: 200 + message: OK - request: - body: '{"trace_id": "1581aff1-2567-43f4-a1f2-a2816533eb7d", "execution_type": - "crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null, - "crew_name": "Unknown Crew", "flow_name": null, "crewai_version": "0.201.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-08T18:11:28.008595+00:00"}}' + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: dummy_tool\nTool + Arguments: {''query'': {''description'': None, ''type'': ''str''}}\nTool Description: + Useful for when you need to get a dummy result for a query.\n\nIMPORTANT: Use + the following format in your response:\n\n```\nThought: you should always think + about what to do\nAction: the action to take, only one name of [dummy_tool], + just the name, exactly as it''s written.\nAction Input: the input to the action, + just a simple JSON object, enclosed in curly braces, using \" to wrap keys and + values.\nObservation: the result of the action\n```\n\nOnce all necessary information + is gathered, return the following format:\n\n```\nThought: I now know the final + answer\nFinal Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: Use the dummy tool to get a result for ''test query''\n\nThis is the expected + criteria for your final answer: The result from the dummy tool\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:"},{"role":"assistant","content":"I should + use the dummy_tool to get a result for the ''test query''.\nAction: dummy_tool\nAction + Input: {\"query\": {\"description\": None, \"type\": \"str\"}}\nObservation: + \nI encountered an error while trying to use the tool. This was the error: Arguments + validation failed: 1 validation error for Dummy_Tool\nquery\n Input should + be a valid string [type=string_type, input_value={''description'': ''None'', + ''type'': ''str''}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.12/v/string_type.\n + Tool dummy_tool accepts these inputs: Tool Name: dummy_tool\nTool Arguments: + {''query'': {''description'': None, ''type'': ''str''}}\nTool Description: Useful + for when you need to get a dummy result for a query..\nMoving on then. I MUST + either use a tool (use one at time) OR give my best final answer not both at + the same time. When responding, I must use the following format:\n\n```\nThought: + you should always think about what to do\nAction: the action to take, should + be one of [dummy_tool]\nAction Input: the input to the action, dictionary enclosed + in curly braces\nObservation: the result of the action\n```\nThis Thought/Action/Action + Input/Result can repeat N times. Once I know the final answer, I must return + the following format:\n\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\n```"},{"role":"assistant","content":"Thought: + I will correct the input format and try using the dummy_tool again.\nAction: + dummy_tool\nAction Input: {\"query\": \"test query\"}\nObservation: Dummy result + for: test query"}],"model":"gpt-3.5-turbo"}' headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, zstd - Connection: - - keep-alive - Content-Length: - - '436' - Content-Type: + accept: - application/json - User-Agent: - - CrewAI-CLI/0.201.1 - X-Crewai-Organization-Id: - - d3a3d10c-35db-423f-a7a4-c026030ba64d - X-Crewai-Version: - - 0.201.1 + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3057' + content-type: + - application/json + cookie: + - __cf_bm=Xa8khOM9zEqqwwmzvZrdS.nMU9nW06e0gk4Xg8ga5BI-1764003516-1.0.1.1-mR_vAWrgEyaykpsxgHq76VhaNTOdAWeNJweR1bmH1wVJgzoE0fuSPEKZMJy9Uon.1KBTV3yJVxLvQ4PjPLuE30IUdwY9Lrfbz.Rhb6UVbwY; + _cfuvid=GP8hWglm1PiEe8AjYsdeCiIUtkA7483Hr9Ws4AZWe5U-1764003516772-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 method: POST - uri: http://localhost:3000/crewai_plus/api/v1/tracing/batches + uri: https://api.openai.com/v1/chat/completions response: body: - string: '{"id":"30844ebe-8ac6-4f67-939a-7a072d792654","trace_id":"1581aff1-2567-43f4-a1f2-a2816533eb7d","execution_type":"crew","crew_name":"Unknown - Crew","flow_name":null,"status":"running","duration_ms":null,"crewai_version":"0.201.1","privacy_level":"standard","total_events":0,"execution_context":{"crew_fingerprint":null,"crew_name":"Unknown - Crew","flow_name":null,"crewai_version":"0.201.1","privacy_level":"standard"},"created_at":"2025-10-08T18:11:28.353Z","updated_at":"2025-10-08T18:11:28.353Z"}' + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFLBbhMxEL3vV4x8TqqkTULZWwFFAq4gpEK18npnd028HmOPW6Iq/47s + pNktFKkXS/abN37vzTwWAEI3ogSheslqcGb+vv36rt7e0uqzbna0ut18uv8mtxSDrddKzBKD6p+o + +Il1oWhwBlmTPcLKo2RMXZdvNqvF4mq9fJuBgRo0idY5nl9drOccfU3zxfJyfWL2pBUGUcL3AgDg + MZ9Jo23wtyhhMXt6GTAE2aEoz0UAwpNJL0KGoANLy2I2gooso82yv/QUu55L+AiWHmCXDu4RWm2l + AWnDA/ofdptvN/lWwoc4DHvwGKJhaMmXwBgYfkX0++k3HtsYZLJpozETQFpLLFNM2eDdCTmcLRnq + nKc6/EUVrbY69JVHGcgm+YHJiYweCoC7HF18loZwngbHFdMO83ebzerYT4zTGtHl9QlkYmkmrOvL + 2Qv9qgZZahMm4QslVY/NSB0nJWOjaQIUE9f/qnmp99G5tt1r2o+AUugYm8p5bLR67ngs85iW+X9l + 55SzYBHQ32uFFWv0aRINtjKa45qJsA+MQ9Vq26F3XuddS5MsDsUfAAAA//8DANWDXp9qAwAA headers: - Content-Length: - - '496' - cache-control: - - no-store - content-security-policy: - - 'default-src ''self'' *.crewai.com crewai.com; script-src ''self'' ''unsafe-inline'' - *.crewai.com 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://share.descript.com/; style-src ''self'' - ''unsafe-inline'' *.crewai.com crewai.com https://cdn.jsdelivr.net/npm/apexcharts; - img-src ''self'' data: *.crewai.com crewai.com https://zeus.tools.crewai.com - https://dashboard.tools.crewai.com https://cdn.jsdelivr.net; font-src ''self'' - data: *.crewai.com crewai.com; connect-src ''self'' *.crewai.com 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 ws://localhost:3036 wss://localhost:3036; - frame-src ''self'' *.crewai.com 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://www.youtube.com https://share.descript.com' - content-type: - - application/json; charset=utf-8 - etag: - - W/"a548892c6a8a52833595a42b35b10009" - expires: - - '0' - permissions-policy: - - camera=(), microphone=(self), geolocation=() - pragma: - - no-cache - referrer-policy: - - strict-origin-when-cross-origin - server-timing: - - cache_read.active_support;dur=0.05, cache_fetch_hit.active_support;dur=0.00, - cache_read_multi.active_support;dur=0.12, start_processing.action_controller;dur=0.00, - sql.active_record;dur=30.46, instantiation.active_record;dur=0.38, feature_operation.flipper;dur=0.03, - start_transaction.active_record;dur=0.01, transaction.active_record;dur=16.78, - process_action.action_controller;dur=309.67 - vary: - - Accept - x-content-type-options: + CF-RAY: + - 9a3a73cd4ff343c2-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 16:58:40 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: - nosniff - x-frame-options: - - SAMEORIGIN - x-permitted-cross-domain-policies: - - none + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - REDACTED + openai-processing-ms: + - '401' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '421' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '50000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '49999290' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s x-request-id: - - 7ec132be-e871-4b0a-93f7-81f8d7c0ccae - x-runtime: - - '0.358533' - x-xss-protection: - - 1; mode=block + - req_REDACTED status: - code: 201 - message: Created + code: 200 + message: OK version: 1 diff --git a/lib/crewai/tests/cassettes/test_agent_repeated_tool_usage.yaml b/lib/crewai/tests/cassettes/test_agent_repeated_tool_usage.yaml index 97723b42d..de3cb94d6 100644 --- a/lib/crewai/tests/cassettes/test_agent_repeated_tool_usage.yaml +++ b/lib/crewai/tests/cassettes/test_agent_repeated_tool_usage.yaml @@ -1,39 +1,38 @@ interactions: - request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: + just re-use this tool non-stop.\n\nIMPORTANT: Use the following format in your + response:\n\n```\nThought: you should always think about what to do\nAction: the action to take, only one name of [get_final_answer], just the name, exactly as it''s written.\nAction Input: the input to the action, just a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce all necessary information is gathered, return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "user", - "content": "\nCurrent Task: The final answer is 42. But don''t give it until - I tell you so, instead keep using the `get_final_answer` tool.\n\nThis is the - expected criteria for your final answer: The final answer, don''t give it until - I tell you so\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-4", "stop": ["\nObservation:"]}' + Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: The final answer is 42. But don''t give it until I tell you so, instead + keep using the `get_final_answer` tool.\n\nThis is the expected criteria for + your final answer: The final answer, don''t give it until I tell you so\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-4"}' headers: accept: - application/json accept-encoding: - - gzip, deflate, zstd + - gzip, deflate connection: - keep-alive content-length: - - '1483' + - '1436' content-type: - application/json host: - api.openai.com user-agent: - - OpenAI/Python 1.68.2 + - OpenAI/Python 1.109.1 x-stainless-arch: - arm64 x-stainless-async: @@ -43,36 +42,34 @@ interactions: x-stainless-os: - MacOS x-stainless-package-version: - - 1.68.2 - x-stainless-raw-response: - - 'true' + - 1.109.1 x-stainless-read-timeout: - - '600.0' + - '600' x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.8 + - 3.12.10 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-BHIzB7ROd8ReBniHGVMZ3KzKcafvL\",\n \"object\": - \"chat.completion\",\n \"created\": 1743464257,\n \"model\": \"gpt-4-0613\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"As I've been instructed to use the `get_final_answer` - tool continuously without revealing the final answer yet, I will use this tool - now.\\n\\nAction: get_final_answer\\nAction Input: {}\",\n \"refusal\": - null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": - \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 308,\n \"completion_tokens\": - 39,\n \"total_tokens\": 347,\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\": null\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFNNbxoxEL3zK0Y+AyLlIym3thJppCq9tKcm2hh72J1ibMeehdCI/17Z + C+zmo1IvPvjNe34z8/zcAxCkxRyEqiSrjTeDL6ufnx+v91e3k3V4epqZ1fTbQl5dL6ZoFreinxhu + +RsVn1hD5TbeIJOzDawCSsakenE5m4xG4+l4koGN02gSrfQ8mAxGs4vxkVE5UhjFHH71AACe85m8 + WY1PYg6j/ulmgzHKEsX8XAQggjPpRsgYKbK0LPotqJxltNnuDayt2wFXCCuy0oC0cYcBKMLkA8gI + HkNGVR0CWgaWcT2Er26HWwx9uIFKbhGWiBbIRg61YtTADuqImfhQIhdZu2i0H4CdSw9psI5TaUlb + fGuhtkymIzq8s59UmukcXkueELixvuY5PB/u7PdlxLCVDeFHhc2rFIEsMUlDf1BnEwGl3icbPrgt + 6Xec7Cq0EPCxpoC6D8uagThJJf8Ni2yZeUfGHrk7vFMT5GwcdjcRcFVHmRJga2M6gLTWcTafM3B/ + RA7nrRtX+uCW8RVVrMhSrIqAMjqbNhzZeZHRQw/gPqerfhEY4YPbeC7YrTE/Nx7NGj3RBrlFLz8e + QXYsTYd1Ne2/o1doZEkmdvIplFQV6pbahlnWmlwH6HW6fuvmPe2mc7Ll/8i3gFLoGXXhA2pSLztu + ywKmf/6vsvOUs2GR8kcKCyYMaRMaV7I2zU8UcR8ZNynFJQYfKH/HtMneofcXAAD//wMACgPmEYUE + AAA= headers: CF-RAY: - - 9293acf9180cf95f-SJC + - 9a3a7429294cd474-EWR Connection: - keep-alive Content-Encoding: @@ -80,15 +77,17 @@ interactions: Content-Type: - application/json Date: - - Mon, 31 Mar 2025 23:37:39 GMT + - Mon, 24 Nov 2025 16:58:57 GMT Server: - cloudflare Set-Cookie: - - __cf_bm=6.AAPW_t0c.fjGv5iZ4zqu8ggsPd2sdq_meXE5Kp4dc-1743464259-1.0.1.1-jFiiyAbj4LNcitqqdrL0JnpALoaFLv3IYVfblBlBqrxVXXjAti0OFK54aPwxqZV1OT6tBbwiaxZ3WkhjNvxBxxWKG_51ZHZSabiE9W.UZK8; - path=/; expires=Tue, 01-Apr-25 00:07:39 GMT; domain=.api.openai.com; HttpOnly; + - __cf_bm=gTjKBzaj8tcUU6pv7q58dg0Pazs_KnIGhmiHkP0e2lc-1764003537-1.0.1.1-t4Zz8_yUK3j89RkvEu75Pv99M6r4OQVBWMwESRuFFCOSCKl1pzreSt6l9bf5qcYis.j3etmAALoDG6FDJU97AhDSDy_B4z7kGnF90NvMdP4; + path=/; expires=Mon, 24-Nov-25 17:28:57 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None - - _cfuvid=wvate7aTgXKzIRpbSDVJqaXt.Liniwr82HOsU.ZK8oA-1743464259480-0.0.1.1-604800000; + - _cfuvid=SwTKol2bK9lOh_5xvE7jRjGV.akj56.Bt1LgAJBaRoo-1764003537835-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload Transfer-Encoding: - chunked X-Content-Type-Options: @@ -100,104 +99,80 @@ interactions: cf-cache-status: - DYNAMIC openai-organization: - - crewai-iuxna1 + - REDACTED openai-processing-ms: - - '1929' + - '3075' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '3098' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '1000000' x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - '1000000' + x-ratelimit-remaining-project-tokens: + - '999668' x-ratelimit-remaining-requests: - '9999' x-ratelimit-remaining-tokens: - - '999665' + - '999668' + x-ratelimit-reset-project-tokens: + - 19ms x-ratelimit-reset-requests: - 6ms x-ratelimit-reset-tokens: - - 20ms + - 19ms x-request-id: - - req_4a6bc6368e4e562d2112de0dd14614e8 - http_version: HTTP/1.1 - status_code: 200 -- request: - body: !!binary | - CtQBCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSqwEKEgoQY3Jld2FpLnRl - bGVtZXRyeRKUAQoQXIzUaoiEGnp402BIfaIMnBIIjaV9Tuoc2usqClRvb2wgVXNhZ2UwATl48hDA - gQcyGEHYUijAgQcyGEobCg5jcmV3YWlfdmVyc2lvbhIJCgcwLjEwOC4wSh8KCXRvb2xfbmFtZRIS - ChBnZXRfZmluYWxfYW5zd2VySg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAA= - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, zstd - Connection: - - keep-alive - Content-Length: - - '215' - Content-Type: - - application/x-protobuf - User-Agent: - - OTel-OTLP-Exporter-Python/1.31.1 - method: POST - uri: https://telemetry.crewai.com:4319/v1/traces - response: - body: - string: "\n\0" - headers: - Content-Length: - - '2' - Content-Type: - - application/x-protobuf - Date: - - Mon, 31 Mar 2025 23:37:40 GMT + - req_REDACTED status: code: 200 message: OK - request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: + just re-use this tool non-stop.\n\nIMPORTANT: Use the following format in your + response:\n\n```\nThought: you should always think about what to do\nAction: the action to take, only one name of [get_final_answer], just the name, exactly as it''s written.\nAction Input: the input to the action, just a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce all necessary information is gathered, return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "user", - "content": "\nCurrent Task: The final answer is 42. But don''t give it until - I tell you so, instead keep using the `get_final_answer` tool.\n\nThis is the - expected criteria for your final answer: The final answer, don''t give it until - I tell you so\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:"}, {"role": - "assistant", "content": "42"}, {"role": "assistant", "content": "As I''ve been - instructed to use the `get_final_answer` tool continuously without revealing - the final answer yet, I will use this tool now.\n\nAction: get_final_answer\nAction - Input: {}\nObservation: 42"}], "model": "gpt-4", "stop": ["\nObservation:"]}' + Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: The final answer is 42. But don''t give it until I tell you so, instead + keep using the `get_final_answer` tool.\n\nThis is the expected criteria for + your final answer: The final answer, don''t give it until I tell you so\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:"},{"role":"assistant","content":"I + know the final answer is 42 as per the current task. However, I have been instructed + to use the `get_final_answer` tool and not to give the final answer until instructed.\nAction: + get_final_answer\nAction Input: {}\nObservation: 42"}],"model":"gpt-4"}' headers: accept: - application/json accept-encoding: - - gzip, deflate, zstd + - gzip, deflate connection: - keep-alive content-length: - - '1761' + - '1703' content-type: - application/json cookie: - - __cf_bm=6.AAPW_t0c.fjGv5iZ4zqu8ggsPd2sdq_meXE5Kp4dc-1743464259-1.0.1.1-jFiiyAbj4LNcitqqdrL0JnpALoaFLv3IYVfblBlBqrxVXXjAti0OFK54aPwxqZV1OT6tBbwiaxZ3WkhjNvxBxxWKG_51ZHZSabiE9W.UZK8; - _cfuvid=wvate7aTgXKzIRpbSDVJqaXt.Liniwr82HOsU.ZK8oA-1743464259480-0.0.1.1-604800000 + - __cf_bm=gTjKBzaj8tcUU6pv7q58dg0Pazs_KnIGhmiHkP0e2lc-1764003537-1.0.1.1-t4Zz8_yUK3j89RkvEu75Pv99M6r4OQVBWMwESRuFFCOSCKl1pzreSt6l9bf5qcYis.j3etmAALoDG6FDJU97AhDSDy_B4z7kGnF90NvMdP4; + _cfuvid=SwTKol2bK9lOh_5xvE7jRjGV.akj56.Bt1LgAJBaRoo-1764003537835-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.68.2 + - OpenAI/Python 1.109.1 x-stainless-arch: - arm64 x-stainless-async: @@ -207,36 +182,33 @@ interactions: x-stainless-os: - MacOS x-stainless-package-version: - - 1.68.2 - x-stainless-raw-response: - - 'true' + - 1.109.1 x-stainless-read-timeout: - - '600.0' + - '600' x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.8 + - 3.12.10 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-BHIzD2gQYpzmDdk4mkyrrhtJlHqKd\",\n \"object\": - \"chat.completion\",\n \"created\": 1743464259,\n \"model\": \"gpt-4-0613\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I need to continue using the - `get_final_answer` tool as part of the current task, and I expect that the answer - will remain the same, which is 42.\\nAction: get_final_answer\\nAction Input: - {}\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": - null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 361,\n \"completion_tokens\": 47,\n \"total_tokens\": 408,\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\": null\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFPLbtswELz7KxY824YdKU6hW1r04FOLvgK0CRSaWklsJC5LLu0Wgf+9 + IGVbzqNALwLImVnO7o4eJwBCV6IAoVrJqrfd7F399W14f7389Ku23z+6b5932Y1c6crn65sgplFB + m5+o+KiaK+pth6zJDLByKBlj1eXVKl8sssvsTQJ6qrCLssbyLJ8tVsvsoGhJK/SigB8TAIDH9I3e + TIW/RQGL6fGmR+9lg6I4kQCEoy7eCOm99iwNi+kIKjKMJtn90lJoWi5gDa3cItDGo9tiBdwiUGAb + GKhOp/sGuay1kV0pjd+huwcm2CDkF1PYtVq10EtWLfpET0wYmNDoLRrQJiEs/cMc1iB78MFa8vE5 + guhKm4AQvDbNwCTq5rfmWsVRFvDcwBGBtbGBC3jc35oPqQE5CPKL87Yd1sHLOG4Tuu4MkMYQJ0ka + +N0B2Z9G3FFjHW38M6motdG+LR1KTyaO0zNZkdD9BOAurTI82Y6wjnrLJdMDpueyVT7UE2NqRvQy + O4BMLLvxPl9eTV+pV1bIUnf+LAxCSdViNUrH5MhQaToDJmddv3TzWu2hc22a/yk/AkqhZaxK67DS + 6mnHI81h/Kn+RTtNORkWcetaYckaXdxEhbUM3RB74f94xj5mp0FnnU7Zj5uc7Cd/AQAA//8DAJ/4 + JYnyAwAA headers: CF-RAY: - - 9293ad06c9b9f95f-SJC + - 9a3a74404e14d474-EWR Connection: - keep-alive Content-Encoding: @@ -244,9 +216,11 @@ interactions: Content-Type: - application/json Date: - - Mon, 31 Mar 2025 23:37:41 GMT + - Mon, 24 Nov 2025 16:59:00 GMT Server: - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload Transfer-Encoding: - chunked X-Content-Type-Options: @@ -258,440 +232,84 @@ interactions: cf-cache-status: - DYNAMIC openai-organization: - - crewai-iuxna1 + - REDACTED openai-processing-ms: - - '1566' + - '1916' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '2029' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '1000000' x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - '1000000' + x-ratelimit-remaining-project-tokens: + - '999609' x-ratelimit-remaining-requests: - '9999' x-ratelimit-remaining-tokens: - - '999614' + - '999609' + x-ratelimit-reset-project-tokens: + - 23ms x-ratelimit-reset-requests: - 6ms x-ratelimit-reset-tokens: - 23ms x-request-id: - - req_951de40b4875b6fcdd2edb472b329696 - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool - Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: - the action to take, only one name of [get_final_answer], just the name, exactly - as it''s written.\nAction Input: the input to the action, just a simple JSON - object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: - the result of the action\n```\n\nOnce all necessary information is gathered, - return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "user", - "content": "\nCurrent Task: The final answer is 42. But don''t give it until - I tell you so, instead keep using the `get_final_answer` tool.\n\nThis is the - expected criteria for your final answer: The final answer, don''t give it until - I tell you so\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:"}, {"role": - "assistant", "content": "42"}, {"role": "assistant", "content": "As I''ve been - instructed to use the `get_final_answer` tool continuously without revealing - the final answer yet, I will use this tool now.\n\nAction: get_final_answer\nAction - Input: {}\nObservation: 42"}, {"role": "assistant", "content": "I tried reusing - the same input, I must stop using this action input. I''ll try something else - instead.\n\n"}, {"role": "assistant", "content": "Thought: I need to continue - using the `get_final_answer` tool as part of the current task, and I expect - that the answer will remain the same, which is 42.\nAction: get_final_answer\nAction - Input: {}\nObservation: I tried reusing the same input, I must stop using this - action input. I''ll try something else instead."}], "model": "gpt-4", "stop": - ["\nObservation:"]}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate, zstd - connection: - - keep-alive - content-length: - - '2256' - content-type: - - application/json - cookie: - - __cf_bm=6.AAPW_t0c.fjGv5iZ4zqu8ggsPd2sdq_meXE5Kp4dc-1743464259-1.0.1.1-jFiiyAbj4LNcitqqdrL0JnpALoaFLv3IYVfblBlBqrxVXXjAti0OFK54aPwxqZV1OT6tBbwiaxZ3WkhjNvxBxxWKG_51ZHZSabiE9W.UZK8; - _cfuvid=wvate7aTgXKzIRpbSDVJqaXt.Liniwr82HOsU.ZK8oA-1743464259480-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.8 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-BHIzFqk1wgU69CqmrWEwtrCA9KbbK\",\n \"object\": - \"chat.completion\",\n \"created\": 1743464261,\n \"model\": \"gpt-4-0613\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: Since the previous action has - not changed, I will use the same tool again as I am expected to use it non-stop.\\nAction: - get_final_answer\\nAction Input: {}\",\n \"refusal\": null,\n \"annotations\": - []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 465,\n \"completion_tokens\": - 37,\n \"total_tokens\": 502,\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\": null\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 9293ad116f7ff95f-SJC - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Mon, 31 Mar 2025 23:37:43 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - alt-svc: - - h3=":443"; ma=86400 - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '2208' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '1000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '999509' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 29ms - x-request-id: - - req_6dc122976660ee1ce778b55cf3febf4d - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool - Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: - the action to take, only one name of [get_final_answer], just the name, exactly - as it''s written.\nAction Input: the input to the action, just a simple JSON - object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: - the result of the action\n```\n\nOnce all necessary information is gathered, - return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "user", - "content": "\nCurrent Task: The final answer is 42. But don''t give it until - I tell you so, instead keep using the `get_final_answer` tool.\n\nThis is the - expected criteria for your final answer: The final answer, don''t give it until - I tell you so\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:"}, {"role": - "assistant", "content": "42"}, {"role": "assistant", "content": "As I''ve been - instructed to use the `get_final_answer` tool continuously without revealing - the final answer yet, I will use this tool now.\n\nAction: get_final_answer\nAction - Input: {}\nObservation: 42"}, {"role": "assistant", "content": "I tried reusing - the same input, I must stop using this action input. I''ll try something else - instead.\n\n"}, {"role": "assistant", "content": "Thought: I need to continue - using the `get_final_answer` tool as part of the current task, and I expect - that the answer will remain the same, which is 42.\nAction: get_final_answer\nAction - Input: {}\nObservation: I tried reusing the same input, I must stop using this - action input. I''ll try something else instead."}, {"role": "assistant", "content": - "I tried reusing the same input, I must stop using this action input. I''ll - try something else instead.\n\n\n\n\nYou ONLY have access to the following tools, - and should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool - Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: - the action to take, only one name of [get_final_answer], just the name, exactly - as it''s written.\nAction Input: the input to the action, just a simple JSON - object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: - the result of the action\n```\n\nOnce all necessary information is gathered, - return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "assistant", - "content": "Thought: Since the previous action has not changed, I will use the - same tool again as I am expected to use it non-stop.\nAction: get_final_answer\nAction - Input: {}\nObservation: I tried reusing the same input, I must stop using this - action input. I''ll try something else instead.\n\n\n\n\nYou ONLY have access - to the following tools, and should NEVER make up tools that are not listed here:\n\nTool - Name: get_final_answer\nTool Arguments: {}\nTool Description: Get the final - answer but don''t give it yet, just re-use this\n tool non-stop.\n\nIMPORTANT: - Use the following format in your response:\n\n```\nThought: you should always - think about what to do\nAction: the action to take, only one name of [get_final_answer], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple JSON object, enclosed in curly braces, using \" to wrap keys and - values.\nObservation: the result of the action\n```\n\nOnce all necessary information - is gathered, return the following format:\n\n```\nThought: I now know the final - answer\nFinal Answer: the final answer to the original input question\n```"}], - "model": "gpt-4", "stop": ["\nObservation:"]}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate, zstd - connection: - - keep-alive - content-length: - - '4406' - content-type: - - application/json - cookie: - - __cf_bm=6.AAPW_t0c.fjGv5iZ4zqu8ggsPd2sdq_meXE5Kp4dc-1743464259-1.0.1.1-jFiiyAbj4LNcitqqdrL0JnpALoaFLv3IYVfblBlBqrxVXXjAti0OFK54aPwxqZV1OT6tBbwiaxZ3WkhjNvxBxxWKG_51ZHZSabiE9W.UZK8; - _cfuvid=wvate7aTgXKzIRpbSDVJqaXt.Liniwr82HOsU.ZK8oA-1743464259480-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.8 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-BHIzHcXbVKSgQaMMGF4TGn7jGUY1k\",\n \"object\": - \"chat.completion\",\n \"created\": 1743464263,\n \"model\": \"gpt-4-0613\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: Given that I can only get the - final answer, but I must not give it yet, I will continue to use the 'get_final_answer' - tool.\\n\\nAction: get_final_answer\\nAction Input: {}\",\n \"refusal\": - null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": - \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 919,\n \"completion_tokens\": - 43,\n \"total_tokens\": 962,\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\": null\n}\n" - headers: - CF-RAY: - - 9293ad1fd98cf95f-SJC - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Mon, 31 Mar 2025 23:37:45 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: - - '1917' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '1000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '999002' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 59ms - x-request-id: - - req_2be75aef153bf8a83fe0286aab0c40d8 - http_version: HTTP/1.1 - status_code: 200 -- request: - body: !!binary | - Cv0CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS1AIKEgoQY3Jld2FpLnRl - bGVtZXRyeRKdAQoQN999WEtgESzCMqeXbQCXFBIInV/VxmXm9MEqE1Rvb2wgUmVwZWF0ZWQgVXNh - Z2UwATn4wIklggcyGEGAA5clggcyGEobCg5jcmV3YWlfdmVyc2lvbhIJCgcwLjEwOC4wSh8KCXRv - b2xfbmFtZRISChBnZXRfZmluYWxfYW5zd2VySg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAASnQEK - EBCAfg3LxE7N34oogybj+aoSCFGlXfz4463NKhNUb29sIFJlcGVhdGVkIFVzYWdlMAE5UGair4IH - MhhB0K+1r4IHMhhKGwoOY3Jld2FpX3ZlcnNpb24SCQoHMC4xMDguMEofCgl0b29sX25hbWUSEgoQ - Z2V0X2ZpbmFsX2Fuc3dlckoOCghhdHRlbXB0cxICGAF6AhgBhQEAAQAA - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, zstd - Connection: - - keep-alive - Content-Length: - - '384' - Content-Type: - - application/x-protobuf - User-Agent: - - OTel-OTLP-Exporter-Python/1.31.1 - method: POST - uri: https://telemetry.crewai.com:4319/v1/traces - response: - body: - string: "\n\0" - headers: - Content-Length: - - '2' - Content-Type: - - application/x-protobuf - Date: - - Mon, 31 Mar 2025 23:37:45 GMT + - req_REDACTED status: code: 200 message: OK - request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: + just re-use this tool non-stop.\n\nIMPORTANT: Use the following format in your + response:\n\n```\nThought: you should always think about what to do\nAction: the action to take, only one name of [get_final_answer], just the name, exactly as it''s written.\nAction Input: the input to the action, just a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce all necessary information is gathered, return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "user", - "content": "\nCurrent Task: The final answer is 42. But don''t give it until - I tell you so, instead keep using the `get_final_answer` tool.\n\nThis is the - expected criteria for your final answer: The final answer, don''t give it until - I tell you so\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:"}, {"role": - "assistant", "content": "42"}, {"role": "assistant", "content": "As I''ve been - instructed to use the `get_final_answer` tool continuously without revealing - the final answer yet, I will use this tool now.\n\nAction: get_final_answer\nAction - Input: {}\nObservation: 42"}, {"role": "assistant", "content": "I tried reusing - the same input, I must stop using this action input. I''ll try something else - instead.\n\n"}, {"role": "assistant", "content": "Thought: I need to continue - using the `get_final_answer` tool as part of the current task, and I expect - that the answer will remain the same, which is 42.\nAction: get_final_answer\nAction - Input: {}\nObservation: I tried reusing the same input, I must stop using this - action input. I''ll try something else instead."}, {"role": "assistant", "content": - "I tried reusing the same input, I must stop using this action input. I''ll - try something else instead.\n\n\n\n\nYou ONLY have access to the following tools, - and should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool - Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: - the action to take, only one name of [get_final_answer], just the name, exactly - as it''s written.\nAction Input: the input to the action, just a simple JSON - object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: - the result of the action\n```\n\nOnce all necessary information is gathered, - return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "assistant", - "content": "Thought: Since the previous action has not changed, I will use the - same tool again as I am expected to use it non-stop.\nAction: get_final_answer\nAction - Input: {}\nObservation: I tried reusing the same input, I must stop using this - action input. I''ll try something else instead.\n\n\n\n\nYou ONLY have access - to the following tools, and should NEVER make up tools that are not listed here:\n\nTool - Name: get_final_answer\nTool Arguments: {}\nTool Description: Get the final - answer but don''t give it yet, just re-use this\n tool non-stop.\n\nIMPORTANT: - Use the following format in your response:\n\n```\nThought: you should always - think about what to do\nAction: the action to take, only one name of [get_final_answer], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple JSON object, enclosed in curly braces, using \" to wrap keys and - values.\nObservation: the result of the action\n```\n\nOnce all necessary information - is gathered, return the following format:\n\n```\nThought: I now know the final - answer\nFinal Answer: the final answer to the original input question\n```"}, - {"role": "assistant", "content": "I tried reusing the same input, I must stop - using this action input. I''ll try something else instead.\n\n"}, {"role": "assistant", - "content": "Thought: Given that I can only get the final answer, but I must - not give it yet, I will continue to use the ''get_final_answer'' tool.\n\nAction: + Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: The final answer is 42. But don''t give it until I tell you so, instead + keep using the `get_final_answer` tool.\n\nThis is the expected criteria for + your final answer: The final answer, don''t give it until I tell you so\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:"},{"role":"assistant","content":"I + know the final answer is 42 as per the current task. However, I have been instructed + to use the `get_final_answer` tool and not to give the final answer until instructed.\nAction: + get_final_answer\nAction Input: {}\nObservation: 42"},{"role":"assistant","content":"Thought: + I have observed the output of the `get_final_answer` to be 42, which matches + the final answer given in the task. I am supposed to continue using the tool.\nAction: get_final_answer\nAction Input: {}\nObservation: I tried reusing the same input, - I must stop using this action input. I''ll try something else instead."}, {"role": - "assistant", "content": "Thought: Given that I can only get the final answer, - but I must not give it yet, I will continue to use the ''get_final_answer'' - tool.\n\nAction: get_final_answer\nAction Input: {}\nObservation: I tried reusing - the same input, I must stop using this action input. I''ll try something else - instead.\n\n\nNow it''s time you MUST give your absolute best final answer. - You''ll ignore all previous instructions, stop using any tools, and just return - your absolute BEST Final answer."}], "model": "gpt-4", "stop": ["\nObservation:"]}' + I must stop using this action input. I''ll try something else instead."}],"model":"gpt-4"}' headers: accept: - application/json accept-encoding: - - gzip, deflate, zstd + - gzip, deflate connection: - keep-alive content-length: - - '5391' + - '2060' content-type: - application/json cookie: - - __cf_bm=6.AAPW_t0c.fjGv5iZ4zqu8ggsPd2sdq_meXE5Kp4dc-1743464259-1.0.1.1-jFiiyAbj4LNcitqqdrL0JnpALoaFLv3IYVfblBlBqrxVXXjAti0OFK54aPwxqZV1OT6tBbwiaxZ3WkhjNvxBxxWKG_51ZHZSabiE9W.UZK8; - _cfuvid=wvate7aTgXKzIRpbSDVJqaXt.Liniwr82HOsU.ZK8oA-1743464259480-0.0.1.1-604800000 + - __cf_bm=gTjKBzaj8tcUU6pv7q58dg0Pazs_KnIGhmiHkP0e2lc-1764003537-1.0.1.1-t4Zz8_yUK3j89RkvEu75Pv99M6r4OQVBWMwESRuFFCOSCKl1pzreSt6l9bf5qcYis.j3etmAALoDG6FDJU97AhDSDy_B4z7kGnF90NvMdP4; + _cfuvid=SwTKol2bK9lOh_5xvE7jRjGV.akj56.Bt1LgAJBaRoo-1764003537835-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.68.2 + - OpenAI/Python 1.109.1 x-stainless-arch: - arm64 x-stainless-async: @@ -701,35 +319,33 @@ interactions: x-stainless-os: - MacOS x-stainless-package-version: - - 1.68.2 - x-stainless-raw-response: - - 'true' + - 1.109.1 x-stainless-read-timeout: - - '600.0' + - '600' x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.8 + - 3.12.10 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-BHIzJVAGX4xEQVj6Asww4mN5QMaFh\",\n \"object\": - \"chat.completion\",\n \"created\": 1743464265,\n \"model\": \"gpt-4-0613\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I now know the final answer\\nFinal - Answer: 42\",\n \"refusal\": null,\n \"annotations\": []\n },\n - \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n - \ \"usage\": {\n \"prompt_tokens\": 1126,\n \"completion_tokens\": 15,\n - \ \"total_tokens\": 1141,\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\": null\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAA4xTwW4TMRC95ytGvnBJooRuG7Q3QAjlQg+0IESrrWPP7jr1jo092xBV+XdkJ82m + pUhcVlq/eW/e+I0fRwDCaFGCUK1k1Xk7+Vhff9isLxfbd/Xq++IzzT+t/a8v387qh+LHSowTw63W + qPiJNVWu8xbZONrDKqBkTKrzxUUxm52dF7MMdE6jTbTG86SYzC7mZwdG64zCKEr4OQIAeMzf5I00 + /hYlZH4+6TBG2aAoj0UAIjibToSM0USWxGI8gMoRI2W7V63rm5ZL+GpIIXCLcNcgV7UhaStJcYPh + Dtg5C47sFloZwRGCId/zONUHBBmQ3jBI2gLhZo9FYAcctlO4SjW1CziGJcTW9VbDPaIHRxBw0kdD + TW6cu2wMt/kvyu7QZnpD71W6zBJeWntCYJkKS3jc3dDlKmJ4kHvC9VF90AMTk93GPCSsw6PxgLG3 + HKewBELUaYLakAYJ2tQ1BiQG6X1wUrXT0wsNWPdRpiCpt/YEkESOs5Uc5e0B2R3Ds67xwa3iC6qo + DZnYVgFldJSCiuy8yOhuBHCbl6R/lrvwwXWeK3b3mNsVxdu9nhj2cUAXTyA7lnY4P58X41f0Ko0s + jY0nayaUVC3qgTrspOy1cSfA6GTqv928pr2f3FDzP/IDoBR6Rl35gNqo5xMPZQHTc/1X2fGWs2GR + tskorNhgSElorGVv9w9KxG1k7NJONhh8MPlVpSRHu9EfAAAA//8DAA47YjJMBAAA headers: CF-RAY: - - 9293ad2cc825f95f-SJC + - 9a3a744d8849d474-EWR Connection: - keep-alive Content-Encoding: @@ -737,9 +353,11 @@ interactions: Content-Type: - application/json Date: - - Mon, 31 Mar 2025 23:37:46 GMT + - Mon, 24 Nov 2025 16:59:02 GMT Server: - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload Transfer-Encoding: - chunked X-Content-Type-Options: @@ -751,115 +369,99 @@ interactions: cf-cache-status: - DYNAMIC openai-organization: - - crewai-iuxna1 + - REDACTED openai-processing-ms: - - '770' + - '2123' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '2149' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '1000000' x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - '1000000' + x-ratelimit-remaining-project-tokens: + - '999528' x-ratelimit-remaining-requests: - '9999' x-ratelimit-remaining-tokens: - - '998785' + - '999528' + x-ratelimit-reset-project-tokens: + - 28ms x-ratelimit-reset-requests: - 6ms x-ratelimit-reset-tokens: - - 72ms + - 28ms x-request-id: - - req_506212769634087eab6c885565fcfb91 - http_version: HTTP/1.1 - status_code: 200 + - req_REDACTED + status: + code: 200 + message: OK - request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: + just re-use this tool non-stop.\n\nIMPORTANT: Use the following format in your + response:\n\n```\nThought: you should always think about what to do\nAction: the action to take, only one name of [get_final_answer], just the name, exactly as it''s written.\nAction Input: the input to the action, just a simple JSON object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce all necessary information is gathered, return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "user", - "content": "\nCurrent Task: The final answer is 42. But don''t give it until - I tell you so, instead keep using the `get_final_answer` tool.\n\nThis is the - expected criteria for your final answer: The final answer, don''t give it until - I tell you so\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:"}, {"role": - "assistant", "content": "42"}, {"role": "assistant", "content": "As I''ve been - instructed to use the `get_final_answer` tool continuously without revealing - the final answer yet, I will use this tool now.\n\nAction: get_final_answer\nAction - Input: {}\nObservation: 42"}, {"role": "assistant", "content": "I tried reusing - the same input, I must stop using this action input. I''ll try something else - instead.\n\n"}, {"role": "assistant", "content": "Thought: I need to continue - using the `get_final_answer` tool as part of the current task, and I expect - that the answer will remain the same, which is 42.\nAction: get_final_answer\nAction - Input: {}\nObservation: I tried reusing the same input, I must stop using this - action input. I''ll try something else instead."}, {"role": "assistant", "content": - "I tried reusing the same input, I must stop using this action input. I''ll - try something else instead.\n\n\n\n\nYou ONLY have access to the following tools, - and should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool - Arguments: {}\nTool Description: Get the final answer but don''t give it yet, - just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format - in your response:\n\n```\nThought: you should always think about what to do\nAction: - the action to take, only one name of [get_final_answer], just the name, exactly - as it''s written.\nAction Input: the input to the action, just a simple JSON - object, enclosed in curly braces, using \" to wrap keys and values.\nObservation: - the result of the action\n```\n\nOnce all necessary information is gathered, - return the following format:\n\n```\nThought: I now know the final answer\nFinal - Answer: the final answer to the original input question\n```"}, {"role": "assistant", - "content": "Thought: Since the previous action has not changed, I will use the - same tool again as I am expected to use it non-stop.\nAction: get_final_answer\nAction - Input: {}\nObservation: I tried reusing the same input, I must stop using this - action input. I''ll try something else instead.\n\n\n\n\nYou ONLY have access - to the following tools, and should NEVER make up tools that are not listed here:\n\nTool - Name: get_final_answer\nTool Arguments: {}\nTool Description: Get the final - answer but don''t give it yet, just re-use this\n tool non-stop.\n\nIMPORTANT: - Use the following format in your response:\n\n```\nThought: you should always - think about what to do\nAction: the action to take, only one name of [get_final_answer], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple JSON object, enclosed in curly braces, using \" to wrap keys and - values.\nObservation: the result of the action\n```\n\nOnce all necessary information - is gathered, return the following format:\n\n```\nThought: I now know the final - answer\nFinal Answer: the final answer to the original input question\n```"}, - {"role": "assistant", "content": "I tried reusing the same input, I must stop - using this action input. I''ll try something else instead.\n\n"}, {"role": "assistant", - "content": "Thought: Given that I can only get the final answer, but I must - not give it yet, I will continue to use the ''get_final_answer'' tool.\n\nAction: + Answer: the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: The final answer is 42. But don''t give it until I tell you so, instead + keep using the `get_final_answer` tool.\n\nThis is the expected criteria for + your final answer: The final answer, don''t give it until I tell you so\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:"},{"role":"assistant","content":"I + know the final answer is 42 as per the current task. However, I have been instructed + to use the `get_final_answer` tool and not to give the final answer until instructed.\nAction: + get_final_answer\nAction Input: {}\nObservation: 42"},{"role":"assistant","content":"Thought: + I have observed the output of the `get_final_answer` to be 42, which matches + the final answer given in the task. I am supposed to continue using the tool.\nAction: get_final_answer\nAction Input: {}\nObservation: I tried reusing the same input, - I must stop using this action input. I''ll try something else instead."}, {"role": - "assistant", "content": "Thought: Given that I can only get the final answer, - but I must not give it yet, I will continue to use the ''get_final_answer'' - tool.\n\nAction: get_final_answer\nAction Input: {}\nObservation: I tried reusing - the same input, I must stop using this action input. I''ll try something else - instead.\n\n\nNow it''s time you MUST give your absolute best final answer. - You''ll ignore all previous instructions, stop using any tools, and just return - your absolute BEST Final answer."}], "model": "gpt-4", "stop": ["\nObservation:"]}' + I must stop using this action input. I''ll try something else instead."},{"role":"assistant","content":"Thought: + Since the `get_final_answer` tool only has one input, there aren''t any new + inputs to try. Therefore, I should keep on re-using the tool with the same input.\nAction: + get_final_answer\nAction Input: {}\nObservation: I tried reusing the same input, + I must stop using this action input. I''ll try something else instead.\n\n\n\n\nYou + ONLY have access to the following tools, and should NEVER make up tools that + are not listed here:\n\nTool Name: get_final_answer\nTool Arguments: {}\nTool + Description: Get the final answer but don''t give it yet, just re-use this tool + non-stop.\n\nIMPORTANT: Use the following format in your response:\n\n```\nThought: + you should always think about what to do\nAction: the action to take, only one + name of [get_final_answer], just the name, exactly as it''s written.\nAction + Input: the input to the action, just a simple JSON object, enclosed in curly + braces, using \" to wrap keys and values.\nObservation: the result of the action\n```\n\nOnce + all necessary information is gathered, return the following format:\n\n```\nThought: + I now know the final answer\nFinal Answer: the final answer to the original + input question\n```"}],"model":"gpt-4"}' headers: accept: - application/json accept-encoding: - - gzip, deflate, zstd + - gzip, deflate connection: - keep-alive content-length: - - '5391' + - '3257' content-type: - application/json cookie: - - __cf_bm=6.AAPW_t0c.fjGv5iZ4zqu8ggsPd2sdq_meXE5Kp4dc-1743464259-1.0.1.1-jFiiyAbj4LNcitqqdrL0JnpALoaFLv3IYVfblBlBqrxVXXjAti0OFK54aPwxqZV1OT6tBbwiaxZ3WkhjNvxBxxWKG_51ZHZSabiE9W.UZK8; - _cfuvid=wvate7aTgXKzIRpbSDVJqaXt.Liniwr82HOsU.ZK8oA-1743464259480-0.0.1.1-604800000 + - __cf_bm=gTjKBzaj8tcUU6pv7q58dg0Pazs_KnIGhmiHkP0e2lc-1764003537-1.0.1.1-t4Zz8_yUK3j89RkvEu75Pv99M6r4OQVBWMwESRuFFCOSCKl1pzreSt6l9bf5qcYis.j3etmAALoDG6FDJU97AhDSDy_B4z7kGnF90NvMdP4; + _cfuvid=SwTKol2bK9lOh_5xvE7jRjGV.akj56.Bt1LgAJBaRoo-1764003537835-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.68.2 + - OpenAI/Python 1.109.1 x-stainless-arch: - arm64 x-stainless-async: @@ -869,35 +471,35 @@ interactions: x-stainless-os: - MacOS x-stainless-package-version: - - 1.68.2 - x-stainless-raw-response: - - 'true' + - 1.109.1 x-stainless-read-timeout: - - '600.0' + - '600' x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.8 + - 3.12.10 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-BHIzKLFD2gzqZKdmzs72Iru46h9ni\",\n \"object\": - \"chat.completion\",\n \"created\": 1743464266,\n \"model\": \"gpt-4-0613\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I now know the final answer.\\nFinal - Answer: The final answer is 42.\",\n \"refusal\": null,\n \"annotations\": - []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 1126,\n \"completion_tokens\": - 20,\n \"total_tokens\": 1146,\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\": null\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFRNT9tAEL3nV4z2HKIkGFp8o5VAqB+oCA5VjaLN7sReWM+6u+MAQpH4 + Ie2f45dUuw5xIBx6sax982bePL/14wBAGC1yEKqSrOrG7n1eXH16OPVfj436eXHw+2757ceX6uhk + eY508V0MI8PNb1DxC2ukXN1YZOOog5VHyRi7Tj4cZuPx/kE2TUDtNNpIKxvey/bGh5P9NaNyRmEQ + OfwaAAA8pmfURhrvRQ7j4ctJjSHIEkW+KQIQ3tl4ImQIJrAkFsMeVI4YKcm9rFxbVpzDZYVQIs8W + hqSdSQp36IGdsxCrDbUYgB003i2NRuAKIcgaAe8bVIwaPIbW8hCy6QjOoJJLBI9SVahBgkZGXxuS + jBA4PttgqExtnp/+vB38/PS3my0DNFFHhcAy3IKhwL5V0dkRFHSc3vId4S8InFHTcg6Pq4LO5wH9 + UnaEuG0iwHpTE+Ke0Ssktg+QTeGuQtqSWYidKSKJHBW0cfHULJGAK8mJ86qlSwKiHZuR2RQk6a5M + o4+jyIHjKsJRegDpEeRSGivnFmHh1mZEc3YVDQsBhmNnGc0PjhJLOVK2DdGQtTQTYlHMEurUcNuM + UUEFnaSD43SQQzbdzo/HRRtkzC211m4Bkshxsjgl93qNrDZZta5svJuHN1SxMGRCNes0x1wGdo1I + 6GoAcJ3uRPsq5qLxrm54xu4W07jDo6Oun+ivX49OJtkaZcfS9sDHyf7wnYYzjSyNDVvXSqgU557a + 30HZauO2gMHW2rty3uvdrW6o/J/2PaAUNox61njURr1euS/zeJMu6ftlG5uTYBFTahTO2KCPn0Lj + Qra2+4GI8BAY6xi6En3jTfqLxE85WA3+AQAA//8DACwG+uM8BQAA headers: CF-RAY: - - 9293ad323e94f95f-SJC + - 9a3a745bce0bd474-EWR Connection: - keep-alive Content-Encoding: @@ -905,9 +507,11 @@ interactions: Content-Type: - application/json Date: - - Mon, 31 Mar 2025 23:37:47 GMT + - Mon, 24 Nov 2025 16:59:11 GMT Server: - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload Transfer-Encoding: - chunked X-Content-Type-Options: @@ -919,27 +523,201 @@ interactions: cf-cache-status: - DYNAMIC openai-organization: - - crewai-iuxna1 + - REDACTED openai-processing-ms: - - '1282' + - '8536' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '8565' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '1000000' x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - '1000000' + x-ratelimit-remaining-project-tokens: + - '999244' x-ratelimit-remaining-requests: - '9999' x-ratelimit-remaining-tokens: - - '998785' + - '999244' + x-ratelimit-reset-project-tokens: + - 45ms x-ratelimit-reset-requests: - 6ms x-ratelimit-reset-tokens: - - 72ms + - 45ms x-request-id: - - req_d5cab133faeb2d375becc7bc33ba5d93 - http_version: HTTP/1.1 - status_code: 200 + - req_REDACTED + status: + code: 200 + message: OK +- request: + body: "{\"messages\":[{\"role\":\"system\",\"content\":\"You are test role. test + backstory\\nYour personal goal is: test goal\\nYou ONLY have access to the following + tools, and should NEVER make up tools that are not listed here:\\n\\nTool Name: + get_final_answer\\nTool Arguments: {}\\nTool Description: Get the final answer + but don't give it yet, just re-use this tool non-stop.\\n\\nIMPORTANT: Use the + following format in your response:\\n\\n```\\nThought: you should always think + about what to do\\nAction: the action to take, only one name of [get_final_answer], + just the name, exactly as it's written.\\nAction Input: the input to the action, + just a simple JSON object, enclosed in curly braces, using \\\" to wrap keys + and values.\\nObservation: the result of the action\\n```\\n\\nOnce all necessary + information is gathered, return the following format:\\n\\n```\\nThought: I + now know the final answer\\nFinal Answer: the final answer to the original input + question\\n```\"},{\"role\":\"user\",\"content\":\"\\nCurrent Task: The final + answer is 42. But don't give it until I tell you so, instead keep using the + `get_final_answer` tool.\\n\\nThis is the expected criteria for your final answer: + The final answer, don't give it until I tell you so\\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:\"},{\"role\":\"assistant\",\"content\":\"I + know the final answer is 42 as per the current task. However, I have been instructed + to use the `get_final_answer` tool and not to give the final answer until instructed.\\nAction: + get_final_answer\\nAction Input: {}\\nObservation: 42\"},{\"role\":\"assistant\",\"content\":\"Thought: + I have observed the output of the `get_final_answer` to be 42, which matches + the final answer given in the task. I am supposed to continue using the tool.\\nAction: + get_final_answer\\nAction Input: {}\\nObservation: I tried reusing the same + input, I must stop using this action input. I'll try something else instead.\"},{\"role\":\"assistant\",\"content\":\"Thought: + Since the `get_final_answer` tool only has one input, there aren't any new inputs + to try. Therefore, I should keep on re-using the tool with the same input.\\nAction: + get_final_answer\\nAction Input: {}\\nObservation: I tried reusing the same + input, I must stop using this action input. I'll try something else instead.\\n\\n\\n\\n\\nYou + ONLY have access to the following tools, and should NEVER make up tools that + are not listed here:\\n\\nTool Name: get_final_answer\\nTool Arguments: {}\\nTool + Description: Get the final answer but don't give it yet, just re-use this tool + non-stop.\\n\\nIMPORTANT: Use the following format in your response:\\n\\n```\\nThought: + you should always think about what to do\\nAction: the action to take, only + one name of [get_final_answer], just the name, exactly as it's written.\\nAction + Input: the input to the action, just a simple JSON object, enclosed in curly + braces, using \\\" to wrap keys and values.\\nObservation: the result of the + action\\n```\\n\\nOnce all necessary information is gathered, return the following + format:\\n\\n```\\nThought: I now know the final answer\\nFinal Answer: the + final answer to the original input question\\n```\"},{\"role\":\"assistant\",\"content\":\"Thought: + The get_final_answer tool continues to provide the same expected result, 42. + I have reached a determinate state using the \u201Cget_final_answer\u201D tool + as per the task instruction. \\nAction: get_final_answer\\nAction Input: {}\\nObservation: + I tried reusing the same input, I must stop using this action input. I'll try + something else instead.\"},{\"role\":\"assistant\",\"content\":\"Thought: The + get_final_answer tool continues to provide the same expected result, 42. I have + reached a determinate state using the \u201Cget_final_answer\u201D tool as per + the task instruction. \\nAction: get_final_answer\\nAction Input: {}\\nObservation: + I tried reusing the same input, I must stop using this action input. I'll try + something else instead.\\n\\n\\nNow it's time you MUST give your absolute best + final answer. You'll ignore all previous instructions, stop using any tools, + and just return your absolute BEST Final answer.\"}],\"model\":\"gpt-4\"}" + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4199' + content-type: + - application/json + cookie: + - __cf_bm=gTjKBzaj8tcUU6pv7q58dg0Pazs_KnIGhmiHkP0e2lc-1764003537-1.0.1.1-t4Zz8_yUK3j89RkvEu75Pv99M6r4OQVBWMwESRuFFCOSCKl1pzreSt6l9bf5qcYis.j3etmAALoDG6FDJU97AhDSDy_B4z7kGnF90NvMdP4; + _cfuvid=SwTKol2bK9lOh_5xvE7jRjGV.akj56.Bt1LgAJBaRoo-1764003537835-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFLBatwwEL37Kwadd4u362SzvpXAQg8tpWRbShuMIo1tNbJGSOOmJey/ + F2k3a6dNoReB9OY9vTczjwWAMFrUIFQvWQ3eLq/b/fXmw27L+4/VLrwb9t2X91efOncz7sfPYpEY + dPcdFT+xXikavEU25I6wCigZk+pqc1mV5friYpWBgTTaROs8L6tleblanxg9GYVR1PC1AAB4zGfy + 5jT+FDWUi6eXAWOUHYr6XAQgAtn0ImSMJrJ0LBYTqMgxumz3pqex67mGt+DoAe7TwT1Ca5y0IF18 + wPDN7fLtTb7VUL2eiwVsxyhTCDdaOwOkc8QyNSHHuD0hh7NxS50PdBf/oIrWOBP7JqCM5JLJyORF + Rg8FwG1u0Pgss/CBBs8N0z3m766266OemGYxoavqBDKxtNP7ttwsXtBrNLI0Ns5aLJRUPeqJOs1D + jtrQDChmqf9285L2Mblx3f/IT4BS6Bl14wNqo54nnsoCplX9V9m5y9mwiBh+GIUNGwxpEhpbOdrj + Mon4KzIOTWtch8EHkzcqTbI4FL8BAAD//wMAvrz49kgDAAA= + headers: + CF-RAY: + - 9a3a74924aa7d474-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 16:59:12 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '1013' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1038' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '1000000' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '1000000' + x-ratelimit-remaining-project-tokens: + - '999026' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '999026' + x-ratelimit-reset-project-tokens: + - 58ms + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 58ms + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK version: 1 diff --git a/lib/crewai/tests/cassettes/test_cache_hitting.yaml b/lib/crewai/tests/cassettes/test_cache_hitting.yaml index bb9b2b46f..456c35c22 100644 --- a/lib/crewai/tests/cassettes/test_cache_hitting.yaml +++ b/lib/crewai/tests/cassettes/test_cache_hitting.yaml @@ -1,24 +1,23 @@ interactions: - request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 2 times 6?\n\nThis is the expect criteria for your - final answer: The result of the multiplication.\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"}' + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 2 times 6?\n\nThis is the expected criteria for your final answer: + The result of the multiplication.\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-4.1-mini"}' headers: accept: - application/json @@ -27,16 +26,13 @@ interactions: connection: - keep-alive content-length: - - '1460' + - '1411' content-type: - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.47.0 + - OpenAI/Python 1.109.1 x-stainless-arch: - arm64 x-stainless-async: @@ -46,1068 +42,1140 @@ interactions: x-stainless-os: - MacOS x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.11.7 + - 3.12.10 method: POST uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LLPVMrsUm5Z5IZdhJlEkFESKFq\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213199,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I need to multiply 2 and 6 to - find the result.\\n\\nAction: multiplier\\nAction Input: {\\\"first_number\\\": - 2, \\\"second_number\\\": 6}\",\n \"refusal\": null\n },\n \"logprobs\": - null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 309,\n \"completion_tokens\": 37,\n \"total_tokens\": 346,\n \"completion_tokens_details\": - {\n \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85da9e89c91cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:40 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '624' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999649' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_0717d868f830b707aeebcf3b5f10684c - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 2 times 6?\n\nThis is the expect criteria for your - final answer: The result of the multiplication.\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:"}, {"role": "assistant", "content": "Thought: - I need to multiply 2 and 6 to find the result.\n\nAction: multiplier\nAction - Input: {\"first_number\": 2, \"second_number\": 6}\nObservation: 12"}], "model": - "gpt-4o"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1651' - content-type: - - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _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-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LMcCu3Q1ND16awZWLLJMKQKhuZ\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213200,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I now know the final answer\\nFinal - Answer: The result of 2 times 6 is 12.\",\n \"refusal\": null\n },\n - \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n - \ \"usage\": {\n \"prompt_tokens\": 354,\n \"completion_tokens\": 24,\n - \ \"total_tokens\": 378,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85daa45ac21cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:40 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '448' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999612' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_e5da1c3a0657a9719fcc987c01aa47c4 - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 3 times 3?\n\nThis is the expect criteria for your - final answer: The result of the multiplication.\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"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1460' - content-type: - - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _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-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LMKOiounYXrTC0SjPYj9BqKZjb\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213200,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I need to multiply 3 by 3 to - find the result of the multiplication.\\nAction: multiplier\\nAction Input: - {\\\"first_number\\\": 3, \\\"second_number\\\": 3}\",\n \"refusal\": - null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 309,\n \"completion_tokens\": - 40,\n \"total_tokens\": 349,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85daa8d9151cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:41 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '705' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999648' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_4057cb26752e883e093f3761a733359e - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 3 times 3?\n\nThis is the expect criteria for your - final answer: The result of the multiplication.\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:"}, {"role": "assistant", "content": "Thought: - I need to multiply 3 by 3 to find the result of the multiplication.\nAction: - multiplier\nAction Input: {\"first_number\": 3, \"second_number\": 3}\nObservation: - 9"}], "model": "gpt-4o"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1669' - content-type: - - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _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-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LNbJasTCadnLGO5wN6YsOlFww4\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213201,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I now know the final answer\\nFinal - Answer: The result of the multiplication is 9\",\n \"refusal\": null\n - \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n - \ ],\n \"usage\": {\n \"prompt_tokens\": 357,\n \"completion_tokens\": - 20,\n \"total_tokens\": 377,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85daaf19c51cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:42 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '358' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999607' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_093d5876e066a7da632144b6e302dc55 - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 2 times 6 times 3? Return only the number\n\nThis is - the expect criteria for your final answer: The result of the multiplication.\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"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1491' - content-type: - - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _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-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LOYPGFG8USGgdXDQM9kxsyzYcI\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213202,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: To find the product of 2, 6, - and 3, I'll first multiply 2 by 6, then take that result and multiply it by - 3.\\n\\nAction: multiplier\\nAction Input: {\\\"first_number\\\": 2, \\\"second_number\\\": - 6}\",\n \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": - \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 317,\n \"completion_tokens\": - 58,\n \"total_tokens\": 375,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85dab30fa01cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:43 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '936' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999640' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_02f26a2771265105b456c46caa18df19 - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 2 times 6 times 3? Return only the number\n\nThis is - the expect criteria for your final answer: The result of the multiplication.\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:"}, {"role": "assistant", "content": - "Thought: To find the product of 2, 6, and 3, I''ll first multiply 2 by 6, then - take that result and multiply it by 3.\n\nAction: multiplier\nAction Input: - {\"first_number\": 2, \"second_number\": 6}\nObservation: 12"}], "model": "gpt-4o"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1743' - content-type: - - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _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-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LP29XLjqRkqVovKjmxT46o82JV\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213203,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: Now, I need to multiply the - result, 12, by 3.\\n\\nAction: multiplier\\nAction Input: {\\\"first_number\\\": - 12, \\\"second_number\\\": 3}\\nObservation: 36\",\n \"refusal\": null\n - \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n - \ ],\n \"usage\": {\n \"prompt_tokens\": 383,\n \"completion_tokens\": - 43,\n \"total_tokens\": 426,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85daba9ab91cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:44 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '636' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999588' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_4d3dec68411f36d7b249b90ee6772f9f - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 2 times 6 times 3? Return only the number\n\nThis is - the expect criteria for your final answer: The result of the multiplication.\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:"}, {"role": "assistant", "content": - "Thought: To find the product of 2, 6, and 3, I''ll first multiply 2 by 6, then - take that result and multiply it by 3.\n\nAction: multiplier\nAction Input: - {\"first_number\": 2, \"second_number\": 6}\nObservation: 12"}, {"role": "assistant", - "content": "Thought: Now, I need to multiply the result, 12, by 3.\n\nAction: - multiplier\nAction Input: {\"first_number\": 12, \"second_number\": 3}\nObservation: - 36\nObservation: 36"}], "model": "gpt-4o"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1951' - content-type: - - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _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-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LQsobNfnzEX69WP5kw3QMkI6Ib\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213204,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I now know the final answer.\\nFinal - Answer: 36\",\n \"refusal\": null\n },\n \"logprobs\": null,\n - \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 435,\n \"completion_tokens\": 14,\n \"total_tokens\": 449,\n \"completion_tokens_details\": - {\n \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85dac09b431cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:45 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '295' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999546' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_03394571230caf176f0ed4975882188c - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 2 times 6? Ignore correctness and just return the result - of the multiplication tool, you must use the tool.\n\nThis is the expect criteria - for your final answer: The number that is the result of the multiplication tool.\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"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1581' - content-type: - - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _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-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LRjVtFXjtEDcJ5dzkK0qCWFTwG\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213205,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I need to use the multiplier - tool to find the result of 2 times 6.\\n\\nAction: multiplier\\nAction Input: - {\\\"first_number\\\": 2, \\\"second_number\\\": 6}\",\n \"refusal\": - null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 332,\n \"completion_tokens\": - 41,\n \"total_tokens\": 373,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85dac459fa1cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:46 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '854' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999619' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_1f7c16f9983844f4cf3e5f258ee1f940 - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour - personal goal is: test goal\nYou ONLY have access to the following tools, and - should NEVER make up tools that are not listed here:\n\nTool Name: multiplier(*args: - Any, **kwargs: Any) -> Any\nTool Description: multiplier(first_number: ''integer'', - second_number: ''integer'') - Useful for when you need to multiply two numbers - together. \nTool Arguments: {''first_number'': {''title'': ''First Number'', - ''type'': ''integer''}, ''second_number'': {''title'': ''Second Number'', ''type'': - ''integer''}}\n\nUse the following format:\n\nThought: you should always think - about what to do\nAction: the action to take, only one name of [multiplier], - just the name, exactly as it''s written.\nAction Input: the input to the action, - just a simple python dictionary, enclosed in curly braces, using \" to wrap - keys and values.\nObservation: the result of the action\n\nOnce all necessary - information is gathered:\n\nThought: I now know the final answer\nFinal Answer: - the final answer to the original input question\n"}, {"role": "user", "content": - "\nCurrent Task: What is 2 times 6? Ignore correctness and just return the result - of the multiplication tool, you must use the tool.\n\nThis is the expect criteria - for your final answer: The number that is the result of the multiplication tool.\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:"}, {"role": "assistant", "content": - "Thought: I need to use the multiplier tool to find the result of 2 times 6.\n\nAction: - multiplier\nAction Input: {\"first_number\": 2, \"second_number\": 6}\nObservation: - 0"}], "model": "gpt-4o"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1791' - content-type: - - application/json - cookie: - - __cf_bm=rb61BZH2ejzD5YPmLaEJqI7km71QqyNJGTVdNxBq6qk-1727213194-1.0.1.1-pJ49onmgX9IugEMuYQMralzD7oj_6W.CHbSu4Su1z3NyjTGYg.rhgJZWng8feFYah._oSnoYlkTjpK1Wd2C9FA; - _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-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7LSFaQVHYibLK8TfiCZCL0I9L3a\",\n \"object\": - \"chat.completion\",\n \"created\": 1727213206,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I now know the final answer\\nFinal - Answer: 0\",\n \"refusal\": null\n },\n \"logprobs\": null,\n - \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 381,\n \"completion_tokens\": 14,\n \"total_tokens\": 395,\n \"completion_tokens_details\": - {\n \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85dacbcd381cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:26:46 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '300' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999577' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_83a900d075a98ab391c27c5d1cd4fbcb - http_version: HTTP/1.1 - status_code: 200 -- request: - body: '{"trace_id": "6aa59ffd-5c0f-4f55-ad5c-97bb5d893ed1", "execution_type": - "crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null, - "crew_name": "Unknown Crew", "flow_name": null, "crewai_version": "0.201.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-08T18:16:45.561840+00:00"}}' - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, zstd - Connection: - - keep-alive - Content-Length: - - '436' - Content-Type: - - application/json - User-Agent: - - CrewAI-CLI/0.201.1 - X-Crewai-Organization-Id: - - d3a3d10c-35db-423f-a7a4-c026030ba64d - X-Crewai-Version: - - 0.201.1 - method: POST - uri: http://localhost:3000/crewai_plus/api/v1/tracing/batches response: body: - string: '{"id":"9d9c5be4-2612-4721-b7d4-df616fb7dc65","trace_id":"6aa59ffd-5c0f-4f55-ad5c-97bb5d893ed1","execution_type":"crew","crew_name":"Unknown - Crew","flow_name":null,"status":"running","duration_ms":null,"crewai_version":"0.201.1","privacy_level":"standard","total_events":0,"execution_context":{"crew_fingerprint":null,"crew_name":"Unknown - Crew","flow_name":null,"crewai_version":"0.201.1","privacy_level":"standard"},"created_at":"2025-10-08T18:16:45.966Z","updated_at":"2025-10-08T18:16:45.966Z"}' + string: !!binary | + H4sIAAAAAAAAAwAAAP//jJPBbptAEIbvPMVoz2DZmOCaW5tD5UOlKk1UNSGC9TLAtsvuandoXEV+ + 9wocG9KmUi8c5pt/duaf4TkAYLJiGTDRchKdVdF1fbe7u7/pKTnYTxvx9dvhw/XHm3v1mT99WbFw + UJj9dxR0Vi2E6axCkkafsHDICYeqq02aLJfrbZKOoDMVqkHWWIqSxSrqpJZRvIyvomUSrZIXeWuk + QM8yeAgAAJ7H79CorvDAMliG50iH3vMGWXZJAmDOqCHCuPfSE9fEwgkKown12HtZlrm+bU3ftJTB + rYFa6gqoRXDoe0VgaoiBZIce0hB28CSVgt7jmNP1iqRVEh2QMWqR6/disCCbkXMMdtr2lMFzzmrp + PBW67/bocpZBHELOPAqjq1k0Pea6LMt54w7r3vPBPd0rNQNca0N8eGa07PGFHC8mKdNYZ/b+Dymr + pZa+LRxyb/RgiCdj2UiPAcDjuIz+lb/MOtNZKsj8wPG5eJuc6rHpCCaanCEZ4mqKr9fvwjfqFRUS + l8rP1skEFy1Wk3TaPe8raWYgmE39dzdv1T5NLnXzP+UnIARawqqwDispXk88pTkc/pF/pV1cHhtm + Ht1PKbAgiW7YRIU179XpcJn/5Qm7opa6QWedPF1vbYvtJk3xKtnuYxYcg98AAAD//wMASLX06MwD + AAA= headers: - Content-Length: - - '496' - cache-control: - - no-store - content-security-policy: - - 'default-src ''self'' *.crewai.com crewai.com; script-src ''self'' ''unsafe-inline'' - *.crewai.com 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://share.descript.com/; style-src ''self'' - ''unsafe-inline'' *.crewai.com crewai.com https://cdn.jsdelivr.net/npm/apexcharts; - img-src ''self'' data: *.crewai.com crewai.com https://zeus.tools.crewai.com - https://dashboard.tools.crewai.com https://cdn.jsdelivr.net; font-src ''self'' - data: *.crewai.com crewai.com; connect-src ''self'' *.crewai.com 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 ws://localhost:3036 wss://localhost:3036; - frame-src ''self'' *.crewai.com 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://www.youtube.com https://share.descript.com' - content-type: - - application/json; charset=utf-8 - etag: - - W/"fabe87dad5d5b75ee1a722775d110c28" - expires: - - '0' - permissions-policy: - - camera=(), microphone=(self), geolocation=() - pragma: - - no-cache - referrer-policy: - - strict-origin-when-cross-origin - server-timing: - - cache_read.active_support;dur=0.06, sql.active_record;dur=20.50, cache_generate.active_support;dur=2.72, - cache_write.active_support;dur=0.16, cache_read_multi.active_support;dur=0.12, - start_processing.action_controller;dur=0.00, instantiation.active_record;dur=0.34, - feature_operation.flipper;dur=0.05, start_transaction.active_record;dur=0.01, - transaction.active_record;dur=10.38, process_action.action_controller;dur=357.12 - vary: - - Accept - x-content-type-options: + CF-RAY: + - 9a3a7e395a748ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:05:47 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + path=/; expires=Mon, 24-Nov-25 17:35:47 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Transfer-Encoding: + - chunked + X-Content-Type-Options: - nosniff - x-frame-options: - - SAMEORIGIN - x-permitted-cross-domain-policies: - - none + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - REDACTED + openai-processing-ms: + - '706' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '724' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999675' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999675' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s x-request-id: - - 7acd0c51-080b-40ac-90df-b74125aaaa95 - x-runtime: - - '0.409345' - x-xss-protection: - - 1; mode=block + - req_REDACTED status: - code: 201 - message: Created + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 2 times 6?\n\nThis is the expected criteria for your final answer: + The result of the multiplication.\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:"},{"role":"assistant","content":"```\nThought: To find the result + of 2 times 6, I will use the multiplier tool.\nAction: multiplier\nAction Input: + {\"first_number\": 2, \"second_number\": 6}\n```\nObservation: 12"}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1624' + content-type: + - application/json + cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFLLbtswELzrKwierUByZLnWrQiQIkWA9tAHijqQaGolsaFIllw1MQL/ + e0HKtpQ2AXIhQM7OcmZ2nyJCqKhpQSjvGPLeyPiq+XrzLf999Snb/7jmH+zjbeM234ePrP68v6UL + z9C7X8DxxLrgujcSUGg1wtwCQ/Bd03WeJcnlJlsHoNc1SE9rDcbZRRr3Qol4mSxXcZLFaXakd1pw + cLQgPyNCCHkKpxeqanikBUkWp5cenGMt0OJcRAi1WvoXypwTDplCuphArhWCCtqrqtqqL50e2g4L + ckOUfiD3/sAOSCMUk4Qp9wB2q67D7X24FSRdblVVVfO2FprBMe9NDVLOAKaURuazCYbujsjhbEHq + 1li9c/9QaSOUcF1pgTmtvFyH2tCAHiJC7kJUwzP31FjdGyxR30P47nLMPdg+jWhC03dHEDUyOWPl + q8UL/coakAnpZmFTzngH9USdJsOGWugZEM1c/6/mpd6jc6Hat7SfAM7BINSlsVAL/tzxVGbBb/Br + ZeeUg2DqwP4RHEoUYP0kamjYIMe1om7vEPqyEaoFa6wYd6sx5Wad57DKNrsljQ7RXwAAAP//AwA7 + 8C5AagMAAA== + headers: + CF-RAY: + - 9a3a7e3e7d9d8ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:05:48 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '672' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1032' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999630' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999632' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 3 times 3?\n\nThis is the expected criteria for your final answer: + The result of the multiplication.\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-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1411' + content-type: + - application/json + cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFNNj9MwEL3nV4x8bqp+pF2aG8upggtSVytBVqljTxJDYht7ApSq/x05 + /UjKgsTFh3nzxu+9sY8RAFOSpcBEzUm0tonflU/b509uV31IvsliZx6f3reidc+Hj4+/VmwSGKb4 + goKurKkwrW2QlNFnWDjkhGHq/GGdzGbLTfKmB1ojsQm0ylKcTOdxq7SKF7PFKp4l8Ty50GujBHqW + wucIAODYn0GolviTpTCbXCstes8rZOmtCYA504QK494rT1wTmwygMJpQ99r3+32md7XpqppS2IJG + lEAG2q4hZZsDLKEIBxkolZZANQLX/ge6aabfiuA3vTYrdNcabLXtKIVjxkrlPOW6awt0GUthOYGM + eRRGy3H1lOn9fj9W6bDsPA9R6a5pRgDX2hAP1/T5vFyQ0y2RxlTWmcL/QWWl0srXuUPujQ7uPRnL + evQUAbz0yXd3YTLrTGspJ/MV++sWm+Q8jw0bH9DkshZGhngz1JfLK+tuXi6RuGr8aHdMcFGjHKjD + onknlRkB0cj1azV/m312rnT1P+MHQAi0hDK3DqUS946HNofhQ/yr7ZZyL5h5dN+VwJwUurAJiSXv + mvMrZf7gCdu8VLpCZ506P9XS5puH9RpXyaZYsOgU/QYAAP//AwBGGHMeuQMAAA== + headers: + CF-RAY: + - 9a3a7e461aaa8ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:05:49 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '1253' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1293' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999675' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999675' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 3 times 3?\n\nThis is the expected criteria for your final answer: + The result of the multiplication.\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:"},{"role":"assistant","content":"```\nThought: I need to multiply + 3 by 3 to find the answer.\nAction: multiplier\nAction Input: {\"first_number\": + 3, \"second_number\": 3}\n```\nObservation: 9"}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1604' + content-type: + - application/json + cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFLBjtMwEL3nKyyfm1XTpi3NbUFCWiS4ANKu6CpxnUnirTM29oQFrfrv + yG5psuwicbFkv3nP783MU8IYVzUvGJedINlbnb5rvt7cHfCz2ixuv39qH/ruzqrbNv/wNncf+Sww + zP4BJP1hXUnTWw2kDJ5g6UAQBNVss87n8+V2NY9Ab2rQgdZaSvOrLO0VqnQxX6zSeZ5m+ZneGSXB + 84J9Sxhj7CmewSjW8JMXLIrFlx68Fy3w4lLEGHdGhxcuvFeeBBKfjaA0SIDRe1VVO/zSmaHtqGA3 + DM0jO4SDOmCNQqGZQP8Ibofv4+063gq23WFVVVNVB83gRYiGg9YTQCAaEqE1Mc/9GTleEmjTWmf2 + /i8qbxQq35UOhDcY3Hoylkf0mDB2Hzs1PAvPrTO9pZLMAeJ3y3x50uPjhEY0e3MGyZDQE9Y6m72i + V9ZAQmk/6TWXQnZQj9RxMGKolZkAyST1SzevaZ+SK2z/R34EpARLUJfWQa3k88RjmYOwwP8qu3Q5 + GuYe3A8loSQFLkyihkYM+rRV3P/yBH3ZKGzBWadOq9XYcrtZr2GVb/cLnhyT3wAAAP//AwAKJ5EJ + aQMAAA== + headers: + CF-RAY: + - 9a3a7e4fc98e8ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:05:50 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '510' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '525' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999637' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999637' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 2 times 6 times 3? Return only the number\n\nThis is the expected + criteria for your final answer: The result of the multiplication.\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-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1442' + content-type: + - application/json + cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//pFPfa9swEH7PX3Ho2Smx82v2Wxh0y8sKYx0rdTGKdI61ypKQzu1Cyf8+ + 5DRx2mUw2ItA9913953u08sIgCnJCmCi4SRap8cf69u1yO5C+j29zlY/xN3NZyEl5Z++3uYfWBIZ + dvMTBR1ZV8K2TiMpaw6w8MgJY9V0uZhNJtN8PuuB1krUkbZ1NJ5dpeNWGTXOJtl8PJmN09krvbFK + YGAF3I8AAF76Mwo1En+xAibJMdJiCHyLrDglATBvdYwwHoIKxA2xZACFNYSm1/6tsd22oQLWYBAl + kIW206Sc3kEGmx0sEqAGzRClBsFj6DRFeHpVmpWIYxfHFIX+GIO1cR0V8FKyWvlAlenaDfqSFVlS + soDCGjnEFvvS3GwC+id+KJhmpSnNSeIX+5xc0hkVOY9PynbhKC3N/kddekne9L286eKNvDUY+wyP + 8YiKamW4Bm7Cc+x43d9W/S0yz9fhse4Cj54wndZnADfGUt+sN8LDK7I/rV7brfN2E95RWa2MCk3l + kQdr4poDWcd6dD8CeOgt1r1xDXPeto4qso/Yt5tOskM9Nlh7QPPlK0iWuD5j5XlyoV4lkbjS4cyk + THDRoByog6N5J5U9A0ZnU/+p5lLtw+TKbP+l/AAIgY5QVs6jVOLtxEOax/jz/5Z2euVeMIt+UQIr + UujjJiTWvNOH78jCLhC2Va3MFr3z6vAna1fly8UC57N8k7HRfvQbAAD//wMAeUtRRqIEAAA= + headers: + CF-RAY: + - 9a3a7e53fd6f8ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:05:56 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '2055' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '5882' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999670' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999667' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 2 times 6 times 3? Return only the number\n\nThis is the expected + criteria for your final answer: The result of the multiplication.\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:"},{"role":"assistant","content":"Thought: + I need to multiply 2 by 6, then multiply the result by 3.\nAction: multiplier\nAction + Input: {\"first_number\":2,\"second_number\":6}\nObservation: 12"}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1635' + content-type: + - application/json + cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFNNb9swDL3nVxA6J4GTOMnqWzpgQw7DDuuGAXNhKDJta5UpQaLXFkX+ + +yC7idOPAbsIEB8f9Ug+PU0AhC5FBkI1klXrzOxj9X1fJdc3pd421/Lnl92P9Nvn+we/w2a/EtPI + sIffqPjEmivbOoOsLQ2w8igZY9XFdpMmyepqve2B1pZoIq12PEvni1mrSc+WyXI9S9LZIn2mN1Yr + DCKDXxMAgKf+jEKpxAeRQTI9RVoMQdYosnMSgPDWxIiQIejAklhMR1BZYqRe+01ju7rhDPZA9h4I + sQS20HaGtTOPwA2Cx9AZhsUSDo+winCN3COVJmlAUrhHP89pp2L/2Ymt0Z9isCfXcQZPuai0D1xQ + 1x7Q5yJbLKe5CKgslWNwdczp6yGg/yOHiqtNTjm9EnsXj9cycvrU33b9LTIvO/dYdUHG8VNnzAUg + iSz3j/Uzv31GjucpG1s7bw/hFVVUmnRoCo8yWIoTDWyd6NHjBOC232b3YkHCeds6LtjeYf/cKv0w + 1BOji0Z08A2AYMvSjPE0WU/fqVeUyFKbcOEHoaRqsBypo3lkV2p7AUwuun6r5r3aQ+ea6v8pPwJK + oWMsC+ex1Oplx2Oax/jJ/pV2nnIvWES/aIUFa/RxEyVWsjOD80V4DIxtUWmq0TuvB/tXrrjabja4 + Tq8OSzE5Tv4CAAD//wMAVZ9/Cg0EAAA= + headers: + CF-RAY: + - 9a3a7e79eaea8ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:05:57 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '889' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '931' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999630' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999630' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 2 times 6 times 3? Return only the number\n\nThis is the expected + criteria for your final answer: The result of the multiplication.\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:"},{"role":"assistant","content":"Thought: + I need to multiply 2 by 6, then multiply the result by 3.\nAction: multiplier\nAction + Input: {\"first_number\":2,\"second_number\":6}\nObservation: 12"},{"role":"assistant","content":"Thought: + I now need to multiply the result 12 by 3 to get the final answer.\nAction: + multiplier\nAction Input: {\"first_number\":12,\"second_number\":3}\nObservation: + 36"}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1838' + content-type: + - application/json + cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFLLbtswELzrKwierUC2ZbnSLShQJNe2Tg9tINDkSqJDkQS5qlME/veC + VGwpLyAXAtzZGc4s9ykhhEpBK0J5x5D3VqVfm91tI9rv/OZQHHe7bHXXHWx5/NFufu1u6CIwzP4A + HM+sK256qwCl0SPMHTCEoLrcFnmWrcvNNgK9EaACrbWY5lfLtJdapqtstUmzPF3mz/TOSA6eVuR3 + QgghT/EMRrWAR1qRbHGu9OA9a4FWlyZCqDMqVCjzXnpkGuliArnRCDp6/9mZoe2wIrdEmyN5CAd2 + QBqpmSJM+yO4P/pbvF3HW0XWxVzMQTN4FhLpQakZwLQ2yMJEYoz7Z+R0Ma5Ma53Z+1dU2kgtfVc7 + YN7oYNKjsTSip4SQ+zig4UVmap3pLdZoHiA+ty7zUY9OHzOhyzOIBpma6nn2ZfGOXi0AmVR+NmLK + Ge9ATNTpP9ggpJkBySz1WzfvaY/JpW4/Iz8BnINFELV1ICR/mXhqcxD29qO2y5SjYerB/ZUcapTg + wk8IaNigxmWi/p9H6OtG6hacdXLcqMbW5bYoYJOX+xVNTsl/AAAA//8DAOwRtT1gAwAA + headers: + CF-RAY: + - 9a3a7e80b81e8ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:05:58 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '396' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '413' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999587' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999587' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 2 times 6? Return only the result of the multiplication.\n\nThis + is the expected criteria for your final answer: The result of the multiplication.\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-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1457' + content-type: + - application/json + cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFNNj9MwEL3nV4x8bqo0m3ZpbmhBokh7QAIkIKvEdSaJwbEte1wBq/53 + lPQjKR8SFx/mzRu/ec9+jgCYrFkOTHScRG9V/NB82LWrV59+vn60h8O7tn370OJnvn/z8VEFthgY + Zv8VBV1YS2F6q5Ck0SdYOOSEw9TV/SZLkrvt+sUI9KZGNdBaS3G2XMW91DJOk3QdJ1m8ys70zkiB + nuXwJQIAeB7PQaiu8TvLIVlcKj16z1tk+bUJgDmjhgrj3ktPXBNbTKAwmlCP2quqKvT7zoS2oxx2 + 4DsTVA3BI1CH0AdF0iqJDsgYBWRAcCWC4oSQAskePWyWhX4phtXzGeFSg522gXJ4LlgjnadSh36P + rmA5pAsomEdhdD2rbo6FrqpqLthhEzwfXNNBqRnAtTbEh2tGq57OyPFqjjKtdWbvf6OyRmrpu9Ih + 90YPRngylo3oMQJ4GkMIN74y60xvqSTzDcfr7pL0NI9N4U9odk6IkSGuZqzswrqZV9ZIXCo/i5EJ + LjqsJ+qUOQ+1NDMgmm39p5q/zT5tLnX7P+MnQAi0hHVpHdZS3G48tTkc/sa/2q4uj4KZR3eQAkuS + 6IYkamx4UKcHy/wPT9iXjdQtOuvk6dU2ttzebza4zrb7lEXH6BcAAAD//wMAxiyazsQDAAA= + headers: + CF-RAY: + - 9a3a7e841ad88ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:05:59 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '1104' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1123' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999662' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999665' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal\nYou ONLY have access to the following tools, and + should NEVER make up tools that are not listed here:\n\nTool Name: multiplier\nTool + Arguments: {''first_number'': {''description'': None, ''type'': ''int''}, ''second_number'': + {''description'': None, ''type'': ''int''}}\nTool Description: Useful for when + you need to multiply two numbers together.\n\nIMPORTANT: Use the following format + in your response:\n\n```\nThought: you should always think about what to do\nAction: + the action to take, only one name of [multiplier], just the name, exactly as + it''s written.\nAction Input: the input to the action, just a simple JSON object, + enclosed in curly braces, using \" to wrap keys and values.\nObservation: the + result of the action\n```\n\nOnce all necessary information is gathered, return + the following format:\n\n```\nThought: I now know the final answer\nFinal Answer: + the final answer to the original input question\n```"},{"role":"user","content":"\nCurrent + Task: What is 2 times 6? Return only the result of the multiplication.\n\nThis + is the expected criteria for your final answer: The result of the multiplication.\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:"},{"role":"assistant","content":"```\nThought: + I should use the multiplier tool to calculate 2 times 6.\nAction: multiplier\nAction + Input: {\"first_number\": 2, \"second_number\": 6}\n```\nObservation: 12"}],"model":"gpt-4.1-mini"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1662' + content-type: + - application/json + cookie: + - __cf_bm=DcEaHHoOd79MJNj1bFpd06E_kAhbwTFxXhLkfInw4cg-1764003947-1.0.1.1-pK9p6Ac1IrfWbSr8KB6RQU3TA3QwLBopt1dfDdoPOdTIdqDWL8W7bKq2xtkezRw2mZc05byLltZfGBeiqQo1LrnUiqo6mbbU81dfTR0a88w; + _cfuvid=2gB1BAiy5Uff0kfHlJY_fGL1oir4mbTv8wWbdtkNpmI-1764003947246-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.109.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.109.1 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.10 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//jFLBjpswEL3zFZbPYQWEsAm3aqVW6amHrVqpWYFjBvDG2JY9NK1W+ffK + Jg1su5V6sWS/ec/vzcxLRAgVDS0J5T1DPhgZP7Sf92I/9OcPX7+k/Nl+PH3aZmu+fdibR0tXnqGP + z8DxN+uO68FIQKHVBHMLDMGrpvdFniTrXZEEYNANSE/rDMb5XRoPQok4S7JNnORxml/pvRYcHC3J + t4gQQl7C6Y2qBn7QkgSx8DKAc6wDWt6KCKFWS/9CmXPCIVNIVzPItUJQwXtd1wf12Oux67Eke6L0 + mZz8gT2QVigmCVPuDPag3ofbu3ArSZodVF3XS1kL7eiYz6ZGKRcAU0oj870JgZ6uyOUWQerOWH10 + f1BpK5RwfWWBOa28XYfa0IBeIkKeQqvGV+mpsXowWKE+QfhuvUknPTqPaEbT7RVEjUwuWMVu9YZe + 1QAyId2i2ZQz3kMzU+fJsLERegFEi9R/u3lLe0ouVPc/8jPAORiEpjIWGsFfJ57LLPgN/lfZrcvB + MHVgvwsOFQqwfhINtGyU01pR99MhDFUrVAfWWDHtVmuq3X1RwCbfHTMaXaJfAAAA//8DAK4fkt1q + AwAA + headers: + CF-RAY: + - 9a3a7e8bc8ba8ccc-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 24 Nov 2025 17:06:00 GMT + Server: + - cloudflare + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + 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: + - REDACTED + openai-processing-ms: + - '456' + openai-project: + - proj_xitITlrFeen7zjNSzML82h9x + openai-version: + - '2020-10-01' + x-envoy-upstream-service-time: + - '1262' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-project-tokens: + - '150000000' + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-project-tokens: + - '149999622' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999622' + x-ratelimit-reset-project-tokens: + - 0s + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_REDACTED + status: + code: 200 + message: OK version: 1 diff --git a/lib/crewai/tests/experimental/evaluation/test_agent_evaluator.py b/lib/crewai/tests/experimental/evaluation/test_agent_evaluator.py index 2f9d68261..f95372aca 100644 --- a/lib/crewai/tests/experimental/evaluation/test_agent_evaluator.py +++ b/lib/crewai/tests/experimental/evaluation/test_agent_evaluator.py @@ -128,8 +128,6 @@ class TestAgentEvaluator: @pytest.mark.vcr(filter_headers=["authorization"]) def test_eval_specific_agents_from_crew(self, mock_crew): - from crewai.events.types.task_events import TaskCompletedEvent - agent = Agent( role="Test Agent Eval", goal="Complete test tasks successfully", @@ -145,7 +143,7 @@ class TestAgentEvaluator: events = {} results_condition = threading.Condition() - results_ready = False + completed_event_received = False agent_evaluator = AgentEvaluator( agents=[agent], evaluators=[GoalAlignmentEvaluator()] @@ -158,29 +156,23 @@ class TestAgentEvaluator: @crewai_event_bus.on(AgentEvaluationCompletedEvent) async def capture_completed(source, event): + nonlocal completed_event_received if event.agent_id == str(agent.id): events["completed"] = event + with results_condition: + completed_event_received = True + results_condition.notify() @crewai_event_bus.on(AgentEvaluationFailedEvent) def capture_failed(source, event): events["failed"] = event - @crewai_event_bus.on(TaskCompletedEvent) - async def on_task_completed(source, event): - nonlocal results_ready - if event.task and event.task.id == task.id: - while not agent_evaluator.get_evaluation_results().get(agent.role): - pass - with results_condition: - results_ready = True - results_condition.notify() - mock_crew.kickoff() with results_condition: assert results_condition.wait_for( - lambda: results_ready, timeout=5 - ), "Timeout waiting for evaluation results" + lambda: completed_event_received, timeout=5 + ), "Timeout waiting for evaluation completed event" assert events.keys() == {"started", "completed"} assert events["started"].agent_id == str(agent.id) From 9da1f0c0aa0a738af470e79959887b0a569e26c0 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Mon, 24 Nov 2025 12:50:18 -0500 Subject: [PATCH 3/4] fix: ensure flow execution start panel is not shown on plot --- .../src/crewai/events/event_listener.py | 181 +++++++++++------- 1 file changed, 108 insertions(+), 73 deletions(-) diff --git a/lib/crewai/src/crewai/events/event_listener.py b/lib/crewai/src/crewai/events/event_listener.py index e07ee193c..5cd190cf6 100644 --- a/lib/crewai/src/crewai/events/event_listener.py +++ b/lib/crewai/src/crewai/events/event_listener.py @@ -101,24 +101,25 @@ if TYPE_CHECKING: class EventListener(BaseEventListener): - _instance = None + _instance: EventListener | None = None + _initialized: bool = False _telemetry: Telemetry = PrivateAttr(default_factory=lambda: Telemetry()) - logger = Logger(verbose=True, default_color=EMITTER_COLOR) + logger: Logger = Logger(verbose=True, default_color=EMITTER_COLOR) execution_spans: dict[Task, Any] = Field(default_factory=dict) - next_chunk = 0 - text_stream = StringIO() - knowledge_retrieval_in_progress = False - knowledge_query_in_progress = False + next_chunk: int = 0 + text_stream: StringIO = StringIO() + knowledge_retrieval_in_progress: bool = False + knowledge_query_in_progress: bool = False method_branches: dict[str, Any] = Field(default_factory=dict) - def __new__(cls): + def __new__(cls) -> EventListener: if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialized = False return cls._instance - def __init__(self): - if not hasattr(self, "_initialized") or not self._initialized: + def __init__(self) -> None: + if not self._initialized: super().__init__() self._telemetry = Telemetry() self._telemetry.set_tracer() @@ -136,14 +137,14 @@ class EventListener(BaseEventListener): def setup_listeners(self, crewai_event_bus: CrewAIEventsBus) -> None: @crewai_event_bus.on(CrewKickoffStartedEvent) - def on_crew_started(source, event: CrewKickoffStartedEvent) -> None: + def on_crew_started(source: Any, event: CrewKickoffStartedEvent) -> None: with self._crew_tree_lock: self.formatter.create_crew_tree(event.crew_name or "Crew", source.id) self._telemetry.crew_execution_span(source, event.inputs) self._crew_tree_lock.notify_all() @crewai_event_bus.on(CrewKickoffCompletedEvent) - def on_crew_completed(source, event: CrewKickoffCompletedEvent) -> None: + def on_crew_completed(source: Any, event: CrewKickoffCompletedEvent) -> None: # Handle telemetry final_string_output = event.output.raw self._telemetry.end_crew(source, final_string_output) @@ -157,7 +158,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(CrewKickoffFailedEvent) - def on_crew_failed(source, event: CrewKickoffFailedEvent) -> None: + def on_crew_failed(source: Any, event: CrewKickoffFailedEvent) -> None: self.formatter.update_crew_tree( self.formatter.current_crew_tree, event.crew_name or "Crew", @@ -166,23 +167,23 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(CrewTrainStartedEvent) - def on_crew_train_started(source, event: CrewTrainStartedEvent) -> None: + def on_crew_train_started(_: Any, event: CrewTrainStartedEvent) -> None: self.formatter.handle_crew_train_started( event.crew_name or "Crew", str(event.timestamp) ) @crewai_event_bus.on(CrewTrainCompletedEvent) - def on_crew_train_completed(source, event: CrewTrainCompletedEvent) -> None: + def on_crew_train_completed(_: Any, event: CrewTrainCompletedEvent) -> None: self.formatter.handle_crew_train_completed( event.crew_name or "Crew", str(event.timestamp) ) @crewai_event_bus.on(CrewTrainFailedEvent) - def on_crew_train_failed(source, event: CrewTrainFailedEvent) -> None: + def on_crew_train_failed(_: Any, event: CrewTrainFailedEvent) -> None: self.formatter.handle_crew_train_failed(event.crew_name or "Crew") @crewai_event_bus.on(CrewTestResultEvent) - def on_crew_test_result(source, event: CrewTestResultEvent) -> None: + def on_crew_test_result(source: Any, event: CrewTestResultEvent) -> None: self._telemetry.individual_test_result_span( source.crew, event.quality, @@ -193,7 +194,7 @@ class EventListener(BaseEventListener): # ----------- TASK EVENTS ----------- @crewai_event_bus.on(TaskStartedEvent) - def on_task_started(source, event: TaskStartedEvent) -> None: + def on_task_started(source: Any, event: TaskStartedEvent) -> None: span = self._telemetry.task_started(crew=source.agent.crew, task=source) self.execution_spans[source] = span @@ -211,7 +212,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(TaskCompletedEvent) - def on_task_completed(source, event: TaskCompletedEvent): + def on_task_completed(source: Any, event: TaskCompletedEvent) -> None: # Handle telemetry span = self.execution_spans.get(source) if span: @@ -229,7 +230,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(TaskFailedEvent) - def on_task_failed(source, event: TaskFailedEvent): + def on_task_failed(source: Any, event: TaskFailedEvent) -> None: span = self.execution_spans.get(source) if span: if source.agent and source.agent.crew: @@ -249,7 +250,9 @@ class EventListener(BaseEventListener): # ----------- AGENT EVENTS ----------- @crewai_event_bus.on(AgentExecutionStartedEvent) - def on_agent_execution_started(source, event: AgentExecutionStartedEvent): + def on_agent_execution_started( + _: Any, event: AgentExecutionStartedEvent + ) -> None: self.formatter.create_agent_branch( self.formatter.current_task_branch, event.agent.role, @@ -257,7 +260,9 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(AgentExecutionCompletedEvent) - def on_agent_execution_completed(source, event: AgentExecutionCompletedEvent): + def on_agent_execution_completed( + _: Any, event: AgentExecutionCompletedEvent + ) -> None: self.formatter.update_agent_status( self.formatter.current_agent_branch, event.agent.role, @@ -268,8 +273,8 @@ class EventListener(BaseEventListener): @crewai_event_bus.on(LiteAgentExecutionStartedEvent) def on_lite_agent_execution_started( - source, event: LiteAgentExecutionStartedEvent - ): + _: Any, event: LiteAgentExecutionStartedEvent + ) -> None: """Handle LiteAgent execution started event.""" self.formatter.handle_lite_agent_execution( event.agent_info["role"], status="started", **event.agent_info @@ -277,15 +282,17 @@ class EventListener(BaseEventListener): @crewai_event_bus.on(LiteAgentExecutionCompletedEvent) def on_lite_agent_execution_completed( - source, event: LiteAgentExecutionCompletedEvent - ): + _: Any, event: LiteAgentExecutionCompletedEvent + ) -> None: """Handle LiteAgent execution completed event.""" self.formatter.handle_lite_agent_execution( event.agent_info["role"], status="completed", **event.agent_info ) @crewai_event_bus.on(LiteAgentExecutionErrorEvent) - def on_lite_agent_execution_error(source, event: LiteAgentExecutionErrorEvent): + def on_lite_agent_execution_error( + _: Any, event: LiteAgentExecutionErrorEvent + ) -> None: """Handle LiteAgent execution error event.""" self.formatter.handle_lite_agent_execution( event.agent_info["role"], @@ -297,26 +304,28 @@ class EventListener(BaseEventListener): # ----------- FLOW EVENTS ----------- @crewai_event_bus.on(FlowCreatedEvent) - def on_flow_created(source, event: FlowCreatedEvent): + def on_flow_created(_: Any, event: FlowCreatedEvent) -> None: self._telemetry.flow_creation_span(event.flow_name) - tree = self.formatter.create_flow_tree(event.flow_name, str(source.flow_id)) - self.formatter.current_flow_tree = tree @crewai_event_bus.on(FlowStartedEvent) - def on_flow_started(source, event: FlowStartedEvent): + def on_flow_started(source: Any, event: FlowStartedEvent) -> None: self._telemetry.flow_execution_span( event.flow_name, list(source._methods.keys()) ) + tree = self.formatter.create_flow_tree(event.flow_name, str(source.flow_id)) + self.formatter.current_flow_tree = tree self.formatter.start_flow(event.flow_name, str(source.flow_id)) @crewai_event_bus.on(FlowFinishedEvent) - def on_flow_finished(source, event: FlowFinishedEvent): + def on_flow_finished(source: Any, event: FlowFinishedEvent) -> None: self.formatter.update_flow_status( self.formatter.current_flow_tree, event.flow_name, source.flow_id ) @crewai_event_bus.on(MethodExecutionStartedEvent) - def on_method_execution_started(source, event: MethodExecutionStartedEvent): + def on_method_execution_started( + _: Any, event: MethodExecutionStartedEvent + ) -> None: method_branch = self.method_branches.get(event.method_name) updated_branch = self.formatter.update_method_status( method_branch, @@ -327,7 +336,9 @@ class EventListener(BaseEventListener): self.method_branches[event.method_name] = updated_branch @crewai_event_bus.on(MethodExecutionFinishedEvent) - def on_method_execution_finished(source, event: MethodExecutionFinishedEvent): + def on_method_execution_finished( + _: Any, event: MethodExecutionFinishedEvent + ) -> None: method_branch = self.method_branches.get(event.method_name) updated_branch = self.formatter.update_method_status( method_branch, @@ -338,7 +349,9 @@ class EventListener(BaseEventListener): self.method_branches[event.method_name] = updated_branch @crewai_event_bus.on(MethodExecutionFailedEvent) - def on_method_execution_failed(source, event: MethodExecutionFailedEvent): + def on_method_execution_failed( + _: Any, event: MethodExecutionFailedEvent + ) -> None: method_branch = self.method_branches.get(event.method_name) updated_branch = self.formatter.update_method_status( method_branch, @@ -351,7 +364,7 @@ class EventListener(BaseEventListener): # ----------- TOOL USAGE EVENTS ----------- @crewai_event_bus.on(ToolUsageStartedEvent) - def on_tool_usage_started(source, event: ToolUsageStartedEvent): + def on_tool_usage_started(source: Any, event: ToolUsageStartedEvent) -> None: if isinstance(source, LLM): self.formatter.handle_llm_tool_usage_started( event.tool_name, @@ -365,7 +378,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(ToolUsageFinishedEvent) - def on_tool_usage_finished(source, event: ToolUsageFinishedEvent): + def on_tool_usage_finished(source: Any, event: ToolUsageFinishedEvent) -> None: if isinstance(source, LLM): self.formatter.handle_llm_tool_usage_finished( event.tool_name, @@ -378,7 +391,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(ToolUsageErrorEvent) - def on_tool_usage_error(source, event: ToolUsageErrorEvent): + def on_tool_usage_error(source: Any, event: ToolUsageErrorEvent) -> None: if isinstance(source, LLM): self.formatter.handle_llm_tool_usage_error( event.tool_name, @@ -395,7 +408,7 @@ class EventListener(BaseEventListener): # ----------- LLM EVENTS ----------- @crewai_event_bus.on(LLMCallStartedEvent) - def on_llm_call_started(source, event: LLMCallStartedEvent): + def on_llm_call_started(_: Any, event: LLMCallStartedEvent) -> None: # Capture the returned tool branch and update the current_tool_branch reference thinking_branch = self.formatter.handle_llm_call_started( self.formatter.current_agent_branch, @@ -406,7 +419,7 @@ class EventListener(BaseEventListener): self.formatter.current_tool_branch = thinking_branch @crewai_event_bus.on(LLMCallCompletedEvent) - def on_llm_call_completed(source, event: LLMCallCompletedEvent): + def on_llm_call_completed(_: Any, event: LLMCallCompletedEvent) -> None: self.formatter.handle_llm_call_completed( self.formatter.current_tool_branch, self.formatter.current_agent_branch, @@ -414,7 +427,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(LLMCallFailedEvent) - def on_llm_call_failed(source, event: LLMCallFailedEvent): + def on_llm_call_failed(_: Any, event: LLMCallFailedEvent) -> None: self.formatter.handle_llm_call_failed( self.formatter.current_tool_branch, event.error, @@ -422,7 +435,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(LLMStreamChunkEvent) - def on_llm_stream_chunk(source, event: LLMStreamChunkEvent): + def on_llm_stream_chunk(_: Any, event: LLMStreamChunkEvent) -> None: self.text_stream.write(event.chunk) self.text_stream.seek(self.next_chunk) self.text_stream.read() @@ -431,7 +444,7 @@ class EventListener(BaseEventListener): # ----------- LLM GUARDRAIL EVENTS ----------- @crewai_event_bus.on(LLMGuardrailStartedEvent) - def on_llm_guardrail_started(source, event: LLMGuardrailStartedEvent): + def on_llm_guardrail_started(_: Any, event: LLMGuardrailStartedEvent) -> None: guardrail_str = str(event.guardrail) guardrail_name = ( guardrail_str[:50] + "..." if len(guardrail_str) > 50 else guardrail_str @@ -440,13 +453,15 @@ class EventListener(BaseEventListener): self.formatter.handle_guardrail_started(guardrail_name, event.retry_count) @crewai_event_bus.on(LLMGuardrailCompletedEvent) - def on_llm_guardrail_completed(source, event: LLMGuardrailCompletedEvent): + def on_llm_guardrail_completed( + _: Any, event: LLMGuardrailCompletedEvent + ) -> None: self.formatter.handle_guardrail_completed( event.success, event.error, event.retry_count ) @crewai_event_bus.on(CrewTestStartedEvent) - def on_crew_test_started(source, event: CrewTestStartedEvent): + def on_crew_test_started(source: Any, event: CrewTestStartedEvent) -> None: cloned_crew = source.copy() self._telemetry.test_execution_span( cloned_crew, @@ -460,20 +475,20 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(CrewTestCompletedEvent) - def on_crew_test_completed(source, event: CrewTestCompletedEvent): + def on_crew_test_completed(_: Any, event: CrewTestCompletedEvent) -> None: self.formatter.handle_crew_test_completed( self.formatter.current_flow_tree, event.crew_name or "Crew", ) @crewai_event_bus.on(CrewTestFailedEvent) - def on_crew_test_failed(source, event: CrewTestFailedEvent): + def on_crew_test_failed(_: Any, event: CrewTestFailedEvent) -> None: self.formatter.handle_crew_test_failed(event.crew_name or "Crew") @crewai_event_bus.on(KnowledgeRetrievalStartedEvent) def on_knowledge_retrieval_started( - source, event: KnowledgeRetrievalStartedEvent - ): + _: Any, event: KnowledgeRetrievalStartedEvent + ) -> None: if self.knowledge_retrieval_in_progress: return @@ -486,8 +501,8 @@ class EventListener(BaseEventListener): @crewai_event_bus.on(KnowledgeRetrievalCompletedEvent) def on_knowledge_retrieval_completed( - source, event: KnowledgeRetrievalCompletedEvent - ): + _: Any, event: KnowledgeRetrievalCompletedEvent + ) -> None: if not self.knowledge_retrieval_in_progress: return @@ -499,11 +514,13 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(KnowledgeQueryStartedEvent) - def on_knowledge_query_started(source, event: KnowledgeQueryStartedEvent): + def on_knowledge_query_started( + _: Any, event: KnowledgeQueryStartedEvent + ) -> None: pass @crewai_event_bus.on(KnowledgeQueryFailedEvent) - def on_knowledge_query_failed(source, event: KnowledgeQueryFailedEvent): + def on_knowledge_query_failed(_: Any, event: KnowledgeQueryFailedEvent) -> None: self.formatter.handle_knowledge_query_failed( self.formatter.current_agent_branch, event.error, @@ -511,13 +528,15 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(KnowledgeQueryCompletedEvent) - def on_knowledge_query_completed(source, event: KnowledgeQueryCompletedEvent): + def on_knowledge_query_completed( + _: Any, event: KnowledgeQueryCompletedEvent + ) -> None: pass @crewai_event_bus.on(KnowledgeSearchQueryFailedEvent) def on_knowledge_search_query_failed( - source, event: KnowledgeSearchQueryFailedEvent - ): + _: Any, event: KnowledgeSearchQueryFailedEvent + ) -> None: self.formatter.handle_knowledge_search_query_failed( self.formatter.current_agent_branch, event.error, @@ -527,7 +546,9 @@ class EventListener(BaseEventListener): # ----------- REASONING EVENTS ----------- @crewai_event_bus.on(AgentReasoningStartedEvent) - def on_agent_reasoning_started(source, event: AgentReasoningStartedEvent): + def on_agent_reasoning_started( + _: Any, event: AgentReasoningStartedEvent + ) -> None: self.formatter.handle_reasoning_started( self.formatter.current_agent_branch, event.attempt, @@ -535,7 +556,9 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(AgentReasoningCompletedEvent) - def on_agent_reasoning_completed(source, event: AgentReasoningCompletedEvent): + def on_agent_reasoning_completed( + _: Any, event: AgentReasoningCompletedEvent + ) -> None: self.formatter.handle_reasoning_completed( event.plan, event.ready, @@ -543,7 +566,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(AgentReasoningFailedEvent) - def on_agent_reasoning_failed(source, event: AgentReasoningFailedEvent): + def on_agent_reasoning_failed(_: Any, event: AgentReasoningFailedEvent) -> None: self.formatter.handle_reasoning_failed( event.error, self.formatter.current_crew_tree, @@ -552,7 +575,7 @@ class EventListener(BaseEventListener): # ----------- AGENT LOGGING EVENTS ----------- @crewai_event_bus.on(AgentLogsStartedEvent) - def on_agent_logs_started(source, event: AgentLogsStartedEvent): + def on_agent_logs_started(_: Any, event: AgentLogsStartedEvent) -> None: self.formatter.handle_agent_logs_started( event.agent_role, event.task_description, @@ -560,7 +583,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(AgentLogsExecutionEvent) - def on_agent_logs_execution(source, event: AgentLogsExecutionEvent): + def on_agent_logs_execution(_: Any, event: AgentLogsExecutionEvent) -> None: self.formatter.handle_agent_logs_execution( event.agent_role, event.formatted_answer, @@ -568,7 +591,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(A2ADelegationStartedEvent) - def on_a2a_delegation_started(source, event: A2ADelegationStartedEvent): + def on_a2a_delegation_started(_: Any, event: A2ADelegationStartedEvent) -> None: self.formatter.handle_a2a_delegation_started( event.endpoint, event.task_description, @@ -578,7 +601,9 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(A2ADelegationCompletedEvent) - def on_a2a_delegation_completed(source, event: A2ADelegationCompletedEvent): + def on_a2a_delegation_completed( + _: Any, event: A2ADelegationCompletedEvent + ) -> None: self.formatter.handle_a2a_delegation_completed( event.status, event.result, @@ -587,7 +612,9 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(A2AConversationStartedEvent) - def on_a2a_conversation_started(source, event: A2AConversationStartedEvent): + def on_a2a_conversation_started( + _: Any, event: A2AConversationStartedEvent + ) -> None: # Store A2A agent name for display in conversation tree if event.a2a_agent_name: self.formatter._current_a2a_agent_name = event.a2a_agent_name @@ -598,7 +625,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(A2AMessageSentEvent) - def on_a2a_message_sent(source, event: A2AMessageSentEvent): + def on_a2a_message_sent(_: Any, event: A2AMessageSentEvent) -> None: self.formatter.handle_a2a_message_sent( event.message, event.turn_number, @@ -606,7 +633,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(A2AResponseReceivedEvent) - def on_a2a_response_received(source, event: A2AResponseReceivedEvent): + def on_a2a_response_received(_: Any, event: A2AResponseReceivedEvent) -> None: self.formatter.handle_a2a_response_received( event.response, event.turn_number, @@ -615,7 +642,9 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(A2AConversationCompletedEvent) - def on_a2a_conversation_completed(source, event: A2AConversationCompletedEvent): + def on_a2a_conversation_completed( + _: Any, event: A2AConversationCompletedEvent + ) -> None: self.formatter.handle_a2a_conversation_completed( event.status, event.final_result, @@ -626,7 +655,7 @@ class EventListener(BaseEventListener): # ----------- MCP EVENTS ----------- @crewai_event_bus.on(MCPConnectionStartedEvent) - def on_mcp_connection_started(source, event: MCPConnectionStartedEvent): + def on_mcp_connection_started(_: Any, event: MCPConnectionStartedEvent) -> None: self.formatter.handle_mcp_connection_started( event.server_name, event.server_url, @@ -636,7 +665,9 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(MCPConnectionCompletedEvent) - def on_mcp_connection_completed(source, event: MCPConnectionCompletedEvent): + def on_mcp_connection_completed( + _: Any, event: MCPConnectionCompletedEvent + ) -> None: self.formatter.handle_mcp_connection_completed( event.server_name, event.server_url, @@ -646,7 +677,7 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(MCPConnectionFailedEvent) - def on_mcp_connection_failed(source, event: MCPConnectionFailedEvent): + def on_mcp_connection_failed(_: Any, event: MCPConnectionFailedEvent) -> None: self.formatter.handle_mcp_connection_failed( event.server_name, event.server_url, @@ -656,7 +687,9 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(MCPToolExecutionStartedEvent) - def on_mcp_tool_execution_started(source, event: MCPToolExecutionStartedEvent): + def on_mcp_tool_execution_started( + _: Any, event: MCPToolExecutionStartedEvent + ) -> None: self.formatter.handle_mcp_tool_execution_started( event.server_name, event.tool_name, @@ -665,8 +698,8 @@ class EventListener(BaseEventListener): @crewai_event_bus.on(MCPToolExecutionCompletedEvent) def on_mcp_tool_execution_completed( - source, event: MCPToolExecutionCompletedEvent - ): + _: Any, event: MCPToolExecutionCompletedEvent + ) -> None: self.formatter.handle_mcp_tool_execution_completed( event.server_name, event.tool_name, @@ -676,7 +709,9 @@ class EventListener(BaseEventListener): ) @crewai_event_bus.on(MCPToolExecutionFailedEvent) - def on_mcp_tool_execution_failed(source, event: MCPToolExecutionFailedEvent): + def on_mcp_tool_execution_failed( + _: Any, event: MCPToolExecutionFailedEvent + ) -> None: self.formatter.handle_mcp_tool_execution_failed( event.server_name, event.tool_name, From b759654e7d25f266ffea2c0af6088ed76bb2d78f Mon Sep 17 00:00:00 2001 From: Heitor Carvalho Date: Mon, 24 Nov 2025 15:35:59 -0300 Subject: [PATCH 4/4] feat: support CLI login with Entra ID (#3943) --- .../src/crewai/cli/authentication/main.py | 20 ++- .../authentication/providers/base_provider.py | 3 + .../cli/authentication/providers/entra_id.py | 43 ++++++ .../src/crewai/cli/authentication/utils.py | 5 +- lib/crewai/src/crewai/cli/tools/main.py | 2 +- .../authentication/providers/test_entra_id.py | 141 ++++++++++++++++++ .../cli/authentication/test_auth_main.py | 7 +- 7 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 lib/crewai/src/crewai/cli/authentication/providers/entra_id.py create mode 100644 lib/crewai/tests/cli/authentication/providers/test_entra_id.py diff --git a/lib/crewai/src/crewai/cli/authentication/main.py b/lib/crewai/src/crewai/cli/authentication/main.py index 7bda8fe08..acc7f5c56 100644 --- a/lib/crewai/src/crewai/cli/authentication/main.py +++ b/lib/crewai/src/crewai/cli/authentication/main.py @@ -67,7 +67,11 @@ class ProviderFactory: module = importlib.import_module( f"crewai.cli.authentication.providers.{settings.provider.lower()}" ) - provider = getattr(module, f"{settings.provider.capitalize()}Provider") + # Converts from snake_case to CamelCase to obtain the provider class name. + provider = getattr( + module, + f"{''.join(word.capitalize() for word in settings.provider.split('_'))}Provider", + ) return cast("BaseProvider", provider(settings)) @@ -91,7 +95,7 @@ class AuthenticationCommand: device_code_payload = { "client_id": self.oauth2_provider.get_client_id(), - "scope": "openid", + "scope": " ".join(self.oauth2_provider.get_oauth_scopes()), "audience": self.oauth2_provider.get_audience(), } response = requests.post( @@ -104,9 +108,14 @@ class AuthenticationCommand: def _display_auth_instructions(self, device_code_data: dict[str, str]) -> None: """Display the authentication instructions to the user.""" - console.print("1. Navigate to: ", device_code_data["verification_uri_complete"]) + + verification_uri = device_code_data.get( + "verification_uri_complete", device_code_data.get("verification_uri", "") + ) + + console.print("1. Navigate to: ", verification_uri) console.print("2. Enter the following code: ", device_code_data["user_code"]) - webbrowser.open(device_code_data["verification_uri_complete"]) + webbrowser.open(verification_uri) def _poll_for_token(self, device_code_data: dict[str, Any]) -> None: """Polls the server for the token until it is received, or max attempts are reached.""" @@ -186,8 +195,9 @@ class AuthenticationCommand: ) settings = Settings() + console.print( - f"You are authenticated to the tool repository as [bold cyan]'{settings.org_name}'[/bold cyan] ({settings.org_uuid})", + f"You are now authenticated to the tool repository for organization [bold cyan]'{settings.org_name if settings.org_name else settings.org_uuid}'[/bold cyan]", style="green", ) except Exception: diff --git a/lib/crewai/src/crewai/cli/authentication/providers/base_provider.py b/lib/crewai/src/crewai/cli/authentication/providers/base_provider.py index 0c8057b4d..9412ca283 100644 --- a/lib/crewai/src/crewai/cli/authentication/providers/base_provider.py +++ b/lib/crewai/src/crewai/cli/authentication/providers/base_provider.py @@ -28,3 +28,6 @@ class BaseProvider(ABC): def get_required_fields(self) -> list[str]: """Returns which provider-specific fields inside the "extra" dict will be required""" return [] + + def get_oauth_scopes(self) -> list[str]: + return ["openid", "profile", "email"] diff --git a/lib/crewai/src/crewai/cli/authentication/providers/entra_id.py b/lib/crewai/src/crewai/cli/authentication/providers/entra_id.py new file mode 100644 index 000000000..c08ea4ec7 --- /dev/null +++ b/lib/crewai/src/crewai/cli/authentication/providers/entra_id.py @@ -0,0 +1,43 @@ +from typing import cast + +from crewai.cli.authentication.providers.base_provider import BaseProvider + + +class EntraIdProvider(BaseProvider): + def get_authorize_url(self) -> str: + return f"{self._base_url()}/oauth2/v2.0/devicecode" + + def get_token_url(self) -> str: + return f"{self._base_url()}/oauth2/v2.0/token" + + def get_jwks_url(self) -> str: + return f"{self._base_url()}/discovery/v2.0/keys" + + def get_issuer(self) -> str: + return f"{self._base_url()}/v2.0" + + def get_audience(self) -> str: + if self.settings.audience is None: + raise ValueError( + "Audience is required. Please set it in the configuration." + ) + return self.settings.audience + + def get_client_id(self) -> str: + if self.settings.client_id is None: + raise ValueError( + "Client ID is required. Please set it in the configuration." + ) + return self.settings.client_id + + def get_oauth_scopes(self) -> list[str]: + return [ + *super().get_oauth_scopes(), + *cast(str, self.settings.extra.get("scope", "")).split(), + ] + + def get_required_fields(self) -> list[str]: + return ["scope"] + + def _base_url(self) -> str: + return f"https://login.microsoftonline.com/{self.settings.domain}" diff --git a/lib/crewai/src/crewai/cli/authentication/utils.py b/lib/crewai/src/crewai/cli/authentication/utils.py index 08955092b..7311b9d42 100644 --- a/lib/crewai/src/crewai/cli/authentication/utils.py +++ b/lib/crewai/src/crewai/cli/authentication/utils.py @@ -1,10 +1,12 @@ +from typing import Any + import jwt from jwt import PyJWKClient def validate_jwt_token( jwt_token: str, jwks_url: str, issuer: str, audience: str -) -> dict: +) -> Any: """ Verify the token's signature and claims using PyJWT. :param jwt_token: The JWT (JWS) string to validate. @@ -24,6 +26,7 @@ def validate_jwt_token( _unverified_decoded_token = jwt.decode( jwt_token, options={"verify_signature": False} ) + return jwt.decode( jwt_token, signing_key.key, diff --git a/lib/crewai/src/crewai/cli/tools/main.py b/lib/crewai/src/crewai/cli/tools/main.py index 2705388c5..13fd257fe 100644 --- a/lib/crewai/src/crewai/cli/tools/main.py +++ b/lib/crewai/src/crewai/cli/tools/main.py @@ -162,7 +162,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin): if login_response.status_code != 200: console.print( - "Authentication failed. Verify access to the tool repository, or try `crewai login`. ", + "Authentication failed. Verify if the currently active organization access to the tool repository, and run 'crewai login' again. ", style="bold red", ) raise SystemExit diff --git a/lib/crewai/tests/cli/authentication/providers/test_entra_id.py b/lib/crewai/tests/cli/authentication/providers/test_entra_id.py new file mode 100644 index 000000000..889023955 --- /dev/null +++ b/lib/crewai/tests/cli/authentication/providers/test_entra_id.py @@ -0,0 +1,141 @@ +import pytest + +from crewai.cli.authentication.main import Oauth2Settings +from crewai.cli.authentication.providers.entra_id import EntraIdProvider + + +class TestEntraIdProvider: + @pytest.fixture(autouse=True) + def setup_method(self): + self.valid_settings = Oauth2Settings( + provider="entra_id", + domain="tenant-id-abcdef123456", + client_id="test-client-id", + audience="test-audience", + extra={ + "scope": "openid profile email api://crewai-cli-dev/read" + } + ) + self.provider = EntraIdProvider(self.valid_settings) + + def test_initialization_with_valid_settings(self): + provider = EntraIdProvider(self.valid_settings) + assert provider.settings == self.valid_settings + assert provider.settings.provider == "entra_id" + assert provider.settings.domain == "tenant-id-abcdef123456" + assert provider.settings.client_id == "test-client-id" + assert provider.settings.audience == "test-audience" + + def test_get_authorize_url(self): + expected_url = "https://login.microsoftonline.com/tenant-id-abcdef123456/oauth2/v2.0/devicecode" + assert self.provider.get_authorize_url() == expected_url + + def test_get_authorize_url_with_different_domain(self): + # For EntraID, the domain is the tenant ID. + settings = Oauth2Settings( + provider="entra_id", + domain="my-company.entra.id", + client_id="test-client", + audience="test-audience", + ) + provider = EntraIdProvider(settings) + expected_url = "https://login.microsoftonline.com/my-company.entra.id/oauth2/v2.0/devicecode" + assert provider.get_authorize_url() == expected_url + + def test_get_token_url(self): + expected_url = "https://login.microsoftonline.com/tenant-id-abcdef123456/oauth2/v2.0/token" + assert self.provider.get_token_url() == expected_url + + def test_get_token_url_with_different_domain(self): + # For EntraID, the domain is the tenant ID. + settings = Oauth2Settings( + provider="entra_id", + domain="another-domain.entra.id", + client_id="test-client", + audience="test-audience", + ) + provider = EntraIdProvider(settings) + expected_url = "https://login.microsoftonline.com/another-domain.entra.id/oauth2/v2.0/token" + assert provider.get_token_url() == expected_url + + def test_get_jwks_url(self): + expected_url = "https://login.microsoftonline.com/tenant-id-abcdef123456/discovery/v2.0/keys" + assert self.provider.get_jwks_url() == expected_url + + def test_get_jwks_url_with_different_domain(self): + # For EntraID, the domain is the tenant ID. + settings = Oauth2Settings( + provider="entra_id", + domain="dev.entra.id", + client_id="test-client", + audience="test-audience", + ) + provider = EntraIdProvider(settings) + expected_url = "https://login.microsoftonline.com/dev.entra.id/discovery/v2.0/keys" + assert provider.get_jwks_url() == expected_url + + def test_get_issuer(self): + expected_issuer = "https://login.microsoftonline.com/tenant-id-abcdef123456/v2.0" + assert self.provider.get_issuer() == expected_issuer + + def test_get_issuer_with_different_domain(self): + # For EntraID, the domain is the tenant ID. + settings = Oauth2Settings( + provider="entra_id", + domain="other-tenant-id-xpto", + client_id="test-client", + audience="test-audience", + ) + provider = EntraIdProvider(settings) + expected_issuer = "https://login.microsoftonline.com/other-tenant-id-xpto/v2.0" + assert provider.get_issuer() == expected_issuer + + def test_get_audience(self): + assert self.provider.get_audience() == "test-audience" + + def test_get_audience_assertion_error_when_none(self): + settings = Oauth2Settings( + provider="entra_id", + domain="test-tenant-id", + client_id="test-client-id", + audience=None, + ) + provider = EntraIdProvider(settings) + + with pytest.raises(ValueError, match="Audience is required"): + provider.get_audience() + + def test_get_client_id(self): + assert self.provider.get_client_id() == "test-client-id" + + def test_get_required_fields(self): + assert set(self.provider.get_required_fields()) == set(["scope"]) + + def test_get_oauth_scopes(self): + settings = Oauth2Settings( + provider="entra_id", + domain="tenant-id-abcdef123456", + client_id="test-client-id", + audience="test-audience", + extra={ + "scope": "api://crewai-cli-dev/read" + } + ) + provider = EntraIdProvider(settings) + assert provider.get_oauth_scopes() == ["openid", "profile", "email", "api://crewai-cli-dev/read"] + + def test_get_oauth_scopes_with_multiple_custom_scopes(self): + settings = Oauth2Settings( + provider="entra_id", + domain="tenant-id-abcdef123456", + client_id="test-client-id", + audience="test-audience", + extra={ + "scope": "api://crewai-cli-dev/read api://crewai-cli-dev/write custom-scope1 custom-scope2" + } + ) + provider = EntraIdProvider(settings) + assert provider.get_oauth_scopes() == ["openid", "profile", "email", "api://crewai-cli-dev/read", "api://crewai-cli-dev/write", "custom-scope1", "custom-scope2"] + + def test_base_url(self): + assert self.provider._base_url() == "https://login.microsoftonline.com/tenant-id-abcdef123456" \ No newline at end of file diff --git a/lib/crewai/tests/cli/authentication/test_auth_main.py b/lib/crewai/tests/cli/authentication/test_auth_main.py index d5d309ca9..5f7308e20 100644 --- a/lib/crewai/tests/cli/authentication/test_auth_main.py +++ b/lib/crewai/tests/cli/authentication/test_auth_main.py @@ -15,6 +15,8 @@ class TestAuthenticationCommand: def setup_method(self): self.auth_command = AuthenticationCommand() + # TODO: these expectations are reading from the actual settings, we should mock them. + # E.g. if you change the client_id locally, this test will fail. @pytest.mark.parametrize( "user_provider,expected_urls", [ @@ -181,7 +183,7 @@ class TestAuthenticationCommand: ), call("Success!\n", style="bold green"), call( - "You are authenticated to the tool repository as [bold cyan]'Test Org'[/bold cyan] (test-uuid-123)", + "You are now authenticated to the tool repository for organization [bold cyan]'Test Org'[/bold cyan]", style="green", ), ] @@ -234,6 +236,7 @@ class TestAuthenticationCommand: "https://example.com/device" ) self.auth_command.oauth2_provider.get_audience.return_value = "test_audience" + self.auth_command.oauth2_provider.get_oauth_scopes.return_value = ["openid", "profile", "email"] result = self.auth_command._get_device_code() @@ -241,7 +244,7 @@ class TestAuthenticationCommand: url="https://example.com/device", data={ "client_id": "test_client", - "scope": "openid", + "scope": "openid profile email", "audience": "test_audience", }, timeout=20,