-
Notifications
You must be signed in to change notification settings - Fork 457
Add gemini-3.5-flash model configuration #3315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5ca143c
dcea291
6fb1bf5
8362654
31d1aaf
46ebff1
f84be45
e260ae1
82fd17f
1282c55
d177acd
fca0ab8
6fba784
e149879
0cbdb10
cef1295
790e9aa
db42456
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,16 @@ def looks_like_malformed_conversation_history_error(exception: Exception) -> boo | |
| return any(p in s for p in MALFORMED_HISTORY_PATTERNS) | ||
|
|
||
|
|
||
| # Vertex AI (Gemini) rejects context-caching requests when the cached content | ||
| # is below the provider's minimum token threshold (currently 4096 tokens). | ||
| # Example error: "The cached content is of 1171 tokens. The minimum token | ||
| # count to start caching is 4096." — the `.lower()` comparison handles case | ||
| # variation across providers but won't match reworded messages; update this | ||
| # pattern if the API phrasing changes. | ||
| PROMPT_CACHE_TOO_SMALL_PATTERNS: list[str] = [ | ||
| "minimum token count to start caching", | ||
|
juanmichelini marked this conversation as resolved.
juanmichelini marked this conversation as resolved.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pattern fragility: Consider adding at least one alternative phrasing (e.g., |
||
| ] | ||
|
|
||
| AUTH_PATTERNS: list[str] = [ | ||
| "invalid api key", | ||
| "unauthorized", | ||
|
|
@@ -85,6 +95,20 @@ def looks_like_malformed_conversation_history_error(exception: Exception) -> boo | |
| ] | ||
|
juanmichelini marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def is_prompt_cache_too_small(exception: Exception) -> bool: | ||
| """Return True if the error indicates the prompt cache content is too small. | ||
|
|
||
| Vertex AI (Gemini) requires a minimum number of tokens (currently 4096) | ||
| to create a context cache. When the cached content is below this threshold, | ||
| the API returns a 400 error. The SDK should detect this and retry without | ||
| prompt caching markers. | ||
| """ | ||
| if not isinstance(exception, (BadRequestError, OpenAIError)): | ||
|
juanmichelini marked this conversation as resolved.
|
||
| return False | ||
| s = str(exception).lower() | ||
| return any(p in s for p in PROMPT_CACHE_TOO_SMALL_PATTERNS) | ||
|
|
||
|
|
||
| def looks_like_auth_error(exception: Exception) -> bool: | ||
| # Trust the typed exception when the provider/LiteLLM raised an explicit | ||
| # 401/403 — its message text may not contain the heuristic patterns below. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,6 +87,7 @@ | |
| from openhands.sdk.llm.exceptions import ( | ||
| LLMContextWindowTooSmallError, | ||
| LLMNoResponseError, | ||
| is_prompt_cache_too_small, | ||
| map_provider_exception, | ||
| ) | ||
|
|
||
|
|
@@ -1144,6 +1145,7 @@ def completion( | |
| removed_in="1.29.0", | ||
| details=_RETURN_METRICS_DETAILS, | ||
| ) | ||
| _caller_kwargs = kwargs.copy() | ||
| enable_streaming = bool(kwargs.get("stream", False)) or self.stream | ||
| if enable_streaming: | ||
| if on_token is None: | ||
|
|
@@ -1183,13 +1185,30 @@ def _one_attempt(**retry_kwargs: Any) -> ModelResponse: | |
| try: | ||
| return self._build_completion_result(_one_attempt()) | ||
| except Exception as e: | ||
| # If the prompt cache content is too small for the provider's | ||
| # minimum token threshold (e.g., Vertex AI requires ≥4096 tokens), | ||
| # retry without prompt caching markers. | ||
| if is_prompt_cache_too_small(e) and self.is_caching_prompt_active(): | ||
|
juanmichelini marked this conversation as resolved.
juanmichelini marked this conversation as resolved.
|
||
| logger.warning( | ||
| "Prompt cache content too small for provider minimum, " | ||
| "retrying without prompt caching" | ||
| ) | ||
| no_cache_llm = self.model_copy(update={"caching_prompt": False}) | ||
| return no_cache_llm.completion( | ||
|
juanmichelini marked this conversation as resolved.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Retry-budget doubling: The same pattern is replicated in |
||
| messages, | ||
| tools, | ||
| add_security_risk_prediction=add_security_risk_prediction, | ||
| on_token=on_token, | ||
| **_caller_kwargs, | ||
| ) | ||
| return self._handle_error( | ||
| e, | ||
| lambda fb: fb.completion( | ||
| messages, | ||
| tools, | ||
| add_security_risk_prediction=add_security_risk_prediction, | ||
| on_token=on_token, | ||
| **_caller_kwargs, | ||
|
juanmichelini marked this conversation as resolved.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implicit bug fix: caller kwargs now forwarded to fallback LLM Before this PR the fallback lambda was: lambda fb: fb.completion(messages, tools, add_security_risk_prediction=add_security_risk_prediction, on_token=on_token)Extra caller kwargs (e.g. Worth calling out in the PR description since it affects any caller with a configured fallback LLM, not just Vertex AI cache users. |
||
| ), | ||
| ) | ||
|
|
||
|
|
@@ -1217,6 +1236,7 @@ async def acompletion( | |
| removed_in="1.29.0", | ||
| details=_RETURN_METRICS_DETAILS, | ||
| ) | ||
| _caller_kwargs = kwargs.copy() | ||
| enable_streaming = bool(kwargs.get("stream", False)) or self.stream | ||
| if enable_streaming: | ||
| if on_token is None: | ||
|
|
@@ -1256,6 +1276,22 @@ async def _one_attempt(**retry_kwargs: Any) -> ModelResponse: | |
| try: | ||
| return self._build_completion_result(await _one_attempt()) | ||
| except Exception as e: | ||
| # If the prompt cache content is too small for the provider's | ||
| # minimum token threshold (e.g., Vertex AI requires ≥4096 tokens), | ||
| # retry without prompt caching markers. | ||
| if is_prompt_cache_too_small(e) and self.is_caching_prompt_active(): | ||
|
juanmichelini marked this conversation as resolved.
|
||
| logger.warning( | ||
| "Prompt cache content too small for provider minimum, " | ||
| "retrying without prompt caching" | ||
| ) | ||
| no_cache_llm = self.model_copy(update={"caching_prompt": False}) | ||
| return await no_cache_llm.acompletion( | ||
| messages, | ||
| tools, | ||
| add_security_risk_prediction=add_security_risk_prediction, | ||
| on_token=on_token, | ||
| **_caller_kwargs, | ||
| ) | ||
| # Fallback is synchronous; cast the token callback since the | ||
| # fallback LLM's sync path accepts TokenCallbackType. | ||
|
juanmichelini marked this conversation as resolved.
|
||
| _fb_token = cast("TokenCallbackType | None", on_token) | ||
|
juanmichelini marked this conversation as resolved.
|
||
|
|
@@ -1266,6 +1302,7 @@ async def _one_attempt(**retry_kwargs: Any) -> ModelResponse: | |
| tools, | ||
| add_security_risk_prediction=add_security_risk_prediction, | ||
| on_token=_fb_token, | ||
| **_caller_kwargs, | ||
| ), | ||
| ) | ||
|
|
||
|
|
@@ -1309,6 +1346,7 @@ def responses( | |
| removed_in="1.29.0", | ||
| details=_RETURN_METRICS_DETAILS, | ||
| ) | ||
| _caller_kwargs = kwargs.copy() | ||
| user_enable_streaming = bool(kwargs.get("stream", False)) or self.stream | ||
| if user_enable_streaming: | ||
| # We allow on_token to be None for subscription mode | ||
|
|
@@ -1387,6 +1425,24 @@ def _one_attempt(**retry_kwargs: Any) -> ResponsesAPIResponse: | |
| try: | ||
| return self._build_responses_result(_one_attempt()) | ||
| except Exception as e: | ||
| # If the prompt cache content is too small for the provider's | ||
| # minimum token threshold (e.g., Vertex AI requires ≥4096 tokens), | ||
| # retry without prompt caching markers. | ||
| if is_prompt_cache_too_small(e) and self.is_caching_prompt_active(): | ||
| logger.warning( | ||
| "Prompt cache content too small for provider minimum, " | ||
| "retrying without prompt caching" | ||
| ) | ||
| no_cache_llm = self.model_copy(update={"caching_prompt": False}) | ||
| return no_cache_llm.responses( | ||
| messages, | ||
| tools, | ||
| include, | ||
| store, | ||
| add_security_risk_prediction=add_security_risk_prediction, | ||
| on_token=on_token, | ||
| **_caller_kwargs, | ||
| ) | ||
| return self._handle_error( | ||
| e, | ||
| lambda fb: fb.responses( | ||
|
|
@@ -1396,6 +1452,7 @@ def _one_attempt(**retry_kwargs: Any) -> ResponsesAPIResponse: | |
| store, | ||
| add_security_risk_prediction=add_security_risk_prediction, | ||
| on_token=on_token, | ||
| **_caller_kwargs, | ||
| ), | ||
| ) | ||
|
|
||
|
|
@@ -1425,6 +1482,7 @@ async def aresponses( | |
| removed_in="1.29.0", | ||
| details=_RETURN_METRICS_DETAILS, | ||
| ) | ||
| _caller_kwargs = kwargs.copy() | ||
| user_enable_streaming = bool(kwargs.get("stream", False)) or self.stream | ||
| if user_enable_streaming: | ||
| # We allow on_token to be None for subscription mode | ||
|
|
@@ -1506,6 +1564,24 @@ async def _one_attempt( | |
| try: | ||
| return self._build_responses_result(await _one_attempt()) | ||
| except Exception as e: | ||
| # If the prompt cache content is too small for the provider's | ||
| # minimum token threshold (e.g., Vertex AI requires ≥4096 tokens), | ||
|
juanmichelini marked this conversation as resolved.
|
||
| # retry without prompt caching markers. | ||
| if is_prompt_cache_too_small(e) and self.is_caching_prompt_active(): | ||
| logger.warning( | ||
| "Prompt cache content too small for provider minimum, " | ||
| "retrying without prompt caching" | ||
| ) | ||
| no_cache_llm = self.model_copy(update={"caching_prompt": False}) | ||
| return await no_cache_llm.aresponses( | ||
| messages, | ||
| tools, | ||
| include, | ||
| store, | ||
| add_security_risk_prediction=add_security_risk_prediction, | ||
| on_token=on_token, | ||
| **_caller_kwargs, | ||
| ) | ||
| _fb_token = cast("TokenCallbackType | None", on_token) | ||
| return await self._ahandle_error( | ||
|
juanmichelini marked this conversation as resolved.
|
||
| e, | ||
|
|
@@ -1516,6 +1592,7 @@ async def _one_attempt( | |
| store, | ||
| add_security_risk_prediction=add_security_risk_prediction, | ||
| on_token=_fb_token, | ||
| **_caller_kwargs, | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.