Trim each provider to current-generation models and drop the special-casing they required: - OpenAI: remove gpt-4.1 (deprecated; the only non-reasoning model). - Anthropic: remove Claude Sonnet 4.5 (legacy; the only Sonnet that 400s on effort). - Google: remove the Gemini 2.5 line (superseded by 3.x). - Gemini client: drop the integer thinking_budget mapping; 3.x takes the string thinking_level directly. Effort/reasoning gates stay as defense in depth for custom model IDs. All kept IDs verified against live APIs.
58 lines
2.2 KiB
Python
58 lines
2.2 KiB
Python
from typing import Any
|
|
|
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
|
|
from .base_client import BaseLLMClient, normalize_content
|
|
from .validators import validate_model
|
|
|
|
|
|
class NormalizedChatGoogleGenerativeAI(ChatGoogleGenerativeAI):
|
|
"""ChatGoogleGenerativeAI with normalized content output.
|
|
|
|
Gemini 3 models return content as list of typed blocks.
|
|
This normalizes to string for consistent downstream handling.
|
|
"""
|
|
|
|
def invoke(self, input, config=None, **kwargs):
|
|
return normalize_content(super().invoke(input, config, **kwargs))
|
|
|
|
|
|
class GoogleClient(BaseLLMClient):
|
|
"""Client for Google Gemini models."""
|
|
|
|
def __init__(self, model: str, base_url: str | None = None, **kwargs):
|
|
super().__init__(model, base_url, **kwargs)
|
|
|
|
def get_llm(self) -> Any:
|
|
"""Return configured ChatGoogleGenerativeAI instance."""
|
|
self.warn_if_unknown_model()
|
|
llm_kwargs = {"model": self.model}
|
|
|
|
if self.base_url:
|
|
llm_kwargs["base_url"] = self.base_url
|
|
|
|
for key in ("timeout", "max_retries", "temperature", "callbacks", "http_client", "http_async_client"):
|
|
if key in self.kwargs:
|
|
llm_kwargs[key] = self.kwargs[key]
|
|
|
|
# Unified api_key maps to provider-specific google_api_key
|
|
google_api_key = self.kwargs.get("api_key") or self.kwargs.get("google_api_key")
|
|
if google_api_key:
|
|
llm_kwargs["google_api_key"] = google_api_key
|
|
|
|
# Gemini 3.x takes the string ``thinking_level`` (the integer
|
|
# ``thinking_budget`` was for the now-retired 2.5 line). Pro accepts
|
|
# low/high; Flash also accepts minimal/medium — so map an unsupported
|
|
# "minimal" on Pro to the nearest level it does accept.
|
|
thinking_level = self.kwargs.get("thinking_level")
|
|
if thinking_level:
|
|
if "pro" in self.model.lower() and thinking_level == "minimal":
|
|
thinking_level = "low"
|
|
llm_kwargs["thinking_level"] = thinking_level
|
|
|
|
return NormalizedChatGoogleGenerativeAI(**llm_kwargs)
|
|
|
|
def validate_model(self) -> bool:
|
|
"""Validate model for Google."""
|
|
return validate_model("google", self.model)
|