Skip to content

Commit d699e73

Browse files
committed
fix: use regional endpoint for non-global Discovery Engine data stores
DiscoveryEngineSearchTool always used the default global endpoint (discoveryengine.googleapis.com), causing 400 errors when accessing data stores in regional locations like "eu" or "us". Parse the location from the data_store_id or search_engine_id resource path and construct the appropriate regional endpoint (e.g., eu-discoveryengine.googleapis.com) via ClientOptions. Fixes #4697
1 parent 4b677e7 commit d699e73

2 files changed

Lines changed: 86 additions & 5 deletions

File tree

src/google/adk/tools/discovery_engine_search_tool.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
r'search_result_mode.*DOCUMENTS', re.IGNORECASE
3737
)
3838

39+
_LOCATION_PATTERN = re.compile(r"/locations/([^/]+)/")
40+
_DEFAULT_ENDPOINT = "discoveryengine.googleapis.com"
41+
3942

4043
class SearchResultMode(enum.Enum):
4144
"""Search result mode for discovery engine search."""
@@ -102,10 +105,19 @@ def __init__(
102105

103106
credentials, _ = google.auth.default()
104107
quota_project_id = getattr(credentials, 'quota_project_id', None)
105-
options = (
106-
client_options.ClientOptions(quota_project_id=quota_project_id)
107-
if quota_project_id
108-
else None
108+
109+
resource_id = data_store_id or search_engine_id or ""
110+
location_match = _LOCATION_PATTERN.search(resource_id)
111+
location = location_match.group(1) if location_match else "global"
112+
api_endpoint = (
113+
f"{location}-{_DEFAULT_ENDPOINT}"
114+
if location != "global"
115+
else _DEFAULT_ENDPOINT
116+
)
117+
118+
options = client_options.ClientOptions(
119+
api_endpoint=api_endpoint,
120+
quota_project_id=quota_project_id,
109121
)
110122
self._discovery_engine_client = discoveryengine.SearchServiceClient(
111123
credentials=credentials, client_options=options

tests/unittests/tools/test_discovery_engine_search_tool.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ def test_discovery_engine_search_success(
122122
assert result["results"][0]["content"] == "Test Content"
123123
mock_auth.assert_called_once()
124124
mock_client_options.ClientOptions.assert_called_once_with(
125-
quota_project_id="test-quota-project"
125+
api_endpoint="discoveryengine.googleapis.com",
126+
quota_project_id="test-quota-project",
126127
)
127128
mock_search_client.assert_called_once_with(
128129
credentials=mock_credentials,
@@ -320,3 +321,71 @@ def test_auto_detect_does_not_retry_on_unrelated_error(
320321
assert result["status"] == "error"
321322
assert "Permission denied" in result["error_message"]
322323
assert mock_search_client.return_value.search.call_count == 1
324+
325+
@mock.patch.object(discovery_engine_search_tool, "client_options")
326+
@mock.patch.object(discoveryengine, "SearchServiceClient")
327+
def test_regional_endpoint_eu_data_store(
328+
self, mock_search_client, mock_client_options
329+
):
330+
"""Test that an EU data store uses the EU regional endpoint."""
331+
DiscoveryEngineSearchTool(
332+
data_store_id="projects/my-project/locations/eu/collections/default_collection/dataStores/my-ds"
333+
)
334+
mock_client_options.ClientOptions.assert_called_once_with(
335+
api_endpoint="eu-discoveryengine.googleapis.com",
336+
quota_project_id=None,
337+
)
338+
339+
@mock.patch.object(discovery_engine_search_tool, "client_options")
340+
@mock.patch.object(discoveryengine, "SearchServiceClient")
341+
def test_regional_endpoint_us_search_engine(
342+
self, mock_search_client, mock_client_options
343+
):
344+
"""Test that a US search engine uses the US regional endpoint."""
345+
DiscoveryEngineSearchTool(
346+
search_engine_id="projects/my-project/locations/us/collections/default_collection/engines/my-engine"
347+
)
348+
mock_client_options.ClientOptions.assert_called_once_with(
349+
api_endpoint="us-discoveryengine.googleapis.com",
350+
quota_project_id=None,
351+
)
352+
353+
@mock.patch.object(discovery_engine_search_tool, "client_options")
354+
@mock.patch.object(discoveryengine, "SearchServiceClient")
355+
def test_regional_endpoint_single_region(
356+
self, mock_search_client, mock_client_options
357+
):
358+
"""Test that a single-region location uses the correct endpoint."""
359+
DiscoveryEngineSearchTool(
360+
data_store_id="projects/my-project/locations/europe-west1/collections/default_collection/dataStores/my-ds"
361+
)
362+
mock_client_options.ClientOptions.assert_called_once_with(
363+
api_endpoint="europe-west1-discoveryengine.googleapis.com",
364+
quota_project_id=None,
365+
)
366+
367+
@mock.patch.object(discovery_engine_search_tool, "client_options")
368+
@mock.patch.object(discoveryengine, "SearchServiceClient")
369+
def test_global_endpoint_explicit(
370+
self, mock_search_client, mock_client_options
371+
):
372+
"""Test that a global data store uses the default global endpoint."""
373+
DiscoveryEngineSearchTool(
374+
data_store_id="projects/my-project/locations/global/collections/default_collection/dataStores/my-ds"
375+
)
376+
mock_client_options.ClientOptions.assert_called_once_with(
377+
api_endpoint="discoveryengine.googleapis.com",
378+
quota_project_id=None,
379+
)
380+
381+
@mock.patch.object(discovery_engine_search_tool, "client_options")
382+
@mock.patch.object(discoveryengine, "SearchServiceClient")
383+
def test_global_endpoint_no_location_in_id(
384+
self, mock_search_client, mock_client_options
385+
):
386+
"""Test that a short ID without location falls back to global endpoint."""
387+
DiscoveryEngineSearchTool(data_store_id="test_data_store")
388+
mock_client_options.ClientOptions.assert_called_once_with(
389+
api_endpoint="discoveryengine.googleapis.com",
390+
quota_project_id=None,
391+
)

0 commit comments

Comments
 (0)