fix(gemini): apply timeout after client_params merge and handle HttpOptions object#7629
Conversation
…ptions object
Two bugs in Gemini.get_client() caused self.timeout to be silently
ignored:
1. Ordering bug: timeout was injected into http_options BEFORE
client_params.update(self.client_params) was called. Any http_options
key inside client_params therefore overwrote the injected timeout.
2. Type bug: the isinstance(http_options, dict) guard silently skipped
timeout injection when the user passed an HttpOptions object (e.g.
HttpOptions(some_option='value')) inside client_params. The object
branch was simply not handled.
Fix:
- Move the timeout injection block AFTER client_params.update() so
client_params are fully merged before the timeout is applied.
- Normalise http_options to a plain dict before merging: call
model_dump(exclude_none=True) for pydantic/HttpOptions objects and
fall back to {} for any other non-dict type.
- Only inject self.timeout when the caller has NOT already set a timeout
key, preserving explicit user overrides.
Fixes agno-agi#7599
VANDRANKI
left a comment
There was a problem hiding this comment.
Two bugs fixed correctly in a single PR.
Bug 1 (order): client_params.update() was called after the timeout was injected, so any http_options key inside client_params would overwrite the just-set timeout. Moving the update() call before the timeout injection is the right fix.
Bug 2 (type): isinstance(http_options, dict) returned False for HttpOptions objects, silently skipping the timeout injection entirely. Normalizing via model_dump(exclude_none=True) is the right approach.
if 'timeout' not in http_options is a good addition - if the caller explicitly set a timeout in their http_options, this PR correctly preserves it rather than overwriting it. The deleted test test_timeout_does_not_override_client_params_http_options was testing wrong behavior; its replacement test_explicit_timeout_in_client_params_http_options_takes_precedence tests the correct behavior.
Minor: hasattr(http_options, 'model_dump') is duck typing that would match any object with a model_dump method. The more precise check would be isinstance(http_options, BaseModel) (importing from pydantic), or even isinstance(http_options, HttpOptions) if google.genai.types.HttpOptions is importable here. That said, duck typing is a reasonable tradeoff if the import adds overhead.
Also minor: the elif not isinstance(http_options, dict): http_options = {} branch silently drops any unrecognized http_options object. A log_warning here would help users debug unexpected configurations.
The three regression tests directly pin both bugs. LGTM.
|
Thanks for the review @VANDRANKI — addressed the minor feedback: log_warning added (e712285): The elif not isinstance(http_options, dict) fallback now logs a warning with the type name before falling back to an empty dict, so users can debug unexpected configurations. Re: duck typing — keeping hasattr(http_options, 'model_dump') as-is. While BaseModel is already imported, HttpOptions inherits from google.genai._common.BaseModel (which itself subclasses pydantic's BaseModel), so isinstance would work today. But the duck-typed check is more resilient to future google-genai refactors where the class hierarchy might change. As you noted, reasonable tradeoff. |
|
The On the duck-typing rationale: agreed. LGTM. |
|
Hi @ashpreetbedi — this fix has community LGTM, tests passing, and addresses the timeout issue in #7599. Would appreciate a review when you get a chance! |
VANDRANKI
left a comment
There was a problem hiding this comment.
The fix correctly addresses both sub-bugs. A few observations.
Bug 1 (ordering): correct fix
Moving before the timeout injection is the right approach. The old order let clobber the injected entirely.
Bug 2 (HttpOptions object): correct fix
to detect a Pydantic model and then to flatten it into a plain dict before merging is clean. One edge case: if is not a Pydantic model in a future SDK version but still has a method with different semantics, this could break. A more defensive check would be with , but the current duck-typing approach is pragmatic and fine for now.
Deleted test concern
The removed test would still pass under the new logic: when supplies , after /dict-check the dict already contains , so the guard correctly skips injection and the effective timeout remains 60000 ms. The test was deleted unnecessarily. Restoring it documents the caller-explicit-timeout-wins precedence rule. Not blocking, but worth adding back as a positive assertion.
New test coverage
and together cover the two regression scenarios directly. Good.
LGTM. Ready to merge once the deleted test is optionally restored.
Description
Fixes two bugs in
Gemini.get_client()that causedself.timeoutto be silently ignored whenclient_paramsis provided.Fixes #7599
Root Cause
Bug 1 — Wrong order: timeout was applied before
client_params.update()Any
http_optionskey insideclient_paramswould overwrite the injected timeout, causing requests to fall back to the underlying client default (e.g. 120 seconds) regardless of what the user set.Bug 2 — Type guard:
HttpOptionsobject silently skippedWhen a user passes
http_options=HttpOptions(some_option="value")insideclient_params, theisinstance(dict)guard evaluates toFalseand timeout injection is silently skipped with no warning or error.Fix
Changes:
client_params.update()so all user params are fully merged first.http_optionsto a plain dict viamodel_dump(exclude_none=True)when it is anHttpOptions/ pydantic object, preserving any other options the user set.self.timeoutwhen the caller has not already set atimeoutkey, so explicit user overrides are respected.Tests
Added 3 regression tests to the existing
TestGeminiTimeoutclass intests/unit/models/google/test_gemini.py:test_timeout_survives_client_params_updateclient_paramscarries unrelated keystest_timeout_with_http_options_object_in_client_paramsHttpOptions-like object is normalized; timeout is injected and existing options preservedtest_explicit_timeout_in_client_params_http_options_takes_precedencetimeoutinsideclient_paramshttp_options is not overwrittenAll 7 tests in
TestGeminiTimeoutpass.Type of Change