diff --git a/docs/en/concepts/llms.mdx b/docs/en/concepts/llms.mdx index 93795549c..bfd2fedf8 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/llm.py b/lib/crewai/src/crewai/llm.py index 8eb052683..77053deeb 100644 --- a/lib/crewai/src/crewai/llm.py +++ b/lib/crewai/src/crewai/llm.py @@ -67,6 +67,7 @@ if TYPE_CHECKING: 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 @@ class LLM(BaseLLM): 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 fd637adbc..723826ea7 100644 --- a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py +++ b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py @@ -3,8 +3,9 @@ from __future__ import annotations 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 from crewai.events.types.llm_events import LLMCallType @@ -22,8 +23,7 @@ if TYPE_CHECKING: try: from anthropic import Anthropic, AsyncAnthropic - 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( @@ -31,6 +31,11 @@ except ImportError: ) from None +class AnthropicThinkingConfig(BaseModel): + type: Literal["enabled", "disabled"] + budget_tokens: int | None = None + + class AnthropicCompletion(BaseLLM): """Anthropic native completion implementation. @@ -52,6 +57,7 @@ class AnthropicCompletion(BaseLLM): stream: bool = False, client_params: dict[str, Any] | None = None, interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None, + thinking: AnthropicThinkingConfig | None = None, **kwargs: Any, ): """Initialize Anthropic chat completion client. @@ -97,6 +103,10 @@ class AnthropicCompletion(BaseLLM): self.top_p = top_p 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 = True @property @@ -326,6 +336,12 @@ class AnthropicCompletion(BaseLLM): if tools and self.supports_tools: params["tools"] = self._convert_tools_for_interference(tools) + if self.thinking: + if isinstance(self.thinking, AnthropicThinkingConfig): + params["thinking"] = self.thinking.model_dump() + else: + params["thinking"] = self.thinking + return params def _convert_tools_for_interference( @@ -365,6 +381,34 @@ class AnthropicCompletion(BaseLLM): 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]: @@ -374,6 +418,7 @@ class AnthropicCompletion(BaseLLM): - 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 @@ -398,8 +443,29 @@ class AnthropicCompletion(BaseLLM): 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: @@ -449,7 +515,6 @@ class AnthropicCompletion(BaseLLM): 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, @@ -477,15 +542,22 @@ class AnthropicCompletion(BaseLLM): 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)) + + 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, @@ -540,6 +612,16 @@ class AnthropicCompletion(BaseLLM): 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) @@ -644,7 +726,26 @@ class AnthropicCompletion(BaseLLM): 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} @@ -663,12 +764,20 @@ class AnthropicCompletion(BaseLLM): 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/llms/anthropic/test_anthropic_function_calling.yaml b/lib/crewai/tests/cassettes/llms/anthropic/test_anthropic_function_calling.yaml new file mode 100644 index 000000000..8423c518b --- /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 8759062f9..4f286b9aa 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 000000000..c46f7b3b5 --- /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 000000000..5cfd3cc78 --- /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 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 000000000..e03c219ba --- /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.yaml b/lib/crewai/tests/cassettes/test_anthropic_thinking.yaml new file mode 100644 index 000000000..8a22c6ee2 --- /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/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 000000000..3b5b70ad5 --- /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 340aeb642..03cd772a1 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -12,8 +12,11 @@ from crewai.task import Task @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.""" + if "ANTHROPIC_API_KEY" not in os.environ: + with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}): + yield + else: yield @@ -63,6 +66,7 @@ def test_anthropic_tool_use_conversation_flow(): 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"} @@ -75,6 +79,7 @@ def test_anthropic_tool_use_conversation_flow(): # 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.") @@ -698,3 +703,167 @@ 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 + + +@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