fix(gemini): address remaining greptile review comments

- google_code_assist: log unrecognized optional_params keys for visibility
- gemini/authenticator: document timeout overshoot in OAuth loopback loop
- gemini/fallback_handler: take primary_call as Callable so async sync-setup errors
  are caught by the fallback try/except (symmetric with sync path)
- gemini/google_genai: pre-fetch OAuth via asyncio.to_thread in async
  get_auth_token_and_url to avoid blocking the event loop
- tests/google_code_assist: add missing __init__.py for parallel pytest discovery
- tests/fallback_handler: pass deferred coroutine factory to async fallback wrapper

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
balazss 2026-05-04 13:38:16 -07:00
parent 8eaa1582eb
commit 092876d377
7 changed files with 26 additions and 4 deletions

View file

@ -257,6 +257,7 @@ class GeminiAuthenticator:
deadline = time.monotonic() + loopback_timeout_s
server.timeout = 1.0
try:
# Note: deadline is checked after handle_request() returns, so the loop can overshoot by up to server.timeout (1s) — acceptable for a one-shot CLI login.
while auth_code is None and error is None:
server.handle_request()
if time.monotonic() >= deadline:

View file

@ -8,7 +8,7 @@ _google_code_assist_chat = get_google_code_assist_chat()
async def run_gemini_acompletion_with_code_assist_fallback(
primary_call: Awaitable[Any],
primary_call: Callable[[], Awaitable[Any]],
fallback_kwargs: Dict[str, Any],
auto_fallback_to_google_code_assist: bool = False,
) -> Any:
@ -17,7 +17,7 @@ async def run_gemini_acompletion_with_code_assist_fallback(
OAuth scope is insufficient.
"""
try:
return await primary_call
return await primary_call()
except Exception as e:
if not auto_fallback_to_google_code_assist:
raise e

View file

@ -2,6 +2,7 @@
Transformation for Calling Google models in their native format.
"""
import asyncio
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union, cast
import httpx
@ -11,6 +12,7 @@ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
from litellm.llms.base_llm.google_genai.transformation import (
BaseGoogleGenAIGenerateContentConfig,
)
from litellm.llms.gemini.common_utils import get_gemini_oauth_token
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexLLM
from litellm.types.router import GenericLiteLLMParams
@ -200,6 +202,7 @@ class GoogleGenAIConfig(BaseGoogleGenAIGenerateContentConfig, VertexLLM):
stream: bool,
api_base: Optional[str],
litellm_params: dict,
gemini_auth_data: Optional[dict] = None,
) -> Tuple[dict, str]:
"""
Build final headers and API URL from auth components.
@ -217,6 +220,7 @@ class GoogleGenAIConfig(BaseGoogleGenAIGenerateContentConfig, VertexLLM):
custom_llm_provider=self.custom_llm_provider,
api_base=api_base,
should_use_v1beta1_features=True,
gemini_auth_data=gemini_auth_data,
)
headers = self.validate_environment(
@ -291,6 +295,12 @@ class GoogleGenAIConfig(BaseGoogleGenAIGenerateContentConfig, VertexLLM):
custom_llm_provider=self.custom_llm_provider,
)
gemini_auth_data: Optional[dict] = None
if self.custom_llm_provider == "gemini":
gemini_api_key = self._get_google_ai_studio_api_key(dict(litellm_params))
if gemini_api_key is None:
gemini_auth_data = await asyncio.to_thread(get_gemini_oauth_token)
return self._build_final_headers_and_url(
model=model,
auth_header=_auth_header,
@ -300,6 +310,7 @@ class GoogleGenAIConfig(BaseGoogleGenAIGenerateContentConfig, VertexLLM):
stream=stream,
api_base=api_base,
litellm_params=litellm_params,
gemini_auth_data=gemini_auth_data,
)
def transform_generate_content_request(

View file

@ -108,6 +108,16 @@ class GoogleCodeAssistConfig(VertexGeminiConfig):
)
generation_config[key] = value
# Log unmapped/unrecognized caller keys so users have visibility (do not auto-forward —
# they may not be valid Gemini fields).
for key in optional_params:
if key not in base_params and key not in generation_config:
verbose_logger.debug(
"google_code_assist: dropping unrecognized optional_param '%s' "
"(not produced by VertexGeminiConfig.map_openai_params)",
key,
)
vertex_request = {
"contents": contents,
"session_id": litellm_params.get("session_id", str(uuid.uuid4())),

View file

@ -3447,7 +3447,7 @@ def completion( # type: ignore # noqa: PLR0915
new_params = safe_deep_copy(optional_params or {})
if acompletion is True:
response = run_gemini_acompletion_with_code_assist_fallback(
primary_call=vertex_chat_completion.completion( # type: ignore
primary_call=lambda: vertex_chat_completion.completion( # type: ignore
model=model,
messages=messages,
model_response=model_response,

View file

@ -66,7 +66,7 @@ async def test_run_gemini_acompletion_with_code_assist_fallback_enabled():
):
mock_acompletion.return_value = "fallback-ok"
result = await run_gemini_acompletion_with_code_assist_fallback(
primary_call=_raise_scope_error(),
primary_call=_raise_scope_error,
fallback_kwargs={},
auto_fallback_to_google_code_assist=True,
)