Skip to content

Commit e221d19

Browse files
jitokimmdrxy
andauthored
fix(vertex): honor max_retries=0 and disable GAPIC retries (#1185)
<!-- # Thank you for contributing to LangChain-google! --> <!-- ## Checklist for PR Creation - [ ] PR Title: "[package]: [brief description]" - Where "package" is genai, vertexai, or community - Use "docs: ..." for purely docs changes, "templates: ..." for template changes, "infra: ..." for CI changes - Example: "community: add foobar LLM" - [ ] PR Description and Relevant issues: - Description of the change - Relevant issues (if applicable) - Any dependencies required for this change - [ ] Add Tests and Docs: - If adding a new integration: 1. Include a test for the integration (preferably unit tests that do not rely on network access) 2. Add an example notebook showing its use (place in the `docs/docs/integrations` directory) - [ ] Lint and Test: - Run `make format`, `make lint`, and `make test` from the root of the package(s) you've modified - See contribution guidelines for more: https://github.com/langchain-ai/langchain-google/blob/main/README.md#contribute-code --> <!-- ## Additional guidelines - [ ] PR title and description are appropriate - [ ] Necessary tests and documentation have been added - [ ] Lint and tests pass successfully - [ ] The following additional guidelines are adhered to: - Optional dependencies are imported within functions - No unnecessary dependencies added to pyproject.toml files (except those required for unit tests) - PR doesn't touch more than one package - Changes are backwards compatible --> ## PR Description Fixes issue where max_retries did not work for VertexAI and ChatVertexAI. Now max_retries=0 correctly disables retries, and GAPIC retries are also disabled. ## Relevant issues Fixes #1093 ## Type <!-- Select the type of Pull Request --> <!-- Keep only the necessary ones --> 🐛 Bug Fix ## Changes(optional) ## Testing(optional) <!-- Test procedure --> <!-- Test result --> ## Note(optional) <!-- Information about the errors fixed by PR --> <!-- Remaining issue or something --> <!-- Other information about PR --> Signed-off-by: jitokim <[email protected]> Co-authored-by: Mason Daugherty <[email protected]>
1 parent 0ab3efc commit e221d19

File tree

3 files changed

+86
-2
lines changed

3 files changed

+86
-2
lines changed

libs/vertexai/langchain_google_vertexai/_retry.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,12 @@ def get_google_api_call_error_retry_instance():
113113
else:
114114
retry_instance = (retry_instance) | (retry_if_exception_type(error))
115115

116+
# Interpret max_retries=0 as "no retries" which still allows 1 attempt.
117+
attempts = 1 if max_retries is None or max_retries <= 0 else max_retries
118+
116119
return retry(
117120
reraise=True,
118-
stop=stop_after_attempt(max_retries),
121+
stop=stop_after_attempt(attempts),
119122
wait=wait_exponential(**wait_params),
120123
retry=retry_instance,
121124
before_sleep=_before_sleep,

libs/vertexai/langchain_google_vertexai/chat_models.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,14 @@
170170
_allowed_beta_params = [
171171
"media_resolution",
172172
]
173-
_allowed_params_prediction_service = ["request", "timeout", "metadata", "labels"]
173+
_allowed_params_prediction_service = [
174+
"request",
175+
"timeout",
176+
"metadata",
177+
"labels",
178+
# Allow controlling GAPIC client retries from callers.
179+
"retry",
180+
]
174181

175182

176183
_FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY = (
@@ -775,6 +782,10 @@ def _completion_with_retry(
775782
def _completion_with_retry_inner(generation_method: Callable, **kwargs: Any) -> Any:
776783
return generation_method(**kwargs)
777784

785+
# If user requested 0 retries, disable GAPIC retries too unless explicitly set.
786+
if max_retries <= 0 and "retry" not in kwargs:
787+
kwargs["retry"] = None
788+
778789
params = {
779790
k: v for k, v in kwargs.items() if k in _allowed_params_prediction_service
780791
}
@@ -805,6 +816,10 @@ async def _completion_with_retry_inner(
805816
) -> Any:
806817
return await generation_method(**kwargs)
807818

819+
# If user requested 0 retries, disable GAPIC retries too unless explicitly set.
820+
if max_retries <= 0 and "retry" not in kwargs:
821+
kwargs["retry"] = None
822+
808823
params = {
809824
k: v for k, v in kwargs.items() if k in _allowed_params_prediction_service
810825
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from unittest.mock import MagicMock
2+
3+
import pytest
4+
5+
from langchain_google_vertexai._retry import create_base_retry_decorator
6+
from langchain_google_vertexai.chat_models import _completion_with_retry
7+
8+
9+
class _DummyTimeoutError(Exception):
10+
pass
11+
12+
13+
def test_create_base_retry_decorator_zero_retries_means_single_attempt() -> None:
14+
"""Ensure max_retries=0 performs exactly one attempt and no retries."""
15+
calls = {"count": 0}
16+
17+
def will_timeout():
18+
calls["count"] += 1
19+
raise _DummyTimeoutError("timeout")
20+
21+
decorator = create_base_retry_decorator(
22+
error_types=[_DummyTimeoutError], max_retries=0
23+
)
24+
wrapped = decorator(will_timeout)
25+
26+
with pytest.raises(_DummyTimeoutError):
27+
wrapped()
28+
29+
assert calls["count"] == 1
30+
31+
32+
def test_completion_with_retry_injects_retry_none_when_zero() -> None:
33+
"""_completion_with_retry should pass retry=None to GAPIC when max_retries=0."""
34+
gen_method = MagicMock(return_value="ok")
35+
36+
result = _completion_with_retry(
37+
gen_method,
38+
max_retries=0,
39+
request={"dummy": True},
40+
timeout=120,
41+
metadata=(),
42+
)
43+
44+
assert result == "ok"
45+
# Ensure called exactly once and with retry=None passed through
46+
assert gen_method.call_count == 1
47+
kwargs = gen_method.call_args.kwargs
48+
assert "retry" in kwargs and kwargs["retry"] is None
49+
50+
51+
def test_completion_with_retry_does_not_inject_retry_when_positive() -> None:
52+
"""When max_retries>0, do not auto-inject retry=None."""
53+
gen_method = MagicMock(return_value="ok")
54+
55+
result = _completion_with_retry(
56+
gen_method,
57+
max_retries=2,
58+
request={"dummy": True},
59+
timeout=30,
60+
metadata=(),
61+
)
62+
63+
assert result == "ok"
64+
assert gen_method.call_count == 1
65+
kwargs = gen_method.call_args.kwargs
66+
assert "retry" not in kwargs

0 commit comments

Comments
 (0)