Fix forward reference crash in Google and Anthropic LLM ThinkingConfig

ThinkingConfig was defined as an inner class on the service but referenced in the Settings dataclass declared before the service class, causing a crash at import time. Move ThinkingConfig to a standalone class defined before Settings, and keep a class attribute alias for backward compatibility.
This commit is contained in:
Paul Kompfner
2026-02-19 15:06:48 -05:00
parent cc54ff4708
commit ebb42a3c6d
2 changed files with 64 additions and 62 deletions

View File

@@ -70,6 +70,25 @@ except ModuleNotFoundError as e:
raise Exception(f"Missing module: {e}") raise Exception(f"Missing module: {e}")
class AnthropicThinkingConfig(BaseModel):
"""Configuration for extended thinking.
Parameters:
type: Type of thinking mode (currently only "enabled" or "disabled").
budget_tokens: Maximum number of tokens for thinking.
With today's models, the minimum is 1024.
Only allowed if type is "enabled".
"""
# Why `| str` here? To not break compatibility in case Anthropic adds
# more types in the future.
type: Literal["enabled", "disabled"] | str
# Why not enforce minimnum of 1024 here? To not break compatibility in
# case Anthropic changes this requirement in the future.
budget_tokens: int
@dataclass @dataclass
class AnthropicLLMSettings(LLMSettings): class AnthropicLLMSettings(LLMSettings):
"""Settings for Anthropic LLM services. """Settings for Anthropic LLM services.
@@ -80,20 +99,18 @@ class AnthropicLLMSettings(LLMSettings):
""" """
enable_prompt_caching: bool | _NotGiven = field(default_factory=lambda: _NOT_GIVEN) enable_prompt_caching: bool | _NotGiven = field(default_factory=lambda: _NOT_GIVEN)
thinking: "AnthropicLLMService.ThinkingConfig" | _NotGiven = field( thinking: AnthropicThinkingConfig | _NotGiven = field(default_factory=lambda: _NOT_GIVEN)
default_factory=lambda: _NOT_GIVEN
)
@classmethod @classmethod
def from_mapping(cls, settings): def from_mapping(cls, settings):
"""Convert a plain dict to settings, coercing thinking dicts. """Convert a plain dict to settings, coercing thinking dicts.
For backward compatibility, a ``thinking`` value that is a plain dict For backward compatibility, a ``thinking`` value that is a plain dict
is converted to a :class:`AnthropicLLMService.ThinkingConfig`. is converted to a :class:`AnthropicThinkingConfig`.
""" """
instance = super().from_mapping(settings) instance = super().from_mapping(settings)
if is_given(instance.thinking) and isinstance(instance.thinking, dict): if is_given(instance.thinking) and isinstance(instance.thinking, dict):
instance.thinking = AnthropicLLMService.ThinkingConfig(**instance.thinking) instance.thinking = AnthropicThinkingConfig(**instance.thinking)
return instance return instance
@@ -148,23 +165,8 @@ class AnthropicLLMService(LLMService):
# Overriding the default adapter to use the Anthropic one. # Overriding the default adapter to use the Anthropic one.
adapter_class = AnthropicLLMAdapter adapter_class = AnthropicLLMAdapter
class ThinkingConfig(BaseModel): # Backward compatibility: ThinkingConfig used to be defined inline here.
"""Configuration for extended thinking. ThinkingConfig = AnthropicThinkingConfig
Parameters:
type: Type of thinking mode (currently only "enabled" or "disabled").
budget_tokens: Maximum number of tokens for thinking.
With today's models, the minimum is 1024.
Only allowed if type is "enabled".
"""
# Why `| str` here? To not break compatibility in case Anthropic adds
# more types in the future.
type: Literal["enabled", "disabled"] | str
# Why not enforce minimnum of 1024 here? To not break compatibility in
# case Anthropic changes this requirement in the future.
budget_tokens: int
class InputParams(BaseModel): class InputParams(BaseModel):
"""Input parameters for Anthropic model inference. """Input parameters for Anthropic model inference.
@@ -193,9 +195,7 @@ class AnthropicLLMService(LLMService):
temperature: Optional[float] = Field(default_factory=lambda: NOT_GIVEN, ge=0.0, le=1.0) temperature: Optional[float] = Field(default_factory=lambda: NOT_GIVEN, ge=0.0, le=1.0)
top_k: Optional[int] = Field(default_factory=lambda: NOT_GIVEN, ge=0) top_k: Optional[int] = Field(default_factory=lambda: NOT_GIVEN, ge=0)
top_p: Optional[float] = Field(default_factory=lambda: NOT_GIVEN, ge=0.0, le=1.0) top_p: Optional[float] = Field(default_factory=lambda: NOT_GIVEN, ge=0.0, le=1.0)
thinking: Optional["AnthropicLLMService.ThinkingConfig"] = Field( thinking: Optional[AnthropicThinkingConfig] = Field(default_factory=lambda: NOT_GIVEN)
default_factory=lambda: NOT_GIVEN
)
extra: Optional[Dict[str, Any]] = Field(default_factory=dict) extra: Optional[Dict[str, Any]] = Field(default_factory=dict)
def model_post_init(self, __context): def model_post_init(self, __context):

View File

@@ -673,45 +673,7 @@ class GoogleLLMContext(OpenAILLMContext):
self._messages = [m for m in self._messages if m.parts] self._messages = [m for m in self._messages if m.parts]
@dataclass class GoogleThinkingConfig(BaseModel):
class GoogleLLMSettings(LLMSettings):
"""Settings for Google LLM services.
Parameters:
thinking: Thinking configuration.
"""
thinking: "GoogleLLMService.ThinkingConfig" | _NotGiven = field(
default_factory=lambda: NOT_GIVEN
)
@classmethod
def from_mapping(cls, settings):
"""Convert a plain dict to settings, coercing thinking dicts.
For backward compatibility, a ``thinking`` value that is a plain dict
is converted to a :class:`GoogleLLMService.ThinkingConfig`.
"""
instance = super().from_mapping(settings)
if is_given(instance.thinking) and isinstance(instance.thinking, dict):
instance.thinking = GoogleLLMService.ThinkingConfig(**instance.thinking)
return instance
class GoogleLLMService(LLMService):
"""Google AI (Gemini) LLM service implementation.
This class implements inference with Google's AI models, translating internally
from an OpenAILLMContext or a universal LLMContext to the messages format
expected by the Google AI model.
"""
_settings: GoogleLLMSettings
# Overriding the default adapter to use the Gemini one.
adapter_class = GeminiLLMAdapter
class ThinkingConfig(BaseModel):
"""Configuration for controlling the model's internal "thinking" process used before generating a response. """Configuration for controlling the model's internal "thinking" process used before generating a response.
Gemini 2.5 and 3 series models have this thinking process. Gemini 2.5 and 3 series models have this thinking process.
@@ -743,6 +705,46 @@ class GoogleLLMService(LLMService):
include_thoughts: Optional[bool] = Field(default=None) include_thoughts: Optional[bool] = Field(default=None)
@dataclass
class GoogleLLMSettings(LLMSettings):
"""Settings for Google LLM services.
Parameters:
thinking: Thinking configuration.
"""
thinking: GoogleThinkingConfig | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@classmethod
def from_mapping(cls, settings):
"""Convert a plain dict to settings, coercing thinking dicts.
For backward compatibility, a ``thinking`` value that is a plain dict
is converted to a :class:`GoogleThinkingConfig`.
"""
instance = super().from_mapping(settings)
if is_given(instance.thinking) and isinstance(instance.thinking, dict):
instance.thinking = GoogleThinkingConfig(**instance.thinking)
return instance
class GoogleLLMService(LLMService):
"""Google AI (Gemini) LLM service implementation.
This class implements inference with Google's AI models, translating internally
from an OpenAILLMContext or a universal LLMContext to the messages format
expected by the Google AI model.
"""
_settings: GoogleLLMSettings
# Overriding the default adapter to use the Gemini one.
adapter_class = GeminiLLMAdapter
# Backward compatibility: ThinkingConfig used to be defined inline here.
ThinkingConfig = GoogleThinkingConfig
class InputParams(BaseModel): class InputParams(BaseModel):
"""Input parameters for Google AI models. """Input parameters for Google AI models.
@@ -764,7 +766,7 @@ class GoogleLLMService(LLMService):
temperature: Optional[float] = Field(default=None, ge=0.0, le=2.0) temperature: Optional[float] = Field(default=None, ge=0.0, le=2.0)
top_k: Optional[int] = Field(default=None, ge=0) top_k: Optional[int] = Field(default=None, ge=0)
top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0) top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0)
thinking: Optional["GoogleLLMService.ThinkingConfig"] = Field(default=None) thinking: Optional[GoogleThinkingConfig] = Field(default=None)
extra: Optional[Dict[str, Any]] = Field(default_factory=dict) extra: Optional[Dict[str, Any]] = Field(default_factory=dict)
def __init__( def __init__(