From 3fec4669af263f0a21a0258174ba5376370376ae Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:30:43 -0800 Subject: [PATCH 01/14] Lorenze/fix/anthropic available functions call (#4360) * feat: enhance AnthropicCompletion to support available functions in tool execution - Updated the `_prepare_completion_params` method to accept `available_functions` for better tool handling. - Modified tool execution logic to directly return results from tools when `available_functions` is provided, aligning behavior with OpenAI's model. - Added new test cases to validate the execution of tools with available functions, ensuring correct argument passing and result formatting. This change improves the flexibility and usability of the Anthropic LLM integration, allowing for more complex interactions with tools. * refactor: remove redundant event emission in AnthropicCompletion * fix test * dry up --- .../llms/providers/anthropic/completion.py | 96 ++++++--- ...xecution_returns_tool_result_directly.yaml | 102 +++++++++ ...ol_execution_with_available_functions.yaml | 108 ++++++++++ .../tests/llms/anthropic/test_anthropic.py | 198 +++++++++++------- 4 files changed, 393 insertions(+), 111 deletions(-) create mode 100644 lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_tool_execution_returns_tool_result_directly.yaml create mode 100644 lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_tool_execution_with_available_functions.yaml diff --git a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py index 815dfe763..657488098 100644 --- a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py +++ b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py @@ -290,7 +290,7 @@ class AnthropicCompletion(BaseLLM): # Prepare completion parameters completion_params = self._prepare_completion_params( - formatted_messages, system_message, tools + formatted_messages, system_message, tools, available_functions ) effective_response_model = response_model or self.response_format @@ -361,7 +361,7 @@ class AnthropicCompletion(BaseLLM): ) completion_params = self._prepare_completion_params( - formatted_messages, system_message, tools + formatted_messages, system_message, tools, available_functions ) effective_response_model = response_model or self.response_format @@ -396,6 +396,7 @@ class AnthropicCompletion(BaseLLM): messages: list[LLMMessage], system_message: str | None = None, tools: list[dict[str, Any]] | None = None, + available_functions: dict[str, Any] | None = None, ) -> dict[str, Any]: """Prepare parameters for Anthropic messages API. @@ -403,6 +404,8 @@ class AnthropicCompletion(BaseLLM): messages: Formatted messages for Anthropic system_message: Extracted system message tools: Tool definitions + available_functions: Available functions for tool calling. When provided + with a single tool, tool_choice is automatically set to force tool use. Returns: Parameters dictionary for Anthropic API @@ -428,7 +431,13 @@ class AnthropicCompletion(BaseLLM): # Handle tools for Claude 3+ if tools and self.supports_tools: - params["tools"] = self._convert_tools_for_interference(tools) + converted_tools = self._convert_tools_for_interference(tools) + params["tools"] = converted_tools + + if available_functions and len(converted_tools) == 1: + tool_name = converted_tools[0].get("name") + if tool_name and tool_name in available_functions: + params["tool_choice"] = {"type": "tool", "name": tool_name} if self.thinking: if isinstance(self.thinking, AnthropicThinkingConfig): @@ -730,15 +739,11 @@ class AnthropicCompletion(BaseLLM): ) return list(tool_uses) - # Handle tool use conversation flow internally - return self._handle_tool_use_conversation( - response, - tool_uses, - params, - available_functions, - from_task, - from_agent, + result = self._execute_first_tool( + tool_uses, available_functions, from_task, from_agent ) + if result is not None: + return result content = "" thinking_blocks: list[ThinkingBlock] = [] @@ -939,14 +944,12 @@ class AnthropicCompletion(BaseLLM): if not available_functions: return list(tool_uses) - return self._handle_tool_use_conversation( - final_message, - tool_uses, - params, - available_functions, - from_task, - from_agent, + # Execute first tool and return result directly + result = self._execute_first_tool( + tool_uses, available_functions, from_task, from_agent ) + if result is not None: + return result full_response = self._apply_stop_words(full_response) @@ -1005,6 +1008,41 @@ class AnthropicCompletion(BaseLLM): return tool_results + def _execute_first_tool( + self, + tool_uses: list[ToolUseBlock | BetaToolUseBlock], + available_functions: dict[str, Any], + from_task: Any | None = None, + from_agent: Any | None = None, + ) -> Any | None: + """Execute the first tool from the tool_uses list and return its result. + + This is used when available_functions is provided, to directly execute + the tool and return its result (matching OpenAI behavior for use cases + like reasoning_handler). + + Args: + tool_uses: List of tool use blocks from Claude's response + available_functions: Available functions for tool calling + from_task: Task that initiated the call + from_agent: Agent that initiated the call + + Returns: + The result of the first tool execution, or None if execution failed + """ + tool_use = tool_uses[0] + function_name = tool_use.name + function_args = cast(dict[str, Any], tool_use.input) + + return self._handle_tool_execution( + function_name=function_name, + function_args=function_args, + available_functions=available_functions, + from_task=from_task, + from_agent=from_agent, + ) + + # TODO: we drop this def _handle_tool_use_conversation( self, initial_response: Message | BetaMessage, @@ -1220,14 +1258,11 @@ class AnthropicCompletion(BaseLLM): ) return list(tool_uses) - return await self._ahandle_tool_use_conversation( - response, - tool_uses, - params, - available_functions, - from_task, - from_agent, + result = self._execute_first_tool( + tool_uses, available_functions, from_task, from_agent ) + if result is not None: + return result content = "" if response.content: @@ -1408,14 +1443,11 @@ class AnthropicCompletion(BaseLLM): if not available_functions: return list(tool_uses) - return await self._ahandle_tool_use_conversation( - final_message, - tool_uses, - params, - available_functions, - from_task, - from_agent, + result = self._execute_first_tool( + tool_uses, available_functions, from_task, from_agent ) + if result is not None: + return result full_response = self._apply_stop_words(full_response) diff --git a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_tool_execution_returns_tool_result_directly.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_tool_execution_returns_tool_result_directly.yaml new file mode 100644 index 000000000..96e4b687a --- /dev/null +++ b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_tool_execution_returns_tool_result_directly.yaml @@ -0,0 +1,102 @@ +interactions: +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Calculate 5 + + 3 using the simple_calculator tool with operation ''add''."}],"model":"claude-3-5-haiku-20241022","stream":false,"tool_choice":{"type":"tool","name":"simple_calculator"},"tools":[{"name":"simple_calculator","description":"Perform + simple math operations","input_schema":{"type":"object","properties":{"operation":{"type":"string","enum":["add","multiply"],"description":"The + operation to perform"},"a":{"type":"integer","description":"First number"},"b":{"type":"integer","description":"Second + number"}},"required":["operation","a","b"]}}]}' + headers: + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '608' + content-type: + - application/json + host: + - api.anthropic.com + user-agent: + - X-USER-AGENT-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-3-5-haiku-20241022","id":"msg_01Q2F83aAeqqTCxsd8WpZjK7","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01BW4XkHnhRVM5JZsvoaQKw5","name":"simple_calculator","input":{"operation":"add","a":5,"b":3}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":498,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":67,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 23:26:35 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2026-02-03T23:26:34Z' + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '1228' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_tool_execution_with_available_functions.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_tool_execution_with_available_functions.yaml new file mode 100644 index 000000000..78638ca0b --- /dev/null +++ b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_tool_execution_with_available_functions.yaml @@ -0,0 +1,108 @@ +interactions: +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Create a simple + plan to say hello. Use the create_reasoning_plan tool."}],"model":"claude-3-5-haiku-20241022","stream":false,"tool_choice":{"type":"tool","name":"create_reasoning_plan"},"tools":[{"name":"create_reasoning_plan","description":"Create + a structured reasoning plan for completing a task","input_schema":{"type":"object","properties":{"plan":{"type":"string","description":"High-level + plan description"},"steps":{"type":"array","items":{"type":"object"},"description":"List + of steps to execute"},"ready":{"type":"boolean","description":"Whether the plan + is ready to execute"}},"required":["plan","steps","ready"]}}]}' + headers: + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '684' + content-type: + - application/json + host: + - api.anthropic.com + user-agent: + - X-USER-AGENT-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-3-5-haiku-20241022","id":"msg_01HLuGgGRFseMdhTYAhkKtfz","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01GQAUFHffGzMd3ufA6YRMZF","name":"create_reasoning_plan","input":{"plan":"Say + hello in a friendly and straightforward manner","steps":[{"description":"Take + a deep breath","action":"Pause and relax"},{"description":"Smile","action":"Prepare + a warm facial expression"},{"description":"Greet the person","action":"Say + ''Hello!''"},{"description":"Wait for response","action":"Listen and be ready + to continue conversation"}],"ready":true}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":513,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":162,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 23:26:38 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2026-02-03T23:26:35Z' + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2994' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/llms/anthropic/test_anthropic.py b/lib/crewai/tests/llms/anthropic/test_anthropic.py index 8d4b69e20..c5ad5f273 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -45,85 +45,6 @@ def test_anthropic_completion_is_used_when_claude_provider(): -def test_anthropic_tool_use_conversation_flow(): - """ - Test that the Anthropic completion properly handles tool use conversation flow - """ - from unittest.mock import Mock, patch - from crewai.llms.providers.anthropic.completion import AnthropicCompletion - from anthropic.types.tool_use_block import ToolUseBlock - - # Create AnthropicCompletion instance - completion = AnthropicCompletion(model="claude-3-5-sonnet-20241022") - - # Mock tool function - def mock_weather_tool(location: str) -> str: - return f"The weather in {location} is sunny and 75°F" - - available_functions = {"get_weather": mock_weather_tool} - - # Mock the Anthropic client responses - with patch.object(completion.client.messages, 'create') as mock_create: - # Mock initial response with tool use - need to properly mock ToolUseBlock - mock_tool_use = Mock(spec=ToolUseBlock) - mock_tool_use.type = "tool_use" - mock_tool_use.id = "tool_123" - mock_tool_use.name = "get_weather" - mock_tool_use.input = {"location": "San Francisco"} - - mock_initial_response = Mock() - mock_initial_response.content = [mock_tool_use] - mock_initial_response.usage = Mock() - mock_initial_response.usage.input_tokens = 100 - mock_initial_response.usage.output_tokens = 50 - - # Mock final response after tool result - properly mock text content - mock_text_block = Mock() - mock_text_block.type = "text" - # Set the text attribute as a string, not another Mock - mock_text_block.configure_mock(text="Based on the weather data, it's a beautiful day in San Francisco with sunny skies and 75°F temperature.") - - mock_final_response = Mock() - mock_final_response.content = [mock_text_block] - mock_final_response.usage = Mock() - mock_final_response.usage.input_tokens = 150 - mock_final_response.usage.output_tokens = 75 - - # Configure mock to return different responses on successive calls - mock_create.side_effect = [mock_initial_response, mock_final_response] - - # Test the call - messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}] - result = completion.call( - messages=messages, - available_functions=available_functions - ) - - # Verify the result contains the final response - assert "beautiful day in San Francisco" in result - assert "sunny skies" in result - assert "75°F" in result - - # Verify that two API calls were made (initial + follow-up) - assert mock_create.call_count == 2 - - # Verify the second call includes tool results - second_call_args = mock_create.call_args_list[1][1] # kwargs of second call - messages_in_second_call = second_call_args["messages"] - - # Should have original user message + assistant tool use + user tool result - assert len(messages_in_second_call) == 3 - assert messages_in_second_call[0]["role"] == "user" - assert messages_in_second_call[1]["role"] == "assistant" - assert messages_in_second_call[2]["role"] == "user" - - # Verify tool result format - tool_result = messages_in_second_call[2]["content"][0] - assert tool_result["type"] == "tool_result" - assert tool_result["tool_use_id"] == "tool_123" - assert "sunny and 75°F" in tool_result["content"] - - def test_anthropic_completion_module_is_imported(): """ Test that the completion module is properly imported when using Anthropic provider @@ -874,6 +795,125 @@ def test_anthropic_function_calling(): # ============================================================================= +@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"]) +def test_anthropic_tool_execution_with_available_functions(): + """ + Test that Anthropic provider correctly executes tools when available_functions is provided. + + This specifically tests the fix for double llm_call_completed emission - when + available_functions is provided, _handle_tool_execution is called which already + emits llm_call_completed, so the caller should not emit it again. + + The test verifies: + 1. The tool is called with correct arguments + 2. The tool result is returned directly (not wrapped in conversation) + 3. The result is valid JSON matching the tool output format + """ + import json + + llm = LLM(model="anthropic/claude-3-5-haiku-20241022") + + # Simple tool that returns a formatted string + def create_reasoning_plan(plan: str, steps: list, ready: bool) -> str: + """Create a reasoning plan with steps.""" + return json.dumps({"plan": plan, "steps": steps, "ready": ready}) + + tools = [ + { + "name": "create_reasoning_plan", + "description": "Create a structured reasoning plan for completing a task", + "input_schema": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "High-level plan description" + }, + "steps": { + "type": "array", + "items": {"type": "object"}, + "description": "List of steps to execute" + }, + "ready": { + "type": "boolean", + "description": "Whether the plan is ready to execute" + } + }, + "required": ["plan", "steps", "ready"] + } + } + ] + + result = llm.call( + messages=[{"role": "user", "content": "Create a simple plan to say hello. Use the create_reasoning_plan tool."}], + tools=tools, + available_functions={"create_reasoning_plan": create_reasoning_plan} + ) + + # Verify result is valid JSON from the tool + assert result is not None + assert isinstance(result, str) + + # Parse the result to verify it's valid JSON + parsed_result = json.loads(result) + assert "plan" in parsed_result + assert "steps" in parsed_result + assert "ready" in parsed_result + + +@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"]) +def test_anthropic_tool_execution_returns_tool_result_directly(): + """ + Test that when available_functions is provided, the tool result is returned directly + without additional LLM conversation (matching OpenAI behavior for reasoning_handler). + """ + llm = LLM(model="anthropic/claude-3-5-haiku-20241022") + + call_count = 0 + + def simple_calculator(operation: str, a: int, b: int) -> str: + """Perform a simple calculation.""" + nonlocal call_count + call_count += 1 + if operation == "add": + return str(a + b) + elif operation == "multiply": + return str(a * b) + return "Unknown operation" + + tools = [ + { + "name": "simple_calculator", + "description": "Perform simple math operations", + "input_schema": { + "type": "object", + "properties": { + "operation": { + "type": "string", + "enum": ["add", "multiply"], + "description": "The operation to perform" + }, + "a": {"type": "integer", "description": "First number"}, + "b": {"type": "integer", "description": "Second number"} + }, + "required": ["operation", "a", "b"] + } + } + ] + + result = llm.call( + messages=[{"role": "user", "content": "Calculate 5 + 3 using the simple_calculator tool with operation 'add'."}], + tools=tools, + available_functions={"simple_calculator": simple_calculator} + ) + + # Tool should have been called exactly once + assert call_count == 1, f"Expected tool to be called once, got {call_count}" + + # Result should be the direct tool output + assert result == "8", f"Expected '8' but got '{result}'" + + @pytest.mark.vcr() def test_anthropic_agent_kickoff_structured_output_without_tools(): """ From 3cc33ef6ab28725b26a7de4a866ec3b9d5b5d3cb Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Tue, 3 Feb 2026 20:47:58 -0500 Subject: [PATCH 02/14] fix: resolve complex schema $ref pointers in mcp tools * fix: resolve complex schema $ref pointers in mcp tools * chore: update tool specifications * fix: adapt mcp tools; sanitize pydantic json schemas * fix: strip nulls from json schemas and simplify mcp args --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../src/crewai_tools/adapters/mcp_adapter.py | 116 ++++++-- lib/crewai-tools/tool.specs.json | 257 ++++++++++++------ .../crewai/utilities/pydantic_schema_utils.py | 149 +++++++++- 3 files changed, 411 insertions(+), 111 deletions(-) diff --git a/lib/crewai-tools/src/crewai_tools/adapters/mcp_adapter.py b/lib/crewai-tools/src/crewai_tools/adapters/mcp_adapter.py index edfb222a3..3bd91f164 100644 --- a/lib/crewai-tools/src/crewai_tools/adapters/mcp_adapter.py +++ b/lib/crewai-tools/src/crewai_tools/adapters/mcp_adapter.py @@ -2,29 +2,95 @@ from __future__ import annotations +from collections.abc import Callable import logging from typing import TYPE_CHECKING, Any from crewai.tools import BaseTool +from crewai.utilities.pydantic_schema_utils import create_model_from_schema +from crewai.utilities.string_utils import sanitize_tool_name +from pydantic import BaseModel from crewai_tools.adapters.tool_collection import ToolCollection -logger = logging.getLogger(__name__) - if TYPE_CHECKING: from mcp import StdioServerParameters - from mcpadapt.core import MCPAdapt - from mcpadapt.crewai_adapter import CrewAIAdapter + from mcp.types import CallToolResult, TextContent, Tool + from mcpadapt.core import MCPAdapt, ToolAdapter + + +logger = logging.getLogger(__name__) try: from mcp import StdioServerParameters - from mcpadapt.core import MCPAdapt - from mcpadapt.crewai_adapter import CrewAIAdapter + from mcp.types import CallToolResult, TextContent, Tool + from mcpadapt.core import MCPAdapt, ToolAdapter + + class CrewAIToolAdapter(ToolAdapter): + """Adapter that creates CrewAI tools with properly normalized JSON schemas. + + This adapter bypasses mcpadapt's model creation which adds invalid null values + to field schemas, instead using CrewAI's own schema utilities. + """ + + def adapt( + self, + func: Callable[[dict[str, Any] | None], CallToolResult], + mcp_tool: Tool, + ) -> BaseTool: + """Adapt a MCP tool to a CrewAI tool. + + Args: + func: The function to call when the tool is invoked. + mcp_tool: The MCP tool definition to adapt. + + Returns: + A CrewAI BaseTool instance. + """ + tool_name = sanitize_tool_name(mcp_tool.name) + tool_description = mcp_tool.description or "" + args_model = create_model_from_schema(mcp_tool.inputSchema) + + class CrewAIMCPTool(BaseTool): + name: str = tool_name + description: str = tool_description + args_schema: type[BaseModel] = args_model + + def _run(self, **kwargs: Any) -> Any: + result = func(kwargs) + if len(result.content) == 1: + first_content = result.content[0] + if isinstance(first_content, TextContent): + return first_content.text + return str(first_content) + return str( + [ + content.text + for content in result.content + if isinstance(content, TextContent) + ] + ) + + def _generate_description(self) -> None: + schema = self.args_schema.model_json_schema() + schema.pop("$defs", None) + self.description = ( + f"Tool Name: {self.name}\n" + f"Tool Arguments: {schema}\n" + f"Tool Description: {self.description}" + ) + + return CrewAIMCPTool() + + async def async_adapt(self, afunc: Any, mcp_tool: Tool) -> Any: + """Async adaptation is not supported by CrewAI.""" + raise NotImplementedError("async is not supported by the CrewAI framework.") MCP_AVAILABLE = True -except ImportError: +except ImportError as e: + logger.debug(f"MCP packages not available: {e}") MCP_AVAILABLE = False @@ -34,9 +100,6 @@ class MCPServerAdapter: Note: tools can only be accessed after the server has been started with the `start()` method. - Attributes: - tools: The CrewAI tools available from the MCP server. - Usage: # context manager + stdio with MCPServerAdapter(...) as tools: @@ -89,7 +152,9 @@ class MCPServerAdapter: super().__init__() self._adapter = None self._tools = None - self._tool_names = list(tool_names) if tool_names else None + self._tool_names = ( + [sanitize_tool_name(name) for name in tool_names] if tool_names else None + ) if not MCP_AVAILABLE: import click @@ -100,7 +165,7 @@ class MCPServerAdapter: import subprocess try: - subprocess.run(["uv", "add", "mcp crewai-tools[mcp]"], check=True) # noqa: S607 + subprocess.run(["uv", "add", "mcp crewai-tools'[mcp]'"], check=True) # noqa: S607 except subprocess.CalledProcessError as e: raise ImportError("Failed to install mcp package") from e @@ -112,7 +177,7 @@ class MCPServerAdapter: try: self._serverparams = serverparams self._adapter = MCPAdapt( - self._serverparams, CrewAIAdapter(), connect_timeout + self._serverparams, CrewAIToolAdapter(), connect_timeout ) self.start() @@ -124,13 +189,13 @@ class MCPServerAdapter: logger.error(f"Error during stop cleanup: {stop_e}") raise RuntimeError(f"Failed to initialize MCP Adapter: {e}") from e - def start(self): + def start(self) -> None: """Start the MCP server and initialize the tools.""" - self._tools = self._adapter.__enter__() + self._tools = self._adapter.__enter__() # type: ignore[union-attr] - def stop(self): + def stop(self) -> None: """Stop the MCP server.""" - self._adapter.__exit__(None, None, None) + self._adapter.__exit__(None, None, None) # type: ignore[union-attr] @property def tools(self) -> ToolCollection[BaseTool]: @@ -152,12 +217,19 @@ class MCPServerAdapter: return tools_collection.filter_by_names(self._tool_names) return tools_collection - def __enter__(self): - """Enter the context manager. Note that `__init__()` already starts the MCP server. - So tools should already be available. + def __enter__(self) -> ToolCollection[BaseTool]: + """Enter the context manager. + + Note that `__init__()` already starts the MCP server, + so tools should already be available. """ return self.tools - def __exit__(self, exc_type, exc_value, traceback): + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: Any, + ) -> None: """Exit the context manager.""" - return self._adapter.__exit__(exc_type, exc_value, traceback) + self._adapter.__exit__(exc_type, exc_value, traceback) # type: ignore[union-attr] diff --git a/lib/crewai-tools/tool.specs.json b/lib/crewai-tools/tool.specs.json index 74e5ace4e..1e32b2d6c 100644 --- a/lib/crewai-tools/tool.specs.json +++ b/lib/crewai-tools/tool.specs.json @@ -197,7 +197,7 @@ } }, { - "description": "A tool that can be used to search the internet with a search_query.", + "description": "A tool that performs web searches using the Brave Search API. Results are returned as structured JSON data.", "env_vars": [ { "default": null, @@ -206,7 +206,7 @@ "required": true } ], - "humanized_name": "Brave Web Search the internet", + "humanized_name": "Brave Search", "init_params_schema": { "$defs": { "EnvVar": { @@ -245,20 +245,8 @@ "type": "object" } }, - "description": "BraveSearchTool - A tool for performing web searches using the Brave Search API.\n\nThis module provides functionality to search the internet using Brave's Search API,\nsupporting customizable result counts and country-specific searches.\n\nDependencies:\n - requests\n - pydantic\n - python-dotenv (for API key management)", + "description": "A tool that performs web searches using the Brave Search API.", "properties": { - "country": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "", - "title": "Country" - }, "n_results": { "default": 10, "title": "N Results", @@ -281,16 +269,161 @@ "name": "BraveSearchTool", "package_dependencies": [], "run_params_schema": { - "description": "Input for BraveSearchTool.", + "description": "Input for BraveSearchTool", "properties": { - "search_query": { - "description": "Mandatory search query you want to use to search the internet", - "title": "Search Query", + "count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The maximum number of results to return. Actual number may be less.", + "title": "Count" + }, + "country": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Country code for geo-targeting (e.g., 'US', 'BR').", + "title": "Country" + }, + "extra_snippets": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Include up to 5 text snippets for each page if possible.", + "title": "Extra Snippets" + }, + "freshness": { + "anyOf": [ + { + "enum": [ + "pd", + "pw", + "pm", + "py" + ], + "type": "string" + }, + { + "pattern": "^\\d{4}-\\d{2}-\\d{2}to\\d{4}-\\d{2}-\\d{2}$", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Enforce freshness of results. Options: pd/pw/pm/py, or YYYY-MM-DDtoYYYY-MM-DD", + "title": "Freshness" + }, + "offset": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Skip the first N result sets/pages. Max is 9.", + "title": "Offset" + }, + "operators": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Whether to apply search operators (e.g., site:example.com).", + "title": "Operators" + }, + "query": { + "description": "Search query to perform", + "title": "Query", "type": "string" + }, + "safesearch": { + "anyOf": [ + { + "enum": [ + "off", + "moderate", + "strict" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Filter out explicit content. Options: off/moderate/strict", + "title": "Safesearch" + }, + "search_language": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Language code for the search results (e.g., 'en', 'es').", + "title": "Search Language" + }, + "spellcheck": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Attempt to correct spelling errors in the search query.", + "title": "Spellcheck" + }, + "text_decorations": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Include markup to highlight search terms in the results.", + "title": "Text Decorations" } }, "required": [ - "search_query" + "query" ], "title": "BraveSearchToolSchema", "type": "object" @@ -3741,10 +3874,6 @@ "title": "Bucket Name", "type": "string" }, - "cluster": { - "description": "An instance of the Couchbase Cluster connected to the desired Couchbase server.", - "title": "Cluster" - }, "collection_name": { "description": "The name of the Couchbase collection to search", "title": "Collection Name", @@ -3793,7 +3922,6 @@ } }, "required": [ - "cluster", "collection_name", "scope_name", "bucket_name", @@ -12537,13 +12665,9 @@ "properties": { "config": { "$ref": "#/$defs/OxylabsAmazonProductScraperConfig" - }, - "oxylabs_api": { - "title": "Oxylabs Api" } }, "required": [ - "oxylabs_api", "config" ], "title": "OxylabsAmazonProductScraperTool", @@ -12766,13 +12890,9 @@ "properties": { "config": { "$ref": "#/$defs/OxylabsAmazonSearchScraperConfig" - }, - "oxylabs_api": { - "title": "Oxylabs Api" } }, "required": [ - "oxylabs_api", "config" ], "title": "OxylabsAmazonSearchScraperTool", @@ -13008,13 +13128,9 @@ "properties": { "config": { "$ref": "#/$defs/OxylabsGoogleSearchScraperConfig" - }, - "oxylabs_api": { - "title": "Oxylabs Api" } }, "required": [ - "oxylabs_api", "config" ], "title": "OxylabsGoogleSearchScraperTool", @@ -13198,13 +13314,9 @@ "properties": { "config": { "$ref": "#/$defs/OxylabsUniversalScraperConfig" - }, - "oxylabs_api": { - "title": "Oxylabs Api" } }, "required": [ - "oxylabs_api", "config" ], "title": "OxylabsUniversalScraperTool", @@ -20005,6 +20117,18 @@ "humanized_name": "Web Automation Tool", "init_params_schema": { "$defs": { + "AvailableModel": { + "enum": [ + "gpt-4o", + "gpt-4o-mini", + "claude-3-5-sonnet-latest", + "claude-3-7-sonnet-latest", + "computer-use-preview", + "gemini-2.0-flash" + ], + "title": "AvailableModel", + "type": "string" + }, "EnvVar": { "properties": { "default": { @@ -20082,6 +20206,17 @@ "default": null, "title": "Model Api Key" }, + "model_name": { + "anyOf": [ + { + "$ref": "#/$defs/AvailableModel" + }, + { + "type": "null" + } + ], + "default": "claude-3-7-sonnet-latest" + }, "project_id": { "anyOf": [ { @@ -21306,26 +21441,6 @@ "description": "The Tavily API key. If not provided, it will be loaded from the environment variable TAVILY_API_KEY.", "title": "Api Key" }, - "async_client": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Async Client" - }, - "client": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Client" - }, "extract_depth": { "default": "basic", "description": "The depth of extraction. 'basic' for basic extraction, 'advanced' for advanced extraction.", @@ -21461,26 +21576,6 @@ "description": "The Tavily API key. If not provided, it will be loaded from the environment variable TAVILY_API_KEY.", "title": "Api Key" }, - "async_client": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Async Client" - }, - "client": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "default": null, - "title": "Client" - }, "days": { "default": 7, "description": "The number of days to search back.", diff --git a/lib/crewai/src/crewai/utilities/pydantic_schema_utils.py b/lib/crewai/src/crewai/utilities/pydantic_schema_utils.py index 2b50caea8..191f38c35 100644 --- a/lib/crewai/src/crewai/utilities/pydantic_schema_utils.py +++ b/lib/crewai/src/crewai/utilities/pydantic_schema_utils.py @@ -19,9 +19,10 @@ from collections.abc import Callable from copy import deepcopy import datetime import logging -from typing import TYPE_CHECKING, Annotated, Any, Literal, Union +from typing import TYPE_CHECKING, Annotated, Any, Final, Literal, TypedDict, Union import uuid +import jsonref # type: ignore[import-untyped] from pydantic import ( UUID1, UUID3, @@ -69,6 +70,21 @@ else: EmailStr = str +class JsonSchemaInfo(TypedDict): + """Inner structure for JSON schema metadata.""" + + name: str + strict: Literal[True] + schema: dict[str, Any] + + +class ModelDescription(TypedDict): + """Return type for generate_model_description.""" + + type: Literal["json_schema"] + json_schema: JsonSchemaInfo + + def resolve_refs(schema: dict[str, Any]) -> dict[str, Any]: """Recursively resolve all local $refs in the given JSON Schema using $defs as the source. @@ -157,6 +173,72 @@ def force_additional_properties_false(d: Any) -> Any: return d +OPENAI_SUPPORTED_FORMATS: Final[ + set[Literal["date-time", "date", "time", "duration"]] +] = { + "date-time", + "date", + "time", + "duration", +} + + +def strip_unsupported_formats(d: Any) -> Any: + """Remove format annotations that OpenAI strict mode doesn't support. + + OpenAI only supports: date-time, date, time, duration. + Other formats like uri, email, uuid etc. cause validation errors. + + Args: + d: The dictionary/list to modify. + + Returns: + The modified dictionary/list. + """ + if isinstance(d, dict): + format_value = d.get("format") + if ( + isinstance(format_value, str) + and format_value not in OPENAI_SUPPORTED_FORMATS + ): + del d["format"] + for v in d.values(): + strip_unsupported_formats(v) + elif isinstance(d, list): + for i in d: + strip_unsupported_formats(i) + return d + + +def ensure_type_in_schemas(d: Any) -> Any: + """Ensure all schema objects in anyOf/oneOf have a 'type' key. + + OpenAI strict mode requires every schema to have a 'type' key. + Empty schemas {} in anyOf/oneOf are converted to {"type": "object"}. + + Args: + d: The dictionary/list to modify. + + Returns: + The modified dictionary/list. + """ + if isinstance(d, dict): + for key in ("anyOf", "oneOf"): + if key in d: + schema_list = d[key] + for i, schema in enumerate(schema_list): + if isinstance(schema, dict) and schema == {}: + schema_list[i] = {"type": "object"} + else: + ensure_type_in_schemas(schema) + for v in d.values(): + ensure_type_in_schemas(v) + elif isinstance(d, list): + for item in d: + ensure_type_in_schemas(item) + return d + + def fix_discriminator_mappings(schema: dict[str, Any]) -> dict[str, Any]: """Replace '#/$defs/...' references in discriminator.mapping with just the model name. @@ -293,7 +375,49 @@ def ensure_all_properties_required(schema: dict[str, Any]) -> dict[str, Any]: return schema -def generate_model_description(model: type[BaseModel]) -> dict[str, Any]: +def strip_null_from_types(schema: dict[str, Any]) -> dict[str, Any]: + """Remove null type from anyOf/type arrays. + + Pydantic generates `T | None` for optional fields, which creates schemas with + null in the type. However, for MCP tools, optional fields should be omitted + entirely rather than sent as null. This function strips null from types. + + Args: + schema: JSON schema dictionary. + + Returns: + Modified schema with null types removed. + """ + if isinstance(schema, dict): + if "anyOf" in schema: + any_of = schema["anyOf"] + non_null = [opt for opt in any_of if opt.get("type") != "null"] + if len(non_null) == 1: + schema.pop("anyOf") + schema.update(non_null[0]) + elif len(non_null) > 1: + schema["anyOf"] = non_null + + type_value = schema.get("type") + if isinstance(type_value, list) and "null" in type_value: + non_null_types = [t for t in type_value if t != "null"] + if len(non_null_types) == 1: + schema["type"] = non_null_types[0] + elif len(non_null_types) > 1: + schema["type"] = non_null_types + + for value in schema.values(): + if isinstance(value, dict): + strip_null_from_types(value) + elif isinstance(value, list): + for item in value: + if isinstance(item, dict): + strip_null_from_types(item) + + return schema + + +def generate_model_description(model: type[BaseModel]) -> ModelDescription: """Generate JSON schema description of a Pydantic model. This function takes a Pydantic model class and returns its JSON schema, @@ -304,11 +428,13 @@ def generate_model_description(model: type[BaseModel]) -> dict[str, Any]: model: A Pydantic model class. Returns: - A JSON schema dictionary representation of the model. + A ModelDescription with JSON schema representation of the model. """ json_schema = model.model_json_schema(ref_template="#/$defs/{model}") json_schema = force_additional_properties_false(json_schema) + json_schema = strip_unsupported_formats(json_schema) + json_schema = ensure_type_in_schemas(json_schema) json_schema = resolve_refs(json_schema) @@ -316,6 +442,7 @@ def generate_model_description(model: type[BaseModel]) -> dict[str, Any]: json_schema = fix_discriminator_mappings(json_schema) json_schema = convert_oneof_to_anyof(json_schema) json_schema = ensure_all_properties_required(json_schema) + json_schema = strip_null_from_types(json_schema) return { "type": "json_schema", @@ -400,6 +527,8 @@ def create_model_from_schema( # type: ignore[no-any-unimported] >>> person.name 'John' """ + json_schema = dict(jsonref.replace_refs(json_schema, proxies=False)) + effective_root = root_schema or json_schema json_schema = force_additional_properties_false(json_schema) @@ -410,7 +539,7 @@ def create_model_from_schema( # type: ignore[no-any-unimported] if "title" not in json_schema and "title" in (root_schema or {}): json_schema["title"] = (root_schema or {}).get("title") - model_name = json_schema.get("title", "DynamicModel") + model_name = json_schema.get("title") or "DynamicModel" field_definitions = { name: _json_schema_to_pydantic_field( name, prop, json_schema.get("required", []), effective_root @@ -418,9 +547,11 @@ def create_model_from_schema( # type: ignore[no-any-unimported] for name, prop in (json_schema.get("properties", {}) or {}).items() } + effective_config = __config__ or ConfigDict(extra="forbid") + return create_model_base( model_name, - __config__=__config__, + __config__=effective_config, __base__=__base__, __module__=__module__, __validators__=__validators__, @@ -599,8 +730,10 @@ def _json_schema_to_pydantic_type( any_of_schemas = json_schema.get("anyOf", []) + json_schema.get("oneOf", []) if any_of_schemas: any_of_types = [ - _json_schema_to_pydantic_type(schema, root_schema) - for schema in any_of_schemas + _json_schema_to_pydantic_type( + schema, root_schema, name_=f"{name_ or 'Union'}Option{i}" + ) + for i, schema in enumerate(any_of_schemas) ] return Union[tuple(any_of_types)] # noqa: UP007 @@ -636,7 +769,7 @@ def _json_schema_to_pydantic_type( if properties: json_schema_ = json_schema.copy() if json_schema_.get("title") is None: - json_schema_["title"] = name_ + json_schema_["title"] = name_ or "DynamicModel" return create_model_from_schema(json_schema_, root_schema=root_schema) return dict if type_ == "null": From 6bfc98e960fd6bd78978d9131005d2eb2b5cb7e5 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Wed, 4 Feb 2026 15:40:22 -0500 Subject: [PATCH 03/14] refactor: extract hitl to provider pattern * refactor: extract hitl to provider pattern - add humaninputprovider protocol with setup_messages and handle_feedback - move sync hitl logic from executor to synchuman inputprovider - add _passthrough_exceptions extension point in agent/core.py - create crewai.core.providers module for extensible components - remove _ask_human_input from base_agent_executor_mixin --- lib/crewai/src/crewai/agent/core.py | 6 + .../base_agent_executor_mixin.py | 50 --- .../src/crewai/agents/crew_agent_executor.py | 121 ++----- lib/crewai/src/crewai/core/__init__.py | 1 + .../src/crewai/core/providers/__init__.py | 1 + .../core/providers/content_processor.py | 78 +++++ .../src/crewai/core/providers/human_input.py | 304 ++++++++++++++++++ lib/crewai/src/crewai/task.py | 20 +- lib/crewai/tests/agents/test_agent.py | 18 +- .../test_flow_human_input_integration.py | 57 ++-- 10 files changed, 465 insertions(+), 191 deletions(-) create mode 100644 lib/crewai/src/crewai/core/__init__.py create mode 100644 lib/crewai/src/crewai/core/providers/__init__.py create mode 100644 lib/crewai/src/crewai/core/providers/content_processor.py create mode 100644 lib/crewai/src/crewai/core/providers/human_input.py diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 6c2626a28..47eb841b4 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -118,6 +118,8 @@ MCP_TOOL_EXECUTION_TIMEOUT: Final[int] = 30 MCP_DISCOVERY_TIMEOUT: Final[int] = 15 MCP_MAX_RETRIES: Final[int] = 3 +_passthrough_exceptions: tuple[type[Exception], ...] = () + # Simple in-memory cache for MCP tool schemas (duration: 5 minutes) _mcp_schema_cache: dict[str, Any] = {} _cache_ttl: Final[int] = 300 # 5 minutes @@ -479,6 +481,8 @@ class Agent(BaseAgent): ), ) raise e + if isinstance(e, _passthrough_exceptions): + raise self._times_executed += 1 if self._times_executed > self.max_retry_limit: crewai_event_bus.emit( @@ -711,6 +715,8 @@ class Agent(BaseAgent): ), ) raise e + if isinstance(e, _passthrough_exceptions): + raise self._times_executed += 1 if self._times_executed > self.max_retry_limit: crewai_event_bus.emit( diff --git a/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py b/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py index c9dceaa84..03787c802 100644 --- a/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py +++ b/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py @@ -4,7 +4,6 @@ import time from typing import TYPE_CHECKING from crewai.agents.parser import AgentFinish -from crewai.events.event_listener import event_listener from crewai.memory.entity.entity_memory_item import EntityMemoryItem from crewai.memory.long_term.long_term_memory_item import LongTermMemoryItem from crewai.utilities.converter import ConverterError @@ -138,52 +137,3 @@ class CrewAgentExecutorMixin: content="Long term memory is enabled, but entity memory is not enabled. Please configure entity memory or set memory=True to automatically enable it.", color="bold_yellow", ) - - def _ask_human_input(self, final_answer: str) -> str: - """Prompt human input with mode-appropriate messaging. - - Note: The final answer is already displayed via the AgentLogsExecutionEvent - panel, so we only show the feedback prompt here. - """ - from rich.panel import Panel - from rich.text import Text - - formatter = event_listener.formatter - formatter.pause_live_updates() - - try: - # Training mode prompt (single iteration) - if self.crew and getattr(self.crew, "_train", False): - prompt_text = ( - "TRAINING MODE: Provide feedback to improve the agent's performance.\n\n" - "This will be used to train better versions of the agent.\n" - "Please provide detailed feedback about the result quality and reasoning process." - ) - title = "🎓 Training Feedback Required" - # Regular human-in-the-loop prompt (multiple iterations) - else: - prompt_text = ( - "Provide feedback on the Final Result above.\n\n" - "• If you are happy with the result, simply hit Enter without typing anything.\n" - "• Otherwise, provide specific improvement requests.\n" - "• You can provide multiple rounds of feedback until satisfied." - ) - title = "💬 Human Feedback Required" - - content = Text() - content.append(prompt_text, style="yellow") - - prompt_panel = Panel( - content, - title=title, - border_style="yellow", - padding=(1, 2), - ) - formatter.console.print(prompt_panel) - - response = input() - if response.strip() != "": - formatter.console.print("\n[cyan]Processing your feedback...[/cyan]") - return response - finally: - formatter.resume_live_updates() diff --git a/lib/crewai/src/crewai/agents/crew_agent_executor.py b/lib/crewai/src/crewai/agents/crew_agent_executor.py index 1218ceae8..c0f24516a 100644 --- a/lib/crewai/src/crewai/agents/crew_agent_executor.py +++ b/lib/crewai/src/crewai/agents/crew_agent_executor.py @@ -19,6 +19,7 @@ from crewai.agents.parser import ( AgentFinish, OutputParserError, ) +from crewai.core.providers.human_input import ExecutorContext, get_provider from crewai.events.event_bus import crewai_event_bus from crewai.events.types.logging_events import ( AgentLogsExecutionEvent, @@ -175,15 +176,16 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): """ return self.llm.supports_stop_words() if self.llm else False - def invoke(self, inputs: dict[str, Any]) -> dict[str, Any]: - """Execute the agent with given inputs. + def _setup_messages(self, inputs: dict[str, Any]) -> None: + """Set up messages for the agent execution. Args: inputs: Input dictionary containing prompt variables. - - Returns: - Dictionary with agent output. """ + provider = get_provider() + if provider.setup_messages(cast(ExecutorContext, cast(object, self))): + return + if "system" in self.prompt: system_prompt = self._format_prompt( cast(str, self.prompt.get("system", "")), inputs @@ -197,6 +199,19 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): user_prompt = self._format_prompt(self.prompt.get("prompt", ""), inputs) self.messages.append(format_message_for_llm(user_prompt)) + provider.post_setup_messages(cast(ExecutorContext, cast(object, self))) + + def invoke(self, inputs: dict[str, Any]) -> dict[str, Any]: + """Execute the agent with given inputs. + + Args: + inputs: Input dictionary containing prompt variables. + + Returns: + Dictionary with agent output. + """ + self._setup_messages(inputs) + self._inject_multimodal_files(inputs) self._show_start_logs() @@ -970,18 +985,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): Returns: Dictionary with agent output. """ - if "system" in self.prompt: - system_prompt = self._format_prompt( - cast(str, self.prompt.get("system", "")), inputs - ) - user_prompt = self._format_prompt( - cast(str, self.prompt.get("user", "")), inputs - ) - self.messages.append(format_message_for_llm(system_prompt, role="system")) - self.messages.append(format_message_for_llm(user_prompt)) - else: - user_prompt = self._format_prompt(self.prompt.get("prompt", ""), inputs) - self.messages.append(format_message_for_llm(user_prompt)) + self._setup_messages(inputs) await self._ainject_multimodal_files(inputs) @@ -1491,7 +1495,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): return prompt.replace("{tools}", inputs["tools"]) def _handle_human_feedback(self, formatted_answer: AgentFinish) -> AgentFinish: - """Process human feedback. + """Process human feedback via the configured provider. Args: formatted_answer: Initial agent result. @@ -1499,17 +1503,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): Returns: Final answer after feedback. """ - output_str = ( - formatted_answer.output - if isinstance(formatted_answer.output, str) - else formatted_answer.output.model_dump_json() - ) - human_feedback = self._ask_human_input(output_str) - - if self._is_training_mode(): - return self._handle_training_feedback(formatted_answer, human_feedback) - - return self._handle_regular_feedback(formatted_answer, human_feedback) + provider = get_provider() + return provider.handle_feedback(formatted_answer, self) def _is_training_mode(self) -> bool: """Check if training mode is active. @@ -1519,74 +1514,18 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): """ return bool(self.crew and self.crew._train) - def _handle_training_feedback( - self, initial_answer: AgentFinish, feedback: str - ) -> AgentFinish: - """Process training feedback. + def _format_feedback_message(self, feedback: str) -> LLMMessage: + """Format feedback as a message for the LLM. Args: - initial_answer: Initial agent output. - feedback: Training feedback. + feedback: User feedback string. Returns: - Improved answer. + Formatted message dict. """ - self._handle_crew_training_output(initial_answer, feedback) - self.messages.append( - format_message_for_llm( - self._i18n.slice("feedback_instructions").format(feedback=feedback) - ) + return format_message_for_llm( + self._i18n.slice("feedback_instructions").format(feedback=feedback) ) - improved_answer = self._invoke_loop() - self._handle_crew_training_output(improved_answer) - self.ask_for_human_input = False - return improved_answer - - def _handle_regular_feedback( - self, current_answer: AgentFinish, initial_feedback: str - ) -> AgentFinish: - """Process regular feedback iteratively. - - Args: - current_answer: Current agent output. - initial_feedback: Initial user feedback. - - Returns: - Final answer after iterations. - """ - feedback = initial_feedback - answer = current_answer - - while self.ask_for_human_input: - # If the user provides a blank response, assume they are happy with the result - if feedback.strip() == "": - self.ask_for_human_input = False - else: - answer = self._process_feedback_iteration(feedback) - output_str = ( - answer.output - if isinstance(answer.output, str) - else answer.output.model_dump_json() - ) - feedback = self._ask_human_input(output_str) - - return answer - - def _process_feedback_iteration(self, feedback: str) -> AgentFinish: - """Process single feedback iteration. - - Args: - feedback: User feedback. - - Returns: - Updated agent response. - """ - self.messages.append( - format_message_for_llm( - self._i18n.slice("feedback_instructions").format(feedback=feedback) - ) - ) - return self._invoke_loop() @classmethod def __get_pydantic_core_schema__( diff --git a/lib/crewai/src/crewai/core/__init__.py b/lib/crewai/src/crewai/core/__init__.py new file mode 100644 index 000000000..714ed9161 --- /dev/null +++ b/lib/crewai/src/crewai/core/__init__.py @@ -0,0 +1 @@ +"""Core crewAI components and interfaces.""" diff --git a/lib/crewai/src/crewai/core/providers/__init__.py b/lib/crewai/src/crewai/core/providers/__init__.py new file mode 100644 index 000000000..fc0a9ed4b --- /dev/null +++ b/lib/crewai/src/crewai/core/providers/__init__.py @@ -0,0 +1 @@ +"""Provider interfaces for extensible crewAI components.""" diff --git a/lib/crewai/src/crewai/core/providers/content_processor.py b/lib/crewai/src/crewai/core/providers/content_processor.py new file mode 100644 index 000000000..828a7e311 --- /dev/null +++ b/lib/crewai/src/crewai/core/providers/content_processor.py @@ -0,0 +1,78 @@ +"""Content processor provider for extensible content processing.""" + +from contextvars import ContextVar +from typing import Any, Protocol, runtime_checkable + + +@runtime_checkable +class ContentProcessorProvider(Protocol): + """Protocol for content processing during task execution.""" + + def process(self, content: str, context: dict[str, Any] | None = None) -> str: + """Process content before use. + + Args: + content: The content to process. + context: Optional context information. + + Returns: + The processed content. + """ + ... + + +class NoOpContentProcessor: + """Default processor that returns content unchanged.""" + + def process(self, content: str, context: dict[str, Any] | None = None) -> str: + """Return content unchanged. + + Args: + content: The content to process. + context: Optional context information (unused). + + Returns: + The original content unchanged. + """ + return content + + +_content_processor: ContextVar[ContentProcessorProvider | None] = ContextVar( + "_content_processor", default=None +) + +_default_processor = NoOpContentProcessor() + + +def get_processor() -> ContentProcessorProvider: + """Get the current content processor. + + Returns: + The registered content processor or the default no-op processor. + """ + processor = _content_processor.get() + if processor is not None: + return processor + return _default_processor + + +def set_processor(processor: ContentProcessorProvider) -> None: + """Set the content processor for the current context. + + Args: + processor: The content processor to use. + """ + _content_processor.set(processor) + + +def process_content(content: str, context: dict[str, Any] | None = None) -> str: + """Process content using the registered processor. + + Args: + content: The content to process. + context: Optional context information. + + Returns: + The processed content. + """ + return get_processor().process(content, context) diff --git a/lib/crewai/src/crewai/core/providers/human_input.py b/lib/crewai/src/crewai/core/providers/human_input.py new file mode 100644 index 000000000..4062e6bb9 --- /dev/null +++ b/lib/crewai/src/crewai/core/providers/human_input.py @@ -0,0 +1,304 @@ +"""Human input provider for HITL (Human-in-the-Loop) flows.""" + +from __future__ import annotations + +from contextvars import ContextVar, Token +from typing import TYPE_CHECKING, Protocol, runtime_checkable + + +if TYPE_CHECKING: + from crewai.agent.core import Agent + from crewai.agents.parser import AgentFinish + from crewai.crew import Crew + from crewai.llms.base_llm import BaseLLM + from crewai.task import Task + from crewai.utilities.types import LLMMessage + + +class ExecutorContext(Protocol): + """Context interface for human input providers to interact with executor.""" + + task: Task | None + crew: Crew | None + messages: list[LLMMessage] + ask_for_human_input: bool + llm: BaseLLM + agent: Agent + + def _invoke_loop(self) -> AgentFinish: + """Invoke the agent loop and return the result.""" + ... + + def _is_training_mode(self) -> bool: + """Check if training mode is active.""" + ... + + def _handle_crew_training_output( + self, + result: AgentFinish, + human_feedback: str | None = None, + ) -> None: + """Handle training output.""" + ... + + def _format_feedback_message(self, feedback: str) -> LLMMessage: + """Format feedback as a message.""" + ... + + +@runtime_checkable +class HumanInputProvider(Protocol): + """Protocol for human input handling. + + Implementations handle the full feedback flow: + - Sync: prompt user, loop until satisfied + - Async: raise exception for external handling + """ + + def setup_messages(self, context: ExecutorContext) -> bool: + """Set up messages for execution. + + Called before standard message setup. Allows providers to handle + conversation resumption or other custom message initialization. + + Args: + context: Executor context with messages list to modify. + + Returns: + True if messages were set up (skip standard setup), + False to use standard setup. + """ + ... + + def post_setup_messages(self, context: ExecutorContext) -> None: + """Called after standard message setup. + + Allows providers to modify messages after standard setup completes. + Only called when setup_messages returned False. + + Args: + context: Executor context with messages list to modify. + """ + ... + + def handle_feedback( + self, + formatted_answer: AgentFinish, + context: ExecutorContext, + ) -> AgentFinish: + """Handle the full human feedback flow. + + Args: + formatted_answer: The agent's current answer. + context: Executor context for callbacks. + + Returns: + The final answer after feedback processing. + + Raises: + Exception: Async implementations may raise to signal external handling. + """ + ... + + @staticmethod + def _get_output_string(answer: AgentFinish) -> str: + """Extract output string from answer. + + Args: + answer: The agent's finished answer. + + Returns: + String representation of the output. + """ + if isinstance(answer.output, str): + return answer.output + return answer.output.model_dump_json() + + +class SyncHumanInputProvider(HumanInputProvider): + """Default synchronous human input via terminal.""" + + def setup_messages(self, context: ExecutorContext) -> bool: + """Use standard message setup. + + Args: + context: Executor context (unused). + + Returns: + False to use standard setup. + """ + return False + + def post_setup_messages(self, context: ExecutorContext) -> None: + """No-op for sync provider. + + Args: + context: Executor context (unused). + """ + + def handle_feedback( + self, + formatted_answer: AgentFinish, + context: ExecutorContext, + ) -> AgentFinish: + """Handle feedback synchronously with terminal prompts. + + Args: + formatted_answer: The agent's current answer. + context: Executor context for callbacks. + + Returns: + The final answer after feedback processing. + """ + feedback = self._prompt_input(context.crew) + + if context._is_training_mode(): + return self._handle_training_feedback(formatted_answer, feedback, context) + + return self._handle_regular_feedback(formatted_answer, feedback, context) + + @staticmethod + def _handle_training_feedback( + initial_answer: AgentFinish, + feedback: str, + context: ExecutorContext, + ) -> AgentFinish: + """Process training feedback (single iteration). + + Args: + initial_answer: The agent's initial answer. + feedback: Human feedback string. + context: Executor context for callbacks. + + Returns: + Improved answer after processing feedback. + """ + context._handle_crew_training_output(initial_answer, feedback) + context.messages.append(context._format_feedback_message(feedback)) + improved_answer = context._invoke_loop() + context._handle_crew_training_output(improved_answer) + context.ask_for_human_input = False + return improved_answer + + def _handle_regular_feedback( + self, + current_answer: AgentFinish, + initial_feedback: str, + context: ExecutorContext, + ) -> AgentFinish: + """Process regular feedback with iteration loop. + + Args: + current_answer: The agent's current answer. + initial_feedback: Initial human feedback string. + context: Executor context for callbacks. + + Returns: + Final answer after all feedback iterations. + """ + feedback = initial_feedback + answer = current_answer + + while context.ask_for_human_input: + if feedback.strip() == "": + context.ask_for_human_input = False + else: + context.messages.append(context._format_feedback_message(feedback)) + answer = context._invoke_loop() + feedback = self._prompt_input(context.crew) + + return answer + + @staticmethod + def _prompt_input(crew: Crew | None) -> str: + """Show rich panel and prompt for input. + + Args: + crew: The crew instance for context. + + Returns: + User input string from terminal. + """ + from rich.panel import Panel + from rich.text import Text + + from crewai.events.event_listener import event_listener + + formatter = event_listener.formatter + formatter.pause_live_updates() + + try: + if crew and getattr(crew, "_train", False): + prompt_text = ( + "TRAINING MODE: Provide feedback to improve the agent's performance.\n\n" + "This will be used to train better versions of the agent.\n" + "Please provide detailed feedback about the result quality and reasoning process." + ) + title = "🎓 Training Feedback Required" + else: + prompt_text = ( + "Provide feedback on the Final Result above.\n\n" + "• If you are happy with the result, simply hit Enter without typing anything.\n" + "• Otherwise, provide specific improvement requests.\n" + "• You can provide multiple rounds of feedback until satisfied." + ) + title = "💬 Human Feedback Required" + + content = Text() + content.append(prompt_text, style="yellow") + + prompt_panel = Panel( + content, + title=title, + border_style="yellow", + padding=(1, 2), + ) + formatter.console.print(prompt_panel) + + response = input() + if response.strip() != "": + formatter.console.print("\n[cyan]Processing your feedback...[/cyan]") + return response + finally: + formatter.resume_live_updates() + + +_provider: ContextVar[HumanInputProvider | None] = ContextVar( + "human_input_provider", + default=None, +) + + +def get_provider() -> HumanInputProvider: + """Get the current human input provider. + + Returns: + The current provider, or a new SyncHumanInputProvider if none set. + """ + provider = _provider.get() + if provider is None: + initialized_provider = SyncHumanInputProvider() + set_provider(initialized_provider) + return initialized_provider + return provider + + +def set_provider(provider: HumanInputProvider) -> Token[HumanInputProvider | None]: + """Set the human input provider for the current context. + + Args: + provider: The provider to use. + + Returns: + Token that can be used to reset to previous value. + """ + return _provider.set(provider) + + +def reset_provider(token: Token[HumanInputProvider | None]) -> None: + """Reset the provider to its previous value. + + Args: + token: Token returned from set_provider. + """ + _provider.reset(token) diff --git a/lib/crewai/src/crewai/task.py b/lib/crewai/src/crewai/task.py index 77056f0ca..d73c3d919 100644 --- a/lib/crewai/src/crewai/task.py +++ b/lib/crewai/src/crewai/task.py @@ -31,6 +31,7 @@ from pydantic_core import PydanticCustomError from typing_extensions import Self from crewai.agents.agent_builder.base_agent import BaseAgent +from crewai.core.providers.content_processor import process_content from crewai.events.event_bus import crewai_event_bus from crewai.events.types.task_events import ( TaskCompletedEvent, @@ -496,6 +497,7 @@ class Task(BaseModel): tools: list[BaseTool] | None = None, ) -> TaskOutput: """Execute the task synchronously.""" + self.start_time = datetime.datetime.now() return self._execute_core(agent, context, tools) @property @@ -536,6 +538,7 @@ class Task(BaseModel): ) -> None: """Execute the task asynchronously with context handling.""" try: + self.start_time = datetime.datetime.now() result = self._execute_core(agent, context, tools) future.set_result(result) except Exception as e: @@ -548,6 +551,7 @@ class Task(BaseModel): tools: list[BaseTool] | None = None, ) -> TaskOutput: """Execute the task asynchronously using native async/await.""" + self.start_time = datetime.datetime.now() return await self._aexecute_core(agent, context, tools) async def _aexecute_core( @@ -566,8 +570,6 @@ class Task(BaseModel): f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, like hierarchical." ) - self.start_time = datetime.datetime.now() - self.prompt_context = context tools = tools or self.tools or [] @@ -579,6 +581,8 @@ class Task(BaseModel): tools=tools, ) + self._post_agent_execution(agent) + if not self._guardrails and not self._guardrail: pydantic_output, json_output = self._export_output(result) else: @@ -661,8 +665,6 @@ class Task(BaseModel): f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, like hierarchical." ) - self.start_time = datetime.datetime.now() - self.prompt_context = context tools = tools or self.tools or [] @@ -674,6 +676,8 @@ class Task(BaseModel): tools=tools, ) + self._post_agent_execution(agent) + if not self._guardrails and not self._guardrail: pydantic_output, json_output = self._export_output(result) else: @@ -741,6 +745,9 @@ class Task(BaseModel): finally: clear_task_files(self.id) + def _post_agent_execution(self, agent: BaseAgent) -> None: + pass + def prompt(self) -> str: """Generates the task prompt with optional markdown formatting. @@ -863,6 +870,11 @@ Follow these guidelines: except ValueError as e: raise ValueError(f"Error interpolating description: {e!s}") from e + self.description = process_content(self.description, {"task": self}) + self._original_expected_output = process_content( + self._original_expected_output, {"task": self} + ) + try: self.expected_output = interpolate_only( input_string=self._original_expected_output, inputs=inputs diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index 32130f900..025bfd334 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -703,6 +703,8 @@ def test_agent_definition_based_on_dict(): # test for human input @pytest.mark.vcr() def test_agent_human_input(): + from crewai.core.providers.human_input import SyncHumanInputProvider + # Agent configuration config = { "role": "test role", @@ -720,7 +722,7 @@ def test_agent_human_input(): human_input=True, ) - # Side effect function for _ask_human_input to simulate multiple feedback iterations + # Side effect function for _prompt_input to simulate multiple feedback iterations feedback_responses = iter( [ "Don't say hi, say Hello instead!", # First feedback: instruct change @@ -728,16 +730,16 @@ def test_agent_human_input(): ] ) - def ask_human_input_side_effect(*args, **kwargs): + def prompt_input_side_effect(*args, **kwargs): return next(feedback_responses) - # Patch both _ask_human_input and _invoke_loop to avoid real API/network calls. + # Patch both _prompt_input on provider and _invoke_loop to avoid real API/network calls. with ( patch.object( - CrewAgentExecutor, - "_ask_human_input", - side_effect=ask_human_input_side_effect, - ) as mock_human_input, + SyncHumanInputProvider, + "_prompt_input", + side_effect=prompt_input_side_effect, + ) as mock_prompt_input, patch.object( CrewAgentExecutor, "_invoke_loop", @@ -749,7 +751,7 @@ def test_agent_human_input(): # Assertions to ensure the agent behaves correctly. # It should have requested feedback twice. - assert mock_human_input.call_count == 2 + assert mock_prompt_input.call_count == 2 # The final result should be processed to "Hello" assert output.strip().lower() == "hello" diff --git a/lib/crewai/tests/test_flow_human_input_integration.py b/lib/crewai/tests/test_flow_human_input_integration.py index e60cfe514..3ce4ebbd7 100644 --- a/lib/crewai/tests/test_flow_human_input_integration.py +++ b/lib/crewai/tests/test_flow_human_input_integration.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock, patch import pytest from crewai.events.event_listener import event_listener +from crewai.core.providers.human_input import SyncHumanInputProvider class TestFlowHumanInputIntegration: @@ -24,14 +25,9 @@ class TestFlowHumanInputIntegration: @patch("builtins.input", return_value="") def test_human_input_pauses_flow_updates(self, mock_input): """Test that human input pauses Flow status updates.""" - from crewai.agents.agent_builder.base_agent_executor_mixin import ( - CrewAgentExecutorMixin, - ) - - executor = CrewAgentExecutorMixin() - executor.crew = MagicMock() - executor.crew._train = False - executor._printer = MagicMock() + provider = SyncHumanInputProvider() + crew = MagicMock() + crew._train = False formatter = event_listener.formatter @@ -39,7 +35,7 @@ class TestFlowHumanInputIntegration: patch.object(formatter, "pause_live_updates") as mock_pause, patch.object(formatter, "resume_live_updates") as mock_resume, ): - result = executor._ask_human_input("Test result") + result = provider._prompt_input(crew) mock_pause.assert_called_once() mock_resume.assert_called_once() @@ -49,14 +45,9 @@ class TestFlowHumanInputIntegration: @patch("builtins.input", side_effect=["feedback", ""]) def test_multiple_human_input_rounds(self, mock_input): """Test multiple rounds of human input with Flow status management.""" - from crewai.agents.agent_builder.base_agent_executor_mixin import ( - CrewAgentExecutorMixin, - ) - - executor = CrewAgentExecutorMixin() - executor.crew = MagicMock() - executor.crew._train = False - executor._printer = MagicMock() + provider = SyncHumanInputProvider() + crew = MagicMock() + crew._train = False formatter = event_listener.formatter @@ -75,10 +66,10 @@ class TestFlowHumanInputIntegration: formatter, "resume_live_updates", side_effect=track_resume ), ): - result1 = executor._ask_human_input("Test result 1") + result1 = provider._prompt_input(crew) assert result1 == "feedback" - result2 = executor._ask_human_input("Test result 2") + result2 = provider._prompt_input(crew) assert result2 == "" assert len(pause_calls) == 2 @@ -103,14 +94,9 @@ class TestFlowHumanInputIntegration: def test_pause_resume_exception_handling(self): """Test that resume is called even if exception occurs during human input.""" - from crewai.agents.agent_builder.base_agent_executor_mixin import ( - CrewAgentExecutorMixin, - ) - - executor = CrewAgentExecutorMixin() - executor.crew = MagicMock() - executor.crew._train = False - executor._printer = MagicMock() + provider = SyncHumanInputProvider() + crew = MagicMock() + crew._train = False formatter = event_listener.formatter @@ -122,21 +108,16 @@ class TestFlowHumanInputIntegration: ), ): with pytest.raises(KeyboardInterrupt): - executor._ask_human_input("Test result") + provider._prompt_input(crew) mock_pause.assert_called_once() mock_resume.assert_called_once() def test_training_mode_human_input(self): """Test human input in training mode.""" - from crewai.agents.agent_builder.base_agent_executor_mixin import ( - CrewAgentExecutorMixin, - ) - - executor = CrewAgentExecutorMixin() - executor.crew = MagicMock() - executor.crew._train = True - executor._printer = MagicMock() + provider = SyncHumanInputProvider() + crew = MagicMock() + crew._train = True formatter = event_listener.formatter @@ -146,7 +127,7 @@ class TestFlowHumanInputIntegration: patch.object(formatter.console, "print") as mock_console_print, patch("builtins.input", return_value="training feedback"), ): - result = executor._ask_human_input("Test result") + result = provider._prompt_input(crew) mock_pause.assert_called_once() mock_resume.assert_called_once() @@ -161,4 +142,4 @@ class TestFlowHumanInputIntegration: for call in call_args if call[0] ) - assert training_panel_found + assert training_panel_found \ No newline at end of file From d86d43d3e0918fe2c17001edd920c438487813b8 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Wed, 4 Feb 2026 16:05:21 -0500 Subject: [PATCH 04/14] chore: refactor crew to provider Enable dynamic extension exports and small behavior fixes across events and flow modules: - events/__init__.py: Added _extension_exports and extended __getattr__ to lazily resolve registered extension values or import paths. - events/event_bus.py: Implemented off() to unregister sync/async handlers, clean handler dependencies, and invalidate execution plan cache. - events/listeners/tracing/utils.py: Added Callable import and _first_time_trace_hook to allow overriding first-time trace auto-collection behavior. - events/types/tool_usage_events.py: Changed ToolUsageEvent.run_attempts default from None to 0 to avoid nullable handling. - events/utils/console_formatter.py: Respect CREWAI_DISABLE_VERSION_CHECK env var to skip version checks in CI-like flows. - flow/async_feedback/__init__.py: Added typing.Any import, _extension_exports and __getattr__ to support extensions via attribute lookup. These changes add extension points and safer defaults, and provide a way to unregister event handlers. --- lib/crewai/src/crewai/crew.py | 19 ++++- lib/crewai/src/crewai/events/__init__.py | 18 ++++- lib/crewai/src/crewai/events/event_bus.py | 33 +++++++++ .../crewai/events/listeners/tracing/utils.py | 7 +- .../crewai/events/types/tool_usage_events.py | 8 +-- .../crewai/events/utils/console_formatter.py | 3 + .../crewai/flow/async_feedback/__init__.py | 13 ++++ .../crewai/knowledge/source/utils/__init__.py | 1 + .../knowledge/source/utils/source_helper.py | 70 +++++++++++++++++++ .../evaluators/crew_evaluator_handler.py | 16 +++-- 10 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 lib/crewai/src/crewai/knowledge/source/utils/__init__.py create mode 100644 lib/crewai/src/crewai/knowledge/source/utils/source_helper.py diff --git a/lib/crewai/src/crewai/crew.py b/lib/crewai/src/crewai/crew.py index f22dd4b72..baabce190 100644 --- a/lib/crewai/src/crewai/crew.py +++ b/lib/crewai/src/crewai/crew.py @@ -751,6 +751,8 @@ class Crew(FlowTrackable, BaseModel): for after_callback in self.after_kickoff_callbacks: result = after_callback(result) + result = self._post_kickoff(result) + self.usage_metrics = self.calculate_usage_metrics() return result @@ -764,6 +766,9 @@ class Crew(FlowTrackable, BaseModel): clear_files(self.id) detach(token) + def _post_kickoff(self, result: CrewOutput) -> CrewOutput: + return result + def kickoff_for_each( self, inputs: list[dict[str, Any]], @@ -936,6 +941,8 @@ class Crew(FlowTrackable, BaseModel): for after_callback in self.after_kickoff_callbacks: result = after_callback(result) + result = self._post_kickoff(result) + self.usage_metrics = self.calculate_usage_metrics() return result @@ -1181,6 +1188,9 @@ class Crew(FlowTrackable, BaseModel): self.manager_agent = manager manager.crew = self + def _get_execution_start_index(self, tasks: list[Task]) -> int | None: + return None + def _execute_tasks( self, tasks: list[Task], @@ -1197,6 +1207,9 @@ class Crew(FlowTrackable, BaseModel): Returns: CrewOutput: Final output of the crew """ + custom_start = self._get_execution_start_index(tasks) + if custom_start is not None: + start_index = custom_start task_outputs: list[TaskOutput] = [] futures: list[tuple[Task, Future[TaskOutput], int]] = [] @@ -1305,8 +1318,10 @@ class Crew(FlowTrackable, BaseModel): if files: supported_types: list[str] = [] if agent and agent.llm and agent.llm.supports_multimodal(): - provider = getattr(agent.llm, "provider", None) or getattr( - agent.llm, "model", "openai" + provider = ( + getattr(agent.llm, "provider", None) + or getattr(agent.llm, "model", None) + or "openai" ) api = getattr(agent.llm, "api", None) supported_types = get_supported_content_types(provider, api) diff --git a/lib/crewai/src/crewai/events/__init__.py b/lib/crewai/src/crewai/events/__init__.py index 61c0ec380..a6f213a54 100644 --- a/lib/crewai/src/crewai/events/__init__.py +++ b/lib/crewai/src/crewai/events/__init__.py @@ -195,6 +195,7 @@ __all__ = [ "ToolUsageFinishedEvent", "ToolUsageStartedEvent", "ToolValidateInputErrorEvent", + "_extension_exports", "crewai_event_bus", ] @@ -210,14 +211,29 @@ _AGENT_EVENT_MAPPING = { "LiteAgentExecutionStartedEvent": "crewai.events.types.agent_events", } +_extension_exports: dict[str, Any] = {} + def __getattr__(name: str) -> Any: - """Lazy import for agent events to avoid circular imports.""" + """Lazy import for agent events and registered extensions.""" if name in _AGENT_EVENT_MAPPING: import importlib module_path = _AGENT_EVENT_MAPPING[name] module = importlib.import_module(module_path) return getattr(module, name) + + if name in _extension_exports: + import importlib + + value = _extension_exports[name] + if isinstance(value, str): + module_path, _, attr_name = value.rpartition(".") + if module_path: + module = importlib.import_module(module_path) + return getattr(module, attr_name) + return importlib.import_module(value) + return value + msg = f"module {__name__!r} has no attribute {name!r}" raise AttributeError(msg) diff --git a/lib/crewai/src/crewai/events/event_bus.py b/lib/crewai/src/crewai/events/event_bus.py index 5c4bec58f..d0aaa4455 100644 --- a/lib/crewai/src/crewai/events/event_bus.py +++ b/lib/crewai/src/crewai/events/event_bus.py @@ -227,6 +227,39 @@ class CrewAIEventsBus: return decorator + def off( + self, + event_type: type[BaseEvent], + handler: Callable[..., Any], + ) -> None: + """Unregister an event handler for a specific event type. + + Args: + event_type: The event class to stop listening for + handler: The handler function to unregister + """ + with self._rwlock.w_locked(): + if event_type in self._sync_handlers: + existing_sync = self._sync_handlers[event_type] + if handler in existing_sync: + self._sync_handlers[event_type] = existing_sync - {handler} + if not self._sync_handlers[event_type]: + del self._sync_handlers[event_type] + + if event_type in self._async_handlers: + existing_async = self._async_handlers[event_type] + if handler in existing_async: + self._async_handlers[event_type] = existing_async - {handler} + if not self._async_handlers[event_type]: + del self._async_handlers[event_type] + + if event_type in self._handler_dependencies: + self._handler_dependencies[event_type].pop(handler, None) + if not self._handler_dependencies[event_type]: + del self._handler_dependencies[event_type] + + self._execution_plan_cache.pop(event_type, None) + def _call_handlers( self, source: Any, diff --git a/lib/crewai/src/crewai/events/listeners/tracing/utils.py b/lib/crewai/src/crewai/events/listeners/tracing/utils.py index 13e26dacb..b6bf1026b 100644 --- a/lib/crewai/src/crewai/events/listeners/tracing/utils.py +++ b/lib/crewai/src/crewai/events/listeners/tracing/utils.py @@ -1,3 +1,4 @@ +from collections.abc import Callable from contextvars import ContextVar, Token from datetime import datetime import getpass @@ -26,6 +27,8 @@ logger = logging.getLogger(__name__) _tracing_enabled: ContextVar[bool | None] = ContextVar("_tracing_enabled", default=None) +_first_time_trace_hook: Callable[[], bool] | None = None + def should_enable_tracing(*, override: bool | None = None) -> bool: """Determine if tracing should be enabled. @@ -407,10 +410,12 @@ def truncate_messages( def should_auto_collect_first_time_traces() -> bool: """True if we should auto-collect traces for first-time user. - Returns: True if first-time user AND telemetry not disabled AND tracing not explicitly enabled, False otherwise. """ + if _first_time_trace_hook is not None: + return _first_time_trace_hook() + if _is_test_environment(): return False diff --git a/lib/crewai/src/crewai/events/types/tool_usage_events.py b/lib/crewai/src/crewai/events/types/tool_usage_events.py index 7fe9b897f..c4e681546 100644 --- a/lib/crewai/src/crewai/events/types/tool_usage_events.py +++ b/lib/crewai/src/crewai/events/types/tool_usage_events.py @@ -16,7 +16,7 @@ class ToolUsageEvent(BaseEvent): tool_name: str tool_args: dict[str, Any] | str tool_class: str | None = None - run_attempts: int | None = None + run_attempts: int = 0 delegations: int | None = None agent: Any | None = None task_name: str | None = None @@ -26,7 +26,7 @@ class ToolUsageEvent(BaseEvent): model_config = ConfigDict(arbitrary_types_allowed=True) - def __init__(self, **data): + def __init__(self, **data: Any) -> None: if data.get("from_task"): task = data["from_task"] data["task_id"] = str(task.id) @@ -96,10 +96,10 @@ class ToolExecutionErrorEvent(BaseEvent): type: str = "tool_execution_error" tool_name: str tool_args: dict[str, Any] - tool_class: Callable + tool_class: Callable[..., Any] agent: Any | None = None - def __init__(self, **data): + def __init__(self, **data: Any) -> None: super().__init__(**data) # Set fingerprint data from the agent if self.agent and hasattr(self.agent, "fingerprint") and self.agent.fingerprint: diff --git a/lib/crewai/src/crewai/events/utils/console_formatter.py b/lib/crewai/src/crewai/events/utils/console_formatter.py index ac6caabcf..eaecc0e74 100644 --- a/lib/crewai/src/crewai/events/utils/console_formatter.py +++ b/lib/crewai/src/crewai/events/utils/console_formatter.py @@ -49,6 +49,9 @@ class ConsoleFormatter: if os.getenv("CI", "").lower() in ("true", "1"): return + if os.getenv("CREWAI_DISABLE_VERSION_CHECK", "").lower() in ("true", "1"): + return + try: is_newer, current, latest = is_newer_version_available() if is_newer and latest: diff --git a/lib/crewai/src/crewai/flow/async_feedback/__init__.py b/lib/crewai/src/crewai/flow/async_feedback/__init__.py index 286fdaa8d..02590a785 100644 --- a/lib/crewai/src/crewai/flow/async_feedback/__init__.py +++ b/lib/crewai/src/crewai/flow/async_feedback/__init__.py @@ -28,6 +28,8 @@ Example: ``` """ +from typing import Any + from crewai.flow.async_feedback.providers import ConsoleProvider from crewai.flow.async_feedback.types import ( HumanFeedbackPending, @@ -41,4 +43,15 @@ __all__ = [ "HumanFeedbackPending", "HumanFeedbackProvider", "PendingFeedbackContext", + "_extension_exports", ] + +_extension_exports: dict[str, Any] = {} + + +def __getattr__(name: str) -> Any: + """Support extensions via dynamic attribute lookup.""" + if name in _extension_exports: + return _extension_exports[name] + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) diff --git a/lib/crewai/src/crewai/knowledge/source/utils/__init__.py b/lib/crewai/src/crewai/knowledge/source/utils/__init__.py new file mode 100644 index 000000000..0f7c5142c --- /dev/null +++ b/lib/crewai/src/crewai/knowledge/source/utils/__init__.py @@ -0,0 +1 @@ +"""Knowledge source utilities.""" diff --git a/lib/crewai/src/crewai/knowledge/source/utils/source_helper.py b/lib/crewai/src/crewai/knowledge/source/utils/source_helper.py new file mode 100644 index 000000000..9ab41cd30 --- /dev/null +++ b/lib/crewai/src/crewai/knowledge/source/utils/source_helper.py @@ -0,0 +1,70 @@ +"""Helper utilities for knowledge sources.""" + +from typing import Any, ClassVar + +from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource +from crewai.knowledge.source.csv_knowledge_source import CSVKnowledgeSource +from crewai.knowledge.source.excel_knowledge_source import ExcelKnowledgeSource +from crewai.knowledge.source.json_knowledge_source import JSONKnowledgeSource +from crewai.knowledge.source.pdf_knowledge_source import PDFKnowledgeSource +from crewai.knowledge.source.text_file_knowledge_source import TextFileKnowledgeSource + + +class SourceHelper: + """Helper class for creating and managing knowledge sources.""" + + SUPPORTED_FILE_TYPES: ClassVar[list[str]] = [ + ".csv", + ".pdf", + ".json", + ".txt", + ".xlsx", + ".xls", + ] + + _FILE_TYPE_MAP: ClassVar[dict[str, type[BaseKnowledgeSource]]] = { + ".csv": CSVKnowledgeSource, + ".pdf": PDFKnowledgeSource, + ".json": JSONKnowledgeSource, + ".txt": TextFileKnowledgeSource, + ".xlsx": ExcelKnowledgeSource, + ".xls": ExcelKnowledgeSource, + } + + @classmethod + def is_supported_file(cls, file_path: str) -> bool: + """Check if a file type is supported. + + Args: + file_path: Path to the file. + + Returns: + True if the file type is supported. + """ + return file_path.lower().endswith(tuple(cls.SUPPORTED_FILE_TYPES)) + + @classmethod + def get_source( + cls, file_path: str, metadata: dict[str, Any] | None = None + ) -> BaseKnowledgeSource: + """Create appropriate KnowledgeSource based on file extension. + + Args: + file_path: Path to the file. + metadata: Optional metadata to attach to the source. + + Returns: + The appropriate KnowledgeSource instance. + + Raises: + ValueError: If the file type is not supported. + """ + if not cls.is_supported_file(file_path): + raise ValueError(f"Unsupported file type: {file_path}") + + lower_path = file_path.lower() + for ext, source_cls in cls._FILE_TYPE_MAP.items(): + if lower_path.endswith(ext): + return source_cls(file_path=[file_path], metadata=metadata) + + raise ValueError(f"Unsupported file type: {file_path}") diff --git a/lib/crewai/src/crewai/utilities/evaluators/crew_evaluator_handler.py b/lib/crewai/src/crewai/utilities/evaluators/crew_evaluator_handler.py index 9c9cac0c6..32b847d73 100644 --- a/lib/crewai/src/crewai/utilities/evaluators/crew_evaluator_handler.py +++ b/lib/crewai/src/crewai/utilities/evaluators/crew_evaluator_handler.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections import defaultdict -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from pydantic import BaseModel, Field, InstanceOf from rich.box import HEAVY_EDGE @@ -36,7 +36,13 @@ class CrewEvaluator: iteration: The current iteration of the evaluation. """ - def __init__(self, crew: Crew, eval_llm: InstanceOf[BaseLLM]) -> None: + def __init__( + self, + crew: Crew, + eval_llm: InstanceOf[BaseLLM] | str | None = None, + openai_model_name: str | None = None, + llm: InstanceOf[BaseLLM] | str | None = None, + ) -> None: self.crew = crew self.llm = eval_llm self.tasks_scores: defaultdict[int, list[float]] = defaultdict(list) @@ -86,7 +92,9 @@ class CrewEvaluator: """ self.iteration = iteration - def print_crew_evaluation_result(self) -> None: + def print_crew_evaluation_result( + self, token_usage: list[dict[str, Any]] | None = None + ) -> None: """ Prints the evaluation result of the crew in a table. A Crew with 2 tasks using the command crewai test -n 3 @@ -204,7 +212,7 @@ class CrewEvaluator: CrewTestResultEvent( quality=quality_score, execution_duration=current_task.execution_duration, - model=self.llm.model, + model=getattr(self.llm, "model", str(self.llm)), crew_name=self.crew.name, crew=self.crew, ), From 76b5f72e81844e7ee592db87317a96f2e05636ca Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Wed, 4 Feb 2026 20:34:08 -0300 Subject: [PATCH 05/14] Fix tool error causing double event scope pop (#4373) When a tool raises an error, both ToolUsageErrorEvent and ToolUsageFinishedEvent were being emitted. Since both events pop the event scope stack, this caused the agent scope to be incorrectly popped along with the tool scope. --- .../src/crewai/agents/crew_agent_executor.py | 30 ++++---- .../src/crewai/experimental/agent_executor.py | 30 ++++---- lib/crewai/src/crewai/tools/tool_usage.py | 8 +- lib/crewai/tests/events/test_event_context.py | 38 ++++++++- lib/crewai/tests/tools/test_tool_usage.py | 77 +++++++++++++++++++ 5 files changed, 152 insertions(+), 31 deletions(-) diff --git a/lib/crewai/src/crewai/agents/crew_agent_executor.py b/lib/crewai/src/crewai/agents/crew_agent_executor.py index c0f24516a..647596f2a 100644 --- a/lib/crewai/src/crewai/agents/crew_agent_executor.py +++ b/lib/crewai/src/crewai/agents/crew_agent_executor.py @@ -814,6 +814,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): agent_key=agent_key, ), ) + error_event_emitted = False track_delegation_if_needed(func_name, args_dict, self.task) @@ -896,6 +897,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): error=e, ), ) + error_event_emitted = True elif max_usage_reached and original_tool: # Return error message when max usage limit is reached result = f"Tool '{func_name}' has reached its usage limit of {original_tool.max_usage_count} times and cannot be used anymore." @@ -923,20 +925,20 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): color="red", ) - # Emit tool usage finished event - crewai_event_bus.emit( - self, - event=ToolUsageFinishedEvent( - output=result, - tool_name=func_name, - tool_args=args_dict, - from_agent=self.agent, - from_task=self.task, - agent_key=agent_key, - started_at=started_at, - finished_at=datetime.now(), - ), - ) + if not error_event_emitted: + crewai_event_bus.emit( + self, + event=ToolUsageFinishedEvent( + output=result, + tool_name=func_name, + tool_args=args_dict, + from_agent=self.agent, + from_task=self.task, + agent_key=agent_key, + started_at=started_at, + finished_at=datetime.now(), + ), + ) # Append tool result message tool_message: LLMMessage = { diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index b9d8adccc..98e8b5bdb 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -689,6 +689,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): agent_key=agent_key, ), ) + error_event_emitted = False track_delegation_if_needed(func_name, args_dict, self.task) @@ -764,6 +765,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): error=e, ), ) + error_event_emitted = True elif max_usage_reached and original_tool: # Return error message when max usage limit is reached result = f"Tool '{func_name}' has reached its usage limit of {original_tool.max_usage_count} times and cannot be used anymore." @@ -792,20 +794,20 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): color="red", ) - # Emit tool usage finished event - crewai_event_bus.emit( - self, - event=ToolUsageFinishedEvent( - output=result, - tool_name=func_name, - tool_args=args_dict, - from_agent=self.agent, - from_task=self.task, - agent_key=agent_key, - started_at=started_at, - finished_at=datetime.now(), - ), - ) + if not error_event_emitted: + crewai_event_bus.emit( + self, + event=ToolUsageFinishedEvent( + output=result, + tool_name=func_name, + tool_args=args_dict, + from_agent=self.agent, + from_task=self.task, + agent_key=agent_key, + started_at=started_at, + finished_at=datetime.now(), + ), + ) # Append tool result message tool_message: LLMMessage = { diff --git a/lib/crewai/src/crewai/tools/tool_usage.py b/lib/crewai/src/crewai/tools/tool_usage.py index e5a9e6154..b6ce5adb6 100644 --- a/lib/crewai/src/crewai/tools/tool_usage.py +++ b/lib/crewai/src/crewai/tools/tool_usage.py @@ -270,6 +270,7 @@ class ToolUsage: result = None # type: ignore should_retry = False available_tool = None + error_event_emitted = False try: if self.tools_handler and self.tools_handler.cache: @@ -408,6 +409,7 @@ class ToolUsage: except Exception as e: self.on_tool_error(tool=tool, tool_calling=calling, e=e) + error_event_emitted = True self._run_attempts += 1 if self._run_attempts > self._max_parsing_attempts: self._telemetry.tool_usage_error(llm=self.function_calling_llm) @@ -435,7 +437,7 @@ class ToolUsage: result = self._format_result(result=result) finally: - if started_event_emitted: + if started_event_emitted and not error_event_emitted: self.on_tool_use_finished( tool=tool, tool_calling=calling, @@ -500,6 +502,7 @@ class ToolUsage: result = None # type: ignore should_retry = False available_tool = None + error_event_emitted = False try: if self.tools_handler and self.tools_handler.cache: @@ -638,6 +641,7 @@ class ToolUsage: except Exception as e: self.on_tool_error(tool=tool, tool_calling=calling, e=e) + error_event_emitted = True self._run_attempts += 1 if self._run_attempts > self._max_parsing_attempts: self._telemetry.tool_usage_error(llm=self.function_calling_llm) @@ -665,7 +669,7 @@ class ToolUsage: result = self._format_result(result=result) finally: - if started_event_emitted: + if started_event_emitted and not error_event_emitted: self.on_tool_use_finished( tool=tool, tool_calling=calling, diff --git a/lib/crewai/tests/events/test_event_context.py b/lib/crewai/tests/events/test_event_context.py index 071e1a34d..2a69ca1ee 100644 --- a/lib/crewai/tests/events/test_event_context.py +++ b/lib/crewai/tests/events/test_event_context.py @@ -177,4 +177,40 @@ class TestTriggeredByScope: raise ValueError("test error") except ValueError: pass - assert get_triggering_event_id() is None \ No newline at end of file + assert get_triggering_event_id() is None + + +def test_agent_scope_preserved_after_tool_error_event() -> None: + from crewai.events import crewai_event_bus + from crewai.events.types.tool_usage_events import ( + ToolUsageErrorEvent, + ToolUsageStartedEvent, + ) + + push_event_scope("crew-1", "crew_kickoff_started") + push_event_scope("task-1", "task_started") + push_event_scope("agent-1", "agent_execution_started") + + crewai_event_bus.emit( + None, + ToolUsageStartedEvent( + tool_name="test_tool", + tool_args={}, + agent_key="test_agent", + ) + ) + + crewai_event_bus.emit( + None, + ToolUsageErrorEvent( + tool_name="test_tool", + tool_args={}, + agent_key="test_agent", + error=ValueError("test error"), + ) + ) + + crewai_event_bus.flush() + + assert get_current_parent_id() == "agent-1" + diff --git a/lib/crewai/tests/tools/test_tool_usage.py b/lib/crewai/tests/tools/test_tool_usage.py index c6dfad58b..b68a41666 100644 --- a/lib/crewai/tests/tools/test_tool_usage.py +++ b/lib/crewai/tests/tools/test_tool_usage.py @@ -10,7 +10,9 @@ from crewai import Agent, Task from crewai.events.event_bus import crewai_event_bus from crewai.events.types.tool_usage_events import ( ToolSelectionErrorEvent, + ToolUsageErrorEvent, ToolUsageFinishedEvent, + ToolUsageStartedEvent, ToolValidateInputErrorEvent, ) from crewai.tools import BaseTool @@ -744,3 +746,78 @@ def test_tool_usage_finished_event_with_cached_result(): assert isinstance(event.started_at, datetime.datetime) assert isinstance(event.finished_at, datetime.datetime) assert event.type == "tool_usage_finished" + + +def test_tool_error_does_not_emit_finished_event(): + from crewai.tools.tool_calling import ToolCalling + + class FailingTool(BaseTool): + name: str = "Failing Tool" + description: str = "A tool that always fails" + + def _run(self, **kwargs) -> str: + raise ValueError("Intentional failure") + + failing_tool = FailingTool().to_structured_tool() + + mock_agent = MagicMock() + mock_agent.key = "test_agent_key" + mock_agent.role = "test_agent_role" + mock_agent._original_role = "test_agent_role" + mock_agent.verbose = False + mock_agent.fingerprint = None + mock_agent.i18n.tools.return_value = {"name": "Add Image"} + mock_agent.i18n.errors.return_value = "Error: {error}" + mock_agent.i18n.slice.return_value = "Available tools: {tool_names}" + + mock_task = MagicMock() + mock_task.delegations = 0 + mock_task.name = "Test Task" + mock_task.description = "A test task" + mock_task.id = "test-task-id" + + mock_action = MagicMock() + mock_action.tool = "failing_tool" + mock_action.tool_input = "{}" + + tool_usage = ToolUsage( + tools_handler=MagicMock(cache=None, last_used_tool=None), + tools=[failing_tool], + task=mock_task, + function_calling_llm=None, + agent=mock_agent, + action=mock_action, + ) + + started_events = [] + error_events = [] + finished_events = [] + error_received = threading.Event() + + @crewai_event_bus.on(ToolUsageStartedEvent) + def on_started(source, event): + if event.tool_name == "failing_tool": + started_events.append(event) + + @crewai_event_bus.on(ToolUsageErrorEvent) + def on_error(source, event): + if event.tool_name == "failing_tool": + error_events.append(event) + error_received.set() + + @crewai_event_bus.on(ToolUsageFinishedEvent) + def on_finished(source, event): + if event.tool_name == "failing_tool": + finished_events.append(event) + + tool_calling = ToolCalling(tool_name="failing_tool", arguments={}) + tool_usage.use(calling=tool_calling, tool_string="Action: failing_tool") + + assert error_received.wait(timeout=5), "Timeout waiting for error event" + crewai_event_bus.flush() + + assert len(started_events) >= 1, "Expected at least one ToolUsageStartedEvent" + assert len(error_events) >= 1, "Expected at least one ToolUsageErrorEvent" + assert len(finished_events) == 0, ( + "ToolUsageFinishedEvent should NOT be emitted after ToolUsageErrorEvent" + ) From 711e7171e1d01ef5a00c5f4f863463c670650e92 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Wed, 4 Feb 2026 21:16:20 -0500 Subject: [PATCH 06/14] chore: improve hook typing and registration Allow hook registration to accept both typed hook types and plain callables by importing and using After*/Before*CallHookCallable types; add explicit LLMCallHookContext and ToolCallHookContext typing in crew_base. Introduce a post-initialize crew hook list and invoke hooks after Crew instance initialization. Refactor filtered hook factory functions to include precise typing and clearer local names (before_llm_hook/after_llm_hook/before_tool_hook/after_tool_hook) and register those with the instance. Update CrewInstance protocol to include _registered_hook_functions and _hooks_being_registered fields. --- lib/crewai/src/crewai/hooks/llm_hooks.py | 25 ++++--- lib/crewai/src/crewai/hooks/tool_hooks.py | 27 +++++--- lib/crewai/src/crewai/project/crew_base.py | 77 +++++++++++++++------- lib/crewai/src/crewai/project/wrappers.py | 2 + 4 files changed, 89 insertions(+), 42 deletions(-) diff --git a/lib/crewai/src/crewai/hooks/llm_hooks.py b/lib/crewai/src/crewai/hooks/llm_hooks.py index 2f5462fe0..3a6abbedf 100644 --- a/lib/crewai/src/crewai/hooks/llm_hooks.py +++ b/lib/crewai/src/crewai/hooks/llm_hooks.py @@ -3,7 +3,12 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, cast from crewai.events.event_listener import event_listener -from crewai.hooks.types import AfterLLMCallHookType, BeforeLLMCallHookType +from crewai.hooks.types import ( + AfterLLMCallHookCallable, + AfterLLMCallHookType, + BeforeLLMCallHookCallable, + BeforeLLMCallHookType, +) from crewai.utilities.printer import Printer @@ -149,12 +154,12 @@ class LLMCallHookContext: event_listener.formatter.resume_live_updates() -_before_llm_call_hooks: list[BeforeLLMCallHookType] = [] -_after_llm_call_hooks: list[AfterLLMCallHookType] = [] +_before_llm_call_hooks: list[BeforeLLMCallHookType | BeforeLLMCallHookCallable] = [] +_after_llm_call_hooks: list[AfterLLMCallHookType | AfterLLMCallHookCallable] = [] def register_before_llm_call_hook( - hook: BeforeLLMCallHookType, + hook: BeforeLLMCallHookType | BeforeLLMCallHookCallable, ) -> None: """Register a global before_llm_call hook. @@ -190,7 +195,7 @@ def register_before_llm_call_hook( def register_after_llm_call_hook( - hook: AfterLLMCallHookType, + hook: AfterLLMCallHookType | AfterLLMCallHookCallable, ) -> None: """Register a global after_llm_call hook. @@ -217,7 +222,9 @@ def register_after_llm_call_hook( _after_llm_call_hooks.append(hook) -def get_before_llm_call_hooks() -> list[BeforeLLMCallHookType]: +def get_before_llm_call_hooks() -> list[ + BeforeLLMCallHookType | BeforeLLMCallHookCallable +]: """Get all registered global before_llm_call hooks. Returns: @@ -226,7 +233,7 @@ def get_before_llm_call_hooks() -> list[BeforeLLMCallHookType]: return _before_llm_call_hooks.copy() -def get_after_llm_call_hooks() -> list[AfterLLMCallHookType]: +def get_after_llm_call_hooks() -> list[AfterLLMCallHookType | AfterLLMCallHookCallable]: """Get all registered global after_llm_call hooks. Returns: @@ -236,7 +243,7 @@ def get_after_llm_call_hooks() -> list[AfterLLMCallHookType]: def unregister_before_llm_call_hook( - hook: BeforeLLMCallHookType, + hook: BeforeLLMCallHookType | BeforeLLMCallHookCallable, ) -> bool: """Unregister a specific global before_llm_call hook. @@ -262,7 +269,7 @@ def unregister_before_llm_call_hook( def unregister_after_llm_call_hook( - hook: AfterLLMCallHookType, + hook: AfterLLMCallHookType | AfterLLMCallHookCallable, ) -> bool: """Unregister a specific global after_llm_call hook. diff --git a/lib/crewai/src/crewai/hooks/tool_hooks.py b/lib/crewai/src/crewai/hooks/tool_hooks.py index 6ee0ab033..ac7f5c362 100644 --- a/lib/crewai/src/crewai/hooks/tool_hooks.py +++ b/lib/crewai/src/crewai/hooks/tool_hooks.py @@ -3,7 +3,12 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any from crewai.events.event_listener import event_listener -from crewai.hooks.types import AfterToolCallHookType, BeforeToolCallHookType +from crewai.hooks.types import ( + AfterToolCallHookCallable, + AfterToolCallHookType, + BeforeToolCallHookCallable, + BeforeToolCallHookType, +) from crewai.utilities.printer import Printer @@ -112,12 +117,12 @@ class ToolCallHookContext: # Global hook registries -_before_tool_call_hooks: list[BeforeToolCallHookType] = [] -_after_tool_call_hooks: list[AfterToolCallHookType] = [] +_before_tool_call_hooks: list[BeforeToolCallHookType | BeforeToolCallHookCallable] = [] +_after_tool_call_hooks: list[AfterToolCallHookType | AfterToolCallHookCallable] = [] def register_before_tool_call_hook( - hook: BeforeToolCallHookType, + hook: BeforeToolCallHookType | BeforeToolCallHookCallable, ) -> None: """Register a global before_tool_call hook. @@ -154,7 +159,7 @@ def register_before_tool_call_hook( def register_after_tool_call_hook( - hook: AfterToolCallHookType, + hook: AfterToolCallHookType | AfterToolCallHookCallable, ) -> None: """Register a global after_tool_call hook. @@ -184,7 +189,9 @@ def register_after_tool_call_hook( _after_tool_call_hooks.append(hook) -def get_before_tool_call_hooks() -> list[BeforeToolCallHookType]: +def get_before_tool_call_hooks() -> list[ + BeforeToolCallHookType | BeforeToolCallHookCallable +]: """Get all registered global before_tool_call hooks. Returns: @@ -193,7 +200,9 @@ def get_before_tool_call_hooks() -> list[BeforeToolCallHookType]: return _before_tool_call_hooks.copy() -def get_after_tool_call_hooks() -> list[AfterToolCallHookType]: +def get_after_tool_call_hooks() -> list[ + AfterToolCallHookType | AfterToolCallHookCallable +]: """Get all registered global after_tool_call hooks. Returns: @@ -203,7 +212,7 @@ def get_after_tool_call_hooks() -> list[AfterToolCallHookType]: def unregister_before_tool_call_hook( - hook: BeforeToolCallHookType, + hook: BeforeToolCallHookType | BeforeToolCallHookCallable, ) -> bool: """Unregister a specific global before_tool_call hook. @@ -229,7 +238,7 @@ def unregister_before_tool_call_hook( def unregister_after_tool_call_hook( - hook: AfterToolCallHookType, + hook: AfterToolCallHookType | AfterToolCallHookCallable, ) -> bool: """Unregister a specific global after_tool_call hook. diff --git a/lib/crewai/src/crewai/project/crew_base.py b/lib/crewai/src/crewai/project/crew_base.py index 202d98898..323450b13 100644 --- a/lib/crewai/src/crewai/project/crew_base.py +++ b/lib/crewai/src/crewai/project/crew_base.py @@ -27,6 +27,8 @@ if TYPE_CHECKING: from crewai import Agent, Task from crewai.agents.cache.cache_handler import CacheHandler from crewai.crews.crew_output import CrewOutput + from crewai.hooks.llm_hooks import LLMCallHookContext + from crewai.hooks.tool_hooks import ToolCallHookContext from crewai.project.wrappers import ( CrewInstance, OutputJsonClass, @@ -34,6 +36,8 @@ if TYPE_CHECKING: ) from crewai.tasks.task_output import TaskOutput +_post_initialize_crew_hooks: list[Callable[[Any], None]] = [] + class AgentConfig(TypedDict, total=False): """Type definition for agent configuration dictionary. @@ -266,6 +270,9 @@ class CrewBaseMeta(type): instance.map_all_agent_variables() instance.map_all_task_variables() + for hook in _post_initialize_crew_hooks: + hook(instance) + original_methods = { name: method for name, method in cls.__dict__.items() @@ -485,47 +492,61 @@ def _register_crew_hooks(instance: CrewInstance, cls: type) -> None: if has_agent_filter: agents_filter = hook_method._filter_agents - def make_filtered_before_llm(bound_fn, agents_list): - def filtered(context): + def make_filtered_before_llm( + bound_fn: Callable[[LLMCallHookContext], bool | None], + agents_list: list[str], + ) -> Callable[[LLMCallHookContext], bool | None]: + def filtered(context: LLMCallHookContext) -> bool | None: if context.agent and context.agent.role not in agents_list: return None return bound_fn(context) return filtered - final_hook = make_filtered_before_llm(bound_hook, agents_filter) + before_llm_hook = make_filtered_before_llm(bound_hook, agents_filter) else: - final_hook = bound_hook + before_llm_hook = bound_hook - register_before_llm_call_hook(final_hook) - instance._registered_hook_functions.append(("before_llm_call", final_hook)) + register_before_llm_call_hook(before_llm_hook) + instance._registered_hook_functions.append( + ("before_llm_call", before_llm_hook) + ) if hasattr(hook_method, "is_after_llm_call_hook"): if has_agent_filter: agents_filter = hook_method._filter_agents - def make_filtered_after_llm(bound_fn, agents_list): - def filtered(context): + def make_filtered_after_llm( + bound_fn: Callable[[LLMCallHookContext], str | None], + agents_list: list[str], + ) -> Callable[[LLMCallHookContext], str | None]: + def filtered(context: LLMCallHookContext) -> str | None: if context.agent and context.agent.role not in agents_list: return None return bound_fn(context) return filtered - final_hook = make_filtered_after_llm(bound_hook, agents_filter) + after_llm_hook = make_filtered_after_llm(bound_hook, agents_filter) else: - final_hook = bound_hook + after_llm_hook = bound_hook - register_after_llm_call_hook(final_hook) - instance._registered_hook_functions.append(("after_llm_call", final_hook)) + register_after_llm_call_hook(after_llm_hook) + instance._registered_hook_functions.append( + ("after_llm_call", after_llm_hook) + ) if hasattr(hook_method, "is_before_tool_call_hook"): if has_tool_filter or has_agent_filter: tools_filter = getattr(hook_method, "_filter_tools", None) agents_filter = getattr(hook_method, "_filter_agents", None) - def make_filtered_before_tool(bound_fn, tools_list, agents_list): - def filtered(context): + def make_filtered_before_tool( + bound_fn: Callable[[ToolCallHookContext], bool | None], + tools_list: list[str] | None, + agents_list: list[str] | None, + ) -> Callable[[ToolCallHookContext], bool | None]: + def filtered(context: ToolCallHookContext) -> bool | None: if tools_list and context.tool_name not in tools_list: return None if ( @@ -538,22 +559,28 @@ def _register_crew_hooks(instance: CrewInstance, cls: type) -> None: return filtered - final_hook = make_filtered_before_tool( + before_tool_hook = make_filtered_before_tool( bound_hook, tools_filter, agents_filter ) else: - final_hook = bound_hook + before_tool_hook = bound_hook - register_before_tool_call_hook(final_hook) - instance._registered_hook_functions.append(("before_tool_call", final_hook)) + register_before_tool_call_hook(before_tool_hook) + instance._registered_hook_functions.append( + ("before_tool_call", before_tool_hook) + ) if hasattr(hook_method, "is_after_tool_call_hook"): if has_tool_filter or has_agent_filter: tools_filter = getattr(hook_method, "_filter_tools", None) agents_filter = getattr(hook_method, "_filter_agents", None) - def make_filtered_after_tool(bound_fn, tools_list, agents_list): - def filtered(context): + def make_filtered_after_tool( + bound_fn: Callable[[ToolCallHookContext], str | None], + tools_list: list[str] | None, + agents_list: list[str] | None, + ) -> Callable[[ToolCallHookContext], str | None]: + def filtered(context: ToolCallHookContext) -> str | None: if tools_list and context.tool_name not in tools_list: return None if ( @@ -566,14 +593,16 @@ def _register_crew_hooks(instance: CrewInstance, cls: type) -> None: return filtered - final_hook = make_filtered_after_tool( + after_tool_hook = make_filtered_after_tool( bound_hook, tools_filter, agents_filter ) else: - final_hook = bound_hook + after_tool_hook = bound_hook - register_after_tool_call_hook(final_hook) - instance._registered_hook_functions.append(("after_tool_call", final_hook)) + register_after_tool_call_hook(after_tool_hook) + instance._registered_hook_functions.append( + ("after_tool_call", after_tool_hook) + ) instance._hooks_being_registered = False diff --git a/lib/crewai/src/crewai/project/wrappers.py b/lib/crewai/src/crewai/project/wrappers.py index 28cd39525..3d570b6f0 100644 --- a/lib/crewai/src/crewai/project/wrappers.py +++ b/lib/crewai/src/crewai/project/wrappers.py @@ -72,6 +72,8 @@ class CrewInstance(Protocol): __crew_metadata__: CrewMetadata _mcp_server_adapter: Any _all_methods: dict[str, Callable[..., Any]] + _registered_hook_functions: list[tuple[str, Callable[..., Any]]] + _hooks_being_registered: bool agents: list[Agent] tasks: list[Task] base_directory: Path From fe2a4b4e40dac46db8ceec45dfc58dbefb8c39c3 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Wed, 4 Feb 2026 21:21:54 -0500 Subject: [PATCH 07/14] chore: bug fixes and more refactor Refactor agent executor to delegate human interactions to a provider: add messages and ask_for_human_input properties, implement _invoke_loop and _format_feedback_message, and replace the internal iterative/training feedback logic with a call to get_provider().handle_feedback. Make LLMGuardrail kickoff coroutine-aware by detecting coroutines and running them via asyncio.run so both sync and async agents are supported. Make telemetry more robust by safely handling missing task.output (use empty string) and returning early if span is None before setting attributes. Improve serialization to detect circular references via an _ancestors set, propagate it through recursive calls, and pass exclude/max_depth/_current_depth consistently to prevent infinite recursion and produce stable serializable output. --- .../src/crewai/experimental/agent_executor.py | 179 ++++++------------ lib/crewai/src/crewai/tasks/llm_guardrail.py | 16 +- lib/crewai/src/crewai/telemetry/telemetry.py | 5 +- .../src/crewai/utilities/serialization.py | 24 ++- 4 files changed, 101 insertions(+), 123 deletions(-) diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index 98e8b5bdb..87f9e83d4 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -18,6 +18,7 @@ from crewai.agents.parser import ( AgentFinish, OutputParserError, ) +from crewai.core.providers.human_input import get_provider from crewai.events.event_bus import crewai_event_bus from crewai.events.listeners.tracing.utils import ( is_tracing_enabled_in_context, @@ -41,7 +42,12 @@ from crewai.hooks.tool_hooks import ( get_after_tool_call_hooks, get_before_tool_call_hooks, ) -from crewai.hooks.types import AfterLLMCallHookType, BeforeLLMCallHookType +from crewai.hooks.types import ( + AfterLLMCallHookCallable, + AfterLLMCallHookType, + BeforeLLMCallHookCallable, + BeforeLLMCallHookType, +) from crewai.utilities.agent_utils import ( convert_tools_to_openai_schema, enforce_rpm_limit, @@ -191,8 +197,12 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): self._instance_id = str(uuid4())[:8] - self.before_llm_call_hooks: list[BeforeLLMCallHookType] = [] - self.after_llm_call_hooks: list[AfterLLMCallHookType] = [] + self.before_llm_call_hooks: list[ + BeforeLLMCallHookType | BeforeLLMCallHookCallable + ] = [] + self.after_llm_call_hooks: list[ + AfterLLMCallHookType | AfterLLMCallHookCallable + ] = [] self.before_llm_call_hooks.extend(get_before_llm_call_hooks()) self.after_llm_call_hooks.extend(get_after_llm_call_hooks()) @@ -207,6 +217,51 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): ) self._state = AgentReActState() + @property + def messages(self) -> list[LLMMessage]: + """Delegate to state for ExecutorContext conformance.""" + return self._state.messages + + @messages.setter + def messages(self, value: list[LLMMessage]) -> None: + """Delegate to state for ExecutorContext conformance.""" + self._state.messages = value + + @property + def ask_for_human_input(self) -> bool: + """Delegate to state for ExecutorContext conformance.""" + return self._state.ask_for_human_input + + @ask_for_human_input.setter + def ask_for_human_input(self, value: bool) -> None: + """Delegate to state for ExecutorContext conformance.""" + self._state.ask_for_human_input = value + + def _invoke_loop(self) -> AgentFinish: + """Invoke the agent loop and return the result. + + Required by ExecutorContext protocol. + """ + self._state.iterations = 0 + self._state.is_finished = False + self._state.current_answer = None + + self.kickoff() + + answer = self._state.current_answer + if not isinstance(answer, AgentFinish): + raise RuntimeError("Agent loop did not produce a final answer") + return answer + + def _format_feedback_message(self, feedback: str) -> LLMMessage: + """Format feedback as a message for the LLM. + + Required by ExecutorContext protocol. + """ + return format_message_for_llm( + self._i18n.slice("feedback_instructions").format(feedback=feedback) + ) + def _ensure_flow_initialized(self) -> None: """Ensure Flow.__init__() has been called. @@ -300,16 +355,6 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): """ return self._state - @property - def messages(self) -> list[LLMMessage]: - """Compatibility property for mixin - returns state messages.""" - return self._state.messages - - @messages.setter - def messages(self, value: list[LLMMessage]) -> None: - """Set state messages.""" - self._state.messages = value - @property def iterations(self) -> int: """Compatibility property for mixin - returns state iterations.""" @@ -1321,17 +1366,8 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): Returns: Final answer after feedback. """ - output_str = ( - str(formatted_answer.output) - if isinstance(formatted_answer.output, BaseModel) - else formatted_answer.output - ) - human_feedback = self._ask_human_input(output_str) - - if self._is_training_mode(): - return self._handle_training_feedback(formatted_answer, human_feedback) - - return self._handle_regular_feedback(formatted_answer, human_feedback) + provider = get_provider() + return provider.handle_feedback(formatted_answer, self) def _is_training_mode(self) -> bool: """Check if training mode is active. @@ -1341,101 +1377,6 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): """ return bool(self.crew and self.crew._train) - def _handle_training_feedback( - self, initial_answer: AgentFinish, feedback: str - ) -> AgentFinish: - """Process training feedback and generate improved answer. - - Args: - initial_answer: Initial agent output. - feedback: Training feedback. - - Returns: - Improved answer. - """ - self._handle_crew_training_output(initial_answer, feedback) - self.state.messages.append( - format_message_for_llm( - self._i18n.slice("feedback_instructions").format(feedback=feedback) - ) - ) - - # Re-run flow for improved answer - self.state.iterations = 0 - self.state.is_finished = False - self.state.current_answer = None - - self.kickoff() - - # Get improved answer from state - improved_answer = self.state.current_answer - if not isinstance(improved_answer, AgentFinish): - raise RuntimeError( - "Training feedback iteration did not produce final answer" - ) - - self._handle_crew_training_output(improved_answer) - self.state.ask_for_human_input = False - return improved_answer - - def _handle_regular_feedback( - self, current_answer: AgentFinish, initial_feedback: str - ) -> AgentFinish: - """Process regular feedback iteratively until user is satisfied. - - Args: - current_answer: Current agent output. - initial_feedback: Initial user feedback. - - Returns: - Final answer after iterations. - """ - feedback = initial_feedback - answer = current_answer - - while self.state.ask_for_human_input: - if feedback.strip() == "": - self.state.ask_for_human_input = False - else: - answer = self._process_feedback_iteration(feedback) - output_str = ( - str(answer.output) - if isinstance(answer.output, BaseModel) - else answer.output - ) - feedback = self._ask_human_input(output_str) - - return answer - - def _process_feedback_iteration(self, feedback: str) -> AgentFinish: - """Process a single feedback iteration and generate updated response. - - Args: - feedback: User feedback. - - Returns: - Updated agent response. - """ - self.state.messages.append( - format_message_for_llm( - self._i18n.slice("feedback_instructions").format(feedback=feedback) - ) - ) - - # Re-run flow - self.state.iterations = 0 - self.state.is_finished = False - self.state.current_answer = None - - self.kickoff() - - # Get answer from state - answer = self.state.current_answer - if not isinstance(answer, AgentFinish): - raise RuntimeError("Feedback iteration did not produce final answer") - - return answer - @classmethod def __get_pydantic_core_schema__( cls, _source_type: Any, _handler: GetCoreSchemaHandler diff --git a/lib/crewai/src/crewai/tasks/llm_guardrail.py b/lib/crewai/src/crewai/tasks/llm_guardrail.py index 803b2d749..3729e8084 100644 --- a/lib/crewai/src/crewai/tasks/llm_guardrail.py +++ b/lib/crewai/src/crewai/tasks/llm_guardrail.py @@ -1,6 +1,10 @@ +import asyncio +from collections.abc import Coroutine +import inspect from typing import Any from pydantic import BaseModel, Field +from typing_extensions import TypeIs from crewai.agent import Agent from crewai.lite_agent_output import LiteAgentOutput @@ -8,6 +12,13 @@ from crewai.llms.base_llm import BaseLLM from crewai.tasks.task_output import TaskOutput +def _is_coroutine( + obj: LiteAgentOutput | Coroutine[Any, Any, LiteAgentOutput], +) -> TypeIs[Coroutine[Any, Any, LiteAgentOutput]]: + """Check if obj is a coroutine for type narrowing.""" + return inspect.iscoroutine(obj) + + class LLMGuardrailResult(BaseModel): valid: bool = Field( description="Whether the task output complies with the guardrail" @@ -62,7 +73,10 @@ class LLMGuardrail: - If the Task result complies with the guardrail, saying that is valid """ - return agent.kickoff(query, response_format=LLMGuardrailResult) + kickoff_result = agent.kickoff(query, response_format=LLMGuardrailResult) + if _is_coroutine(kickoff_result): + return asyncio.run(kickoff_result) + return kickoff_result def __call__(self, task_output: TaskOutput) -> tuple[bool, Any]: """Validates the output of a task based on specified criteria. diff --git a/lib/crewai/src/crewai/telemetry/telemetry.py b/lib/crewai/src/crewai/telemetry/telemetry.py index 27d5d6090..04303fc3d 100644 --- a/lib/crewai/src/crewai/telemetry/telemetry.py +++ b/lib/crewai/src/crewai/telemetry/telemetry.py @@ -903,7 +903,7 @@ class Telemetry: { "id": str(task.id), "description": task.description, - "output": task.output.raw_output, + "output": task.output.raw if task.output else "", } for task in crew.tasks ] @@ -923,6 +923,9 @@ class Telemetry: value: The attribute value. """ + if span is None: + return + def _operation() -> None: return span.set_attribute(key, value) diff --git a/lib/crewai/src/crewai/utilities/serialization.py b/lib/crewai/src/crewai/utilities/serialization.py index a6b871d3d..0207e80ab 100644 --- a/lib/crewai/src/crewai/utilities/serialization.py +++ b/lib/crewai/src/crewai/utilities/serialization.py @@ -19,6 +19,7 @@ def to_serializable( exclude: set[str] | None = None, max_depth: int = 5, _current_depth: int = 0, + _ancestors: set[int] | None = None, ) -> Serializable: """Converts a Python object into a JSON-compatible representation. @@ -31,6 +32,7 @@ def to_serializable( exclude: Set of keys to exclude from the result. max_depth: Maximum recursion depth. Defaults to 5. _current_depth: Current recursion depth (for internal use). + _ancestors: Set of ancestor object ids for cycle detection (for internal use). Returns: Serializable: A JSON-compatible structure. @@ -41,16 +43,29 @@ def to_serializable( if exclude is None: exclude = set() + if _ancestors is None: + _ancestors = set() + if isinstance(obj, (str, int, float, bool, type(None))): return obj if isinstance(obj, uuid.UUID): return str(obj) if isinstance(obj, (date, datetime)): return obj.isoformat() + + object_id = id(obj) + if object_id in _ancestors: + return f"" + new_ancestors = _ancestors | {object_id} + if isinstance(obj, (list, tuple, set)): return [ to_serializable( - item, max_depth=max_depth, _current_depth=_current_depth + 1 + item, + exclude=exclude, + max_depth=max_depth, + _current_depth=_current_depth + 1, + _ancestors=new_ancestors, ) for item in obj ] @@ -61,6 +76,7 @@ def to_serializable( exclude=exclude, max_depth=max_depth, _current_depth=_current_depth + 1, + _ancestors=new_ancestors, ) for key, value in obj.items() if key not in exclude @@ -71,12 +87,16 @@ def to_serializable( obj=obj.model_dump(exclude=exclude), max_depth=max_depth, _current_depth=_current_depth + 1, + _ancestors=new_ancestors, ) except Exception: try: return { _to_serializable_key(k): to_serializable( - v, max_depth=max_depth, _current_depth=_current_depth + 1 + v, + max_depth=max_depth, + _current_depth=_current_depth + 1, + _ancestors=new_ancestors, ) for k, v in obj.__dict__.items() if k not in (exclude or set()) From 6bb1b178a10139160575cccc4ce1be62800b28c0 Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Thu, 5 Feb 2026 12:49:54 -0500 Subject: [PATCH 08/14] chore: extension points Introduce ContextVar-backed hooks and small API/behavior changes to improve extensibility and testability. Changes include: - agents: mark configure_structured_output as abstract and change its parameter to task to reflect use of task metadata. - tracing: convert _first_time_trace_hook to a ContextVar and call .get() to safely retrieve the hook. - console formatter: add _disable_version_check ContextVar and skip version checks when set (avoids noisy checks in certain contexts). - flow: use current_triggering_event_id variable when scheduling listener tasks to keep naming consistent. - hallucination guardrail: make context optional, add _validate_output_hook to allow custom validation hooks, update examples and return contract to allow hooks to override behavior. - agent utilities: add _create_plus_client_hook for injecting a Plus client (used in tests/alternate flows), ensure structured tools have current_usage_count initialized and propagate to original tool, and fall back to creating PlusAPI client when no hook is provided. --- .../agent_adapters/base_agent_adapter.py | 5 ++- lib/crewai/src/crewai/crew.py | 8 +++- .../listeners/tracing/trace_listener.py | 8 +++- .../crewai/events/listeners/tracing/utils.py | 37 ++++++++++++++-- .../crewai/events/utils/console_formatter.py | 43 +++++++++++++++++++ lib/crewai/src/crewai/flow/flow.py | 9 +++- .../crewai/tasks/hallucination_guardrail.py | 31 +++++++------ .../src/crewai/utilities/agent_utils.py | 17 ++++++-- lib/crewai/src/crewai/utilities/printer.py | 4 ++ .../agent_adapters/test_base_agent_adapter.py | 4 ++ 10 files changed, 141 insertions(+), 25 deletions(-) diff --git a/lib/crewai/src/crewai/agents/agent_adapters/base_agent_adapter.py b/lib/crewai/src/crewai/agents/agent_adapters/base_agent_adapter.py index 8001628ed..12dfcf43c 100644 --- a/lib/crewai/src/crewai/agents/agent_adapters/base_agent_adapter.py +++ b/lib/crewai/src/crewai/agents/agent_adapters/base_agent_adapter.py @@ -37,9 +37,10 @@ class BaseAgentAdapter(BaseAgent, ABC): tools: Optional list of BaseTool instances to be configured """ - def configure_structured_output(self, structured_output: Any) -> None: + @abstractmethod + def configure_structured_output(self, task: Any) -> None: """Configure the structured output for the specific agent implementation. Args: - structured_output: The structured output to be configured + task: The task object containing output format specifications. """ diff --git a/lib/crewai/src/crewai/crew.py b/lib/crewai/src/crewai/crew.py index baabce190..09a103eba 100644 --- a/lib/crewai/src/crewai/crew.py +++ b/lib/crewai/src/crewai/crew.py @@ -2026,7 +2026,13 @@ class Crew(FlowTrackable, BaseModel): @staticmethod def _show_tracing_disabled_message() -> None: """Show a message when tracing is disabled.""" - from crewai.events.listeners.tracing.utils import has_user_declined_tracing + from crewai.events.listeners.tracing.utils import ( + has_user_declined_tracing, + should_suppress_tracing_messages, + ) + + if should_suppress_tracing_messages(): + return console = Console() diff --git a/lib/crewai/src/crewai/events/listeners/tracing/trace_listener.py b/lib/crewai/src/crewai/events/listeners/tracing/trace_listener.py index ee337d7fd..0e4d7d8a2 100644 --- a/lib/crewai/src/crewai/events/listeners/tracing/trace_listener.py +++ b/lib/crewai/src/crewai/events/listeners/tracing/trace_listener.py @@ -797,7 +797,13 @@ class TraceCollectionListener(BaseEventListener): from rich.console import Console from rich.panel import Panel - from crewai.events.listeners.tracing.utils import has_user_declined_tracing + from crewai.events.listeners.tracing.utils import ( + has_user_declined_tracing, + should_suppress_tracing_messages, + ) + + if should_suppress_tracing_messages(): + return console = Console() diff --git a/lib/crewai/src/crewai/events/listeners/tracing/utils.py b/lib/crewai/src/crewai/events/listeners/tracing/utils.py index b6bf1026b..a98142619 100644 --- a/lib/crewai/src/crewai/events/listeners/tracing/utils.py +++ b/lib/crewai/src/crewai/events/listeners/tracing/utils.py @@ -27,7 +27,34 @@ logger = logging.getLogger(__name__) _tracing_enabled: ContextVar[bool | None] = ContextVar("_tracing_enabled", default=None) -_first_time_trace_hook: Callable[[], bool] | None = None +_first_time_trace_hook: ContextVar[Callable[[], bool] | None] = ContextVar( + "_first_time_trace_hook", default=None +) + +_suppress_tracing_messages: ContextVar[bool] = ContextVar( + "_suppress_tracing_messages", default=False +) + + +def set_suppress_tracing_messages(suppress: bool) -> object: + """Set whether to suppress tracing-related console messages. + + Args: + suppress: True to suppress messages, False to show them. + + Returns: + A token that can be used to restore the previous value. + """ + return _suppress_tracing_messages.set(suppress) + + +def should_suppress_tracing_messages() -> bool: + """Check if tracing messages should be suppressed. + + Returns: + True if messages should be suppressed, False otherwise. + """ + return _suppress_tracing_messages.get() def should_enable_tracing(*, override: bool | None = None) -> bool: @@ -413,8 +440,9 @@ def should_auto_collect_first_time_traces() -> bool: Returns: True if first-time user AND telemetry not disabled AND tracing not explicitly enabled, False otherwise. """ - if _first_time_trace_hook is not None: - return _first_time_trace_hook() + hook = _first_time_trace_hook.get() + if hook is not None: + return hook() if _is_test_environment(): return False @@ -437,6 +465,9 @@ def prompt_user_for_trace_viewing(timeout_seconds: int = 20) -> bool: if _is_test_environment(): return False + if should_suppress_tracing_messages(): + return False + try: import threading diff --git a/lib/crewai/src/crewai/events/utils/console_formatter.py b/lib/crewai/src/crewai/events/utils/console_formatter.py index eaecc0e74..ee466c344 100644 --- a/lib/crewai/src/crewai/events/utils/console_formatter.py +++ b/lib/crewai/src/crewai/events/utils/console_formatter.py @@ -1,3 +1,4 @@ +from contextvars import ContextVar import os import threading from typing import Any, ClassVar, cast @@ -10,6 +11,36 @@ from rich.text import Text from crewai.cli.version import is_newer_version_available +_disable_version_check: ContextVar[bool] = ContextVar( + "_disable_version_check", default=False +) + +_suppress_console_output: ContextVar[bool] = ContextVar( + "_suppress_console_output", default=False +) + + +def set_suppress_console_output(suppress: bool) -> object: + """Set whether to suppress all console output. + + Args: + suppress: True to suppress output, False to show it. + + Returns: + A token that can be used to restore the previous value. + """ + return _suppress_console_output.set(suppress) + + +def should_suppress_console_output() -> bool: + """Check if console output should be suppressed. + + Returns: + True if output should be suppressed, False otherwise. + """ + return _suppress_console_output.get() + + class ConsoleFormatter: tool_usage_counts: ClassVar[dict[str, int]] = {} @@ -46,6 +77,9 @@ class ConsoleFormatter: if not self.verbose: return + if _disable_version_check.get(): + return + if os.getenv("CI", "").lower() in ("true", "1"): return @@ -79,8 +113,12 @@ To update, run: uv sync --upgrade-package crewai""" from crewai.events.listeners.tracing.utils import ( has_user_declined_tracing, is_tracing_enabled_in_context, + should_suppress_tracing_messages, ) + if should_suppress_tracing_messages(): + return + if not is_tracing_enabled_in_context(): if has_user_declined_tracing(): message = """Info: Tracing is disabled. @@ -132,6 +170,8 @@ To enable tracing, do any one of these: def print(self, *args: Any, **kwargs: Any) -> None: """Print to console. Simplified to only handle panel-based output.""" + if should_suppress_console_output(): + return # Skip blank lines during streaming if len(args) == 0 and self._is_streaming: return @@ -488,6 +528,9 @@ To enable tracing, do any one of these: if not self.verbose: return + if should_suppress_console_output(): + return + self._is_streaming = True self._last_stream_call_type = call_type diff --git a/lib/crewai/src/crewai/flow/flow.py b/lib/crewai/src/crewai/flow/flow.py index 1b4665620..64bd86d53 100644 --- a/lib/crewai/src/crewai/flow/flow.py +++ b/lib/crewai/src/crewai/flow/flow.py @@ -45,6 +45,7 @@ from crewai.events.listeners.tracing.utils import ( has_user_declined_tracing, set_tracing_enabled, should_enable_tracing, + should_suppress_tracing_messages, ) from crewai.events.types.flow_events import ( FlowCreatedEvent, @@ -2074,12 +2075,14 @@ class Flow(Generic[T], metaclass=FlowMeta): racing_members, other_listeners, listener_result, - triggering_event_id, + current_triggering_event_id, ) else: tasks = [ self._execute_single_listener( - listener_name, listener_result, triggering_event_id + listener_name, + listener_result, + current_triggering_event_id, ) for listener_name in listeners_triggered ] @@ -2626,6 +2629,8 @@ class Flow(Generic[T], metaclass=FlowMeta): @staticmethod def _show_tracing_disabled_message() -> None: """Show a message when tracing is disabled.""" + if should_suppress_tracing_messages(): + return console = Console() diff --git a/lib/crewai/src/crewai/tasks/hallucination_guardrail.py b/lib/crewai/src/crewai/tasks/hallucination_guardrail.py index dd000a83c..c48b890a8 100644 --- a/lib/crewai/src/crewai/tasks/hallucination_guardrail.py +++ b/lib/crewai/src/crewai/tasks/hallucination_guardrail.py @@ -6,6 +6,7 @@ Classes: HallucinationGuardrail: Placeholder guardrail that validates task outputs. """ +from collections.abc import Callable from typing import Any from crewai.llm import LLM @@ -13,32 +14,36 @@ from crewai.tasks.task_output import TaskOutput from crewai.utilities.logger import Logger +_validate_output_hook: Callable[..., tuple[bool, Any]] | None = None + + class HallucinationGuardrail: """Placeholder for the HallucinationGuardrail feature. Attributes: - context: The reference context that outputs would be checked against. + context: Optional reference context that outputs would be checked against. llm: The language model that would be used for evaluation. threshold: Optional minimum faithfulness score that would be required to pass. tool_response: Optional tool response information that would be used in evaluation. Examples: - >>> # Basic usage with default verdict logic + >>> # Basic usage without context (uses task expected_output as context) + >>> guardrail = HallucinationGuardrail(llm=agent.llm) + + >>> # With context for reference >>> guardrail = HallucinationGuardrail( - ... context="AI helps with various tasks including analysis and generation.", ... llm=agent.llm, + ... context="AI helps with various tasks including analysis and generation.", ... ) >>> # With custom threshold for stricter validation >>> strict_guardrail = HallucinationGuardrail( - ... context="Quantum computing uses qubits in superposition.", ... llm=agent.llm, - ... threshold=8.0, # Would require score >= 8 to pass in enterprise version + ... threshold=8.0, # Require score >= 8 to pass ... ) >>> # With tool response for additional context >>> guardrail_with_tools = HallucinationGuardrail( - ... context="The current weather data", ... llm=agent.llm, ... tool_response="Weather API returned: Temperature 22°C, Humidity 65%", ... ) @@ -46,16 +51,17 @@ class HallucinationGuardrail: def __init__( self, - context: str, llm: LLM, + context: str | None = None, threshold: float | None = None, tool_response: str = "", ): """Initialize the HallucinationGuardrail placeholder. Args: - context: The reference context that outputs would be checked against. llm: The language model that would be used for evaluation. + context: Optional reference context that outputs would be checked against. + If not provided, the task's expected_output will be used as context. threshold: Optional minimum faithfulness score that would be required to pass. tool_response: Optional tool response information that would be used in evaluation. """ @@ -78,16 +84,17 @@ class HallucinationGuardrail: def __call__(self, task_output: TaskOutput) -> tuple[bool, Any]: """Validate a task output against hallucination criteria. - In the open source, this method always returns that the output is valid. - Args: task_output: The output to be validated. Returns: A tuple containing: - - True - - The raw task output + - True if validation passed, False otherwise + - The raw task output if valid, or error feedback if invalid """ + if callable(_validate_output_hook): + return _validate_output_hook(self, task_output) + self._logger.log( "warning", "Premium hallucination detection skipped (use for free at https://app.crewai.com)\n", diff --git a/lib/crewai/src/crewai/utilities/agent_utils.py b/lib/crewai/src/crewai/utilities/agent_utils.py index 1ede7f07d..ee76dc53f 100644 --- a/lib/crewai/src/crewai/utilities/agent_utils.py +++ b/lib/crewai/src/crewai/utilities/agent_utils.py @@ -42,6 +42,8 @@ if TYPE_CHECKING: from crewai.llm import LLM from crewai.task import Task +_create_plus_client_hook: Callable[[], Any] | None = None + class SummaryContent(TypedDict): """Structure for summary content entries. @@ -91,7 +93,11 @@ def parse_tools(tools: list[BaseTool]) -> list[CrewStructuredTool]: for tool in tools: if isinstance(tool, CrewAITool): - tools_list.append(tool.to_structured_tool()) + structured_tool = tool.to_structured_tool() + structured_tool.current_usage_count = 0 + if structured_tool._original_tool: + structured_tool._original_tool.current_usage_count = 0 + tools_list.append(structured_tool) else: raise ValueError("Tool is not a CrewStructuredTool or BaseTool") @@ -818,10 +824,13 @@ def load_agent_from_repository(from_repository: str) -> dict[str, Any]: if from_repository: import importlib - from crewai.cli.authentication.token import get_auth_token - from crewai.cli.plus_api import PlusAPI + if callable(_create_plus_client_hook): + client = _create_plus_client_hook() + else: + from crewai.cli.authentication.token import get_auth_token + from crewai.cli.plus_api import PlusAPI - client = PlusAPI(api_key=get_auth_token()) + client = PlusAPI(api_key=get_auth_token()) _print_current_organization() response = client.get_agent(from_repository) if response.status_code == 404: diff --git a/lib/crewai/src/crewai/utilities/printer.py b/lib/crewai/src/crewai/utilities/printer.py index c40de684e..949da543a 100644 --- a/lib/crewai/src/crewai/utilities/printer.py +++ b/lib/crewai/src/crewai/utilities/printer.py @@ -4,6 +4,8 @@ from __future__ import annotations from typing import TYPE_CHECKING, Final, Literal, NamedTuple +from crewai.events.utils.console_formatter import should_suppress_console_output + if TYPE_CHECKING: from _typeshed import SupportsWrite @@ -77,6 +79,8 @@ class Printer: file: A file-like object (stream); defaults to the current sys.stdout. flush: Whether to forcibly flush the stream. """ + if should_suppress_console_output(): + return if isinstance(content, str): content = [ColoredText(content, color)] print( diff --git a/lib/crewai/tests/agents/agent_adapters/test_base_agent_adapter.py b/lib/crewai/tests/agents/agent_adapters/test_base_agent_adapter.py index 6ed42b5d1..78320d187 100644 --- a/lib/crewai/tests/agents/agent_adapters/test_base_agent_adapter.py +++ b/lib/crewai/tests/agents/agent_adapters/test_base_agent_adapter.py @@ -51,6 +51,10 @@ class ConcreteAgentAdapter(BaseAgentAdapter): # Dummy implementation for MCP tools return [] + def configure_structured_output(self, task: Any) -> None: + # Dummy implementation for structured output + pass + async def aexecute_task( self, task: Any, From 1308bdee63f52e000f5cf201c5a97fa96c825bff Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Thu, 5 Feb 2026 21:28:23 -0500 Subject: [PATCH 09/14] feat: add started_event_id and set in eventbus * feat: add started_event_id and set in eventbus * chore: update additional test assumption * fix: restore event bus handlers on context exit fix rollback in crewai events bus so that exiting the context restores the previous _sync_handlers, _async_handlers, _handler_dependencies, and _execution_plan_cache by assigning shallow copies of the saved dicts. previously these were set to empty dicts on exit, which caused registered handlers and cached execution plans to be lost. --- lib/crewai/src/crewai/events/base_events.py | 1 + lib/crewai/src/crewai/events/event_bus.py | 59 ++++++++++++++----- .../tests/memory/test_external_memory.py | 4 ++ .../tests/memory/test_long_term_memory.py | 4 ++ .../tests/memory/test_short_term_memory.py | 4 ++ 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/lib/crewai/src/crewai/events/base_events.py b/lib/crewai/src/crewai/events/base_events.py index 7148e5e1d..6eeaa06e8 100644 --- a/lib/crewai/src/crewai/events/base_events.py +++ b/lib/crewai/src/crewai/events/base_events.py @@ -63,6 +63,7 @@ class BaseEvent(BaseModel): parent_event_id: str | None = None previous_event_id: str | None = None triggered_by_event_id: str | None = None + started_event_id: str | None = None emission_sequence: int | None = None def to_json(self, exclude: set[str] | None = None) -> Serializable: diff --git a/lib/crewai/src/crewai/events/event_bus.py b/lib/crewai/src/crewai/events/event_bus.py index d0aaa4455..b30d469b9 100644 --- a/lib/crewai/src/crewai/events/event_bus.py +++ b/lib/crewai/src/crewai/events/event_bus.py @@ -407,7 +407,8 @@ class CrewAIEventsBus: if popped is None: handle_empty_pop(event_type_name) else: - _, popped_type = popped + popped_event_id, popped_type = popped + event.started_event_id = popped_event_id expected_start = VALID_EVENT_PAIRS.get(event_type_name) if expected_start and popped_type and popped_type != expected_start: handle_mismatch(event_type_name, popped_type, expected_start) @@ -569,24 +570,52 @@ class CrewAIEventsBus: ... # Do stuff... ... # Handlers are cleared after the context """ - with self._rwlock.w_locked(): - prev_sync = self._sync_handlers - prev_async = self._async_handlers - prev_deps = self._handler_dependencies - prev_cache = self._execution_plan_cache - self._sync_handlers = {} - self._async_handlers = {} - self._handler_dependencies = {} - self._execution_plan_cache = {} + with self._rwlock.r_locked(): + saved_sync: dict[type[BaseEvent], frozenset[SyncHandler]] = dict( + self._sync_handlers + ) + saved_async: dict[type[BaseEvent], frozenset[AsyncHandler]] = dict( + self._async_handlers + ) + saved_deps: dict[type[BaseEvent], dict[Handler, list[Depends[Any]]]] = { + event_type: dict(handlers) + for event_type, handlers in self._handler_dependencies.items() + } + + for event_type, sync_handlers in saved_sync.items(): + for sync_handler in sync_handlers: + self.off(event_type, sync_handler) + + for event_type, async_handlers in saved_async.items(): + for async_handler in async_handlers: + self.off(event_type, async_handler) try: yield finally: - with self._rwlock.w_locked(): - self._sync_handlers = prev_sync - self._async_handlers = prev_async - self._handler_dependencies = prev_deps - self._execution_plan_cache = prev_cache + with self._rwlock.r_locked(): + current_sync = dict(self._sync_handlers) + current_async = dict(self._async_handlers) + + for event_type, cur_sync in current_sync.items(): + orig_sync = saved_sync.get(event_type, frozenset()) + for new_handler in cur_sync - orig_sync: + self.off(event_type, new_handler) + + for event_type, cur_async in current_async.items(): + orig_async = saved_async.get(event_type, frozenset()) + for new_async_handler in cur_async - orig_async: + self.off(event_type, new_async_handler) + + for event_type, sync_handlers in saved_sync.items(): + for sync_handler in sync_handlers: + deps = saved_deps.get(event_type, {}).get(sync_handler) + self._register_handler(event_type, sync_handler, deps) + + for event_type, async_handlers in saved_async.items(): + for async_handler in async_handlers: + deps = saved_deps.get(event_type, {}).get(async_handler) + self._register_handler(event_type, async_handler, deps) def shutdown(self, wait: bool = True) -> None: """Gracefully shutdown the event loop and wait for all tasks to finish. diff --git a/lib/crewai/tests/memory/test_external_memory.py b/lib/crewai/tests/memory/test_external_memory.py index 8718c5aca..1872bc0af 100644 --- a/lib/crewai/tests/memory/test_external_memory.py +++ b/lib/crewai/tests/memory/test_external_memory.py @@ -308,6 +308,7 @@ def test_external_memory_search_events( "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "query": "test value", "limit": 3, @@ -330,6 +331,7 @@ def test_external_memory_search_events( "parent_event_id": ANY, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "query": "test value", "results": [], @@ -390,6 +392,7 @@ def test_external_memory_save_events( "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "value": "saving value", "metadata": {"task": "test_task"}, @@ -411,6 +414,7 @@ def test_external_memory_save_events( "parent_event_id": ANY, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "value": "saving value", "metadata": {"task": "test_task"}, diff --git a/lib/crewai/tests/memory/test_long_term_memory.py b/lib/crewai/tests/memory/test_long_term_memory.py index c33e4469b..500fab169 100644 --- a/lib/crewai/tests/memory/test_long_term_memory.py +++ b/lib/crewai/tests/memory/test_long_term_memory.py @@ -74,6 +74,7 @@ def test_long_term_memory_save_events(long_term_memory): "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "value": "test_task", "metadata": {"task": "test_task", "quality": 0.5}, @@ -94,6 +95,7 @@ def test_long_term_memory_save_events(long_term_memory): "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "value": "test_task", "metadata": { @@ -153,6 +155,7 @@ def test_long_term_memory_search_events(long_term_memory): "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "query": "test query", "limit": 5, @@ -175,6 +178,7 @@ def test_long_term_memory_search_events(long_term_memory): "parent_event_id": ANY, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "query": "test query", "results": None, diff --git a/lib/crewai/tests/memory/test_short_term_memory.py b/lib/crewai/tests/memory/test_short_term_memory.py index 8ea64553a..5e74b688d 100644 --- a/lib/crewai/tests/memory/test_short_term_memory.py +++ b/lib/crewai/tests/memory/test_short_term_memory.py @@ -85,6 +85,7 @@ def test_short_term_memory_search_events(short_term_memory): "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "query": "test value", "limit": 3, @@ -107,6 +108,7 @@ def test_short_term_memory_search_events(short_term_memory): "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "query": "test value", "results": [], @@ -164,6 +166,7 @@ def test_short_term_memory_save_events(short_term_memory): "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "value": "test value", "metadata": {"task": "test_task"}, @@ -185,6 +188,7 @@ def test_short_term_memory_save_events(short_term_memory): "parent_event_id": None, "previous_event_id": ANY, "triggered_by_event_id": None, + "started_event_id": ANY, "emission_sequence": ANY, "value": "test value", "metadata": {"task": "test_task"}, From 7d498b29beaf626a19ae3ef61900594b41f8fcef Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Fri, 6 Feb 2026 14:02:43 -0500 Subject: [PATCH 10/14] fix: event ordering; flow state locks, routing * fix: add current task id context and flow updates introduce a context var for the current task id in `crewai.context` to track task scope. update `Flow._execute_single_listener` to return `(result, event_id)` and adjust callers to unpack it and append `FlowMethodName(str(result))` to `router_results`. set/reset the current task id at the start/end of task execution (async + sync) with minor import and call-site tweaks. * fix: await event futures and flush event bus call `crewai_event_bus.flush()` after crew kickoff. in `Flow`, await event handler futures instead of just collecting them: await pending `_event_futures` before finishing, await emitted futures immediately with try/except to log failures, then clear `_event_futures`. ensures handlers complete and errors surface. * fix: continue iteration on tool completion events expand the loop bridge listener to also trigger on tool completion events (`tool_completed` and `native_tool_completed`) so agent iteration resumes after tools finish. add a `requests.post` mock and response fixture in the liteagent test to simulate platform tool execution. refresh and sanitize vcr cassettes (updated model responses, timestamps, and header placeholders) to reflect tool-call flows and new recordings. * fix: thread-safe state proxies & native routing add thread-safe state proxies and refactor native tool routing. * introduce `LockedListProxy` and `LockedDictProxy` in `flow.py` and update `StateProxy` to return them for list/dict attrs so mutations are protected by the flow lock. * update `AgentExecutor` to use `StateProxy` on flow init, guard the messages setter with the state lock, and return a `StateProxy` from the temp state accessor. * convert `call_llm_native_tools` into a listener (no direct routing return) and add `route_native_tool_result` to route based on state (pending tool calls, final answer, or context error). * minor cleanup in `continue_iteration` to drop orphan listeners on init. * update test cassettes for new native tool call responses, timestamps, and ids. improves concurrency safety for shared state and makes native tool routing explicit. * chore: regen cassettes * chore: regen cassettes, remove duplicate listener call path --- lib/crewai/src/crewai/context.py | 20 + lib/crewai/src/crewai/crew.py | 1 + .../src/crewai/experimental/agent_executor.py | 51 +- lib/crewai/src/crewai/flow/flow.py | 229 ++++++--- lib/crewai/src/crewai/task.py | 5 + lib/crewai/tests/agents/test_lite_agent.py | 12 +- ...est_agent_kickoff_with_platform_tools.yaml | 193 ++++++-- .../test_guardrail_reached_attempt_limit.yaml | 460 ++++++++++-------- .../test_lite_agent_inside_flow_sync.yaml | 37 +- ...est_lite_agent_standalone_still_works.yaml | 37 +- .../test_multiple_agents_in_same_flow.yaml | 77 +-- ...st_native_tool_calling_error_handling.yaml | 65 ++- ..._kickoff_structured_output_with_tools.yaml | 12 +- ...ckoff_structured_output_without_tools.yaml | 21 +- 14 files changed, 745 insertions(+), 475 deletions(-) diff --git a/lib/crewai/src/crewai/context.py b/lib/crewai/src/crewai/context.py index 8edc4fdfb..bf73a221c 100644 --- a/lib/crewai/src/crewai/context.py +++ b/lib/crewai/src/crewai/context.py @@ -43,3 +43,23 @@ def platform_context(integration_token: str) -> Generator[None, Any, None]: yield finally: _platform_integration_token.reset(token) + + +_current_task_id: contextvars.ContextVar[str | None] = contextvars.ContextVar( + "current_task_id", default=None +) + + +def set_current_task_id(task_id: str | None) -> contextvars.Token[str | None]: + """Set the current task ID in the context. Returns a token for reset.""" + return _current_task_id.set(task_id) + + +def reset_current_task_id(token: contextvars.Token[str | None]) -> None: + """Reset the current task ID to its previous value.""" + _current_task_id.reset(token) + + +def get_current_task_id() -> str | None: + """Get the current task ID from the context.""" + return _current_task_id.get() diff --git a/lib/crewai/src/crewai/crew.py b/lib/crewai/src/crewai/crew.py index 09a103eba..c69dae65a 100644 --- a/lib/crewai/src/crewai/crew.py +++ b/lib/crewai/src/crewai/crew.py @@ -1517,6 +1517,7 @@ class Crew(FlowTrackable, BaseModel): final_string_output = final_task_output.raw self._finish_execution(final_string_output) self.token_usage = self.calculate_usage_metrics() + crewai_event_bus.flush() crewai_event_bus.emit( self, CrewKickoffCompletedEvent( diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index 87f9e83d4..037df6793 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -32,7 +32,8 @@ from crewai.events.types.tool_usage_events import ( ToolUsageFinishedEvent, ToolUsageStartedEvent, ) -from crewai.flow.flow import Flow, listen, or_, router, start +from crewai.flow.flow import Flow, StateProxy, listen, or_, router, start +from crewai.flow.types import FlowMethodName from crewai.hooks.llm_hooks import ( get_after_llm_call_hooks, get_before_llm_call_hooks, @@ -225,7 +226,11 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): @messages.setter def messages(self, value: list[LLMMessage]) -> None: """Delegate to state for ExecutorContext conformance.""" - self._state.messages = value + if self._flow_initialized and hasattr(self, "_state_lock"): + with self._state_lock: + self._state.messages = value + else: + self._state.messages = value @property def ask_for_human_input(self) -> bool: @@ -353,6 +358,8 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): Flow initialization is deferred to prevent event emission during agent setup. Returns the temporary state until invoke() is called. """ + if self._flow_initialized and hasattr(self, "_state_lock"): + return StateProxy(self._state, self._state_lock) # type: ignore[return-value] return self._state @property @@ -461,15 +468,14 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): raise @listen("continue_reasoning_native") - def call_llm_native_tools( - self, - ) -> Literal["native_tool_calls", "native_finished", "context_error"]: + def call_llm_native_tools(self) -> None: """Execute LLM call with native function calling. Always calls the LLM so it can read reflection prompts and decide whether to provide a final answer or request more tools. - Returns routing decision based on whether tool calls or final answer. + Note: This is a listener, not a router. The route_native_tool_result + router fires after this to determine the next step based on state. """ try: # Clear pending tools - LLM will decide what to do next after reading @@ -499,8 +505,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): if isinstance(answer, list) and answer and self._is_tool_call_list(answer): # Store tool calls for sequential processing self.state.pending_tool_calls = list(answer) - - return "native_tool_calls" + return # Router will check pending_tool_calls if isinstance(answer, BaseModel): self.state.current_answer = AgentFinish( @@ -510,7 +515,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): ) self._invoke_step_callback(self.state.current_answer) self._append_message_to_state(answer.model_dump_json()) - return "native_finished" + return # Router will check current_answer # Text response - this is the final answer if isinstance(answer, str): @@ -521,8 +526,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): ) self._invoke_step_callback(self.state.current_answer) self._append_message_to_state(answer) - - return "native_finished" + return # Router will check current_answer # Unexpected response type, treat as final answer self.state.current_answer = AgentFinish( @@ -532,13 +536,12 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): ) self._invoke_step_callback(self.state.current_answer) self._append_message_to_state(str(answer)) - - return "native_finished" + # Router will check current_answer except Exception as e: if is_context_length_exceeded(e): self._last_context_error = e - return "context_error" + return # Router will check _last_context_error if e.__class__.__module__.startswith("litellm"): raise e handle_unknown_error(self._printer, e, verbose=self.agent.verbose) @@ -551,6 +554,22 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): return "execute_tool" return "agent_finished" + @router(call_llm_native_tools) + def route_native_tool_result( + self, + ) -> Literal["native_tool_calls", "native_finished", "context_error"]: + """Route based on LLM response for native tool calling. + + Checks state set by call_llm_native_tools to determine next step. + This router is needed because only router return values trigger + downstream listeners. + """ + if self._last_context_error is not None: + return "context_error" + if self.state.pending_tool_calls: + return "native_tool_calls" + return "native_finished" + @listen("execute_tool") def execute_tool_action(self) -> Literal["tool_completed", "tool_result_is_final"]: """Execute the tool action and handle the result.""" @@ -908,9 +927,11 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): self.state.iterations += 1 return "initialized" - @listen("initialized") + @listen(or_("initialized", "tool_completed", "native_tool_completed")) def continue_iteration(self) -> Literal["check_iteration"]: """Bridge listener that connects iteration loop back to iteration check.""" + if self._flow_initialized: + self._discard_or_listener(FlowMethodName("continue_iteration")) return "check_iteration" @router(or_(initialize_reasoning, continue_iteration)) diff --git a/lib/crewai/src/crewai/flow/flow.py b/lib/crewai/src/crewai/flow/flow.py index 64bd86d53..5a6ac4557 100644 --- a/lib/crewai/src/crewai/flow/flow.py +++ b/lib/crewai/src/crewai/flow/flow.py @@ -7,7 +7,14 @@ for building event-driven workflows with conditional execution and routing. from __future__ import annotations import asyncio -from collections.abc import Callable, Sequence +from collections.abc import ( + Callable, + ItemsView, + Iterator, + KeysView, + Sequence, + ValuesView, +) from concurrent.futures import Future import copy import inspect @@ -409,6 +416,132 @@ def and_(*conditions: str | FlowCondition | Callable[..., Any]) -> FlowCondition return {"type": AND_CONDITION, "conditions": processed_conditions} +class LockedListProxy(Generic[T]): + """Thread-safe proxy for list operations. + + Wraps a list and uses a lock for all mutating operations. + """ + + def __init__(self, lst: list[T], lock: threading.Lock) -> None: + self._list = lst + self._lock = lock + + def append(self, item: T) -> None: + with self._lock: + self._list.append(item) + + def extend(self, items: list[T]) -> None: + with self._lock: + self._list.extend(items) + + def insert(self, index: int, item: T) -> None: + with self._lock: + self._list.insert(index, item) + + def remove(self, item: T) -> None: + with self._lock: + self._list.remove(item) + + def pop(self, index: int = -1) -> T: + with self._lock: + return self._list.pop(index) + + def clear(self) -> None: + with self._lock: + self._list.clear() + + def __setitem__(self, index: int, value: T) -> None: + with self._lock: + self._list[index] = value + + def __delitem__(self, index: int) -> None: + with self._lock: + del self._list[index] + + def __getitem__(self, index: int) -> T: + return self._list[index] + + def __len__(self) -> int: + return len(self._list) + + def __iter__(self) -> Iterator[T]: + return iter(self._list) + + def __contains__(self, item: object) -> bool: + return item in self._list + + def __repr__(self) -> str: + return repr(self._list) + + def __bool__(self) -> bool: + return bool(self._list) + + +class LockedDictProxy(Generic[T]): + """Thread-safe proxy for dict operations. + + Wraps a dict and uses a lock for all mutating operations. + """ + + def __init__(self, d: dict[str, T], lock: threading.Lock) -> None: + self._dict = d + self._lock = lock + + def __setitem__(self, key: str, value: T) -> None: + with self._lock: + self._dict[key] = value + + def __delitem__(self, key: str) -> None: + with self._lock: + del self._dict[key] + + def pop(self, key: str, *default: T) -> T: + with self._lock: + return self._dict.pop(key, *default) + + def update(self, other: dict[str, T]) -> None: + with self._lock: + self._dict.update(other) + + def clear(self) -> None: + with self._lock: + self._dict.clear() + + def setdefault(self, key: str, default: T) -> T: + with self._lock: + return self._dict.setdefault(key, default) + + def __getitem__(self, key: str) -> T: + return self._dict[key] + + def __len__(self) -> int: + return len(self._dict) + + def __iter__(self) -> Iterator[str]: + return iter(self._dict) + + def __contains__(self, key: object) -> bool: + return key in self._dict + + def keys(self) -> KeysView[str]: + return self._dict.keys() + + def values(self) -> ValuesView[T]: + return self._dict.values() + + def items(self) -> ItemsView[str, T]: + return self._dict.items() + + def get(self, key: str, default: T | None = None) -> T | None: + return self._dict.get(key, default) + + def __repr__(self) -> str: + return repr(self._dict) + + def __bool__(self) -> bool: + return bool(self._dict) + + class StateProxy(Generic[T]): """Proxy that provides thread-safe access to flow state. @@ -423,7 +556,13 @@ class StateProxy(Generic[T]): object.__setattr__(self, "_proxy_lock", lock) def __getattr__(self, name: str) -> Any: - return getattr(object.__getattribute__(self, "_proxy_state"), name) + value = getattr(object.__getattribute__(self, "_proxy_state"), name) + lock = object.__getattribute__(self, "_proxy_lock") + if isinstance(value, list): + return LockedListProxy(value, lock) + if isinstance(value, dict): + return LockedDictProxy(value, lock) + return value def __setattr__(self, name: str, value: Any) -> None: if name in ("_proxy_state", "_proxy_lock"): @@ -1593,7 +1732,6 @@ class Flow(Generic[T], metaclass=FlowMeta): reset_emission_counter() reset_last_event_id() - # Emit FlowStartedEvent and log the start of the flow. if not self.suppress_flow_events: future = crewai_event_bus.emit( self, @@ -1604,7 +1742,10 @@ class Flow(Generic[T], metaclass=FlowMeta): ), ) if future: - self._event_futures.append(future) + try: + await asyncio.wrap_future(future) + except Exception: + logger.warning("FlowStartedEvent handler failed", exc_info=True) self._log_flow_event( f"Flow started with ID: {self.flow_id}", color="bold magenta" ) @@ -1696,6 +1837,12 @@ class Flow(Generic[T], metaclass=FlowMeta): final_output = self._method_outputs[-1] if self._method_outputs else None + if self._event_futures: + await asyncio.gather( + *[asyncio.wrap_future(f) for f in self._event_futures] + ) + self._event_futures.clear() + if not self.suppress_flow_events: future = crewai_event_bus.emit( self, @@ -1707,13 +1854,12 @@ class Flow(Generic[T], metaclass=FlowMeta): ), ) if future: - self._event_futures.append(future) - - if self._event_futures: - await asyncio.gather( - *[asyncio.wrap_future(f) for f in self._event_futures] - ) - self._event_futures.clear() + try: + await asyncio.wrap_future(future) + except Exception: + logger.warning( + "FlowFinishedEvent handler failed", exc_info=True + ) if not self.suppress_flow_events: trace_listener = TraceCollectionListener() @@ -2027,15 +2173,14 @@ class Flow(Generic[T], metaclass=FlowMeta): router_input = router_result_to_feedback.get( str(current_trigger), current_result ) - current_triggering_event_id = await self._execute_single_listener( + ( + router_result, + current_triggering_event_id, + ) = await self._execute_single_listener( router_name, router_input, current_triggering_event_id ) - # After executing router, the router's result is the path - router_result = ( - self._method_outputs[-1] if self._method_outputs else None - ) if router_result: # Only add non-None results - router_results.append(router_result) + router_results.append(FlowMethodName(str(router_result))) # If this was a human_feedback router, map the outcome to the feedback if self.last_human_feedback is not None: router_result_to_feedback[str(router_result)] = ( @@ -2265,7 +2410,7 @@ class Flow(Generic[T], metaclass=FlowMeta): listener_name: FlowMethodName, result: Any, triggering_event_id: str | None = None, - ) -> str | None: + ) -> tuple[Any, str | None]: """Executes a single listener method with proper event handling. This internal method manages the execution of an individual listener, @@ -2278,8 +2423,9 @@ class Flow(Generic[T], metaclass=FlowMeta): used for causal chain tracking. Returns: - The event_id of the MethodExecutionFinishedEvent emitted by this listener, - or None if events are suppressed. + A tuple of (listener_result, event_id) where listener_result is the return + value of the listener method and event_id is the MethodExecutionFinishedEvent + id, or (None, None) if skipped during resumption. Note: - Inspects method signature to determine if it accepts the trigger result @@ -2305,7 +2451,7 @@ class Flow(Generic[T], metaclass=FlowMeta): ): # This conditional start was executed, continue its chain await self._execute_start_method(start_method_name) - return None + return (None, None) # For cyclic flows, clear from completed to allow re-execution self._completed_methods.discard(listener_name) # Also clear from fired OR listeners for cyclic flows @@ -2343,46 +2489,7 @@ class Flow(Generic[T], metaclass=FlowMeta): listener_name, listener_result, finished_event_id ) - # If this listener is also a router (e.g., has @human_feedback with emit), - # we need to trigger listeners for the router result as well - if listener_name in self._routers and listener_result is not None: - router_result_trigger = FlowMethodName(str(listener_result)) - listeners_for_result = self._find_triggered_methods( - router_result_trigger, router_only=False - ) - if listeners_for_result: - # Pass the HumanFeedbackResult if available - feedback_result = ( - self.last_human_feedback - if self.last_human_feedback is not None - else listener_result - ) - racing_group = self._get_racing_group_for_listeners( - listeners_for_result - ) - if racing_group: - racing_members, _ = racing_group - other_listeners = [ - name - for name in listeners_for_result - if name not in racing_members - ] - await self._execute_racing_listeners( - racing_members, - other_listeners, - feedback_result, - finished_event_id, - ) - else: - tasks = [ - self._execute_single_listener( - name, feedback_result, finished_event_id - ) - for name in listeners_for_result - ] - await asyncio.gather(*tasks) - - return finished_event_id + return (listener_result, finished_event_id) except Exception as e: # Don't log HumanFeedbackPending as an error - it's expected control flow diff --git a/lib/crewai/src/crewai/task.py b/lib/crewai/src/crewai/task.py index d73c3d919..eac42f956 100644 --- a/lib/crewai/src/crewai/task.py +++ b/lib/crewai/src/crewai/task.py @@ -31,6 +31,7 @@ from pydantic_core import PydanticCustomError from typing_extensions import Self from crewai.agents.agent_builder.base_agent import BaseAgent +from crewai.context import reset_current_task_id, set_current_task_id from crewai.core.providers.content_processor import process_content from crewai.events.event_bus import crewai_event_bus from crewai.events.types.task_events import ( @@ -561,6 +562,7 @@ class Task(BaseModel): tools: list[Any] | None, ) -> TaskOutput: """Run the core execution logic of the task asynchronously.""" + task_id_token = set_current_task_id(str(self.id)) self._store_input_files() try: agent = agent or self.agent @@ -648,6 +650,7 @@ class Task(BaseModel): raise e # Re-raise the exception after emitting the event finally: clear_task_files(self.id) + reset_current_task_id(task_id_token) def _execute_core( self, @@ -656,6 +659,7 @@ class Task(BaseModel): tools: list[Any] | None, ) -> TaskOutput: """Run the core execution logic of the task.""" + task_id_token = set_current_task_id(str(self.id)) self._store_input_files() try: agent = agent or self.agent @@ -744,6 +748,7 @@ class Task(BaseModel): raise e # Re-raise the exception after emitting the event finally: clear_task_files(self.id) + reset_current_task_id(task_id_token) def _post_agent_execution(self, agent: BaseAgent) -> None: pass diff --git a/lib/crewai/tests/agents/test_lite_agent.py b/lib/crewai/tests/agents/test_lite_agent.py index 32a7c0ef1..6f989a27c 100644 --- a/lib/crewai/tests/agents/test_lite_agent.py +++ b/lib/crewai/tests/agents/test_lite_agent.py @@ -606,9 +606,10 @@ def test_lite_agent_with_invalid_llm(): @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"}) +@patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post") @patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get") @pytest.mark.vcr() -def test_agent_kickoff_with_platform_tools(mock_get): +def test_agent_kickoff_with_platform_tools(mock_get, mock_post): """Test that Agent.kickoff() properly integrates platform tools with LiteAgent""" mock_response = Mock() mock_response.raise_for_status.return_value = None @@ -632,6 +633,15 @@ def test_agent_kickoff_with_platform_tools(mock_get): } mock_get.return_value = mock_response + # Mock the platform tool execution + mock_post_response = Mock() + mock_post_response.ok = True + mock_post_response.json.return_value = { + "success": True, + "issue_url": "https://github.com/test/repo/issues/1" + } + mock_post.return_value = mock_post_response + agent = Agent( role="Test Agent", goal="Test goal", diff --git a/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_platform_tools.yaml b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_platform_tools.yaml index 53b4c57e2..72c629c70 100644 --- a/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_platform_tools.yaml +++ b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_platform_tools.yaml @@ -1,98 +1,227 @@ interactions: - request: - body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test backstory\nYour personal goal is: Test goal\n\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\nTool Name: create_issue\nTool Arguments: {''title'': {''description'': ''Issue title'', ''type'': ''str''}, ''body'': {''description'': ''Issue body'', ''type'': ''Union[str, NoneType]''}}\nTool Description: Create a GitHub issue\nDetailed Parameter Structure:\nObject with properties:\n - title: Issue title (required)\n - body: Issue body (optional)\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 [create_issue], 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": "Create a GitHub issue"}], "model": "gpt-3.5-turbo", "stream": false}' + body: '{"messages":[{"role":"system","content":"You are Test Agent. Test backstory\nYour + personal goal is: Test goal"},{"role":"user","content":"\nCurrent Task: Create + a GitHub issue"}],"model":"gpt-3.5-turbo","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_issue","description":"Create + a GitHub issue","strict":true,"parameters":{"additionalProperties":false,"properties":{"title":{"description":"Issue + title","title":"Title","type":"string"},"body":{"default":null,"description":"Issue + body","title":"Body","type":"string"}},"required":["title","body"],"type":"object"}}}]}' headers: + User-Agent: + - X-USER-AGENT-XXX accept: - application/json accept-encoding: - - gzip, deflate + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX connection: - keep-alive content-length: - - '1233' + - '596' content-type: - application/json host: - api.openai.com - user-agent: - - OpenAI/Python 1.109.1 x-stainless-arch: - - arm64 + - X-STAINLESS-ARCH-XXX x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - X-STAINLESS-OS-XXX x-stainless-package-version: - - 1.109.1 + - 1.83.0 x-stainless-read-timeout: - - '600' + - X-STAINLESS-READ-TIMEOUT-XXX x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-CULxKTEIB85AVItcEQ09z4Xi0JCID\",\n \"object\": \"chat.completion\",\n \"created\": 1761350274,\n \"model\": \"gpt-3.5-turbo-0125\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"I will need more specific information to create a GitHub issue. Could you please provide more details such as the title and body of the issue you would like to create?\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 255,\n \"completion_tokens\": 33,\n \"total_tokens\": 288,\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" + string: "{\n \"id\": \"chatcmpl-D6L3fqygkUIZ3bN4wvSpAhdaSk7MF\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403287,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_RuWuYzjzgRL3byVGhLlPi0rq\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"create_issue\",\n + \ \"arguments\": \"{\\\"title\\\":\\\"Test issue\\\",\\\"body\\\":\\\"This + is a test issue created for testing purposes.\\\"}\"\n }\n }\n + \ ],\n \"refusal\": null,\n \"annotations\": []\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 93,\n \"completion_tokens\": + 28,\n \"total_tokens\": 121,\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: - - 993d6b4be9862379-SJC + - CF-RAY-XXX Connection: - keep-alive Content-Type: - application/json Date: - - Fri, 24 Oct 2025 23:57:54 GMT + - Fri, 06 Feb 2026 18:41:28 GMT Server: - cloudflare Set-Cookie: - - __cf_bm=WY9bgemMDI_hUYISAPlQ2a.DBGeZfM6AjVEa3SKNg1c-1761350274-1.0.1.1-K3Qm2cl6IlDAgmocoKZ8IMUTmue6Q81hH9stECprUq_SM8LF8rR9d1sHktvRCN3.jEM.twEuFFYDNpBnN8NBRJFZcea1yvpm8Uo0G_UhyDs; path=/; expires=Sat, 25-Oct-25 00:27:54 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None - - _cfuvid=JklLS4i3hBGELpS9cz1KMpTbj72hCwP41LyXDSxWIv8-1761350274521-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + - SET-COOKIE-XXX Strict-Transport-Security: - - max-age=31536000; includeSubDomains; preload + - STS-XXX Transfer-Encoding: - chunked X-Content-Type-Options: - - nosniff + - X-CONTENT-TYPE-XXX access-control-expose-headers: - - X-Request-ID + - ACCESS-CONTROL-XXX alt-svc: - h3=":443"; ma=86400 cf-cache-status: - DYNAMIC openai-organization: - - crewai-iuxna1 + - OPENAI-ORG-XXX openai-processing-ms: - - '487' + - '1406' openai-project: - - proj_xitITlrFeen7zjNSzML82h9x + - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '526' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: - - '10000' + - X-RATELIMIT-LIMIT-REQUESTS-XXX x-ratelimit-limit-tokens: - - '50000000' + - X-RATELIMIT-LIMIT-TOKENS-XXX x-ratelimit-remaining-requests: - - '9999' + - X-RATELIMIT-REMAINING-REQUESTS-XXX x-ratelimit-remaining-tokens: - - '49999727' + - X-RATELIMIT-REMAINING-TOKENS-XXX x-ratelimit-reset-requests: - - 6ms + - X-RATELIMIT-RESET-REQUESTS-XXX x-ratelimit-reset-tokens: - - 0s + - X-RATELIMIT-RESET-TOKENS-XXX x-request-id: - - req_1708dc0928c64882aaa5bc2c168c140f + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Test Agent. Test backstory\nYour + personal goal is: Test goal"},{"role":"user","content":"\nCurrent Task: Create + a GitHub issue"},{"role":"assistant","content":null,"tool_calls":[{"id":"call_RuWuYzjzgRL3byVGhLlPi0rq","type":"function","function":{"name":"create_issue","arguments":"{\"title\":\"Test + issue\",\"body\":\"This is a test issue created for testing purposes.\"}"}}]},{"role":"tool","tool_call_id":"call_RuWuYzjzgRL3byVGhLlPi0rq","name":"create_issue","content":"{\n \"success\": + true,\n \"issue_url\": \"https://github.com/test/repo/issues/1\"\n}"}],"model":"gpt-3.5-turbo","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_issue","description":"Create + a GitHub issue","strict":true,"parameters":{"additionalProperties":false,"properties":{"title":{"description":"Issue + title","title":"Title","type":"string"},"body":{"default":null,"description":"Issue + body","title":"Body","type":"string"}},"required":["title","body"],"type":"object"}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1028' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.5 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D6L3hfuBxk36LIb3ekD1IVwFD5VVL\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403289,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I have successfully created a GitHub + issue for testing purposes. You can view the issue at this URL: [Test issue](https://github.com/test/repo/issues/1)\",\n + \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 154,\n \"completion_tokens\": 36,\n \"total_tokens\": 190,\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: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Fri, 06 Feb 2026 18:41:29 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '888' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX status: code: 200 message: OK diff --git a/lib/crewai/tests/cassettes/agents/test_guardrail_reached_attempt_limit.yaml b/lib/crewai/tests/cassettes/agents/test_guardrail_reached_attempt_limit.yaml index e5e104fef..fb04df412 100644 --- a/lib/crewai/tests/cassettes/agents/test_guardrail_reached_attempt_limit.yaml +++ b/lib/crewai/tests/cassettes/agents/test_guardrail_reached_attempt_limit.yaml @@ -1,400 +1,428 @@ interactions: - request: - body: '{"messages": [{"role": "system", "content": "You are Sports Analyst. You are an expert at gathering and organizing information. You carefully collect details and present them in a structured way.\nYour personal goal is: Gather information about the best soccer players\n\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", "content": "Top 10 best players in the world?"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"]}' + body: '{"messages":[{"role":"system","content":"You are Sports Analyst. You are + an expert at gathering and organizing information. You carefully collect details + and present them in a structured way.\nYour personal goal is: Gather information + about the best soccer players"},{"role":"user","content":"\nCurrent Task: Top + 10 best players in the world?\n\nProvide your complete response:"}],"model":"gpt-4.1-mini"}' headers: + User-Agent: + - X-USER-AGENT-XXX accept: - application/json accept-encoding: - - gzip, deflate, zstd + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX connection: - keep-alive content-length: - - '694' + - '404' content-type: - application/json host: - api.openai.com - user-agent: - - OpenAI/Python 1.78.0 x-stainless-arch: - - arm64 + - X-STAINLESS-ARCH-XXX x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - X-STAINLESS-OS-XXX x-stainless-package-version: - - 1.78.0 - x-stainless-raw-response: - - 'true' + - 1.83.0 x-stainless-read-timeout: - - '600.0' + - X-STAINLESS-READ-TIMEOUT-XXX x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.9 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-BgufUtDqGzvqPZx2NmkqqxdW4G8rQ\",\n \"object\": \"chat.completion\",\n \"created\": 1749567308,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer \\nFinal Answer: The top 10 best soccer players in the world, as of October 2023, can be identified based on their recent performances, skills, impact on games, and overall contributions to their teams. Here is the structured list:\\n\\n1. **Lionel Messi (Inter Miami CF)**\\n - Position: Forward\\n - Key Attributes: Dribbling, vision, goal-scoring ability.\\n - Achievements: Multiple Ballon d'Or winner, Copa America champion, World Cup champion (2022).\\n\\n2. **Kylian Mbappé (Paris Saint-Germain)**\\n - Position: Forward\\n - Key Attributes: Speed, technique, finishing.\\n - Achievements: FIFA World Cup champion (2018), Ligue 1 titles, multiple\ - \ domestic cups.\\n\\n3. **Erling Haaland (Manchester City)**\\n - Position: Forward\\n - Key Attributes: Power, speed, goal-scoring instinct.\\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\\n\\n4. **Kevin De Bruyne (Manchester City)**\\n - Position: Midfielder\\n - Key Attributes: Passing, vision, creativity.\\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\\n\\n5. **Karim Benzema (Al-Ittihad)**\\n - Position: Forward\\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\\n - Achievements: 2022 Ballon d'Or winner, multiple Champions Leagues with Real Madrid.\\n\\n6. **Neymar Jr. (Al Hilal)**\\n - Position: Forward\\n - Key Attributes: Flair, dribbling, creativity.\\n - Achievements: Multiple domestic league titles, Champions League runner-up.\\n\\n7. **Robert Lewandowski (FC Barcelona)**\\n - Position: Forward\\n - Key Attributes: Finishing,\ - \ positioning, aerial ability.\\n - Achievements: FIFA Best Men's Player, multiple Bundesliga titles, La Liga champion (2023).\\n\\n8. **Mohamed Salah (Liverpool)**\\n - Position: Forward\\n - Key Attributes: Speed, finishing, dribbling.\\n - Achievements: Premier League champion, FA Cup, UEFA Champions League winner.\\n\\n9. **Vinícius Júnior (Real Madrid)**\\n - Position: Forward\\n - Key Attributes: Speed, dribbling, creativity.\\n - Achievements: UEFA Champions League winner (2022), La Liga champion (2023).\\n\\n10. **Luka Modrić (Real Madrid)**\\n - Position: Midfielder\\n - Key Attributes: Passing, vision, tactical intelligence.\\n - Achievements: Multiple Champions League titles, Ballon d'Or winner (2018).\\n\\nThis list is compiled based on their current form, past performances, and contributions to their respective teams in both domestic and international competitions. Player rankings can vary based on personal opinion and specific criteria used for\ - \ evaluation, but these players have consistently been regarded as some of the best in the world as of October 2023.\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 122,\n \"completion_tokens\": 643,\n \"total_tokens\": 765,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_34a54ae93c\"\n}\n" + string: "{\n \"id\": \"chatcmpl-D6L3hzoRVVEa07HZsM9wpi2RVRKQp\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403289,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Here is a structured list of the top + 10 best soccer players in the world as of 2024, based on recent performances, + awards, and overall impact on the game:\\n\\n1. **Kylian Mbapp\xE9** \\n + \ - Nationality: French \\n - Club: Paris Saint-Germain (PSG) \\n - + Position: Forward \\n - Key Highlights: Multiple Ligue 1 titles, World + Cup winner (2018), known for speed, dribbling, and scoring prowess.\\n\\n2. + **Erling Haaland** \\n - Nationality: Norwegian \\n - Club: Manchester + City \\n - Position: Striker \\n - Key Highlights: Premier League Golden + Boot winner, incredible goal-scoring record, physical presence, and finishing + skills.\\n\\n3. **Lionel Messi** \\n - Nationality: Argentine \\n - + Club: Inter Miami \\n - Position: Forward/Attacking Midfielder \\n - + Key Highlights: Seven Ballon d\u2019Or awards, World Cup winner (2022), exceptional + playmaking and dribbling ability.\\n\\n4. **Kevin De Bruyne** \\n - Nationality: + Belgian \\n - Club: Manchester City \\n - Position: Midfielder \\n + \ - Key Highlights: One of the best playmakers globally, assists leader, + consistent high-level performance in the Premier League.\\n\\n5. **Robert + Lewandowski** \\n - Nationality: Polish \\n - Club: FC Barcelona \\n + \ - Position: Striker \\n - Key Highlights: Exceptional goal-scoring record, + multiple Bundesliga top scorer awards, key figure in Bayern Munich\u2019s + dominance before transferring.\\n\\n6. **Karim Benzema** \\n - Nationality: + French \\n - Club: Al-Ittihad \\n - Position: Striker \\n - Key Highlights: + Ballon d\u2019Or winner (2022), excellent technical skills, leadership at + Real Madrid before recent transfer.\\n\\n7. **Mohamed Salah** \\n - Nationality: + Egyptian \\n - Club: Liverpool \\n - Position: Forward \\n - Key + Highlights: Premier League Golden Boot winner, known for speed, dribbling, + and goal-scoring consistency.\\n\\n8. **Vin\xEDcius J\xFAnior** \\n - Nationality: + Brazilian \\n - Club: Real Madrid \\n - Position: Winger \\n - Key + Highlights: Key player for Real Madrid, exceptional dribbling and pace, rising + star in world football.\\n\\n9. **Jude Bellingham** \\n - Nationality: + English \\n - Club: Real Madrid \\n - Position: Midfielder \\n - + Key Highlights: Young talent with maturity beyond years, influential midfielder + with great vision and work rate.\\n\\n10. **Thibaut Courtois** \\n - Nationality: + Belgian \\n - Club: Real Madrid \\n - Position: Goalkeeper \\n - + Key Highlights: One of the best goalkeepers globally, crucial performances + in La Liga and Champions League.\\n\\nThese rankings consider individual talent, + recent achievements, influence on matches, and overall contribution to club + and country.\",\n \"refusal\": null,\n \"annotations\": []\n + \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 68,\n \"completion_tokens\": + 621,\n \"total_tokens\": 689,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_75546bd1a7\"\n}\n" headers: CF-RAY: - - 94d9b5400dcd624b-GRU + - CF-RAY-XXX Connection: - keep-alive Content-Type: - application/json Date: - - Tue, 10 Jun 2025 14:55:42 GMT + - Fri, 06 Feb 2026 18:41:40 GMT Server: - cloudflare Set-Cookie: - - __cf_bm=8Yv8F0ZCFAo2lf.qoqxao70yxyjVvIV90zQqVF6bVzQ-1749567342-1.0.1.1-fZgnv3RDfunvCO1koxwwFJrHnxSx_rwS_FHvQ6xxDPpKHwYr7dTqIQLZrNgSX5twGyK4F22rUmkuiS6KMVogcinChk8lmHtJBTUVTFjr2KU; path=/; expires=Tue, 10-Jun-25 15:25:42 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None - - _cfuvid=wzh8YnmXvLq1G0RcIVijtzboQtCZyIe2uZiochkBLqE-1749567342267-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX Transfer-Encoding: - chunked X-Content-Type-Options: - - nosniff + - X-CONTENT-TYPE-XXX access-control-expose-headers: - - X-Request-ID + - ACCESS-CONTROL-XXX alt-svc: - h3=":443"; ma=86400 cf-cache-status: - DYNAMIC openai-organization: - - crewai-iuxna1 + - OPENAI-ORG-XXX openai-processing-ms: - - '33288' + - '10634' + openai-project: + - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-envoy-upstream-service-time: - - '33292' + x-openai-proxy-wasm: + - v0.1 x-ratelimit-limit-requests: - - '30000' + - X-RATELIMIT-LIMIT-REQUESTS-XXX x-ratelimit-limit-tokens: - - '150000000' + - X-RATELIMIT-LIMIT-TOKENS-XXX x-ratelimit-remaining-requests: - - '29999' + - X-RATELIMIT-REMAINING-REQUESTS-XXX x-ratelimit-remaining-tokens: - - '149999859' + - X-RATELIMIT-REMAINING-TOKENS-XXX x-ratelimit-reset-requests: - - 2ms + - X-RATELIMIT-RESET-REQUESTS-XXX x-ratelimit-reset-tokens: - - 0s + - X-RATELIMIT-RESET-TOKENS-XXX x-request-id: - - req_6a587ea22edef774ecdada790a320cab + - X-REQUEST-ID-XXX status: code: 200 message: OK - request: - body: '{"messages": [{"role": "system", "content": "You are Sports Analyst. You are an expert at gathering and organizing information. You carefully collect details and present them in a structured way.\nYour personal goal is: Gather information about the best soccer players\n\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", "content": "Top 10 best players in the world?"}, {"role": "assistant", "content": "Thought: I now can give a great answer \nFinal Answer: The top 10 best soccer players in the world, as of October 2023, can be identified based on their recent performances, skills, impact on games, and overall contributions to their teams. Here is the structured list:\n\n1. **Lionel Messi (Inter Miami CF)**\n - - Position: Forward\n - Key Attributes: Dribbling, vision, goal-scoring ability.\n - Achievements: Multiple Ballon d''Or winner, Copa America champion, World Cup champion (2022).\n\n2. **Kylian Mbapp\u00e9 (Paris Saint-Germain)**\n - Position: Forward\n - Key Attributes: Speed, technique, finishing.\n - Achievements: FIFA World Cup champion (2018), Ligue 1 titles, multiple domestic cups.\n\n3. **Erling Haaland (Manchester City)**\n - Position: Forward\n - Key Attributes: Power, speed, goal-scoring instinct.\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\n\n4. **Kevin De Bruyne (Manchester City)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, creativity.\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\n\n5. **Karim Benzema (Al-Ittihad)**\n - Position: Forward\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\n - Achievements: - 2022 Ballon d''Or winner, multiple Champions Leagues with Real Madrid.\n\n6. **Neymar Jr. (Al Hilal)**\n - Position: Forward\n - Key Attributes: Flair, dribbling, creativity.\n - Achievements: Multiple domestic league titles, Champions League runner-up.\n\n7. **Robert Lewandowski (FC Barcelona)**\n - Position: Forward\n - Key Attributes: Finishing, positioning, aerial ability.\n - Achievements: FIFA Best Men''s Player, multiple Bundesliga titles, La Liga champion (2023).\n\n8. **Mohamed Salah (Liverpool)**\n - Position: Forward\n - Key Attributes: Speed, finishing, dribbling.\n - Achievements: Premier League champion, FA Cup, UEFA Champions League winner.\n\n9. **Vin\u00edcius J\u00fanior (Real Madrid)**\n - Position: Forward\n - Key Attributes: Speed, dribbling, creativity.\n - Achievements: UEFA Champions League winner (2022), La Liga champion (2023).\n\n10. **Luka Modri\u0107 (Real Madrid)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, - tactical intelligence.\n - Achievements: Multiple Champions League titles, Ballon d''Or winner (2018).\n\nThis list is compiled based on their current form, past performances, and contributions to their respective teams in both domestic and international competitions. Player rankings can vary based on personal opinion and specific criteria used for evaluation, but these players have consistently been regarded as some of the best in the world as of October 2023."}, {"role": "user", "content": "You are not allowed to include Brazilian players"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"]}' + body: '{"messages":[{"role":"system","content":"You are Sports Analyst. You are + an expert at gathering and organizing information. You carefully collect details + and present them in a structured way.\nYour personal goal is: Gather information + about the best soccer players"},{"role":"user","content":"\nCurrent Task: Top + 10 best players in the world?\n\nProvide your complete response:"}],"model":"gpt-4.1-mini"}' headers: + User-Agent: + - X-USER-AGENT-XXX accept: - application/json accept-encoding: - - gzip, deflate, zstd + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX connection: - keep-alive content-length: - - '3594' + - '404' content-type: - application/json cookie: - - __cf_bm=8Yv8F0ZCFAo2lf.qoqxao70yxyjVvIV90zQqVF6bVzQ-1749567342-1.0.1.1-fZgnv3RDfunvCO1koxwwFJrHnxSx_rwS_FHvQ6xxDPpKHwYr7dTqIQLZrNgSX5twGyK4F22rUmkuiS6KMVogcinChk8lmHtJBTUVTFjr2KU; _cfuvid=wzh8YnmXvLq1G0RcIVijtzboQtCZyIe2uZiochkBLqE-1749567342267-0.0.1.1-604800000 + - COOKIE-XXX host: - api.openai.com - user-agent: - - OpenAI/Python 1.78.0 x-stainless-arch: - - arm64 + - X-STAINLESS-ARCH-XXX x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - X-STAINLESS-OS-XXX x-stainless-package-version: - - 1.78.0 - x-stainless-raw-response: - - 'true' + - 1.83.0 x-stainless-read-timeout: - - '600.0' + - X-STAINLESS-READ-TIMEOUT-XXX x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.9 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-BgugJkCDtB2EfvAMiIFK0reeLKFBl\",\n \"object\": \"chat.completion\",\n \"created\": 1749567359,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer \\nFinal Answer: Here is an updated list of the top 10 best soccer players in the world as of October 2023, excluding Brazilian players:\\n\\n1. **Lionel Messi (Inter Miami CF)**\\n - Position: Forward\\n - Key Attributes: Dribbling, vision, goal-scoring ability.\\n - Achievements: Multiple Ballon d'Or winner, Copa America champion, World Cup champion (2022).\\n\\n2. **Kylian Mbappé (Paris Saint-Germain)**\\n - Position: Forward\\n - Key Attributes: Speed, technique, finishing.\\n - Achievements: FIFA World Cup champion (2018), Ligue 1 titles, multiple domestic cups.\\n\\n3. **Erling Haaland (Manchester City)**\\n - Position: Forward\\\ - n - Key Attributes: Power, speed, goal-scoring instinct.\\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\\n\\n4. **Kevin De Bruyne (Manchester City)**\\n - Position: Midfielder\\n - Key Attributes: Passing, vision, creativity.\\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\\n\\n5. **Karim Benzema (Al-Ittihad)**\\n - Position: Forward\\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\\n - Achievements: 2022 Ballon d'Or winner, multiple Champions Leagues with Real Madrid.\\n\\n6. **Robert Lewandowski (FC Barcelona)**\\n - Position: Forward\\n - Key Attributes: Finishing, positioning, aerial ability.\\n - Achievements: FIFA Best Men's Player, multiple Bundesliga titles, La Liga champion (2023).\\n\\n7. **Mohamed Salah (Liverpool)**\\n - Position: Forward\\n - Key Attributes: Speed, finishing, dribbling.\\n - Achievements: Premier League\ - \ champion, FA Cup, UEFA Champions League winner.\\n\\n8. **Vinícius Júnior (Real Madrid)**\\n - Position: Forward\\n - Key Attributes: Speed, dribbling, creativity.\\n - Achievements: UEFA Champions League winner (2022), La Liga champion (2023).\\n\\n9. **Luka Modrić (Real Madrid)**\\n - Position: Midfielder\\n - Key Attributes: Passing, vision, tactical intelligence.\\n - Achievements: Multiple Champions League titles, Ballon d'Or winner (2018).\\n\\n10. **Harry Kane (Bayern Munich)**\\n - Position: Forward\\n - Key Attributes: Goal-scoring, technique, playmaking.\\n - Achievements: Golden Boot winner, Premier League titles, UEFA European Championship runner-up.\\n\\nThis list has been adjusted to exclude Brazilian players and focuses on those who have made significant impacts in their clubs and on the international stage as of October 2023. Each player is recognized for their exceptional skills, performances, and achievements.\",\n \"refusal\": null,\n\ - \ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 781,\n \"completion_tokens\": 610,\n \"total_tokens\": 1391,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_34a54ae93c\"\n}\n" + string: "{\n \"id\": \"chatcmpl-D6L3sn9nSnGGOMKrS88avliVF7XTv\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403300,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Certainly! Here's a structured list + of the top 10 best soccer players in the world as of 2024, considering their + performance, skills, achievements, and impact in recent seasons:\\n\\n### + Top 10 Best Soccer Players in the World (2024)\\n\\n| Rank | Player Name | + Nationality | Club (2023/24 Season) | Position | Key Attributes + \ | Recent Achievements |\\n|-------|---------------------|-------------|----------------------------|------------------|---------------------------------|------------------------------------|\\n| + 1 | Lionel Messi | Argentina | Paris Saint-Germain (PSG) | + Forward/Playmaker| Dribbling, Vision, Free kicks | 2023 World Cup Golden + Ball, Club Successes |\\n| 2 | Kylian Mbapp\xE9 | France | + Paris Saint-Germain (PSG) | Forward | Speed, Finishing, Dribbling + \ | Ligue 1 Top Scorer, World Cup Winner 2018|\\n| 3 | Erling Haaland + \ | Norway | Manchester City | Striker | Strength, + Finishing, Positioning| Premier League Golden Boot, Champions League Impact|\\n| + 4 | Kevin De Bruyne | Belgium | Manchester City | + Midfielder | Passing, Vision, Creativity | Premier League Titles, + Key Playmaker|\\n| 5 | Robert Lewandowski | Poland | FC Barcelona + \ | Striker | Finishing, Positioning, Composure| La + Liga Top Scorer, Consistent Scorer|\\n| 6 | Neymar Jr. | Brazil + \ | Al-Hilal | Forward/Winger | Dribbling, Creativity, + Flair | Copa America Titles, Club Success |\\n| 7 | Mohamed Salah | + Egypt | Liverpool | Forward/Winger | Pace, Finishing, + Work Rate | Premier League Golden Boot, Champions League Winner|\\n| + 8 | Vin\xEDcius Jr. | Brazil | Real Madrid | + Winger | Speed, Dribbling, Crossing | La Liga Titles, UEFA Champions + League Winner|\\n| 9 | Luka Modri\u0107 | Croatia | Real Madrid + \ | Midfielder | Passing, Control, Experience | Ballon + d\u2019Or 2018, Multiple Champions League Titles|\\n| 10 | Karim Benzema + \ | France | Al-Ittihad | Striker | Finishing, + Link-up Play, Movements| Ballon d\u2019Or 2022, UEFA Champions League Top + Scorer |\\n\\n### Notes:\\n- The rankings reflect a combination of individual + skill, recent performance, consistency, and influence on the game.\\n- Players\u2019 + clubs are based on the 2023/24 season affiliations.\\n- Achievements highlight + recent titles, awards, or standout contributions.\\n\\nIf you would like me + to focus on specific leagues, historical players, or emerging talents, just + let me know!\",\n \"refusal\": null,\n \"annotations\": []\n + \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 68,\n \"completion_tokens\": + 605,\n \"total_tokens\": 673,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_75546bd1a7\"\n}\n" headers: CF-RAY: - - 94d9b6782db84d3b-GRU + - CF-RAY-XXX Connection: - keep-alive Content-Type: - application/json Date: - - Tue, 10 Jun 2025 14:56:30 GMT + - Fri, 06 Feb 2026 18:41:49 GMT Server: - cloudflare + Strict-Transport-Security: + - STS-XXX Transfer-Encoding: - chunked X-Content-Type-Options: - - nosniff + - X-CONTENT-TYPE-XXX access-control-expose-headers: - - X-Request-ID + - ACCESS-CONTROL-XXX alt-svc: - h3=":443"; ma=86400 cf-cache-status: - DYNAMIC openai-organization: - - crewai-iuxna1 + - OPENAI-ORG-XXX openai-processing-ms: - - '31484' + - '9044' + openai-project: + - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-envoy-upstream-service-time: - - '31490' + x-openai-proxy-wasm: + - v0.1 x-ratelimit-limit-requests: - - '30000' + - X-RATELIMIT-LIMIT-REQUESTS-XXX x-ratelimit-limit-tokens: - - '150000000' + - X-RATELIMIT-LIMIT-TOKENS-XXX x-ratelimit-remaining-requests: - - '29999' + - X-RATELIMIT-REMAINING-REQUESTS-XXX x-ratelimit-remaining-tokens: - - '149999166' + - X-RATELIMIT-REMAINING-TOKENS-XXX x-ratelimit-reset-requests: - - 2ms + - X-RATELIMIT-RESET-REQUESTS-XXX x-ratelimit-reset-tokens: - - 0s + - X-RATELIMIT-RESET-TOKENS-XXX x-request-id: - - req_aa737cf40bb76af9f458bfd35f7a77a1 + - X-REQUEST-ID-XXX status: code: 200 message: OK - request: - body: '{"messages": [{"role": "system", "content": "You are Sports Analyst. You are an expert at gathering and organizing information. You carefully collect details and present them in a structured way.\nYour personal goal is: Gather information about the best soccer players\n\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", "content": "Top 10 best players in the world?"}, {"role": "assistant", "content": "Thought: I now can give a great answer \nFinal Answer: The top 10 best soccer players in the world, as of October 2023, can be identified based on their recent performances, skills, impact on games, and overall contributions to their teams. Here is the structured list:\n\n1. **Lionel Messi (Inter Miami CF)**\n - - Position: Forward\n - Key Attributes: Dribbling, vision, goal-scoring ability.\n - Achievements: Multiple Ballon d''Or winner, Copa America champion, World Cup champion (2022).\n\n2. **Kylian Mbapp\u00e9 (Paris Saint-Germain)**\n - Position: Forward\n - Key Attributes: Speed, technique, finishing.\n - Achievements: FIFA World Cup champion (2018), Ligue 1 titles, multiple domestic cups.\n\n3. **Erling Haaland (Manchester City)**\n - Position: Forward\n - Key Attributes: Power, speed, goal-scoring instinct.\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\n\n4. **Kevin De Bruyne (Manchester City)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, creativity.\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\n\n5. **Karim Benzema (Al-Ittihad)**\n - Position: Forward\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\n - Achievements: - 2022 Ballon d''Or winner, multiple Champions Leagues with Real Madrid.\n\n6. **Neymar Jr. (Al Hilal)**\n - Position: Forward\n - Key Attributes: Flair, dribbling, creativity.\n - Achievements: Multiple domestic league titles, Champions League runner-up.\n\n7. **Robert Lewandowski (FC Barcelona)**\n - Position: Forward\n - Key Attributes: Finishing, positioning, aerial ability.\n - Achievements: FIFA Best Men''s Player, multiple Bundesliga titles, La Liga champion (2023).\n\n8. **Mohamed Salah (Liverpool)**\n - Position: Forward\n - Key Attributes: Speed, finishing, dribbling.\n - Achievements: Premier League champion, FA Cup, UEFA Champions League winner.\n\n9. **Vin\u00edcius J\u00fanior (Real Madrid)**\n - Position: Forward\n - Key Attributes: Speed, dribbling, creativity.\n - Achievements: UEFA Champions League winner (2022), La Liga champion (2023).\n\n10. **Luka Modri\u0107 (Real Madrid)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, - tactical intelligence.\n - Achievements: Multiple Champions League titles, Ballon d''Or winner (2018).\n\nThis list is compiled based on their current form, past performances, and contributions to their respective teams in both domestic and international competitions. Player rankings can vary based on personal opinion and specific criteria used for evaluation, but these players have consistently been regarded as some of the best in the world as of October 2023."}, {"role": "user", "content": "You are not allowed to include Brazilian players"}, {"role": "assistant", "content": "Thought: I now can give a great answer \nFinal Answer: Here is an updated list of the top 10 best soccer players in the world as of October 2023, excluding Brazilian players:\n\n1. **Lionel Messi (Inter Miami CF)**\n - Position: Forward\n - Key Attributes: Dribbling, vision, goal-scoring ability.\n - Achievements: Multiple Ballon d''Or winner, Copa America champion, World Cup champion (2022).\n\n2. - **Kylian Mbapp\u00e9 (Paris Saint-Germain)**\n - Position: Forward\n - Key Attributes: Speed, technique, finishing.\n - Achievements: FIFA World Cup champion (2018), Ligue 1 titles, multiple domestic cups.\n\n3. **Erling Haaland (Manchester City)**\n - Position: Forward\n - Key Attributes: Power, speed, goal-scoring instinct.\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\n\n4. **Kevin De Bruyne (Manchester City)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, creativity.\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\n\n5. **Karim Benzema (Al-Ittihad)**\n - Position: Forward\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\n - Achievements: 2022 Ballon d''Or winner, multiple Champions Leagues with Real Madrid.\n\n6. **Robert Lewandowski (FC Barcelona)**\n - Position: Forward\n - Key Attributes: Finishing, positioning, - aerial ability.\n - Achievements: FIFA Best Men''s Player, multiple Bundesliga titles, La Liga champion (2023).\n\n7. **Mohamed Salah (Liverpool)**\n - Position: Forward\n - Key Attributes: Speed, finishing, dribbling.\n - Achievements: Premier League champion, FA Cup, UEFA Champions League winner.\n\n8. **Vin\u00edcius J\u00fanior (Real Madrid)**\n - Position: Forward\n - Key Attributes: Speed, dribbling, creativity.\n - Achievements: UEFA Champions League winner (2022), La Liga champion (2023).\n\n9. **Luka Modri\u0107 (Real Madrid)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, tactical intelligence.\n - Achievements: Multiple Champions League titles, Ballon d''Or winner (2018).\n\n10. **Harry Kane (Bayern Munich)**\n - Position: Forward\n - Key Attributes: Goal-scoring, technique, playmaking.\n - Achievements: Golden Boot winner, Premier League titles, UEFA European Championship runner-up.\n\nThis list has been adjusted to exclude Brazilian - players and focuses on those who have made significant impacts in their clubs and on the international stage as of October 2023. Each player is recognized for their exceptional skills, performances, and achievements."}, {"role": "user", "content": "You are not allowed to include Brazilian players"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"]}' + body: '{"messages":[{"role":"system","content":"You are Sports Analyst. You are + an expert at gathering and organizing information. You carefully collect details + and present them in a structured way.\nYour personal goal is: Gather information + about the best soccer players"},{"role":"user","content":"\nCurrent Task: Top + 10 best players in the world?\n\nProvide your complete response:"}],"model":"gpt-4.1-mini"}' headers: + User-Agent: + - X-USER-AGENT-XXX accept: - application/json accept-encoding: - - gzip, deflate, zstd + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX connection: - keep-alive content-length: - - '6337' + - '404' content-type: - application/json cookie: - - __cf_bm=8Yv8F0ZCFAo2lf.qoqxao70yxyjVvIV90zQqVF6bVzQ-1749567342-1.0.1.1-fZgnv3RDfunvCO1koxwwFJrHnxSx_rwS_FHvQ6xxDPpKHwYr7dTqIQLZrNgSX5twGyK4F22rUmkuiS6KMVogcinChk8lmHtJBTUVTFjr2KU; _cfuvid=wzh8YnmXvLq1G0RcIVijtzboQtCZyIe2uZiochkBLqE-1749567342267-0.0.1.1-604800000 + - COOKIE-XXX host: - api.openai.com - user-agent: - - OpenAI/Python 1.78.0 x-stainless-arch: - - arm64 + - X-STAINLESS-ARCH-XXX x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - X-STAINLESS-OS-XXX x-stainless-package-version: - - 1.78.0 - x-stainless-raw-response: - - 'true' + - 1.83.0 x-stainless-read-timeout: - - '600.0' + - X-STAINLESS-READ-TIMEOUT-XXX x-stainless-retry-count: - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.9 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-BgugsAmyI50uQ6SpCp89ZZY4eD1Pz\",\n \"object\": \"chat.completion\",\n \"created\": 1749567394,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer \\nFinal Answer: Here is the revised list of the top 10 best soccer players in the world as of October 2023, explicitly excluding Brazilian players:\\n\\n1. **Lionel Messi (Inter Miami CF)**\\n - Position: Forward\\n - Key Attributes: Dribbling, vision, goal-scoring ability.\\n - Achievements: Multiple Ballon d'Or winner, Copa America champion, World Cup champion (2022).\\n\\n2. **Kylian Mbappé (Paris Saint-Germain)**\\n - Position: Forward\\n - Key Attributes: Speed, technique, finishing.\\n - Achievements: FIFA World Cup champion (2018), multiple Ligue 1 titles, and various domestic cups.\\n\\n3. **Erling Haaland (Manchester City)**\\n\ - \ - Position: Forward\\n - Key Attributes: Power, speed, goal-scoring instinct.\\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\\n\\n4. **Kevin De Bruyne (Manchester City)**\\n - Position: Midfielder\\n - Key Attributes: Passing, vision, creativity.\\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\\n\\n5. **Karim Benzema (Al-Ittihad)**\\n - Position: Forward\\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\\n - Achievements: 2022 Ballon d'Or winner, multiple Champions Leagues with Real Madrid.\\n\\n6. **Robert Lewandowski (FC Barcelona)**\\n - Position: Forward\\n - Key Attributes: Finishing, positioning, aerial ability.\\n - Achievements: FIFA Best Men's Player, multiple Bundesliga titles, La Liga champion (2023).\\n\\n7. **Mohamed Salah (Liverpool)**\\n - Position: Forward\\n - Key Attributes: Speed, finishing, dribbling.\\n -\ - \ Achievements: Premier League champion, FA Cup, UEFA Champions League winner.\\n\\n8. **Luka Modrić (Real Madrid)**\\n - Position: Midfielder\\n - Key Attributes: Passing, vision, tactical intelligence.\\n - Achievements: Multiple Champions League titles, Ballon d'Or winner (2018).\\n\\n9. **Harry Kane (Bayern Munich)**\\n - Position: Forward\\n - Key Attributes: Goal-scoring, technique, playmaking.\\n - Achievements: Golden Boot winner, Premier League titles, UEFA European Championship runner-up.\\n\\n10. **Rodri (Manchester City)**\\n - Position: Midfielder\\n - Key Attributes: Defensive skills, passing, positional awareness.\\n - Achievements: Premier League titles, UEFA Champions League winner (2023).\\n\\nThis list is curated while adhering to the restriction of excluding Brazilian players. Each player included has demonstrated exceptional skills and remarkable performances, solidifying their status as some of the best in the world as of October 2023.\"\ - ,\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 1407,\n \"completion_tokens\": 605,\n \"total_tokens\": 2012,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_62a23a81ef\"\n}\n" + string: "{\n \"id\": \"chatcmpl-D6L4102eMwTEPeHxfyN9Kh7rjBoX6\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403309,\n \"model\": \"gpt-4.1-mini-2025-04-14\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Certainly! Here is a list of the top + 10 best soccer players in the world as of 2024, considering their recent performances, + skills, impact, and accolades:\\n\\n1. **Lionel Messi** \\n - Nationality: + Argentine \\n - Position: Forward \\n - Key Achievements: 7 Ballon d'Or + awards, led Argentina to 2021 Copa Am\xE9rica victory and 2022 FIFA World + Cup triumph, exceptional dribbling and playmaking skills.\\n\\n2. **Kylian + Mbapp\xE9** \\n - Nationality: French \\n - Position: Forward \\n - + Key Achievements: FIFA World Cup winner (2018), multiple Ligue 1 titles, known + for incredible speed, finishing, and consistency.\\n\\n3. **Erling Haaland** + \ \\n - Nationality: Norwegian \\n - Position: Striker \\n - Key Achievements: + Premier League Golden Boot winner (2022-23), prolific goal scorer, physical + presence, and finishing ability.\\n\\n4. **Karim Benzema** \\n - Nationality: + French \\n - Position: Forward \\n - Key Achievements: 2022 Ballon d'Or + winner, key player for Real Madrid\u2019s recent Champions League victories, + excellent technical skills and leadership.\\n\\n5. **Kevin De Bruyne** \\n + \ - Nationality: Belgian \\n - Position: Midfielder \\n - Key Achievements: + Premier League playmaker, known for vision, passing accuracy, and creativity.\\n\\n6. + **Robert Lewandowski** \\n - Nationality: Polish \\n - Position: Striker + \ \\n - Key Achievements: Multiple Bundesliga top scorer titles, consistent + goal scorer, known for positioning and finishing.\\n\\n7. **Neymar Jr.** \\n + \ - Nationality: Brazilian \\n - Position: Forward \\n - Key Achievements: + Exceptional dribbling, creativity, and flair; multiple domestic titles and + Copa Libertadores winner.\\n\\n8. **Mohamed Salah** \\n - Nationality: + Egyptian \\n - Position: Forward \\n - Key Achievements: Premier League + Golden Boot, consistent goal scoring with Liverpool, known for speed and finishing.\\n\\n9. + **Luka Modri\u0107** \\n - Nationality: Croatian \\n - Position: Midfielder + \ \\n - Key Achievements: 2018 Ballon d\u2019Or winner, pivotal midfield + maestro, excellent passing and control.\\n\\n10. **Thibaut Courtois** \\n + \ - Nationality: Belgian \\n - Position: Goalkeeper \\n - Key Achievements: + Exceptional shot-stopper, key player in Real Madrid's recent successes.\\n\\nThis + list includes a blend of forwards, midfielders, and a goalkeeper, showcasing + the best talents in various positions worldwide. The rankings may vary slightly + depending on current form and opinions, but these players consistently rank + among the best globally.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 68,\n \"completion_tokens\": + 575,\n \"total_tokens\": 643,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_75546bd1a7\"\n}\n" headers: CF-RAY: - - 94d9b7561f204d3b-GRU + - CF-RAY-XXX Connection: - keep-alive Content-Type: - application/json Date: - - Tue, 10 Jun 2025 14:56:46 GMT + - Fri, 06 Feb 2026 18:41:57 GMT Server: - cloudflare + Strict-Transport-Security: + - STS-XXX Transfer-Encoding: - chunked X-Content-Type-Options: - - nosniff + - X-CONTENT-TYPE-XXX access-control-expose-headers: - - X-Request-ID + - ACCESS-CONTROL-XXX alt-svc: - h3=":443"; ma=86400 cf-cache-status: - DYNAMIC openai-organization: - - crewai-iuxna1 + - OPENAI-ORG-XXX openai-processing-ms: - - '12189' + - '7948' + openai-project: + - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-envoy-upstream-service-time: - - '12193' + x-openai-proxy-wasm: + - v0.1 x-ratelimit-limit-requests: - - '30000' + - X-RATELIMIT-LIMIT-REQUESTS-XXX x-ratelimit-limit-tokens: - - '150000000' + - X-RATELIMIT-LIMIT-TOKENS-XXX x-ratelimit-remaining-requests: - - '29999' + - X-RATELIMIT-REMAINING-REQUESTS-XXX x-ratelimit-remaining-tokens: - - '149998513' + - X-RATELIMIT-REMAINING-TOKENS-XXX x-ratelimit-reset-requests: - - 2ms + - X-RATELIMIT-RESET-REQUESTS-XXX x-ratelimit-reset-tokens: - - 0s + - X-RATELIMIT-RESET-TOKENS-XXX x-request-id: - - req_1098f5a5384f4a26aecf0c9e4e4d1fc0 - status: - code: 200 - message: OK -- request: - body: '{"messages": [{"role": "system", "content": "You are Sports Analyst. You are an expert at gathering and organizing information. You carefully collect details and present them in a structured way.\nYour personal goal is: Gather information about the best soccer players\n\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user", "content": "Top 10 best players in the world?"}, {"role": "assistant", "content": "Thought: I now can give a great answer \nFinal Answer: The top 10 best soccer players in the world, as of October 2023, can be identified based on their recent performances, skills, impact on games, and overall contributions to their teams. Here is the structured list:\n\n1. **Lionel Messi (Inter Miami CF)**\n - - Position: Forward\n - Key Attributes: Dribbling, vision, goal-scoring ability.\n - Achievements: Multiple Ballon d''Or winner, Copa America champion, World Cup champion (2022).\n\n2. **Kylian Mbapp\u00e9 (Paris Saint-Germain)**\n - Position: Forward\n - Key Attributes: Speed, technique, finishing.\n - Achievements: FIFA World Cup champion (2018), Ligue 1 titles, multiple domestic cups.\n\n3. **Erling Haaland (Manchester City)**\n - Position: Forward\n - Key Attributes: Power, speed, goal-scoring instinct.\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\n\n4. **Kevin De Bruyne (Manchester City)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, creativity.\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\n\n5. **Karim Benzema (Al-Ittihad)**\n - Position: Forward\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\n - Achievements: - 2022 Ballon d''Or winner, multiple Champions Leagues with Real Madrid.\n\n6. **Neymar Jr. (Al Hilal)**\n - Position: Forward\n - Key Attributes: Flair, dribbling, creativity.\n - Achievements: Multiple domestic league titles, Champions League runner-up.\n\n7. **Robert Lewandowski (FC Barcelona)**\n - Position: Forward\n - Key Attributes: Finishing, positioning, aerial ability.\n - Achievements: FIFA Best Men''s Player, multiple Bundesliga titles, La Liga champion (2023).\n\n8. **Mohamed Salah (Liverpool)**\n - Position: Forward\n - Key Attributes: Speed, finishing, dribbling.\n - Achievements: Premier League champion, FA Cup, UEFA Champions League winner.\n\n9. **Vin\u00edcius J\u00fanior (Real Madrid)**\n - Position: Forward\n - Key Attributes: Speed, dribbling, creativity.\n - Achievements: UEFA Champions League winner (2022), La Liga champion (2023).\n\n10. **Luka Modri\u0107 (Real Madrid)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, - tactical intelligence.\n - Achievements: Multiple Champions League titles, Ballon d''Or winner (2018).\n\nThis list is compiled based on their current form, past performances, and contributions to their respective teams in both domestic and international competitions. Player rankings can vary based on personal opinion and specific criteria used for evaluation, but these players have consistently been regarded as some of the best in the world as of October 2023."}, {"role": "user", "content": "You are not allowed to include Brazilian players"}, {"role": "assistant", "content": "Thought: I now can give a great answer \nFinal Answer: Here is an updated list of the top 10 best soccer players in the world as of October 2023, excluding Brazilian players:\n\n1. **Lionel Messi (Inter Miami CF)**\n - Position: Forward\n - Key Attributes: Dribbling, vision, goal-scoring ability.\n - Achievements: Multiple Ballon d''Or winner, Copa America champion, World Cup champion (2022).\n\n2. - **Kylian Mbapp\u00e9 (Paris Saint-Germain)**\n - Position: Forward\n - Key Attributes: Speed, technique, finishing.\n - Achievements: FIFA World Cup champion (2018), Ligue 1 titles, multiple domestic cups.\n\n3. **Erling Haaland (Manchester City)**\n - Position: Forward\n - Key Attributes: Power, speed, goal-scoring instinct.\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\n\n4. **Kevin De Bruyne (Manchester City)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, creativity.\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\n\n5. **Karim Benzema (Al-Ittihad)**\n - Position: Forward\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\n - Achievements: 2022 Ballon d''Or winner, multiple Champions Leagues with Real Madrid.\n\n6. **Robert Lewandowski (FC Barcelona)**\n - Position: Forward\n - Key Attributes: Finishing, positioning, - aerial ability.\n - Achievements: FIFA Best Men''s Player, multiple Bundesliga titles, La Liga champion (2023).\n\n7. **Mohamed Salah (Liverpool)**\n - Position: Forward\n - Key Attributes: Speed, finishing, dribbling.\n - Achievements: Premier League champion, FA Cup, UEFA Champions League winner.\n\n8. **Vin\u00edcius J\u00fanior (Real Madrid)**\n - Position: Forward\n - Key Attributes: Speed, dribbling, creativity.\n - Achievements: UEFA Champions League winner (2022), La Liga champion (2023).\n\n9. **Luka Modri\u0107 (Real Madrid)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, tactical intelligence.\n - Achievements: Multiple Champions League titles, Ballon d''Or winner (2018).\n\n10. **Harry Kane (Bayern Munich)**\n - Position: Forward\n - Key Attributes: Goal-scoring, technique, playmaking.\n - Achievements: Golden Boot winner, Premier League titles, UEFA European Championship runner-up.\n\nThis list has been adjusted to exclude Brazilian - players and focuses on those who have made significant impacts in their clubs and on the international stage as of October 2023. Each player is recognized for their exceptional skills, performances, and achievements."}, {"role": "user", "content": "You are not allowed to include Brazilian players"}, {"role": "assistant", "content": "Thought: I now can give a great answer \nFinal Answer: Here is the revised list of the top 10 best soccer players in the world as of October 2023, explicitly excluding Brazilian players:\n\n1. **Lionel Messi (Inter Miami CF)**\n - Position: Forward\n - Key Attributes: Dribbling, vision, goal-scoring ability.\n - Achievements: Multiple Ballon d''Or winner, Copa America champion, World Cup champion (2022).\n\n2. **Kylian Mbapp\u00e9 (Paris Saint-Germain)**\n - Position: Forward\n - Key Attributes: Speed, technique, finishing.\n - Achievements: FIFA World Cup champion (2018), multiple Ligue 1 titles, and various domestic cups.\n\n3. **Erling Haaland - (Manchester City)**\n - Position: Forward\n - Key Attributes: Power, speed, goal-scoring instinct.\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\n\n4. **Kevin De Bruyne (Manchester City)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, creativity.\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\n\n5. **Karim Benzema (Al-Ittihad)**\n - Position: Forward\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\n - Achievements: 2022 Ballon d''Or winner, multiple Champions Leagues with Real Madrid.\n\n6. **Robert Lewandowski (FC Barcelona)**\n - Position: Forward\n - Key Attributes: Finishing, positioning, aerial ability.\n - Achievements: FIFA Best Men''s Player, multiple Bundesliga titles, La Liga champion (2023).\n\n7. **Mohamed Salah (Liverpool)**\n - Position: Forward\n - Key Attributes: Speed, finishing, dribbling.\n - Achievements: - Premier League champion, FA Cup, UEFA Champions League winner.\n\n8. **Luka Modri\u0107 (Real Madrid)**\n - Position: Midfielder\n - Key Attributes: Passing, vision, tactical intelligence.\n - Achievements: Multiple Champions League titles, Ballon d''Or winner (2018).\n\n9. **Harry Kane (Bayern Munich)**\n - Position: Forward\n - Key Attributes: Goal-scoring, technique, playmaking.\n - Achievements: Golden Boot winner, Premier League titles, UEFA European Championship runner-up.\n\n10. **Rodri (Manchester City)**\n - Position: Midfielder\n - Key Attributes: Defensive skills, passing, positional awareness.\n - Achievements: Premier League titles, UEFA Champions League winner (2023).\n\nThis list is curated while adhering to the restriction of excluding Brazilian players. Each player included has demonstrated exceptional skills and remarkable performances, solidifying their status as some of the best in the world as of October 2023."}, {"role": "user", "content": - "You are not allowed to include Brazilian players"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"]}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate, zstd - connection: - - keep-alive - content-length: - - '9093' - content-type: - - application/json - cookie: - - __cf_bm=8Yv8F0ZCFAo2lf.qoqxao70yxyjVvIV90zQqVF6bVzQ-1749567342-1.0.1.1-fZgnv3RDfunvCO1koxwwFJrHnxSx_rwS_FHvQ6xxDPpKHwYr7dTqIQLZrNgSX5twGyK4F22rUmkuiS6KMVogcinChk8lmHtJBTUVTFjr2KU; _cfuvid=wzh8YnmXvLq1G0RcIVijtzboQtCZyIe2uZiochkBLqE-1749567342267-0.0.1.1-604800000 - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.78.0 - x-stainless-arch: - - arm64 - x-stainless-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.78.0 - x-stainless-raw-response: - - 'true' - x-stainless-read-timeout: - - '600.0' - x-stainless-retry-count: - - '0' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.12.9 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - body: - string: "{\n \"id\": \"chatcmpl-BguhCefN1bN2OeYRo5ChhUqNBLUda\",\n \"object\": \"chat.completion\",\n \"created\": 1749567414,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"Thought: I now can give a great answer \\nFinal Answer: Here is a refined list of the top 10 best soccer players in the world as of October 2023, ensuring that no Brazilian players are included:\\n\\n1. **Lionel Messi (Inter Miami CF)**\\n - Position: Forward\\n - Key Attributes: Dribbling, vision, goal-scoring ability.\\n - Achievements: Multiple Ballon d'Or winner, Copa America champion, World Cup champion (2022).\\n\\n2. **Kylian Mbappé (Paris Saint-Germain)**\\n - Position: Forward\\n - Key Attributes: Speed, technique, finishing.\\n - Achievements: FIFA World Cup champion (2018), multiple Ligue 1 titles, various domestic cups.\\n\\n3. **Erling Haaland (Manchester City)**\\\ - n - Position: Forward\\n - Key Attributes: Power, speed, goal-scoring instinct.\\n - Achievements: Bundesliga top scorer, UEFA Champions League winner (2023), Premier League titles.\\n\\n4. **Kevin De Bruyne (Manchester City)**\\n - Position: Midfielder\\n - Key Attributes: Passing, vision, creativity.\\n - Achievements: Multiple Premier League titles, FA Cups, UEFA Champions League winner (2023).\\n\\n5. **Karim Benzema (Al-Ittihad)**\\n - Position: Forward\\n - Key Attributes: Goal-scoring, playmaking, tactical intelligence.\\n - Achievements: 2022 Ballon d'Or winner, multiple Champions Leagues with Real Madrid.\\n\\n6. **Robert Lewandowski (FC Barcelona)**\\n - Position: Forward\\n - Key Attributes: Finishing, positioning, aerial ability.\\n - Achievements: FIFA Best Men's Player, multiple Bundesliga titles, La Liga champion (2023).\\n\\n7. **Mohamed Salah (Liverpool)**\\n - Position: Forward\\n - Key Attributes: Speed, finishing, dribbling.\\n -\ - \ Achievements: Premier League champion, FA Cup, UEFA Champions League winner.\\n\\n8. **Luka Modrić (Real Madrid)**\\n - Position: Midfielder\\n - Key Attributes: Passing, vision, tactical intelligence.\\n - Achievements: Multiple Champions League titles, Ballon d'Or winner (2018).\\n\\n9. **Harry Kane (Bayern Munich)**\\n - Position: Forward\\n - Key Attributes: Goal-scoring, technique, playmaking.\\n - Achievements: Golden Boot winner, multiple Premier League titles, UEFA European Championship runner-up.\\n\\n10. **Son Heung-min (Tottenham Hotspur)**\\n - Position: Forward\\n - Key Attributes: Speed, finishing, playmaking.\\n - Achievements: Premier League Golden Boot winner, multiple domestic cup titles.\\n\\nThis list has been carefully revised to exclude all Brazilian players while highlighting some of the most talented individuals in soccer as of October 2023. Each player has showcased remarkable effectiveness and skill, contributing significantly to their\ - \ teams on both domestic and international stages.\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 2028,\n \"completion_tokens\": 614,\n \"total_tokens\": 2642,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 1280,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_34a54ae93c\"\n}\n" - headers: - CF-RAY: - - 94d9b7d24d991d2c-GRU - Connection: - - keep-alive - Content-Type: - - application/json - Date: - - Tue, 10 Jun 2025 14:57:29 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: - - '35291' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-envoy-upstream-service-time: - - '35294' - x-ratelimit-limit-requests: - - '30000' - x-ratelimit-limit-tokens: - - '150000000' - x-ratelimit-remaining-requests: - - '29999' - x-ratelimit-remaining-tokens: - - '149997855' - x-ratelimit-reset-requests: - - 2ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_4676152d4227ac1825d1240ddef231d6 + - X-REQUEST-ID-XXX status: code: 200 message: OK diff --git a/lib/crewai/tests/cassettes/agents/test_lite_agent_inside_flow_sync.yaml b/lib/crewai/tests/cassettes/agents/test_lite_agent_inside_flow_sync.yaml index 89749c490..10a5cfcaa 100644 --- a/lib/crewai/tests/cassettes/agents/test_lite_agent_inside_flow_sync.yaml +++ b/lib/crewai/tests/cassettes/agents/test_lite_agent_inside_flow_sync.yaml @@ -1,14 +1,8 @@ interactions: - request: body: '{"messages":[{"role":"system","content":"You are Test Agent. A helpful - test assistant\nYour personal goal is: Answer questions\nTo give my best complete - final answer to the task respond using the exact following format:\n\nThought: - I now can give a great answer\nFinal Answer: Your final answer must be the great - and the most complete as possible, it must be outcome described.\n\nI MUST use - these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: - What is 2+2? Reply with just the number.\n\nBegin! This is VERY important to - you, use the tools available and give your best Final Answer, your job depends - on it!\n\nThought:"}],"model":"gpt-4o-mini"}' + test assistant\nYour personal goal is: Answer questions"},{"role":"user","content":"\nCurrent + Task: What is 2+2? Reply with just the number.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' headers: User-Agent: - X-USER-AGENT-XXX @@ -21,7 +15,7 @@ interactions: connection: - keep-alive content-length: - - '673' + - '272' content-type: - application/json host: @@ -43,23 +37,22 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-Cy7b0HjL79y39EkUcMLrRhPFe3XGj\",\n \"object\": - \"chat.completion\",\n \"created\": 1768444914,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + string: "{\n \"id\": \"chatcmpl-D6L4AzMHXLXDfyclWS6fJSwS0cvOl\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403318,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal - Answer: 4\",\n \"refusal\": null,\n \"annotations\": []\n },\n - \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n - \ \"usage\": {\n \"prompt_tokens\": 136,\n \"completion_tokens\": 13,\n - \ \"total_tokens\": 149,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + \"assistant\",\n \"content\": \"4\",\n \"refusal\": null,\n + \ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 50,\n \"completion_tokens\": + 1,\n \"total_tokens\": 51,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": - \"default\",\n \"system_fingerprint\": \"fp_8bbc38b4db\"\n}\n" + \"default\",\n \"system_fingerprint\": \"fp_f4ae844694\"\n}\n" headers: CF-RAY: - CF-RAY-XXX @@ -68,7 +61,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 15 Jan 2026 02:41:55 GMT + - Fri, 06 Feb 2026 18:41:58 GMT Server: - cloudflare Set-Cookie: @@ -85,18 +78,14 @@ interactions: - h3=":443"; ma=86400 cf-cache-status: - DYNAMIC - content-length: - - '857' openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '341' + - '264' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '358' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/agents/test_lite_agent_standalone_still_works.yaml b/lib/crewai/tests/cassettes/agents/test_lite_agent_standalone_still_works.yaml index 83bec39ce..d3f8bb9e5 100644 --- a/lib/crewai/tests/cassettes/agents/test_lite_agent_standalone_still_works.yaml +++ b/lib/crewai/tests/cassettes/agents/test_lite_agent_standalone_still_works.yaml @@ -1,14 +1,8 @@ interactions: - request: body: '{"messages":[{"role":"system","content":"You are Standalone Agent. A helpful - assistant\nYour personal goal is: Answer questions\nTo give my best complete - final answer to the task respond using the exact following format:\n\nThought: - I now can give a great answer\nFinal Answer: Your final answer must be the great - and the most complete as possible, it must be outcome described.\n\nI MUST use - these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: - What is 5+5? Reply with just the number.\n\nBegin! This is VERY important to - you, use the tools available and give your best Final Answer, your job depends - on it!\n\nThought:"}],"model":"gpt-4o-mini"}' + assistant\nYour personal goal is: Answer questions"},{"role":"user","content":"\nCurrent + Task: What is 5+5? Reply with just the number.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' headers: User-Agent: - X-USER-AGENT-XXX @@ -21,7 +15,7 @@ interactions: connection: - keep-alive content-length: - - '674' + - '273' content-type: - application/json host: @@ -43,23 +37,22 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-Cy7azhPwUHQ0p5tdhxSAmLPoE8UgC\",\n \"object\": - \"chat.completion\",\n \"created\": 1768444913,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + string: "{\n \"id\": \"chatcmpl-D6L3cLs2ndBaXV2wnqYCdi6X1ykvv\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403284,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal - Answer: 10\",\n \"refusal\": null,\n \"annotations\": []\n },\n - \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n - \ \"usage\": {\n \"prompt_tokens\": 136,\n \"completion_tokens\": 13,\n - \ \"total_tokens\": 149,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + \"assistant\",\n \"content\": \"10\",\n \"refusal\": null,\n + \ \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 50,\n \"completion_tokens\": + 1,\n \"total_tokens\": 51,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": - \"default\",\n \"system_fingerprint\": \"fp_29330a9688\"\n}\n" + \"default\",\n \"system_fingerprint\": \"fp_f4ae844694\"\n}\n" headers: CF-RAY: - CF-RAY-XXX @@ -68,7 +61,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 15 Jan 2026 02:41:54 GMT + - Fri, 06 Feb 2026 18:41:25 GMT Server: - cloudflare Set-Cookie: @@ -85,18 +78,14 @@ interactions: - h3=":443"; ma=86400 cf-cache-status: - DYNAMIC - content-length: - - '858' openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '455' + - '270' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '583' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/agents/test_multiple_agents_in_same_flow.yaml b/lib/crewai/tests/cassettes/agents/test_multiple_agents_in_same_flow.yaml index 46ba712c1..e66c25d99 100644 --- a/lib/crewai/tests/cassettes/agents/test_multiple_agents_in_same_flow.yaml +++ b/lib/crewai/tests/cassettes/agents/test_multiple_agents_in_same_flow.yaml @@ -1,13 +1,8 @@ interactions: - request: body: '{"messages":[{"role":"system","content":"You are First Agent. A friendly - greeter\nYour personal goal is: Greet users\nTo give my best complete final - answer to the task respond using the exact following format:\n\nThought: I now - can give a great answer\nFinal Answer: Your final answer must be the great and - the most complete as possible, it must be outcome described.\n\nI MUST use these - formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Say - hello\n\nBegin! This is VERY important to you, use the tools available and give - your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4o-mini"}' + greeter\nYour personal goal is: Greet users"},{"role":"user","content":"\nCurrent + Task: Say hello\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' headers: User-Agent: - X-USER-AGENT-XXX @@ -20,7 +15,7 @@ interactions: connection: - keep-alive content-length: - - '632' + - '231' content-type: - application/json host: @@ -42,24 +37,22 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-CyRKzgODZ9yn3F9OkaXsscLk2Ln3N\",\n \"object\": - \"chat.completion\",\n \"created\": 1768520801,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + string: "{\n \"id\": \"chatcmpl-D6L4A8Aad6P1YUxWjQpvyltn8GaKT\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403318,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal - Answer: Hello! Welcome! I'm so glad to see you here. If you need any assistance - or have any questions, feel free to ask. Have a wonderful day!\",\n \"refusal\": - null,\n \"annotations\": []\n },\n \"logprobs\": null,\n - \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 127,\n \"completion_tokens\": 43,\n \"total_tokens\": 170,\n \"prompt_tokens_details\": + \"assistant\",\n \"content\": \"Hello! \U0001F60A How are you today?\",\n + \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 41,\n \"completion_tokens\": 8,\n \"total_tokens\": 49,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": - \"default\",\n \"system_fingerprint\": \"fp_c4585b5b9c\"\n}\n" + \"default\",\n \"system_fingerprint\": \"fp_f4ae844694\"\n}\n" headers: CF-RAY: - CF-RAY-XXX @@ -68,7 +61,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 15 Jan 2026 23:46:42 GMT + - Fri, 06 Feb 2026 18:41:58 GMT Server: - cloudflare Set-Cookie: @@ -85,18 +78,14 @@ interactions: - h3=":443"; ma=86400 cf-cache-status: - DYNAMIC - content-length: - - '990' openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '880' + - '325' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '1160' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: @@ -118,13 +107,8 @@ interactions: message: OK - request: body: '{"messages":[{"role":"system","content":"You are Second Agent. A polite - farewell agent\nYour personal goal is: Say goodbye\nTo give my best complete - final answer to the task respond using the exact following format:\n\nThought: - I now can give a great answer\nFinal Answer: Your final answer must be the great - and the most complete as possible, it must be outcome described.\n\nI MUST use - these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: - Say goodbye\n\nBegin! This is VERY important to you, use the tools available - and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4o-mini"}' + farewell agent\nYour personal goal is: Say goodbye"},{"role":"user","content":"\nCurrent + Task: Say goodbye\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' headers: User-Agent: - X-USER-AGENT-XXX @@ -137,7 +121,7 @@ interactions: connection: - keep-alive content-length: - - '640' + - '239' content-type: - application/json host: @@ -159,27 +143,24 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-CyRL1Ua2PkK5xXPp3KeF0AnGAk3JP\",\n \"object\": - \"chat.completion\",\n \"created\": 1768520803,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + string: "{\n \"id\": \"chatcmpl-D6L4BLMYC3ODccwbKfBIdtrEyd3no\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403319,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal - Answer: As we reach the end of our conversation, I want to express my gratitude - for the time we've shared. It's been a pleasure assisting you, and I hope - you found our interaction helpful and enjoyable. Remember, whenever you need - assistance, I'm just a message away. Wishing you all the best in your future - endeavors. Goodbye and take care!\",\n \"refusal\": null,\n \"annotations\": + \"assistant\",\n \"content\": \"Thank you for the time we've spent + together! I wish you all the best in your future endeavors. Take care, and + until we meet again, goodbye!\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 126,\n \"completion_tokens\": - 79,\n \"total_tokens\": 205,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 40,\n \"completion_tokens\": + 31,\n \"total_tokens\": 71,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": - \"default\",\n \"system_fingerprint\": \"fp_29330a9688\"\n}\n" + \"default\",\n \"system_fingerprint\": \"fp_f4ae844694\"\n}\n" headers: CF-RAY: - CF-RAY-XXX @@ -188,7 +169,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 15 Jan 2026 23:46:44 GMT + - Fri, 06 Feb 2026 18:41:59 GMT Server: - cloudflare Set-Cookie: @@ -205,18 +186,14 @@ interactions: - h3=":443"; ma=86400 cf-cache-status: - DYNAMIC - content-length: - - '1189' openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '1363' + - '726' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '1605' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/agents/test_native_tool_calling_error_handling.yaml b/lib/crewai/tests/cassettes/agents/test_native_tool_calling_error_handling.yaml index f56e36c04..c61d2c034 100644 --- a/lib/crewai/tests/cassettes/agents/test_native_tool_calling_error_handling.yaml +++ b/lib/crewai/tests/cassettes/agents/test_native_tool_calling_error_handling.yaml @@ -2,9 +2,8 @@ interactions: - request: body: '{"messages":[{"role":"system","content":"You are Calculator. You calculate things.\nYour personal goal is: Perform calculations efficiently"},{"role":"user","content":"\nCurrent - Task: Use the failing_tool to do something.\n\nThis is VERY important to you, - your job depends on it!"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"failing_tool","description":"This - tool always fails","parameters":{"properties":{},"type":"object"}}}]}' + Task: Use the failing_tool to do something."}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"failing_tool","description":"This + tool always fails","strict":true,"parameters":{"properties":{},"type":"object","additionalProperties":false,"required":[]}}}]}' headers: User-Agent: - X-USER-AGENT-XXX @@ -17,7 +16,7 @@ interactions: connection: - keep-alive content-length: - - '477' + - '476' content-type: - application/json host: @@ -39,26 +38,26 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-D0vm2JDsOmy0czXPAr4vnw3wvuqYZ\",\n \"object\": - \"chat.completion\",\n \"created\": 1769114454,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + string: "{\n \"id\": \"chatcmpl-D6L3dV6acwapgRyxmnzGfuOXemtjJ\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403285,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n - \ \"id\": \"call_8xr8rPUDWzLfQ3LOWPHtBUjK\",\n \"type\": + \ \"id\": \"call_GCdaOdo32pr1sSk4RzO0tiB9\",\n \"type\": \"function\",\n \"function\": {\n \"name\": \"failing_tool\",\n \ \"arguments\": \"{}\"\n }\n }\n ],\n \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n }\n ],\n \"usage\": - {\n \"prompt_tokens\": 78,\n \"completion_tokens\": 11,\n \"total_tokens\": - 89,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": + {\n \"prompt_tokens\": 65,\n \"completion_tokens\": 11,\n \"total_tokens\": + 76,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \ \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": - \"default\",\n \"system_fingerprint\": \"fp_c4585b5b9c\"\n}\n" + \"default\",\n \"system_fingerprint\": \"fp_6c0d1490cb\"\n}\n" headers: CF-RAY: - CF-RAY-XXX @@ -67,7 +66,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 22 Jan 2026 20:40:54 GMT + - Fri, 06 Feb 2026 18:41:25 GMT Server: - cloudflare Set-Cookie: @@ -87,13 +86,11 @@ interactions: openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '593' + - '436' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '621' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: @@ -116,12 +113,9 @@ interactions: - request: body: '{"messages":[{"role":"system","content":"You are Calculator. You calculate things.\nYour personal goal is: Perform calculations efficiently"},{"role":"user","content":"\nCurrent - Task: Use the failing_tool to do something.\n\nThis is VERY important to you, - your job depends on it!"},{"role":"assistant","content":null,"tool_calls":[{"id":"call_8xr8rPUDWzLfQ3LOWPHtBUjK","type":"function","function":{"name":"failing_tool","arguments":"{}"}}]},{"role":"tool","tool_call_id":"call_8xr8rPUDWzLfQ3LOWPHtBUjK","content":"Error - executing tool: This tool always fails"},{"role":"user","content":"Analyze the - tool result. If requirements are met, provide the Final Answer. Otherwise, call - the next tool. Deliver only the answer without meta-commentary."}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"failing_tool","description":"This - tool always fails","parameters":{"properties":{},"type":"object"}}}]}' + Task: Use the failing_tool to do something."},{"role":"assistant","content":null,"tool_calls":[{"id":"call_GCdaOdo32pr1sSk4RzO0tiB9","type":"function","function":{"name":"failing_tool","arguments":"{}"}}]},{"role":"tool","tool_call_id":"call_GCdaOdo32pr1sSk4RzO0tiB9","name":"failing_tool","content":"Error + executing tool: This tool always fails"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"failing_tool","description":"This + tool always fails","strict":true,"parameters":{"properties":{},"type":"object","additionalProperties":false,"required":[]}}}]}' headers: User-Agent: - X-USER-AGENT-XXX @@ -134,7 +128,7 @@ interactions: connection: - keep-alive content-length: - - '941' + - '778' content-type: - application/json cookie: @@ -158,22 +152,25 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-D0vm3xcywoKBW75bhBXfkGJNim6Th\",\n \"object\": - \"chat.completion\",\n \"created\": 1769114455,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + string: "{\n \"id\": \"chatcmpl-D6L3dhjDZOoihHvXvRpbJD3ReGu0z\",\n \"object\": + \"chat.completion\",\n \"created\": 1770403285,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Error: This tool always fails.\",\n - \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": - null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 141,\n \"completion_tokens\": 8,\n \"total_tokens\": 149,\n \"prompt_tokens_details\": - {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + \"assistant\",\n \"content\": \"The attempt to use the failing tool + resulted in an error, as expected since it is designed to always fail. If + there's anything else you would like to calculate or explore, please let me + know!\",\n \"refusal\": null,\n \"annotations\": []\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 93,\n \"completion_tokens\": 40,\n + \ \"total_tokens\": 133,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": - \"default\",\n \"system_fingerprint\": \"fp_c4585b5b9c\"\n}\n" + \"default\",\n \"system_fingerprint\": \"fp_6c0d1490cb\"\n}\n" headers: CF-RAY: - CF-RAY-XXX @@ -182,7 +179,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 22 Jan 2026 20:40:55 GMT + - Fri, 06 Feb 2026 18:41:26 GMT Server: - cloudflare Strict-Transport-Security: @@ -200,13 +197,11 @@ interactions: openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '420' + - '776' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '436' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_agent_kickoff_structured_output_with_tools.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_agent_kickoff_structured_output_with_tools.yaml index 879eea9e6..31124d09c 100644 --- a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_agent_kickoff_structured_output_with_tools.yaml +++ b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_agent_kickoff_structured_output_with_tools.yaml @@ -43,15 +43,15 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 x-stainless-timeout: - NOT_GIVEN method: POST uri: https://api.anthropic.com/v1/messages response: body: - string: '{"model":"claude-3-5-haiku-20241022","id":"msg_0149zKBgM47utdBdrfJjM6YZ","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_011jnBYLgtzXqdmSi7JDyQHj","name":"structured_output","input":{"operation":"Addition","result":42,"explanation":"Adding - 15 and 27 together results in 42"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":573,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":79,"service_tier":"standard"}}' + string: '{"model":"claude-3-5-haiku-20241022","id":"msg_01A41GpDoJbZLUhR8dQzUcUX","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01UNPdzpayoWyqDYVE7fR5oA","name":"structured_output","input":{"operation":"Addition","result":42,"explanation":"Added + 15 and 27 together"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":573,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":75,"service_tier":"standard","inference_geo":"not_available"}}' headers: CF-RAY: - CF-RAY-XXX @@ -62,7 +62,7 @@ interactions: Content-Type: - application/json Date: - - Fri, 30 Jan 2026 18:56:15 GMT + - Fri, 06 Feb 2026 18:41:25 GMT Server: - cloudflare Transfer-Encoding: @@ -88,7 +88,7 @@ interactions: anthropic-ratelimit-requests-remaining: - '3999' anthropic-ratelimit-requests-reset: - - '2026-01-30T18:56:14Z' + - '2026-02-06T18:41:24Z' anthropic-ratelimit-tokens-limit: - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX anthropic-ratelimit-tokens-remaining: @@ -102,7 +102,7 @@ interactions: strict-transport-security: - STS-XXX x-envoy-upstream-service-time: - - '1473' + - '1247' status: code: 200 message: OK diff --git a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_agent_kickoff_structured_output_without_tools.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_agent_kickoff_structured_output_without_tools.yaml index 02739fef1..70478203b 100644 --- a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_agent_kickoff_structured_output_without_tools.yaml +++ b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_agent_kickoff_structured_output_without_tools.yaml @@ -44,21 +44,20 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.13.3 + - 3.13.5 x-stainless-timeout: - NOT_GIVEN method: POST uri: https://api.anthropic.com/v1/messages response: body: - string: '{"model":"claude-3-5-haiku-20241022","id":"msg_013iHkpmto99iyH5kDvn8uER","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01Kpda2DzHBqWq9a2FS2Bdw6","name":"structured_output","input":{"topic":"Benefits + string: '{"model":"claude-3-5-haiku-20241022","id":"msg_016wrV83wm3FLYD4JoTy2Piw","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01V6Pzr7eGfuG4Q3mc25ZXwN","name":"structured_output","input":{"topic":"Benefits of Remote Work","summary":"Remote work offers significant advantages for both - employees and employers, transforming traditional work paradigms by providing - flexibility, increased productivity, and cost savings.","key_points":["Increased - employee flexibility and work-life balance","Reduced commuting time and associated - stress","Cost savings for companies on office infrastructure","Access to a - global talent pool","Higher employee productivity and job satisfaction","Lower - carbon footprint due to reduced travel"]}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":589,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":153,"service_tier":"standard"}}' + employees and employers, transforming traditional workplace dynamics.","key_points":["Increased + flexibility in work schedule","Reduced commute time and transportation costs","Improved + work-life balance","Higher productivity for many employees","Cost savings + for companies on office infrastructure","Expanded talent pool for hiring","Enhanced + employee job satisfaction"]}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":589,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":142,"service_tier":"standard","inference_geo":"not_available"}}' headers: CF-RAY: - CF-RAY-XXX @@ -69,7 +68,7 @@ interactions: Content-Type: - application/json Date: - - Fri, 30 Jan 2026 18:56:19 GMT + - Fri, 06 Feb 2026 18:41:28 GMT Server: - cloudflare Transfer-Encoding: @@ -95,7 +94,7 @@ interactions: anthropic-ratelimit-requests-remaining: - '3999' anthropic-ratelimit-requests-reset: - - '2026-01-30T18:56:16Z' + - '2026-02-06T18:41:26Z' anthropic-ratelimit-tokens-limit: - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX anthropic-ratelimit-tokens-remaining: @@ -109,7 +108,7 @@ interactions: strict-transport-security: - STS-XXX x-envoy-upstream-service-time: - - '3107' + - '2650' status: code: 200 message: OK From f6fa04528ab67230320bd581ea673d2900e1aaba Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Fri, 6 Feb 2026 16:29:27 -0500 Subject: [PATCH 11/14] fix: add async HITL support and chained-router tests asynchronous human-in-the-loop handling and related fixes. - Extend human_input provider with async support: AsyncExecutorContext, handle_feedback_async, async prompt helpers (_prompt_input_async, _async_readline), and async training/regular feedback loops in SyncHumanInputProvider. - Add async handler methods in CrewAgentExecutor and AgentExecutor (_ahandle_human_feedback, _ainvoke_loop) to integrate async provider flows. - Change PlusAPI.get_agent to an async httpx call and adapt caller in agent_utils to run it via asyncio.run. - Simplify listener execution in flow.Flow to correctly pass HumanFeedbackResult to listeners and unify execution path for router outcomes. - Remove deprecated types/hitl.py definitions. - Add tests covering chained router feedback, rejected paths, and mixed router/non-router listeners to prevent regressions. --- .../src/crewai/agents/crew_agent_executor.py | 16 +- lib/crewai/src/crewai/cli/plus_api.py | 16 +- .../src/crewai/core/providers/human_input.py | 191 +++++++++++++++++- .../src/crewai/experimental/agent_executor.py | 32 ++- lib/crewai/src/crewai/flow/flow.py | 40 +--- lib/crewai/src/crewai/types/hitl.py | 37 ---- .../src/crewai/utilities/agent_utils.py | 2 +- lib/crewai/tests/cli/test_plus_api.py | 81 +++++--- .../tests/test_human_feedback_integration.py | 170 ++++++++++++++++ 9 files changed, 473 insertions(+), 112 deletions(-) delete mode 100644 lib/crewai/src/crewai/types/hitl.py diff --git a/lib/crewai/src/crewai/agents/crew_agent_executor.py b/lib/crewai/src/crewai/agents/crew_agent_executor.py index 647596f2a..c7adcbe09 100644 --- a/lib/crewai/src/crewai/agents/crew_agent_executor.py +++ b/lib/crewai/src/crewai/agents/crew_agent_executor.py @@ -1009,7 +1009,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): raise if self.ask_for_human_input: - formatted_answer = self._handle_human_feedback(formatted_answer) + formatted_answer = await self._ahandle_human_feedback(formatted_answer) self._create_short_term_memory(formatted_answer) self._create_long_term_memory(formatted_answer) @@ -1508,6 +1508,20 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): provider = get_provider() return provider.handle_feedback(formatted_answer, self) + async def _ahandle_human_feedback( + self, formatted_answer: AgentFinish + ) -> AgentFinish: + """Process human feedback asynchronously via the configured provider. + + Args: + formatted_answer: Initial agent result. + + Returns: + Final answer after feedback. + """ + provider = get_provider() + return await provider.handle_feedback_async(formatted_answer, self) + def _is_training_mode(self) -> bool: """Check if training mode is active. diff --git a/lib/crewai/src/crewai/cli/plus_api.py b/lib/crewai/src/crewai/cli/plus_api.py index 62f34095b..e07d44d10 100644 --- a/lib/crewai/src/crewai/cli/plus_api.py +++ b/lib/crewai/src/crewai/cli/plus_api.py @@ -1,6 +1,8 @@ +import os from typing import Any from urllib.parse import urljoin -import os + +import httpx import requests from crewai.cli.config import Settings @@ -33,7 +35,11 @@ class PlusAPI: if settings.org_uuid: self.headers["X-Crewai-Organization-Id"] = settings.org_uuid - self.base_url = os.getenv("CREWAI_PLUS_URL") or str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL + self.base_url = ( + os.getenv("CREWAI_PLUS_URL") + or str(settings.enterprise_base_url) + or DEFAULT_CREWAI_ENTERPRISE_URL + ) def _make_request( self, method: str, endpoint: str, **kwargs: Any @@ -49,8 +55,10 @@ class PlusAPI: def get_tool(self, handle: str) -> requests.Response: return self._make_request("GET", f"{self.TOOLS_RESOURCE}/{handle}") - def get_agent(self, handle: str) -> requests.Response: - return self._make_request("GET", f"{self.AGENTS_RESOURCE}/{handle}") + async def get_agent(self, handle: str) -> httpx.Response: + url = urljoin(self.base_url, f"{self.AGENTS_RESOURCE}/{handle}") + async with httpx.AsyncClient() as client: + return await client.get(url, headers=self.headers) def publish_tool( self, diff --git a/lib/crewai/src/crewai/core/providers/human_input.py b/lib/crewai/src/crewai/core/providers/human_input.py index 4062e6bb9..ecbc09a41 100644 --- a/lib/crewai/src/crewai/core/providers/human_input.py +++ b/lib/crewai/src/crewai/core/providers/human_input.py @@ -2,7 +2,9 @@ from __future__ import annotations +import asyncio from contextvars import ContextVar, Token +import sys from typing import TYPE_CHECKING, Protocol, runtime_checkable @@ -46,13 +48,21 @@ class ExecutorContext(Protocol): ... +class AsyncExecutorContext(ExecutorContext, Protocol): + """Extended context for executors that support async invocation.""" + + async def _ainvoke_loop(self) -> AgentFinish: + """Invoke the agent loop asynchronously and return the result.""" + ... + + @runtime_checkable class HumanInputProvider(Protocol): """Protocol for human input handling. Implementations handle the full feedback flow: - Sync: prompt user, loop until satisfied - - Async: raise exception for external handling + - Async: use non-blocking I/O and async invoke loop """ def setup_messages(self, context: ExecutorContext) -> bool: @@ -86,7 +96,7 @@ class HumanInputProvider(Protocol): formatted_answer: AgentFinish, context: ExecutorContext, ) -> AgentFinish: - """Handle the full human feedback flow. + """Handle the full human feedback flow synchronously. Args: formatted_answer: The agent's current answer. @@ -100,6 +110,25 @@ class HumanInputProvider(Protocol): """ ... + async def handle_feedback_async( + self, + formatted_answer: AgentFinish, + context: AsyncExecutorContext, + ) -> AgentFinish: + """Handle the full human feedback flow asynchronously. + + Uses non-blocking I/O for user prompts and async invoke loop + for agent re-execution. + + Args: + formatted_answer: The agent's current answer. + context: Async executor context for callbacks. + + Returns: + The final answer after feedback processing. + """ + ... + @staticmethod def _get_output_string(answer: AgentFinish) -> str: """Extract output string from answer. @@ -116,7 +145,7 @@ class HumanInputProvider(Protocol): class SyncHumanInputProvider(HumanInputProvider): - """Default synchronous human input via terminal.""" + """Default human input provider with sync and async support.""" def setup_messages(self, context: ExecutorContext) -> bool: """Use standard message setup. @@ -157,6 +186,33 @@ class SyncHumanInputProvider(HumanInputProvider): return self._handle_regular_feedback(formatted_answer, feedback, context) + async def handle_feedback_async( + self, + formatted_answer: AgentFinish, + context: AsyncExecutorContext, + ) -> AgentFinish: + """Handle feedback asynchronously without blocking the event loop. + + Args: + formatted_answer: The agent's current answer. + context: Async executor context for callbacks. + + Returns: + The final answer after feedback processing. + """ + feedback = await self._prompt_input_async(context.crew) + + if context._is_training_mode(): + return await self._handle_training_feedback_async( + formatted_answer, feedback, context + ) + + return await self._handle_regular_feedback_async( + formatted_answer, feedback, context + ) + + # ── Sync helpers ────────────────────────────────────────────────── + @staticmethod def _handle_training_feedback( initial_answer: AgentFinish, @@ -209,6 +265,62 @@ class SyncHumanInputProvider(HumanInputProvider): return answer + # ── Async helpers ───────────────────────────────────────────────── + + @staticmethod + async def _handle_training_feedback_async( + initial_answer: AgentFinish, + feedback: str, + context: AsyncExecutorContext, + ) -> AgentFinish: + """Process training feedback asynchronously (single iteration). + + Args: + initial_answer: The agent's initial answer. + feedback: Human feedback string. + context: Async executor context for callbacks. + + Returns: + Improved answer after processing feedback. + """ + context._handle_crew_training_output(initial_answer, feedback) + context.messages.append(context._format_feedback_message(feedback)) + improved_answer = await context._ainvoke_loop() + context._handle_crew_training_output(improved_answer) + context.ask_for_human_input = False + return improved_answer + + async def _handle_regular_feedback_async( + self, + current_answer: AgentFinish, + initial_feedback: str, + context: AsyncExecutorContext, + ) -> AgentFinish: + """Process regular feedback with async iteration loop. + + Args: + current_answer: The agent's current answer. + initial_feedback: Initial human feedback string. + context: Async executor context for callbacks. + + Returns: + Final answer after all feedback iterations. + """ + feedback = initial_feedback + answer = current_answer + + while context.ask_for_human_input: + if feedback.strip() == "": + context.ask_for_human_input = False + else: + context.messages.append(context._format_feedback_message(feedback)) + answer = await context._ainvoke_loop() + feedback = await self._prompt_input_async(context.crew) + + return answer + + # ── I/O ─────────────────────────────────────────────────────────── + @staticmethod def _prompt_input(crew: Crew | None) -> str: """Show rich panel and prompt for input. @@ -262,6 +374,79 @@ class SyncHumanInputProvider(HumanInputProvider): finally: formatter.resume_live_updates() + @staticmethod + async def _prompt_input_async(crew: Crew | None) -> str: + """Show rich panel and prompt for input without blocking the event loop. + + Args: + crew: The crew instance for context. + + Returns: + User input string from terminal. + """ + from rich.panel import Panel + from rich.text import Text + + from crewai.events.event_listener import event_listener + + formatter = event_listener.formatter + formatter.pause_live_updates() + + try: + if crew and getattr(crew, "_train", False): + prompt_text = ( + "TRAINING MODE: Provide feedback to improve the agent's performance.\n\n" + "This will be used to train better versions of the agent.\n" + "Please provide detailed feedback about the result quality and reasoning process." + ) + title = "🎓 Training Feedback Required" + else: + prompt_text = ( + "Provide feedback on the Final Result above.\n\n" + "• If you are happy with the result, simply hit Enter without typing anything.\n" + "• Otherwise, provide specific improvement requests.\n" + "• You can provide multiple rounds of feedback until satisfied." + ) + title = "💬 Human Feedback Required" + + content = Text() + content.append(prompt_text, style="yellow") + + prompt_panel = Panel( + content, + title=title, + border_style="yellow", + padding=(1, 2), + ) + formatter.console.print(prompt_panel) + + response = await _async_readline() + if response.strip() != "": + formatter.console.print("\n[cyan]Processing your feedback...[/cyan]") + return response + finally: + formatter.resume_live_updates() + + +async def _async_readline() -> str: + """Read a line from stdin using the event loop's native I/O. + + Falls back to asyncio.to_thread on platforms where piping stdin + is unsupported. + + Returns: + The line read from stdin, with trailing newline stripped. + """ + loop = asyncio.get_running_loop() + try: + reader = asyncio.StreamReader() + protocol = asyncio.StreamReaderProtocol(reader) + await loop.connect_read_pipe(lambda: protocol, sys.stdin) + raw = await reader.readline() + return raw.decode().rstrip("\n") + except (OSError, NotImplementedError, ValueError): + return await asyncio.to_thread(input) + _provider: ContextVar[HumanInputProvider | None] = ContextVar( "human_input_provider", diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index 037df6793..9f2fecb25 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -258,6 +258,22 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): raise RuntimeError("Agent loop did not produce a final answer") return answer + async def _ainvoke_loop(self) -> AgentFinish: + """Invoke the agent loop asynchronously and return the result. + + Required by AsyncExecutorContext protocol. + """ + self._state.iterations = 0 + self._state.is_finished = False + self._state.current_answer = None + + await self.akickoff() + + answer = self._state.current_answer + if not isinstance(answer, AgentFinish): + raise RuntimeError("Agent loop did not produce a final answer") + return answer + def _format_feedback_message(self, feedback: str) -> LLMMessage: """Format feedback as a message for the LLM. @@ -1173,7 +1189,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): ) if self.state.ask_for_human_input: - formatted_answer = self._handle_human_feedback(formatted_answer) + formatted_answer = await self._ahandle_human_feedback(formatted_answer) self._create_short_term_memory(formatted_answer) self._create_long_term_memory(formatted_answer) @@ -1390,6 +1406,20 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): provider = get_provider() return provider.handle_feedback(formatted_answer, self) + async def _ahandle_human_feedback( + self, formatted_answer: AgentFinish + ) -> AgentFinish: + """Process human feedback asynchronously and refine answer. + + Args: + formatted_answer: Initial agent result. + + Returns: + Final answer after feedback. + """ + provider = get_provider() + return await provider.handle_feedback_async(formatted_answer, self) + def _is_training_mode(self) -> bool: """Check if training mode is active. diff --git a/lib/crewai/src/crewai/flow/flow.py b/lib/crewai/src/crewai/flow/flow.py index 5a6ac4557..f9f6843aa 100644 --- a/lib/crewai/src/crewai/flow/flow.py +++ b/lib/crewai/src/crewai/flow/flow.py @@ -1934,40 +1934,14 @@ class Flow(Generic[T], metaclass=FlowMeta): await self._execute_listeners(start_method_name, result, finished_event_id) # Then execute listeners for the router result (e.g., "approved") router_result_trigger = FlowMethodName(str(result)) - listeners_for_result = self._find_triggered_methods( - router_result_trigger, router_only=False + listener_result = ( + self.last_human_feedback + if self.last_human_feedback is not None + else result + ) + await self._execute_listeners( + router_result_trigger, listener_result, finished_event_id ) - if listeners_for_result: - # Pass the HumanFeedbackResult if available - listener_result = ( - self.last_human_feedback - if self.last_human_feedback is not None - else result - ) - racing_group = self._get_racing_group_for_listeners( - listeners_for_result - ) - if racing_group: - racing_members, _ = racing_group - other_listeners = [ - name - for name in listeners_for_result - if name not in racing_members - ] - await self._execute_racing_listeners( - racing_members, - other_listeners, - listener_result, - finished_event_id, - ) - else: - tasks = [ - self._execute_single_listener( - listener_name, listener_result, finished_event_id - ) - for listener_name in listeners_for_result - ] - await asyncio.gather(*tasks) else: await self._execute_listeners(start_method_name, result, finished_event_id) diff --git a/lib/crewai/src/crewai/types/hitl.py b/lib/crewai/src/crewai/types/hitl.py deleted file mode 100644 index d5fd33e14..000000000 --- a/lib/crewai/src/crewai/types/hitl.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Human-in-the-loop (HITL) type definitions. - -This module provides type definitions for human-in-the-loop interactions -in crew executions. -""" - -from typing import TypedDict - - -class HITLResumeInfo(TypedDict, total=False): - """HITL resume information passed from flow to crew. - - Attributes: - task_id: Unique identifier for the task. - crew_execution_id: Unique identifier for the crew execution. - task_key: Key identifying the specific task. - task_output: Output from the task before human intervention. - human_feedback: Feedback provided by the human. - previous_messages: History of messages in the conversation. - """ - - task_id: str - crew_execution_id: str - task_key: str - task_output: str - human_feedback: str - previous_messages: list[dict[str, str]] - - -class CrewInputsWithHITL(TypedDict, total=False): - """Crew inputs that may contain HITL resume information. - - Attributes: - _hitl_resume: Optional HITL resume information for continuing execution. - """ - - _hitl_resume: HITLResumeInfo diff --git a/lib/crewai/src/crewai/utilities/agent_utils.py b/lib/crewai/src/crewai/utilities/agent_utils.py index ee76dc53f..4ded93505 100644 --- a/lib/crewai/src/crewai/utilities/agent_utils.py +++ b/lib/crewai/src/crewai/utilities/agent_utils.py @@ -832,7 +832,7 @@ def load_agent_from_repository(from_repository: str) -> dict[str, Any]: client = PlusAPI(api_key=get_auth_token()) _print_current_organization() - response = client.get_agent(from_repository) + response = asyncio.run(client.get_agent(from_repository)) if response.status_code == 404: raise AgentRepositoryError( f"Agent {from_repository} does not exist, make sure the name is correct or the agent is available on your organization." diff --git a/lib/crewai/tests/cli/test_plus_api.py b/lib/crewai/tests/cli/test_plus_api.py index 0a8946c2b..70eff917e 100644 --- a/lib/crewai/tests/cli/test_plus_api.py +++ b/lib/crewai/tests/cli/test_plus_api.py @@ -1,6 +1,8 @@ import os import unittest -from unittest.mock import ANY, MagicMock, patch +from unittest.mock import ANY, AsyncMock, MagicMock, patch + +import pytest from crewai.cli.plus_api import PlusAPI @@ -68,37 +70,6 @@ class TestPlusAPI(unittest.TestCase): ) self.assertEqual(response, mock_response) - @patch("crewai.cli.plus_api.PlusAPI._make_request") - def test_get_agent(self, mock_make_request): - mock_response = MagicMock() - mock_make_request.return_value = mock_response - - response = self.api.get_agent("test_agent_handle") - mock_make_request.assert_called_once_with( - "GET", "/crewai_plus/api/v1/agents/test_agent_handle" - ) - self.assertEqual(response, mock_response) - - @patch("crewai.cli.plus_api.Settings") - @patch("requests.Session.request") - def test_get_agent_with_org_uuid(self, mock_make_request, mock_settings_class): - mock_settings = MagicMock() - mock_settings.org_uuid = self.org_uuid - mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL') - mock_settings_class.return_value = mock_settings - # re-initialize Client - self.api = PlusAPI(self.api_key) - - mock_response = MagicMock() - mock_make_request.return_value = mock_response - - response = self.api.get_agent("test_agent_handle") - - self.assert_request_with_org_id( - mock_make_request, "GET", "/crewai_plus/api/v1/agents/test_agent_handle" - ) - self.assertEqual(response, mock_response) - @patch("crewai.cli.plus_api.PlusAPI._make_request") def test_get_tool(self, mock_make_request): mock_response = MagicMock() @@ -338,3 +309,49 @@ class TestPlusAPI(unittest.TestCase): custom_api.base_url, "https://custom-url-from-env.com", ) + + +@pytest.mark.asyncio +@patch("httpx.AsyncClient") +async def test_get_agent(mock_async_client_class): + api = PlusAPI("test_api_key") + mock_response = MagicMock() + mock_client_instance = AsyncMock() + mock_client_instance.get.return_value = mock_response + mock_async_client_class.return_value.__aenter__.return_value = mock_client_instance + + response = await api.get_agent("test_agent_handle") + + mock_client_instance.get.assert_called_once_with( + f"{api.base_url}/crewai_plus/api/v1/agents/test_agent_handle", + headers=api.headers, + ) + assert response == mock_response + + +@pytest.mark.asyncio +@patch("httpx.AsyncClient") +@patch("crewai.cli.plus_api.Settings") +async def test_get_agent_with_org_uuid(mock_settings_class, mock_async_client_class): + org_uuid = "test-org-uuid" + mock_settings = MagicMock() + mock_settings.org_uuid = org_uuid + mock_settings.enterprise_base_url = os.getenv("CREWAI_PLUS_URL") + mock_settings_class.return_value = mock_settings + + api = PlusAPI("test_api_key") + + mock_response = MagicMock() + mock_client_instance = AsyncMock() + mock_client_instance.get.return_value = mock_response + mock_async_client_class.return_value.__aenter__.return_value = mock_client_instance + + response = await api.get_agent("test_agent_handle") + + mock_client_instance.get.assert_called_once_with( + f"{api.base_url}/crewai_plus/api/v1/agents/test_agent_handle", + headers=api.headers, + ) + assert "X-Crewai-Organization-Id" in api.headers + assert api.headers["X-Crewai-Organization-Id"] == org_uuid + assert response == mock_response diff --git a/lib/crewai/tests/test_human_feedback_integration.py b/lib/crewai/tests/test_human_feedback_integration.py index dd21724b4..d2d6a6f31 100644 --- a/lib/crewai/tests/test_human_feedback_integration.py +++ b/lib/crewai/tests/test_human_feedback_integration.py @@ -157,6 +157,176 @@ class TestMultiStepFlows: assert execution_order == ["generate", "review", "finalize"] + def test_chained_router_feedback_steps(self): + """Test that a router outcome can trigger another router method. + + Regression test: @listen("outcome") combined with @human_feedback(emit=...) + creates a method that is both a listener and a router. The flow must find + and execute it when the upstream router emits the matching outcome. + """ + execution_order: list[str] = [] + + class ChainedRouterFlow(Flow): + @start() + @human_feedback( + message="First review:", + emit=["approved", "rejected"], + llm="gpt-4o-mini", + ) + def draft(self): + execution_order.append("draft") + return "draft content" + + @listen("approved") + @human_feedback( + message="Final review:", + emit=["publish", "revise"], + llm="gpt-4o-mini", + ) + def final_review(self, prev: HumanFeedbackResult): + execution_order.append("final_review") + return "final content" + + @listen("rejected") + def on_rejected(self, prev: HumanFeedbackResult): + execution_order.append("on_rejected") + return "rejected" + + @listen("publish") + def on_publish(self, prev: HumanFeedbackResult): + execution_order.append("on_publish") + return "published" + + @listen("revise") + def on_revise(self, prev: HumanFeedbackResult): + execution_order.append("on_revise") + return "revised" + + flow = ChainedRouterFlow() + + with ( + patch.object( + flow, + "_request_human_feedback", + side_effect=["looks good", "ship it"], + ), + patch.object( + flow, + "_collapse_to_outcome", + side_effect=["approved", "publish"], + ), + ): + result = flow.kickoff() + + assert execution_order == ["draft", "final_review", "on_publish"] + assert result == "published" + assert len(flow.human_feedback_history) == 2 + assert flow.human_feedback_history[0].outcome == "approved" + assert flow.human_feedback_history[1].outcome == "publish" + + def test_chained_router_rejected_path(self): + """Test that a start-router outcome routes to a non-router listener.""" + execution_order: list[str] = [] + + class ChainedRouterFlow(Flow): + @start() + @human_feedback( + message="Review:", + emit=["approved", "rejected"], + llm="gpt-4o-mini", + ) + def draft(self): + execution_order.append("draft") + return "draft" + + @listen("approved") + @human_feedback( + message="Final:", + emit=["publish", "revise"], + llm="gpt-4o-mini", + ) + def final_review(self, prev: HumanFeedbackResult): + execution_order.append("final_review") + return "final" + + @listen("rejected") + def on_rejected(self, prev: HumanFeedbackResult): + execution_order.append("on_rejected") + return "rejected" + + flow = ChainedRouterFlow() + + with ( + patch.object( + flow, "_request_human_feedback", return_value="bad" + ), + patch.object( + flow, "_collapse_to_outcome", return_value="rejected" + ), + ): + result = flow.kickoff() + + assert execution_order == ["draft", "on_rejected"] + assert result == "rejected" + assert len(flow.human_feedback_history) == 1 + assert flow.human_feedback_history[0].outcome == "rejected" + + def test_router_and_non_router_listeners_for_same_outcome(self): + """Test that both router and non-router listeners fire for the same outcome.""" + execution_order: list[str] = [] + + class MixedListenerFlow(Flow): + @start() + @human_feedback( + message="Review:", + emit=["approved", "rejected"], + llm="gpt-4o-mini", + ) + def draft(self): + execution_order.append("draft") + return "draft" + + @listen("approved") + @human_feedback( + message="Final:", + emit=["publish", "revise"], + llm="gpt-4o-mini", + ) + def router_listener(self, prev: HumanFeedbackResult): + execution_order.append("router_listener") + return "final" + + @listen("approved") + def plain_listener(self, prev: HumanFeedbackResult): + execution_order.append("plain_listener") + return "logged" + + @listen("publish") + def on_publish(self, prev: HumanFeedbackResult): + execution_order.append("on_publish") + return "published" + + flow = MixedListenerFlow() + + with ( + patch.object( + flow, + "_request_human_feedback", + side_effect=["approve it", "publish it"], + ), + patch.object( + flow, + "_collapse_to_outcome", + side_effect=["approved", "publish"], + ), + ): + flow.kickoff() + + assert "draft" in execution_order + assert "router_listener" in execution_order + assert "plain_listener" in execution_order + assert "on_publish" in execution_order + class TestStateManagement: """Tests for state management with human feedback.""" From a3bee66be8e1f99b56731acde70f4026a7c35de0 Mon Sep 17 00:00:00 2001 From: Lucas Gomide Date: Tue, 10 Feb 2026 14:39:35 -0300 Subject: [PATCH 12/14] Address OpenSSL CVE-2025-15467 vulnerability (#4426) * fix(security): bump regex from 2024.9.11 to 2026.1.15 Address security vulnerability flagged in regex==2024.9.11 * bump mcp from 1.23.1 to 1.26.0 Address security vulnerability flagged in mcp==1.16.0 (resolved to 1.23.3) --- lib/crewai/pyproject.toml | 4 +- uv.lock | 156 +++++++++++++++++++++----------------- 2 files changed, 90 insertions(+), 70 deletions(-) diff --git a/lib/crewai/pyproject.toml b/lib/crewai/pyproject.toml index 5ecc0f0bb..da8c851df 100644 --- a/lib/crewai/pyproject.toml +++ b/lib/crewai/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "instructor>=1.3.3", # Text Processing "pdfplumber~=0.11.4", - "regex~=2024.9.11", + "regex~=2026.1.15", # Telemetry and Monitoring "opentelemetry-api~=1.34.0", "opentelemetry-sdk~=1.34.0", @@ -36,7 +36,7 @@ dependencies = [ "json5~=0.10.0", "portalocker~=2.7.0", "pydantic-settings~=2.10.1", - "mcp~=1.23.1", + "mcp~=1.26.0", "uv~=0.9.13", "aiosqlite~=0.21.0", ] diff --git a/uv.lock b/uv.lock index db5618250..c84758360 100644 --- a/uv.lock +++ b/uv.lock @@ -1295,7 +1295,7 @@ requires-dist = [ { name = "json5", specifier = "~=0.10.0" }, { name = "jsonref", specifier = "~=1.1.0" }, { name = "litellm", marker = "extra == 'litellm'", specifier = ">=1.74.9,<3" }, - { name = "mcp", specifier = "~=1.23.1" }, + { name = "mcp", specifier = "~=1.26.0" }, { name = "mem0ai", marker = "extra == 'mem0'", specifier = "~=0.1.94" }, { name = "openai", specifier = ">=1.83.0,<3" }, { name = "openpyxl", specifier = "~=3.1.5" }, @@ -1311,7 +1311,7 @@ requires-dist = [ { name = "pyjwt", specifier = ">=2.9.0,<3" }, { name = "python-dotenv", specifier = "~=1.1.1" }, { name = "qdrant-client", extras = ["fastembed"], marker = "extra == 'qdrant'", specifier = "~=1.14.3" }, - { name = "regex", specifier = "~=2024.9.11" }, + { name = "regex", specifier = "~=2026.1.15" }, { name = "tiktoken", marker = "extra == 'embeddings'", specifier = "~=0.8.0" }, { name = "tokenizers", specifier = "~=0.20.3" }, { name = "tomli", specifier = "~=2.0.2" }, @@ -3777,7 +3777,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.23.3" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -3795,9 +3795,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/a4/d06a303f45997e266f2c228081abe299bbcba216cb806128e2e49095d25f/mcp-1.23.3.tar.gz", hash = "sha256:b3b0da2cc949950ce1259c7bfc1b081905a51916fcd7c8182125b85e70825201", size = 600697, upload-time = "2025-12-09T16:04:37.351Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/c6/13c1a26b47b3f3a3b480783001ada4268917c9f42d78a079c336da2e75e5/mcp-1.23.3-py3-none-any.whl", hash = "sha256:32768af4b46a1b4f7df34e2bfdf5c6011e7b63d7f1b0e321d0fdef4cd6082031", size = 231570, upload-time = "2025-12-09T16:04:35.56Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, ] [[package]] @@ -6792,71 +6792,91 @@ wheels = [ [[package]] name = "regex" -version = "2024.9.11" +version = "2026.1.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/38/148df33b4dbca3bd069b963acab5e0fa1a9dbd6820f8c322d0dd6faeff96/regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd", size = 399403, upload-time = "2024-09-11T19:00:09.814Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/86/07d5056945f9ec4590b518171c4254a5925832eb727b56d3c38a7476f316/regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5", size = 414811, upload-time = "2026-01-14T23:18:02.775Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/12/497bd6599ce8a239ade68678132296aec5ee25ebea45fc8ba91aa60fceec/regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408", size = 482488, upload-time = "2024-09-11T18:56:55.331Z" }, - { url = "https://files.pythonhosted.org/packages/c1/24/595ddb9bec2a9b151cdaf9565b0c9f3da9f0cb1dca6c158bc5175332ddf8/regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d", size = 287443, upload-time = "2024-09-11T18:56:58.531Z" }, - { url = "https://files.pythonhosted.org/packages/69/a8/b2fb45d9715b1469383a0da7968f8cacc2f83e9fbbcd6b8713752dd980a6/regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5", size = 284561, upload-time = "2024-09-11T18:57:00.655Z" }, - { url = "https://files.pythonhosted.org/packages/88/87/1ce4a5357216b19b7055e7d3b0efc75a6e426133bf1e7d094321df514257/regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c", size = 783177, upload-time = "2024-09-11T18:57:01.958Z" }, - { url = "https://files.pythonhosted.org/packages/3c/65/b9f002ab32f7b68e7d1dcabb67926f3f47325b8dbc22cc50b6a043e1d07c/regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8", size = 823193, upload-time = "2024-09-11T18:57:04.06Z" }, - { url = "https://files.pythonhosted.org/packages/22/91/8339dd3abce101204d246e31bc26cdd7ec07c9f91598472459a3a902aa41/regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35", size = 809950, upload-time = "2024-09-11T18:57:05.805Z" }, - { url = "https://files.pythonhosted.org/packages/cb/19/556638aa11c2ec9968a1da998f07f27ec0abb9bf3c647d7c7985ca0b8eea/regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71", size = 782661, upload-time = "2024-09-11T18:57:07.881Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e9/7a5bc4c6ef8d9cd2bdd83a667888fc35320da96a4cc4da5fa084330f53db/regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8", size = 772348, upload-time = "2024-09-11T18:57:09.494Z" }, - { url = "https://files.pythonhosted.org/packages/f1/0b/29f2105bfac3ed08e704914c38e93b07c784a6655f8a015297ee7173e95b/regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a", size = 697460, upload-time = "2024-09-11T18:57:11.595Z" }, - { url = "https://files.pythonhosted.org/packages/71/3a/52ff61054d15a4722605f5872ad03962b319a04c1ebaebe570b8b9b7dde1/regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d", size = 769151, upload-time = "2024-09-11T18:57:14.358Z" }, - { url = "https://files.pythonhosted.org/packages/97/07/37e460ab5ca84be8e1e197c3b526c5c86993dcc9e13cbc805c35fc2463c1/regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137", size = 777478, upload-time = "2024-09-11T18:57:16.397Z" }, - { url = "https://files.pythonhosted.org/packages/65/7b/953075723dd5ab00780043ac2f9de667306ff9e2a85332975e9f19279174/regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6", size = 845373, upload-time = "2024-09-11T18:57:17.938Z" }, - { url = "https://files.pythonhosted.org/packages/40/b8/3e9484c6230b8b6e8f816ab7c9a080e631124991a4ae2c27a81631777db0/regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca", size = 845369, upload-time = "2024-09-11T18:57:20.091Z" }, - { url = "https://files.pythonhosted.org/packages/b7/99/38434984d912edbd2e1969d116257e869578f67461bd7462b894c45ed874/regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a", size = 773935, upload-time = "2024-09-11T18:57:21.652Z" }, - { url = "https://files.pythonhosted.org/packages/ab/67/43174d2b46fa947b7b9dfe56b6c8a8a76d44223f35b1d64645a732fd1d6f/regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0", size = 261624, upload-time = "2024-09-11T18:57:23.777Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2a/4f9c47d9395b6aff24874c761d8d620c0232f97c43ef3cf668c8b355e7a7/regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623", size = 274020, upload-time = "2024-09-11T18:57:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/86/a1/d526b7b6095a0019aa360948c143aacfeb029919c898701ce7763bbe4c15/regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df", size = 482483, upload-time = "2024-09-11T18:57:26.694Z" }, - { url = "https://files.pythonhosted.org/packages/32/d9/bfdd153179867c275719e381e1e8e84a97bd186740456a0dcb3e7125c205/regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268", size = 287442, upload-time = "2024-09-11T18:57:28.133Z" }, - { url = "https://files.pythonhosted.org/packages/33/c4/60f3370735135e3a8d673ddcdb2507a8560d0e759e1398d366e43d000253/regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad", size = 284561, upload-time = "2024-09-11T18:57:30.83Z" }, - { url = "https://files.pythonhosted.org/packages/b1/51/91a5ebdff17f9ec4973cb0aa9d37635efec1c6868654bbc25d1543aca4ec/regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679", size = 791779, upload-time = "2024-09-11T18:57:32.461Z" }, - { url = "https://files.pythonhosted.org/packages/07/4a/022c5e6f0891a90cd7eb3d664d6c58ce2aba48bff107b00013f3d6167069/regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4", size = 832605, upload-time = "2024-09-11T18:57:34.01Z" }, - { url = "https://files.pythonhosted.org/packages/ac/1c/3793990c8c83ca04e018151ddda83b83ecc41d89964f0f17749f027fc44d/regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664", size = 818556, upload-time = "2024-09-11T18:57:36.363Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5c/8b385afbfacb853730682c57be56225f9fe275c5bf02ac1fc88edbff316d/regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50", size = 792808, upload-time = "2024-09-11T18:57:38.493Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8b/a4723a838b53c771e9240951adde6af58c829fb6a6a28f554e8131f53839/regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199", size = 781115, upload-time = "2024-09-11T18:57:41.4Z" }, - { url = "https://files.pythonhosted.org/packages/83/5f/031a04b6017033d65b261259c09043c06f4ef2d4eac841d0649d76d69541/regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4", size = 778155, upload-time = "2024-09-11T18:57:43.608Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cd/4660756070b03ce4a66663a43f6c6e7ebc2266cc6b4c586c167917185eb4/regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd", size = 784614, upload-time = "2024-09-11T18:57:45.219Z" }, - { url = "https://files.pythonhosted.org/packages/93/8d/65b9bea7df120a7be8337c415b6d256ba786cbc9107cebba3bf8ff09da99/regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f", size = 853744, upload-time = "2024-09-11T18:57:46.907Z" }, - { url = "https://files.pythonhosted.org/packages/96/a7/fba1eae75eb53a704475baf11bd44b3e6ccb95b316955027eb7748f24ef8/regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96", size = 855890, upload-time = "2024-09-11T18:57:49.264Z" }, - { url = "https://files.pythonhosted.org/packages/45/14/d864b2db80a1a3358534392373e8a281d95b28c29c87d8548aed58813910/regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1", size = 781887, upload-time = "2024-09-11T18:57:51.619Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a9/bfb29b3de3eb11dc9b412603437023b8e6c02fb4e11311863d9bf62c403a/regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9", size = 261644, upload-time = "2024-09-11T18:57:53.334Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ab/1ad2511cf6a208fde57fafe49829cab8ca018128ab0d0b48973d8218634a/regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf", size = 274033, upload-time = "2024-09-11T18:57:55.605Z" }, - { url = "https://files.pythonhosted.org/packages/6e/92/407531450762bed778eedbde04407f68cbd75d13cee96c6f8d6903d9c6c1/regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7", size = 483590, upload-time = "2024-09-11T18:57:57.793Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a2/048acbc5ae1f615adc6cba36cc45734e679b5f1e4e58c3c77f0ed611d4e2/regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231", size = 288175, upload-time = "2024-09-11T18:57:59.671Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ea/909d8620329ab710dfaf7b4adee41242ab7c9b95ea8d838e9bfe76244259/regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d", size = 284749, upload-time = "2024-09-11T18:58:01.855Z" }, - { url = "https://files.pythonhosted.org/packages/ca/fa/521eb683b916389b4975337873e66954e0f6d8f91bd5774164a57b503185/regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64", size = 795181, upload-time = "2024-09-11T18:58:03.985Z" }, - { url = "https://files.pythonhosted.org/packages/28/db/63047feddc3280cc242f9c74f7aeddc6ee662b1835f00046f57d5630c827/regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42", size = 835842, upload-time = "2024-09-11T18:58:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/e3/94/86adc259ff8ec26edf35fcca7e334566c1805c7493b192cb09679f9c3dee/regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766", size = 823533, upload-time = "2024-09-11T18:58:07.427Z" }, - { url = "https://files.pythonhosted.org/packages/29/52/84662b6636061277cb857f658518aa7db6672bc6d1a3f503ccd5aefc581e/regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a", size = 797037, upload-time = "2024-09-11T18:58:09.879Z" }, - { url = "https://files.pythonhosted.org/packages/c3/2a/cd4675dd987e4a7505f0364a958bc41f3b84942de9efaad0ef9a2646681c/regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9", size = 784106, upload-time = "2024-09-11T18:58:11.55Z" }, - { url = "https://files.pythonhosted.org/packages/6f/75/3ea7ec29de0bbf42f21f812f48781d41e627d57a634f3f23947c9a46e303/regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d", size = 782468, upload-time = "2024-09-11T18:58:13.552Z" }, - { url = "https://files.pythonhosted.org/packages/d3/67/15519d69b52c252b270e679cb578e22e0c02b8dd4e361f2b04efcc7f2335/regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822", size = 790324, upload-time = "2024-09-11T18:58:15.268Z" }, - { url = "https://files.pythonhosted.org/packages/9c/71/eff77d3fe7ba08ab0672920059ec30d63fa7e41aa0fb61c562726e9bd721/regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0", size = 860214, upload-time = "2024-09-11T18:58:17.583Z" }, - { url = "https://files.pythonhosted.org/packages/81/11/e1bdf84a72372e56f1ea4b833dd583b822a23138a616ace7ab57a0e11556/regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a", size = 859420, upload-time = "2024-09-11T18:58:19.898Z" }, - { url = "https://files.pythonhosted.org/packages/ea/75/9753e9dcebfa7c3645563ef5c8a58f3a47e799c872165f37c55737dadd3e/regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a", size = 787333, upload-time = "2024-09-11T18:58:21.699Z" }, - { url = "https://files.pythonhosted.org/packages/bc/4e/ba1cbca93141f7416624b3ae63573e785d4bc1834c8be44a8f0747919eca/regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776", size = 262058, upload-time = "2024-09-11T18:58:23.452Z" }, - { url = "https://files.pythonhosted.org/packages/6e/16/efc5f194778bf43e5888209e5cec4b258005d37c613b67ae137df3b89c53/regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009", size = 273526, upload-time = "2024-09-11T18:58:25.191Z" }, - { url = "https://files.pythonhosted.org/packages/93/0a/d1c6b9af1ff1e36832fe38d74d5c5bab913f2bdcbbd6bc0e7f3ce8b2f577/regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784", size = 483376, upload-time = "2024-09-11T18:58:27.11Z" }, - { url = "https://files.pythonhosted.org/packages/a4/42/5910a050c105d7f750a72dcb49c30220c3ae4e2654e54aaaa0e9bc0584cb/regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36", size = 288112, upload-time = "2024-09-11T18:58:28.78Z" }, - { url = "https://files.pythonhosted.org/packages/8d/56/0c262aff0e9224fa7ffce47b5458d373f4d3e3ff84e99b5ff0cb15e0b5b2/regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92", size = 284608, upload-time = "2024-09-11T18:58:30.498Z" }, - { url = "https://files.pythonhosted.org/packages/b9/54/9fe8f9aec5007bbbbce28ba3d2e3eaca425f95387b7d1e84f0d137d25237/regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86", size = 795337, upload-time = "2024-09-11T18:58:32.665Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e7/6b2f642c3cded271c4f16cc4daa7231be544d30fe2b168e0223724b49a61/regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85", size = 835848, upload-time = "2024-09-11T18:58:34.337Z" }, - { url = "https://files.pythonhosted.org/packages/cd/9e/187363bdf5d8c0e4662117b92aa32bf52f8f09620ae93abc7537d96d3311/regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963", size = 823503, upload-time = "2024-09-11T18:58:36.17Z" }, - { url = "https://files.pythonhosted.org/packages/f8/10/601303b8ee93589f879664b0cfd3127949ff32b17f9b6c490fb201106c4d/regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6", size = 797049, upload-time = "2024-09-11T18:58:38.225Z" }, - { url = "https://files.pythonhosted.org/packages/ef/1c/ea200f61ce9f341763f2717ab4daebe4422d83e9fd4ac5e33435fd3a148d/regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802", size = 784144, upload-time = "2024-09-11T18:58:40.605Z" }, - { url = "https://files.pythonhosted.org/packages/d8/5c/d2429be49ef3292def7688401d3deb11702c13dcaecdc71d2b407421275b/regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29", size = 782483, upload-time = "2024-09-11T18:58:42.58Z" }, - { url = "https://files.pythonhosted.org/packages/12/d9/cbc30f2ff7164f3b26a7760f87c54bf8b2faed286f60efd80350a51c5b99/regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8", size = 790320, upload-time = "2024-09-11T18:58:44.5Z" }, - { url = "https://files.pythonhosted.org/packages/19/1d/43ed03a236313639da5a45e61bc553c8d41e925bcf29b0f8ecff0c2c3f25/regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84", size = 860435, upload-time = "2024-09-11T18:58:47.014Z" }, - { url = "https://files.pythonhosted.org/packages/34/4f/5d04da61c7c56e785058a46349f7285ae3ebc0726c6ea7c5c70600a52233/regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554", size = 859571, upload-time = "2024-09-11T18:58:48.974Z" }, - { url = "https://files.pythonhosted.org/packages/12/7f/8398c8155a3c70703a8e91c29532558186558e1aea44144b382faa2a6f7a/regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8", size = 787398, upload-time = "2024-09-11T18:58:51.05Z" }, - { url = "https://files.pythonhosted.org/packages/58/3a/f5903977647a9a7e46d5535e9e96c194304aeeca7501240509bde2f9e17f/regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8", size = 262035, upload-time = "2024-09-11T18:58:53.526Z" }, - { url = "https://files.pythonhosted.org/packages/ff/80/51ba3a4b7482f6011095b3a036e07374f64de180b7d870b704ed22509002/regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f", size = 273510, upload-time = "2024-09-11T18:58:55.263Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d2/e6ee96b7dff201a83f650241c52db8e5bd080967cb93211f57aa448dc9d6/regex-2026.1.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e3dd93c8f9abe8aa4b6c652016da9a3afa190df5ad822907efe6b206c09896e", size = 488166, upload-time = "2026-01-14T23:13:46.408Z" }, + { url = "https://files.pythonhosted.org/packages/23/8a/819e9ce14c9f87af026d0690901b3931f3101160833e5d4c8061fa3a1b67/regex-2026.1.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97499ff7862e868b1977107873dd1a06e151467129159a6ffd07b66706ba3a9f", size = 290632, upload-time = "2026-01-14T23:13:48.688Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c3/23dfe15af25d1d45b07dfd4caa6003ad710dcdcb4c4b279909bdfe7a2de8/regex-2026.1.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bda75ebcac38d884240914c6c43d8ab5fb82e74cde6da94b43b17c411aa4c2b", size = 288500, upload-time = "2026-01-14T23:13:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/c6/31/1adc33e2f717df30d2f4d973f8776d2ba6ecf939301efab29fca57505c95/regex-2026.1.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dcc02368585334f5bc81fc73a2a6a0bbade60e7d83da21cead622faf408f32c", size = 781670, upload-time = "2026-01-14T23:13:52.453Z" }, + { url = "https://files.pythonhosted.org/packages/23/ce/21a8a22d13bc4adcb927c27b840c948f15fc973e21ed2346c1bd0eae22dc/regex-2026.1.15-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:693b465171707bbe882a7a05de5e866f33c76aa449750bee94a8d90463533cc9", size = 850820, upload-time = "2026-01-14T23:13:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/3eeacdf587a4705a44484cd0b30e9230a0e602811fb3e2cc32268c70d509/regex-2026.1.15-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0d190e6f013ea938623a58706d1469a62103fb2a241ce2873a9906e0386582c", size = 898777, upload-time = "2026-01-14T23:13:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/79/a9/1898a077e2965c35fc22796488141a22676eed2d73701e37c73ad7c0b459/regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ff818702440a5878a81886f127b80127f5d50563753a28211482867f8318106", size = 791750, upload-time = "2026-01-14T23:13:58.527Z" }, + { url = "https://files.pythonhosted.org/packages/4c/84/e31f9d149a178889b3817212827f5e0e8c827a049ff31b4b381e76b26e2d/regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f052d1be37ef35a54e394de66136e30fa1191fab64f71fc06ac7bc98c9a84618", size = 782674, upload-time = "2026-01-14T23:13:59.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ff/adf60063db24532add6a1676943754a5654dcac8237af024ede38244fd12/regex-2026.1.15-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6bfc31a37fd1592f0c4fc4bfc674b5c42e52efe45b4b7a6a14f334cca4bcebe4", size = 767906, upload-time = "2026-01-14T23:14:01.298Z" }, + { url = "https://files.pythonhosted.org/packages/af/3e/e6a216cee1e2780fec11afe7fc47b6f3925d7264e8149c607ac389fd9b1a/regex-2026.1.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d6ce5ae80066b319ae3bc62fd55a557c9491baa5efd0d355f0de08c4ba54e79", size = 774798, upload-time = "2026-01-14T23:14:02.715Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/23a4a8378a9208514ed3efc7e7850c27fa01e00ed8557c958df0335edc4a/regex-2026.1.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1704d204bd42b6bb80167df0e4554f35c255b579ba99616def38f69e14a5ccb9", size = 845861, upload-time = "2026-01-14T23:14:04.824Z" }, + { url = "https://files.pythonhosted.org/packages/f8/57/d7605a9d53bd07421a8785d349cd29677fe660e13674fa4c6cbd624ae354/regex-2026.1.15-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e3174a5ed4171570dc8318afada56373aa9289eb6dc0d96cceb48e7358b0e220", size = 755648, upload-time = "2026-01-14T23:14:06.371Z" }, + { url = "https://files.pythonhosted.org/packages/6f/76/6f2e24aa192da1e299cc1101674a60579d3912391867ce0b946ba83e2194/regex-2026.1.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87adf5bd6d72e3e17c9cb59ac4096b1faaf84b7eb3037a5ffa61c4b4370f0f13", size = 836250, upload-time = "2026-01-14T23:14:08.343Z" }, + { url = "https://files.pythonhosted.org/packages/11/3a/1f2a1d29453299a7858eab7759045fc3d9d1b429b088dec2dc85b6fa16a2/regex-2026.1.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e85dc94595f4d766bd7d872a9de5ede1ca8d3063f3bdf1e2c725f5eb411159e3", size = 779919, upload-time = "2026-01-14T23:14:09.954Z" }, + { url = "https://files.pythonhosted.org/packages/c0/67/eab9bc955c9dcc58e9b222c801e39cff7ca0b04261792a2149166ce7e792/regex-2026.1.15-cp310-cp310-win32.whl", hash = "sha256:21ca32c28c30d5d65fc9886ff576fc9b59bbca08933e844fa2363e530f4c8218", size = 265888, upload-time = "2026-01-14T23:14:11.35Z" }, + { url = "https://files.pythonhosted.org/packages/1d/62/31d16ae24e1f8803bddb0885508acecaec997fcdcde9c243787103119ae4/regex-2026.1.15-cp310-cp310-win_amd64.whl", hash = "sha256:3038a62fc7d6e5547b8915a3d927a0fbeef84cdbe0b1deb8c99bbd4a8961b52a", size = 277830, upload-time = "2026-01-14T23:14:12.908Z" }, + { url = "https://files.pythonhosted.org/packages/e5/36/5d9972bccd6417ecd5a8be319cebfd80b296875e7f116c37fb2a2deecebf/regex-2026.1.15-cp310-cp310-win_arm64.whl", hash = "sha256:505831646c945e3e63552cc1b1b9b514f0e93232972a2d5bedbcc32f15bc82e3", size = 270376, upload-time = "2026-01-14T23:14:14.782Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c9/0c80c96eab96948363d270143138d671d5731c3a692b417629bf3492a9d6/regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a", size = 488168, upload-time = "2026-01-14T23:14:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/17/f0/271c92f5389a552494c429e5cc38d76d1322eb142fb5db3c8ccc47751468/regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f", size = 290636, upload-time = "2026-01-14T23:14:17.715Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f9/5f1fd077d106ca5655a0f9ff8f25a1ab55b92128b5713a91ed7134ff688e/regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1", size = 288496, upload-time = "2026-01-14T23:14:19.326Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e1/8f43b03a4968c748858ec77f746c286d81f896c2e437ccf050ebc5d3128c/regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b", size = 793503, upload-time = "2026-01-14T23:14:20.922Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/a39a5e8edc5377a46a7c875c2f9a626ed3338cb3bb06931be461c3e1a34a/regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8", size = 860535, upload-time = "2026-01-14T23:14:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1c/9dce667a32a9477f7a2869c1c767dc00727284a9fa3ff5c09a5c6c03575e/regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413", size = 907225, upload-time = "2026-01-14T23:14:23.897Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3c/87ca0a02736d16b6262921425e84b48984e77d8e4e572c9072ce96e66c30/regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026", size = 800526, upload-time = "2026-01-14T23:14:26.039Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/647d5715aeea7c87bdcbd2f578f47b415f55c24e361e639fe8c0cc88878f/regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785", size = 773446, upload-time = "2026-01-14T23:14:28.109Z" }, + { url = "https://files.pythonhosted.org/packages/af/89/bf22cac25cb4ba0fe6bff52ebedbb65b77a179052a9d6037136ae93f42f4/regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e", size = 783051, upload-time = "2026-01-14T23:14:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f4/6ed03e71dca6348a5188363a34f5e26ffd5db1404780288ff0d79513bce4/regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763", size = 854485, upload-time = "2026-01-14T23:14:31.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/8e8560bd78caded8eb137e3e47612430a05b9a772caf60876435192d670a/regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb", size = 762195, upload-time = "2026-01-14T23:14:32.802Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61fc710f9aa8dfcd764fe27d37edfaa023b1a23305a0d84fccd5adb346ea/regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2", size = 845986, upload-time = "2026-01-14T23:14:34.898Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2e/fbee4cb93f9d686901a7ca8d94285b80405e8c34fe4107f63ffcbfb56379/regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1", size = 788992, upload-time = "2026-01-14T23:14:37.116Z" }, + { url = "https://files.pythonhosted.org/packages/ed/14/3076348f3f586de64b1ab75a3fbabdaab7684af7f308ad43be7ef1849e55/regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569", size = 265893, upload-time = "2026-01-14T23:14:38.426Z" }, + { url = "https://files.pythonhosted.org/packages/0f/19/772cf8b5fc803f5c89ba85d8b1870a1ca580dc482aa030383a9289c82e44/regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7", size = 277840, upload-time = "2026-01-14T23:14:39.785Z" }, + { url = "https://files.pythonhosted.org/packages/78/84/d05f61142709474da3c0853222d91086d3e1372bcdab516c6fd8d80f3297/regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec", size = 270374, upload-time = "2026-01-14T23:14:41.592Z" }, + { url = "https://files.pythonhosted.org/packages/92/81/10d8cf43c807d0326efe874c1b79f22bfb0fb226027b0b19ebc26d301408/regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1", size = 489398, upload-time = "2026-01-14T23:14:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/90/b0/7c2a74e74ef2a7c32de724658a69a862880e3e4155cba992ba04d1c70400/regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681", size = 291339, upload-time = "2026-01-14T23:14:45.183Z" }, + { url = "https://files.pythonhosted.org/packages/19/4d/16d0773d0c818417f4cc20aa0da90064b966d22cd62a8c46765b5bd2d643/regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f", size = 289003, upload-time = "2026-01-14T23:14:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e4/1fc4599450c9f0863d9406e944592d968b8d6dfd0d552a7d569e43bceada/regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa", size = 798656, upload-time = "2026-01-14T23:14:48.77Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e6/59650d73a73fa8a60b3a590545bfcf1172b4384a7df2e7fe7b9aab4e2da9/regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804", size = 864252, upload-time = "2026-01-14T23:14:50.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ab/1d0f4d50a1638849a97d731364c9a80fa304fec46325e48330c170ee8e80/regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c", size = 912268, upload-time = "2026-01-14T23:14:52.952Z" }, + { url = "https://files.pythonhosted.org/packages/dd/df/0d722c030c82faa1d331d1921ee268a4e8fb55ca8b9042c9341c352f17fa/regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5", size = 803589, upload-time = "2026-01-14T23:14:55.182Z" }, + { url = "https://files.pythonhosted.org/packages/66/23/33289beba7ccb8b805c6610a8913d0131f834928afc555b241caabd422a9/regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3", size = 775700, upload-time = "2026-01-14T23:14:56.707Z" }, + { url = "https://files.pythonhosted.org/packages/e7/65/bf3a42fa6897a0d3afa81acb25c42f4b71c274f698ceabd75523259f6688/regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb", size = 787928, upload-time = "2026-01-14T23:14:58.312Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f5/13bf65864fc314f68cdd6d8ca94adcab064d4d39dbd0b10fef29a9da48fc/regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410", size = 858607, upload-time = "2026-01-14T23:15:00.657Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/040e589834d7a439ee43fb0e1e902bc81bd58a5ba81acffe586bb3321d35/regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4", size = 763729, upload-time = "2026-01-14T23:15:02.248Z" }, + { url = "https://files.pythonhosted.org/packages/9b/84/6921e8129687a427edf25a34a5594b588b6d88f491320b9de5b6339a4fcb/regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d", size = 850697, upload-time = "2026-01-14T23:15:03.878Z" }, + { url = "https://files.pythonhosted.org/packages/8a/87/3d06143d4b128f4229158f2de5de6c8f2485170c7221e61bf381313314b2/regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22", size = 789849, upload-time = "2026-01-14T23:15:06.102Z" }, + { url = "https://files.pythonhosted.org/packages/77/69/c50a63842b6bd48850ebc7ab22d46e7a2a32d824ad6c605b218441814639/regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913", size = 266279, upload-time = "2026-01-14T23:15:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/f2/36/39d0b29d087e2b11fd8191e15e81cce1b635fcc845297c67f11d0d19274d/regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a", size = 277166, upload-time = "2026-01-14T23:15:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/28/32/5b8e476a12262748851fa8ab1b0be540360692325975b094e594dfebbb52/regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056", size = 270415, upload-time = "2026-01-14T23:15:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2e/6870bb16e982669b674cce3ee9ff2d1d46ab80528ee6bcc20fb2292efb60/regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e", size = 489164, upload-time = "2026-01-14T23:15:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/9774542e203849b0286badf67199970a44ebdb0cc5fb739f06e47ada72f8/regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10", size = 291218, upload-time = "2026-01-14T23:15:15.647Z" }, + { url = "https://files.pythonhosted.org/packages/b2/87/b0cda79f22b8dee05f774922a214da109f9a4c0eca5da2c9d72d77ea062c/regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc", size = 288895, upload-time = "2026-01-14T23:15:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6a/0041f0a2170d32be01ab981d6346c83a8934277d82c780d60b127331f264/regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599", size = 798680, upload-time = "2026-01-14T23:15:19.342Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/30e1cfcdbe3e891324aa7568b7c968771f82190df5524fabc1138cb2d45a/regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae", size = 864210, upload-time = "2026-01-14T23:15:22.005Z" }, + { url = "https://files.pythonhosted.org/packages/64/44/4db2f5c5ca0ccd40ff052ae7b1e9731352fcdad946c2b812285a7505ca75/regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5", size = 912358, upload-time = "2026-01-14T23:15:24.569Z" }, + { url = "https://files.pythonhosted.org/packages/79/b6/e6a5665d43a7c42467138c8a2549be432bad22cbd206f5ec87162de74bd7/regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6", size = 803583, upload-time = "2026-01-14T23:15:26.526Z" }, + { url = "https://files.pythonhosted.org/packages/e7/53/7cd478222169d85d74d7437e74750005e993f52f335f7c04ff7adfda3310/regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788", size = 775782, upload-time = "2026-01-14T23:15:29.352Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b5/75f9a9ee4b03a7c009fe60500fe550b45df94f0955ca29af16333ef557c5/regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714", size = 787978, upload-time = "2026-01-14T23:15:31.295Z" }, + { url = "https://files.pythonhosted.org/packages/72/b3/79821c826245bbe9ccbb54f6eadb7879c722fd3e0248c17bfc90bf54e123/regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d", size = 858550, upload-time = "2026-01-14T23:15:33.558Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/2ab5f77a1c465745bfbfcb3ad63178a58337ae8d5274315e2cc623a822fa/regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3", size = 763747, upload-time = "2026-01-14T23:15:35.206Z" }, + { url = "https://files.pythonhosted.org/packages/6d/84/c27df502d4bfe2873a3e3a7cf1bdb2b9cc10284d1a44797cf38bed790470/regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31", size = 850615, upload-time = "2026-01-14T23:15:37.523Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b7/658a9782fb253680aa8ecb5ccbb51f69e088ed48142c46d9f0c99b46c575/regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3", size = 789951, upload-time = "2026-01-14T23:15:39.582Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/5928af114441e059f15b2f63e188bd00c6529b3051c974ade7444b85fcda/regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f", size = 266275, upload-time = "2026-01-14T23:15:42.108Z" }, + { url = "https://files.pythonhosted.org/packages/4f/16/5bfbb89e435897bff28cf0352a992ca719d9e55ebf8b629203c96b6ce4f7/regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e", size = 277145, upload-time = "2026-01-14T23:15:44.244Z" }, + { url = "https://files.pythonhosted.org/packages/56/c1/a09ff7392ef4233296e821aec5f78c51be5e91ffde0d163059e50fd75835/regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337", size = 270411, upload-time = "2026-01-14T23:15:45.858Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/0cfd5a78e5c6db00e6782fdae70458f89850ce95baa5e8694ab91d89744f/regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be", size = 492068, upload-time = "2026-01-14T23:15:47.616Z" }, + { url = "https://files.pythonhosted.org/packages/50/72/6c86acff16cb7c959c4355826bbf06aad670682d07c8f3998d9ef4fee7cd/regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8", size = 292756, upload-time = "2026-01-14T23:15:49.307Z" }, + { url = "https://files.pythonhosted.org/packages/4e/58/df7fb69eadfe76526ddfce28abdc0af09ffe65f20c2c90932e89d705153f/regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd", size = 291114, upload-time = "2026-01-14T23:15:51.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6c/a4011cd1cf96b90d2cdc7e156f91efbd26531e822a7fbb82a43c1016678e/regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a", size = 807524, upload-time = "2026-01-14T23:15:53.102Z" }, + { url = "https://files.pythonhosted.org/packages/1d/25/a53ffb73183f69c3e9f4355c4922b76d2840aee160af6af5fac229b6201d/regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93", size = 873455, upload-time = "2026-01-14T23:15:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/66/0b/8b47fc2e8f97d9b4a851736f3890a5f786443aa8901061c55f24c955f45b/regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af", size = 915007, upload-time = "2026-01-14T23:15:57.041Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/97de0d681e6d26fabe71968dbee06dd52819e9a22fdce5dac7256c31ed84/regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09", size = 812794, upload-time = "2026-01-14T23:15:58.916Z" }, + { url = "https://files.pythonhosted.org/packages/22/38/e752f94e860d429654aa2b1c51880bff8dfe8f084268258adf9151cf1f53/regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5", size = 781159, upload-time = "2026-01-14T23:16:00.817Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a7/d739ffaef33c378fc888302a018d7f81080393d96c476b058b8c64fd2b0d/regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794", size = 795558, upload-time = "2026-01-14T23:16:03.267Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c4/542876f9a0ac576100fc73e9c75b779f5c31e3527576cfc9cb3009dcc58a/regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a", size = 868427, upload-time = "2026-01-14T23:16:05.646Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0f/d5655bea5b22069e32ae85a947aa564912f23758e112cdb74212848a1a1b/regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80", size = 769939, upload-time = "2026-01-14T23:16:07.542Z" }, + { url = "https://files.pythonhosted.org/packages/20/06/7e18a4fa9d326daeda46d471a44ef94201c46eaa26dbbb780b5d92cbfdda/regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2", size = 854753, upload-time = "2026-01-14T23:16:10.395Z" }, + { url = "https://files.pythonhosted.org/packages/3b/67/dc8946ef3965e166f558ef3b47f492bc364e96a265eb4a2bb3ca765c8e46/regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60", size = 799559, upload-time = "2026-01-14T23:16:12.347Z" }, + { url = "https://files.pythonhosted.org/packages/a5/61/1bba81ff6d50c86c65d9fd84ce9699dd106438ee4cdb105bf60374ee8412/regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952", size = 268879, upload-time = "2026-01-14T23:16:14.049Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/cef7d4c5fb0ea3ac5c775fd37db5747f7378b29526cc83f572198924ff47/regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10", size = 280317, upload-time = "2026-01-14T23:16:15.718Z" }, + { url = "https://files.pythonhosted.org/packages/b4/52/4317f7a5988544e34ab57b4bde0f04944c4786128c933fb09825924d3e82/regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829", size = 271551, upload-time = "2026-01-14T23:16:17.533Z" }, ] [[package]] From 79a01fca317de12737858f85f81cabf52570a4a2 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:26:49 -0800 Subject: [PATCH 13/14] feat: introduce PlanningConfig for enhanced agent planning capabilities (#4344) * feat: introduce PlanningConfig for enhanced agent planning capabilities This update adds a new PlanningConfig class to manage agent planning configurations, allowing for customizable planning behavior before task execution. The existing reasoning parameter is deprecated in favor of this new configuration, ensuring backward compatibility while enhancing the planning process. Additionally, the Agent class has been updated to utilize this new configuration, and relevant utility functions have been adjusted accordingly. Tests have been added to validate the new planning functionality and ensure proper integration with existing agent workflows. * dropping redundancy * fix test * revert handle_reasoning here * refactor: update reasoning handling in Agent class This commit modifies the Agent class to conditionally call the handle_reasoning function based on the executor class being used. The legacy CrewAgentExecutor will continue to utilize handle_reasoning, while the new AgentExecutor will manage planning internally. Additionally, the PlanningConfig class has been referenced in the documentation to clarify its role in enabling or disabling planning. Tests have been updated to reflect these changes and ensure proper functionality. * improve planning prompts * matching * refactor: remove default enabled flag from PlanningConfig in Agent class * more cassettes * fix test * refactor: update planning prompt and remove deprecated methods in reasoning handler * improve planning prompt --- lib/crewai/src/crewai/__init__.py | 2 + lib/crewai/src/crewai/agent/core.py | 44 +- .../src/crewai/agent/planning_config.py | 83 ++++ lib/crewai/src/crewai/agent/utils.py | 21 +- .../crewai/events/types/reasoning_events.py | 2 +- .../src/crewai/experimental/agent_executor.py | 51 ++ lib/crewai/src/crewai/translations/en.json | 13 +- .../src/crewai/utilities/reasoning_handler.py | 398 ++++++++++----- lib/crewai/tests/agents/test_agent.py | 8 +- .../tests/agents/test_agent_executor.py | 258 ++++++++++ .../tests/agents/test_agent_reasoning.py | 453 +++++++++++------- ...ff_with_planning_stores_plan_in_state.yaml | 234 +++++++++ ...ithout_planning_skips_plan_generation.yaml | 108 +++++ ...or_state_contains_plan_after_planning.yaml | 230 +++++++++ ...anning_config_disabled_skips_planning.yaml | 108 +++++ ...tes_minimal_steps_for_multi_step_task.yaml | 247 ++++++++++ ...test_planning_disabled_skips_planning.yaml | 108 +++++ ...ng_handles_sequential_dependency_task.yaml | 242 ++++++++++ .../agents/test_agent_execute_task_basic.yaml | 30 +- .../test_agent_execute_task_with_context.yaml | 32 +- ...st_agent_execute_task_with_custom_llm.yaml | 30 +- ...test_agent_execute_task_with_planning.yaml | 231 +++++++++ ...ent_execute_task_with_planning_refine.yaml | 243 ++++++++++ .../test_agent_execute_task_with_tool.yaml | 147 +++++- ...t_agent_execute_task_without_planning.yaml | 110 +++++ ...kickoff_multi_step_task_with_planning.yaml | 243 ++++++++++ .../test_agent_kickoff_with_planning.yaml | 238 +++++++++ ..._agent_kickoff_with_planning_disabled.yaml | 110 +++++ .../test_agent_kickoff_without_planning.yaml | 108 +++++ 29 files changed, 3770 insertions(+), 362 deletions(-) create mode 100644 lib/crewai/src/crewai/agent/planning_config.py create mode 100644 lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_agent_kickoff_with_planning_stores_plan_in_state.yaml create mode 100644 lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_agent_kickoff_without_planning_skips_plan_generation.yaml create mode 100644 lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_executor_state_contains_plan_after_planning.yaml create mode 100644 lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_config_disabled_skips_planning.yaml create mode 100644 lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_creates_minimal_steps_for_multi_step_task.yaml create mode 100644 lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_disabled_skips_planning.yaml create mode 100644 lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_handles_sequential_dependency_task.yaml create mode 100644 lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_planning.yaml create mode 100644 lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_planning_refine.yaml create mode 100644 lib/crewai/tests/cassettes/agents/test_agent_execute_task_without_planning.yaml create mode 100644 lib/crewai/tests/cassettes/agents/test_agent_kickoff_multi_step_task_with_planning.yaml create mode 100644 lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_planning.yaml create mode 100644 lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_planning_disabled.yaml create mode 100644 lib/crewai/tests/cassettes/agents/test_agent_kickoff_without_planning.yaml diff --git a/lib/crewai/src/crewai/__init__.py b/lib/crewai/src/crewai/__init__.py index b410be7e5..c670ac8e8 100644 --- a/lib/crewai/src/crewai/__init__.py +++ b/lib/crewai/src/crewai/__init__.py @@ -4,6 +4,7 @@ import urllib.request import warnings from crewai.agent.core import Agent +from crewai.agent.planning_config import PlanningConfig from crewai.crew import Crew from crewai.crews.crew_output import CrewOutput from crewai.flow.flow import Flow @@ -80,6 +81,7 @@ __all__ = [ "Flow", "Knowledge", "LLMGuardrail", + "PlanningConfig", "Process", "Task", "TaskOutput", diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 6c2626a28..660610e67 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -24,6 +24,7 @@ from pydantic import ( ) from typing_extensions import Self +from crewai.agent.planning_config import PlanningConfig from crewai.agent.utils import ( ahandle_knowledge_retrieval, apply_training_data, @@ -210,13 +211,23 @@ class Agent(BaseAgent): default="safe", description="Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution).", ) - reasoning: bool = Field( + planning_config: PlanningConfig | None = Field( + default=None, + description="Configuration for agent planning before task execution.", + ) + planning: bool = Field( default=False, description="Whether the agent should reflect and create a plan before executing a task.", ) + reasoning: bool = Field( + default=False, + description="[DEPRECATED: Use planning_config instead] Whether the agent should reflect and create a plan before executing a task.", + deprecated=True, + ) max_reasoning_attempts: int | None = Field( default=None, - description="Maximum number of reasoning attempts before executing the task. If None, will try until ready.", + description="[DEPRECATED: Use planning_config.max_attempts instead] Maximum number of reasoning attempts before executing the task. If None, will try until ready.", + deprecated=True, ) embedder: EmbedderConfig | None = Field( default=None, @@ -283,8 +294,26 @@ class Agent(BaseAgent): if self.allow_code_execution: self._validate_docker_installation() + # Handle backward compatibility: convert reasoning=True to planning_config + if self.reasoning and self.planning_config is None: + import warnings + + warnings.warn( + "The 'reasoning' parameter is deprecated. Use 'planning_config=PlanningConfig()' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.planning_config = PlanningConfig( + max_attempts=self.max_reasoning_attempts, + ) + return self + @property + def planning_enabled(self) -> bool: + """Check if planning is enabled for this agent.""" + return self.planning_config is not None or self.planning + def _setup_agent_executor(self) -> None: if not self.cache_handler: self.cache_handler = CacheHandler() @@ -360,7 +389,11 @@ class Agent(BaseAgent): ValueError: If the max execution time is not a positive integer. RuntimeError: If the agent execution fails for other reasons. """ - handle_reasoning(self, task) + # Only call handle_reasoning for legacy CrewAgentExecutor + # For AgentExecutor, planning is handled in AgentExecutor.generate_plan() + if self.executor_class is not AgentExecutor: + handle_reasoning(self, task) + self._inject_date_to_task(task) if self.tools_handler: @@ -595,7 +628,10 @@ class Agent(BaseAgent): ValueError: If the max execution time is not a positive integer. RuntimeError: If the agent execution fails for other reasons. """ - handle_reasoning(self, task) + if self.executor_class is not AgentExecutor: + handle_reasoning( + self, task + ) # we need this till CrewAgentExecutor migrates to AgentExecutor self._inject_date_to_task(task) if self.tools_handler: diff --git a/lib/crewai/src/crewai/agent/planning_config.py b/lib/crewai/src/crewai/agent/planning_config.py new file mode 100644 index 000000000..f93e6dd8d --- /dev/null +++ b/lib/crewai/src/crewai/agent/planning_config.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, Field + + +class PlanningConfig(BaseModel): + """Configuration for agent planning/reasoning before task execution. + + This allows users to customize the planning behavior including prompts, + iteration limits, and the LLM used for planning. + + Note: To disable planning, don't pass a planning_config or set planning=False + on the Agent. The presence of a PlanningConfig enables planning. + + Attributes: + max_attempts: Maximum number of planning refinement attempts. + If None, will continue until the agent indicates readiness. + max_steps: Maximum number of steps in the generated plan. + system_prompt: Custom system prompt for planning. Uses default if None. + plan_prompt: Custom prompt for creating the initial plan. + refine_prompt: Custom prompt for refining the plan. + llm: LLM to use for planning. Uses agent's LLM if None. + + Example: + ```python + from crewai import Agent + from crewai.agent.planning_config import PlanningConfig + + # Simple usage + agent = Agent( + role="Researcher", + goal="Research topics", + backstory="Expert researcher", + planning_config=PlanningConfig(), + ) + + # Customized planning + agent = Agent( + role="Researcher", + goal="Research topics", + backstory="Expert researcher", + planning_config=PlanningConfig( + max_attempts=3, + max_steps=10, + plan_prompt="Create a focused plan for: {description}", + llm="gpt-4o-mini", # Use cheaper model for planning + ), + ) + ``` + """ + + max_attempts: int | None = Field( + default=None, + description=( + "Maximum number of planning refinement attempts. " + "If None, will continue until the agent indicates readiness." + ), + ) + max_steps: int = Field( + default=20, + description="Maximum number of steps in the generated plan.", + ge=1, + ) + system_prompt: str | None = Field( + default=None, + description="Custom system prompt for planning. Uses default if None.", + ) + plan_prompt: str | None = Field( + default=None, + description="Custom prompt for creating the initial plan.", + ) + refine_prompt: str | None = Field( + default=None, + description="Custom prompt for refining the plan.", + ) + llm: str | Any | None = Field( + default=None, + description="LLM to use for planning. Uses agent's LLM if None.", + ) + + model_config = {"arbitrary_types_allowed": True} diff --git a/lib/crewai/src/crewai/agent/utils.py b/lib/crewai/src/crewai/agent/utils.py index fb9d2b75a..fc74db433 100644 --- a/lib/crewai/src/crewai/agent/utils.py +++ b/lib/crewai/src/crewai/agent/utils.py @@ -28,13 +28,20 @@ if TYPE_CHECKING: def handle_reasoning(agent: Agent, task: Task) -> None: - """Handle the reasoning process for an agent before task execution. + """Handle the reasoning/planning process for an agent before task execution. + + This function checks if planning is enabled for the agent and, if so, + creates a plan that gets appended to the task description. + + Note: This function is used by CrewAgentExecutor (legacy path). + For AgentExecutor, planning is handled in AgentExecutor.generate_plan(). Args: agent: The agent performing the task. task: The task to execute. """ - if not agent.reasoning: + # Check if planning is enabled using the planning_enabled property + if not getattr(agent, "planning_enabled", False): return try: @@ -43,13 +50,13 @@ def handle_reasoning(agent: Agent, task: Task) -> None: AgentReasoningOutput, ) - reasoning_handler = AgentReasoning(task=task, agent=agent) - reasoning_output: AgentReasoningOutput = ( - reasoning_handler.handle_agent_reasoning() + planning_handler = AgentReasoning(agent=agent, task=task) + planning_output: AgentReasoningOutput = ( + planning_handler.handle_agent_reasoning() ) - task.description += f"\n\nReasoning Plan:\n{reasoning_output.plan.plan}" + task.description += f"\n\nPlanning:\n{planning_output.plan.plan}" except Exception as e: - agent._logger.log("error", f"Error during reasoning process: {e!s}") + agent._logger.log("error", f"Error during planning: {e!s}") def build_task_prompt_with_schema(task: Task, task_prompt: str, i18n: I18N) -> str: diff --git a/lib/crewai/src/crewai/events/types/reasoning_events.py b/lib/crewai/src/crewai/events/types/reasoning_events.py index 53ac47b07..7b61d69f7 100644 --- a/lib/crewai/src/crewai/events/types/reasoning_events.py +++ b/lib/crewai/src/crewai/events/types/reasoning_events.py @@ -9,7 +9,7 @@ class ReasoningEvent(BaseEvent): type: str attempt: int = 1 agent_role: str - task_id: str + task_id: str | None = None task_name: str | None = None from_task: Any | None = None agent_id: str | None = None diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index b9d8adccc..12dbc6bf3 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -94,6 +94,10 @@ class AgentReActState(BaseModel): ask_for_human_input: bool = Field(default=False) use_native_tools: bool = Field(default=False) pending_tool_calls: list[Any] = Field(default_factory=list) + plan: str | None = Field(default=None, description="Generated execution plan") + plan_ready: bool = Field( + default=False, description="Whether agent is ready to execute" + ) class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): @@ -321,6 +325,45 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): self._state.iterations = value @start() + def generate_plan(self) -> None: + """Generate execution plan if planning is enabled. + + This is the entry point for the agent execution flow. If planning is + enabled on the agent, it generates a plan before execution begins. + The plan is stored in state but not executed on yet (Phase 2). + """ + if not getattr(self.agent, "planning_enabled", False): + return + + try: + from crewai.utilities.reasoning_handler import AgentReasoning + + if self.task: + planning_handler = AgentReasoning(agent=self.agent, task=self.task) + else: + # For kickoff() path - use input text directly, no Task needed + input_text = getattr(self, "_kickoff_input", "") + planning_handler = AgentReasoning( + agent=self.agent, + description=input_text or "Complete the requested task", + expected_output="Complete the task successfully", + ) + + output = planning_handler.handle_agent_reasoning() + + self.state.plan = output.plan.plan + self.state.plan_ready = output.plan.ready + + # Backward compatibility: append plan to task description + # This can be removed in Phase 2 when plan execution is implemented + if self.task and self.state.plan: + self.task.description += f"\n\nPlanning:\n{self.state.plan}" + + except Exception as e: + if hasattr(self.agent, "_logger"): + self.agent._logger.log("error", f"Error during planning: {e!s}") + + @listen(generate_plan) def initialize_reasoning(self) -> Literal["initialized"]: """Initialize the reasoning flow and emit agent start logs.""" self._show_start_logs() @@ -991,6 +1034,10 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): self.state.is_finished = False self.state.use_native_tools = False self.state.pending_tool_calls = [] + self.state.plan = None + self.state.plan_ready = False + + self._kickoff_input = inputs.get("input", "") if "system" in self.prompt: prompt = cast("SystemPromptResult", self.prompt) @@ -1075,6 +1122,10 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): self.state.is_finished = False self.state.use_native_tools = False self.state.pending_tool_calls = [] + self.state.plan = None + self.state.plan_ready = False + + self._kickoff_input = inputs.get("input", "") if "system" in self.prompt: prompt = cast("SystemPromptResult", self.prompt) diff --git a/lib/crewai/src/crewai/translations/en.json b/lib/crewai/src/crewai/translations/en.json index ab05ece4e..cd1d4e3c4 100644 --- a/lib/crewai/src/crewai/translations/en.json +++ b/lib/crewai/src/crewai/translations/en.json @@ -58,9 +58,14 @@ } }, "reasoning": { - "initial_plan": "You are {role}, a professional with the following background: {backstory}\n\nYour primary goal is: {goal}\n\nAs {role}, you are creating a strategic plan for a task that requires your expertise and unique perspective.", - "refine_plan": "You are {role}, a professional with the following background: {backstory}\n\nYour primary goal is: {goal}\n\nAs {role}, you are refining a strategic plan for a task that requires your expertise and unique perspective.", - "create_plan_prompt": "You are {role} with this background: {backstory}\n\nYour primary goal is: {goal}\n\nYou have been assigned the following task:\n{description}\n\nExpected output:\n{expected_output}\n\nAvailable tools: {tools}\n\nBefore executing this task, create a detailed plan that leverages your expertise as {role} and outlines:\n1. Your understanding of the task from your professional perspective\n2. The key steps you'll take to complete it, drawing on your background and skills\n3. How you'll approach any challenges that might arise, considering your expertise\n4. How you'll strategically use the available tools based on your experience, exactly what tools to use and how to use them\n5. The expected outcome and how it aligns with your goal\n\nAfter creating your plan, assess whether you feel ready to execute the task or if you could do better.\nConclude with one of these statements:\n- \"READY: I am ready to execute the task.\"\n- \"NOT READY: I need to refine my plan because [specific reason].\"", - "refine_plan_prompt": "You are {role} with this background: {backstory}\n\nYour primary goal is: {goal}\n\nYou created the following plan for this task:\n{current_plan}\n\nHowever, you indicated that you're not ready to execute the task yet.\n\nPlease refine your plan further, drawing on your expertise as {role} to address any gaps or uncertainties. As you refine your plan, be specific about which available tools you will use, how you will use them, and why they are the best choices for each step. Clearly outline your tool usage strategy as part of your improved plan.\n\nAfter refining your plan, assess whether you feel ready to execute the task.\nConclude with one of these statements:\n- \"READY: I am ready to execute the task.\"\n- \"NOT READY: I need to refine my plan further because [specific reason].\"" + "initial_plan": "You are {role}. Create a focused execution plan using only the essential steps needed.", + "refine_plan": "You are {role}. Refine your plan to address the specific gap while keeping it minimal.", + "create_plan_prompt": "You are {role}.\n\nTask: {description}\n\nExpected output: {expected_output}\n\nAvailable tools: {tools}\n\nCreate a focused plan with ONLY the essential steps needed. Most tasks require just 2-5 steps. Do NOT pad with unnecessary steps like \"review\", \"verify\", \"document\", or \"finalize\" unless explicitly required.\n\nFor each step, specify the action and which tool to use (if any).\n\nConclude with:\n- \"READY: I am ready to execute the task.\"\n- \"NOT READY: I need to refine my plan because [specific reason].\"", + "refine_plan_prompt": "Your plan:\n{current_plan}\n\nYou indicated you're not ready. Address the specific gap while keeping the plan minimal.\n\nConclude with READY or NOT READY." + }, + "planning": { + "system_prompt": "You are a strategic planning assistant. Create minimal, effective execution plans. Prefer fewer steps over more.", + "create_plan_prompt": "Create a focused execution plan for the following task:\n\n## Task\n{description}\n\n## Expected Output\n{expected_output}\n\n## Available Tools\n{tools}\n\n## Planning Principles\nFocus on WHAT needs to be accomplished, not HOW. Group related actions into logical units. Fewer steps = better. Most tasks need 3-6 steps. Hard limit: {max_steps} steps.\n\n## Step Types (only these are valid):\n1. **Tool Step**: Uses a tool to gather information or take action\n2. **Output Step**: Synthesizes prior results into the final deliverable (usually the last step)\n\n## Rules:\n- Each step must either USE A TOOL or PRODUCE THE FINAL OUTPUT\n- Combine related tool calls: \"Research A, B, and C\" = ONE step, not three\n- Combine all synthesis into ONE final output step\n- NO standalone \"thinking\" steps (review, verify, confirm, refine, analyze) - these happen naturally between steps\n\nFor each step: State the action, specify the tool (if any), and note dependencies.\n\nAfter your plan, state READY or NOT READY.", + "refine_plan_prompt": "Your previous plan:\n{current_plan}\n\nYou indicated you weren't ready. Refine your plan to address the specific gap.\n\nKeep the plan minimal - only add steps that directly address the issue.\n\nConclude with READY or NOT READY as before." } } diff --git a/lib/crewai/src/crewai/utilities/reasoning_handler.py b/lib/crewai/src/crewai/utilities/reasoning_handler.py index e9bb62997..cab2fc4f9 100644 --- a/lib/crewai/src/crewai/utilities/reasoning_handler.py +++ b/lib/crewai/src/crewai/utilities/reasoning_handler.py @@ -1,10 +1,13 @@ +"""Handles planning/reasoning for agents before task execution.""" + +from __future__ import annotations + import json import logging -from typing import Any, Final, Literal, cast +from typing import TYPE_CHECKING, Any, Final, Literal, cast from pydantic import BaseModel, Field -from crewai.agent import Agent from crewai.events.event_bus import crewai_event_bus from crewai.events.types.reasoning_events import ( AgentReasoningCompletedEvent, @@ -12,10 +15,16 @@ from crewai.events.types.reasoning_events import ( AgentReasoningStartedEvent, ) from crewai.llm import LLM -from crewai.task import Task +from crewai.utilities.llm_utils import create_llm from crewai.utilities.string_utils import sanitize_tool_name +if TYPE_CHECKING: + from crewai.agent import Agent + from crewai.agent.planning_config import PlanningConfig + from crewai.task import Task + + class ReasoningPlan(BaseModel): """Model representing a reasoning plan for a task.""" @@ -29,6 +38,11 @@ class AgentReasoningOutput(BaseModel): plan: ReasoningPlan = Field(description="The reasoning plan for the task.") +# Aliases for backward compatibility +PlanningPlan = ReasoningPlan +AgentPlanningOutput = AgentReasoningOutput + + FUNCTION_SCHEMA: Final[dict[str, Any]] = { "type": "function", "function": { @@ -47,6 +61,7 @@ FUNCTION_SCHEMA: Final[dict[str, Any]] = { }, }, "required": ["plan", "ready"], + "additionalProperties": False, }, }, } @@ -54,41 +69,101 @@ FUNCTION_SCHEMA: Final[dict[str, Any]] = { class AgentReasoning: """ - Handles the agent reasoning process, enabling an agent to reflect and create a plan - before executing a task. + Handles the agent planning/reasoning process, enabling an agent to reflect + and create a plan before executing a task. Attributes: - task: The task for which the agent is reasoning. - agent: The agent performing the reasoning. - llm: The language model used for reasoning. + task: The task for which the agent is planning (optional). + agent: The agent performing the planning. + config: The planning configuration. + llm: The language model used for planning. logger: Logger for logging events and errors. + description: Task description or input text for planning. + expected_output: Expected output description. """ - def __init__(self, task: Task, agent: Agent) -> None: - """Initialize the AgentReasoning with a task and an agent. + def __init__( + self, + agent: Agent, + task: Task | None = None, + *, + description: str | None = None, + expected_output: str | None = None, + ) -> None: + """Initialize the AgentReasoning with an agent and optional task. Args: - task: The task for which the agent is reasoning. - agent: The agent performing the reasoning. + agent: The agent performing the planning. + task: The task for which the agent is planning (optional). + description: Task description or input text (used if task is None). + expected_output: Expected output (used if task is None). """ - self.task = task self.agent = agent - self.llm = cast(LLM, agent.llm) + self.task = task + # Use task attributes if available, otherwise use provided values + self._description = description or ( + task.description if task else "Complete the requested task" + ) + self._expected_output = expected_output or ( + task.expected_output if task else "Complete the task successfully" + ) + self.config = self._get_planning_config() + self.llm = self._resolve_llm() self.logger = logging.getLogger(__name__) - def handle_agent_reasoning(self) -> AgentReasoningOutput: - """Public method for the reasoning process that creates and refines a plan for the task until the agent is ready to execute it. + @property + def description(self) -> str: + """Get the task/input description.""" + return self._description + + @property + def expected_output(self) -> str: + """Get the expected output.""" + return self._expected_output + + def _get_planning_config(self) -> PlanningConfig: + """Get the planning configuration from the agent. Returns: - AgentReasoningOutput: The output of the agent reasoning process. + The planning configuration, using defaults if not set. """ - # Emit a reasoning started event (attempt 1) + from crewai.agent.planning_config import PlanningConfig + + if self.agent.planning_config is not None: + return self.agent.planning_config + # Fallback for backward compatibility + return PlanningConfig( + max_attempts=getattr(self.agent, "max_reasoning_attempts", None), + ) + + def _resolve_llm(self) -> LLM: + """Resolve which LLM to use for planning. + + Returns: + The LLM to use - either from config or the agent's LLM. + """ + if self.config.llm is not None: + if isinstance(self.config.llm, LLM): + return self.config.llm + return create_llm(self.config.llm) + return cast(LLM, self.agent.llm) + + def handle_agent_reasoning(self) -> AgentReasoningOutput: + """Public method for the planning process that creates and refines a plan + for the task until the agent is ready to execute it. + + Returns: + AgentReasoningOutput: The output of the agent planning process. + """ + task_id = str(self.task.id) if self.task else "kickoff" + + # Emit a planning started event (attempt 1) try: crewai_event_bus.emit( self.agent, AgentReasoningStartedEvent( agent_role=self.agent.role, - task_id=str(self.task.id), + task_id=task_id, attempt=1, from_task=self.task, ), @@ -98,13 +173,13 @@ class AgentReasoning: pass try: - output = self.__handle_agent_reasoning() + output = self._execute_planning() crewai_event_bus.emit( self.agent, AgentReasoningCompletedEvent( agent_role=self.agent.role, - task_id=str(self.task.id), + task_id=task_id, plan=output.plan.plan, ready=output.plan.ready, attempt=1, @@ -115,71 +190,68 @@ class AgentReasoning: return output except Exception as e: - # Emit reasoning failed event + # Emit planning failed event try: crewai_event_bus.emit( self.agent, AgentReasoningFailedEvent( agent_role=self.agent.role, - task_id=str(self.task.id), + task_id=task_id, error=str(e), attempt=1, from_task=self.task, from_agent=self.agent, ), ) - except Exception as e: - logging.error(f"Error emitting reasoning failed event: {e}") + except Exception as event_error: + logging.error(f"Error emitting planning failed event: {event_error}") raise - def __handle_agent_reasoning(self) -> AgentReasoningOutput: - """Private method that handles the agent reasoning process. + def _execute_planning(self) -> AgentReasoningOutput: + """Execute the planning process. Returns: - The output of the agent reasoning process. + The output of the agent planning process. """ - plan, ready = self.__create_initial_plan() - - plan, ready = self.__refine_plan_if_needed(plan, ready) + plan, ready = self._create_initial_plan() + plan, ready = self._refine_plan_if_needed(plan, ready) reasoning_plan = ReasoningPlan(plan=plan, ready=ready) return AgentReasoningOutput(plan=reasoning_plan) - def __create_initial_plan(self) -> tuple[str, bool]: - """Creates the initial reasoning plan for the task. + def _create_initial_plan(self) -> tuple[str, bool]: + """Creates the initial plan for the task. Returns: The initial plan and whether the agent is ready to execute the task. """ - reasoning_prompt = self.__create_reasoning_prompt() + planning_prompt = self._create_planning_prompt() if self.llm.supports_function_calling(): - plan, ready = self.__call_with_function(reasoning_prompt, "initial_plan") + plan, ready = self._call_with_function(planning_prompt, "create_plan") return plan, ready - response = _call_llm_with_reasoning_prompt( - llm=self.llm, - prompt=reasoning_prompt, - task=self.task, - reasoning_agent=self.agent, - backstory=self.__get_agent_backstory(), - plan_type="initial_plan", + + response = self._call_llm_with_prompt( + prompt=planning_prompt, + plan_type="create_plan", ) - return self.__parse_reasoning_response(str(response)) + return self._parse_planning_response(str(response)) - def __refine_plan_if_needed(self, plan: str, ready: bool) -> tuple[str, bool]: - """Refines the reasoning plan if the agent is not ready to execute the task. + def _refine_plan_if_needed(self, plan: str, ready: bool) -> tuple[str, bool]: + """Refines the plan if the agent is not ready to execute the task. Args: - plan: The current reasoning plan. + plan: The current plan. ready: Whether the agent is ready to execute the task. Returns: The refined plan and whether the agent is ready to execute the task. """ attempt = 1 - max_attempts = self.agent.max_reasoning_attempts + max_attempts = self.config.max_attempts + task_id = str(self.task.id) if self.task else "kickoff" while not ready and (max_attempts is None or attempt < max_attempts): # Emit event for each refinement attempt @@ -188,7 +260,7 @@ class AgentReasoning: self.agent, AgentReasoningStartedEvent( agent_role=self.agent.role, - task_id=str(self.task.id), + task_id=task_id, attempt=attempt + 1, from_task=self.task, ), @@ -196,53 +268,48 @@ class AgentReasoning: except Exception: # noqa: S110 pass - refine_prompt = self.__create_refine_prompt(plan) + refine_prompt = self._create_refine_prompt(plan) if self.llm.supports_function_calling(): - plan, ready = self.__call_with_function(refine_prompt, "refine_plan") + plan, ready = self._call_with_function(refine_prompt, "refine_plan") else: - response = _call_llm_with_reasoning_prompt( - llm=self.llm, + response = self._call_llm_with_prompt( prompt=refine_prompt, - task=self.task, - reasoning_agent=self.agent, - backstory=self.__get_agent_backstory(), plan_type="refine_plan", ) - plan, ready = self.__parse_reasoning_response(str(response)) + plan, ready = self._parse_planning_response(str(response)) attempt += 1 if max_attempts is not None and attempt >= max_attempts: self.logger.warning( - f"Agent reasoning reached maximum attempts ({max_attempts}) without being ready. Proceeding with current plan." + f"Agent planning reached maximum attempts ({max_attempts}) " + "without being ready. Proceeding with current plan." ) break return plan, ready - def __call_with_function(self, prompt: str, prompt_type: str) -> tuple[str, bool]: - """Calls the LLM with function calling to get a reasoning plan. + def _call_with_function( + self, prompt: str, plan_type: Literal["create_plan", "refine_plan"] + ) -> tuple[str, bool]: + """Calls the LLM with function calling to get a plan. Args: prompt: The prompt to send to the LLM. - prompt_type: The type of prompt (initial_plan or refine_plan). + plan_type: The type of plan being created. Returns: A tuple containing the plan and whether the agent is ready. """ - self.logger.debug(f"Using function calling for {prompt_type} reasoning") + self.logger.debug(f"Using function calling for {plan_type} planning") try: - system_prompt = self.agent.i18n.retrieve("reasoning", prompt_type).format( - role=self.agent.role, - goal=self.agent.goal, - backstory=self.__get_agent_backstory(), - ) + system_prompt = self._get_system_prompt() # Prepare a simple callable that just returns the tool arguments as JSON def _create_reasoning_plan(plan: str, ready: bool = True) -> str: - """Return the reasoning plan result in JSON string form.""" + """Return the planning result in JSON string form.""" return json.dumps({"plan": plan, "ready": ready}) response = self.llm.call( @@ -256,8 +323,6 @@ class AgentReasoning: from_agent=self.agent, ) - self.logger.debug(f"Function calling response: {response[:100]}...") - try: result = json.loads(response) if "plan" in result and "ready" in result: @@ -277,13 +342,7 @@ class AgentReasoning: ) try: - system_prompt = self.agent.i18n.retrieve( - "reasoning", prompt_type - ).format( - role=self.agent.role, - goal=self.agent.goal, - backstory=self.__get_agent_backstory(), - ) + system_prompt = self._get_system_prompt() fallback_response = self.llm.call( [ @@ -306,69 +365,154 @@ class AgentReasoning: True, ) # Default to ready to avoid getting stuck - def __get_agent_backstory(self) -> str: - """ - Safely gets the agent's backstory, providing a default if not available. + def _call_llm_with_prompt( + self, + prompt: str, + plan_type: Literal["create_plan", "refine_plan"], + ) -> str: + """Calls the LLM with the planning prompt. + + Args: + prompt: The prompt to send to the LLM. + plan_type: The type of plan being created. Returns: - str: The agent's backstory or a default value. + The LLM response. + """ + system_prompt = self._get_system_prompt() + + response = self.llm.call( + [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt}, + ], + from_task=self.task, + from_agent=self.agent, + ) + return str(response) + + def _get_system_prompt(self) -> str: + """Get the system prompt for planning. + + Returns: + The system prompt, either custom or from i18n. + """ + if self.config.system_prompt is not None: + return self.config.system_prompt + + # Try new "planning" section first, fall back to "reasoning" for compatibility + try: + return self.agent.i18n.retrieve("planning", "system_prompt") + except (KeyError, AttributeError): + # Fallback to reasoning section for backward compatibility + return self.agent.i18n.retrieve("reasoning", "initial_plan").format( + role=self.agent.role, + goal=self.agent.goal, + backstory=self._get_agent_backstory(), + ) + + def _get_agent_backstory(self) -> str: + """Safely gets the agent's backstory, providing a default if not available. + + Returns: + The agent's backstory or a default value. """ return getattr(self.agent, "backstory", "No backstory provided") - def __create_reasoning_prompt(self) -> str: - """ - Creates a prompt for the agent to reason about the task. + def _create_planning_prompt(self) -> str: + """Creates a prompt for the agent to plan the task. Returns: - str: The reasoning prompt. + The planning prompt. """ - available_tools = self.__format_available_tools() + available_tools = self._format_available_tools() - return self.agent.i18n.retrieve("reasoning", "create_plan_prompt").format( - role=self.agent.role, - goal=self.agent.goal, - backstory=self.__get_agent_backstory(), - description=self.task.description, - expected_output=self.task.expected_output, - tools=available_tools, - ) + # Use custom prompt if provided + if self.config.plan_prompt is not None: + return self.config.plan_prompt.format( + role=self.agent.role, + goal=self.agent.goal, + backstory=self._get_agent_backstory(), + description=self.description, + expected_output=self.expected_output, + tools=available_tools, + max_steps=self.config.max_steps, + ) - def __format_available_tools(self) -> str: - """ - Formats the available tools for inclusion in the prompt. + # Try new "planning" section first + try: + return self.agent.i18n.retrieve("planning", "create_plan_prompt").format( + description=self.description, + expected_output=self.expected_output, + tools=available_tools, + max_steps=self.config.max_steps, + ) + except (KeyError, AttributeError): + # Fallback to reasoning section for backward compatibility + return self.agent.i18n.retrieve("reasoning", "create_plan_prompt").format( + role=self.agent.role, + goal=self.agent.goal, + backstory=self._get_agent_backstory(), + description=self.description, + expected_output=self.expected_output, + tools=available_tools, + ) + + def _format_available_tools(self) -> str: + """Formats the available tools for inclusion in the prompt. Returns: - str: Comma-separated list of tool names. + Comma-separated list of tool names. """ try: - return ", ".join( - [sanitize_tool_name(tool.name) for tool in (self.task.tools or [])] - ) + # Try task tools first, then agent tools + tools = [] + if self.task: + tools = self.task.tools or [] + if not tools: + tools = getattr(self.agent, "tools", []) or [] + if not tools: + return "No tools available" + return ", ".join([sanitize_tool_name(tool.name) for tool in tools]) except (AttributeError, TypeError): return "No tools available" - def __create_refine_prompt(self, current_plan: str) -> str: - """ - Creates a prompt for the agent to refine its reasoning plan. + def _create_refine_prompt(self, current_plan: str) -> str: + """Creates a prompt for the agent to refine its plan. Args: - current_plan: The current reasoning plan. + current_plan: The current plan. Returns: - str: The refine prompt. + The refine prompt. """ - return self.agent.i18n.retrieve("reasoning", "refine_plan_prompt").format( - role=self.agent.role, - goal=self.agent.goal, - backstory=self.__get_agent_backstory(), - current_plan=current_plan, - ) + # Use custom prompt if provided + if self.config.refine_prompt is not None: + return self.config.refine_prompt.format( + role=self.agent.role, + goal=self.agent.goal, + backstory=self._get_agent_backstory(), + current_plan=current_plan, + max_steps=self.config.max_steps, + ) + + # Try new "planning" section first + try: + return self.agent.i18n.retrieve("planning", "refine_plan_prompt").format( + current_plan=current_plan, + ) + except (KeyError, AttributeError): + # Fallback to reasoning section for backward compatibility + return self.agent.i18n.retrieve("reasoning", "refine_plan_prompt").format( + role=self.agent.role, + goal=self.agent.goal, + backstory=self._get_agent_backstory(), + current_plan=current_plan, + ) @staticmethod - def __parse_reasoning_response(response: str) -> tuple[str, bool]: - """ - Parses the reasoning response to extract the plan and whether - the agent is ready to execute the task. + def _parse_planning_response(response: str) -> tuple[str, bool]: + """Parses the planning response to extract the plan and readiness. Args: response: The LLM response. @@ -380,25 +524,13 @@ class AgentReasoning: return "No plan was generated.", False plan = response - ready = False - - if "READY: I am ready to execute the task." in response: - ready = True + ready = "READY: I am ready to execute the task." in response return plan, ready - def _handle_agent_reasoning(self) -> AgentReasoningOutput: - """ - Deprecated method for backward compatibility. - Use handle_agent_reasoning() instead. - Returns: - AgentReasoningOutput: The output of the agent reasoning process. - """ - self.logger.warning( - "The _handle_agent_reasoning method is deprecated. Use handle_agent_reasoning instead." - ) - return self.handle_agent_reasoning() +# Alias for backward compatibility +AgentPlanning = AgentReasoning def _call_llm_with_reasoning_prompt( @@ -409,7 +541,9 @@ def _call_llm_with_reasoning_prompt( backstory: str, plan_type: Literal["initial_plan", "refine_plan"], ) -> str: - """Calls the LLM with the reasoning prompt. + """Deprecated: Calls the LLM with the reasoning prompt. + + This function is kept for backward compatibility. Args: llm: The language model to use. @@ -417,7 +551,7 @@ def _call_llm_with_reasoning_prompt( task: The task for which the agent is reasoning. reasoning_agent: The agent performing the reasoning. backstory: The agent's backstory. - plan_type: The type of plan being created ("initial_plan" or "refine_plan"). + plan_type: The type of plan being created. Returns: The LLM response. diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index 32130f900..3e922edc9 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -1454,7 +1454,7 @@ def test_agent_execute_task_with_tool(): ) result = agent.execute_task(task) - assert "you should always think about what to do" in result + assert "test query" in result @pytest.mark.vcr() @@ -1473,9 +1473,9 @@ def test_agent_execute_task_with_custom_llm(): ) result = agent.execute_task(task) - assert "In circuits they thrive" in result - assert "Artificial minds awake" in result - assert "Future's coded drive" in result + assert "Artificial minds" in result + assert "Code and circuits" in result + assert "Future undefined" in result @pytest.mark.vcr() diff --git a/lib/crewai/tests/agents/test_agent_executor.py b/lib/crewai/tests/agents/test_agent_executor.py index 8560d9321..f5ecf4de9 100644 --- a/lib/crewai/tests/agents/test_agent_executor.py +++ b/lib/crewai/tests/agents/test_agent_executor.py @@ -25,6 +25,18 @@ class TestAgentReActState: assert state.current_answer is None assert state.is_finished is False assert state.ask_for_human_input is False + # Planning state fields + assert state.plan is None + assert state.plan_ready is False + + def test_state_with_plan(self): + """Test AgentReActState initialization with planning fields.""" + state = AgentReActState( + plan="Step 1: Do X\nStep 2: Do Y", + plan_ready=True, + ) + assert state.plan == "Step 1: Do X\nStep 2: Do Y" + assert state.plan_ready is True def test_state_with_values(self): """Test AgentReActState initialization with values.""" @@ -477,3 +489,249 @@ class TestFlowInvoke: assert result == {"output": "Done"} assert len(executor.state.messages) >= 2 + + +class TestAgentExecutorPlanning: + """Test planning functionality in AgentExecutor with real agent kickoff.""" + + @pytest.mark.vcr() + def test_agent_kickoff_with_planning_stores_plan_in_state(self): + """Test that Agent.kickoff() with planning enabled stores plan in executor state.""" + from crewai import Agent, PlanningConfig + from crewai.llm import LLM + + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Math Assistant", + goal="Help solve simple math problems", + backstory="A helpful assistant that solves math problems step by step", + llm=llm, + planning_config=PlanningConfig(max_attempts=1), + verbose=False, + ) + + # Execute kickoff with a simple task + result = agent.kickoff("What is 2 + 2?") + + # Verify result + assert result is not None + assert "4" in str(result) + + @pytest.mark.vcr() + def test_agent_kickoff_without_planning_skips_plan_generation(self): + """Test that Agent.kickoff() without planning skips planning phase.""" + from crewai import Agent + from crewai.llm import LLM + + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Math Assistant", + goal="Help solve simple math problems", + backstory="A helpful assistant", + llm=llm, + # No planning_config = no planning + verbose=False, + ) + + # Execute kickoff + result = agent.kickoff("What is 3 + 3?") + + # Verify we get a result + assert result is not None + assert "6" in str(result) + + @pytest.mark.vcr() + def test_planning_disabled_skips_planning(self): + """Test that planning=False skips planning.""" + from crewai import Agent + from crewai.llm import LLM + + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Math Assistant", + goal="Help solve simple math problems", + backstory="A helpful assistant", + llm=llm, + planning=False, # Explicitly disable planning + verbose=False, + ) + + result = agent.kickoff("What is 5 + 5?") + + # Should still complete successfully + assert result is not None + assert "10" in str(result) + + def test_backward_compat_reasoning_true_enables_planning(self): + """Test that reasoning=True (deprecated) still enables planning.""" + import warnings + from crewai import Agent + from crewai.llm import LLM + + llm = LLM("gpt-4o-mini") + + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + agent = Agent( + role="Test Agent", + goal="Complete tasks", + backstory="A helpful agent", + llm=llm, + reasoning=True, # Deprecated but should still work + verbose=False, + ) + + # Should have planning_config created from reasoning=True + assert agent.planning_config is not None + assert agent.planning_enabled is True + + @pytest.mark.vcr() + def test_executor_state_contains_plan_after_planning(self): + """Test that executor state contains plan after planning phase.""" + from crewai import Agent, PlanningConfig + from crewai.llm import LLM + from crewai.experimental.agent_executor import AgentExecutor + + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Math Assistant", + goal="Help solve simple math problems", + backstory="A helpful assistant that solves math problems step by step", + llm=llm, + planning_config=PlanningConfig(max_attempts=1), + verbose=False, + ) + + # Track executor for inspection + executor_ref = [None] + original_invoke = AgentExecutor.invoke + + def capture_executor(self, inputs): + executor_ref[0] = self + return original_invoke(self, inputs) + + with patch.object(AgentExecutor, "invoke", capture_executor): + result = agent.kickoff("What is 7 + 7?") + + # Verify result + assert result is not None + + # If we captured an executor, check its state + if executor_ref[0] is not None: + # After planning, state should have plan info + assert hasattr(executor_ref[0].state, "plan") + assert hasattr(executor_ref[0].state, "plan_ready") + + @pytest.mark.vcr() + def test_planning_creates_minimal_steps_for_multi_step_task(self): + """Test that planning creates only necessary steps for a multi-step task. + + This task requires exactly 3 dependent steps: + 1. Identify the first 3 prime numbers (2, 3, 5) + 2. Sum them (2 + 3 + 5 = 10) + 3. Multiply by 2 (10 * 2 = 20) + + The plan should reflect these dependencies without unnecessary padding. + """ + from crewai import Agent, PlanningConfig + from crewai.llm import LLM + from crewai.experimental.agent_executor import AgentExecutor + + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Math Tutor", + goal="Solve multi-step math problems accurately", + backstory="An expert math tutor who breaks down problems step by step", + llm=llm, + planning_config=PlanningConfig(max_attempts=1, max_steps=10), + verbose=False, + ) + + # Track the plan that gets generated + captured_plan = [None] + original_invoke = AgentExecutor.invoke + + def capture_plan(self, inputs): + result = original_invoke(self, inputs) + captured_plan[0] = self.state.plan + return result + + with patch.object(AgentExecutor, "invoke", capture_plan): + result = agent.kickoff( + "Calculate the sum of the first 3 prime numbers, then multiply that result by 2. " + "Show your work for each step." + ) + + # Verify result contains the correct answer (20) + assert result is not None + assert "20" in str(result) + + # Verify a plan was generated + assert captured_plan[0] is not None + + # The plan should be concise - this task needs ~3 steps, not 10+ + plan_text = captured_plan[0] + # Count steps by looking for numbered items or bullet points + import re + + step_pattern = r"^\s*\d+[\.\):]|\n\s*-\s+" + steps = re.findall(step_pattern, plan_text, re.MULTILINE) + # Plan should have roughly 3-5 steps, not fill up to max_steps + assert len(steps) <= 6, f"Plan has too many steps ({len(steps)}): {plan_text}" + + @pytest.mark.vcr() + def test_planning_handles_sequential_dependency_task(self): + """Test planning for a task where step N depends on step N-1. + + Task: Convert 100 Celsius to Fahrenheit, then round to nearest 10. + Step 1: Apply formula (C * 9/5 + 32) = 212 + Step 2: Round 212 to nearest 10 = 210 + + This tests that the planner recognizes sequential dependencies. + """ + from crewai import Agent, PlanningConfig + from crewai.llm import LLM + from crewai.experimental.agent_executor import AgentExecutor + + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Unit Converter", + goal="Accurately convert between units and apply transformations", + backstory="A precise unit conversion specialist", + llm=llm, + planning_config=PlanningConfig(max_attempts=1, max_steps=10), + verbose=False, + ) + + captured_plan = [None] + original_invoke = AgentExecutor.invoke + + def capture_plan(self, inputs): + result = original_invoke(self, inputs) + captured_plan[0] = self.state.plan + return result + + with patch.object(AgentExecutor, "invoke", capture_plan): + result = agent.kickoff( + "Convert 100 degrees Celsius to Fahrenheit, then round the result to the nearest 10." + ) + + assert result is not None + # 100C = 212F, rounded to nearest 10 = 210 + assert "210" in str(result) or "212" in str(result) + + # Plan should exist and be minimal (2-3 steps for this task) + assert captured_plan[0] is not None + plan_text = captured_plan[0] + + import re + + step_pattern = r"^\s*\d+[\.\):]|\n\s*-\s+" + steps = re.findall(step_pattern, plan_text, re.MULTILINE) + assert len(steps) <= 5, f"Plan should be minimal ({len(steps)} steps): {plan_text}" diff --git a/lib/crewai/tests/agents/test_agent_reasoning.py b/lib/crewai/tests/agents/test_agent_reasoning.py index a12d5af9a..ed4c6a60b 100644 --- a/lib/crewai/tests/agents/test_agent_reasoning.py +++ b/lib/crewai/tests/agents/test_agent_reasoning.py @@ -1,240 +1,345 @@ -"""Tests for reasoning in agents.""" +"""Tests for planning/reasoning in agents.""" -import json +import warnings import pytest -from crewai import Agent, Task +from crewai import Agent, PlanningConfig, Task from crewai.llm import LLM -@pytest.fixture -def mock_llm_responses(): - """Fixture for mock LLM responses.""" - return { - "ready": "I'll solve this simple math problem.\n\nREADY: I am ready to execute the task.\n\n", - "not_ready": "I need to think about derivatives.\n\nNOT READY: I need to refine my plan because I'm not sure about the derivative rules.", - "ready_after_refine": "I'll use the power rule for derivatives where d/dx(x^n) = n*x^(n-1).\n\nREADY: I am ready to execute the task.", - "execution": "4", - } +# ============================================================================= +# Tests for PlanningConfig configuration (no LLM calls needed) +# ============================================================================= -def test_agent_with_reasoning(mock_llm_responses): - """Test agent with reasoning.""" - llm = LLM("gpt-3.5-turbo") +def test_planning_config_default_values(): + """Test PlanningConfig default values.""" + config = PlanningConfig() + + assert config.max_attempts is None + assert config.max_steps == 20 + assert config.system_prompt is None + assert config.plan_prompt is None + assert config.refine_prompt is None + assert config.llm is None + + +def test_planning_config_custom_values(): + """Test PlanningConfig with custom values.""" + config = PlanningConfig( + max_attempts=5, + max_steps=15, + system_prompt="Custom system", + plan_prompt="Custom plan: {description}", + refine_prompt="Custom refine: {current_plan}", + llm="gpt-4", + ) + + assert config.max_attempts == 5 + assert config.max_steps == 15 + assert config.system_prompt == "Custom system" + assert config.plan_prompt == "Custom plan: {description}" + assert config.refine_prompt == "Custom refine: {current_plan}" + assert config.llm == "gpt-4" + + +def test_agent_with_planning_config_custom_prompts(): + """Test agent with PlanningConfig using custom prompts.""" + llm = LLM("gpt-4o-mini") + + custom_system_prompt = "You are a specialized planner." + custom_plan_prompt = "Plan this task: {description}" + + agent = Agent( + role="Test Agent", + goal="To test custom prompts", + backstory="I am a test agent.", + llm=llm, + planning_config=PlanningConfig( + system_prompt=custom_system_prompt, + plan_prompt=custom_plan_prompt, + max_steps=10, + ), + verbose=False, + ) + + # Just test that the agent is created properly + assert agent.planning_config is not None + assert agent.planning_config.system_prompt == custom_system_prompt + assert agent.planning_config.plan_prompt == custom_plan_prompt + assert agent.planning_config.max_steps == 10 + + +def test_agent_with_planning_config_disabled(): + """Test agent with PlanningConfig disabled.""" + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Test Agent", + goal="To test disabled planning", + backstory="I am a test agent.", + llm=llm, + planning=False, + verbose=False, + ) + + # Planning should be disabled + assert agent.planning_enabled is False + + +def test_planning_enabled_property(): + """Test the planning_enabled property on Agent.""" + llm = LLM("gpt-4o-mini") + + # With planning_config enabled + agent_with_planning = Agent( + role="Test Agent", + goal="Test", + backstory="Test", + llm=llm, + planning=True, + ) + assert agent_with_planning.planning_enabled is True + + # With planning_config disabled + agent_disabled = Agent( + role="Test Agent", + goal="Test", + backstory="Test", + llm=llm, + planning=False, + ) + assert agent_disabled.planning_enabled is False + + # Without planning_config + agent_no_planning = Agent( + role="Test Agent", + goal="Test", + backstory="Test", + llm=llm, + ) + assert agent_no_planning.planning_enabled is False + + +# ============================================================================= +# Tests for backward compatibility with reasoning=True (no LLM calls) +# ============================================================================= + + +def test_agent_with_reasoning_backward_compat(): + """Test agent with reasoning=True (backward compatibility).""" + llm = LLM("gpt-4o-mini") + + # This should emit a deprecation warning + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + agent = Agent( + role="Test Agent", + goal="To test the reasoning feature", + backstory="I am a test agent created to verify the reasoning feature works correctly.", + llm=llm, + reasoning=True, + verbose=False, + ) + + # Should have created a PlanningConfig internally + assert agent.planning_config is not None + assert agent.planning_enabled is True + + +def test_agent_with_reasoning_and_max_attempts_backward_compat(): + """Test agent with reasoning=True and max_reasoning_attempts (backward compatibility).""" + llm = LLM("gpt-4o-mini") agent = Agent( role="Test Agent", goal="To test the reasoning feature", - backstory="I am a test agent created to verify the reasoning feature works correctly.", + backstory="I am a test agent.", llm=llm, reasoning=True, - verbose=True, + max_reasoning_attempts=5, + verbose=False, ) - task = Task( - description="Simple math task: What's 2+2?", - expected_output="The answer should be a number.", - agent=agent, - ) - - agent.llm.call = lambda messages, *args, **kwargs: ( - mock_llm_responses["ready"] - if any("create a detailed plan" in msg.get("content", "") for msg in messages) - else mock_llm_responses["execution"] - ) - - result = agent.execute_task(task) - - assert result == mock_llm_responses["execution"] - assert "Reasoning Plan:" in task.description + # Should have created a PlanningConfig with max_attempts + assert agent.planning_config is not None + assert agent.planning_config.max_attempts == 5 -def test_agent_with_reasoning_not_ready_initially(mock_llm_responses): - """Test agent with reasoning that requires refinement.""" - llm = LLM("gpt-3.5-turbo") +# ============================================================================= +# Tests for Agent.kickoff() with planning (uses AgentExecutor) +# ============================================================================= + + +@pytest.mark.vcr() +def test_agent_kickoff_with_planning(): + """Test Agent.kickoff() with planning enabled generates a plan.""" + llm = LLM("gpt-4o-mini") agent = Agent( - role="Test Agent", - goal="To test the reasoning feature", - backstory="I am a test agent created to verify the reasoning feature works correctly.", + role="Math Assistant", + goal="Help solve math problems step by step", + backstory="A helpful math tutor", llm=llm, - reasoning=True, - max_reasoning_attempts=2, - verbose=True, + planning_config=PlanningConfig(max_attempts=1), + verbose=False, ) - task = Task( - description="Complex math task: What's the derivative of x²?", - expected_output="The answer should be a mathematical expression.", - agent=agent, - ) + result = agent.kickoff("What is 15 + 27?") - call_count = [0] - - def mock_llm_call(messages, *args, **kwargs): - if any( - "create a detailed plan" in msg.get("content", "") for msg in messages - ) or any("refine your plan" in msg.get("content", "") for msg in messages): - call_count[0] += 1 - if call_count[0] == 1: - return mock_llm_responses["not_ready"] - return mock_llm_responses["ready_after_refine"] - return "2x" - - agent.llm.call = mock_llm_call - - result = agent.execute_task(task) - - assert result == "2x" - assert call_count[0] == 2 # Should have made 2 reasoning calls - assert "Reasoning Plan:" in task.description + assert result is not None + assert "42" in str(result) -def test_agent_with_reasoning_max_attempts_reached(): - """Test agent with reasoning that reaches max attempts without being ready.""" - llm = LLM("gpt-3.5-turbo") +@pytest.mark.vcr() +def test_agent_kickoff_without_planning(): + """Test Agent.kickoff() without planning skips plan generation.""" + llm = LLM("gpt-4o-mini") agent = Agent( - role="Test Agent", - goal="To test the reasoning feature", - backstory="I am a test agent created to verify the reasoning feature works correctly.", + role="Math Assistant", + goal="Help solve math problems", + backstory="A helpful assistant", llm=llm, - reasoning=True, - max_reasoning_attempts=2, - verbose=True, + # No planning_config = no planning + verbose=False, ) - task = Task( - description="Complex math task: Solve the Riemann hypothesis.", - expected_output="A proof or disproof of the hypothesis.", - agent=agent, - ) + result = agent.kickoff("What is 8 * 7?") - call_count = [0] - - def mock_llm_call(messages, *args, **kwargs): - if any( - "create a detailed plan" in msg.get("content", "") for msg in messages - ) or any("refine your plan" in msg.get("content", "") for msg in messages): - call_count[0] += 1 - return f"Attempt {call_count[0]}: I need more time to think.\n\nNOT READY: I need to refine my plan further." - return "This is an unsolved problem in mathematics." - - agent.llm.call = mock_llm_call - - result = agent.execute_task(task) - - assert result == "This is an unsolved problem in mathematics." - assert ( - call_count[0] == 2 - ) # Should have made exactly 2 reasoning calls (max_attempts) - assert "Reasoning Plan:" in task.description + assert result is not None + assert "56" in str(result) -def test_agent_reasoning_error_handling(): - """Test error handling during the reasoning process.""" - llm = LLM("gpt-3.5-turbo") +@pytest.mark.vcr() +def test_agent_kickoff_with_planning_disabled(): + """Test Agent.kickoff() with planning explicitly disabled via planning=False.""" + llm = LLM("gpt-4o-mini") agent = Agent( - role="Test Agent", - goal="To test the reasoning feature", - backstory="I am a test agent created to verify the reasoning feature works correctly.", + role="Math Assistant", + goal="Help solve math problems", + backstory="A helpful assistant", llm=llm, - reasoning=True, + planning=False, # Explicitly disable planning + verbose=False, ) - task = Task( - description="Task that will cause an error", - expected_output="Output that will never be generated", - agent=agent, - ) + result = agent.kickoff("What is 100 / 4?") - call_count = [0] - - def mock_llm_call_error(*args, **kwargs): - call_count[0] += 1 - if call_count[0] <= 2: # First calls are for reasoning - raise Exception("LLM error during reasoning") - return "Fallback execution result" # Return a value for task execution - - agent.llm.call = mock_llm_call_error - - result = agent.execute_task(task) - - assert result == "Fallback execution result" - assert call_count[0] > 2 # Ensure we called the mock multiple times + assert result is not None + assert "25" in str(result) -@pytest.mark.skip(reason="Test requires updates for native tool calling changes") -def test_agent_with_function_calling(): - """Test agent with reasoning using function calling.""" - llm = LLM("gpt-3.5-turbo") +@pytest.mark.vcr() +def test_agent_kickoff_multi_step_task_with_planning(): + """Test Agent.kickoff() with a multi-step task that benefits from planning.""" + llm = LLM("gpt-4o-mini") agent = Agent( - role="Test Agent", - goal="To test the reasoning feature", - backstory="I am a test agent created to verify the reasoning feature works correctly.", + role="Math Tutor", + goal="Solve multi-step math problems", + backstory="An expert tutor who explains step by step", llm=llm, - reasoning=True, - verbose=True, + planning_config=PlanningConfig(max_attempts=1, max_steps=5), + verbose=False, ) - task = Task( - description="Simple math task: What's 2+2?", - expected_output="The answer should be a number.", - agent=agent, + # Task requires: find primes, sum them, then double + result = agent.kickoff( + "Find the first 3 prime numbers, add them together, then multiply by 2." ) - agent.llm.supports_function_calling = lambda: True - - def mock_function_call(messages, *args, **kwargs): - if "tools" in kwargs: - return json.dumps( - {"plan": "I'll solve this simple math problem: 2+2=4.", "ready": True} - ) - return "4" - - agent.llm.call = mock_function_call - - result = agent.execute_task(task) - - assert result == "4" - assert "Reasoning Plan:" in task.description - assert "I'll solve this simple math problem: 2+2=4." in task.description + assert result is not None + # First 3 primes: 2, 3, 5 -> sum = 10 -> doubled = 20 + assert "20" in str(result) -@pytest.mark.skip(reason="Test requires updates for native tool calling changes") -def test_agent_with_function_calling_fallback(): - """Test agent with reasoning using function calling that falls back to text parsing.""" - llm = LLM("gpt-3.5-turbo") +# ============================================================================= +# Tests for Agent.execute_task() with planning (uses CrewAgentExecutor) +# These test the legacy path via handle_reasoning() +# ============================================================================= + + +@pytest.mark.vcr() +def test_agent_execute_task_with_planning(): + """Test Agent.execute_task() with planning via CrewAgentExecutor.""" + llm = LLM("gpt-4o-mini") agent = Agent( - role="Test Agent", - goal="To test the reasoning feature", - backstory="I am a test agent created to verify the reasoning feature works correctly.", + role="Math Assistant", + goal="Help solve math problems", + backstory="A helpful math tutor", llm=llm, - reasoning=True, - verbose=True, + planning_config=PlanningConfig(max_attempts=1), + verbose=False, ) task = Task( - description="Simple math task: What's 2+2?", - expected_output="The answer should be a number.", + description="What is 9 + 11?", + expected_output="A number", agent=agent, ) - agent.llm.supports_function_calling = lambda: True + result = agent.execute_task(task) - def mock_function_call(messages, *args, **kwargs): - if "tools" in kwargs: - return "Invalid JSON that will trigger fallback. READY: I am ready to execute the task." - return "4" + assert result is not None + assert "20" in str(result) + # Planning should be appended to task description + assert "Planning:" in task.description - agent.llm.call = mock_function_call + +@pytest.mark.vcr() +def test_agent_execute_task_without_planning(): + """Test Agent.execute_task() without planning.""" + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Math Assistant", + goal="Help solve math problems", + backstory="A helpful assistant", + llm=llm, + verbose=False, + ) + + task = Task( + description="What is 12 * 3?", + expected_output="A number", + agent=agent, + ) result = agent.execute_task(task) - assert result == "4" - assert "Reasoning Plan:" in task.description - assert "Invalid JSON that will trigger fallback" in task.description + assert result is not None + assert "36" in str(result) + # No planning should be added + assert "Planning:" not in task.description + + +@pytest.mark.vcr() +def test_agent_execute_task_with_planning_refine(): + """Test Agent.execute_task() with planning that requires refinement.""" + llm = LLM("gpt-4o-mini") + + agent = Agent( + role="Math Tutor", + goal="Solve complex math problems step by step", + backstory="An expert tutor", + llm=llm, + planning_config=PlanningConfig(max_attempts=2), + verbose=False, + ) + + task = Task( + description="Calculate the area of a circle with radius 5 (use pi = 3.14)", + expected_output="The area as a number", + agent=agent, + ) + + result = agent.execute_task(task) + + assert result is not None + # Area = pi * r^2 = 3.14 * 25 = 78.5 + assert "78" in str(result) or "79" in str(result) + assert "Planning:" in task.description diff --git a/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_agent_kickoff_with_planning_stores_plan_in_state.yaml b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_agent_kickoff_with_planning_stores_plan_in_state.yaml new file mode 100644 index 000000000..97ca50aa2 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_agent_kickoff_with_planning_stores_plan_in_state.yaml @@ -0,0 +1,234 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nWhat is 2 + 2?\n\n## + Expected Output\nComplete the task successfully\n\n## Available Tools\nNo tools + available\n\n## Instructions\nCreate ONLY the essential steps needed to complete + this task. Use the MINIMUM number of steps required - do NOT pad your plan with + unnecessary steps. Most tasks need only 2-5 steps.\n\nFor each step:\n- State + the specific action to take\n- Specify which tool to use (if any)\n\nDo NOT + include:\n- Setup or preparation steps that are obvious\n- Verification steps + unless critical\n- Documentation or cleanup steps unless explicitly required\n- + Generic steps like \"review results\" or \"finalize output\"\n\nAfter your plan, + state:\n- \"READY: I am ready to execute the task.\" if the plan is complete\n- + \"NOT READY: I need to refine my plan because [reason].\" if you need more thinking"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"The + detailed reasoning plan for the task."},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1541' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTTAh68P65LybtqkwNI3p2HXcRv\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078147,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"## Execution Plan\\n\\n1. **Action:** + Perform the addition operation. \\n **Tool:** None (manually calculate).\\n\\n2. + **Action:** State the result. \\n **Tool:** None (manually output).\\n\\nREADY: + I am ready to execute the task.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 281,\n \"completion_tokens\": + 56,\n \"total_tokens\": 337,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:28 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '1165' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + assistant that solves math problems step by step\nYour personal goal is: Help + solve simple math problems"},{"role":"user","content":"\nCurrent Task: What + is 2 + 2?\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '299' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTVB9mdtq1YZrUVf1aSb6dVVQ8G\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078149,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"To solve the problem of 2 + 2, we simply + perform the addition:\\n\\n1. Start with the first number: 2\\n2. Add the + second number: + 2\\n3. Combine the two: 2 + 2 = 4\\n\\nTherefore, the answer + is 4.\",\n \"refusal\": null,\n \"annotations\": []\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 54,\n \"completion_tokens\": 62,\n + \ \"total_tokens\": 116,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:30 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '1300' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_agent_kickoff_without_planning_skips_plan_generation.yaml b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_agent_kickoff_without_planning_skips_plan_generation.yaml new file mode 100644 index 000000000..3ceb8fa34 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_agent_kickoff_without_planning_skips_plan_generation.yaml @@ -0,0 +1,108 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + assistant\nYour personal goal is: Help solve simple math problems"},{"role":"user","content":"\nCurrent + Task: What is 3 + 3?\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '260' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTTFxQ75llVmJv0ee902FIjXE8p\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078147,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"3 + 3 equals 6.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 47,\n \"completion_tokens\": 8,\n \"total_tokens\": 55,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:27 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '401' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_executor_state_contains_plan_after_planning.yaml b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_executor_state_contains_plan_after_planning.yaml new file mode 100644 index 000000000..003471628 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_executor_state_contains_plan_after_planning.yaml @@ -0,0 +1,230 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nWhat is 7 + 7?\n\n## + Expected Output\nComplete the task successfully\n\n## Available Tools\nNo tools + available\n\n## Instructions\nCreate ONLY the essential steps needed to complete + this task. Use the MINIMUM number of steps required - do NOT pad your plan with + unnecessary steps. Most tasks need only 2-5 steps.\n\nFor each step:\n- State + the specific action to take\n- Specify which tool to use (if any)\n\nDo NOT + include:\n- Setup or preparation steps that are obvious\n- Verification steps + unless critical\n- Documentation or cleanup steps unless explicitly required\n- + Generic steps like \"review results\" or \"finalize output\"\n\nAfter your plan, + state:\n- \"READY: I am ready to execute the task.\" if the plan is complete\n- + \"NOT READY: I need to refine my plan because [reason].\" if you need more thinking"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"The + detailed reasoning plan for the task."},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1541' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTdqlxwWowSdLncBERFrCgxTvVj\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078157,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"## Execution Plan\\n\\n1. Calculate + the sum of 7 and 7.\\n \\nREADY: I am ready to execute the task.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 281,\n \"completion_tokens\": 28,\n \"total_tokens\": 309,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:38 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '709' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + assistant that solves math problems step by step\nYour personal goal is: Help + solve simple math problems"},{"role":"user","content":"\nCurrent Task: What + is 7 + 7?\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '299' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTeB6Miecallw9SjSfLAXPjX2XD\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078158,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"To find the sum of 7 and 7, you simply + add the two numbers together:\\n\\n7 + 7 = 14\\n\\nSo, the answer is 14.\",\n + \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 54,\n \"completion_tokens\": 35,\n \"total_tokens\": 89,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:38 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '733' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_config_disabled_skips_planning.yaml b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_config_disabled_skips_planning.yaml new file mode 100644 index 000000000..ccb9aee30 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_config_disabled_skips_planning.yaml @@ -0,0 +1,108 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + assistant\nYour personal goal is: Help solve simple math problems"},{"role":"user","content":"\nCurrent + Task: What is 5 + 5?\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '260' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTf8T2iADffpPCJBZhntLlaoaSy\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078159,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"5 + 5 equals 10.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 47,\n \"completion_tokens\": 8,\n \"total_tokens\": 55,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:40 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '515' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_creates_minimal_steps_for_multi_step_task.yaml b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_creates_minimal_steps_for_multi_step_task.yaml new file mode 100644 index 000000000..17307bdcc --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_creates_minimal_steps_for_multi_step_task.yaml @@ -0,0 +1,247 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nCalculate the sum + of the first 3 prime numbers, then multiply that result by 2. Show your work + for each step.\n\n## Expected Output\nComplete the task successfully\n\n## Available + Tools\nNo tools available\n\n## Instructions\nCreate ONLY the essential steps + needed to complete this task. Use the MINIMUM number of steps required - do + NOT pad your plan with unnecessary steps. Most tasks need only 2-5 steps.\n\nFor + each step:\n- State the specific action to take\n- Specify which tool to use + (if any)\n\nDo NOT include:\n- Setup or preparation steps that are obvious\n- + Verification steps unless critical\n- Documentation or cleanup steps unless + explicitly required\n- Generic steps like \"review results\" or \"finalize output\"\n\nAfter + your plan, state:\n- \"READY: I am ready to execute the task.\" if the plan + is complete\n- \"NOT READY: I need to refine my plan because [reason].\" if + you need more thinking"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"The + detailed reasoning plan for the task."},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1636' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTWa7FxCHkHwHF25AYXXeJDBOuY\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078150,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"## Execution Plan\\n\\n1. Identify + the first 3 prime numbers: 2, 3, and 5.\\n2. Calculate the sum: \\\\(2 + 3 + + 5 = 10\\\\).\\n3. Multiply the sum by 2: \\\\(10 \\\\times 2 = 20\\\\).\\n\\nREADY: + I am ready to execute the task.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 299,\n \"completion_tokens\": + 74,\n \"total_tokens\": 373,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:32 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '1716' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Math Tutor. An expert + math tutor who breaks down problems step by step\nYour personal goal is: Solve + multi-step math problems accurately"},{"role":"user","content":"\nCurrent Task: + Calculate the sum of the first 3 prime numbers, then multiply that result by + 2. Show your work for each step.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '400' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTYJgCZf2oY7wiPMZmN4QEQhHb5\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078152,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"To solve the problem, let's break it + down into two main steps: \\n\\n1. Calculate the sum of the first 3 prime + numbers.\\n2. Multiply the result of that sum by 2.\\n\\n### Step 1: Identify + the first 3 prime numbers\\nPrime numbers are natural numbers greater than + 1 that have no positive divisors other than 1 and themselves. \\n\\nThe first + three prime numbers are:\\n- 2\\n- 3\\n- 5\\n\\n### Step 2: Calculate the + sum of the first 3 prime numbers\\nNow, we add these prime numbers together:\\n\\n\\\\[\\n2 + + 3 + 5\\n\\\\]\\n\\nCalculating this step-by-step:\\n- First, add 2 and 3:\\n + \ \\\\[\\n 2 + 3 = 5\\n \\\\]\\n \\n- Next, add this result to 5:\\n \\\\[\\n + \ 5 + 5 = 10\\n \\\\]\\n\\nSo, the sum of the first 3 prime numbers is \\\\(10\\\\).\\n\\n### + Step 3: Multiply the sum by 2\\nNext, we take the sum we calculated and multiply + it by 2:\\n\\n\\\\[\\n10 \\\\times 2\\n\\\\]\\n\\nCalculating this:\\n\\\\[\\n10 + \\\\times 2 = 20\\n\\\\]\\n\\n### Final Answer\\nThus, the final result obtained + after performing all the steps is:\\n\\n\\\\[\\n\\\\boxed{20}\\n\\\\]\",\n + \ \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 74,\n \"completion_tokens\": 288,\n \"total_tokens\": 362,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:37 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '4751' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_disabled_skips_planning.yaml b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_disabled_skips_planning.yaml new file mode 100644 index 000000000..88617c427 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_disabled_skips_planning.yaml @@ -0,0 +1,108 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + assistant\nYour personal goal is: Help solve simple math problems"},{"role":"user","content":"\nCurrent + Task: What is 5 + 5?\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '260' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yXGD5IrieoUDSK5hDmJyA2gJtDc\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078382,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"5 + 5 equals 10.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 47,\n \"completion_tokens\": 8,\n \"total_tokens\": 55,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:26:23 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '363' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_handles_sequential_dependency_task.yaml b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_handles_sequential_dependency_task.yaml new file mode 100644 index 000000000..35714d2cf --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/TestAgentExecutorPlanning.test_planning_handles_sequential_dependency_task.yaml @@ -0,0 +1,242 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nConvert 100 degrees + Celsius to Fahrenheit, then round the result to the nearest 10.\n\n## Expected + Output\nComplete the task successfully\n\n## Available Tools\nNo tools available\n\n## + Instructions\nCreate ONLY the essential steps needed to complete this task. + Use the MINIMUM number of steps required - do NOT pad your plan with unnecessary + steps. Most tasks need only 2-5 steps.\n\nFor each step:\n- State the specific + action to take\n- Specify which tool to use (if any)\n\nDo NOT include:\n- Setup + or preparation steps that are obvious\n- Verification steps unless critical\n- + Documentation or cleanup steps unless explicitly required\n- Generic steps like + \"review results\" or \"finalize output\"\n\nAfter your plan, state:\n- \"READY: + I am ready to execute the task.\" if the plan is complete\n- \"NOT READY: I + need to refine my plan because [reason].\" if you need more thinking"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"The + detailed reasoning plan for the task."},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1610' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTN8fHOefyzzhvdUOHjxdFDR2HW\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078141,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"## Execution Plan\\n\\n1. Convert 100 + degrees Celsius to Fahrenheit using the formula: \\\\( F = C \\\\times \\\\frac{9}{5} + + 32 \\\\).\\n2. Round the Fahrenheit result to the nearest 10.\\n\\nREADY: + I am ready to execute the task.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 291,\n \"completion_tokens\": + 58,\n \"total_tokens\": 349,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:22 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '1089' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Unit Converter. A precise + unit conversion specialist\nYour personal goal is: Accurately convert between + units and apply transformations"},{"role":"user","content":"\nCurrent Task: + Convert 100 degrees Celsius to Fahrenheit, then round the result to the nearest + 10.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '373' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTPQewXDyPdYHI4dHPH7YGHcRge\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078143,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"To convert degrees Celsius to Fahrenheit, + you can use the formula:\\n\\n\\\\[ F = \\\\left( C \\\\times \\\\frac{9}{5} + \\\\right) + 32 \\\\]\\n\\nPlugging in 100 degrees Celsius:\\n\\n\\\\[ F = + \\\\left( 100 \\\\times \\\\frac{9}{5} \\\\right) + 32 \\\\]\\n\\nCalculating + that step-by-step:\\n\\n1. Multiply 100 by 9: \\n \\\\[ 100 \\\\times 9 + = 900 \\\\]\\n\\n2. Divide by 5:\\n \\\\[ 900 \\\\div 5 = 180 \\\\]\\n\\n3. + Add 32:\\n \\\\[ 180 + 32 = 212 \\\\]\\n\\nSo, 100 degrees Celsius is equal + to 212 degrees Fahrenheit.\\n\\nNow, rounding 212 to the nearest 10:\\n\\nThe + nearest multiple of 10 to 212 is 210.\\n\\nTherefore, the final result is + **210 degrees Fahrenheit**.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 63,\n \"completion_tokens\": + 191,\n \"total_tokens\": 254,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:26 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '3736' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_basic.yaml b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_basic.yaml index 74f6ddd8e..9e477af43 100644 --- a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_basic.yaml +++ b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_basic.yaml @@ -1,6 +1,10 @@ interactions: - request: - body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Calculate 2 + 2\n\nThis is the expected criteria for your final answer: The result of the calculation\nyou MUST return the actual complete content as the final answer, not a summary.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}],"model":"gpt-4o-mini"}' + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal"},{"role":"user","content":"\nCurrent Task: Calculate + 2 + 2\n\nThis is the expected criteria for your final answer: The result of + the calculation\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' headers: User-Agent: - X-USER-AGENT-XXX @@ -13,7 +17,7 @@ interactions: connection: - keep-alive content-length: - - '797' + - '396' content-type: - application/json host: @@ -35,13 +39,23 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.10 + - 3.13.3 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-CjDsYJQa2tIYBbNloukSWecpsTvdK\",\n \"object\": \"chat.completion\",\n \"created\": 1764894146,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal Answer: The result of the calculation 2 + 2 is 4.\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 161,\n \"completion_tokens\": 25,\n \"total_tokens\": 186,\n \"prompt_tokens_details\": {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": \"default\",\n \"system_fingerprint\": \"fp_11f3029f6b\"\ - \n}\n" + string: "{\n \"id\": \"chatcmpl-D5DTjYe6n92Rjo4Ox6NiZpAAdBLF0\",\n \"object\": + \"chat.completion\",\n \"created\": 1770135823,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"The result of the calculation 2 + 2 + is 4.\",\n \"refusal\": null,\n \"annotations\": []\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 75,\n \"completion_tokens\": 14,\n + \ \"total_tokens\": 89,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" headers: CF-RAY: - CF-RAY-XXX @@ -50,7 +64,7 @@ interactions: Content-Type: - application/json Date: - - Fri, 05 Dec 2025 00:22:27 GMT + - Tue, 03 Feb 2026 16:23:43 GMT Server: - cloudflare Set-Cookie: @@ -70,13 +84,11 @@ interactions: openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '516' + - '636' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '529' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_context.yaml b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_context.yaml index 77d036e24..01e4cf99f 100644 --- a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_context.yaml +++ b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_context.yaml @@ -1,6 +1,12 @@ interactions: - request: - body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Summarize the given context in one sentence\n\nThis is the expected criteria for your final answer: A one-sentence summary\nyou MUST return the actual complete content as the final answer, not a summary.\n\nThis is the context you''re working with:\nThe quick brown fox jumps over the lazy dog. This sentence contains every letter of the alphabet.\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"}' + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal"},{"role":"user","content":"\nCurrent Task: Summarize + the given context in one sentence\n\nThis is the expected criteria for your + final answer: A one-sentence summary\nyou MUST return the actual complete content + as the final answer, not a summary.\n\nThis is the context you''re working with:\nThe + quick brown fox jumps over the lazy dog. This sentence contains every letter + of the alphabet.\n\nProvide your complete response:"}],"model":"gpt-3.5-turbo"}' headers: User-Agent: - X-USER-AGENT-XXX @@ -13,7 +19,7 @@ interactions: connection: - keep-alive content-length: - - '963' + - '562' content-type: - application/json host: @@ -35,13 +41,23 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.10 + - 3.13.3 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-CjDtsaX0LJ0dzZz02KwKeRGYgazv1\",\n \"object\": \"chat.completion\",\n \"created\": 1764894228,\n \"model\": \"gpt-3.5-turbo-0125\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"I now can give a great answer\\n\\nFinal Answer: The quick brown fox jumps over the lazy dog. This sentence contains every letter of the alphabet.\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 191,\n \"completion_tokens\": 30,\n \"total_tokens\": 221,\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" + string: "{\n \"id\": \"chatcmpl-D5DTn6yIQ7HpIn5j5Bsbag1efzXPa\",\n \"object\": + \"chat.completion\",\n \"created\": 1770135827,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"The quick brown fox jumps over the + lazy dog. This sentence contains every letter of the alphabet.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 105,\n \"completion_tokens\": 19,\n \"total_tokens\": 124,\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: - CF-RAY-XXX @@ -50,7 +66,7 @@ interactions: Content-Type: - application/json Date: - - Fri, 05 Dec 2025 00:23:49 GMT + - Tue, 03 Feb 2026 16:23:48 GMT Server: - cloudflare Set-Cookie: @@ -70,13 +86,11 @@ interactions: openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '506' + - '606' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '559' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_custom_llm.yaml b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_custom_llm.yaml index 27d8337dd..a92cb6b27 100644 --- a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_custom_llm.yaml +++ b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_custom_llm.yaml @@ -1,6 +1,10 @@ interactions: - request: - body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour personal goal is: test goal\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent Task: Write a haiku about AI\n\nThis is the expected criteria for your final answer: A haiku (3 lines, 5-7-5 syllable pattern) about AI\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","max_tokens":50,"temperature":0.7}' + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal"},{"role":"user","content":"\nCurrent Task: Write + a haiku about AI\n\nThis is the expected criteria for your final answer: A haiku + (3 lines, 5-7-5 syllable pattern) about AI\nyou MUST return the actual complete + content as the final answer, not a summary.\n\nProvide your complete response:"}],"model":"gpt-3.5-turbo","max_tokens":50,"temperature":0.7}' headers: User-Agent: - X-USER-AGENT-XXX @@ -13,7 +17,7 @@ interactions: connection: - keep-alive content-length: - - '861' + - '460' content-type: - application/json host: @@ -35,13 +39,23 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.10 + - 3.13.3 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-CjDqr2BmEXQ08QzZKslTZJZ5vV9lo\",\n \"object\": \"chat.completion\",\n \"created\": 1764894041,\n \"model\": \"gpt-3.5-turbo-0125\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"I now can give a great answer\\n\\nFinal Answer: \\nIn circuits they thrive, \\nArtificial minds awake, \\nFuture's coded drive.\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 174,\n \"completion_tokens\": 29,\n \"total_tokens\": 203,\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" + string: "{\n \"id\": \"chatcmpl-D5DTgAqxaC8RmEvikXK0UDaxmVmf9\",\n \"object\": + \"chat.completion\",\n \"created\": 1770135820,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Artificial minds,\\nCode and circuits + intertwine,\\nFuture undefined.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 88,\n \"completion_tokens\": + 13,\n \"total_tokens\": 101,\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: - CF-RAY-XXX @@ -50,7 +64,7 @@ interactions: Content-Type: - application/json Date: - - Fri, 05 Dec 2025 00:20:41 GMT + - Tue, 03 Feb 2026 16:23:40 GMT Server: - cloudflare Set-Cookie: @@ -70,13 +84,11 @@ interactions: openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '434' + - '277' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '456' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_planning.yaml b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_planning.yaml new file mode 100644 index 000000000..e8c4d0519 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_planning.yaml @@ -0,0 +1,231 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nWhat is 9 + 11?\n\n## + Expected Output\nA number\n\n## Available Tools\nNo tools available\n\n## Instructions\nCreate + ONLY the essential steps needed to complete this task. Use the MINIMUM number + of steps required - do NOT pad your plan with unnecessary steps. Most tasks + need only 2-5 steps.\n\nFor each step:\n- State the specific action to take\n- + Specify which tool to use (if any)\n\nDo NOT include:\n- Setup or preparation + steps that are obvious\n- Verification steps unless critical\n- Documentation + or cleanup steps unless explicitly required\n- Generic steps like \"review results\" + or \"finalize output\"\n\nAfter your plan, state:\n- \"READY: I am ready to + execute the task.\" if the plan is complete\n- \"NOT READY: I need to refine + my plan because [reason].\" if you need more thinking"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"The + detailed reasoning plan for the task."},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1520' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yVACNTzZcghQRwt5kFYQ4HAvbgI\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078252,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"## Execution Plan\\n1. Calculate the + sum of 9 and 11.\\n \\nREADY: I am ready to execute the task.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 279,\n \"completion_tokens\": 28,\n \"total_tokens\": 307,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:24:13 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '951' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + math tutor\nYour personal goal is: Help solve math problems"},{"role":"user","content":"\nCurrent + Task: What is 9 + 11?\n\nPlanning:\n## Execution Plan\n1. Calculate the sum + of 9 and 11.\n \nREADY: I am ready to execute the task.\n\nThis is the expected + criteria for your final answer: A number\nyou MUST return the actual complete + content as the final answer, not a summary.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '513' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yVBdTCKSdfcJYlIOX9BbzrObgFI\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078253,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"9 + 11 = 20\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 105,\n \"completion_tokens\": 7,\n \"total_tokens\": 112,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:24:13 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '477' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_planning_refine.yaml b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_planning_refine.yaml new file mode 100644 index 000000000..a9d23f6f7 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_planning_refine.yaml @@ -0,0 +1,243 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nCalculate the area + of a circle with radius 5 (use pi = 3.14)\n\n## Expected Output\nThe area as + a number\n\n## Available Tools\nNo tools available\n\n## Instructions\nCreate + ONLY the essential steps needed to complete this task. Use the MINIMUM number + of steps required - do NOT pad your plan with unnecessary steps. Most tasks + need only 2-5 steps.\n\nFor each step:\n- State the specific action to take\n- + Specify which tool to use (if any)\n\nDo NOT include:\n- Setup or preparation + steps that are obvious\n- Verification steps unless critical\n- Documentation + or cleanup steps unless explicitly required\n- Generic steps like \"review results\" + or \"finalize output\"\n\nAfter your plan, state:\n- \"READY: I am ready to + execute the task.\" if the plan is complete\n- \"NOT READY: I need to refine + my plan because [reason].\" if you need more thinking"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"The + detailed reasoning plan for the task."},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1577' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yVCdA1csIzfoHSQvxkfrA4gDn4z\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078254,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"## Execution Plan\\n1. Multiply the + radius (5) by itself (5) to get the square of the radius.\\n2. Multiply the + squared radius by pi (3.14) to calculate the area.\\n\\nREADY: I am ready + to execute the task.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 293,\n \"completion_tokens\": + 54,\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\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:24:15 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '845' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Math Tutor. An expert + tutor\nYour personal goal is: Solve complex math problems step by step"},{"role":"user","content":"\nCurrent + Task: Calculate the area of a circle with radius 5 (use pi = 3.14)\n\nPlanning:\n## + Execution Plan\n1. Multiply the radius (5) by itself (5) to get the square of + the radius.\n2. Multiply the squared radius by pi (3.14) to calculate the area.\n\nREADY: + I am ready to execute the task.\n\nThis is the expected criteria for your final + answer: The area as a number\nyou MUST return the actual complete content as + the final answer, not a summary.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '682' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yVDh2U2xx3qeYHcDQvbetOmVCxb\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078255,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"To calculate the area of a circle with + a radius of 5, we will follow the steps outlined in the execution plan.\\n\\n1. + **Square the radius**:\\n \\\\[\\n 5 \\\\times 5 = 25\\n \\\\]\\n\\n2. + **Multiply the squared radius by pi (using \\\\(\\\\pi \\\\approx 3.14\\\\))**:\\n + \ \\\\[\\n \\\\text{Area} = \\\\pi \\\\times (\\\\text{radius})^2 = 3.14 + \\\\times 25\\n \\\\]\\n\\n Now, let's perform the multiplication:\\n + \ \\\\[\\n 3.14 \\\\times 25 = 78.5\\n \\\\]\\n\\nThus, the area of the + circle is \\\\( \\\\boxed{78.5} \\\\).\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 147,\n \"completion_tokens\": + 155,\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 \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:24:18 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '2228' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_tool.yaml b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_tool.yaml index e6b810d10..bc437a90a 100644 --- a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_tool.yaml +++ b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_with_tool.yaml @@ -1,7 +1,11 @@ interactions: - 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: 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:"}],"model":"gpt-3.5-turbo"}' + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal"},{"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."}],"model":"gpt-3.5-turbo","tool_choice":"auto","tools":[{"type":"function","function":{"name":"dummy_tool","description":"Useful + for when you need to get a dummy result for a query.","strict":true,"parameters":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}}}]}' headers: User-Agent: - X-USER-AGENT-XXX @@ -14,7 +18,7 @@ interactions: connection: - keep-alive content-length: - - '1381' + - '712' content-type: - application/json host: @@ -36,12 +40,26 @@ interactions: x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.12.10 + - 3.13.3 method: POST uri: https://api.openai.com/v1/chat/completions response: body: - string: "{\n \"id\": \"chatcmpl-CjDrE1Z8bFQjjxI2vDPPKgtOTm28p\",\n \"object\": \"chat.completion\",\n \"created\": 1764894064,\n \"model\": \"gpt-3.5-turbo-0125\",\n \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"you should always think about what to do\",\n \"refusal\": null,\n \"annotations\": []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 289,\n \"completion_tokens\": 8,\n \"total_tokens\": 297,\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" + string: "{\n \"id\": \"chatcmpl-D5DTlUmKYee1DaS5AqnaUCZ6B14xV\",\n \"object\": + \"chat.completion\",\n \"created\": 1770135825,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_tBCgelchfQjXXJrrM15MxqGJ\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"dummy_tool\",\n + \ \"arguments\": \"{\\\"query\\\":\\\"test query\\\"}\"\n }\n + \ }\n ],\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 122,\n \"completion_tokens\": + 16,\n \"total_tokens\": 138,\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: - CF-RAY-XXX @@ -50,7 +68,7 @@ interactions: Content-Type: - application/json Date: - - Fri, 05 Dec 2025 00:21:05 GMT + - Tue, 03 Feb 2026 16:23:46 GMT Server: - cloudflare Set-Cookie: @@ -70,13 +88,124 @@ interactions: openai-organization: - OPENAI-ORG-XXX openai-processing-ms: - - '379' + - '694' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are test role. test backstory\nYour + personal goal is: test goal"},{"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."},{"role":"assistant","content":null,"tool_calls":[{"id":"call_tBCgelchfQjXXJrrM15MxqGJ","type":"function","function":{"name":"dummy_tool","arguments":"{\"query\":\"test + query\"}"}}]},{"role":"tool","tool_call_id":"call_tBCgelchfQjXXJrrM15MxqGJ","name":"dummy_tool","content":"Dummy + result for: test query"},{"role":"user","content":"Analyze the tool result. + If requirements are met, provide the Final Answer. Otherwise, call the next + tool. Deliver only the answer without meta-commentary."}],"model":"gpt-3.5-turbo","tool_choice":"auto","tools":[{"type":"function","function":{"name":"dummy_tool","description":"Useful + for when you need to get a dummy result for a query.","strict":true,"parameters":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1202' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D5DTmxZJI2Ee7fHNc9dYtQkD7sIY2\",\n \"object\": + \"chat.completion\",\n \"created\": 1770135826,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Dummy result for: test query\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 188,\n \"completion_tokens\": 7,\n \"total_tokens\": 195,\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: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 16:23:47 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '416' openai-project: - OPENAI-PROJECT-XXX openai-version: - '2020-10-01' - x-envoy-upstream-service-time: - - '399' x-openai-proxy-wasm: - v0.1 x-ratelimit-limit-requests: diff --git a/lib/crewai/tests/cassettes/agents/test_agent_execute_task_without_planning.yaml b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_without_planning.yaml new file mode 100644 index 000000000..e4edcb095 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/test_agent_execute_task_without_planning.yaml @@ -0,0 +1,110 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + assistant\nYour personal goal is: Help solve math problems"},{"role":"user","content":"\nCurrent + Task: What is 12 * 3?\n\nThis is the expected criteria for your final answer: + A number\nyou MUST return the actual complete content as the final answer, not + a summary.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '400' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yVCw0CGLFmcVvniplwCCt8avtRb\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078254,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"12 * 3 = 36\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 75,\n \"completion_tokens\": 7,\n \"total_tokens\": 82,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:24:14 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '331' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/test_agent_kickoff_multi_step_task_with_planning.yaml b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_multi_step_task_with_planning.yaml new file mode 100644 index 000000000..6efd74ee1 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_multi_step_task_with_planning.yaml @@ -0,0 +1,243 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nFind the first + 3 prime numbers, add them together, then multiply by 2.\n\n## Expected Output\nComplete + the task successfully\n\n## Available Tools\nNo tools available\n\n## Instructions\nCreate + ONLY the essential steps needed to complete this task. Use the MINIMUM number + of steps required - do NOT pad your plan with unnecessary steps. Most tasks + need only 2-5 steps.\n\nFor each step:\n- State the specific action to take\n- + Specify which tool to use (if any)\n\nDo NOT include:\n- Setup or preparation + steps that are obvious\n- Verification steps unless critical\n- Documentation + or cleanup steps unless explicitly required\n- Generic steps like \"review results\" + or \"finalize output\"\n\nAfter your plan, state:\n- \"READY: I am ready to + execute the task.\" if the plan is complete\n- \"NOT READY: I need to refine + my plan because [reason].\" if you need more thinking"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"The + detailed reasoning plan for the task."},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1597' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yU0MD5GfSUjRW0R4cBmFJ6Hcjbi\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078180,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"### Execution Plan\\n1. Identify the + first 3 prime numbers: 2, 3, and 5.\\n2. Add the prime numbers together: 2 + + 3 + 5 = 10.\\n3. Multiply the sum by 2: 10 * 2 = 20.\\n\\nREADY: I am ready + to execute the task.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 291,\n \"completion_tokens\": + 73,\n \"total_tokens\": 364,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:23:02 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '1253' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Math Tutor. An expert + tutor who explains step by step\nYour personal goal is: Solve multi-step math + problems"},{"role":"user","content":"\nCurrent Task: Find the first 3 prime + numbers, add them together, then multiply by 2.\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '333' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yU2qY6Xqpkz2D5yVAwagQzuPpen\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078182,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"To solve the problem, let\u2019s go + through each step methodically.\\n\\n1. **Identify the first three prime numbers**:\\n + \ - **Prime numbers** are numbers greater than 1 that have no positive divisors + other than 1 and themselves.\\n - The first three prime numbers are:\\n + \ - 2\\n - 3\\n - 5\\n\\n2. **Add these prime numbers together**:\\n + \ - We add them together:\\n \\\\[\\n 2 + 3 + 5\\n \\\\]\\n - + Performing the addition step-by-step:\\n - First, add 2 and 3:\\n \\\\[\\n + \ 2 + 3 = 5\\n \\\\]\\n - Then add 5 to this result:\\n \\\\[\\n + \ 5 + 5 = 10\\n \\\\]\\n - So, the sum of the first three prime + numbers is **10**.\\n\\n3. **Multiply the sum by 2**:\\n - Now we multiply + the result by 2:\\n \\\\[\\n 10 \\\\times 2 = 20\\n \\\\]\\n \\nTherefore, + the final answer is **20**.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 62,\n \"completion_tokens\": + 236,\n \"total_tokens\": 298,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:23:06 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '3846' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_planning.yaml b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_planning.yaml new file mode 100644 index 000000000..d72d4a0b6 --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_planning.yaml @@ -0,0 +1,238 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nWhat is 15 + 27?\n\n## + Expected Output\nComplete the task successfully\n\n## Available Tools\nNo tools + available\n\n## Instructions\nCreate ONLY the essential steps needed to complete + this task. Use the MINIMUM number of steps required - do NOT pad your plan with + unnecessary steps. Most tasks need only 2-5 steps.\n\nFor each step:\n- State + the specific action to take\n- Specify which tool to use (if any)\n\nDo NOT + include:\n- Setup or preparation steps that are obvious\n- Verification steps + unless critical\n- Documentation or cleanup steps unless explicitly required\n- + Generic steps like \"review results\" or \"finalize output\"\n\nAfter your plan, + state:\n- \"READY: I am ready to execute the task.\" if the plan is complete\n- + \"NOT READY: I need to refine my plan because [reason].\" if you need more thinking"}],"model":"gpt-4o-mini","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"The + detailed reasoning plan for the task."},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1543' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTrm3GkzDX47DIcce9uA3iF8kFE\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078171,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"## Execution Plan\\n\\n1. Calculate + the sum of 15 and 27.\\n\\nREADY: I am ready to execute the task.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 281,\n \"completion_tokens\": 27,\n \"total_tokens\": 308,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:51 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '691' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + math tutor\nYour personal goal is: Help solve math problems step by step"},{"role":"user","content":"\nCurrent + Task: What is 15 + 27?\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '269' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTrUOvExA9fTFDwYxvG4xEgRP6L\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078171,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"To solve the problem \\\\( 15 + 27 + \\\\), we can follow these steps:\\n\\n1. **Align the numbers**: Write them + one on top of the other, aligned by their rightmost digits:\\n\\n ```\\n + \ 15\\n + 27\\n ```\\n\\n2. **Add the units place**: Start from the + rightmost digits (units place):\\n - \\\\( 5 + 7 = 12 \\\\)\\n - Write + down 2 and carry over 1.\\n\\n3. **Add the tens place**: Now, move to the + next column (tens place):\\n - \\\\( 1 + 2 + 1 \\\\) (the 1 is from the + carry) \\\\( = 4 \\\\)\\n\\n4. **Combine the results**: Now, combine the results + from the tens and units places:\\n - The result in the tens place is 4 and + in the units place is 2, giving us \\\\( 42 \\\\).\\n\\nTherefore, \\\\( 15 + + 27 = 42 \\\\).\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 50,\n \"completion_tokens\": + 209,\n \"total_tokens\": 259,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:55 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '3263' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_planning_disabled.yaml b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_planning_disabled.yaml new file mode 100644 index 000000000..2c24aa25a --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_with_planning_disabled.yaml @@ -0,0 +1,110 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + assistant\nYour personal goal is: Help solve math problems"},{"role":"user","content":"\nCurrent + Task: What is 100 / 4?\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '255' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yU6mFapBLuCx4fJtYBup52dwwrs\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078186,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"To solve the problem 100 divided by + 4, you can perform the division as follows:\\n\\n100 \xF7 4 = 25\\n\\nSo, + the answer is 25.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 46,\n \"completion_tokens\": + 36,\n \"total_tokens\": 82,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:23:07 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '1098' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/test_agent_kickoff_without_planning.yaml b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_without_planning.yaml new file mode 100644 index 000000000..250b3961c --- /dev/null +++ b/lib/crewai/tests/cassettes/agents/test_agent_kickoff_without_planning.yaml @@ -0,0 +1,108 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are Math Assistant. A helpful + assistant\nYour personal goal is: Help solve math problems"},{"role":"user","content":"\nCurrent + Task: What is 8 * 7?\n\nProvide your complete response:"}],"model":"gpt-4o-mini"}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '253' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D4yTqLFhGtfq2CyS2aPPhiZL4GjtQ\",\n \"object\": + \"chat.completion\",\n \"created\": 1770078170,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"8 * 7 equals 56.\",\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 46,\n \"completion_tokens\": 8,\n \"total_tokens\": 54,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_1590f93f9d\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 00:22:50 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '443' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 From a6dcb275e1ebb6585d485ccdc583a49f6d3aa55c Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:08:26 -0800 Subject: [PATCH 14/14] Lorenze/feat planning pt 2 todo list gen (#4449) * feat: introduce PlanningConfig for enhanced agent planning capabilities This update adds a new PlanningConfig class to manage agent planning configurations, allowing for customizable planning behavior before task execution. The existing reasoning parameter is deprecated in favor of this new configuration, ensuring backward compatibility while enhancing the planning process. Additionally, the Agent class has been updated to utilize this new configuration, and relevant utility functions have been adjusted accordingly. Tests have been added to validate the new planning functionality and ensure proper integration with existing agent workflows. * dropping redundancy * fix test * revert handle_reasoning here * refactor: update reasoning handling in Agent class This commit modifies the Agent class to conditionally call the handle_reasoning function based on the executor class being used. The legacy CrewAgentExecutor will continue to utilize handle_reasoning, while the new AgentExecutor will manage planning internally. Additionally, the PlanningConfig class has been referenced in the documentation to clarify its role in enabling or disabling planning. Tests have been updated to reflect these changes and ensure proper functionality. * improve planning prompts * matching * refactor: remove default enabled flag from PlanningConfig in Agent class * more cassettes * fix test * feat: enhance agent planning with structured todo management This commit introduces a new planning system within the AgentExecutor class, allowing for the creation of structured todo items from planning steps. The TodoList and TodoItem models have been added to facilitate tracking of plan execution. The reasoning plan now includes a list of steps, improving the clarity and organization of agent tasks. Additionally, tests have been added to validate the new planning functionality and ensure proper integration with existing workflows. * refactor: update planning prompt and remove deprecated methods in reasoning handler * improve planning prompt * improve handler * linted * linted --- .../src/crewai/experimental/agent_executor.py | 28 +- .../src/crewai/utilities/planning_types.py | 103 ++ .../src/crewai/utilities/reasoning_handler.py | 139 +- ...pic_research_workflow_generates_steps.yaml | 1621 +++++++++++++++++ ...ure_research_workflow_generates_steps.yaml | 548 ++++++ ...ini_research_workflow_generates_steps.yaml | 613 +++++++ ...nai_research_workflow_generates_steps.yaml | 708 +++++++ .../tests/utilities/test_planning_types.py | 389 ++++ .../utilities/test_structured_planning.py | 698 +++++++ 9 files changed, 4824 insertions(+), 23 deletions(-) create mode 100644 lib/crewai/src/crewai/utilities/planning_types.py create mode 100644 lib/crewai/tests/cassettes/utilities/TestAnthropicStructuredPlanning.test_anthropic_research_workflow_generates_steps.yaml create mode 100644 lib/crewai/tests/cassettes/utilities/TestAzureStructuredPlanning.test_azure_research_workflow_generates_steps.yaml create mode 100644 lib/crewai/tests/cassettes/utilities/TestGeminiStructuredPlanning.test_gemini_research_workflow_generates_steps.yaml create mode 100644 lib/crewai/tests/cassettes/utilities/TestOpenAIStructuredPlanning.test_openai_research_workflow_generates_steps.yaml create mode 100644 lib/crewai/tests/utilities/test_planning_types.py create mode 100644 lib/crewai/tests/utilities/test_structured_planning.py diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index 12dbc6bf3..e22b8c129 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -61,6 +61,7 @@ from crewai.utilities.agent_utils import ( ) from crewai.utilities.constants import TRAINING_DATA_FILE from crewai.utilities.i18n import I18N, get_i18n +from crewai.utilities.planning_types import PlanStep, TodoItem, TodoList from crewai.utilities.printer import Printer from crewai.utilities.string_utils import sanitize_tool_name from crewai.utilities.tool_utils import execute_tool_and_check_finality @@ -98,6 +99,9 @@ class AgentReActState(BaseModel): plan_ready: bool = Field( default=False, description="Whether agent is ready to execute" ) + todos: TodoList = Field( + default_factory=TodoList, description="Todo list for tracking plan execution" + ) class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): @@ -330,7 +334,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): This is the entry point for the agent execution flow. If planning is enabled on the agent, it generates a plan before execution begins. - The plan is stored in state but not executed on yet (Phase 2). + The plan is stored in state and todos are created from the steps. """ if not getattr(self.agent, "planning_enabled", False): return @@ -354,6 +358,9 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): self.state.plan = output.plan.plan self.state.plan_ready = output.plan.ready + if self.state.plan_ready and output.plan.steps: + self._create_todos_from_plan(output.plan.steps) + # Backward compatibility: append plan to task description # This can be removed in Phase 2 when plan execution is implemented if self.task and self.state.plan: @@ -363,6 +370,25 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): if hasattr(self.agent, "_logger"): self.agent._logger.log("error", f"Error during planning: {e!s}") + def _create_todos_from_plan(self, steps: list[PlanStep]) -> None: + """Convert plan steps into trackable todo items. + + Args: + steps: List of PlanStep objects from the reasoning handler. + """ + todos: list[TodoItem] = [] + for step in steps: + todo = TodoItem( + step_number=step.step_number, + description=step.description, + tool_to_use=step.tool_to_use, + depends_on=step.depends_on, + status="pending", + ) + todos.append(todo) + + self.state.todos = TodoList(items=todos) + @listen(generate_plan) def initialize_reasoning(self) -> Literal["initialized"]: """Initialize the reasoning flow and emit agent start logs.""" diff --git a/lib/crewai/src/crewai/utilities/planning_types.py b/lib/crewai/src/crewai/utilities/planning_types.py new file mode 100644 index 000000000..bafd04453 --- /dev/null +++ b/lib/crewai/src/crewai/utilities/planning_types.py @@ -0,0 +1,103 @@ +"""Types for agent planning and todo tracking.""" + +from __future__ import annotations + +from typing import Literal +from uuid import uuid4 + +from pydantic import BaseModel, Field + + +# Todo status type +TodoStatus = Literal["pending", "running", "completed"] + + +class PlanStep(BaseModel): + """A single step in the reasoning plan.""" + + step_number: int = Field(description="Step number (1-based)") + description: str = Field(description="What to do in this step") + tool_to_use: str | None = Field( + default=None, description="Tool to use for this step, if any" + ) + depends_on: list[int] = Field( + default_factory=list, description="Step numbers this step depends on" + ) + + +class TodoItem(BaseModel): + """A single todo item representing a step in the execution plan.""" + + id: str = Field(default_factory=lambda: str(uuid4())) + step_number: int = Field(description="Order of this step in the plan (1-based)") + description: str = Field(description="What needs to be done") + tool_to_use: str | None = Field( + default=None, description="Tool to use for this step, if any" + ) + status: TodoStatus = Field(default="pending", description="Current status") + depends_on: list[int] = Field( + default_factory=list, description="Step numbers this depends on" + ) + result: str | None = Field( + default=None, description="Result after completion, if any" + ) + + +class TodoList(BaseModel): + """Collection of todos for tracking plan execution.""" + + items: list[TodoItem] = Field(default_factory=list) + + @property + def current_todo(self) -> TodoItem | None: + """Get the currently running todo item.""" + for item in self.items: + if item.status == "running": + return item + return None + + @property + def next_pending(self) -> TodoItem | None: + """Get the next pending todo item.""" + for item in self.items: + if item.status == "pending": + return item + return None + + @property + def is_complete(self) -> bool: + """Check if all todos are completed.""" + return len(self.items) > 0 and all( + item.status == "completed" for item in self.items + ) + + @property + def pending_count(self) -> int: + """Count of pending todos.""" + return sum(1 for item in self.items if item.status == "pending") + + @property + def completed_count(self) -> int: + """Count of completed todos.""" + return sum(1 for item in self.items if item.status == "completed") + + def get_by_step_number(self, step_number: int) -> TodoItem | None: + """Get a todo by its step number.""" + for item in self.items: + if item.step_number == step_number: + return item + return None + + def mark_running(self, step_number: int) -> None: + """Mark a todo as running by step number.""" + item = self.get_by_step_number(step_number) + if item: + item.status = "running" + + def mark_completed(self, step_number: int, result: str | None = None) -> None: + """Mark a todo as completed by step number.""" + item = self.get_by_step_number(step_number) + if item: + item.status = "completed" + if result: + item.result = result diff --git a/lib/crewai/src/crewai/utilities/reasoning_handler.py b/lib/crewai/src/crewai/utilities/reasoning_handler.py index cab2fc4f9..374d074ff 100644 --- a/lib/crewai/src/crewai/utilities/reasoning_handler.py +++ b/lib/crewai/src/crewai/utilities/reasoning_handler.py @@ -16,9 +16,16 @@ from crewai.events.types.reasoning_events import ( ) from crewai.llm import LLM from crewai.utilities.llm_utils import create_llm +from crewai.utilities.planning_types import PlanStep from crewai.utilities.string_utils import sanitize_tool_name +if TYPE_CHECKING: + from crewai.agent import Agent + from crewai.agent.planning_config import PlanningConfig + from crewai.task import Task + + if TYPE_CHECKING: from crewai.agent import Agent from crewai.agent.planning_config import PlanningConfig @@ -29,6 +36,9 @@ class ReasoningPlan(BaseModel): """Model representing a reasoning plan for a task.""" plan: str = Field(description="The detailed reasoning plan for the task.") + steps: list[PlanStep] = Field( + default_factory=list, description="Structured steps to execute" + ) ready: bool = Field(description="Whether the agent is ready to execute the task.") @@ -47,20 +57,53 @@ FUNCTION_SCHEMA: Final[dict[str, Any]] = { "type": "function", "function": { "name": "create_reasoning_plan", - "description": "Create or refine a reasoning plan for a task", + "description": "Create or refine a reasoning plan for a task with structured steps", "parameters": { "type": "object", "properties": { "plan": { "type": "string", - "description": "The detailed reasoning plan for the task.", + "description": "A brief summary of the overall plan.", + }, + "steps": { + "type": "array", + "description": "List of discrete steps to execute the plan", + "items": { + "type": "object", + "properties": { + "step_number": { + "type": "integer", + "description": "Step number (1-based)", + }, + "description": { + "type": "string", + "description": "What to do in this step", + }, + "tool_to_use": { + "type": ["string", "null"], + "description": "Tool to use for this step, or null if no tool needed", + }, + "depends_on": { + "type": "array", + "items": {"type": "integer"}, + "description": "Step numbers this step depends on (empty array if none)", + }, + }, + "required": [ + "step_number", + "description", + "tool_to_use", + "depends_on", + ], + "additionalProperties": False, + }, }, "ready": { "type": "boolean", "description": "Whether the agent is ready to execute the task.", }, }, - "required": ["plan", "ready"], + "required": ["plan", "steps", "ready"], "additionalProperties": False, }, }, @@ -214,44 +257,53 @@ class AgentReasoning: Returns: The output of the agent planning process. """ - plan, ready = self._create_initial_plan() - plan, ready = self._refine_plan_if_needed(plan, ready) + plan, steps, ready = self._create_initial_plan() + plan, steps, ready = self._refine_plan_if_needed(plan, steps, ready) - reasoning_plan = ReasoningPlan(plan=plan, ready=ready) + reasoning_plan = ReasoningPlan(plan=plan, steps=steps, ready=ready) return AgentReasoningOutput(plan=reasoning_plan) - def _create_initial_plan(self) -> tuple[str, bool]: + def _create_initial_plan(self) -> tuple[str, list[PlanStep], bool]: """Creates the initial plan for the task. Returns: - The initial plan and whether the agent is ready to execute the task. + A tuple of the plan summary, list of steps, and whether the agent is ready. """ planning_prompt = self._create_planning_prompt() + planning_prompt = self._create_planning_prompt() if self.llm.supports_function_calling(): - plan, ready = self._call_with_function(planning_prompt, "create_plan") - return plan, ready + plan, steps, ready = self._call_with_function( + planning_prompt, "create_plan" + ) + return plan, steps, ready response = self._call_llm_with_prompt( prompt=planning_prompt, plan_type="create_plan", ) - return self._parse_planning_response(str(response)) + plan, ready = self._parse_planning_response(str(response)) + return plan, [], ready # No structured steps from text parsing - def _refine_plan_if_needed(self, plan: str, ready: bool) -> tuple[str, bool]: + def _refine_plan_if_needed( + self, plan: str, steps: list[PlanStep], ready: bool + ) -> tuple[str, list[PlanStep], bool]: """Refines the plan if the agent is not ready to execute the task. Args: plan: The current plan. + steps: The current list of steps. ready: Whether the agent is ready to execute the task. Returns: - The refined plan and whether the agent is ready to execute the task. + The refined plan, steps, and whether the agent is ready to execute. """ + attempt = 1 max_attempts = self.config.max_attempts task_id = str(self.task.id) if self.task else "kickoff" + current_attempt = attempt + 1 while not ready and (max_attempts is None or attempt < max_attempts): # Emit event for each refinement attempt @@ -261,7 +313,7 @@ class AgentReasoning: AgentReasoningStartedEvent( agent_role=self.agent.role, task_id=task_id, - attempt=attempt + 1, + attempt=current_attempt, from_task=self.task, ), ) @@ -269,15 +321,36 @@ class AgentReasoning: pass refine_prompt = self._create_refine_prompt(plan) + refine_prompt = self._create_refine_prompt(plan) if self.llm.supports_function_calling(): - plan, ready = self._call_with_function(refine_prompt, "refine_plan") + plan, steps, ready = self._call_with_function( + refine_prompt, "refine_plan" + ) else: response = self._call_llm_with_prompt( prompt=refine_prompt, plan_type="refine_plan", ) plan, ready = self._parse_planning_response(str(response)) + steps = [] # No structured steps from text parsing + + # Emit completed event for this refinement attempt + try: + crewai_event_bus.emit( + self.agent, + AgentReasoningCompletedEvent( + agent_role=self.agent.role, + task_id=task_id, + plan=plan, + ready=ready, + attempt=current_attempt, + from_task=self.task, + from_agent=self.agent, + ), + ) + except Exception: # noqa: S110 + pass attempt += 1 @@ -288,11 +361,11 @@ class AgentReasoning: ) break - return plan, ready + return plan, steps, ready def _call_with_function( self, prompt: str, plan_type: Literal["create_plan", "refine_plan"] - ) -> tuple[str, bool]: + ) -> tuple[str, list[PlanStep], bool]: """Calls the LLM with function calling to get a plan. Args: @@ -300,7 +373,7 @@ class AgentReasoning: plan_type: The type of plan being created. Returns: - A tuple containing the plan and whether the agent is ready. + A tuple containing the plan summary, list of steps, and whether the agent is ready. """ self.logger.debug(f"Using function calling for {plan_type} planning") @@ -308,9 +381,13 @@ class AgentReasoning: system_prompt = self._get_system_prompt() # Prepare a simple callable that just returns the tool arguments as JSON - def _create_reasoning_plan(plan: str, ready: bool = True) -> str: + def _create_reasoning_plan( + plan: str, + steps: list[dict[str, Any]] | None = None, + ready: bool = True, + ) -> str: """Return the planning result in JSON string form.""" - return json.dumps({"plan": plan, "ready": ready}) + return json.dumps({"plan": plan, "steps": steps or [], "ready": ready}) response = self.llm.call( [ @@ -322,17 +399,33 @@ class AgentReasoning: from_task=self.task, from_agent=self.agent, ) - try: result = json.loads(response) if "plan" in result and "ready" in result: - return result["plan"], result["ready"] + # Parse steps from the response + steps: list[PlanStep] = [] + raw_steps = result.get("steps", []) + try: + for step_data in raw_steps: + step = PlanStep( + step_number=step_data.get("step_number", 0), + description=step_data.get("description", ""), + tool_to_use=step_data.get("tool_to_use"), + depends_on=step_data.get("depends_on", []), + ) + steps.append(step) + except Exception as step_error: + self.logger.warning( + f"Failed to parse step: {step_data}, error: {step_error}" + ) + return result["plan"], steps, result["ready"] except (json.JSONDecodeError, KeyError): pass response_str = str(response) return ( response_str, + [], "READY: I am ready to execute the task." in response_str, ) @@ -356,12 +449,14 @@ class AgentReasoning: fallback_str = str(fallback_response) return ( fallback_str, + [], "READY: I am ready to execute the task." in fallback_str, ) except Exception as inner_e: self.logger.error(f"Error during fallback text parsing: {inner_e!s}") return ( "Failed to generate a plan due to an error.", + [], True, ) # Default to ready to avoid getting stuck diff --git a/lib/crewai/tests/cassettes/utilities/TestAnthropicStructuredPlanning.test_anthropic_research_workflow_generates_steps.yaml b/lib/crewai/tests/cassettes/utilities/TestAnthropicStructuredPlanning.test_anthropic_research_workflow_generates_steps.yaml new file mode 100644 index 000000000..e9e089149 --- /dev/null +++ b/lib/crewai/tests/cassettes/utilities/TestAnthropicStructuredPlanning.test_anthropic_research_workflow_generates_steps.yaml @@ -0,0 +1,1621 @@ +interactions: +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Create a focused + execution plan for the following task:\n\n## Task\nResearch the current state + of the AI agent market:\n1. Search for recent information about AI agents and + their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step.\n\n## Expected Output\nComplete the task successfully\n\n## Available + Tools\nweb_search, read_website, generate_report\n\n## Instructions\nCreate + ONLY the essential steps needed to complete this task. Use the MINIMUM number + of steps required - do NOT pad your plan with unnecessary steps. Most tasks + need only 2-5 steps.\n\nFor each step:\n- State the specific action to take\n- + Specify which tool to use (if any)\n- Note dependencies on previous steps if + this step requires their output\n- If a step involves multiple items (e.g., + research 3 competitors), note this explicitly\n\nDo NOT include:\n- Setup or + preparation steps that are obvious\n- Verification steps unless critical\n- + Documentation or cleanup steps unless explicitly required\n- Generic steps like + \"review results\" or \"finalize output\"\n\nAfter your plan, state:\n- \"READY: + I am ready to execute the task.\" if the plan is complete\n- \"NOT READY: I + need to refine my plan because [reason].\" if you need more thinking"}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are a strategic planning assistant. Create minimal, effective execution plans. + Prefer fewer steps over more.","tools":[{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task with structured steps","input_schema":{"type":"object","properties":{"plan":{"type":"string","description":"A + brief summary of the overall plan."},"steps":{"type":"array","description":"List + of discrete steps to execute the plan","items":{"type":"object","properties":{"step_number":{"type":"integer","description":"Step + number (1-based)"},"description":{"type":"string","description":"What to do + in this step"},"tool_to_use":{"type":["string","null"],"description":"Tool to + use for this step, or null if no tool needed"},"depends_on":{"type":"array","items":{"type":"integer"},"description":"Step + numbers this step depends on (empty array if none)"}},"required":["step_number","description","tool_to_use","depends_on"],"additionalProperties":false}},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","steps","ready"],"additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '2623' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_016Qg3VgHjbPnXw7yU3bCzS3","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01FNX3spUotR7NWBkbeJG6zU","name":"create_reasoning_plan","input":{"plan":"Execute + a 3-step research process: search for current AI agent market information, + read detailed content from a key industry source, and generate a comprehensive + report summarizing the findings.","steps":[{"step_number":1,"description":"Search + for recent information about AI agents market trends, including market size, + growth, key players, and current developments","tool_to_use":"web_search","depends_on":[]},{"step_number":2,"description":"Read + detailed content from the most relevant and authoritative industry source + found in the search results","tool_to_use":"read_website","depends_on":[1]},{"step_number":3,"description":"Generate + a brief report summarizing key findings about the current state of the AI + agent market, including trends, market dynamics, and significant insights","tool_to_use":"generate_report","depends_on":[1,2]}],"ready":true}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":933,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":333,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:35 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '5423' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Create a focused + execution plan for the following task:\n\n## Task\nResearch the current state + of the AI agent market:\n1. Search for recent information about AI agents and + their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step.\n\n## Expected Output\nComplete the task successfully\n\n## Available + Tools\nweb_search, read_website, generate_report\n\n## Instructions\nCreate + ONLY the essential steps needed to complete this task. Use the MINIMUM number + of steps required - do NOT pad your plan with unnecessary steps. Most tasks + need only 2-5 steps.\n\nFor each step:\n- State the specific action to take\n- + Specify which tool to use (if any)\n- Note dependencies on previous steps if + this step requires their output\n- If a step involves multiple items (e.g., + research 3 competitors), note this explicitly\n\nDo NOT include:\n- Setup or + preparation steps that are obvious\n- Verification steps unless critical\n- + Documentation or cleanup steps unless explicitly required\n- Generic steps like + \"review results\" or \"finalize output\"\n\nAfter your plan, state:\n- \"READY: + I am ready to execute the task.\" if the plan is complete\n- \"NOT READY: I + need to refine my plan because [reason].\" if you need more thinking"},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01FNX3spUotR7NWBkbeJG6zU","name":"create_reasoning_plan","input":{"plan":"Execute + a 3-step research process: search for current AI agent market information, read + detailed content from a key industry source, and generate a comprehensive report + summarizing the findings.","steps":[{"step_number":1,"description":"Search for + recent information about AI agents market trends, including market size, growth, + key players, and current developments","tool_to_use":"web_search","depends_on":[]},{"step_number":2,"description":"Read + detailed content from the most relevant and authoritative industry source found + in the search results","tool_to_use":"read_website","depends_on":[1]},{"step_number":3,"description":"Generate + a brief report summarizing key findings about the current state of the AI agent + market, including trends, market dynamics, and significant insights","tool_to_use":"generate_report","depends_on":[1,2]}],"ready":true}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01FNX3spUotR7NWBkbeJG6zU","content":"{\"plan\": + \"Execute a 3-step research process: search for current AI agent market information, + read detailed content from a key industry source, and generate a comprehensive + report summarizing the findings.\", \"steps\": [{\"step_number\": 1, \"description\": + \"Search for recent information about AI agents market trends, including market + size, growth, key players, and current developments\", \"tool_to_use\": \"web_search\", + \"depends_on\": []}, {\"step_number\": 2, \"description\": \"Read detailed content + from the most relevant and authoritative industry source found in the search + results\", \"tool_to_use\": \"read_website\", \"depends_on\": [1]}, {\"step_number\": + 3, \"description\": \"Generate a brief report summarizing key findings about + the current state of the AI agent market, including trends, market dynamics, + and significant insights\", \"tool_to_use\": \"generate_report\", \"depends_on\": + [1, 2]}], \"ready\": true}"}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are a strategic planning assistant. Create minimal, effective execution plans. + Prefer fewer steps over more.","tools":[{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task with structured steps","input_schema":{"type":"object","properties":{"plan":{"type":"string","description":"A + brief summary of the overall plan."},"steps":{"type":"array","description":"List + of discrete steps to execute the plan","items":{"type":"object","properties":{"step_number":{"type":"integer","description":"Step + number (1-based)"},"description":{"type":"string","description":"What to do + in this step"},"tool_to_use":{"type":["string","null"],"description":"Tool to + use for this step, or null if no tool needed"},"depends_on":{"type":"array","items":{"type":"integer"},"description":"Step + numbers this step depends on (empty array if none)"}},"required":["step_number","description","tool_to_use","depends_on"],"additionalProperties":false}},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","steps","ready"],"additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '4666' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: "{\"model\":\"claude-sonnet-4-20250514\",\"id\":\"msg_01SmXmUZtMpfmWiiy3xaFH73\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"READY: + I am ready to execute the task.\\n\\nThe plan consists of exactly 3 essential + steps that directly map to the task requirements:\\n\\n1. **Web Search** - + Find current AI agent market information and trends\\n2. **Read Website** + - Deep dive into the most relevant industry source from search results \\n3. + **Generate Report** - Create a brief summary report of key findings\\n\\nEach + step uses the specified tools (web_search \u2192 read_website \u2192 generate_report) + and builds logically on the previous step's output. This is the minimum viable + plan to complete the research task effectively.\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":1455,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":129,\"service_tier\":\"standard\"}}" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:39 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '3504' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '1767' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_017n2pRrBb8x11ejJioQdSrk","type":"message","role":"assistant","content":[{"type":"text","text":"I''ll + help you research the current state of the AI agent market. Let me start by + searching for recent information about AI agents and market trends."},{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":753,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":92,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:41 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '1788' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '2229' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01YVwCuUq7amJSNfqx3UFjtW","type":"message","role":"assistant","content":[{"type":"text","text":"Let + me search for more specific information about the AI agent market:"},{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":863,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":78,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:43 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '1874' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01LPFvMaBVKRamhKoovTXXpY","content":"Search + results for ''AI agent market size growth forecast 2024 enterprise adoption'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '2723' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01GY7iw51CKwGevvmDnHjiSX","type":"message","role":"assistant","content":[{"type":"text","text":"Now + let me search for more specific recent information about AI agents and their + applications:"},{"type":"tool_use","id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","name":"web_search","input":{"query":"\"AI + agents\" market report 2024 autonomous intelligent systems"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":976,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":82,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:45 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2191' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01LPFvMaBVKRamhKoovTXXpY","content":"Search + results for ''AI agent market size growth forecast 2024 enterprise adoption'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","name":"web_search","input":{"query":"\"AI + agents\" market report 2024 autonomous intelligent systems"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","content":"Search + results for ''\"AI agents\" market report 2024 autonomous intelligent systems'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '3221' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01H6AoWyG2M28SM6F249wrnc","type":"message","role":"assistant","content":[{"type":"text","text":"Now + let me search for a specific industry source to get detailed content:"},{"type":"tool_use","id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","name":"web_search","input":{"query":"Gartner + McKinsey Deloitte AI agent market report 2024"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":1089,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":85,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:48 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2356' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01LPFvMaBVKRamhKoovTXXpY","content":"Search + results for ''AI agent market size growth forecast 2024 enterprise adoption'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","name":"web_search","input":{"query":"\"AI + agents\" market report 2024 autonomous intelligent systems"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","content":"Search + results for ''\"AI agents\" market report 2024 autonomous intelligent systems'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","name":"web_search","input":{"query":"Gartner + McKinsey Deloitte AI agent market report 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","content":"Search + results for ''Gartner McKinsey Deloitte AI agent market report 2024'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '3699' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01GjNzYdtG7YLwQiLKjKsnBv","type":"message","role":"assistant","content":[{"type":"text","text":"Let + me search for more specific and recent information about the AI agent market + to get actual URLs I can read from:"},{"type":"tool_use","id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","name":"web_search","input":{"query":"site:gartner.com + OR site:mckinsey.com OR site:forrester.com AI agents market 2024"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":1214,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":104,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:50 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2220' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01LPFvMaBVKRamhKoovTXXpY","content":"Search + results for ''AI agent market size growth forecast 2024 enterprise adoption'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","name":"web_search","input":{"query":"\"AI + agents\" market report 2024 autonomous intelligent systems"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","content":"Search + results for ''\"AI agents\" market report 2024 autonomous intelligent systems'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","name":"web_search","input":{"query":"Gartner + McKinsey Deloitte AI agent market report 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","content":"Search + results for ''Gartner McKinsey Deloitte AI agent market report 2024'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","name":"web_search","input":{"query":"site:gartner.com + OR site:mckinsey.com OR site:forrester.com AI agents market 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","content":"Search + results for ''site:gartner.com OR site:mckinsey.com OR site:forrester.com AI + agents market 2024'': Found 3 relevant articles about the topic including market + analysis, competitor data, and industry trends."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '4233' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01KxuhEyXmBJmFJRXYhjrrX2","type":"message","role":"assistant","content":[{"type":"text","text":"Let + me try a more general search to find specific industry sources with detailed + content:"},{"type":"tool_use","id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","name":"web_search","input":{"query":"AI + agent market research report industry analysis URL"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":1359,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":77,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:52 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2383' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01LPFvMaBVKRamhKoovTXXpY","content":"Search + results for ''AI agent market size growth forecast 2024 enterprise adoption'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","name":"web_search","input":{"query":"\"AI + agents\" market report 2024 autonomous intelligent systems"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","content":"Search + results for ''\"AI agents\" market report 2024 autonomous intelligent systems'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","name":"web_search","input":{"query":"Gartner + McKinsey Deloitte AI agent market report 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","content":"Search + results for ''Gartner McKinsey Deloitte AI agent market report 2024'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","name":"web_search","input":{"query":"site:gartner.com + OR site:mckinsey.com OR site:forrester.com AI agents market 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","content":"Search + results for ''site:gartner.com OR site:mckinsey.com OR site:forrester.com AI + agents market 2024'': Found 3 relevant articles about the topic including market + analysis, competitor data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","name":"web_search","input":{"query":"AI + agent market research report industry analysis URL"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","content":"Search + results for ''AI agent market research report industry analysis URL'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '4711' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01BDYJZns1pmXqHaJvmH6CoM","type":"message","role":"assistant","content":[{"type":"text","text":"Based + on the search results, I can see there are relevant articles about AI agent + market trends, but I need to read detailed content from a specific industry + source. Let me try to access a comprehensive market report by searching for + a specific URL."},{"type":"tool_use","id":"toolu_01QRY1ogb95zfGesVjxC2JDR","name":"read_website","input":{"url":"https://www.gartner.com/en/newsroom/press-releases/2024-03-18-gartner-forecasts-worldwide-spending-on-ai-software-to-grow-22-percent-in-2024"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":1464,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":156,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:56 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '3505' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01LPFvMaBVKRamhKoovTXXpY","content":"Search + results for ''AI agent market size growth forecast 2024 enterprise adoption'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","name":"web_search","input":{"query":"\"AI + agents\" market report 2024 autonomous intelligent systems"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","content":"Search + results for ''\"AI agents\" market report 2024 autonomous intelligent systems'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","name":"web_search","input":{"query":"Gartner + McKinsey Deloitte AI agent market report 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","content":"Search + results for ''Gartner McKinsey Deloitte AI agent market report 2024'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","name":"web_search","input":{"query":"site:gartner.com + OR site:mckinsey.com OR site:forrester.com AI agents market 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","content":"Search + results for ''site:gartner.com OR site:mckinsey.com OR site:forrester.com AI + agents market 2024'': Found 3 relevant articles about the topic including market + analysis, competitor data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","name":"web_search","input":{"query":"AI + agent market research report industry analysis URL"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","content":"Search + results for ''AI agent market research report industry analysis URL'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01QRY1ogb95zfGesVjxC2JDR","name":"read_website","input":{"url":"https://www.gartner.com/en/newsroom/press-releases/2024-03-18-gartner-forecasts-worldwide-spending-on-ai-software-to-grow-22-percent-in-2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01QRY1ogb95zfGesVjxC2JDR","content":"Content + from https://www.gartner.com/en/newsroom/press-releases/2024-03-18-gartner-forecasts-worldwide-spending-on-ai-software-to-grow-22-percent-in-2024: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '5388' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01NKpr5WzqykTDeFcETpE3Bx","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01GsC9SQeiwRhRS2W5dgc1BB","name":"read_website","input":{"url":"https://www.mckinsey.com/capabilities/mckinsey-digital/our-insights/the-economic-potential-of-generative-ai-the-next-productivity-frontier"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":1671,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":94,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:58 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '1838' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01LPFvMaBVKRamhKoovTXXpY","content":"Search + results for ''AI agent market size growth forecast 2024 enterprise adoption'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","name":"web_search","input":{"query":"\"AI + agents\" market report 2024 autonomous intelligent systems"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","content":"Search + results for ''\"AI agents\" market report 2024 autonomous intelligent systems'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","name":"web_search","input":{"query":"Gartner + McKinsey Deloitte AI agent market report 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","content":"Search + results for ''Gartner McKinsey Deloitte AI agent market report 2024'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","name":"web_search","input":{"query":"site:gartner.com + OR site:mckinsey.com OR site:forrester.com AI agents market 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","content":"Search + results for ''site:gartner.com OR site:mckinsey.com OR site:forrester.com AI + agents market 2024'': Found 3 relevant articles about the topic including market + analysis, competitor data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","name":"web_search","input":{"query":"AI + agent market research report industry analysis URL"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","content":"Search + results for ''AI agent market research report industry analysis URL'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01QRY1ogb95zfGesVjxC2JDR","name":"read_website","input":{"url":"https://www.gartner.com/en/newsroom/press-releases/2024-03-18-gartner-forecasts-worldwide-spending-on-ai-software-to-grow-22-percent-in-2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01QRY1ogb95zfGesVjxC2JDR","content":"Content + from https://www.gartner.com/en/newsroom/press-releases/2024-03-18-gartner-forecasts-worldwide-spending-on-ai-software-to-grow-22-percent-in-2024: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01GsC9SQeiwRhRS2W5dgc1BB","name":"read_website","input":{"url":"https://www.mckinsey.com/capabilities/mckinsey-digital/our-insights/the-economic-potential-of-generative-ai-the-next-productivity-frontier"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01GsC9SQeiwRhRS2W5dgc1BB","content":"Content + from https://www.mckinsey.com/capabilities/mckinsey-digital/our-insights/the-economic-potential-of-generative-ai-the-next-productivity-frontier: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '6061' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01JhdevC7FFtcDY3194v65WK","type":"message","role":"assistant","content":[{"type":"text","text":"Based + on my research, let me now generate a comprehensive report summarizing the + key findings about the current state of the AI agent market:"},{"type":"tool_use","id":"toolu_01K2UjYMDgyRwP15rmRMhCbE","name":"generate_report","input":{"title":"AI + Agent Market Analysis 2024: Current State and Trends","findings":"**Market + Size and Growth:**\nThe AI agent market has reached approximately $50 billion + in 2024, demonstrating robust expansion with a year-over-year growth rate + of 15%. According to Gartner''s latest forecasts, worldwide spending on AI + software is expected to grow 22% in 2024, indicating strong momentum in the + sector.\n\n**Key Market Drivers:**\n1. Enterprise adoption of autonomous intelligent + systems is accelerating\n2. Growing demand for process automation and efficiency + gains\n3. Advances in large language models enabling more sophisticated agent + capabilities\n4. Increased investment in AI infrastructure by major corporations\n\n**Industry + Landscape:**\nThe market features both established technology giants and emerging + specialized players. Major contributors include cloud providers, enterprise + software companies, and AI-first startups developing agent frameworks and + platforms.\n\n**Enterprise Adoption Patterns:**\nOrganizations are increasingly + deploying AI agents for:\n- Customer service automation\n- Internal process + optimization\n- Data analysis and decision support\n- Content generation and + management\n\n**Future Outlook:**\nThe economic potential of generative AI + and autonomous agents suggests continued strong growth trajectory. McKinsey''s + analysis indicates significant productivity gains are driving widespread adoption + across industries.\n\n**Challenges and Considerations:**\n- Integration complexity + with existing systems\n- Data privacy and security concerns\n- Need for specialized + talent and training\n- Regulatory compliance requirements\n\nThe AI agent + market represents a transformational shift toward autonomous business processes, + with substantial growth potential as organizations seek competitive advantages + through intelligent automation."}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":1852,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":424,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 19:00:07 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '8549' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","name":"web_search","input":{"query":"AI + agent market trends 2024 industry analysis"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_014To8zMjudmbenNj6ZxZ5Eb","content":"Search + results for ''AI agent market trends 2024 industry analysis'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01LPFvMaBVKRamhKoovTXXpY","name":"web_search","input":{"query":"AI + agent market size growth forecast 2024 enterprise adoption"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01LPFvMaBVKRamhKoovTXXpY","content":"Search + results for ''AI agent market size growth forecast 2024 enterprise adoption'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","name":"web_search","input":{"query":"\"AI + agents\" market report 2024 autonomous intelligent systems"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01DmDXQS9Bd4R59N4mr2D56o","content":"Search + results for ''\"AI agents\" market report 2024 autonomous intelligent systems'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","name":"web_search","input":{"query":"Gartner + McKinsey Deloitte AI agent market report 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01C9UiLvHqYhWXTDaDFLJoNN","content":"Search + results for ''Gartner McKinsey Deloitte AI agent market report 2024'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","name":"web_search","input":{"query":"site:gartner.com + OR site:mckinsey.com OR site:forrester.com AI agents market 2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01PX1GBKhWXVTPNd69DVvwAG","content":"Search + results for ''site:gartner.com OR site:mckinsey.com OR site:forrester.com AI + agents market 2024'': Found 3 relevant articles about the topic including market + analysis, competitor data, and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","name":"web_search","input":{"query":"AI + agent market research report industry analysis URL"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_015dw1Swb4oRfN1gL1K9bkTm","content":"Search + results for ''AI agent market research report industry analysis URL'': Found + 3 relevant articles about the topic including market analysis, competitor data, + and industry trends."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01QRY1ogb95zfGesVjxC2JDR","name":"read_website","input":{"url":"https://www.gartner.com/en/newsroom/press-releases/2024-03-18-gartner-forecasts-worldwide-spending-on-ai-software-to-grow-22-percent-in-2024"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01QRY1ogb95zfGesVjxC2JDR","content":"Content + from https://www.gartner.com/en/newsroom/press-releases/2024-03-18-gartner-forecasts-worldwide-spending-on-ai-software-to-grow-22-percent-in-2024: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01GsC9SQeiwRhRS2W5dgc1BB","name":"read_website","input":{"url":"https://www.mckinsey.com/capabilities/mckinsey-digital/our-insights/the-economic-potential-of-generative-ai-the-next-productivity-frontier"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01GsC9SQeiwRhRS2W5dgc1BB","content":"Content + from https://www.mckinsey.com/capabilities/mckinsey-digital/our-insights/the-economic-potential-of-generative-ai-the-next-productivity-frontier: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01K2UjYMDgyRwP15rmRMhCbE","name":"generate_report","input":{"title":"AI + Agent Market Analysis 2024: Current State and Trends","findings":"**Market Size + and Growth:**\nThe AI agent market has reached approximately $50 billion in + 2024, demonstrating robust expansion with a year-over-year growth rate of 15%. + According to Gartner''s latest forecasts, worldwide spending on AI software + is expected to grow 22% in 2024, indicating strong momentum in the sector.\n\n**Key + Market Drivers:**\n1. Enterprise adoption of autonomous intelligent systems + is accelerating\n2. Growing demand for process automation and efficiency gains\n3. + Advances in large language models enabling more sophisticated agent capabilities\n4. + Increased investment in AI infrastructure by major corporations\n\n**Industry + Landscape:**\nThe market features both established technology giants and emerging + specialized players. Major contributors include cloud providers, enterprise + software companies, and AI-first startups developing agent frameworks and platforms.\n\n**Enterprise + Adoption Patterns:**\nOrganizations are increasingly deploying AI agents for:\n- + Customer service automation\n- Internal process optimization\n- Data analysis + and decision support\n- Content generation and management\n\n**Future Outlook:**\nThe + economic potential of generative AI and autonomous agents suggests continued + strong growth trajectory. McKinsey''s analysis indicates significant productivity + gains are driving widespread adoption across industries.\n\n**Challenges and + Considerations:**\n- Integration complexity with existing systems\n- Data privacy + and security concerns\n- Need for specialized talent and training\n- Regulatory + compliance requirements\n\nThe AI agent market represents a transformational + shift toward autonomous business processes, with substantial growth potential + as organizations seek competitive advantages through intelligent automation."}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01K2UjYMDgyRwP15rmRMhCbE","content":"# + AI Agent Market Analysis 2024: Current State and Trends\n\n## Executive Summary\n**Market + Size and Growth:**\nThe AI agent market has reached approximately $50 billion + in 2024, demonstrating robust expansion with a year-over-year growth rate of + 15%. According to Gartner''s latest forecasts, worldwide spending on AI software + is expected to grow 22% in 2024, indicating strong momentum in the sector.\n\n**Key + Market Drivers:**\n1. Enterprise adoption of autonomous intelligent systems + is accelerating\n2. Growing demand for process automation and efficiency gains\n3. + Advances in large language models enabling more sophisticated agent capabilities\n4. + Increased investment in AI infrastructure by major corporations\n\n**Industry + Landscape:**\nThe market features both established technology giants and emerging + specialized players. Major contributors include cloud providers, enterprise + software companies, and AI-first startups developing agent frameworks and platforms.\n\n**Enterprise + Adoption Patterns:**\nOrganizations are increasingly deploying AI agents for:\n- + Customer service automation\n- Internal process optimization\n- Data analysis + and decision support\n- Content generation and management\n\n**Future Outlook:**\nThe + economic potential of generative AI and autonomous agents suggests continued + strong growth trajectory. McKinsey''s analysis indicates significant productivity + gains are driving widespread adoption across industries.\n\n**Challenges and + Considerations:**\n- Integration complexity with existing systems\n- Data privacy + and security concerns\n- Need for specialized talent and training\n- Regulatory + compliance requirements\n\nThe AI agent market represents a transformational + shift toward autonomous business processes, with substantial growth potential + as organizations seek competitive advantages through intelligent automation.\n\n## + Conclusion\nBased on the analysis, the market shows strong growth potential."}]}],"model":"claude-sonnet-4-20250514","stop_sequences":["\nObservation:"],"stream":false,"system":"You + are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports","tools":[{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","input_schema":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}},{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","input_schema":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}},{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","input_schema":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '10108' + content-type: + - application/json + host: + - api.anthropic.com + x-api-key: + - X-API-KEY-XXX + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 0.73.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: '{"model":"claude-sonnet-4-20250514","id":"msg_01PnisDjuxagjCAybvcz8wpY","type":"message","role":"assistant","content":[{"type":"text","text":"I''ve + completed a comprehensive research analysis of the AI agent market as requested. + Here''s what I accomplished:\n\n## Research Process:\n\n**Step 1: Search Research** + - I conducted multiple targeted searches to gather current information about + AI agents, market trends, growth forecasts, and enterprise adoption patterns.\n\n**Step + 2: Industry Source Analysis** - I accessed detailed content from authoritative + sources including Gartner and McKinsey reports on AI market forecasts and + the economic potential of generative AI.\n\n**Step 3: Report Generation** + - I synthesized the findings into a structured report covering market size, + growth trends, key drivers, adoption patterns, and future outlook.\n\n## Key + Findings Summary:\n\n- **Market Size**: The AI agent market has reached approximately + $50 billion in 2024\n- **Growth Rate**: 15% year-over-year growth with AI + software spending projected to grow 22% in 2024\n- **Primary Drivers**: Enterprise + automation needs, LLM advances, and productivity gains\n- **Adoption Areas**: + Customer service, process optimization, data analysis, and content management\n- + **Outlook**: Strong continued growth expected due to competitive advantages + from intelligent automation\n\nThe research indicates that AI agents represent + a significant and rapidly expanding market segment, driven by enterprise demand + for autonomous intelligent systems that can deliver measurable productivity + improvements."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":2610,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":287,"service_tier":"standard"}}' + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Security-Policy: + - CSP-FILTERED + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 19:00:13 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - ANTHROPIC-ORGANIZATION-ID-XXX + anthropic-ratelimit-input-tokens-limit: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-input-tokens-remaining: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-input-tokens-reset: + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX + anthropic-ratelimit-output-tokens-limit: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX + anthropic-ratelimit-output-tokens-remaining: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX + anthropic-ratelimit-output-tokens-reset: + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX + anthropic-ratelimit-tokens-limit: + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX + anthropic-ratelimit-tokens-remaining: + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX + anthropic-ratelimit-tokens-reset: + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX + cf-cache-status: + - DYNAMIC + request-id: + - REQUEST-ID-XXX + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '6513' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/utilities/TestAzureStructuredPlanning.test_azure_research_workflow_generates_steps.yaml b/lib/crewai/tests/cassettes/utilities/TestAzureStructuredPlanning.test_azure_research_workflow_generates_steps.yaml new file mode 100644 index 000000000..43c383b65 --- /dev/null +++ b/lib/crewai/tests/cassettes/utilities/TestAzureStructuredPlanning.test_azure_research_workflow_generates_steps.yaml @@ -0,0 +1,548 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are a strategic planning + assistant. Create minimal, effective execution plans. Prefer fewer steps over + more."}, {"role": "user", "content": "Create a focused execution plan for the + following task:\n\n## Task\nResearch the current state of the AI agent market:\n1. + Search for recent information about AI agents and their market trends\n2. Read + detailed content from a relevant industry source\n3. Generate a brief report + summarizing the key findings\n\nUse the available tools for each step.\n\n## + Expected Output\nComplete the task successfully\n\n## Available Tools\nweb_search, + read_website, generate_report\n\n## Instructions\nCreate ONLY the essential + steps needed to complete this task. Use the MINIMUM number of steps required + - do NOT pad your plan with unnecessary steps. Most tasks need only 2-5 steps.\n\nFor + each step:\n- State the specific action to take\n- Specify which tool to use + (if any)\n- Note dependencies on previous steps if this step requires their + output\n- If a step involves multiple items (e.g., research 3 competitors), + note this explicitly\n\nDo NOT include:\n- Setup or preparation steps that are + obvious\n- Verification steps unless critical\n- Documentation or cleanup steps + unless explicitly required\n- Generic steps like \"review results\" or \"finalize + output\"\n\nAfter your plan, state:\n- \"READY: I am ready to execute the task.\" + if the plan is complete\n- \"NOT READY: I need to refine my plan because [reason].\" + if you need more thinking"}], "stream": false, "stop": ["\nObservation:"], "tool_choice": + "auto", "tools": [{"function": {"name": "create_reasoning_plan", "description": + "Create or refine a reasoning plan for a task with structured steps", "parameters": + {"type": "object", "properties": {"plan": {"type": "string", "description": + "A brief summary of the overall plan."}, "steps": {"type": "array", "description": + "List of discrete steps to execute the plan", "items": {"type": "object", "properties": + {"step_number": {"type": "integer", "description": "Step number (1-based)"}, + "description": {"type": "string", "description": "What to do in this step"}, + "tool_to_use": {"type": ["string", "null"], "description": "Tool to use for + this step, or null if no tool needed"}, "depends_on": {"type": "array", "items": + {"type": "integer"}, "description": "Step numbers this step depends on (empty + array if none)"}}, "required": ["step_number", "description", "tool_to_use", + "depends_on"], "additionalProperties": false}}, "ready": {"type": "boolean", + "description": "Whether the agent is ready to execute the task."}}, "required": + ["plan", "steps", "ready"], "additionalProperties": false}}, "type": "function"}]}' + headers: + Accept: + - application/json + Connection: + - keep-alive + Content-Length: + - '2711' + Content-Type: + - application/json + User-Agent: + - X-USER-AGENT-XXX + accept-encoding: + - ACCEPT-ENCODING-XXX + api-key: + - X-API-KEY-XXX + authorization: + - AUTHORIZATION-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + method: POST + uri: https://fake-azure-endpoint.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview + response: + body: + string: '{"choices":[{"content_filter_results":{},"finish_reason":"tool_calls","index":0,"logprobs":null,"message":{"annotations":[],"content":null,"refusal":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\"plan\":\"Research + the current state of the AI agent market and summarize the key findings.\",\"steps\":[{\"step_number\":1,\"description\":\"Search + for recent information about AI agents and their market trends using web_search.\",\"tool_to_use\":\"web_search\",\"depends_on\":[]},{\"step_number\":2,\"description\":\"Read + detailed content from a relevant industry source using read_website, gathering + insights on trends and competitive analysis.\",\"tool_to_use\":\"read_website\",\"depends_on\":[1]},{\"step_number\":3,\"description\":\"Using + the knowledge from steps 1 and 2, generate a brief report summarizing the + AI agent market findings.\",\"tool_to_use\":\"generate_report\",\"depends_on\":[1,2]}],\"ready\":true}","name":"create_reasoning_plan"},"id":"call_TPmou69xLfxPqApRnPwI6zYV","type":"function"}]}}],"created":1770145131,"id":"chatcmpl-D5Ftr1QP6lTPXIemws2EtuKaWeSxt","model":"gpt-4o-2024-11-20","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_b54fe76834","usage":{"completion_tokens":157,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":480,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":637}} + + ' + headers: + Content-Length: + - '1762' + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:58:54 GMT + Strict-Transport-Security: + - STS-XXX + apim-request-id: + - APIM-REQUEST-ID-XXX + azureml-model-session: + - AZUREML-MODEL-SESSION-XXX + x-accel-buffering: + - 'no' + x-content-type-options: + - X-CONTENT-TYPE-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + x-ms-deployment-name: + - gpt-4o + x-ms-rai-invoked: + - 'true' + x-ms-region: + - X-MS-REGION-XXX + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Research Analyst. + An experienced analyst skilled at gathering information and synthesizing findings + into actionable insights.\nYour personal goal is: Conduct thorough research + and produce insightful reports"}, {"role": "user", "content": "\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."}], "stream": false, "stop": ["\nObservation:"], + "tool_choice": "auto", "tools": [{"function": {"name": "web_search", "description": + "Search the web for information on a given topic.\n\nArgs:\n query: The search + query to look up.\n\nReturns:\n Search results as a string.", "parameters": + {"properties": {"query": {"title": "Query", "type": "string"}}, "required": + ["query"], "type": "object", "additionalProperties": false}}, "type": "function"}, + {"function": {"name": "read_website", "description": "Read and extract content + from a website URL.\n\nArgs:\n url: The URL of the website to read.\n\nReturns:\n The + extracted content from the website.", "parameters": {"properties": {"url": {"title": + "Url", "type": "string"}}, "required": ["url"], "type": "object", "additionalProperties": + false}}, "type": "function"}, {"function": {"name": "generate_report", "description": + "Generate a structured report based on research findings.\n\nArgs:\n title: + The title of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.", "parameters": {"properties": {"title": {"title": + "Title", "type": "string"}, "findings": {"title": "Findings", "type": "string"}}, + "required": ["title", "findings"], "type": "object", "additionalProperties": + false}}, "type": "function"}]}' + headers: + Accept: + - application/json + Connection: + - keep-alive + Content-Length: + - '1912' + Content-Type: + - application/json + User-Agent: + - X-USER-AGENT-XXX + accept-encoding: + - ACCEPT-ENCODING-XXX + api-key: + - X-API-KEY-XXX + authorization: + - AUTHORIZATION-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + method: POST + uri: https://fake-azure-endpoint.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview + response: + body: + string: '{"choices":[{"content_filter_results":{},"finish_reason":"tool_calls","index":0,"logprobs":null,"message":{"annotations":[],"content":null,"refusal":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\"query\":\"current + state of AI agent market 2023\"}","name":"web_search"},"id":"call_6RDgkQSr8S7luEHqqOaI734w","type":"function"}]}}],"created":1770145136,"id":"chatcmpl-D5FtwVvV3KE10L2JIOd7n8Ph1Iu3Q","model":"gpt-4o-2024-11-20","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_b54fe76834","usage":{"completion_tokens":23,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":267,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":290}} + + ' + headers: + Content-Length: + - '1079' + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:58:55 GMT + Strict-Transport-Security: + - STS-XXX + apim-request-id: + - APIM-REQUEST-ID-XXX + azureml-model-session: + - AZUREML-MODEL-SESSION-XXX + x-accel-buffering: + - 'no' + x-content-type-options: + - X-CONTENT-TYPE-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + x-ms-deployment-name: + - gpt-4o + x-ms-rai-invoked: + - 'true' + x-ms-region: + - X-MS-REGION-XXX + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Research Analyst. + An experienced analyst skilled at gathering information and synthesizing findings + into actionable insights.\nYour personal goal is: Conduct thorough research + and produce insightful reports"}, {"role": "user", "content": "\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."}, {"role": "assistant", "content": "", "tool_calls": + [{"id": "call_6RDgkQSr8S7luEHqqOaI734w", "type": "function", "function": {"name": + "web_search", "arguments": "{\"query\":\"current state of AI agent market 2023\"}"}}]}, + {"role": "tool", "tool_call_id": "call_6RDgkQSr8S7luEHqqOaI734w", "content": + "Search results for ''current state of AI agent market 2023'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}], "stream": false, "stop": ["\nObservation:"], "tool_choice": "auto", + "tools": [{"function": {"name": "web_search", "description": "Search the web + for information on a given topic.\n\nArgs:\n query: The search query to look + up.\n\nReturns:\n Search results as a string.", "parameters": {"properties": + {"query": {"title": "Query", "type": "string"}}, "required": ["query"], "type": + "object", "additionalProperties": false}}, "type": "function"}, {"function": + {"name": "read_website", "description": "Read and extract content from a website + URL.\n\nArgs:\n url: The URL of the website to read.\n\nReturns:\n The + extracted content from the website.", "parameters": {"properties": {"url": {"title": + "Url", "type": "string"}}, "required": ["url"], "type": "object", "additionalProperties": + false}}, "type": "function"}, {"function": {"name": "generate_report", "description": + "Generate a structured report based on research findings.\n\nArgs:\n title: + The title of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.", "parameters": {"properties": {"title": {"title": + "Title", "type": "string"}, "findings": {"title": "Findings", "type": "string"}}, + "required": ["title", "findings"], "type": "object", "additionalProperties": + false}}, "type": "function"}]}' + headers: + Accept: + - application/json + Connection: + - keep-alive + Content-Length: + - '2381' + Content-Type: + - application/json + User-Agent: + - X-USER-AGENT-XXX + accept-encoding: + - ACCEPT-ENCODING-XXX + api-key: + - X-API-KEY-XXX + authorization: + - AUTHORIZATION-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + method: POST + uri: https://fake-azure-endpoint.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview + response: + body: + string: '{"choices":[{"content_filter_results":{},"finish_reason":"tool_calls","index":0,"logprobs":null,"message":{"annotations":[],"content":null,"refusal":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\"url\": + \"https://example.com/article1\"}","name":"read_website"},"id":"call_ie6tNHSbW9TWIqoXD9CN3MNZ","type":"function"},{"function":{"arguments":"{\"url\": + \"https://example.com/article2\"}","name":"read_website"},"id":"call_qxn4V4mMMpOnYSAwVuwarFkB","type":"function"},{"function":{"arguments":"{\"url\": + \"https://example.com/article3\"}","name":"read_website"},"id":"call_7ElzUIHHJvuciFWj6eIF5RhF","type":"function"}]}}],"created":1770145137,"id":"chatcmpl-D5Ftxnr2VyEYZd6zSpTJavdxSoE18","model":"gpt-4o-2024-11-20","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_b54fe76834","usage":{"completion_tokens":77,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":330,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":407}} + + ' + headers: + Content-Length: + - '1371' + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:58:57 GMT + Strict-Transport-Security: + - STS-XXX + apim-request-id: + - APIM-REQUEST-ID-XXX + azureml-model-session: + - AZUREML-MODEL-SESSION-XXX + x-accel-buffering: + - 'no' + x-content-type-options: + - X-CONTENT-TYPE-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + x-ms-deployment-name: + - gpt-4o + x-ms-rai-invoked: + - 'true' + x-ms-region: + - X-MS-REGION-XXX + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Research Analyst. + An experienced analyst skilled at gathering information and synthesizing findings + into actionable insights.\nYour personal goal is: Conduct thorough research + and produce insightful reports"}, {"role": "user", "content": "\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."}, {"role": "assistant", "content": "", "tool_calls": + [{"id": "call_6RDgkQSr8S7luEHqqOaI734w", "type": "function", "function": {"name": + "web_search", "arguments": "{\"query\":\"current state of AI agent market 2023\"}"}}]}, + {"role": "tool", "tool_call_id": "call_6RDgkQSr8S7luEHqqOaI734w", "content": + "Search results for ''current state of AI agent market 2023'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}, {"role": "assistant", "content": "", "tool_calls": [{"id": "call_ie6tNHSbW9TWIqoXD9CN3MNZ", + "type": "function", "function": {"name": "read_website", "arguments": "{\"url\": + \"https://example.com/article1\"}"}}, {"id": "call_qxn4V4mMMpOnYSAwVuwarFkB", + "type": "function", "function": {"name": "read_website", "arguments": "{\"url\": + \"https://example.com/article2\"}"}}, {"id": "call_7ElzUIHHJvuciFWj6eIF5RhF", + "type": "function", "function": {"name": "read_website", "arguments": "{\"url\": + \"https://example.com/article3\"}"}}]}, {"role": "tool", "tool_call_id": "call_ie6tNHSbW9TWIqoXD9CN3MNZ", + "content": "Content from https://example.com/article1: This article discusses + key insights about the topic including market size ($50B), growth rate (15% + YoY), and major players in the industry."}, {"role": "tool", "tool_call_id": + "call_qxn4V4mMMpOnYSAwVuwarFkB", "content": "Content from https://example.com/article2: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}, {"role": "tool", + "tool_call_id": "call_7ElzUIHHJvuciFWj6eIF5RhF", "content": "Content from https://example.com/article3: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}], "stream": false, + "stop": ["\nObservation:"], "tool_choice": "auto", "tools": [{"function": {"name": + "web_search", "description": "Search the web for information on a given topic.\n\nArgs:\n query: + The search query to look up.\n\nReturns:\n Search results as a string.", + "parameters": {"properties": {"query": {"title": "Query", "type": "string"}}, + "required": ["query"], "type": "object", "additionalProperties": false}}, "type": + "function"}, {"function": {"name": "read_website", "description": "Read and + extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.", "parameters": + {"properties": {"url": {"title": "Url", "type": "string"}}, "required": ["url"], + "type": "object", "additionalProperties": false}}, "type": "function"}, {"function": + {"name": "generate_report", "description": "Generate a structured report based + on research findings.\n\nArgs:\n title: The title of the report.\n findings: + The research findings to include.\n\nReturns:\n A formatted report string.", + "parameters": {"properties": {"title": {"title": "Title", "type": "string"}, + "findings": {"title": "Findings", "type": "string"}}, "required": ["title", + "findings"], "type": "object", "additionalProperties": false}}, "type": "function"}]}' + headers: + Accept: + - application/json + Connection: + - keep-alive + Content-Length: + - '3704' + Content-Type: + - application/json + User-Agent: + - X-USER-AGENT-XXX + accept-encoding: + - ACCEPT-ENCODING-XXX + api-key: + - X-API-KEY-XXX + authorization: + - AUTHORIZATION-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + method: POST + uri: https://fake-azure-endpoint.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview + response: + body: + string: '{"choices":[{"content_filter_results":{},"finish_reason":"tool_calls","index":0,"logprobs":null,"message":{"annotations":[],"content":null,"refusal":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\"title\":\"Current + State of the AI Agent Market\",\"findings\":\"The AI agent market in 2023 + is valued at $50 billion, with a growth rate of 15% YoY. Major players in + the market have been identified as contributing to the expansion of industry + capabilities. Trends point to increasing adoption across industries such as + healthcare and finance, where automation and intelligence are becoming central + to operations. Emerging competitive forces are influencing pricing and technological + advancements in AI agents. Existing and new entrants focus on innovation to + differentiate their offerings and capture market share.\"}","name":"generate_report"},"id":"call_7eE0bJbpvO6YYQNs87iTifQ3","type":"function"}]}}],"created":1770145138,"id":"chatcmpl-D5Ftys4PE1B1dkTuv6EPOQyqk0xuv","model":"gpt-4o-2024-11-20","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_b54fe76834","usage":{"completion_tokens":120,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":537,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":657}} + + ' + headers: + Content-Length: + - '1652' + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:58:59 GMT + Strict-Transport-Security: + - STS-XXX + apim-request-id: + - APIM-REQUEST-ID-XXX + azureml-model-session: + - AZUREML-MODEL-SESSION-XXX + x-accel-buffering: + - 'no' + x-content-type-options: + - X-CONTENT-TYPE-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + x-ms-deployment-name: + - gpt-4o + x-ms-rai-invoked: + - 'true' + x-ms-region: + - X-MS-REGION-XXX + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Research Analyst. + An experienced analyst skilled at gathering information and synthesizing findings + into actionable insights.\nYour personal goal is: Conduct thorough research + and produce insightful reports"}, {"role": "user", "content": "\nCurrent Task: + Research the current state of the AI agent market:\n1. Search for recent information + about AI agents and their market trends\n2. Read detailed content from a relevant + industry source\n3. Generate a brief report summarizing the key findings\n\nUse + the available tools for each step."}, {"role": "assistant", "content": "", "tool_calls": + [{"id": "call_6RDgkQSr8S7luEHqqOaI734w", "type": "function", "function": {"name": + "web_search", "arguments": "{\"query\":\"current state of AI agent market 2023\"}"}}]}, + {"role": "tool", "tool_call_id": "call_6RDgkQSr8S7luEHqqOaI734w", "content": + "Search results for ''current state of AI agent market 2023'': Found 3 relevant + articles about the topic including market analysis, competitor data, and industry + trends."}, {"role": "assistant", "content": "", "tool_calls": [{"id": "call_ie6tNHSbW9TWIqoXD9CN3MNZ", + "type": "function", "function": {"name": "read_website", "arguments": "{\"url\": + \"https://example.com/article1\"}"}}, {"id": "call_qxn4V4mMMpOnYSAwVuwarFkB", + "type": "function", "function": {"name": "read_website", "arguments": "{\"url\": + \"https://example.com/article2\"}"}}, {"id": "call_7ElzUIHHJvuciFWj6eIF5RhF", + "type": "function", "function": {"name": "read_website", "arguments": "{\"url\": + \"https://example.com/article3\"}"}}]}, {"role": "tool", "tool_call_id": "call_ie6tNHSbW9TWIqoXD9CN3MNZ", + "content": "Content from https://example.com/article1: This article discusses + key insights about the topic including market size ($50B), growth rate (15% + YoY), and major players in the industry."}, {"role": "tool", "tool_call_id": + "call_qxn4V4mMMpOnYSAwVuwarFkB", "content": "Content from https://example.com/article2: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}, {"role": "tool", + "tool_call_id": "call_7ElzUIHHJvuciFWj6eIF5RhF", "content": "Content from https://example.com/article3: + This article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}, {"role": "assistant", + "content": "", "tool_calls": [{"id": "call_7eE0bJbpvO6YYQNs87iTifQ3", "type": + "function", "function": {"name": "generate_report", "arguments": "{\"title\":\"Current + State of the AI Agent Market\",\"findings\":\"The AI agent market in 2023 is + valued at $50 billion, with a growth rate of 15% YoY. Major players in the market + have been identified as contributing to the expansion of industry capabilities. + Trends point to increasing adoption across industries such as healthcare and + finance, where automation and intelligence are becoming central to operations. + Emerging competitive forces are influencing pricing and technological advancements + in AI agents. Existing and new entrants focus on innovation to differentiate + their offerings and capture market share.\"}"}}]}, {"role": "tool", "tool_call_id": + "call_7eE0bJbpvO6YYQNs87iTifQ3", "content": "# Current State of the AI Agent + Market\n\n## Executive Summary\nThe AI agent market in 2023 is valued at $50 + billion, with a growth rate of 15% YoY. Major players in the market have been + identified as contributing to the expansion of industry capabilities. Trends + point to increasing adoption across industries such as healthcare and finance, + where automation and intelligence are becoming central to operations. Emerging + competitive forces are influencing pricing and technological advancements in + AI agents. Existing and new entrants focus on innovation to differentiate their + offerings and capture market share.\n\n## Conclusion\nBased on the analysis, + the market shows strong growth potential."}], "stream": false, "stop": ["\nObservation:"], + "tool_choice": "auto", "tools": [{"function": {"name": "web_search", "description": + "Search the web for information on a given topic.\n\nArgs:\n query: The search + query to look up.\n\nReturns:\n Search results as a string.", "parameters": + {"properties": {"query": {"title": "Query", "type": "string"}}, "required": + ["query"], "type": "object", "additionalProperties": false}}, "type": "function"}, + {"function": {"name": "read_website", "description": "Read and extract content + from a website URL.\n\nArgs:\n url: The URL of the website to read.\n\nReturns:\n The + extracted content from the website.", "parameters": {"properties": {"url": {"title": + "Url", "type": "string"}}, "required": ["url"], "type": "object", "additionalProperties": + false}}, "type": "function"}, {"function": {"name": "generate_report", "description": + "Generate a structured report based on research findings.\n\nArgs:\n title: + The title of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.", "parameters": {"properties": {"title": {"title": + "Title", "type": "string"}, "findings": {"title": "Findings", "type": "string"}}, + "required": ["title", "findings"], "type": "object", "additionalProperties": + false}}, "type": "function"}]}' + headers: + Accept: + - application/json + Connection: + - keep-alive + Content-Length: + - '5276' + Content-Type: + - application/json + User-Agent: + - X-USER-AGENT-XXX + accept-encoding: + - ACCEPT-ENCODING-XXX + api-key: + - X-API-KEY-XXX + authorization: + - AUTHORIZATION-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + method: POST + uri: https://fake-azure-endpoint.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-12-01-preview + response: + body: + string: '{"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"protected_material_code":{"detected":false,"filtered":false},"protected_material_text":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"finish_reason":"stop","index":0,"logprobs":null,"message":{"annotations":[],"content":"The + report detailing the current state of the AI agent market has been generated + successfully. It highlights the market''s $50 billion valuation in 2023, with + a consistent annual growth rate of 15%. Key findings identify the role of + major players, impactful trends like sector adoption (healthcare and finance), + and the competitive dynamic driving innovation and technological advancements.","refusal":null,"role":"assistant"}}],"created":1770145141,"id":"chatcmpl-D5Fu18JFkDPGsKf10eiS2e2uI04MU","model":"gpt-4o-2024-11-20","object":"chat.completion","prompt_filter_results":[{"prompt_index":0,"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"jailbreak":{"detected":false,"filtered":false},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}}}],"system_fingerprint":"fp_b54fe76834","usage":{"completion_tokens":72,"completion_tokens_details":{"accepted_prediction_tokens":0,"audio_tokens":0,"reasoning_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":787,"prompt_tokens_details":{"audio_tokens":0,"cached_tokens":0},"total_tokens":859}} + + ' + headers: + Content-Length: + - '1597' + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:01 GMT + Strict-Transport-Security: + - STS-XXX + apim-request-id: + - APIM-REQUEST-ID-XXX + azureml-model-session: + - AZUREML-MODEL-SESSION-XXX + x-accel-buffering: + - 'no' + x-content-type-options: + - X-CONTENT-TYPE-XXX + x-ms-client-request-id: + - X-MS-CLIENT-REQUEST-ID-XXX + x-ms-deployment-name: + - gpt-4o + x-ms-rai-invoked: + - 'true' + x-ms-region: + - X-MS-REGION-XXX + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/utilities/TestGeminiStructuredPlanning.test_gemini_research_workflow_generates_steps.yaml b/lib/crewai/tests/cassettes/utilities/TestGeminiStructuredPlanning.test_gemini_research_workflow_generates_steps.yaml new file mode 100644 index 000000000..e760c5128 --- /dev/null +++ b/lib/crewai/tests/cassettes/utilities/TestGeminiStructuredPlanning.test_gemini_research_workflow_generates_steps.yaml @@ -0,0 +1,613 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "Create a focused execution plan for + the following task:\n\n## Task\nResearch the current state of the AI agent market:\n1. + Search for recent information about AI agents and their market trends\n2. Read + detailed content from a relevant industry source\n3. Generate a brief report + summarizing the key findings\n\nUse the available tools for each step.\n\n## + Expected Output\nComplete the task successfully\n\n## Available Tools\nweb_search, + read_website, generate_report\n\n## Instructions\nCreate ONLY the essential + steps needed to complete this task. Use the MINIMUM number of steps required + - do NOT pad your plan with unnecessary steps. Most tasks need only 2-5 steps.\n\nFor + each step:\n- State the specific action to take\n- Specify which tool to use + (if any)\n- Note dependencies on previous steps if this step requires their + output\n- If a step involves multiple items (e.g., research 3 competitors), + note this explicitly\n\nDo NOT include:\n- Setup or preparation steps that are + obvious\n- Verification steps unless critical\n- Documentation or cleanup steps + unless explicitly required\n- Generic steps like \"review results\" or \"finalize + output\"\n\nAfter your plan, state:\n- \"READY: I am ready to execute the task.\" + if the plan is complete\n- \"NOT READY: I need to refine my plan because [reason].\" + if you need more thinking"}], "role": "user"}], "systemInstruction": {"parts": + [{"text": "You are a strategic planning assistant. Create minimal, effective + execution plans. Prefer fewer steps over more."}], "role": "user"}, "tools": + [{"functionDeclarations": [{"description": "Create or refine a reasoning plan + for a task with structured steps", "name": "create_reasoning_plan", "parameters_json_schema": + {"type": "object", "properties": {"plan": {"type": "string", "description": + "A brief summary of the overall plan."}, "steps": {"type": "array", "description": + "List of discrete steps to execute the plan", "items": {"type": "object", "properties": + {"step_number": {"type": "integer", "description": "Step number (1-based)"}, + "description": {"type": "string", "description": "What to do in this step"}, + "tool_to_use": {"type": ["string", "null"], "description": "Tool to use for + this step, or null if no tool needed"}, "depends_on": {"type": "array", "items": + {"type": "integer"}, "description": "Step numbers this step depends on (empty + array if none)"}}, "required": ["step_number", "description", "tool_to_use", + "depends_on"], "additionalProperties": false}}, "ready": {"type": "boolean", + "description": "Whether the agent is ready to execute the task."}}, "required": + ["plan", "steps", "ready"], "additionalProperties": false}}]}], "generationConfig": + {"stopSequences": ["\nObservation:"]}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + content-length: + - '2747' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + x-goog-api-client: + - google-genai-sdk/1.49.0 gl-python/3.13.3 + x-goog-api-key: + - X-GOOG-API-KEY-XXX + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + response: + body: + string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": + [\n {\n \"functionCall\": {\n \"name\": \"create_reasoning_plan\",\n + \ \"args\": {\n \"steps\": [\n {\n + \ \"step_number\": 1,\n \"tool_to_use\": + \"web_search\",\n \"depends_on\": [],\n \"description\": + \"Search for recent information on AI agent market trends.\"\n },\n + \ {\n \"description\": \"Read detailed + content from a relevant industry source found in the search results.\",\n + \ \"depends_on\": [\n 1\n ],\n + \ \"step_number\": 2,\n \"tool_to_use\": + \"read_website\"\n },\n {\n \"depends_on\": + [\n 2\n ],\n \"step_number\": + 3,\n \"tool_to_use\": \"generate_report\",\n \"description\": + \"Generate a brief report summarizing the key findings from the researched + content.\"\n }\n ],\n \"plan\": + \"Research the current state of the AI agent market and generate a summary + report.\",\n \"ready\": true\n }\n },\n + \ \"thoughtSignature\": \"CsIIAXLI2nztq0xBScBM9fsnIBhVvD6CWKYYyj3khtIYTNl4oRbOYnH8LQBzET8fRrxiWqnxib7Nn/UVK29pvsHX7TacFxm0ieHb5gmb2lgqo4GpG0P2+HI4xo4vnibPl3OM+bl4M39/JOdBNBmYGUJETnzV++m8INVLIlOuRPGTXRDUOKLNfkyktsJRdNuO68OMu5WXDqrlbuppG1dsK+Y8cyvzDRoMTsYU1arFOZuyLisuhDQ7nTbdXQ8AO0Oc43MdYrSFmfeDBbsnrxDKBMPYtFVadggz4RwKIFvg3Fb9ORho2GkBUum8PNWHZCBXuAoU9FaVdDFVReduuTfZuBwy/Y6MS+8mnLqcQ1wADlg7DnzaOXmALxgfApTEL6YUvwVjVYI1dpCR5ACkFgNq6ecYYumrlZHuHE59p+XOHMhrzu9c99A0mtRvpkz1yu75dMACliSf/2fu4gCPRRLsgez7llp92g7+GzVkbwkRVlMcYQ+0l28sBsfBQd01lQR0vNNbK3JHf/chtCtrRIcgqqnUbTTqm5n8iPHFBWMdEa0L4I1WfPbiUSC9OGwwaoP+Ro6h/gPH9o96gjZQpCft1myrKtgl7dpOaSharMuDfulk2Wd5wJoZ9XR/fxVYQos8sNEIZq8O1cjOW9L74ryKMtw6hF8A4kxGLhOSgXwszNqHoLCLoX6Cz5vb2hZVkCWxq8yn5k3IrTcw/16Jdkx6oNWjHZJmJMz8vl85sUH1ZwZocWHWcUUMo33ZlsBnqPZxwnAj5CW+vQS/xCC7Kq1FciqosYyHgN43bm8JqnHL4qgBAEtDPVrFfaWBpzWWVfXQkX/EVApU/Jr3t9D3gz10CFPsV7d0lx0P7jur8u8Q9n8r2HEi320kNf1EX4YnDioX+nWmHPN203OCOHpDEcEQ89gECMk5M9Xu6EZ94rXmrZJtP5kc0k37fvMexlxIZPuUmV7RpoCTrMVqMyP93eIq9FY/9WsqHVlydumTfEMPI1WY5ObNeHJFhyu2Y6dGB3ONQL1bQ0oboNZujX/AhnauV0A8OS2wsA3yLVk0GFZpTISU+WQ2/Gm7t8CUIgKT3BV4JkYuyNBTONQPLitpfO+TEMcuNZlodinLvkBtBA8B0W55kOAK7y21I9znnNKONo87jg9kfoZMvYlb2DrO9YovQDDhdCZjxXr5VZqbhMH4tb8t5kP/1auCv1GfzV6RVSgyNVYnruqJKEtqgNbLid1FB9EH3Qu0A92HLqdGsuC3qRm2qfyMEmz7iYJ5n/WA5BertGZ/O0SGLEBftgEjQOJhj7flGTEio3yyCHyvw/yGP6S8F3+mPvVLO6/eWMCGm7ig/mG8pjnysRTQTjDv966XhU9SYpinoxTd5mEuWdIHKMZxtBK7qeKY369njKJOP2K220Rk9/Ii+KyzNvbzyobK6oMNcwXgHjO+ssbH+blVUbLai3UblQ==\"\n + \ }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": + \"STOP\",\n \"index\": 0,\n \"finishMessage\": \"Model generated + function call(s).\"\n }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\": + 529,\n \"candidatesTokenCount\": 169,\n \"totalTokenCount\": 955,\n + \ \"promptTokensDetails\": [\n {\n \"modality\": \"TEXT\",\n + \ \"tokenCount\": 529\n }\n ],\n \"thoughtsTokenCount\": + 257\n },\n \"modelVersion\": \"gemini-2.5-flash\",\n \"responseId\": \"h0WCacqWA6WM_PUPl-niyQ0\"\n}\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Type: + - application/json; charset=UTF-8 + Date: + - Tue, 03 Feb 2026 18:59:19 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=2345 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + X-Frame-Options: + - X-FRAME-OPTIONS-XXX + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"contents": [{"parts": [{"text": "\nCurrent Task: Research the current + state of the AI agent market:\n1. Search for recent information about AI agents + and their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."}], "role": "user"}], "systemInstruction": {"parts": [{"text": + "You are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports"}], "role": "user"}, + "tools": [{"functionDeclarations": [{"description": "Search the web for information + on a given topic.\n\nArgs:\n query: The search query to look up.\n\nReturns:\n Search + results as a string.", "name": "web_search", "parameters_json_schema": {"properties": + {"query": {"title": "Query", "type": "string"}}, "required": ["query"], "type": + "object", "additionalProperties": false}}, {"description": "Read and extract + content from a website URL.\n\nArgs:\n url: The URL of the website to read.\n\nReturns:\n The + extracted content from the website.", "name": "read_website", "parameters_json_schema": + {"properties": {"url": {"title": "Url", "type": "string"}}, "required": ["url"], + "type": "object", "additionalProperties": false}}, {"description": "Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.", "name": "generate_report", "parameters_json_schema": + {"properties": {"title": {"title": "Title", "type": "string"}, "findings": {"title": + "Findings", "type": "string"}}, "required": ["title", "findings"], "type": "object", + "additionalProperties": false}}]}], "generationConfig": {"stopSequences": ["\nObservation:"]}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + content-length: + - '1904' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + x-goog-api-client: + - google-genai-sdk/1.49.0 gl-python/3.13.3 + x-goog-api-key: + - X-GOOG-API-KEY-XXX + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + response: + body: + string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": + [\n {\n \"functionCall\": {\n \"name\": \"web_search\",\n + \ \"args\": {\n \"query\": \"AI agent market trends + 2023-2024\"\n }\n },\n \"thoughtSignature\": + \"CoYEAXLI2nzmyDix3/QA+tMOiUwpDVoA5+RJoRW7kw3okJaVYCa5Usx7eBn4xowP7oXNynS4NfawCYqboufBXjHinq13UTcYg0Y74qIrza4KuctliGmf8G7S4QoS0Y3gqCHQKsxTdShQOg8wirnr8Rdu1eyrrhWE0XKk0HPA0Ssj7zUVoJBqHPqwyvkFyXkMtpcmtq9qXmZYfMFuSKRQnYLVLllL/BpOIL3w7MuofpviO85bvYk9gX0vsDjYWS6EdVEfC9k2BWGjhHaILXT9A1iwNPdDAg33SOC+BlPrGox0ghCr5qEKnBMZhUszqaUCykczFCq+xMIA3xDGNbTjicWb53sL/PXBYLsNty1giW3nKFe8+8eRpUsHUx7oQ82m4AUxKqk99mZjaLp8bHk+rERjFZErcw/pe/3190K0WGHH5ecB4amJCzZtVrQJ1oAZhb7/P1VZ57xmt1z/c1pQgjuvnV+cWE9blh5o6mNNFbFuzJDIO2k8qrFeeDwlCF8OOrxo8F+z1evg4yjZ1+9TLCVFTmZ0S0PI54FS5afb0RdPol2/ISNw7H/dtnO4z6LhT2NmlYqYZr8qfVoUD21rmI08NFs+f/6JW5+7eSQbax76SW+6A2IqqPPyF66MCpqtEzC+hpzVsCBcIQyRQWsdm+RNAs50gmqF6W3CcTPryWkeS7w9ORqxdiU=\"\n + \ }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": + \"STOP\",\n \"index\": 0,\n \"finishMessage\": \"Model generated + function call(s).\"\n }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\": + 319,\n \"candidatesTokenCount\": 28,\n \"totalTokenCount\": 461,\n \"promptTokensDetails\": + [\n {\n \"modality\": \"TEXT\",\n \"tokenCount\": 319\n + \ }\n ],\n \"thoughtsTokenCount\": 114\n },\n \"modelVersion\": + \"gemini-2.5-flash\",\n \"responseId\": \"iEWCaYyJENDn_uMP4q3N8QE\"\n}\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Type: + - application/json; charset=UTF-8 + Date: + - Tue, 03 Feb 2026 18:59:20 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=1193 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + X-Frame-Options: + - X-FRAME-OPTIONS-XXX + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"contents": [{"parts": [{"text": "\nCurrent Task: Research the current + state of the AI agent market:\n1. Search for recent information about AI agents + and their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."}], "role": "user"}, {"parts": [{"functionCall": {"args": {"query": + "AI agent market trends 2023-2024"}, "name": "web_search"}, "thoughtSignature": + "CoYEAXLI2nzmyDix3_QA-tMOiUwpDVoA5-RJoRW7kw3okJaVYCa5Usx7eBn4xowP7oXNynS4NfawCYqboufBXjHinq13UTcYg0Y74qIrza4KuctliGmf8G7S4QoS0Y3gqCHQKsxTdShQOg8wirnr8Rdu1eyrrhWE0XKk0HPA0Ssj7zUVoJBqHPqwyvkFyXkMtpcmtq9qXmZYfMFuSKRQnYLVLllL_BpOIL3w7MuofpviO85bvYk9gX0vsDjYWS6EdVEfC9k2BWGjhHaILXT9A1iwNPdDAg33SOC-BlPrGox0ghCr5qEKnBMZhUszqaUCykczFCq-xMIA3xDGNbTjicWb53sL_PXBYLsNty1giW3nKFe8-8eRpUsHUx7oQ82m4AUxKqk99mZjaLp8bHk-rERjFZErcw_pe_3190K0WGHH5ecB4amJCzZtVrQJ1oAZhb7_P1VZ57xmt1z_c1pQgjuvnV-cWE9blh5o6mNNFbFuzJDIO2k8qrFeeDwlCF8OOrxo8F-z1evg4yjZ1-9TLCVFTmZ0S0PI54FS5afb0RdPol2_ISNw7H_dtnO4z6LhT2NmlYqYZr8qfVoUD21rmI08NFs-f_6JW5-7eSQbax76SW-6A2IqqPPyF66MCpqtEzC-hpzVsCBcIQyRQWsdm-RNAs50gmqF6W3CcTPryWkeS7w9ORqxdiU="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "web_search", "response": + {"result": "Search results for ''AI agent market trends 2023-2024'': Found 3 + relevant articles about the topic including market analysis, competitor data, + and industry trends."}}}], "role": "user"}], "systemInstruction": {"parts": + [{"text": "You are Research Analyst. An experienced analyst skilled at gathering + information and synthesizing findings into actionable insights.\nYour personal + goal is: Conduct thorough research and produce insightful reports"}], "role": + "user"}, "tools": [{"functionDeclarations": [{"description": "Search the web + for information on a given topic.\n\nArgs:\n query: The search query to look + up.\n\nReturns:\n Search results as a string.", "name": "web_search", "parameters_json_schema": + {"properties": {"query": {"title": "Query", "type": "string"}}, "required": + ["query"], "type": "object", "additionalProperties": false}}, {"description": + "Read and extract content from a website URL.\n\nArgs:\n url: The URL of + the website to read.\n\nReturns:\n The extracted content from the website.", + "name": "read_website", "parameters_json_schema": {"properties": {"url": {"title": + "Url", "type": "string"}}, "required": ["url"], "type": "object", "additionalProperties": + false}}, {"description": "Generate a structured report based on research findings.\n\nArgs:\n title: + The title of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.", "name": "generate_report", "parameters_json_schema": + {"properties": {"title": {"title": "Title", "type": "string"}, "findings": {"title": + "Findings", "type": "string"}}, "required": ["title", "findings"], "type": "object", + "additionalProperties": false}}]}], "generationConfig": {"stopSequences": ["\nObservation:"]}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + content-length: + - '3015' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + x-goog-api-client: + - google-genai-sdk/1.49.0 gl-python/3.13.3 + x-goog-api-key: + - X-GOOG-API-KEY-XXX + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + response: + body: + string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": + [\n {\n \"functionCall\": {\n \"name\": \"web_search\",\n + \ \"args\": {\n \"query\": \"latest reports AI + agent market analysis\"\n }\n },\n \"thoughtSignature\": + \"CpwGAXLI2nybmVGffIN1k+T5M2HmQNlvybZRPoo/ysgNARa+9nrPdRoBZ9RC+Dee9KSk1o7O+IU9l0sWCTirQYcroEhXon+JIQUVTed/L0s//sBOR+hJnZWoaG5ucsfJQvovAQba2Wb7uViEkdySvHfRApF0atewbC+TCKZrxDAQ6Naby8nwUTauJPKlgsBsZVnlViRfIbF7pom1zvXD+d5htjMiuJr1nOuSH0EGQWC4TUiuJD23hgockzhmIpbU/bStn8PFIQNsySEzl6H5sZdlD4auwCMCD2Q+Ur05w1uLv7n8GoSZn5dkdXLR5R7dZ+kkX+xP4w841Ih2gc6rBKT5tSedN01AuJsK65NSfOXZBwakxs58WZXDQXnIQe4d2QThAX3nPdUmhvVI6sHX+ZdtQZIrhE7hRf9j/T/wvvrUao5VDv+mxXd9bcPEV2BzSXkvkAB1SbJ+5wN7Qb2j31lkiUu5uRnZOiVxL5iCS+8Z/jEl4NjpGithbcPoNpFIDOeiE/f8kf7tQJDX+YNquPbYZRJHvIfLalVQndVGNlZVN2jXT3Wwo8So3vmzIDFVjV+pj7tSRNK8hTITm6bfHS+XUZqJdm7eHCzhonyJ7/tl7LbsstPXoZU3ZN50tNpXYOK+NzgzU7iwd9SaHVXgzQRdujWgHuBmSiSd9qNHvdaNwgARVTnMj3VYpehgIuaYMzQmgM99TdC/zmzcqHa5VZSnHKHqMIVc9gjRvVwz4DUm6VLLnKnVYFClM6gofmUI6s9fThiR5EdfimaDTlRlzh6Df33jAbRA9rUTDH6uE+DjiopCvXuHjmQqK9Smyxt2vTao9H7AYIRN7yWmdVoaG0tUSL2XQ31wIW3cEyhz7ihQwFYKJnOkQ3/CiU6KV+4ldk9UY/vKWSgItVTTE6G8Di0iviiCAkmL59Uj7vnIp80+U9rDIK7WhxpAWlrDA6cGQT2LGAXQ2liXtLa31nXyfvCezhtSS8jSVm4SHaiU+INvYtpq2Q7nXPTFbjjZooyC5FePGqAH1T+sRYbR02jaa572/NuwFgBCObTfqO8G5A==\"\n + \ }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": + \"STOP\",\n \"index\": 0,\n \"finishMessage\": \"Model generated + function call(s).\"\n }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\": + 400,\n \"candidatesTokenCount\": 20,\n \"totalTokenCount\": 589,\n \"promptTokensDetails\": + [\n {\n \"modality\": \"TEXT\",\n \"tokenCount\": 400\n + \ }\n ],\n \"thoughtsTokenCount\": 169\n },\n \"modelVersion\": + \"gemini-2.5-flash\",\n \"responseId\": \"ikWCacqBG8ve_uMPj4KFyAY\"\n}\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Type: + - application/json; charset=UTF-8 + Date: + - Tue, 03 Feb 2026 18:59:22 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=2125 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + X-Frame-Options: + - X-FRAME-OPTIONS-XXX + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"contents": [{"parts": [{"text": "\nCurrent Task: Research the current + state of the AI agent market:\n1. Search for recent information about AI agents + and their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."}], "role": "user"}, {"parts": [{"functionCall": {"args": {"query": + "AI agent market trends 2023-2024"}, "name": "web_search"}, "thoughtSignature": + "CoYEAXLI2nzmyDix3_QA-tMOiUwpDVoA5-RJoRW7kw3okJaVYCa5Usx7eBn4xowP7oXNynS4NfawCYqboufBXjHinq13UTcYg0Y74qIrza4KuctliGmf8G7S4QoS0Y3gqCHQKsxTdShQOg8wirnr8Rdu1eyrrhWE0XKk0HPA0Ssj7zUVoJBqHPqwyvkFyXkMtpcmtq9qXmZYfMFuSKRQnYLVLllL_BpOIL3w7MuofpviO85bvYk9gX0vsDjYWS6EdVEfC9k2BWGjhHaILXT9A1iwNPdDAg33SOC-BlPrGox0ghCr5qEKnBMZhUszqaUCykczFCq-xMIA3xDGNbTjicWb53sL_PXBYLsNty1giW3nKFe8-8eRpUsHUx7oQ82m4AUxKqk99mZjaLp8bHk-rERjFZErcw_pe_3190K0WGHH5ecB4amJCzZtVrQJ1oAZhb7_P1VZ57xmt1z_c1pQgjuvnV-cWE9blh5o6mNNFbFuzJDIO2k8qrFeeDwlCF8OOrxo8F-z1evg4yjZ1-9TLCVFTmZ0S0PI54FS5afb0RdPol2_ISNw7H_dtnO4z6LhT2NmlYqYZr8qfVoUD21rmI08NFs-f_6JW5-7eSQbax76SW-6A2IqqPPyF66MCpqtEzC-hpzVsCBcIQyRQWsdm-RNAs50gmqF6W3CcTPryWkeS7w9ORqxdiU="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "web_search", "response": + {"result": "Search results for ''AI agent market trends 2023-2024'': Found 3 + relevant articles about the topic including market analysis, competitor data, + and industry trends."}}}], "role": "user"}, {"parts": [{"functionCall": {"args": + {"query": "latest reports AI agent market analysis"}, "name": "web_search"}, + "thoughtSignature": "CpwGAXLI2nybmVGffIN1k-T5M2HmQNlvybZRPoo_ysgNARa-9nrPdRoBZ9RC-Dee9KSk1o7O-IU9l0sWCTirQYcroEhXon-JIQUVTed_L0s__sBOR-hJnZWoaG5ucsfJQvovAQba2Wb7uViEkdySvHfRApF0atewbC-TCKZrxDAQ6Naby8nwUTauJPKlgsBsZVnlViRfIbF7pom1zvXD-d5htjMiuJr1nOuSH0EGQWC4TUiuJD23hgockzhmIpbU_bStn8PFIQNsySEzl6H5sZdlD4auwCMCD2Q-Ur05w1uLv7n8GoSZn5dkdXLR5R7dZ-kkX-xP4w841Ih2gc6rBKT5tSedN01AuJsK65NSfOXZBwakxs58WZXDQXnIQe4d2QThAX3nPdUmhvVI6sHX-ZdtQZIrhE7hRf9j_T_wvvrUao5VDv-mxXd9bcPEV2BzSXkvkAB1SbJ-5wN7Qb2j31lkiUu5uRnZOiVxL5iCS-8Z_jEl4NjpGithbcPoNpFIDOeiE_f8kf7tQJDX-YNquPbYZRJHvIfLalVQndVGNlZVN2jXT3Wwo8So3vmzIDFVjV-pj7tSRNK8hTITm6bfHS-XUZqJdm7eHCzhonyJ7_tl7LbsstPXoZU3ZN50tNpXYOK-NzgzU7iwd9SaHVXgzQRdujWgHuBmSiSd9qNHvdaNwgARVTnMj3VYpehgIuaYMzQmgM99TdC_zmzcqHa5VZSnHKHqMIVc9gjRvVwz4DUm6VLLnKnVYFClM6gofmUI6s9fThiR5EdfimaDTlRlzh6Df33jAbRA9rUTDH6uE-DjiopCvXuHjmQqK9Smyxt2vTao9H7AYIRN7yWmdVoaG0tUSL2XQ31wIW3cEyhz7ihQwFYKJnOkQ3_CiU6KV-4ldk9UY_vKWSgItVTTE6G8Di0iviiCAkmL59Uj7vnIp80-U9rDIK7WhxpAWlrDA6cGQT2LGAXQ2liXtLa31nXyfvCezhtSS8jSVm4SHaiU-INvYtpq2Q7nXPTFbjjZooyC5FePGqAH1T-sRYbR02jaa572_NuwFgBCObTfqO8G5A=="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "web_search", "response": + {"result": "Search results for ''latest reports AI agent market analysis'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}}}], "role": "user"}], "systemInstruction": {"parts": + [{"text": "You are Research Analyst. An experienced analyst skilled at gathering + information and synthesizing findings into actionable insights.\nYour personal + goal is: Conduct thorough research and produce insightful reports"}], "role": + "user"}, "tools": [{"functionDeclarations": [{"description": "Search the web + for information on a given topic.\n\nArgs:\n query: The search query to look + up.\n\nReturns:\n Search results as a string.", "name": "web_search", "parameters_json_schema": + {"properties": {"query": {"title": "Query", "type": "string"}}, "required": + ["query"], "type": "object", "additionalProperties": false}}, {"description": + "Read and extract content from a website URL.\n\nArgs:\n url: The URL of + the website to read.\n\nReturns:\n The extracted content from the website.", + "name": "read_website", "parameters_json_schema": {"properties": {"url": {"title": + "Url", "type": "string"}}, "required": ["url"], "type": "object", "additionalProperties": + false}}, {"description": "Generate a structured report based on research findings.\n\nArgs:\n title: + The title of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.", "name": "generate_report", "parameters_json_schema": + {"properties": {"title": {"title": "Title", "type": "string"}, "findings": {"title": + "Findings", "type": "string"}}, "required": ["title", "findings"], "type": "object", + "additionalProperties": false}}]}], "generationConfig": {"stopSequences": ["\nObservation:"]}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + content-length: + - '4512' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + x-goog-api-client: + - google-genai-sdk/1.49.0 gl-python/3.13.3 + x-goog-api-key: + - X-GOOG-API-KEY-XXX + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + response: + body: + string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": + [\n {\n \"functionCall\": {\n \"name\": \"read_website\",\n + \ \"args\": {\n \"url\": \"https://www.example.com/ai-agent-market-report\"\n + \ }\n },\n \"thoughtSignature\": \"CooPAXLI2nzYy6ki8YJo4RDDzryK6qtkbIEWXCh0ZjpoRX2fgNghUrxXZUwrsrbrwEpccdvWCDpb5ZkwYMNuzMi4yRUsjfcebfd2VCwQgvWnWmitj3taAcLCUDIJX5pGt0L2O8V6ehWmrANQHGw6Qc/QVx5dMlSFeFKHtfc1M95CJz2BxZd3lnuKLCEu7LCCiqIDdd1o1y/EcGsl7OHai6WyQJg49Cvcww//Z+kfSVoAPGNedTYPIf4ImttMyofV8+yczI0IGjhFzE0Qk1Pvo84O9NyOufpPELeKY8l1yfZgSZEL0sUA6weqf1P/xzNKt6h96Rh1KpAx5iTGFtqOWJrJ8OP+ZdGJ+rA+xZQuTRtKbW/e60rC5kPgJnkhWCp1p7HTLgTGwpzzxztqL0ggURaWw8GJw8S41BcM5mFEA+a7ivMWPMLdMk6h5gr/Y8JTnLSRZFoZYHkY9bTcAFPc9gapyFlKkQDciYet/MHe2zfE7ycx0e8c0W7ISoYPHpXW/WxNekMiNdfx1eg/mEX/Y0Vjc70p+HtbnEGaBoZWJSo+cJtZdA4sNJaIuEnnVTtAdAtnoxJUOyr2jrKDAsSjHmVoeLARZO2/DuJKsEVNHzsNPMw4SrBq1DkG52Aof7KcskOfW2OyoZEUnIf23IRabKflUG/7dHGqYfLtkWRApWZYBy2eQILwsmXJ4xx27S7/02Jl0D8rX3vOQhjS7lTjrXvy8wU3biUFvbnqJuLj3ACLn7Hn1axe4dB7zIpYC1di63DDs1fQp79d2VaGRC+tVMKiQu/+yC6nCM4j8JSnpBNgvyhq3ilAr/iDHD6GkbxdBBChFbZl0KY+WkrcZpPp2g2m/beiQuzF+cM6RuaqF2W0TEjVT+OZ1ivObreXFVFWa9T5qFsTjV3E/SFlmrXdx6Kf8d0i9QeEwbzBIvvX3VNUdW1DJ95WylaUXFtljk/cqylri2j2WTPMTSV9fcM/a8UWoayUAuLzq8zgxd2Wvrm8uvGcaewAQT+yK+u5FsLFoKxaMkoakb2/1tjWfhn+MrbIoYOBIebUapUhADUJgGDTG7byEEQlbwNaa8rOZ5ZpdBrDXjtj1tsz6cqaEiCbtZWqei0myuMa0J6Z3FHoceSUgOACD/bHWSkzdW7LdKBKLxB25fKfD1hCLscE1skZ7nAEZWmrUzKz9yCiqAPfatQhHqnr3EchEB/dxbNXxCCT6IFxemhb9AsaQWjcTrlQJiuz8t4VMmu1slgEcrhweZ/PpxI/E74qvV9ljcFLcziW0BZaqepJMeBe2cLfVVki5R5kudQVuAcCEdkrwBDWAwZlO9aWTmiZ9+ggfl3F/63JzZXSM/NDc/2rJ1k65e+O+vyTzkrUwnSPnh4RPK6jeIul4SSLPgyZBwp0qHyTl4As8jpMe9Rbt0NRli6Z5eAr/IYdbicKo/pzqiupdxvu4u+jKt+F4KojU9avgWFAB01isHHO5Z1vzVJ6XDilWeah5DvHc1lkcPCXtbBGvykfFqJIBhthnFi0f/S4HV+IlOKwvKbfmL5GplD7H+DjUA5UdHjw8HoJa6uQxhbERy5dZlYqxN918aL147Afq4LuPoWUJULEsYULwUuA6HspemIKUltHacOAxZgP4OLVkB3zwssj8E6rMtU1puupHiL7J66fFaR3co71gzzlvl8R2Xi3xEQdpigxxCUAsZWMrSrITRBnKPKGF1CrFEhU6FP6bQAQ6UuhAdqLoJihwVnMTyY9fGUriwCkrQu5gK7ZQlnXyluV0J/5xWh4sOaPmwmeiUXMBPN0iGb7z24lyHaI3QXz3kGlJBnIFhseJTJo3ed66Z/LAf8I/hFC6s/sIioEmRgd4tm3Q1U05ETKrptnzo8Ac4AOTdJtbQv0uDQSkoefUbebu7x6L9Dns04VSvDPKLwlFQd81sl9DYmi9SPDxKT7S6gfG6WjJ9z35eNSR01QW1QIgAAhMU8UX4o7QQaXnUZxfZeYRMXlzu/xb4KSREvc/FeLJ+IvnPFgzryAgLi/Sipl//Eul0sjrREPYJE1GxBOIoURGa+Bsmc3yy8aWArxv/HGpbLzwjmH8TaMvB3P/4tkvT+5IjJlpe0UrR1ssqasUtwfH9yWv5+4i+EdJrJ/SJ4Hl1Vlj9zi9lEFr33Zs96kn8ZOkBHp0m7Sxr5xP+krSxQkROIpu0d02kIqQ3nob+kGbnAf0zkmH6pS3H4mG2Zksu8KcGvghx7XTl3qBOJ+ZY4vlev2cSMBfMmnbUHKvMlz4YsmTGR1JPntDguA8UJJ1UZCex4E/W3KcHwd4qvzqgMZNjdarIvGjlmnKmoL1RV7EqUsBkpk+jauY0SqoWsIIf4b6O9Y3fQRRJURLeIfGCTmlxl+df+4yjqUhxUXTaQsX2KlEJ3tZK+ZI9FdKMzqPip2oMFwf2XfLIyIuRA0NU5fDRIxqPUzYv6RQ+zhXXB1QscsAtyf1t+LsaVw//1Vfj6V3Ups4pNyiwMAZg1z9DaIHht++VfgCdqT4RLcaGI8eEDrjkOc9G/iLkLBCfOB7EZkcXT20Lc1Silxa++dZsaYp5Tni8dz6f5YWg==\"\n + \ }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": + \"STOP\",\n \"index\": 0,\n \"finishMessage\": \"Model generated + function call(s).\"\n }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\": + 465,\n \"candidatesTokenCount\": 29,\n \"totalTokenCount\": 916,\n \"promptTokensDetails\": + [\n {\n \"modality\": \"TEXT\",\n \"tokenCount\": 465\n + \ }\n ],\n \"thoughtsTokenCount\": 422\n },\n \"modelVersion\": + \"gemini-2.5-flash\",\n \"responseId\": \"jUWCadDOIdi8_uMPxuOu4Ak\"\n}\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Type: + - application/json; charset=UTF-8 + Date: + - Tue, 03 Feb 2026 18:59:25 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=3079 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + X-Frame-Options: + - X-FRAME-OPTIONS-XXX + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"contents": [{"parts": [{"text": "\nCurrent Task: Research the current + state of the AI agent market:\n1. Search for recent information about AI agents + and their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."}], "role": "user"}, {"parts": [{"functionCall": {"args": {"query": + "AI agent market trends 2023-2024"}, "name": "web_search"}, "thoughtSignature": + "CoYEAXLI2nzmyDix3_QA-tMOiUwpDVoA5-RJoRW7kw3okJaVYCa5Usx7eBn4xowP7oXNynS4NfawCYqboufBXjHinq13UTcYg0Y74qIrza4KuctliGmf8G7S4QoS0Y3gqCHQKsxTdShQOg8wirnr8Rdu1eyrrhWE0XKk0HPA0Ssj7zUVoJBqHPqwyvkFyXkMtpcmtq9qXmZYfMFuSKRQnYLVLllL_BpOIL3w7MuofpviO85bvYk9gX0vsDjYWS6EdVEfC9k2BWGjhHaILXT9A1iwNPdDAg33SOC-BlPrGox0ghCr5qEKnBMZhUszqaUCykczFCq-xMIA3xDGNbTjicWb53sL_PXBYLsNty1giW3nKFe8-8eRpUsHUx7oQ82m4AUxKqk99mZjaLp8bHk-rERjFZErcw_pe_3190K0WGHH5ecB4amJCzZtVrQJ1oAZhb7_P1VZ57xmt1z_c1pQgjuvnV-cWE9blh5o6mNNFbFuzJDIO2k8qrFeeDwlCF8OOrxo8F-z1evg4yjZ1-9TLCVFTmZ0S0PI54FS5afb0RdPol2_ISNw7H_dtnO4z6LhT2NmlYqYZr8qfVoUD21rmI08NFs-f_6JW5-7eSQbax76SW-6A2IqqPPyF66MCpqtEzC-hpzVsCBcIQyRQWsdm-RNAs50gmqF6W3CcTPryWkeS7w9ORqxdiU="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "web_search", "response": + {"result": "Search results for ''AI agent market trends 2023-2024'': Found 3 + relevant articles about the topic including market analysis, competitor data, + and industry trends."}}}], "role": "user"}, {"parts": [{"functionCall": {"args": + {"query": "latest reports AI agent market analysis"}, "name": "web_search"}, + "thoughtSignature": "CpwGAXLI2nybmVGffIN1k-T5M2HmQNlvybZRPoo_ysgNARa-9nrPdRoBZ9RC-Dee9KSk1o7O-IU9l0sWCTirQYcroEhXon-JIQUVTed_L0s__sBOR-hJnZWoaG5ucsfJQvovAQba2Wb7uViEkdySvHfRApF0atewbC-TCKZrxDAQ6Naby8nwUTauJPKlgsBsZVnlViRfIbF7pom1zvXD-d5htjMiuJr1nOuSH0EGQWC4TUiuJD23hgockzhmIpbU_bStn8PFIQNsySEzl6H5sZdlD4auwCMCD2Q-Ur05w1uLv7n8GoSZn5dkdXLR5R7dZ-kkX-xP4w841Ih2gc6rBKT5tSedN01AuJsK65NSfOXZBwakxs58WZXDQXnIQe4d2QThAX3nPdUmhvVI6sHX-ZdtQZIrhE7hRf9j_T_wvvrUao5VDv-mxXd9bcPEV2BzSXkvkAB1SbJ-5wN7Qb2j31lkiUu5uRnZOiVxL5iCS-8Z_jEl4NjpGithbcPoNpFIDOeiE_f8kf7tQJDX-YNquPbYZRJHvIfLalVQndVGNlZVN2jXT3Wwo8So3vmzIDFVjV-pj7tSRNK8hTITm6bfHS-XUZqJdm7eHCzhonyJ7_tl7LbsstPXoZU3ZN50tNpXYOK-NzgzU7iwd9SaHVXgzQRdujWgHuBmSiSd9qNHvdaNwgARVTnMj3VYpehgIuaYMzQmgM99TdC_zmzcqHa5VZSnHKHqMIVc9gjRvVwz4DUm6VLLnKnVYFClM6gofmUI6s9fThiR5EdfimaDTlRlzh6Df33jAbRA9rUTDH6uE-DjiopCvXuHjmQqK9Smyxt2vTao9H7AYIRN7yWmdVoaG0tUSL2XQ31wIW3cEyhz7ihQwFYKJnOkQ3_CiU6KV-4ldk9UY_vKWSgItVTTE6G8Di0iviiCAkmL59Uj7vnIp80-U9rDIK7WhxpAWlrDA6cGQT2LGAXQ2liXtLa31nXyfvCezhtSS8jSVm4SHaiU-INvYtpq2Q7nXPTFbjjZooyC5FePGqAH1T-sRYbR02jaa572_NuwFgBCObTfqO8G5A=="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "web_search", "response": + {"result": "Search results for ''latest reports AI agent market analysis'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}}}], "role": "user"}, {"parts": [{"functionCall": + {"args": {"url": "https://www.example.com/ai-agent-market-report"}, "name": + "read_website"}, "thoughtSignature": "CooPAXLI2nzYy6ki8YJo4RDDzryK6qtkbIEWXCh0ZjpoRX2fgNghUrxXZUwrsrbrwEpccdvWCDpb5ZkwYMNuzMi4yRUsjfcebfd2VCwQgvWnWmitj3taAcLCUDIJX5pGt0L2O8V6ehWmrANQHGw6Qc_QVx5dMlSFeFKHtfc1M95CJz2BxZd3lnuKLCEu7LCCiqIDdd1o1y_EcGsl7OHai6WyQJg49Cvcww__Z-kfSVoAPGNedTYPIf4ImttMyofV8-yczI0IGjhFzE0Qk1Pvo84O9NyOufpPELeKY8l1yfZgSZEL0sUA6weqf1P_xzNKt6h96Rh1KpAx5iTGFtqOWJrJ8OP-ZdGJ-rA-xZQuTRtKbW_e60rC5kPgJnkhWCp1p7HTLgTGwpzzxztqL0ggURaWw8GJw8S41BcM5mFEA-a7ivMWPMLdMk6h5gr_Y8JTnLSRZFoZYHkY9bTcAFPc9gapyFlKkQDciYet_MHe2zfE7ycx0e8c0W7ISoYPHpXW_WxNekMiNdfx1eg_mEX_Y0Vjc70p-HtbnEGaBoZWJSo-cJtZdA4sNJaIuEnnVTtAdAtnoxJUOyr2jrKDAsSjHmVoeLARZO2_DuJKsEVNHzsNPMw4SrBq1DkG52Aof7KcskOfW2OyoZEUnIf23IRabKflUG_7dHGqYfLtkWRApWZYBy2eQILwsmXJ4xx27S7_02Jl0D8rX3vOQhjS7lTjrXvy8wU3biUFvbnqJuLj3ACLn7Hn1axe4dB7zIpYC1di63DDs1fQp79d2VaGRC-tVMKiQu_-yC6nCM4j8JSnpBNgvyhq3ilAr_iDHD6GkbxdBBChFbZl0KY-WkrcZpPp2g2m_beiQuzF-cM6RuaqF2W0TEjVT-OZ1ivObreXFVFWa9T5qFsTjV3E_SFlmrXdx6Kf8d0i9QeEwbzBIvvX3VNUdW1DJ95WylaUXFtljk_cqylri2j2WTPMTSV9fcM_a8UWoayUAuLzq8zgxd2Wvrm8uvGcaewAQT-yK-u5FsLFoKxaMkoakb2_1tjWfhn-MrbIoYOBIebUapUhADUJgGDTG7byEEQlbwNaa8rOZ5ZpdBrDXjtj1tsz6cqaEiCbtZWqei0myuMa0J6Z3FHoceSUgOACD_bHWSkzdW7LdKBKLxB25fKfD1hCLscE1skZ7nAEZWmrUzKz9yCiqAPfatQhHqnr3EchEB_dxbNXxCCT6IFxemhb9AsaQWjcTrlQJiuz8t4VMmu1slgEcrhweZ_PpxI_E74qvV9ljcFLcziW0BZaqepJMeBe2cLfVVki5R5kudQVuAcCEdkrwBDWAwZlO9aWTmiZ9-ggfl3F_63JzZXSM_NDc_2rJ1k65e-O-vyTzkrUwnSPnh4RPK6jeIul4SSLPgyZBwp0qHyTl4As8jpMe9Rbt0NRli6Z5eAr_IYdbicKo_pzqiupdxvu4u-jKt-F4KojU9avgWFAB01isHHO5Z1vzVJ6XDilWeah5DvHc1lkcPCXtbBGvykfFqJIBhthnFi0f_S4HV-IlOKwvKbfmL5GplD7H-DjUA5UdHjw8HoJa6uQxhbERy5dZlYqxN918aL147Afq4LuPoWUJULEsYULwUuA6HspemIKUltHacOAxZgP4OLVkB3zwssj8E6rMtU1puupHiL7J66fFaR3co71gzzlvl8R2Xi3xEQdpigxxCUAsZWMrSrITRBnKPKGF1CrFEhU6FP6bQAQ6UuhAdqLoJihwVnMTyY9fGUriwCkrQu5gK7ZQlnXyluV0J_5xWh4sOaPmwmeiUXMBPN0iGb7z24lyHaI3QXz3kGlJBnIFhseJTJo3ed66Z_LAf8I_hFC6s_sIioEmRgd4tm3Q1U05ETKrptnzo8Ac4AOTdJtbQv0uDQSkoefUbebu7x6L9Dns04VSvDPKLwlFQd81sl9DYmi9SPDxKT7S6gfG6WjJ9z35eNSR01QW1QIgAAhMU8UX4o7QQaXnUZxfZeYRMXlzu_xb4KSREvc_FeLJ-IvnPFgzryAgLi_Sipl__Eul0sjrREPYJE1GxBOIoURGa-Bsmc3yy8aWArxv_HGpbLzwjmH8TaMvB3P_4tkvT-5IjJlpe0UrR1ssqasUtwfH9yWv5-4i-EdJrJ_SJ4Hl1Vlj9zi9lEFr33Zs96kn8ZOkBHp0m7Sxr5xP-krSxQkROIpu0d02kIqQ3nob-kGbnAf0zkmH6pS3H4mG2Zksu8KcGvghx7XTl3qBOJ-ZY4vlev2cSMBfMmnbUHKvMlz4YsmTGR1JPntDguA8UJJ1UZCex4E_W3KcHwd4qvzqgMZNjdarIvGjlmnKmoL1RV7EqUsBkpk-jauY0SqoWsIIf4b6O9Y3fQRRJURLeIfGCTmlxl-df-4yjqUhxUXTaQsX2KlEJ3tZK-ZI9FdKMzqPip2oMFwf2XfLIyIuRA0NU5fDRIxqPUzYv6RQ-zhXXB1QscsAtyf1t-LsaVw__1Vfj6V3Ups4pNyiwMAZg1z9DaIHht--VfgCdqT4RLcaGI8eEDrjkOc9G_iLkLBCfOB7EZkcXT20Lc1Silxa--dZsaYp5Tni8dz6f5YWg=="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "read_website", "response": + {"result": "Content from https://www.example.com/ai-agent-market-report: This + article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}}}], "role": "user"}], + "systemInstruction": {"parts": [{"text": "You are Research Analyst. An experienced + analyst skilled at gathering information and synthesizing findings into actionable + insights.\nYour personal goal is: Conduct thorough research and produce insightful + reports"}], "role": "user"}, "tools": [{"functionDeclarations": [{"description": + "Search the web for information on a given topic.\n\nArgs:\n query: The search + query to look up.\n\nReturns:\n Search results as a string.", "name": "web_search", + "parameters_json_schema": {"properties": {"query": {"title": "Query", "type": + "string"}}, "required": ["query"], "type": "object", "additionalProperties": + false}}, {"description": "Read and extract content from a website URL.\n\nArgs:\n url: + The URL of the website to read.\n\nReturns:\n The extracted content from + the website.", "name": "read_website", "parameters_json_schema": {"properties": + {"url": {"title": "Url", "type": "string"}}, "required": ["url"], "type": "object", + "additionalProperties": false}}, {"description": "Generate a structured report + based on research findings.\n\nArgs:\n title: The title of the report.\n findings: + The research findings to include.\n\nReturns:\n A formatted report string.", + "name": "generate_report", "parameters_json_schema": {"properties": {"title": + {"title": "Title", "type": "string"}, "findings": {"title": "Findings", "type": + "string"}}, "required": ["title", "findings"], "type": "object", "additionalProperties": + false}}]}], "generationConfig": {"stopSequences": ["\nObservation:"]}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + content-length: + - '7562' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + x-goog-api-client: + - google-genai-sdk/1.49.0 gl-python/3.13.3 + x-goog-api-key: + - X-GOOG-API-KEY-XXX + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + response: + body: + string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": + [\n {\n \"functionCall\": {\n \"name\": \"generate_report\",\n + \ \"args\": {\n \"findings\": \"The AI agent market + is currently valued at $50 billion and is experiencing a strong growth rate + of 15% year-over-year. Key players in the industry are contributing to this + expansion, driven by increasing adoption across various sectors.\",\n \"title\": + \"Current State of the AI Agent Market\"\n }\n },\n + \ \"thoughtSignature\": \"CuACAXLI2nzhub94iTgphfrBnaQV13Wv0+kRJj+l9jEDfxsyNg9eGnhxRfj4cued3Mgvv1CR9vkpfDV/V9x57TVWLNwPsVvsTgZKI0pwmV355sUCRKqAyfRBwDWhn0UAl9+sYNFCKUJCp8G5QBGfrar1lYLltX/z83d4O13Wn5Ugvxco9o4CdxnnPJOWkTPETRgXA/1HSdEiwGNqt0A3lKYkGRXQx+XyK54lDwmOBg+Yx4ZVgUxANXLK1z91NF/6cpmSVgoE2sL0VrIHISYs4XdUCj1fL3R7DpnZdrrZhmqnTjEgkeR1C3BR5GJeSUmg+kmqjPSPnP0NYiZo9H9SGL/ewGz3wg+GKcILAa4nD7/tfdqIMbAff8PJemwJE4ONT5zAmJ69/NFj0i1X01v04E8f2NdHewPSsKO5mptk5qzWxoW3G3uUhXccxk62EydTsESf6WRwolsmphaGgVE9AwuhzQ==\"\n + \ }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": + \"STOP\",\n \"index\": 0,\n \"finishMessage\": \"Model generated + function call(s).\"\n }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\": + 557,\n \"candidatesTokenCount\": 73,\n \"totalTokenCount\": 700,\n \"cachedContentTokenCount\": + 330,\n \"promptTokensDetails\": [\n {\n \"modality\": \"TEXT\",\n + \ \"tokenCount\": 557\n }\n ],\n \"cacheTokensDetails\": + [\n {\n \"modality\": \"TEXT\",\n \"tokenCount\": 330\n + \ }\n ],\n \"thoughtsTokenCount\": 70\n },\n \"modelVersion\": + \"gemini-2.5-flash\",\n \"responseId\": \"jkWCacGZMLrv_uMPibCHmQU\"\n}\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Type: + - application/json; charset=UTF-8 + Date: + - Tue, 03 Feb 2026 18:59:26 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=1224 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + X-Frame-Options: + - X-FRAME-OPTIONS-XXX + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +- request: + body: '{"contents": [{"parts": [{"text": "\nCurrent Task: Research the current + state of the AI agent market:\n1. Search for recent information about AI agents + and their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."}], "role": "user"}, {"parts": [{"functionCall": {"args": {"query": + "AI agent market trends 2023-2024"}, "name": "web_search"}, "thoughtSignature": + "CoYEAXLI2nzmyDix3_QA-tMOiUwpDVoA5-RJoRW7kw3okJaVYCa5Usx7eBn4xowP7oXNynS4NfawCYqboufBXjHinq13UTcYg0Y74qIrza4KuctliGmf8G7S4QoS0Y3gqCHQKsxTdShQOg8wirnr8Rdu1eyrrhWE0XKk0HPA0Ssj7zUVoJBqHPqwyvkFyXkMtpcmtq9qXmZYfMFuSKRQnYLVLllL_BpOIL3w7MuofpviO85bvYk9gX0vsDjYWS6EdVEfC9k2BWGjhHaILXT9A1iwNPdDAg33SOC-BlPrGox0ghCr5qEKnBMZhUszqaUCykczFCq-xMIA3xDGNbTjicWb53sL_PXBYLsNty1giW3nKFe8-8eRpUsHUx7oQ82m4AUxKqk99mZjaLp8bHk-rERjFZErcw_pe_3190K0WGHH5ecB4amJCzZtVrQJ1oAZhb7_P1VZ57xmt1z_c1pQgjuvnV-cWE9blh5o6mNNFbFuzJDIO2k8qrFeeDwlCF8OOrxo8F-z1evg4yjZ1-9TLCVFTmZ0S0PI54FS5afb0RdPol2_ISNw7H_dtnO4z6LhT2NmlYqYZr8qfVoUD21rmI08NFs-f_6JW5-7eSQbax76SW-6A2IqqPPyF66MCpqtEzC-hpzVsCBcIQyRQWsdm-RNAs50gmqF6W3CcTPryWkeS7w9ORqxdiU="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "web_search", "response": + {"result": "Search results for ''AI agent market trends 2023-2024'': Found 3 + relevant articles about the topic including market analysis, competitor data, + and industry trends."}}}], "role": "user"}, {"parts": [{"functionCall": {"args": + {"query": "latest reports AI agent market analysis"}, "name": "web_search"}, + "thoughtSignature": "CpwGAXLI2nybmVGffIN1k-T5M2HmQNlvybZRPoo_ysgNARa-9nrPdRoBZ9RC-Dee9KSk1o7O-IU9l0sWCTirQYcroEhXon-JIQUVTed_L0s__sBOR-hJnZWoaG5ucsfJQvovAQba2Wb7uViEkdySvHfRApF0atewbC-TCKZrxDAQ6Naby8nwUTauJPKlgsBsZVnlViRfIbF7pom1zvXD-d5htjMiuJr1nOuSH0EGQWC4TUiuJD23hgockzhmIpbU_bStn8PFIQNsySEzl6H5sZdlD4auwCMCD2Q-Ur05w1uLv7n8GoSZn5dkdXLR5R7dZ-kkX-xP4w841Ih2gc6rBKT5tSedN01AuJsK65NSfOXZBwakxs58WZXDQXnIQe4d2QThAX3nPdUmhvVI6sHX-ZdtQZIrhE7hRf9j_T_wvvrUao5VDv-mxXd9bcPEV2BzSXkvkAB1SbJ-5wN7Qb2j31lkiUu5uRnZOiVxL5iCS-8Z_jEl4NjpGithbcPoNpFIDOeiE_f8kf7tQJDX-YNquPbYZRJHvIfLalVQndVGNlZVN2jXT3Wwo8So3vmzIDFVjV-pj7tSRNK8hTITm6bfHS-XUZqJdm7eHCzhonyJ7_tl7LbsstPXoZU3ZN50tNpXYOK-NzgzU7iwd9SaHVXgzQRdujWgHuBmSiSd9qNHvdaNwgARVTnMj3VYpehgIuaYMzQmgM99TdC_zmzcqHa5VZSnHKHqMIVc9gjRvVwz4DUm6VLLnKnVYFClM6gofmUI6s9fThiR5EdfimaDTlRlzh6Df33jAbRA9rUTDH6uE-DjiopCvXuHjmQqK9Smyxt2vTao9H7AYIRN7yWmdVoaG0tUSL2XQ31wIW3cEyhz7ihQwFYKJnOkQ3_CiU6KV-4ldk9UY_vKWSgItVTTE6G8Di0iviiCAkmL59Uj7vnIp80-U9rDIK7WhxpAWlrDA6cGQT2LGAXQ2liXtLa31nXyfvCezhtSS8jSVm4SHaiU-INvYtpq2Q7nXPTFbjjZooyC5FePGqAH1T-sRYbR02jaa572_NuwFgBCObTfqO8G5A=="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "web_search", "response": + {"result": "Search results for ''latest reports AI agent market analysis'': + Found 3 relevant articles about the topic including market analysis, competitor + data, and industry trends."}}}], "role": "user"}, {"parts": [{"functionCall": + {"args": {"url": "https://www.example.com/ai-agent-market-report"}, "name": + "read_website"}, "thoughtSignature": "CooPAXLI2nzYy6ki8YJo4RDDzryK6qtkbIEWXCh0ZjpoRX2fgNghUrxXZUwrsrbrwEpccdvWCDpb5ZkwYMNuzMi4yRUsjfcebfd2VCwQgvWnWmitj3taAcLCUDIJX5pGt0L2O8V6ehWmrANQHGw6Qc_QVx5dMlSFeFKHtfc1M95CJz2BxZd3lnuKLCEu7LCCiqIDdd1o1y_EcGsl7OHai6WyQJg49Cvcww__Z-kfSVoAPGNedTYPIf4ImttMyofV8-yczI0IGjhFzE0Qk1Pvo84O9NyOufpPELeKY8l1yfZgSZEL0sUA6weqf1P_xzNKt6h96Rh1KpAx5iTGFtqOWJrJ8OP-ZdGJ-rA-xZQuTRtKbW_e60rC5kPgJnkhWCp1p7HTLgTGwpzzxztqL0ggURaWw8GJw8S41BcM5mFEA-a7ivMWPMLdMk6h5gr_Y8JTnLSRZFoZYHkY9bTcAFPc9gapyFlKkQDciYet_MHe2zfE7ycx0e8c0W7ISoYPHpXW_WxNekMiNdfx1eg_mEX_Y0Vjc70p-HtbnEGaBoZWJSo-cJtZdA4sNJaIuEnnVTtAdAtnoxJUOyr2jrKDAsSjHmVoeLARZO2_DuJKsEVNHzsNPMw4SrBq1DkG52Aof7KcskOfW2OyoZEUnIf23IRabKflUG_7dHGqYfLtkWRApWZYBy2eQILwsmXJ4xx27S7_02Jl0D8rX3vOQhjS7lTjrXvy8wU3biUFvbnqJuLj3ACLn7Hn1axe4dB7zIpYC1di63DDs1fQp79d2VaGRC-tVMKiQu_-yC6nCM4j8JSnpBNgvyhq3ilAr_iDHD6GkbxdBBChFbZl0KY-WkrcZpPp2g2m_beiQuzF-cM6RuaqF2W0TEjVT-OZ1ivObreXFVFWa9T5qFsTjV3E_SFlmrXdx6Kf8d0i9QeEwbzBIvvX3VNUdW1DJ95WylaUXFtljk_cqylri2j2WTPMTSV9fcM_a8UWoayUAuLzq8zgxd2Wvrm8uvGcaewAQT-yK-u5FsLFoKxaMkoakb2_1tjWfhn-MrbIoYOBIebUapUhADUJgGDTG7byEEQlbwNaa8rOZ5ZpdBrDXjtj1tsz6cqaEiCbtZWqei0myuMa0J6Z3FHoceSUgOACD_bHWSkzdW7LdKBKLxB25fKfD1hCLscE1skZ7nAEZWmrUzKz9yCiqAPfatQhHqnr3EchEB_dxbNXxCCT6IFxemhb9AsaQWjcTrlQJiuz8t4VMmu1slgEcrhweZ_PpxI_E74qvV9ljcFLcziW0BZaqepJMeBe2cLfVVki5R5kudQVuAcCEdkrwBDWAwZlO9aWTmiZ9-ggfl3F_63JzZXSM_NDc_2rJ1k65e-O-vyTzkrUwnSPnh4RPK6jeIul4SSLPgyZBwp0qHyTl4As8jpMe9Rbt0NRli6Z5eAr_IYdbicKo_pzqiupdxvu4u-jKt-F4KojU9avgWFAB01isHHO5Z1vzVJ6XDilWeah5DvHc1lkcPCXtbBGvykfFqJIBhthnFi0f_S4HV-IlOKwvKbfmL5GplD7H-DjUA5UdHjw8HoJa6uQxhbERy5dZlYqxN918aL147Afq4LuPoWUJULEsYULwUuA6HspemIKUltHacOAxZgP4OLVkB3zwssj8E6rMtU1puupHiL7J66fFaR3co71gzzlvl8R2Xi3xEQdpigxxCUAsZWMrSrITRBnKPKGF1CrFEhU6FP6bQAQ6UuhAdqLoJihwVnMTyY9fGUriwCkrQu5gK7ZQlnXyluV0J_5xWh4sOaPmwmeiUXMBPN0iGb7z24lyHaI3QXz3kGlJBnIFhseJTJo3ed66Z_LAf8I_hFC6s_sIioEmRgd4tm3Q1U05ETKrptnzo8Ac4AOTdJtbQv0uDQSkoefUbebu7x6L9Dns04VSvDPKLwlFQd81sl9DYmi9SPDxKT7S6gfG6WjJ9z35eNSR01QW1QIgAAhMU8UX4o7QQaXnUZxfZeYRMXlzu_xb4KSREvc_FeLJ-IvnPFgzryAgLi_Sipl__Eul0sjrREPYJE1GxBOIoURGa-Bsmc3yy8aWArxv_HGpbLzwjmH8TaMvB3P_4tkvT-5IjJlpe0UrR1ssqasUtwfH9yWv5-4i-EdJrJ_SJ4Hl1Vlj9zi9lEFr33Zs96kn8ZOkBHp0m7Sxr5xP-krSxQkROIpu0d02kIqQ3nob-kGbnAf0zkmH6pS3H4mG2Zksu8KcGvghx7XTl3qBOJ-ZY4vlev2cSMBfMmnbUHKvMlz4YsmTGR1JPntDguA8UJJ1UZCex4E_W3KcHwd4qvzqgMZNjdarIvGjlmnKmoL1RV7EqUsBkpk-jauY0SqoWsIIf4b6O9Y3fQRRJURLeIfGCTmlxl-df-4yjqUhxUXTaQsX2KlEJ3tZK-ZI9FdKMzqPip2oMFwf2XfLIyIuRA0NU5fDRIxqPUzYv6RQ-zhXXB1QscsAtyf1t-LsaVw__1Vfj6V3Ups4pNyiwMAZg1z9DaIHht--VfgCdqT4RLcaGI8eEDrjkOc9G_iLkLBCfOB7EZkcXT20Lc1Silxa--dZsaYp5Tni8dz6f5YWg=="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "read_website", "response": + {"result": "Content from https://www.example.com/ai-agent-market-report: This + article discusses key insights about the topic including market size ($50B), + growth rate (15% YoY), and major players in the industry."}}}], "role": "user"}, + {"parts": [{"functionCall": {"args": {"findings": "The AI agent market is currently + valued at $50 billion and is experiencing a strong growth rate of 15% year-over-year. + Key players in the industry are contributing to this expansion, driven by increasing + adoption across various sectors.", "title": "Current State of the AI Agent Market"}, + "name": "generate_report"}, "thoughtSignature": "CuACAXLI2nzhub94iTgphfrBnaQV13Wv0-kRJj-l9jEDfxsyNg9eGnhxRfj4cued3Mgvv1CR9vkpfDV_V9x57TVWLNwPsVvsTgZKI0pwmV355sUCRKqAyfRBwDWhn0UAl9-sYNFCKUJCp8G5QBGfrar1lYLltX_z83d4O13Wn5Ugvxco9o4CdxnnPJOWkTPETRgXA_1HSdEiwGNqt0A3lKYkGRXQx-XyK54lDwmOBg-Yx4ZVgUxANXLK1z91NF_6cpmSVgoE2sL0VrIHISYs4XdUCj1fL3R7DpnZdrrZhmqnTjEgkeR1C3BR5GJeSUmg-kmqjPSPnP0NYiZo9H9SGL_ewGz3wg-GKcILAa4nD7_tfdqIMbAff8PJemwJE4ONT5zAmJ69_NFj0i1X01v04E8f2NdHewPSsKO5mptk5qzWxoW3G3uUhXccxk62EydTsESf6WRwolsmphaGgVE9AwuhzQ=="}], + "role": "model"}, {"parts": [{"functionResponse": {"name": "generate_report", + "response": {"result": "# Current State of the AI Agent Market\n\n## Executive + Summary\nThe AI agent market is currently valued at $50 billion and is experiencing + a strong growth rate of 15% year-over-year. Key players in the industry are + contributing to this expansion, driven by increasing adoption across various + sectors.\n\n## Conclusion\nBased on the analysis, the market shows strong growth + potential."}}}], "role": "user"}], "systemInstruction": {"parts": [{"text": + "You are Research Analyst. An experienced analyst skilled at gathering information + and synthesizing findings into actionable insights.\nYour personal goal is: + Conduct thorough research and produce insightful reports"}], "role": "user"}, + "tools": [{"functionDeclarations": [{"description": "Search the web for information + on a given topic.\n\nArgs:\n query: The search query to look up.\n\nReturns:\n Search + results as a string.", "name": "web_search", "parameters_json_schema": {"properties": + {"query": {"title": "Query", "type": "string"}}, "required": ["query"], "type": + "object", "additionalProperties": false}}, {"description": "Read and extract + content from a website URL.\n\nArgs:\n url: The URL of the website to read.\n\nReturns:\n The + extracted content from the website.", "name": "read_website", "parameters_json_schema": + {"properties": {"url": {"title": "Url", "type": "string"}}, "required": ["url"], + "type": "object", "additionalProperties": false}}, {"description": "Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.", "name": "generate_report", "parameters_json_schema": + {"properties": {"title": {"title": "Title", "type": "string"}, "findings": {"title": + "Findings", "type": "string"}}, "required": ["title", "findings"], "type": "object", + "additionalProperties": false}}]}], "generationConfig": {"stopSequences": ["\nObservation:"]}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + content-length: + - '8941' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + x-goog-api-client: + - google-genai-sdk/1.49.0 gl-python/3.13.3 + x-goog-api-key: + - X-GOOG-API-KEY-XXX + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + response: + body: + string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": + [\n {\n \"text\": \"The research on the current state + of the AI agent market has been completed. A report has been generated with + the following key findings:\\n\\n**Current State of the AI Agent Market**\\n\\n**Executive + Summary**\\nThe AI agent market is currently valued at $50 billion and is + experiencing a strong growth rate of 15% year-over-year. Key players in the + industry are contributing to this expansion, driven by increasing adoption + across various sectors.\\n\\n**Conclusion**\\nBased on the analysis, the market + shows strong growth potential.\",\n \"thoughtSignature\": \"CpIDAXLI2nwVVOjjKtAsnvvRhuJU79oCZksDIi1i7PcIr+FkXVHX8sS8kM0optXLnRQWDRKKxUDKA9C1myhIfnDfc3ef44xc4UaczwM80/TbYanden25qpZRA2kztBz9HiWEPyGjeX8M/8BGAj7mh3q6hwPtTFtmhFTzlw190YQoZLELqOyQzTSECt8roXPdWN1XhU/NbHg4x+H3IFSQ2HZKxbY/JC6tx5FYYh444tIT4798iVHI5HOUVb1pfdLfV45ju/DOD+pTONuqVcTX+jgusjoaH32pdu4Q19atg5BR6zanqwv93vkYPXx0hF4rI8FHtV9jrqwtjLqzXvh7LANtNpCvO3HG++lIoeRTy5RzfYQRkLkrfuLWW+xkGDYQh+CQ7jbeurx344pHBjzZTVDaSNTA0QMTYwDH7YUkxIsyw5Hv1F8tpVvjgoKqvJnar1d/EvrbiOwygpiEZOrmPEn/DKp4qPk2+hhFS4JpcnNGva9cFM22ObwHydIQdoXHOX3wci0nhshAZ0e8hd5u820gfrya\"\n + \ }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": + \"STOP\",\n \"index\": 0\n }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\": + 727,\n \"candidatesTokenCount\": 108,\n \"totalTokenCount\": 910,\n + \ \"cachedContentTokenCount\": 375,\n \"promptTokensDetails\": [\n {\n + \ \"modality\": \"TEXT\",\n \"tokenCount\": 727\n }\n ],\n + \ \"cacheTokensDetails\": [\n {\n \"modality\": \"TEXT\",\n + \ \"tokenCount\": 375\n }\n ],\n \"thoughtsTokenCount\": + 75\n },\n \"modelVersion\": \"gemini-2.5-flash\",\n \"responseId\": \"kUWCabvgOafg_uMP06Ga0QI\"\n}\n" + headers: + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Content-Type: + - application/json; charset=UTF-8 + Date: + - Tue, 03 Feb 2026 18:59:30 GMT + Server: + - scaffolding on HTTPServer2 + Server-Timing: + - gfet4t7; dur=3125 + Transfer-Encoding: + - chunked + Vary: + - Origin + - X-Origin + - Referer + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + X-Frame-Options: + - X-FRAME-OPTIONS-XXX + X-XSS-Protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/utilities/TestOpenAIStructuredPlanning.test_openai_research_workflow_generates_steps.yaml b/lib/crewai/tests/cassettes/utilities/TestOpenAIStructuredPlanning.test_openai_research_workflow_generates_steps.yaml new file mode 100644 index 000000000..d81dfb7c2 --- /dev/null +++ b/lib/crewai/tests/cassettes/utilities/TestOpenAIStructuredPlanning.test_openai_research_workflow_generates_steps.yaml @@ -0,0 +1,708 @@ +interactions: +- request: + body: '{"messages":[{"role":"system","content":"You are a strategic planning assistant. + Create minimal, effective execution plans. Prefer fewer steps over more."},{"role":"user","content":"Create + a focused execution plan for the following task:\n\n## Task\nResearch the current + state of the AI agent market:\n1. Search for recent information about AI agents + and their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step.\n\n## Expected Output\nComplete the task successfully\n\n## Available + Tools\nweb_search, read_website, generate_report\n\n## Instructions\nCreate + ONLY the essential steps needed to complete this task. Use the MINIMUM number + of steps required - do NOT pad your plan with unnecessary steps. Most tasks + need only 2-5 steps.\n\nFor each step:\n- State the specific action to take\n- + Specify which tool to use (if any)\n- Note dependencies on previous steps if + this step requires their output\n- If a step involves multiple items (e.g., + research 3 competitors), note this explicitly\n\nDo NOT include:\n- Setup or + preparation steps that are obvious\n- Verification steps unless critical\n- + Documentation or cleanup steps unless explicitly required\n- Generic steps like + \"review results\" or \"finalize output\"\n\nAfter your plan, state:\n- \"READY: + I am ready to execute the task.\" if the plan is complete\n- \"NOT READY: I + need to refine my plan because [reason].\" if you need more thinking"}],"model":"gpt-4o","tool_choice":"auto","tools":[{"type":"function","function":{"name":"create_reasoning_plan","description":"Create + or refine a reasoning plan for a task with structured steps","strict":true,"parameters":{"type":"object","properties":{"plan":{"type":"string","description":"A + brief summary of the overall plan."},"steps":{"type":"array","description":"List + of discrete steps to execute the plan","items":{"type":"object","properties":{"step_number":{"type":"integer","description":"Step + number (1-based)"},"description":{"type":"string","description":"What to do + in this step"},"tool_to_use":{"type":["string","null"],"description":"Tool to + use for this step, or null if no tool needed"},"depends_on":{"type":"array","items":{"type":"integer"},"description":"Step + numbers this step depends on (empty array if none)"}},"required":["step_number","description","tool_to_use","depends_on"],"additionalProperties":false}},"ready":{"type":"boolean","description":"Whether + the agent is ready to execute the task."}},"required":["plan","steps","ready"],"additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '2619' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D5Fu3HzCCoZJXtY9WqBmBv4QA4PS8\",\n \"object\": + \"chat.completion\",\n \"created\": 1770145143,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_bVEiQHpiVX9FEfuWVwTe8XGj\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"create_reasoning_plan\",\n + \ \"arguments\": \"{\\\"plan\\\":\\\"Research the current state + of the AI agent market by gathering recent market trend data, reading in-depth + content from a reliable industry source, and generating a concise report.\\\",\\\"steps\\\":[{\\\"step_number\\\":1,\\\"description\\\":\\\"Search + for recent information about AI agents and their market trends.\\\",\\\"tool_to_use\\\":\\\"web_search\\\",\\\"depends_on\\\":[]},{\\\"step_number\\\":2,\\\"description\\\":\\\"Read + detailed content from a relevant industry source found during the search.\\\",\\\"tool_to_use\\\":\\\"read_website\\\",\\\"depends_on\\\":[1]},{\\\"step_number\\\":3,\\\"description\\\":\\\"Generate + a brief report summarizing the key findings from the gathered information.\\\",\\\"tool_to_use\\\":\\\"generate_report\\\",\\\"depends_on\\\":[1,2]}],\\\"ready\\\":true}\"\n + \ }\n }\n ],\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 480,\n \"completion_tokens\": + 153,\n \"total_tokens\": 633,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_fa7f5b168b\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:05 GMT + Server: + - cloudflare + Set-Cookie: + - SET-COOKIE-XXX + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '2629' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Research Analyst. An experienced + analyst skilled at gathering information and synthesizing findings into actionable + insights.\nYour personal goal is: Conduct thorough research and produce insightful + reports"},{"role":"user","content":"\nCurrent Task: Research the current state + of the AI agent market:\n1. Search for recent information about AI agents and + their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."}],"model":"gpt-4o","tool_choice":"auto","tools":[{"type":"function","function":{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","strict":true,"parameters":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","strict":true,"parameters":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","strict":true,"parameters":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '1849' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D5Fu6H5Oz7CA6xtmPwoBDIAr59nyJ\",\n \"object\": + \"chat.completion\",\n \"created\": 1770145146,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_QlnPEA94TbaFA83eRDhOHXRY\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"web_search\",\n + \ \"arguments\": \"{\\\"query\\\":\\\"current state of AI agent + market 2023\\\"}\"\n }\n }\n ],\n \"refusal\": + null,\n \"annotations\": []\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"tool_calls\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 267,\n \"completion_tokens\": 22,\n \"total_tokens\": 289,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_fa7f5b168b\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:07 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '752' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Research Analyst. An experienced + analyst skilled at gathering information and synthesizing findings into actionable + insights.\nYour personal goal is: Conduct thorough research and produce insightful + reports"},{"role":"user","content":"\nCurrent Task: Research the current state + of the AI agent market:\n1. Search for recent information about AI agents and + their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."},{"role":"assistant","content":null,"tool_calls":[{"id":"call_QlnPEA94TbaFA83eRDhOHXRY","type":"function","function":{"name":"web_search","arguments":"{\"query\":\"current + state of AI agent market 2023\"}"}}]},{"role":"tool","tool_call_id":"call_QlnPEA94TbaFA83eRDhOHXRY","name":"web_search","content":"Search + results for ''current state of AI agent market 2023'': Found 3 relevant articles + about the topic including market analysis, competitor data, and industry trends."}],"model":"gpt-4o","tool_choice":"auto","tools":[{"type":"function","function":{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","strict":true,"parameters":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","strict":true,"parameters":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","strict":true,"parameters":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '2320' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D5Fu7QFl2h9pGJ0uhX6g4Fi4MMzMX\",\n \"object\": + \"chat.completion\",\n \"created\": 1770145147,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_sOaxpAdq5dvpRhUJMAct2oNP\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"read_website\",\n + \ \"arguments\": \"{\\\"url\\\": \\\"https://www.example.com/ai-agent-market-analysis-2023\\\"}\"\n + \ }\n },\n {\n \"id\": \"call_1GRSbggp4SYHg5WqAUQx5Dce\",\n + \ \"type\": \"function\",\n \"function\": {\n \"name\": + \"read_website\",\n \"arguments\": \"{\\\"url\\\": \\\"https://www.example.com/ai-agent-competitor-data-2023\\\"}\"\n + \ }\n },\n {\n \"id\": \"call_43s9ebATowN3hA5piPjL2z5N\",\n + \ \"type\": \"function\",\n \"function\": {\n \"name\": + \"read_website\",\n \"arguments\": \"{\\\"url\\\": \\\"https://www.example.com/ai-agent-industry-trends-2023\\\"}\"\n + \ }\n }\n ],\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 330,\n \"completion_tokens\": + 101,\n \"total_tokens\": 431,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_fa7f5b168b\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:09 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '1885' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Research Analyst. An experienced + analyst skilled at gathering information and synthesizing findings into actionable + insights.\nYour personal goal is: Conduct thorough research and produce insightful + reports"},{"role":"user","content":"\nCurrent Task: Research the current state + of the AI agent market:\n1. Search for recent information about AI agents and + their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."},{"role":"assistant","content":null,"tool_calls":[{"id":"call_QlnPEA94TbaFA83eRDhOHXRY","type":"function","function":{"name":"web_search","arguments":"{\"query\":\"current + state of AI agent market 2023\"}"}}]},{"role":"tool","tool_call_id":"call_QlnPEA94TbaFA83eRDhOHXRY","name":"web_search","content":"Search + results for ''current state of AI agent market 2023'': Found 3 relevant articles + about the topic including market analysis, competitor data, and industry trends."},{"role":"assistant","content":null,"tool_calls":[{"id":"call_sOaxpAdq5dvpRhUJMAct2oNP","type":"function","function":{"name":"read_website","arguments":"{\"url\": + \"https://www.example.com/ai-agent-market-analysis-2023\"}"}},{"id":"call_1GRSbggp4SYHg5WqAUQx5Dce","type":"function","function":{"name":"read_website","arguments":"{\"url\": + \"https://www.example.com/ai-agent-competitor-data-2023\"}"}},{"id":"call_43s9ebATowN3hA5piPjL2z5N","type":"function","function":{"name":"read_website","arguments":"{\"url\": + \"https://www.example.com/ai-agent-industry-trends-2023\"}"}}]},{"role":"tool","tool_call_id":"call_sOaxpAdq5dvpRhUJMAct2oNP","name":"read_website","content":"Content + from https://www.example.com/ai-agent-market-analysis-2023: This article discusses + key insights about the topic including market size ($50B), growth rate (15% + YoY), and major players in the industry."},{"role":"tool","tool_call_id":"call_1GRSbggp4SYHg5WqAUQx5Dce","name":"read_website","content":"Content + from https://www.example.com/ai-agent-competitor-data-2023: This article discusses + key insights about the topic including market size ($50B), growth rate (15% + YoY), and major players in the industry."},{"role":"tool","tool_call_id":"call_43s9ebATowN3hA5piPjL2z5N","name":"read_website","content":"Content + from https://www.example.com/ai-agent-industry-trends-2023: This article discusses + key insights about the topic including market size ($50B), growth rate (15% + YoY), and major players in the industry."}],"model":"gpt-4o","tool_choice":"auto","tools":[{"type":"function","function":{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","strict":true,"parameters":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","strict":true,"parameters":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","strict":true,"parameters":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '3811' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D5Fu95gX9R1zxpiJUa1wSOzIGA9CL\",\n \"object\": + \"chat.completion\",\n \"created\": 1770145149,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n + \ \"id\": \"call_QjEKbvT6OnKkCOTvwTu0TmAK\",\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"generate_report\",\n + \ \"arguments\": \"{\\\"title\\\":\\\"Current State of the AI + Agent Market 2023\\\",\\\"findings\\\":\\\"1. Market Size: The AI agent market + is currently valued at $50 billion.\\\\n2. Growth Rate: The industry is experiencing + a growth rate of 15% year-over-year.\\\\n3. Major Players: Significant companies + in this space include tech giants and specialized AI startups.\\\\n4. Market + Trends: The demand for AI agents is being driven by improvements in machine + learning algorithms and increasing adoption in customer service and automation + processes.\\\\n5. Competitive Landscape: The market is competitive with ongoing + innovation and investment in developing more advanced AI capabilities.\\\\n6. + Future Prospects: Continued growth is expected as businesses further integrate + AI agents into their operations for efficiency gains and customer engagement.\\\"}\"\n + \ }\n }\n ],\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 587,\n \"completion_tokens\": + 163,\n \"total_tokens\": 750,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_fa7f5b168b\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:12 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '3444' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +- request: + body: '{"messages":[{"role":"system","content":"You are Research Analyst. An experienced + analyst skilled at gathering information and synthesizing findings into actionable + insights.\nYour personal goal is: Conduct thorough research and produce insightful + reports"},{"role":"user","content":"\nCurrent Task: Research the current state + of the AI agent market:\n1. Search for recent information about AI agents and + their market trends\n2. Read detailed content from a relevant industry source\n3. + Generate a brief report summarizing the key findings\n\nUse the available tools + for each step."},{"role":"assistant","content":null,"tool_calls":[{"id":"call_QlnPEA94TbaFA83eRDhOHXRY","type":"function","function":{"name":"web_search","arguments":"{\"query\":\"current + state of AI agent market 2023\"}"}}]},{"role":"tool","tool_call_id":"call_QlnPEA94TbaFA83eRDhOHXRY","name":"web_search","content":"Search + results for ''current state of AI agent market 2023'': Found 3 relevant articles + about the topic including market analysis, competitor data, and industry trends."},{"role":"assistant","content":null,"tool_calls":[{"id":"call_sOaxpAdq5dvpRhUJMAct2oNP","type":"function","function":{"name":"read_website","arguments":"{\"url\": + \"https://www.example.com/ai-agent-market-analysis-2023\"}"}},{"id":"call_1GRSbggp4SYHg5WqAUQx5Dce","type":"function","function":{"name":"read_website","arguments":"{\"url\": + \"https://www.example.com/ai-agent-competitor-data-2023\"}"}},{"id":"call_43s9ebATowN3hA5piPjL2z5N","type":"function","function":{"name":"read_website","arguments":"{\"url\": + \"https://www.example.com/ai-agent-industry-trends-2023\"}"}}]},{"role":"tool","tool_call_id":"call_sOaxpAdq5dvpRhUJMAct2oNP","name":"read_website","content":"Content + from https://www.example.com/ai-agent-market-analysis-2023: This article discusses + key insights about the topic including market size ($50B), growth rate (15% + YoY), and major players in the industry."},{"role":"tool","tool_call_id":"call_1GRSbggp4SYHg5WqAUQx5Dce","name":"read_website","content":"Content + from https://www.example.com/ai-agent-competitor-data-2023: This article discusses + key insights about the topic including market size ($50B), growth rate (15% + YoY), and major players in the industry."},{"role":"tool","tool_call_id":"call_43s9ebATowN3hA5piPjL2z5N","name":"read_website","content":"Content + from https://www.example.com/ai-agent-industry-trends-2023: This article discusses + key insights about the topic including market size ($50B), growth rate (15% + YoY), and major players in the industry."},{"role":"assistant","content":null,"tool_calls":[{"id":"call_QjEKbvT6OnKkCOTvwTu0TmAK","type":"function","function":{"name":"generate_report","arguments":"{\"title\":\"Current + State of the AI Agent Market 2023\",\"findings\":\"1. Market Size: The AI agent + market is currently valued at $50 billion.\\n2. Growth Rate: The industry is + experiencing a growth rate of 15% year-over-year.\\n3. Major Players: Significant + companies in this space include tech giants and specialized AI startups.\\n4. + Market Trends: The demand for AI agents is being driven by improvements in machine + learning algorithms and increasing adoption in customer service and automation + processes.\\n5. Competitive Landscape: The market is competitive with ongoing + innovation and investment in developing more advanced AI capabilities.\\n6. + Future Prospects: Continued growth is expected as businesses further integrate + AI agents into their operations for efficiency gains and customer engagement.\"}"}}]},{"role":"tool","tool_call_id":"call_QjEKbvT6OnKkCOTvwTu0TmAK","name":"generate_report","content":"# + Current State of the AI Agent Market 2023\n\n## Executive Summary\n1. Market + Size: The AI agent market is currently valued at $50 billion.\n2. Growth Rate: + The industry is experiencing a growth rate of 15% year-over-year.\n3. Major + Players: Significant companies in this space include tech giants and specialized + AI startups.\n4. Market Trends: The demand for AI agents is being driven by + improvements in machine learning algorithms and increasing adoption in customer + service and automation processes.\n5. Competitive Landscape: The market is competitive + with ongoing innovation and investment in developing more advanced AI capabilities.\n6. + Future Prospects: Continued growth is expected as businesses further integrate + AI agents into their operations for efficiency gains and customer engagement.\n\n## + Conclusion\nBased on the analysis, the market shows strong growth potential."}],"model":"gpt-4o","tool_choice":"auto","tools":[{"type":"function","function":{"name":"web_search","description":"Search + the web for information on a given topic.\n\nArgs:\n query: The search query + to look up.\n\nReturns:\n Search results as a string.","strict":true,"parameters":{"properties":{"query":{"title":"Query","type":"string"}},"required":["query"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"read_website","description":"Read + and extract content from a website URL.\n\nArgs:\n url: The URL of the website + to read.\n\nReturns:\n The extracted content from the website.","strict":true,"parameters":{"properties":{"url":{"title":"Url","type":"string"}},"required":["url"],"type":"object","additionalProperties":false}}},{"type":"function","function":{"name":"generate_report","description":"Generate + a structured report based on research findings.\n\nArgs:\n title: The title + of the report.\n findings: The research findings to include.\n\nReturns:\n A + formatted report string.","strict":true,"parameters":{"properties":{"title":{"title":"Title","type":"string"},"findings":{"title":"Findings","type":"string"}},"required":["title","findings"],"type":"object","additionalProperties":false}}}]}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + authorization: + - AUTHORIZATION-XXX + connection: + - keep-alive + content-length: + - '5771' + content-type: + - application/json + cookie: + - COOKIE-XXX + host: + - api.openai.com + x-stainless-arch: + - X-STAINLESS-ARCH-XXX + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - X-STAINLESS-OS-XXX + x-stainless-package-version: + - 1.83.0 + x-stainless-read-timeout: + - X-STAINLESS-READ-TIMEOUT-XXX + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.13.3 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-D5FuCTJhAII96iBV05ECT71cV6QmJ\",\n \"object\": + \"chat.completion\",\n \"created\": 1770145152,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"The research on the current state of + the AI agent market indicates a robust and rapidly growing industry. Here\u2019s + a summary of the findings:\\n\\n1. **Market Size**: The AI agent market is + currently valued at $50 billion.\\n2. **Growth Rate**: It is experiencing + a significant growth rate of 15% year-over-year.\\n3. **Major Players**: The + market is dominated by tech giants and specialized AI startups.\\n4. **Market + Trends**: The increasing adoption of machine learning algorithms and the integration + of AI in customer service and automation are key drivers.\\n5. **Competitive + Landscape**: There is substantial competition and continuous innovation in + AI capabilities.\\n6. **Future Prospects**: The sector is expected to keep + growing as businesses increasingly use AI agents for efficiency and improved + customer engagement.\\n\\nThis analysis highlights significant opportunities + in the AI agent sector, underlining its importance in future technological + advancements.\",\n \"refusal\": null,\n \"annotations\": []\n + \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 920,\n \"completion_tokens\": + 182,\n \"total_tokens\": 1102,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_fa7f5b168b\"\n}\n" + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 03 Feb 2026 18:59:16 GMT + Server: + - cloudflare + Strict-Transport-Security: + - STS-XXX + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - X-CONTENT-TYPE-XXX + access-control-expose-headers: + - ACCESS-CONTROL-XXX + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-organization: + - OPENAI-ORG-XXX + openai-processing-ms: + - '3456' + openai-project: + - OPENAI-PROJECT-XXX + openai-version: + - '2020-10-01' + x-openai-proxy-wasm: + - v0.1 + x-ratelimit-limit-requests: + - X-RATELIMIT-LIMIT-REQUESTS-XXX + x-ratelimit-limit-tokens: + - X-RATELIMIT-LIMIT-TOKENS-XXX + x-ratelimit-remaining-requests: + - X-RATELIMIT-REMAINING-REQUESTS-XXX + x-ratelimit-remaining-tokens: + - X-RATELIMIT-REMAINING-TOKENS-XXX + x-ratelimit-reset-requests: + - X-RATELIMIT-RESET-REQUESTS-XXX + x-ratelimit-reset-tokens: + - X-RATELIMIT-RESET-TOKENS-XXX + x-request-id: + - X-REQUEST-ID-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/utilities/test_planning_types.py b/lib/crewai/tests/utilities/test_planning_types.py new file mode 100644 index 000000000..8a84ffe50 --- /dev/null +++ b/lib/crewai/tests/utilities/test_planning_types.py @@ -0,0 +1,389 @@ +"""Tests for planning types (PlanStep, TodoItem, TodoList).""" + +import pytest +from uuid import UUID + +from crewai.utilities.planning_types import ( + PlanStep, + TodoItem, + TodoList, + TodoStatus, +) + + +class TestPlanStep: + """Tests for the PlanStep model.""" + + def test_plan_step_with_required_fields(self): + """Test PlanStep creation with only required fields.""" + step = PlanStep( + step_number=1, + description="Research the topic", + ) + + assert step.step_number == 1 + assert step.description == "Research the topic" + assert step.tool_to_use is None + assert step.depends_on == [] + + def test_plan_step_with_all_fields(self): + """Test PlanStep creation with all fields.""" + step = PlanStep( + step_number=2, + description="Search for information", + tool_to_use="search_tool", + depends_on=[1], + ) + + assert step.step_number == 2 + assert step.description == "Search for information" + assert step.tool_to_use == "search_tool" + assert step.depends_on == [1] + + def test_plan_step_with_multiple_dependencies(self): + """Test PlanStep with multiple dependencies.""" + step = PlanStep( + step_number=4, + description="Synthesize results", + depends_on=[1, 2, 3], + ) + + assert step.depends_on == [1, 2, 3] + + def test_plan_step_requires_step_number(self): + """Test that step_number is required.""" + with pytest.raises(ValueError): + PlanStep(description="Missing step number") + + def test_plan_step_requires_description(self): + """Test that description is required.""" + with pytest.raises(ValueError): + PlanStep(step_number=1) + + def test_plan_step_serialization(self): + """Test PlanStep can be serialized to dict.""" + step = PlanStep( + step_number=1, + description="Test step", + tool_to_use="test_tool", + depends_on=[], + ) + + data = step.model_dump() + assert data["step_number"] == 1 + assert data["description"] == "Test step" + assert data["tool_to_use"] == "test_tool" + assert data["depends_on"] == [] + + +class TestTodoItem: + """Tests for the TodoItem model.""" + + def test_todo_item_with_required_fields(self): + """Test TodoItem creation with only required fields.""" + todo = TodoItem( + step_number=1, + description="First task", + ) + + assert todo.step_number == 1 + assert todo.description == "First task" + assert todo.status == "pending" + assert todo.tool_to_use is None + assert todo.depends_on == [] + assert todo.result is None + # ID should be auto-generated + assert todo.id is not None + # Verify it's a valid UUID + UUID(todo.id) + + def test_todo_item_with_all_fields(self): + """Test TodoItem creation with all fields.""" + todo = TodoItem( + id="custom-id-123", + step_number=2, + description="Second task", + tool_to_use="search_tool", + status="running", + depends_on=[1], + result="Task completed", + ) + + assert todo.id == "custom-id-123" + assert todo.step_number == 2 + assert todo.description == "Second task" + assert todo.tool_to_use == "search_tool" + assert todo.status == "running" + assert todo.depends_on == [1] + assert todo.result == "Task completed" + + def test_todo_item_status_values(self): + """Test all valid status values.""" + for status in ["pending", "running", "completed"]: + todo = TodoItem( + step_number=1, + description="Test", + status=status, + ) + assert todo.status == status + + def test_todo_item_auto_generates_unique_ids(self): + """Test that each TodoItem gets a unique auto-generated ID.""" + todo1 = TodoItem(step_number=1, description="Task 1") + todo2 = TodoItem(step_number=2, description="Task 2") + + assert todo1.id != todo2.id + + def test_todo_item_serialization(self): + """Test TodoItem can be serialized to dict.""" + todo = TodoItem( + step_number=1, + description="Test task", + status="pending", + ) + + data = todo.model_dump() + assert "id" in data + assert data["step_number"] == 1 + assert data["description"] == "Test task" + assert data["status"] == "pending" + + +class TestTodoList: + """Tests for the TodoList model.""" + + @pytest.fixture + def empty_todo_list(self): + """Create an empty TodoList.""" + return TodoList() + + @pytest.fixture + def sample_todo_list(self): + """Create a TodoList with sample items.""" + return TodoList( + items=[ + TodoItem(step_number=1, description="Step 1", status="completed"), + TodoItem(step_number=2, description="Step 2", status="running"), + TodoItem(step_number=3, description="Step 3", status="pending"), + TodoItem(step_number=4, description="Step 4", status="pending"), + ] + ) + + def test_empty_todo_list(self, empty_todo_list): + """Test empty TodoList properties.""" + assert empty_todo_list.items == [] + assert empty_todo_list.current_todo is None + assert empty_todo_list.next_pending is None + assert empty_todo_list.is_complete is False + assert empty_todo_list.pending_count == 0 + assert empty_todo_list.completed_count == 0 + + def test_current_todo_property(self, sample_todo_list): + """Test current_todo returns the running item.""" + current = sample_todo_list.current_todo + assert current is not None + assert current.step_number == 2 + assert current.status == "running" + + def test_current_todo_returns_none_when_no_running(self): + """Test current_todo returns None when no running items.""" + todo_list = TodoList( + items=[ + TodoItem(step_number=1, description="Step 1", status="completed"), + TodoItem(step_number=2, description="Step 2", status="pending"), + ] + ) + assert todo_list.current_todo is None + + def test_next_pending_property(self, sample_todo_list): + """Test next_pending returns the first pending item.""" + next_item = sample_todo_list.next_pending + assert next_item is not None + assert next_item.step_number == 3 + assert next_item.status == "pending" + + def test_next_pending_returns_none_when_no_pending(self): + """Test next_pending returns None when no pending items.""" + todo_list = TodoList( + items=[ + TodoItem(step_number=1, description="Step 1", status="completed"), + TodoItem(step_number=2, description="Step 2", status="completed"), + ] + ) + assert todo_list.next_pending is None + + def test_is_complete_property_when_complete(self): + """Test is_complete returns True when all items completed.""" + todo_list = TodoList( + items=[ + TodoItem(step_number=1, description="Step 1", status="completed"), + TodoItem(step_number=2, description="Step 2", status="completed"), + ] + ) + assert todo_list.is_complete is True + + def test_is_complete_property_when_not_complete(self, sample_todo_list): + """Test is_complete returns False when items are pending.""" + assert sample_todo_list.is_complete is False + + def test_is_complete_false_for_empty_list(self, empty_todo_list): + """Test is_complete returns False for empty list.""" + assert empty_todo_list.is_complete is False + + def test_pending_count(self, sample_todo_list): + """Test pending_count returns correct count.""" + assert sample_todo_list.pending_count == 2 + + def test_completed_count(self, sample_todo_list): + """Test completed_count returns correct count.""" + assert sample_todo_list.completed_count == 1 + + def test_get_by_step_number(self, sample_todo_list): + """Test get_by_step_number returns correct item.""" + item = sample_todo_list.get_by_step_number(3) + assert item is not None + assert item.step_number == 3 + assert item.description == "Step 3" + + def test_get_by_step_number_returns_none_for_missing(self, sample_todo_list): + """Test get_by_step_number returns None for non-existent step.""" + item = sample_todo_list.get_by_step_number(99) + assert item is None + + def test_mark_running(self, sample_todo_list): + """Test mark_running changes status correctly.""" + sample_todo_list.mark_running(3) + item = sample_todo_list.get_by_step_number(3) + assert item.status == "running" + + def test_mark_running_does_nothing_for_missing(self, sample_todo_list): + """Test mark_running handles missing step gracefully.""" + # Should not raise an error + sample_todo_list.mark_running(99) + + def test_mark_completed(self, sample_todo_list): + """Test mark_completed changes status correctly.""" + sample_todo_list.mark_completed(3) + item = sample_todo_list.get_by_step_number(3) + assert item.status == "completed" + assert item.result is None + + def test_mark_completed_with_result(self, sample_todo_list): + """Test mark_completed with result.""" + sample_todo_list.mark_completed(3, result="Task output") + item = sample_todo_list.get_by_step_number(3) + assert item.status == "completed" + assert item.result == "Task output" + + def test_mark_completed_does_nothing_for_missing(self, sample_todo_list): + """Test mark_completed handles missing step gracefully.""" + # Should not raise an error + sample_todo_list.mark_completed(99, result="Some result") + + def test_todo_list_workflow(self): + """Test a complete workflow through TodoList.""" + # Create a todo list with 3 items + todo_list = TodoList( + items=[ + TodoItem( + step_number=1, + description="Research", + tool_to_use="search_tool", + ), + TodoItem( + step_number=2, + description="Analyze", + depends_on=[1], + ), + TodoItem( + step_number=3, + description="Report", + depends_on=[1, 2], + ), + ] + ) + + # Initial state + assert todo_list.pending_count == 3 + assert todo_list.completed_count == 0 + assert todo_list.is_complete is False + + # Start first task + todo_list.mark_running(1) + assert todo_list.current_todo.step_number == 1 + assert todo_list.next_pending.step_number == 2 + + # Complete first task + todo_list.mark_completed(1, result="Research done") + assert todo_list.current_todo is None + assert todo_list.completed_count == 1 + + # Start and complete second task + todo_list.mark_running(2) + todo_list.mark_completed(2, result="Analysis complete") + assert todo_list.completed_count == 2 + + # Start and complete third task + todo_list.mark_running(3) + todo_list.mark_completed(3, result="Report generated") + + # Final state + assert todo_list.is_complete is True + assert todo_list.pending_count == 0 + assert todo_list.completed_count == 3 + assert todo_list.current_todo is None + assert todo_list.next_pending is None + + +class TestTodoFromPlanStep: + """Tests for converting PlanStep to TodoItem.""" + + def test_convert_plan_step_to_todo_item(self): + """Test converting a PlanStep to TodoItem.""" + step = PlanStep( + step_number=1, + description="Search for information", + tool_to_use="search_tool", + depends_on=[], + ) + + todo = TodoItem( + step_number=step.step_number, + description=step.description, + tool_to_use=step.tool_to_use, + depends_on=step.depends_on, + status="pending", + ) + + assert todo.step_number == step.step_number + assert todo.description == step.description + assert todo.tool_to_use == step.tool_to_use + assert todo.depends_on == step.depends_on + assert todo.status == "pending" + + def test_convert_multiple_plan_steps_to_todo_list(self): + """Test converting multiple PlanSteps to a TodoList.""" + steps = [ + PlanStep(step_number=1, description="Step 1", tool_to_use="tool1"), + PlanStep(step_number=2, description="Step 2", depends_on=[1]), + PlanStep(step_number=3, description="Step 3", depends_on=[1, 2]), + ] + + todos = [] + for step in steps: + todo = TodoItem( + step_number=step.step_number, + description=step.description, + tool_to_use=step.tool_to_use, + depends_on=step.depends_on, + status="pending", + ) + todos.append(todo) + + todo_list = TodoList(items=todos) + + assert len(todo_list.items) == 3 + assert todo_list.pending_count == 3 + assert todo_list.items[0].tool_to_use == "tool1" + assert todo_list.items[1].depends_on == [1] + assert todo_list.items[2].depends_on == [1, 2] diff --git a/lib/crewai/tests/utilities/test_structured_planning.py b/lib/crewai/tests/utilities/test_structured_planning.py new file mode 100644 index 000000000..91bca9c0d --- /dev/null +++ b/lib/crewai/tests/utilities/test_structured_planning.py @@ -0,0 +1,698 @@ +"""Tests for structured planning with steps and todo generation. + +These tests verify that the planning system correctly generates structured +PlanStep objects and converts them to TodoItems across different LLM providers. +""" + +import json +import os +from unittest.mock import MagicMock, Mock, patch + +import pytest + +from crewai import Agent, PlanningConfig, Task +from crewai.llm import LLM +from crewai.utilities.planning_types import PlanStep, TodoItem, TodoList +from crewai.utilities.reasoning_handler import ( + FUNCTION_SCHEMA, + AgentReasoning, + ReasoningPlan, +) + + +class TestFunctionSchema: + """Tests for the FUNCTION_SCHEMA used in structured planning.""" + + def test_schema_has_required_structure(self): + """Test that FUNCTION_SCHEMA has the correct structure.""" + assert FUNCTION_SCHEMA["type"] == "function" + assert "function" in FUNCTION_SCHEMA + assert FUNCTION_SCHEMA["function"]["name"] == "create_reasoning_plan" + + def test_schema_parameters_structure(self): + """Test that parameters have correct structure.""" + params = FUNCTION_SCHEMA["function"]["parameters"] + assert params["type"] == "object" + assert "properties" in params + assert "required" in params + + def test_schema_has_plan_property(self): + """Test that schema includes plan property.""" + props = FUNCTION_SCHEMA["function"]["parameters"]["properties"] + assert "plan" in props + assert props["plan"]["type"] == "string" + + def test_schema_has_steps_property(self): + """Test that schema includes steps array property.""" + props = FUNCTION_SCHEMA["function"]["parameters"]["properties"] + assert "steps" in props + assert props["steps"]["type"] == "array" + + def test_schema_steps_items_structure(self): + """Test that steps items have correct structure.""" + items = FUNCTION_SCHEMA["function"]["parameters"]["properties"]["steps"]["items"] + assert items["type"] == "object" + assert "properties" in items + assert "required" in items + assert "additionalProperties" in items + assert items["additionalProperties"] is False + + def test_schema_step_properties(self): + """Test that step items have all required properties.""" + step_props = FUNCTION_SCHEMA["function"]["parameters"]["properties"]["steps"]["items"]["properties"] + + assert "step_number" in step_props + assert step_props["step_number"]["type"] == "integer" + + assert "description" in step_props + assert step_props["description"]["type"] == "string" + + assert "tool_to_use" in step_props + # tool_to_use should be nullable + assert step_props["tool_to_use"]["type"] == ["string", "null"] + + assert "depends_on" in step_props + assert step_props["depends_on"]["type"] == "array" + + def test_schema_step_required_fields(self): + """Test that step required fields are correct.""" + required = FUNCTION_SCHEMA["function"]["parameters"]["properties"]["steps"]["items"]["required"] + assert "step_number" in required + assert "description" in required + assert "tool_to_use" in required + assert "depends_on" in required + + def test_schema_has_ready_property(self): + """Test that schema includes ready property.""" + props = FUNCTION_SCHEMA["function"]["parameters"]["properties"] + assert "ready" in props + assert props["ready"]["type"] == "boolean" + + def test_schema_top_level_required(self): + """Test that top-level required fields are correct.""" + required = FUNCTION_SCHEMA["function"]["parameters"]["required"] + assert "plan" in required + assert "steps" in required + assert "ready" in required + + def test_schema_top_level_additional_properties(self): + """Test that additionalProperties is False at top level.""" + params = FUNCTION_SCHEMA["function"]["parameters"] + assert params["additionalProperties"] is False + + +class TestReasoningPlan: + """Tests for the ReasoningPlan model with structured steps.""" + + def test_reasoning_plan_with_empty_steps(self): + """Test ReasoningPlan can be created with empty steps.""" + plan = ReasoningPlan( + plan="Simple plan", + steps=[], + ready=True, + ) + + assert plan.plan == "Simple plan" + assert plan.steps == [] + assert plan.ready is True + + def test_reasoning_plan_with_steps(self): + """Test ReasoningPlan with structured steps.""" + steps = [ + PlanStep(step_number=1, description="First step", tool_to_use="tool1"), + PlanStep(step_number=2, description="Second step", depends_on=[1]), + ] + + plan = ReasoningPlan( + plan="Multi-step plan", + steps=steps, + ready=True, + ) + + assert plan.plan == "Multi-step plan" + assert len(plan.steps) == 2 + assert plan.steps[0].step_number == 1 + assert plan.steps[1].depends_on == [1] + + +class TestAgentReasoningWithMockedLLM: + """Tests for AgentReasoning with mocked LLM responses.""" + + @pytest.fixture + def mock_agent(self): + """Create a mock agent for testing.""" + agent = MagicMock() + agent.role = "Test Agent" + agent.goal = "Test goal" + agent.backstory = "Test backstory" + agent.verbose = False + agent.planning_config = PlanningConfig() + agent.i18n = MagicMock() + agent.i18n.retrieve.return_value = "Test prompt: {description}" + # Mock the llm attribute + agent.llm = MagicMock() + agent.llm.supports_function_calling.return_value = True + return agent + + def test_parse_steps_from_function_response(self, mock_agent): + """Test that steps are correctly parsed from LLM function response.""" + # Mock the LLM response with structured steps + mock_response = json.dumps({ + "plan": "Research and analyze", + "steps": [ + { + "step_number": 1, + "description": "Search for information", + "tool_to_use": "search_tool", + "depends_on": [], + }, + { + "step_number": 2, + "description": "Analyze results", + "tool_to_use": None, + "depends_on": [1], + }, + ], + "ready": True, + }) + + mock_agent.llm.call.return_value = mock_response + + handler = AgentReasoning( + agent=mock_agent, + task=None, + description="Test task", + expected_output="Test output", + ) + + # Call the function parsing method + plan, steps, ready = handler._call_with_function( + prompt="Test prompt", + plan_type="create_plan", + ) + + assert plan == "Research and analyze" + assert len(steps) == 2 + assert steps[0].step_number == 1 + assert steps[0].tool_to_use == "search_tool" + assert steps[1].depends_on == [1] + assert ready is True + + def test_parse_steps_handles_missing_optional_fields(self, mock_agent): + """Test that missing optional fields are handled correctly.""" + mock_response = json.dumps({ + "plan": "Simple plan", + "steps": [ + { + "step_number": 1, + "description": "Do something", + "tool_to_use": None, + "depends_on": [], + }, + ], + "ready": True, + }) + + mock_agent.llm.call.return_value = mock_response + + handler = AgentReasoning( + agent=mock_agent, + task=None, + description="Test task", + expected_output="Test output", + ) + + plan, steps, ready = handler._call_with_function( + prompt="Test prompt", + plan_type="create_plan", + ) + + assert len(steps) == 1 + assert steps[0].tool_to_use is None + assert steps[0].depends_on == [] + + def test_parse_steps_with_missing_fields_uses_defaults(self, mock_agent): + """Test that steps with missing fields get default values.""" + mock_response = json.dumps({ + "plan": "Plan with step missing fields", + "steps": [ + {"step_number": 1, "description": "Valid step", "tool_to_use": None, "depends_on": []}, + {"step_number": 2}, # Missing description, tool_to_use, depends_on + {"step_number": 3, "description": "Another valid", "tool_to_use": None, "depends_on": []}, + ], + "ready": True, + }) + + mock_agent.llm.call.return_value = mock_response + + handler = AgentReasoning( + agent=mock_agent, + task=None, + description="Test task", + expected_output="Test output", + ) + + plan, steps, ready = handler._call_with_function( + prompt="Test prompt", + plan_type="create_plan", + ) + + # All 3 steps should be parsed, with defaults for missing fields + assert len(steps) == 3 + assert steps[0].step_number == 1 + assert steps[0].description == "Valid step" + assert steps[1].step_number == 2 + assert steps[1].description == "" # Default value + assert steps[2].step_number == 3 + + +class TestTodoCreationFromPlan: + """Tests for converting plan steps to todo items.""" + + def test_create_todos_from_plan_steps(self): + """Test creating TodoList from PlanSteps.""" + steps = [ + PlanStep( + step_number=1, + description="Research competitors", + tool_to_use="search_tool", + depends_on=[], + ), + PlanStep( + step_number=2, + description="Analyze data", + tool_to_use=None, + depends_on=[1], + ), + PlanStep( + step_number=3, + description="Generate report", + tool_to_use="write_tool", + depends_on=[1, 2], + ), + ] + + # Convert steps to todos (mirroring agent_executor._create_todos_from_plan) + todos = [] + for step in steps: + todo = TodoItem( + step_number=step.step_number, + description=step.description, + tool_to_use=step.tool_to_use, + depends_on=step.depends_on, + status="pending", + ) + todos.append(todo) + + todo_list = TodoList(items=todos) + + assert len(todo_list.items) == 3 + assert todo_list.pending_count == 3 + assert todo_list.completed_count == 0 + + # Verify todo properties match step properties + assert todo_list.items[0].description == "Research competitors" + assert todo_list.items[0].tool_to_use == "search_tool" + assert todo_list.items[1].depends_on == [1] + assert todo_list.items[2].depends_on == [1, 2] + + +# ============================================================================= +# Provider-Specific Integration Tests (VCR recorded) +# ============================================================================= + + +# Common test tools used across provider tests +def create_research_tools(): + """Create research tools for testing structured planning.""" + from crewai.tools import tool + + @tool + def web_search(query: str) -> str: + """Search the web for information on a given topic. + + Args: + query: The search query to look up. + + Returns: + Search results as a string. + """ + # Simulated search results for testing + return f"Search results for '{query}': Found 3 relevant articles about the topic including market analysis, competitor data, and industry trends." + + @tool + def read_website(url: str) -> str: + """Read and extract content from a website URL. + + Args: + url: The URL of the website to read. + + Returns: + The extracted content from the website. + """ + # Simulated website content for testing + return f"Content from {url}: This article discusses key insights about the topic including market size ($50B), growth rate (15% YoY), and major players in the industry." + + @tool + def generate_report(title: str, findings: str) -> str: + """Generate a structured report based on research findings. + + Args: + title: The title of the report. + findings: The research findings to include. + + Returns: + A formatted report string. + """ + return f"# {title}\n\n## Executive Summary\n{findings}\n\n## Conclusion\nBased on the analysis, the market shows strong growth potential." + + return web_search, read_website, generate_report + + +RESEARCH_TASK = """Research the current state of the AI agent market: +1. Search for recent information about AI agents and their market trends +2. Read detailed content from a relevant industry source +3. Generate a brief report summarizing the key findings + +Use the available tools for each step.""" + + +class TestOpenAIStructuredPlanning: + """Integration tests for OpenAI structured planning with research workflow.""" + + @pytest.mark.vcr() + def test_openai_research_workflow_generates_steps(self): + """Test that OpenAI generates structured plan steps for a research task.""" + web_search, read_website, generate_report = create_research_tools() + llm = LLM(model="gpt-4o") + + agent = Agent( + role="Research Analyst", + goal="Conduct thorough research and produce insightful reports", + backstory="An experienced analyst skilled at gathering information and synthesizing findings into actionable insights.", + llm=llm, + tools=[web_search, read_website, generate_report], + planning_config=PlanningConfig(max_attempts=1), + verbose=False, + ) + + result = agent.kickoff(RESEARCH_TASK) + + # Verify result exists + assert result is not None + assert result.raw is not None + # The result should contain some report-like content + assert len(str(result.raw)) > 50 + + +class TestAnthropicStructuredPlanning: + """Integration tests for Anthropic structured planning with research workflow.""" + + @pytest.fixture(autouse=True) + def mock_anthropic_api_key(self): + """Mock API key if not set.""" + if "ANTHROPIC_API_KEY" not in os.environ: + with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}): + yield + else: + yield + + @pytest.mark.vcr() + def test_anthropic_research_workflow_generates_steps(self): + """Test that Anthropic generates structured plan steps for a research task.""" + web_search, read_website, generate_report = create_research_tools() + llm = LLM(model="anthropic/claude-sonnet-4-20250514") + + agent = Agent( + role="Research Analyst", + goal="Conduct thorough research and produce insightful reports", + backstory="An experienced analyst skilled at gathering information and synthesizing findings into actionable insights.", + llm=llm, + tools=[web_search, read_website, generate_report], + planning_config=PlanningConfig(max_attempts=1), + verbose=False, + ) + + result = agent.kickoff(RESEARCH_TASK) + + # Verify result exists + assert result is not None + assert result.raw is not None + # The result should contain some report-like content + assert len(str(result.raw)) > 50 + + +class TestGeminiStructuredPlanning: + """Integration tests for Google Gemini structured planning with research workflow.""" + + @pytest.fixture(autouse=True) + def mock_google_api_key(self): + """Mock API key if not set.""" + if "GOOGLE_API_KEY" not in os.environ and "GEMINI_API_KEY" not in os.environ: + with patch.dict(os.environ, {"GOOGLE_API_KEY": "test-key"}): + yield + else: + yield + + @pytest.mark.vcr() + def test_gemini_research_workflow_generates_steps(self): + """Test that Gemini generates structured plan steps for a research task.""" + web_search, read_website, generate_report = create_research_tools() + llm = LLM(model="gemini/gemini-2.5-flash") + + agent = Agent( + role="Research Analyst", + goal="Conduct thorough research and produce insightful reports", + backstory="An experienced analyst skilled at gathering information and synthesizing findings into actionable insights.", + llm=llm, + tools=[web_search, read_website, generate_report], + planning_config=PlanningConfig(max_attempts=1), + verbose=False, + ) + + result = agent.kickoff(RESEARCH_TASK) + + # Verify result exists + assert result is not None + assert result.raw is not None + # The result should contain some report-like content + assert len(str(result.raw)) > 50 + + +class TestAzureStructuredPlanning: + """Integration tests for Azure OpenAI structured planning with research workflow.""" + + @pytest.fixture(autouse=True) + def mock_azure_credentials(self): + """Mock Azure credentials for tests.""" + if "AZURE_API_KEY" not in os.environ: + with patch.dict(os.environ, { + "AZURE_API_KEY": "test-key", + "AZURE_ENDPOINT": "https://test.openai.azure.com" + }): + yield + else: + yield + + @pytest.mark.vcr() + def test_azure_research_workflow_generates_steps(self): + """Test that Azure OpenAI generates structured plan steps for a research task.""" + web_search, read_website, generate_report = create_research_tools() + llm = LLM(model="azure/gpt-4o") + + agent = Agent( + role="Research Analyst", + goal="Conduct thorough research and produce insightful reports", + backstory="An experienced analyst skilled at gathering information and synthesizing findings into actionable insights.", + llm=llm, + tools=[web_search, read_website, generate_report], + planning_config=PlanningConfig(max_attempts=1), + verbose=False, + ) + + result = agent.kickoff(RESEARCH_TASK) + + # Verify result exists + assert result is not None + assert result.raw is not None + # The result should contain some report-like content + assert len(str(result.raw)) > 50 + + +# ============================================================================= +# Unit Tests with Mocked LLM Providers +# ============================================================================= + + +class TestStructuredPlanningWithMockedProviders: + """Unit tests with mocked LLM providers for faster execution.""" + + def _create_mock_plan_response(self, steps_data): + """Helper to create mock plan response.""" + return json.dumps({ + "plan": "Test plan", + "steps": steps_data, + "ready": True, + }) + + def test_openai_mock_structured_response(self): + """Test parsing OpenAI structured response.""" + steps_data = [ + {"step_number": 1, "description": "Search", "tool_to_use": "search", "depends_on": []}, + {"step_number": 2, "description": "Analyze", "tool_to_use": None, "depends_on": [1]}, + ] + + response = self._create_mock_plan_response(steps_data) + parsed = json.loads(response) + + assert len(parsed["steps"]) == 2 + assert parsed["steps"][0]["tool_to_use"] == "search" + assert parsed["steps"][1]["depends_on"] == [1] + + def test_anthropic_mock_structured_response(self): + """Test parsing Anthropic structured response (same format).""" + steps_data = [ + {"step_number": 1, "description": "Research", "tool_to_use": "web_search", "depends_on": []}, + {"step_number": 2, "description": "Summarize", "tool_to_use": None, "depends_on": [1]}, + {"step_number": 3, "description": "Report", "tool_to_use": "write_file", "depends_on": [1, 2]}, + ] + + response = self._create_mock_plan_response(steps_data) + parsed = json.loads(response) + + assert len(parsed["steps"]) == 3 + assert parsed["steps"][2]["depends_on"] == [1, 2] + + def test_gemini_mock_structured_response(self): + """Test parsing Gemini structured response (same format).""" + steps_data = [ + {"step_number": 1, "description": "Gather data", "tool_to_use": "data_tool", "depends_on": []}, + {"step_number": 2, "description": "Process", "tool_to_use": None, "depends_on": [1]}, + ] + + response = self._create_mock_plan_response(steps_data) + parsed = json.loads(response) + + assert len(parsed["steps"]) == 2 + assert parsed["ready"] is True + + def test_azure_mock_structured_response(self): + """Test parsing Azure OpenAI structured response (same format as OpenAI).""" + steps_data = [ + {"step_number": 1, "description": "Initialize", "tool_to_use": None, "depends_on": []}, + {"step_number": 2, "description": "Execute", "tool_to_use": "executor", "depends_on": [1]}, + {"step_number": 3, "description": "Finalize", "tool_to_use": None, "depends_on": [1, 2]}, + ] + + response = self._create_mock_plan_response(steps_data) + parsed = json.loads(response) + + assert len(parsed["steps"]) == 3 + assert parsed["steps"][0]["tool_to_use"] is None + + +class TestTodoListIntegration: + """Integration tests for TodoList with plan execution simulation.""" + + def test_full_plan_execution_workflow(self): + """Test complete workflow from plan to todos to execution.""" + # Simulate plan steps from LLM + plan_steps = [ + PlanStep( + step_number=1, + description="Research the topic", + tool_to_use="search_tool", + depends_on=[], + ), + PlanStep( + step_number=2, + description="Compile findings", + tool_to_use=None, + depends_on=[1], + ), + PlanStep( + step_number=3, + description="Generate summary", + tool_to_use="summarize_tool", + depends_on=[1, 2], + ), + ] + + # Convert to todos (like agent_executor._create_todos_from_plan) + todos = [ + TodoItem( + step_number=step.step_number, + description=step.description, + tool_to_use=step.tool_to_use, + depends_on=step.depends_on, + status="pending", + ) + for step in plan_steps + ] + todo_list = TodoList(items=todos) + + # Verify initial state + assert todo_list.pending_count == 3 + assert todo_list.is_complete is False + + # Simulate execution + for i in range(1, 4): + todo_list.mark_running(i) + assert todo_list.current_todo.step_number == i + todo_list.mark_completed(i, result=f"Step {i} completed") + + # Verify final state + assert todo_list.is_complete is True + assert todo_list.completed_count == 3 + assert all(item.result is not None for item in todo_list.items) + + def test_dependency_aware_execution(self): + """Test that dependencies are respected in execution order.""" + steps = [ + PlanStep(step_number=1, description="Base step", depends_on=[]), + PlanStep(step_number=2, description="Depends on 1", depends_on=[1]), + PlanStep(step_number=3, description="Depends on 1", depends_on=[1]), + PlanStep(step_number=4, description="Depends on 2 and 3", depends_on=[2, 3]), + ] + + todos = [ + TodoItem( + step_number=s.step_number, + description=s.description, + depends_on=s.depends_on, + ) + for s in steps + ] + todo_list = TodoList(items=todos) + + # Helper to check if dependencies are satisfied + def can_execute(todo: TodoItem) -> bool: + for dep in todo.depends_on: + dep_todo = todo_list.get_by_step_number(dep) + if dep_todo and dep_todo.status != "completed": + return False + return True + + # Step 1 has no dependencies + assert can_execute(todo_list.items[0]) is True + + # Steps 2 and 3 depend on 1 (not yet done) + assert can_execute(todo_list.items[1]) is False + assert can_execute(todo_list.items[2]) is False + + # Complete step 1 + todo_list.mark_completed(1) + + # Now steps 2 and 3 can execute + assert can_execute(todo_list.items[1]) is True + assert can_execute(todo_list.items[2]) is True + + # Step 4 still can't (depends on 2 and 3) + assert can_execute(todo_list.items[3]) is False + + # Complete steps 2 and 3 + todo_list.mark_completed(2) + todo_list.mark_completed(3) + + # Now step 4 can execute + assert can_execute(todo_list.items[3]) is True