From 254d1c8e9d2a8c2d9d8951e3e50d614649a78b32 Mon Sep 17 00:00:00 2001 From: lorenzejay Date: Wed, 26 Nov 2025 09:12:58 -0800 Subject: [PATCH 1/5] supporting thinking for anthropic models --- docs/en/concepts/llms.mdx | 44 +++++++ .../llms/providers/anthropic/completion.py | 6 +- .../cassettes/test_anthropic_thinking.yaml | 122 ++++++++++++++++++ .../tests/llms/anthropic/test_anthropic.py | 45 ++++++- 4 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 lib/crewai/tests/cassettes/test_anthropic_thinking.yaml diff --git a/docs/en/concepts/llms.mdx b/docs/en/concepts/llms.mdx index 1ebfafd3d3..9f31b9415b 100644 --- a/docs/en/concepts/llms.mdx +++ b/docs/en/concepts/llms.mdx @@ -283,11 +283,54 @@ In this section, you'll find detailed examples that help you select, configure, ) ``` + **Extended Thinking (Claude Sonnet 4 and Beyond):** + + CrewAI supports Anthropic's Extended Thinking feature, which allows Claude to think through problems in a more human-like way before responding. This is particularly useful for complex reasoning, analysis, and problem-solving tasks. + + ```python Code + from crewai import LLM + + # Enable extended thinking with default settings + llm = LLM( + model="anthropic/claude-sonnet-4", + thinking={"type": "enabled"}, + max_tokens=10000 + ) + + # Configure thinking with budget control + llm = LLM( + model="anthropic/claude-sonnet-4", + thinking={ + "type": "enabled", + "budget_tokens": 5000 # Limit thinking tokens + }, + max_tokens=10000 + ) + ``` + + **Thinking Configuration Options:** + - `type`: Set to `"enabled"` to activate extended thinking mode + - `budget_tokens` (optional): Maximum tokens to use for thinking (helps control costs) + + **Models Supporting Extended Thinking:** + - `claude-sonnet-4` and newer models + - `claude-3-7-sonnet` (with extended thinking capabilities) + + **When to Use Extended Thinking:** + - Complex reasoning and multi-step problem solving + - Mathematical calculations and proofs + - Code analysis and debugging + - Strategic planning and decision making + - Research and analytical tasks + + **Note:** Extended thinking consumes additional tokens but can significantly improve response quality for complex tasks. + **Supported Environment Variables:** - `ANTHROPIC_API_KEY`: Your Anthropic API key (required) **Features:** - Native tool use support for Claude 3+ models + - Extended Thinking support for Claude Sonnet 4+ - Streaming support for real-time responses - Automatic system message handling - Stop sequences for controlled output @@ -305,6 +348,7 @@ In this section, you'll find detailed examples that help you select, configure, | Model | Context Window | Best For | |------------------------------|----------------|-----------------------------------------------| + | claude-sonnet-4 | 200,000 tokens | Latest with extended thinking capabilities | | claude-3-7-sonnet | 200,000 tokens | Advanced reasoning and agentic tasks | | claude-3-5-sonnet-20241022 | 200,000 tokens | Latest Sonnet with best performance | | claude-3-5-haiku | 200,000 tokens | Fast, compact model for quick responses | diff --git a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py index ea161fc635..652ee3da7f 100644 --- a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py +++ b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py @@ -52,6 +52,7 @@ def __init__( stream: bool = False, client_params: dict[str, Any] | None = None, interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None, + thinking: dict[str, Any] | None = None, **kwargs: Any, ): """Initialize Anthropic chat completion client. @@ -89,7 +90,7 @@ def __init__( self.top_p = top_p self.stream = stream self.stop_sequences = stop_sequences or [] - + self.thinking = thinking # Model-specific settings self.is_claude_3 = "claude-3" in model.lower() self.supports_tools = self.is_claude_3 # Claude 3+ supports tool use @@ -252,6 +253,9 @@ def _prepare_completion_params( if tools and self.supports_tools: params["tools"] = self._convert_tools_for_interference(tools) + if self.thinking: + params["thinking"] = self.thinking + return params def _convert_tools_for_interference( diff --git a/lib/crewai/tests/cassettes/test_anthropic_thinking.yaml b/lib/crewai/tests/cassettes/test_anthropic_thinking.yaml new file mode 100644 index 0000000000..8a22c6ee26 --- /dev/null +++ b/lib/crewai/tests/cassettes/test_anthropic_thinking.yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: '{"max_tokens":10000,"messages":[{"role":"user","content":"What is the weather + in Tokyo?"}],"model":"claude-sonnet-4-5","stream":false,"thinking":{"type":"enabled","budget_tokens":5000}}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '185' + content-type: + - application/json + host: + - api.anthropic.com + user-agent: + - Anthropic/Python 0.75.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 0.75.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: !!binary | + H4sIAAAAAAAAAwAAAP//hJNdr6o4GIX/SsPNXIjbD/xAk3OhiIiyZasI4uxkUmkFBFpsy4f75Pz3 + CU72PjOTyZyrNm/WWu9K0+e7lFGEU2kqBSksEG5zSggW7UF72O53+8PupD+RZClG0lTKePhHtzeo + vMQjm621jXbB3apfy6MTbyRZEo8cNyrMOQyxJEuMps0Ach5zAYmQZCmgRGAipOnv3z/1IopJEpOw + Sfi8TiUnwqDgmIGYA8ibIcgwgBdaCCAiDIKCMUwEqDAUUSMjwKHJg76AFa1wiZkMTIAo+U2ACJYY + wCDAnANBAcMwbYs4wyAmV8oyKGJKZBCTIC1Qs+ff0QElKG5E/AWYgEe0SBFI8bNHBhJCKyAiKIAJ + AtjsyxktY/RfFb/WAUgQ4EUYYi5A9AzADxA8k68xQUBEMf+74UWSJR6HBIqCNY+ms2ShJb6pRaG2 + SRYxKo4IGeFJ88ywQ4ptYvST/ai6kfRUWkZy7GgByjU9+ugeNMtZwbi/qm19TY38SJR0LLbWSHGx + 07MyVd3ODVsYt4Uefhw42u+9QutPWpfwEnG4WIR6vP6YxC70t7hUiWZWSydeP4I7bUX9JOrtkJJk + W9VLbgfDHSmm+qhIoYxXd7O3UtTOCfOQ3CLYJYwNB2dFW/Y295nG3BmZ7V6z1ezijBkcdWsbovTi + ozl8qOb6TrpZy7xdnVw7l4OROuK0YMVB25da2FuuD+Ftm26LnZLYnRPOjGprCe1YuaWXtd7Ouj9z + W/PCNy3DCvhmtwwYsxfeil6KfWtvJZdKqz+83B7ekxQRmrVUdDesszLcLOqxPTg583l/fbtk+n2j + L2Z1NkrXTgdmV66og+H89Co67jLprVl5K7eHFJdmHYyx0XH7kbtfDpA6r52ra9rzYbgx/OH4auuW + r78Nc3j3rkd/sIJqy1F07VR4F19bmG8t1PU8pYv2+0FL7O4DrF1VcVQ1Ha3z0l5lll/Dw80IcN7P + B8GEr6+FueP2Yuzl+s7P6t79sRlN5gGH/Gzs3XNkcMUux841JxVx+ditJxd2sm5vNTyIdc4mxGDG + TP8m/ZB/QonrBtfnMZV+SdLnF0dQQBlw+sWCwGkKHrT4BbPv5J04FIRY/C81V8r+csjPzCcv03fS + BlqEg+TLUuELjwXmII2Tr24vAc3kpnzxjwFlX4KQlk3WAUMWROBPusqlJYIYCMJ/JfRJYVhcHwhz + EY8KHvW0EppMMZM104l5KIvMf5fMOD7xmq86VXSgsqPZaaU7qnFZDiotHNJbQR24T1D8ac4hKC81 + X1Rh8IvkwSa79NYtBxZ1hwwfvfO9NezUdQ8xhzW3OtqPvOn9Zh+O62JuUp2MqO61IXsFl9YuXELO + 61YDXJgX82rzcEXTY0Mp+6AjOHmhliCdziUKfYCE5wIxoFaKcw2VubjbN7ISStbZP0EStednDRk2 + A7SJmF9C/xScrDyCu//YOlvvRxgwIrLTF+Nf/RfdDr/p1JAv+fvR9vK0oYT4Yg10tojUUv1uOo4d + TdM7AAAA//8DANemNnfhBgAA + headers: + CF-RAY: + - 9a4b033548b28ea2-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 26 Nov 2025 17:12:50 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - 32982f3d-354e-4063-a1d9-6e3e0361bcfe + anthropic-ratelimit-input-tokens-limit: + - '30000' + anthropic-ratelimit-input-tokens-remaining: + - '30000' + anthropic-ratelimit-input-tokens-reset: + - '2025-11-26T17:12:47Z' + anthropic-ratelimit-output-tokens-limit: + - '8000' + anthropic-ratelimit-output-tokens-remaining: + - '8000' + anthropic-ratelimit-output-tokens-reset: + - '2025-11-26T17:12:51Z' + anthropic-ratelimit-requests-limit: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-11-26T17:12:46Z' + anthropic-ratelimit-tokens-limit: + - '38000' + anthropic-ratelimit-tokens-remaining: + - '38000' + anthropic-ratelimit-tokens-reset: + - '2025-11-26T17:12:47Z' + cf-cache-status: + - DYNAMIC + request-id: + - req_011CVWsgwRugLxGNv45mLXEL + retry-after: + - '13' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '4553' + 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 72e3149b61..7fc31362ed 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -12,8 +12,13 @@ @pytest.fixture(autouse=True) def mock_anthropic_api_key(): - """Automatically mock ANTHROPIC_API_KEY for all tests in this module.""" - with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}): + """Automatically mock ANTHROPIC_API_KEY for all tests in this module if not already set.""" + # Only mock if ANTHROPIC_API_KEY is not already set (for VCR recording) + if "ANTHROPIC_API_KEY" not in os.environ: + with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}): + yield + else: + # Real API key exists, don't override it (needed for VCR recording) yield @@ -698,3 +703,39 @@ def test_anthropic_stop_sequences_sent_to_api(): assert result is not None assert isinstance(result, str) assert len(result) > 0 + +@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"]) +def test_anthropic_thinking(): + """Test that thinking is properly handled and thinking params are passed to messages.create""" + from unittest.mock import patch + from crewai.llms.providers.anthropic.completion import AnthropicCompletion + + llm = LLM( + model="anthropic/claude-sonnet-4-5", + thinking={"type": "enabled", "budget_tokens": 5000}, + max_tokens=10000 + ) + + assert isinstance(llm, AnthropicCompletion) + + original_create = llm.client.messages.create + captured_params = {} + + def capture_and_call(**kwargs): + captured_params.update(kwargs) + return original_create(**kwargs) + + with patch.object(llm.client.messages, 'create', side_effect=capture_and_call): + result = llm.call("What is the weather in Tokyo?") + + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + + assert "thinking" in captured_params + assert captured_params["thinking"] == {"type": "enabled", "budget_tokens": 5000} + + assert captured_params["model"] == "claude-sonnet-4-5" + assert captured_params["max_tokens"] == 10000 + assert "messages" in captured_params + assert len(captured_params["messages"]) > 0 From cd4f0876cbbd85ee105ac5fc4af8e1f035d335a4 Mon Sep 17 00:00:00 2001 From: lorenzejay Date: Wed, 26 Nov 2025 09:14:27 -0800 Subject: [PATCH 2/5] drop comments here --- lib/crewai/tests/llms/anthropic/test_anthropic.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/crewai/tests/llms/anthropic/test_anthropic.py b/lib/crewai/tests/llms/anthropic/test_anthropic.py index 7fc31362ed..ba9fede873 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -13,12 +13,10 @@ @pytest.fixture(autouse=True) def mock_anthropic_api_key(): """Automatically mock ANTHROPIC_API_KEY for all tests in this module if not already set.""" - # Only mock if ANTHROPIC_API_KEY is not already set (for VCR recording) if "ANTHROPIC_API_KEY" not in os.environ: with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}): yield else: - # Real API key exists, don't override it (needed for VCR recording) yield From e508dcaabbd16e894ed3272f48d200971c35e0e9 Mon Sep 17 00:00:00 2001 From: lorenzejay Date: Wed, 26 Nov 2025 12:27:52 -0800 Subject: [PATCH 3/5] thinking and tool calling support --- .../llms/providers/anthropic/completion.py | 119 +++++++++- .../test_anthropic_function_calling.yaml | 219 +++++++++++++++++ ...hinking_blocks_preserved_across_turns.yaml | 222 ++++++++++++++++++ .../tests/llms/anthropic/test_anthropic.py | 128 ++++++++++ 4 files changed, 678 insertions(+), 10 deletions(-) create mode 100644 lib/crewai/tests/cassettes/test_anthropic_function_calling.yaml create mode 100644 lib/crewai/tests/cassettes/test_anthropic_thinking_blocks_preserved_across_turns.yaml diff --git a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py index 652ee3da7f..d370bbb6ba 100644 --- a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py +++ b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py @@ -22,8 +22,7 @@ try: from anthropic import Anthropic - from anthropic.types import Message - from anthropic.types.tool_use_block import ToolUseBlock + from anthropic.types import Message, TextBlock, ThinkingBlock, ToolUseBlock import httpx except ImportError: raise ImportError( @@ -91,9 +90,16 @@ def __init__( self.stream = stream self.stop_sequences = stop_sequences or [] self.thinking = thinking + self.previous_thinking_blocks: list[ThinkingBlock] = [] # Model-specific settings self.is_claude_3 = "claude-3" in model.lower() - self.supports_tools = self.is_claude_3 # Claude 3+ supports tool use + self.supports_tools = ( + "claude-3" in model.lower() + or "claude-4" in model.lower() + or "claude-sonnet-4" in model.lower() + or "claude-opus-4" in model.lower() + or "claude-haiku-4" in model.lower() + ) @property def stop(self) -> list[str]: @@ -295,6 +301,34 @@ def _convert_tools_for_interference( return anthropic_tools + def _extract_thinking_block( + self, content_block: Any + ) -> ThinkingBlock | dict[str, Any] | None: + """Extract and format thinking block from content block. + + Args: + content_block: Content block from Anthropic response + + Returns: + Dictionary with thinking block data including signature, or None if not a thinking block + """ + if content_block.type == "thinking": + thinking_block = { + "type": "thinking", + "thinking": content_block.thinking, + } + if hasattr(content_block, "signature"): + thinking_block["signature"] = content_block.signature + return thinking_block + if content_block.type == "redacted_thinking": + redacted_block = {"type": "redacted_thinking"} + if hasattr(content_block, "thinking"): + redacted_block["thinking"] = content_block.thinking + if hasattr(content_block, "signature"): + redacted_block["signature"] = content_block.signature + return redacted_block + return None + def _format_messages_for_anthropic( self, messages: str | list[LLMMessage] ) -> tuple[list[LLMMessage], str | None]: @@ -304,6 +338,7 @@ def _format_messages_for_anthropic( - System messages are separate from conversation messages - Messages must alternate between user and assistant - First message must be from user + - When thinking is enabled, assistant messages must start with thinking blocks Args: messages: Input messages @@ -328,8 +363,29 @@ def _format_messages_for_anthropic( system_message = cast(str, content) else: role_str = role if role is not None else "user" - content_str = content if content is not None else "" - formatted_messages.append({"role": role_str, "content": content_str}) + + if isinstance(content, list): + formatted_messages.append({"role": role_str, "content": content}) + elif ( + role_str == "assistant" + and self.thinking + and self.previous_thinking_blocks + ): + structured_content = cast( + list[dict[str, Any]], + [ + *self.previous_thinking_blocks, + {"type": "text", "text": content if content else ""}, + ], + ) + formatted_messages.append( + LLMMessage(role=role_str, content=structured_content) + ) + else: + content_str = content if content is not None else "" + formatted_messages.append( + LLMMessage(role=role_str, content=content_str) + ) # Ensure first message is from user (Anthropic requirement) if not formatted_messages: @@ -379,7 +435,6 @@ def _handle_completion( if tool_uses and tool_uses[0].name == "structured_output": structured_data = tool_uses[0].input structured_json = json.dumps(structured_data) - self._emit_call_completed_event( response=structured_json, call_type=LLMCallType.LLM_CALL, @@ -407,15 +462,22 @@ def _handle_completion( from_agent, ) - # Extract text content content = "" + thinking_blocks: list[ThinkingBlock] = [] + if response.content: for content_block in response.content: if hasattr(content_block, "text"): content += content_block.text + else: + thinking_block = self._extract_thinking_block(content_block) + if thinking_block: + thinking_blocks.append(cast(ThinkingBlock, thinking_block)) - content = self._apply_stop_words(content) + if thinking_blocks: + self.previous_thinking_blocks = thinking_blocks + content = self._apply_stop_words(content) self._emit_call_completed_event( response=content, call_type=LLMCallType.LLM_CALL, @@ -468,6 +530,16 @@ def _handle_streaming_completion( final_message: Message = stream.get_final_message() + thinking_blocks: list[ThinkingBlock] = [] + if final_message.content: + for content_block in final_message.content: + thinking_block = self._extract_thinking_block(content_block) + if thinking_block: + thinking_blocks.append(cast(ThinkingBlock, thinking_block)) + + if thinking_blocks: + self.previous_thinking_blocks = thinking_blocks + usage = self._extract_anthropic_token_usage(final_message) self._track_token_usage_internal(usage) @@ -570,7 +642,26 @@ def _handle_tool_use_conversation( follow_up_params = params.copy() # Add Claude's tool use response to conversation - assistant_message = {"role": "assistant", "content": initial_response.content} + assistant_content: list[ + ThinkingBlock | ToolUseBlock | TextBlock | dict[str, Any] + ] = [] + for block in initial_response.content: + thinking_block = self._extract_thinking_block(block) + if thinking_block: + assistant_content.append(thinking_block) + elif block.type == "tool_use": + assistant_content.append( + { + "type": "tool_use", + "id": block.id, + "name": block.name, + "input": block.input, + } + ) + elif hasattr(block, "text"): + assistant_content.append({"type": "text", "text": block.text}) + + assistant_message = {"role": "assistant", "content": assistant_content} # Add user message with tool results user_message = {"role": "user", "content": tool_results} @@ -589,12 +680,20 @@ def _handle_tool_use_conversation( follow_up_usage = self._extract_anthropic_token_usage(final_response) self._track_token_usage_internal(follow_up_usage) - # Extract final text content final_content = "" + thinking_blocks: list[ThinkingBlock] = [] + if final_response.content: for content_block in final_response.content: if hasattr(content_block, "text"): final_content += content_block.text + else: + thinking_block = self._extract_thinking_block(content_block) + if thinking_block: + thinking_blocks.append(cast(ThinkingBlock, thinking_block)) + + if thinking_blocks: + self.previous_thinking_blocks = thinking_blocks final_content = self._apply_stop_words(final_content) diff --git a/lib/crewai/tests/cassettes/test_anthropic_function_calling.yaml b/lib/crewai/tests/cassettes/test_anthropic_function_calling.yaml new file mode 100644 index 0000000000..e03c219ba0 --- /dev/null +++ b/lib/crewai/tests/cassettes/test_anthropic_function_calling.yaml @@ -0,0 +1,219 @@ +interactions: +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"What is the weather + in Tokyo? Use the get_weather tool."}],"model":"claude-sonnet-4-5","stream":false,"tools":[{"name":"get_weather","description":"Get + the current weather in a given location","input_schema":{"type":"object","properties":{"location":{"type":"string","description":"The + city and state, e.g. San Francisco, CA"},"unit":{"type":"string","enum":["celsius","fahrenheit"],"description":"The + unit of temperature"}},"required":["location"]}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '509' + content-type: + - application/json + host: + - api.anthropic.com + user-agent: + - Anthropic/Python 0.71.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 0.71.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: !!binary | + H4sIAAAAAAAAA3SQzU7DMBCE32XPjpQEUqhviCKkcmn5qVQhZBlnSaI6drDXlDbKuyMHFQqI4843 + s6PdHlpbogYOSstQYuKtMUjJaVIkeZoX6TSfAoOmBA6tr0SaPSwXLrt7uZ6vFup9ttpfLCd0WwED + 2nUYXei9rBAYOKujIL1vPElDwEBZQ2gI+GN/8JO1WgSPh5Y4B5FmV/a52p5Xs9nkBqv1fJ81u/Xl + GTAwso25CklsUVKNLkZNFwh4D9oqSY01wOHebnYWhuGJgSfbCYfSj+CocgQeXwMahcBN0JpBGA/g + /edWQXaDxgOf5CkDJVWNQjkcW8RPxxd3KMv/2CEbC7CrsUUntSjav/5vmtW/6cDABjqWihMGHt1b + o1BQgw44xLeX0pUwDB8AAAD//wMA6heqv+kBAAA= + headers: + CF-RAY: + - 9a4c0d75ba3d6800-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 26 Nov 2025 20:14:34 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - 32982f3d-354e-4063-a1d9-6e3e0361bcfe + anthropic-ratelimit-input-tokens-limit: + - '30000' + anthropic-ratelimit-input-tokens-remaining: + - '30000' + anthropic-ratelimit-input-tokens-reset: + - '2025-11-26T20:14:33Z' + anthropic-ratelimit-output-tokens-limit: + - '8000' + anthropic-ratelimit-output-tokens-remaining: + - '8000' + anthropic-ratelimit-output-tokens-reset: + - '2025-11-26T20:14:34Z' + anthropic-ratelimit-requests-limit: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-11-26T20:14:32Z' + anthropic-ratelimit-tokens-limit: + - '38000' + anthropic-ratelimit-tokens-remaining: + - '38000' + anthropic-ratelimit-tokens-reset: + - '2025-11-26T20:14:33Z' + cf-cache-status: + - DYNAMIC + request-id: + - req_011CVX7YqtSMhTjqLnjZvaj1 + retry-after: + - '28' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '2874' + status: + code: 200 + message: OK +- request: + body: "{\"max_tokens\":4096,\"messages\":[{\"role\":\"user\",\"content\":\"What + is the weather in Tokyo? Use the get_weather tool.\"},{\"role\":\"assistant\",\"content\":[{\"type\":\"tool_use\",\"id\":\"toolu_01Eobgw8gDD6KegYJz1iyYC7\",\"name\":\"get_weather\",\"input\":{\"location\":\"Tokyo\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"toolu_01Eobgw8gDD6KegYJz1iyYC7\",\"content\":\"The + weather in Tokyo is sunny and 72\xB0F\"}]}],\"model\":\"claude-sonnet-4-5\",\"stream\":false,\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get + the current weather in a given location\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"The + city and state, e.g. San Francisco, CA\"},\"unit\":{\"type\":\"string\",\"enum\":[\"celsius\",\"fahrenheit\"],\"description\":\"The + unit of temperature\"}},\"required\":[\"location\"]}}]}" + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '800' + content-type: + - application/json + host: + - api.anthropic.com + user-agent: + - Anthropic/Python 0.71.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 0.71.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: !!binary | + H4sIAAAAAAAAA3SQTWrDMBBGryJm1YJcHNE0RMsGktLu2mxKKUZYQ6zGHjnSqIkJvlPOkJMVh4b+ + 0dXA994ww7eHxlusQUNZm2Qxi54IObvOxpnK1TifqilIcBY0NHFV5KO5fWsebv32WW0XT/d3oxnu + HhcbkMBdi4OFMZoVgoTg6yEwMbrIhhgklJ4YiUG/7M8+424gp6FhWaHYouEKg3Akln7deeGiKFMI + SFx3IiaiThiyYqKOh7m4MG0b/M41hrHuhFLHw+zyCvpXCZF9WwQ00RNoQLIFp0DwCSJuElKJoCnV + tYR0elvvwVGbuGC/Roqgb6YTCaUpKyzKgIadp+KnkZ95QGP/Y+fd4QC2FTYYTF2Mm7/+Fx1Vv2kv + wSf+HiklIWJ4dyUW7DCAhqFsa4KFvv8AAAD//wMANWIHid8BAAA= + headers: + CF-RAY: + - 9a4c0d886f9e6800-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 26 Nov 2025 20:14:36 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - 32982f3d-354e-4063-a1d9-6e3e0361bcfe + anthropic-ratelimit-input-tokens-limit: + - '30000' + anthropic-ratelimit-input-tokens-remaining: + - '30000' + anthropic-ratelimit-input-tokens-reset: + - '2025-11-26T20:14:36Z' + anthropic-ratelimit-output-tokens-limit: + - '8000' + anthropic-ratelimit-output-tokens-remaining: + - '8000' + anthropic-ratelimit-output-tokens-reset: + - '2025-11-26T20:14:36Z' + anthropic-ratelimit-requests-limit: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-11-26T20:14:35Z' + anthropic-ratelimit-tokens-limit: + - '38000' + anthropic-ratelimit-tokens-remaining: + - '38000' + anthropic-ratelimit-tokens-reset: + - '2025-11-26T20:14:36Z' + cf-cache-status: + - DYNAMIC + request-id: + - req_011CVX7Z4is92Sz8oA63UNxC + retry-after: + - '26' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '2240' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/test_anthropic_thinking_blocks_preserved_across_turns.yaml b/lib/crewai/tests/cassettes/test_anthropic_thinking_blocks_preserved_across_turns.yaml new file mode 100644 index 0000000000..3b5b70ad51 --- /dev/null +++ b/lib/crewai/tests/cassettes/test_anthropic_thinking_blocks_preserved_across_turns.yaml @@ -0,0 +1,222 @@ +interactions: +- request: + body: '{"max_tokens":10000,"messages":[{"role":"user","content":"What is 2+2?"}],"model":"claude-sonnet-4-5","stream":false,"thinking":{"type":"enabled","budget_tokens":5000}}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '168' + content-type: + - application/json + host: + - api.anthropic.com + user-agent: + - Anthropic/Python 0.71.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 0.71.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: !!binary | + H4sIAAAAAAAAAwAAAP//dJJbj6JAEIX/CqlXcBYRvJDMg3hXcJzVZS6bDelAIS1NI3SjMhP/+4bJ + mtlL9qkq9Z06L+e8Q5ZHyMCGkJEqwpbIOUfZMltWy9ANSx8YA9CARmBDJvaB3u4f1kXqmPlrvvR4 + bJXhjPgHAhrI+oiNCoUgewQNypw1ByIEFZJwCRqEOZfIJdjf3296mVCeUr5vHG6rDbuECoUKhSiC + ZkeGCimpTDKUNFSKCoWkOb9TDNVQsKgIE4p5BxoIuudEVmXjO5HCGaUvi1GyH63S4bp8MLH3+FA7 + Un02+tPXlXvava3zx7G/3fne3Bk/rGTEdf2xroTsqdnKceu4MqzVxlu2Txfamzi+wZ/o7uyMh8XW + Wmw328n+re7HQ8PR1fPQWnS445HxzBlZbvW6e/Gn5y9fu2W9OO85O0cTGlvr5dOa0faCvYzbh03/ + sLSQMevLud1zlt8G7i5Vaxx1J3PuRjKLfS9i4bQbs1W6nsre2J+5SVTIPHGeLxezyJz+YrU69rzB + UFW9tt6J5Xanmt5hM68HmPiX43qiP81JTsL5crjR3W6x8NLTQZI4RDyQlI3dDpkNJ/dw1T7zwEuT + 1MewwVBUxVDuFROuPzQQMj8GJRKRc7ABeRTIquTwCwgsKuQhgs0rxjSoPnpgvwPlx0oGMk+RC7DN + jgYhCRMMwhJJE2Twp0C/8RJJ9D92+2388ZhghiVhgZX9q/+k7eRvetUgr+Tvp05XA4HliYYYSIol + 2NCUNyJlBNfrTwAAAP//AwD5LVCFLwMAAA== + headers: + CF-RAY: + - 9a4bfbbe0e3b26b0-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 26 Nov 2025 20:02:28 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - 32982f3d-354e-4063-a1d9-6e3e0361bcfe + anthropic-ratelimit-input-tokens-limit: + - '30000' + anthropic-ratelimit-input-tokens-remaining: + - '30000' + anthropic-ratelimit-input-tokens-reset: + - '2025-11-26T20:02:28Z' + anthropic-ratelimit-output-tokens-limit: + - '8000' + anthropic-ratelimit-output-tokens-remaining: + - '8000' + anthropic-ratelimit-output-tokens-reset: + - '2025-11-26T20:02:28Z' + anthropic-ratelimit-requests-limit: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-11-26T20:02:26Z' + anthropic-ratelimit-tokens-limit: + - '38000' + anthropic-ratelimit-tokens-remaining: + - '38000' + anthropic-ratelimit-tokens-reset: + - '2025-11-26T20:02:28Z' + cf-cache-status: + - DYNAMIC + request-id: + - req_011CVX6dM9aWdpYdmnHARrXh + retry-after: + - '35' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '2775' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":10000,"messages":[{"role":"user","content":"What is 2+2?"},{"role":"assistant","content":[{"type":"thinking","thinking":"This + is a simple arithmetic question. 2+2 equals 4.","signature":"EtsBCkYIChgCKkANrO4e7QOyBt+X28FZKLvTzNoQDVSTVMHBDOKtdn00Qyust7+mKBLyfu25KPMJ1vxi7EBV2nWiTwBDAqS5ISPSEgzy8fA2B0+wA5I3nBMaDGBC5LuZTYVFw/R6ryIwgnlwdEif5NJWNli1IlYD1jP8jJ5ell5/w17BJU9LTk+yeC6EHnLdtmfVMdlcF6flKkNFt7DVGLhdqtohBXxx4qmB8IKKp7M9A++M103ftST+4MjPHy9ehVxpNE0WHaoacHJAP0L6qIMkvjtafceejaklDL3aGAE="},{"type":"text","text":"2 + + 2 = 4"}]},{"role":"user","content":"Now what is 3+3?"}],"model":"claude-sonnet-4-5","stream":false,"thinking":{"type":"enabled","budget_tokens":5000}}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '681' + content-type: + - application/json + host: + - api.anthropic.com + user-agent: + - Anthropic/Python 0.71.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 0.71.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: !!binary | + H4sIAAAAAAAAAwAAAP//dJJbc6JAEIX/CtWvwURQUajKAyJe4jUGEd3aokYYYQQGZIYIpvzvW6TW + zV5qn7qrv9PnoU9/QJL6OAYNvBgVPm6wlFLMG+1GpyE35U5TlVUQgfigQcICtynZg93JKmUrP676 + RqRcncXr69QCEXiV4VqFGUMBBhHyNK4HiDHCOKIcRPBSyjHloH37uOt5SGhEaFA73FsNrJAwgTAB + CYzniAQhP6b5BeW+gHLCwwRz4gnnAjNOUvootIQHoSU8C8ojiMBIQBEv8trdLMy+Ee0mRhgY08gI + 9Y01WlOyUZFMl14fIV5J64dSyTpWheyNtLyO+PRQIUXfT9vKcdsbt9DSsQeDUElOUknG22wgq6dg + G7wtV/tqli2cuRlc5Hn/Ye4508nQGPOu1UMDw5A27y1HovHTm9l2SjK5TJq0v8/R1hm3PX2SssQJ + omsvvRpnc9gcGvP4YNtqudKV1L6envbtWVZZzswazgev2ctwx6ZR5G+qve33jd1ZL9F40euG0bmX + RM1DNl322Vtw6q6k97EtKX7lqNZMduyhWarkOsbt6cqXuvEmLC7labY4X4/Julr3DnpyGnkv567p + B3pRViPdfIab+JUOLuvcPosGv84Mt+8iMJ5mbo4RSylogKnv8iKn8BMwfC4w9TBotIhjEYrPr9A+ + gNCs4C5PI0wZaEpXBA95IXa9HKM6TfdPQfPOc4z8/7H7bu2PsxAnOEex20n+1X9RKfyb3kRIC/77 + qKWKwHD+TjzscoJz0KB+ZR/lPtxuPwAAAP//AwBO51LgPQMAAA== + headers: + CF-RAY: + - 9a4bfbd00b1426b0-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 26 Nov 2025 20:02:31 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-organization-id: + - 32982f3d-354e-4063-a1d9-6e3e0361bcfe + anthropic-ratelimit-input-tokens-limit: + - '30000' + anthropic-ratelimit-input-tokens-remaining: + - '30000' + anthropic-ratelimit-input-tokens-reset: + - '2025-11-26T20:02:30Z' + anthropic-ratelimit-output-tokens-limit: + - '8000' + anthropic-ratelimit-output-tokens-remaining: + - '8000' + anthropic-ratelimit-output-tokens-reset: + - '2025-11-26T20:02:31Z' + anthropic-ratelimit-requests-limit: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-11-26T20:02:29Z' + anthropic-ratelimit-tokens-limit: + - '38000' + anthropic-ratelimit-tokens-remaining: + - '38000' + anthropic-ratelimit-tokens-reset: + - '2025-11-26T20:02:30Z' + cf-cache-status: + - DYNAMIC + request-id: + - req_011CVX6dZXDfHGDm98QN81RZ + retry-after: + - '30' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-envoy-upstream-service-time: + - '2540' + 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 ba9fede873..eb0cb681c7 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -737,3 +737,131 @@ def capture_and_call(**kwargs): assert captured_params["max_tokens"] == 10000 assert "messages" in captured_params assert len(captured_params["messages"]) > 0 + + +@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"]) +def test_anthropic_thinking_blocks_preserved_across_turns(): + """Test that thinking blocks are stored and included in subsequent API calls across turns""" + from unittest.mock import patch + from crewai.llms.providers.anthropic.completion import AnthropicCompletion + + llm = LLM( + model="anthropic/claude-sonnet-4-5", + thinking={"type": "enabled", "budget_tokens": 5000}, + max_tokens=10000 + ) + + assert isinstance(llm, AnthropicCompletion) + + # Capture all messages.create calls to verify thinking blocks are included + original_create = llm.client.messages.create + captured_calls = [] + + def capture_and_call(**kwargs): + captured_calls.append(kwargs) + return original_create(**kwargs) + + with patch.object(llm.client.messages, 'create', side_effect=capture_and_call): + # First call - establishes context and generates thinking blocks + messages = [{"role": "user", "content": "What is 2+2?"}] + first_result = llm.call(messages) + + # Verify first call completed + assert first_result is not None + assert isinstance(first_result, str) + assert len(first_result) > 0 + + # Verify thinking blocks were stored after first response + assert len(llm.previous_thinking_blocks) > 0, "No thinking blocks stored after first call" + first_thinking = llm.previous_thinking_blocks[0] + assert first_thinking["type"] == "thinking" + assert "thinking" in first_thinking + assert "signature" in first_thinking + + # Store the thinking block content for comparison + stored_thinking_content = first_thinking["thinking"] + stored_signature = first_thinking["signature"] + + # Second call - should include thinking blocks from first call + messages.append({"role": "assistant", "content": first_result}) + messages.append({"role": "user", "content": "Now what is 3+3?"}) + second_result = llm.call(messages) + + # Verify second call completed + assert second_result is not None + assert isinstance(second_result, str) + + # Verify at least 2 API calls were made + assert len(captured_calls) >= 2, f"Expected at least 2 API calls, got {len(captured_calls)}" + + # Verify second call includes thinking blocks in assistant message + second_call_messages = captured_calls[1]["messages"] + + # Should have: user message + assistant message (with thinking blocks) + follow-up user message + assert len(second_call_messages) >= 2 + + # Find the assistant message in the second call + assistant_message = None + for msg in second_call_messages: + if msg["role"] == "assistant" and isinstance(msg.get("content"), list): + assistant_message = msg + break + + assert assistant_message is not None, "Assistant message with list content not found in second call" + assert isinstance(assistant_message["content"], list) + + # Verify thinking block is included in assistant message content + thinking_found = False + for block in assistant_message["content"]: + if isinstance(block, dict) and block.get("type") == "thinking": + thinking_found = True + assert "thinking" in block + assert "signature" in block + # Verify it matches what was stored from the first call + assert block["thinking"] == stored_thinking_content + assert block["signature"] == stored_signature + break + + assert thinking_found, "Thinking block not found in assistant message content in second call" + +@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"]) +def test_anthropic_function_calling(): + """Test that function calling is properly handled""" + llm = LLM(model="anthropic/claude-sonnet-4-5") + + def get_weather(location: str) -> str: + return f"The weather in {location} is sunny and 72°F" + + tools = [ + { + "name": "get_weather", + "description": "Get the current weather in a given location", + "input_schema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"], + "description": "The unit of temperature" + } + }, + "required": ["location"] + } + } + ] + + result = llm.call( + "What is the weather in Tokyo? Use the get_weather tool.", + tools=tools, + available_functions={"get_weather": get_weather} + ) + + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # Verify the response includes information about Tokyo's weather + assert "tokyo" in result.lower() or "72" in result From 17160cc6470743c029ce96e6c86e0205b3bed564 Mon Sep 17 00:00:00 2001 From: lorenzejay Date: Wed, 26 Nov 2025 12:32:16 -0800 Subject: [PATCH 4/5] fix: properly mock tool use and text block types in Anthropic tests - Updated the test for the Anthropic tool use conversation flow to include type attributes for mocked ToolUseBlock and text blocks, ensuring accurate simulation of tool interactions during testing. --- lib/crewai/tests/llms/anthropic/test_anthropic.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/crewai/tests/llms/anthropic/test_anthropic.py b/lib/crewai/tests/llms/anthropic/test_anthropic.py index eb0cb681c7..9152c8594e 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -66,6 +66,7 @@ def mock_weather_tool(location: str) -> str: 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"} @@ -78,6 +79,7 @@ def mock_weather_tool(location: str) -> str: # 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.") From d2ce1089756a1377c6398c1583c1e35d96623845 Mon Sep 17 00:00:00 2001 From: lorenzejay Date: Mon, 8 Dec 2025 15:21:58 -0800 Subject: [PATCH 5/5] feat: add AnthropicThinkingConfig for enhanced thinking capabilities This update introduces the AnthropicThinkingConfig class to manage thinking parameters for the Anthropic completion model. The LLM and AnthropicCompletion classes have been updated to utilize this new configuration. Additionally, new test cassettes have been added to validate the functionality of thinking blocks across interactions. --- lib/crewai/src/crewai/llm.py | 2 + .../llms/providers/anthropic/completion.py | 14 +- .../test_anthropic_function_calling.yaml | 220 +++++++++++++++++ ..._anthropic_stop_sequences_sent_to_api.yaml | 198 +++++++++++----- .../anthropic/test_anthropic_thinking.yaml | 126 ++++++++++ ...hinking_blocks_preserved_across_turns.yaml | 223 ++++++++++++++++++ 6 files changed, 719 insertions(+), 64 deletions(-) create mode 100644 lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_function_calling.yaml create mode 100644 lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_thinking.yaml create mode 100644 lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_thinking_blocks_preserved_across_turns.yaml diff --git a/lib/crewai/src/crewai/llm.py b/lib/crewai/src/crewai/llm.py index 8eb052683b..77053deeb9 100644 --- a/lib/crewai/src/crewai/llm.py +++ b/lib/crewai/src/crewai/llm.py @@ -67,6 +67,7 @@ from crewai.agent.core import Agent from crewai.llms.hooks.base import BaseInterceptor + from crewai.llms.providers.anthropic.completion import AnthropicThinkingConfig from crewai.task import Task from crewai.tools.base_tool import BaseTool from crewai.utilities.types import LLMMessage @@ -585,6 +586,7 @@ def __init__( reasoning_effort: Literal["none", "low", "medium", "high"] | None = None, stream: bool = False, interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None, + thinking: AnthropicThinkingConfig | dict[str, Any] | None = None, **kwargs: Any, ) -> None: """Initialize LLM instance. diff --git a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py index 61b118ecc9..723826ea7c 100644 --- a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py +++ b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py @@ -3,7 +3,7 @@ import json import logging import os -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, Literal, cast from anthropic.types import ThinkingBlock from pydantic import BaseModel @@ -31,6 +31,11 @@ ) from None +class AnthropicThinkingConfig(BaseModel): + type: Literal["enabled", "disabled"] + budget_tokens: int | None = None + + class AnthropicCompletion(BaseLLM): """Anthropic native completion implementation. @@ -52,7 +57,7 @@ def __init__( stream: bool = False, client_params: dict[str, Any] | None = None, interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None, - thinking: dict[str, Any] | None = None, + thinking: AnthropicThinkingConfig | None = None, **kwargs: Any, ): """Initialize Anthropic chat completion client. @@ -332,7 +337,10 @@ def _prepare_completion_params( params["tools"] = self._convert_tools_for_interference(tools) if self.thinking: - params["thinking"] = self.thinking + if isinstance(self.thinking, AnthropicThinkingConfig): + params["thinking"] = self.thinking.model_dump() + else: + params["thinking"] = self.thinking return params diff --git a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_function_calling.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_function_calling.yaml new file mode 100644 index 0000000000..8423c518ba --- /dev/null +++ b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_function_calling.yaml @@ -0,0 +1,220 @@ +interactions: +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"What is the weather + in Tokyo? Use the get_weather tool."}],"model":"claude-sonnet-4-5","stream":false,"tools":[{"name":"get_weather","description":"Get + the current weather in a given location","input_schema":{"type":"object","properties":{"location":{"type":"string","description":"The + city and state, e.g. San Francisco, CA"},"unit":{"type":"string","enum":["celsius","fahrenheit"],"description":"The + unit of temperature"}},"required":["location"]}}]}' + headers: + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '509' + 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.71.1 + 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: !!binary | + H4sIAAAAAAAAAwAAAP//dJBBS8NAEIX/isx5A0k0he5NhR60FRG9KLKsmzGJ3eyku7O1JeS/SyLV + qnic9703j5keWirRggRjdSwxCeQccnKWFEme5kU6z+cgoClBQhsqlWYXp6+rl+a8NrvIzfbuMqti + tnkEAbzvcHRhCLpCEODJjoIOoQmsHYMAQ47RMcin/uBnIqtiwEPLOEeVZuf0sFjla7pZhkUw17Pd + m1veEghwuh1zFbJ6R801+jHqusgge7BkNDfkQMI9rfckTq50px0Mw7OAwNQpjzpM/Kh5AgE3EZ1B + kC5aKyBOd8j+c7liWqMLIGd5KsBoU6MyHqcy9dPxxT3q8j92yI4F2NXYotdWFe1f/zfN6t90EECR + j6WiEBDQbxuDihv0IGH8fql9CcPwAQAA//8DAI8uRSjwAQAA + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 08 Dec 2025 23:16: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-requests-limit: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-12-08T23:16:54Z' + 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 + retry-after: + - '5' + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2925' + status: + code: 200 + message: OK +- request: + body: "{\"max_tokens\":4096,\"messages\":[{\"role\":\"user\",\"content\":\"What + is the weather in Tokyo? Use the get_weather tool.\"},{\"role\":\"assistant\",\"content\":[{\"type\":\"tool_use\",\"id\":\"toolu_01AoUFM2koNLsFscK6xjnLPo\",\"name\":\"get_weather\",\"input\":{\"location\":\"Tokyo, + Japan\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"toolu_01AoUFM2koNLsFscK6xjnLPo\",\"content\":\"The + weather in Tokyo, Japan is sunny and 72\xB0F\"}]}],\"model\":\"claude-sonnet-4-5\",\"stream\":false,\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get + the current weather in a given location\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"The + city and state, e.g. San Francisco, CA\"},\"unit\":{\"type\":\"string\",\"enum\":[\"celsius\",\"fahrenheit\"],\"description\":\"The + unit of temperature\"}},\"required\":[\"location\"]}}]}" + headers: + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '814' + 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.71.1 + 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: !!binary | + H4sIAAAAAAAAA3SQ3UoDMRBGXyXMlcKubGOrNreiiHdC70RCTIZu2t3Jmky0a9l36jP0yWSLxT+8 + GvjOGWb4ttAGhw0osI3JDssUiJDLaTkrZSVn1VzOoQDvQEGblrqaTN9bvlmt7qYPmytif++e1+cX + CAVw3+FoYUpmOQYxNGNgUvKJDTEUYAMxEoN63B59xs1IDkPBokbxhoZrjMKTWIR1H4RPwuYYkbjp + RcpEvTDkxKXc727Fiem6GDa+NYxNL6Tc765Pz2B4KiBx6HREkwKBAiSnOUeCT5DwJSNZBEW5aQrI + h7fVFjx1mTWHNVICdVlNCrDG1qhtRMM+kP5pVEce0bj/2HF3PIBdjS1G0+hZ+9f/opP6Nx0KCJm/ + R1IWkDC+eouaPUZQMJbtTHQwDB8AAAD//wMAkp8os98BAAA= + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 08 Dec 2025 23:16:59 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: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-12-08T23:16:58Z' + 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 + retry-after: + - '1' + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2878' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_stop_sequences_sent_to_api.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_stop_sequences_sent_to_api.yaml index 8759062f93..4f286b9aa1 100644 --- a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_stop_sequences_sent_to_api.yaml +++ b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_stop_sequences_sent_to_api.yaml @@ -8,8 +8,6 @@ interactions: headers: Accept: - '*/*' - Accept-Encoding: - - gzip, deflate, zstd Connection: - keep-alive Content-Length: @@ -17,9 +15,11 @@ interactions: Content-Type: - application/json User-Agent: - - CrewAI-CLI/1.3.0 + - X-USER-AGENT-XXX X-Crewai-Version: - 1.3.0 + accept-encoding: + - ACCEPT-ENCODING-XXX method: POST uri: https://app.crewai.com/crewai_plus/api/v1/tracing/batches response: @@ -37,61 +37,31 @@ interactions: cache-control: - no-store content-security-policy: - - 'default-src ''self'' *.app.crewai.com app.crewai.com; script-src ''self'' - ''unsafe-inline'' *.app.crewai.com app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts - https://www.gstatic.com https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js - https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map - https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com - https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com - https://js-na1.hs-scripts.com https://js.hubspot.com http://js-na1.hs-scripts.com - https://bat.bing.com https://cdn.amplitude.com https://cdn.segment.com https://d1d3n03t5zntha.cloudfront.net/ - https://descriptusercontent.com https://edge.fullstory.com https://googleads.g.doubleclick.net - https://js.hs-analytics.net https://js.hs-banner.com https://js.hsadspixel.net - https://js.hscollectedforms.net https://js.usemessages.com https://snap.licdn.com - https://static.cloudflareinsights.com https://static.reo.dev https://www.google-analytics.com - https://share.descript.com/; style-src ''self'' ''unsafe-inline'' *.app.crewai.com - app.crewai.com https://cdn.jsdelivr.net/npm/apexcharts; img-src ''self'' data: - *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com https://dashboard.tools.crewai.com - https://cdn.jsdelivr.net https://forms.hsforms.com https://track.hubspot.com - https://px.ads.linkedin.com https://px4.ads.linkedin.com https://www.google.com - https://www.google.com.br; font-src ''self'' data: *.app.crewai.com app.crewai.com; - connect-src ''self'' *.app.crewai.com app.crewai.com https://zeus.tools.crewai.com - https://connect.useparagon.com/ https://zeus.useparagon.com/* https://*.useparagon.com/* - https://run.pstmn.io https://connect.tools.crewai.com/ https://*.sentry.io - https://www.google-analytics.com https://edge.fullstory.com https://rs.fullstory.com - https://api.hubspot.com https://forms.hscollectedforms.net https://api.hubapi.com - https://px.ads.linkedin.com https://px4.ads.linkedin.com https://google.com/pagead/form-data/16713662509 - https://google.com/ccm/form-data/16713662509 https://www.google.com/ccm/collect - https://worker-actionkit.tools.crewai.com https://api.reo.dev; frame-src ''self'' - *.app.crewai.com app.crewai.com https://connect.useparagon.com/ https://zeus.tools.crewai.com - https://zeus.useparagon.com/* https://connect.tools.crewai.com/ https://docs.google.com - https://drive.google.com https://slides.google.com https://accounts.google.com - https://*.google.com https://app.hubspot.com/ https://td.doubleclick.net https://www.googletagmanager.com/ - https://www.youtube.com https://share.descript.com' + - CSP-FILTERED expires: - '0' permissions-policy: - - camera=(), microphone=(self), geolocation=() + - PERMISSIONS-POLICY-XXX pragma: - no-cache referrer-policy: - - strict-origin-when-cross-origin + - REFERRER-POLICY-XXX strict-transport-security: - - max-age=63072000; includeSubDomains + - STS-XXX vary: - Accept x-content-type-options: - - nosniff + - X-CONTENT-TYPE-XXX x-frame-options: - - SAMEORIGIN + - X-FRAME-OPTIONS-XXX x-permitted-cross-domain-policies: - - none + - X-PERMITTED-XXX x-request-id: - - 4124c4ce-02cf-4d08-9b0b-8983c2e9da6e + - X-REQUEST-ID-XXX x-runtime: - - '0.073764' + - X-RUNTIME-XXX x-xss-protection: - - 1; mode=block + - X-XSS-PROTECTION-XXX status: code: 401 message: Unauthorized @@ -99,10 +69,12 @@ interactions: body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Say hello in one word"}],"model":"claude-3-5-haiku-20241022","stop_sequences":["\nObservation:","\nThought:"],"stream":false}' headers: + User-Agent: + - X-USER-AGENT-XXX accept: - application/json accept-encoding: - - gzip, deflate, zstd + - ACCEPT-ENCODING-XXX anthropic-version: - '2023-06-01' connection: @@ -113,16 +85,14 @@ interactions: - application/json host: - api.anthropic.com - user-agent: - - Anthropic/Python 0.71.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: - 0.71.0 x-stainless-retry-count: @@ -145,7 +115,7 @@ interactions: 0ab7TSeBWPh7tBbIlF6dIcWOEiTmoqxOFtP0AQAA//8DAM5WvkqaAQAA headers: CF-RAY: - - 99a939a5a931556e-EWR + - CF-RAY-XXX Connection: - keep-alive Content-Encoding: @@ -161,19 +131,19 @@ interactions: X-Robots-Tag: - none anthropic-organization-id: - - SCRUBBED-ORG-ID + - ANTHROPIC-ORGANIZATION-ID-XXX anthropic-ratelimit-input-tokens-limit: - - '400000' + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX anthropic-ratelimit-input-tokens-remaining: - - '400000' + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX anthropic-ratelimit-input-tokens-reset: - - '2025-11-07T01:58:22Z' + - ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX anthropic-ratelimit-output-tokens-limit: - - '80000' + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX anthropic-ratelimit-output-tokens-remaining: - - '80000' + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX anthropic-ratelimit-output-tokens-reset: - - '2025-11-07T01:58:22Z' + - ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX anthropic-ratelimit-requests-limit: - '4000' anthropic-ratelimit-requests-remaining: @@ -181,22 +151,128 @@ interactions: anthropic-ratelimit-requests-reset: - '2025-11-07T01:58:22Z' anthropic-ratelimit-tokens-limit: - - '480000' + - ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX anthropic-ratelimit-tokens-remaining: - - '480000' + - ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX anthropic-ratelimit-tokens-reset: - - '2025-11-07T01:58:22Z' + - ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX cf-cache-status: - DYNAMIC request-id: - - req_011CUshbL7CEVoner91hUvxL + - REQUEST-ID-XXX retry-after: - '41' strict-transport-security: - - max-age=31536000; includeSubDomains; preload + - STS-XXX x-envoy-upstream-service-time: - '390' status: code: 200 message: OK +- request: + body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Say hello in one + word"}],"model":"claude-3-5-haiku-20241022","stop_sequences":["\nObservation:","\nThought:"],"stream":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: + - '182' + 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.71.1 + 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: !!binary | + H4sIAAAAAAAAA3SQ3UrEMBCFn8VznUJb7SJ5gr2UvRCsSAjJ7Da2TbrJRFxK3126WPzDq4HzfTMD + Z8YYLA2QMIPOlorboik67fpc1GV9V5V1DQFnITGmkyqr+9Qe7e78+HRpjw87v3ev7aHqIcCXiVaL + UtIngkAMwxrolFxi7RkCJngmz5DP8+Yzva/kOiT27gbLi0DiMKlIOgUPCfJWcY4enyDROZM3BOnz + MAjk60c5w/kps+LQk0+QVS1gtOlImUiaXfDqp1BuPJK2/7Ftd71PU0cjRT2oZvzrf9Gq+00XgZD5 + e9QIJIpvzpBiRxESa0tWR4tl+QAAAP//AwD8cXPFlwEAAA== + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 08 Dec 2025 23:16:53 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: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-12-08T23:16:53Z' + 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 + retry-after: + - '8' + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '787' + status: + code: 200 + message: OK version: 1 diff --git a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_thinking.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_thinking.yaml new file mode 100644 index 0000000000..c46f7b3b5c --- /dev/null +++ b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_thinking.yaml @@ -0,0 +1,126 @@ +interactions: +- request: + body: '{"max_tokens":10000,"messages":[{"role":"user","content":"What is the weather + in Tokyo?"}],"model":"claude-sonnet-4-5","stream":false,"thinking":{"type":"enabled","budget_tokens":5000}}' + headers: + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '185' + 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.71.1 + 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: !!binary | + H4sIAAAAAAAAAwAAAP//hFVrj+o4Ev0rVr7s7O00JAGahzRa0bxC84YGGrZXLeMUiYljB9tJCFf9 + 31fhNnNnZmd3PyWqOnXqJZ/6bkTCA2a0DMJw4sGjEpyDfqw+1h4dy6lZTadpmAb1jJYRKf/Dsgfg + Vdv9ST1+al/t3kvWSeIXf2qYhs5jKFCgFPbBMA0pWGHASlGlMdeGaRDBNXBttP75/Y7XAeUh5X7B + cP9tGa8BoESBRFQhrAojwgeRaKQDQCSRErhGGWAdFBiOXkWYixJyRQYpSBMNkSf43zQKcAoIEwJK + IS2QBMweNY0AUX4UMsKaCm4iyglLvCLJn6k9rHEJTXIUcpEx8HxAGVaIYaVREntYg1ekb8eSMuRY + TtVEmHtoiAjmXGh0kCJTcKuacg2Sg0ZC3itiNIXfUimQKSWgSu/8nQ+RCkTCPMTgR8+3YRRFIB1g + /TNBLEVKvb+ayW/93SpSie+D0ii4MUCOyI3+SLmHdEDVHwJ0AJECloIqGaahqM+xTmSxrZ5s9zrh + btgJ/M4obC8ZsU8T3o93TetY8/pVt3fci+XbzOVVq532q7bUM5aH9u706kAY1li2GuW1jnjDy3CK + K/tDm4t0Mn1acPs68OIHPK5cen4+rV+kNbPPIc9pb9cUuDs9QJj3Hfck/dpuPit3htmRLy13OdqP + uwoLCINtvb0YP7+yZXm1cYUgzqT66k4vR/fkjLuV5/LTotv3H5y8F6mK9ZIHo5h0iTv39+csFbLh + KuY4ad1K7PVll6rRbtWojuPJaMP5sgJil8p501KOb9sg8kN3zsrTNVUTubuU49W41h7U6udhuuzt + JmR+rcrKJhpEvaeH/np2PU8228VyF70t7CteXb1pLGu9cfttvhjMppVKN58fZjpq5OdrZp+sN+vc + S7flfjbw1dSqNoP9au/t1Xw8TC+Lq+1tGxWyGdU5cdsv17W7SSOezfx9vm12t1XyvI8lX0+qtex6 + mSWxPM5ZNdo48m3aPETNbcweqoNyeA12+DDrdSZzq45P9ejhnK7eukPhAJvMJ+58dGls6KWfDUG/ + dJyUOits6/6s6VcuwyEf6u3zYr3PdTaLyy6ez8rts3jbdkV7WXE3Z3/uhlfRC+gwTrJsu5tdgvoq + 0Lt1J6Zrfap0r76V9222kN6ETp96h9eVHciTnZEsV/vJ0KLrRKqkPH1ggZOchnI/G0yvEmqBOK3W + y12WEG8wEAv9/FQDl0+U7ngz1wl0I2o07Oilweq6606PnK27dqZk+OqoZtOz+4faQZ7K+YWOus2X + ALrj8kUNO711KJ6S7KE6AE0vp8bJy3pqVyV4I3btxa+/Gp/mT7WCS6Fjt0/L+L8q8xevsYReBfLh + fyuZiXKR/HiirUIO7BL69m0FWJIACc4oh2/fWmgghM8AvRu3mDvJu4GOQiIaReBRrAFJUAnT6p07 + Bcv2K1UGB0U1qDKOY1WwdQIg4Z2kRERkojYhyRfeLGTrHrvmHkhfioR777xSsI4FwQwpkUgCN7ZC + wF9wjDmagAYhBRM+LTBtHzjJ0S+nCJd8UTrFf79rmELieKSEYlY0AASrouh3PlTFrCQgzPPiRPgI + mIKve/Cj9ZscogBYfBtcRnVgIpWQAGGFfOAgMUOE0aiYx++VTkiUFXp6X4AWCC4xEI28RBapPHo8 + wm1HCrASXP3D+PyXaSgt4g95sxgtA7j3oRPJjS+HgnMCnIDR4gljppHcLmLru0F5nOgPLULgymhV + K6ZBMAngg0i41fPxR4B190vA3n/z3WMLfogDiIpmP2rRf+J/eu3gz95P0xCJ/r3JbjZN4+sofWgK + 0mgZxR33sPSMz89/AwAA//8DAHYWWLc6CAAA + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 08 Dec 2025 23:16:46 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: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-12-08T23:16:42Z' + 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 + retry-after: + - '18' + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '5323' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_thinking_blocks_preserved_across_turns.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_thinking_blocks_preserved_across_turns.yaml new file mode 100644 index 0000000000..5cfd3cc783 --- /dev/null +++ b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_thinking_blocks_preserved_across_turns.yaml @@ -0,0 +1,223 @@ +interactions: +- request: + body: '{"max_tokens":10000,"messages":[{"role":"user","content":"What is 2+2?"}],"model":"claude-sonnet-4-5","stream":false,"thinking":{"type":"enabled","budget_tokens":5000}}' + headers: + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '168' + 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.71.1 + 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: !!binary | + H4sIAAAAAAAAAwAAAP//dJLdb6JAFMX/FXJfxRYRaiXpgyBWRUWL1W03GzKFW5gCMzoz+NHG/31j + s6b7kX26N/d37nk55wMqnmIJDiQlqVNsSs4YqqbVtJumYdpG1+yCDjQFByqZxUbLfrrnbbbmviff + CkymnYQVaw90UMcNnlUoJckQdBC8PB+IlFQqwhTokHCmkClwvn9c9CqnrKAsOztcVgeWOZUalRrR + JK02JWpEUJVXqGiibWuUinJ2pZkNU8NtTUqpWVegg6QZI6oWZ19fSdcrnkZennlB0ZuJ0MLOIjy6 + qvHNvB08B5Pd8n3GF/1VtFxNh24/DFTKDGNxrKXqNKrAnRxfa9MO5tNxa3egHd9dmWxNl3u339tG + 9iiaR362t7j0/OzAR8FBDSzik/7sbbBqlf25mppe+3kYRaN9+PDipTvSLY1FL+nMn94X7nZ/W6yv + 3f6Lz/aLyEvUMN+LxwVVue8+d6zVhJuV1XhI6oEf2kExOzbE22sg0+MAxxNx2/W4Ox6HlXcUUk4H + vcZ4yPq9Sfry3l0ak32xnOQh3gyvw0Pa8zPf9GxXLML8cOD123o93QwiI9i6nYA/3vf8OzjpX3ng + 4ZzU53DA1Bqaqd1pFpx+6CAV38QCieQMHECWxqoWDH4BidsaWYLgsLosdag/e+B8AGWbWsWKF8gk + OFZbh4QkOcaJQHIOMv5TYFy4QJL+j11+z/64ybFCQcrYrv7Vf9FW/jc96cBr9fupfaODRLGjCcaK + ogAHzuVNiUjhdPoJAAD//wMAg2mrwC8DAAA= + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 08 Dec 2025 23:16:49 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: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-12-08T23:16:48Z' + 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 + retry-after: + - '12' + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2856' + status: + code: 200 + message: OK +- request: + body: '{"max_tokens":10000,"messages":[{"role":"user","content":"What is 2+2?"},{"role":"assistant","content":[{"type":"thinking","thinking":"This + is a simple arithmetic question. 2+2 equals 4.","signature":"EtsBCkYIChgCKkANrO4e7QOyBt+X28FZKLvTzNoQDVSTVMHBDOKtdn00Qyust7+mKBLyfu25KPMJ1vxi7EBV2nWiTwBDAqS5ISPSEgw4osCEgxoIKxtF4aEaDNjFV1lDPtM2C3ZHSSIwORbCdva9l0QAc7PYzQBqw8kW/BDbEnwQSCctHhwrUQithEBZ74VLo2m4+RcuFEO5KkNy+rjfKsdyFeJLr89CoBJJOmCyrssMFA+JHnDALdbz9T0LwkTLhOe6H/OxdAEgE2C5BrQOhxxoujWWMpFS0KqB7KoUGAE="},{"type":"text","text":"2 + + 2 = 4"}]},{"role":"user","content":"Now what is 3+3?"}],"model":"claude-sonnet-4-5","stream":false,"thinking":{"type":"enabled","budget_tokens":5000}}' + headers: + accept: + - application/json + accept-encoding: + - ACCEPT-ENCODING-XXX + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '681' + 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.71.1 + 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: !!binary | + H4sIAAAAAAAAAwAAAP//dJNbb6M6FIX/CvJraHMloUjnIQ3kMmlC0twgp0eRC65xDDbxBWiq/PcR + 1enMnDOaJ2+vb+0tS3v5A2Q8RilwQJRCHaM7yRlD6q53Z911Wh2r9dB5ACYgMXBAJvGp1W7bBTnn + 4TYQx7au1CoYL577Z2AC9Z6j2oWkhBgBEwie1gKUkkgFmQImiDhTiCng/P3x5VcJYZQwXE/4Kh2w + TZChJRIGkQaUtWhkyFDciGAa6RQqZHSNhtG9f2Ev7LMy/jL69WWbEPnZZUglIMGJeuOihCI2oCAq + yZAikXHRSCrC2T0wgSSYQaVF/RiPl6MRDWejBI/m9HFdFAPfb0yOdNmerfc9Ee6myaaa+ZDCp7Et + bD9/L9tRZ5i6h1Ased8uWb7GXFTBDodb9O73vP3+mDM3X1yJUmz62qcevo4vawpVY0Xd89i1OYau + HwyHVdWa0zXlj15ejWbl7Im/40vTf+Zv8/BirXfNRXXsjTw6ecKKe24yvdi+aqy+Na1goi2LQuEd + usXy29YuxoQ8zNnzfBc2r738cPQyP2FMb9rhBq0C5R47MztIBslGXadYD9iqd5i/Tjaj4fUabPYB + brx2znKnhVxG8nieoIjuznNKS68bHIdnSp8a7fWbJmG5aJ6FG8aTbnYY0NLdqsmisdTTnPD+Opk9 + uOW+wj2sg2ZYbK3eqhjIhb3NCrfCj+Bm/gwBqup4fB4O+LFPcPvHBFLx/CQQlJwBByAWn5QWDPwL + JLpoxCIEHKbT1AT6M3zOByAs1+qkOEVMAqc/MEEEowSdIoFgvfrTfw2tLy4QjP/Evnrr+ShPUIYE + TE9W9rv/J20n/6c3E3CtfpWsrgkkEgWJ0EkRJIAD6h8TQxGD2+07AAAA//8DADmgnx6kAwAA + headers: + CF-RAY: + - CF-RAY-XXX + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 08 Dec 2025 23:16: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-requests-limit: + - '50' + anthropic-ratelimit-requests-remaining: + - '49' + anthropic-ratelimit-requests-reset: + - '2025-12-08T23:16:51Z' + 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 + retry-after: + - '9' + strict-transport-security: + - STS-XXX + x-envoy-upstream-service-time: + - '2431' + status: + code: 200 + message: OK +version: 1