3131from typing import Optional
3232from typing import TYPE_CHECKING
3333from typing import Union
34+ import warnings
3435
3536from anthropic import AsyncAnthropic
3637from anthropic import AsyncAnthropicVertex
3940from anthropic import types as anthropic_types
4041from google .genai import types
4142from pydantic import BaseModel
43+ from pydantic import Field
44+ from pydantic import model_validator
4245from typing_extensions import override
4346
4447from ..utils ._google_client_headers import get_tracking_headers
4548from .base_llm import BaseLlm
49+ from .interactions_utils import extract_system_instruction
4650from .llm_response import LlmResponse
4751
4852if TYPE_CHECKING :
4953 from .llm_request import LlmRequest
5054
51- __all__ = ["AnthropicLlm" , "Claude" ]
55+ __all__ = ["AnthropicLlm" , "Claude" , "AnthropicGenerateContentConfig" ]
5256
5357logger = logging .getLogger ("google_adk." + __name__ )
5458
@@ -98,6 +102,12 @@ def _build_anthropic_thinking_param(
98102 caller gets the canonical error message). Rejected by Claude Opus 4.7
99103 -- callers targeting 4.7+ must use a negative value (adaptive) or
100104 ``0`` (disabled).
105+
106+ Args:
107+ config: Optional GenerateContentConfig object.
108+
109+ Returns:
110+ Mapped thinking parameter or NotGiven.
101111 """
102112 if not config or not config .thinking_config :
103113 return NOT_GIVEN
@@ -130,6 +140,85 @@ def _build_anthropic_thinking_param(
130140 )
131141
132142
143+ class AnthropicGenerateContentConfig (types .GenerateContentConfig ):
144+ """Configuration options for Anthropic Claude content generation.
145+
146+ This specialized configuration class is the recommended way to
147+ configure reasoning and extended thinking for newer Claude models.
148+
149+ Attributes:
150+ effort: The reasoning effort level for adaptive extended thinking. Set
151+ directly to guide the reasoning depth ("low", "medium", "high", "xhigh",
152+ "max"). This is the preferred alternative to the deprecated manual
153+ `thinking_budget` on newer Claude models.
154+ """
155+
156+ effort : Optional [Literal ["low" , "medium" , "high" , "xhigh" , "max" ]] = Field (
157+ default = None ,
158+ description = (
159+ "Configures the Claude-specific reasoning effort level for adaptive"
160+ " extended thinking. This is the recommended, future-proof way to"
161+ " control reasoning depth on newer Claude models."
162+ ),
163+ )
164+
165+ @model_validator (mode = "after" )
166+ def validate_no_thinking_level (self ) -> "AnthropicGenerateContentConfig" :
167+ """Ensures thinking_level is not configured on Anthropic-specific config."""
168+
169+ if self .thinking_config and self .thinking_config .thinking_level is not None :
170+ raise ValueError (
171+ "thinking_level is not supported in AnthropicGenerateContentConfig. "
172+ "Use the `effort` field directly to configure reasoning effort."
173+ )
174+ return self
175+
176+
177+ def _build_effort_param (
178+ config : Optional [types .GenerateContentConfig ],
179+ ) -> Optional [str ]:
180+ """Extracts Anthropic's effort parameter from the configuration.
181+
182+ To configure a specific reasoning effort level for Anthropic models,
183+ callers must use ``google.adk.models.AnthropicGenerateContentConfig`` and
184+ set the ``effort`` field directly.
185+ Using the standard ``thinking_config.thinking_level`` is explicitly
186+ unsupported because the standard `ThinkingLevel` enum (4 levels) cannot map
187+ consistently to Anthropic's 5 effort levels
188+ ("low", "medium", "high", "xhigh", "max").
189+
190+ Any attempt to set `thinking_level` will not be passed to the model and will
191+ log a warning.
192+
193+ If `effort` is not set, we return `None`.
194+ If `effort` and `thinking_level` are both set, `effort` takes precedence.
195+
196+ Args:
197+ config: Optional GenerateContentConfig object.
198+
199+ Returns:
200+ The effort level string (e.g., "xhigh") if specified via
201+ AnthropicGenerateContentConfig, or None.
202+ """
203+ if not config :
204+ return None
205+
206+ if isinstance (config , AnthropicGenerateContentConfig ) and config .effort :
207+ return config .effort
208+
209+ # If effort is not set, but thinking_level is, log a warning and ignore it.
210+ if config .thinking_config and config .thinking_config .thinking_level :
211+ warnings .warn (
212+ "Standard thinking_config.thinking_level is not supported for Anthropic"
213+ " models and will be ignored. Use AnthropicGenerateContentConfig and"
214+ " set the `effort` field directly to configure reasoning effort." ,
215+ category = UserWarning ,
216+ stacklevel = 4 ,
217+ )
218+
219+ return None
220+
221+
133222class ClaudeRequest (BaseModel ):
134223 system_instruction : str
135224 messages : Iterable [anthropic_types .MessageParam ]
@@ -509,6 +598,14 @@ def function_declaration_to_tool_param(
509598class AnthropicLlm (BaseLlm ):
510599 """Integration with Claude models via the Anthropic API.
511600
601+ Note:
602+ Anthropic Claude supports 5 distinct effort levels ("low", "medium",
603+ "high", "xhigh", "max") while the standard `ThinkingLevel` enum defines 4
604+ levels (MINIMAL, LOW, MEDIUM, HIGH), the standard
605+ `thinking_config.thinking_level` is not supported for Anthropic models.
606+ To configure thinking effort, user must use `AnthropicGenerateContentConfig`
607+ and set its `effort` field directly (e.g., `effort="xhigh"`).
608+
512609 Attributes:
513610 model: The name of the Claude model.
514611 max_tokens: The maximum number of tokens to generate.
@@ -534,11 +631,86 @@ def _resolve_model_name(self, model: Optional[str]) -> str:
534631 return match .group (1 )
535632 return model
536633
634+ def _build_anthropic_kwargs (
635+ self ,
636+ llm_request : LlmRequest ,
637+ messages : list [anthropic_types .MessageParam ],
638+ tools : Union [Iterable [anthropic_types .ToolUnionParam ], NotGiven ],
639+ tool_choice : Union [anthropic_types .ToolChoiceParam , NotGiven ],
640+ thinking : Union [
641+ anthropic_types .ThinkingConfigEnabledParam ,
642+ anthropic_types .ThinkingConfigDisabledParam ,
643+ anthropic_types .ThinkingConfigAdaptiveParam ,
644+ NotGiven ,
645+ ],
646+ ) -> dict [str , Any ]:
647+ system = NOT_GIVEN
648+ if llm_request .config :
649+ system_str = extract_system_instruction (llm_request .config )
650+ if system_str :
651+ system = system_str
652+
653+ model_to_use = self ._resolve_model_name (llm_request .model )
654+ kwargs = {
655+ "model" : model_to_use ,
656+ "system" : system ,
657+ "messages" : messages ,
658+ "tools" : tools ,
659+ "tool_choice" : tool_choice ,
660+ "thinking" : thinking ,
661+ }
662+
663+ effort = _build_effort_param (llm_request .config )
664+ if effort :
665+ kwargs ["output_config" ] = {"effort" : effort }
666+
667+ # Determine if thinking is enabled to avoid parameter conflicts.
668+ thinking_enabled = False
669+ if thinking is not NOT_GIVEN and thinking is not None :
670+ if isinstance (thinking , dict ):
671+ thinking_enabled = thinking .get ("type" ) in ["enabled" , "adaptive" ]
672+
673+ exclude_sampling = thinking_enabled or (effort is not None )
674+
675+ if llm_request .config :
676+ # Models released after Claude Opus 4.6 do not support setting
677+ # temperature, top_k, or top_p when thinking is enabled or effort is set.
678+ if not exclude_sampling :
679+ if llm_request .config .temperature is not None :
680+ kwargs ["temperature" ] = llm_request .config .temperature
681+ if llm_request .config .top_p is not None :
682+ kwargs ["top_p" ] = llm_request .config .top_p
683+ if llm_request .config .top_k is not None :
684+ kwargs ["top_k" ] = int (llm_request .config .top_k )
685+ else :
686+ if (
687+ llm_request .config .temperature is not None
688+ or llm_request .config .top_p is not None
689+ or llm_request .config .top_k is not None
690+ ):
691+ warnings .warn (
692+ "Sampling parameters (temperature, top_p, top_k) are ignored "
693+ "because thinking/effort is enabled." ,
694+ category = UserWarning ,
695+ stacklevel = 3 ,
696+ )
697+
698+ if llm_request .config .stop_sequences :
699+ kwargs ["stop_sequences" ] = llm_request .config .stop_sequences
700+
701+ if llm_request .config .max_output_tokens is not None :
702+ kwargs ["max_tokens" ] = llm_request .config .max_output_tokens
703+ else :
704+ kwargs ["max_tokens" ] = self .max_tokens
705+ else :
706+ kwargs ["max_tokens" ] = self .max_tokens
707+
708+ return kwargs
709+
537710 @override
538711 async def generate_content_async (
539712 self , llm_request : LlmRequest , stream : bool = False
540713 ) -> AsyncGenerator [LlmResponse , None ]:
541- model_to_use = self ._resolve_model_name (llm_request .model )
542714 sanitizer = _ToolUseIdSanitizer ()
543715 messages = [
544716 _content_to_message_param (content , sanitizer )
@@ -560,55 +732,51 @@ async def generate_content_async(
560732 else NOT_GIVEN
561733 )
562734 thinking = _build_anthropic_thinking_param (llm_request .config )
563- system = NOT_GIVEN
564- if llm_request .config .system_instruction is not None :
565- system = llm_request .config .system_instruction
566735
567736 if not stream :
568- message = await self ._anthropic_client .messages .create (
569- model = model_to_use ,
570- system = system ,
571- messages = messages ,
572- tools = tools ,
573- tool_choice = tool_choice ,
574- max_tokens = self .max_tokens ,
575- thinking = thinking ,
737+ kwargs = self ._build_anthropic_kwargs (
738+ llm_request , messages , tools , tool_choice , thinking
576739 )
740+ message = await self ._anthropic_client .messages .create (** kwargs )
577741 yield message_to_generate_content_response (message )
578742 else :
579743 async for response in self ._generate_content_streaming (
580- llm_request , messages , system , tools , tool_choice , thinking
744+ llm_request , messages , tools , tool_choice , thinking
581745 ):
582746 yield response
583747
584748 async def _generate_content_streaming (
585749 self ,
586750 llm_request : LlmRequest ,
587751 messages : list [anthropic_types .MessageParam ],
588- system : Union [str , types .Content , NotGiven ],
589752 tools : Union [Iterable [anthropic_types .ToolUnionParam ], NotGiven ],
590753 tool_choice : Union [anthropic_types .ToolChoiceParam , NotGiven ],
591754 thinking : Union [
592755 anthropic_types .ThinkingConfigEnabledParam ,
593756 anthropic_types .ThinkingConfigDisabledParam ,
757+ anthropic_types .ThinkingConfigAdaptiveParam ,
594758 NotGiven ,
595759 ] = NOT_GIVEN ,
596760 ) -> AsyncGenerator [LlmResponse , None ]:
597761 """Handles streaming responses from Anthropic models.
598762
599- Yields partial LlmResponse objects as content arrives, followed by
600- a final aggregated LlmResponse with all content.
763+ Args:
764+ llm_request: LlmRequest containing configurations and contents.
765+ messages: List of formatted Anthropic messages.
766+ tools: Optional tool configurations.
767+ tool_choice: Optional tool choice setting.
768+ thinking: Optional thinking details.
769+
770+ Yields:
771+ Partial LlmResponse objects as content arrives, followed by
772+ a final aggregated LlmResponse with all content.
601773 """
602- model_to_use = self ._resolve_model_name (llm_request .model )
774+ kwargs = self ._build_anthropic_kwargs (
775+ llm_request , messages , tools , tool_choice , thinking
776+ )
603777 raw_stream = await self ._anthropic_client .messages .create (
604- model = model_to_use ,
605- system = system ,
606- messages = messages ,
607- tools = tools ,
608- tool_choice = tool_choice ,
609- max_tokens = self .max_tokens ,
610778 stream = True ,
611- thinking = thinking ,
779+ ** kwargs ,
612780 )
613781
614782 # Track content blocks being built during streaming.
@@ -730,6 +898,15 @@ def _anthropic_client(self) -> AsyncAnthropic:
730898class Claude (AnthropicLlm ):
731899 """Integration with Claude models served from Vertex AI.
732900
901+ Note:
902+ Because Anthropic Claude supports 5 distinct effort levels ("low", "medium",
903+ "high", "xhigh", "max") while the standard `ThinkingLevel` enum defines 4
904+ levels (MINIMAL, LOW, MEDIUM, HIGH), the standard
905+ `thinking_config.thinking_level` is not supported for Anthropic models.
906+
907+ To configure thinking effort, user must use `AnthropicGenerateContentConfig`
908+ and set its `effort` field directly (e.g., `effort="xhigh"`).
909+
733910 Attributes:
734911 model: The name of the Claude model.
735912 max_tokens: The maximum number of tokens to generate.
0 commit comments