Skip to content

Commit 9e26f84

Browse files
google-genai-botcopybara-github
authored andcommitted
feat: GenAI SDK client - Support agent engine sandbox http request in genai sdk
PiperOrigin-RevId: 816865842
1 parent 160997e commit 9e26f84

File tree

3 files changed

+119
-3
lines changed

3 files changed

+119
-3
lines changed

google/genai/_api_client.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,73 @@ def _request_once(
11211121
response.headers, response if stream else [response.text]
11221122
)
11231123

1124+
def _request_once_sandbox(
1125+
self,
1126+
http_request: HttpRequest,
1127+
) -> HttpResponse:
1128+
# Only used by Orcas sandbox, which doesn't send request to vertex dataplane
1129+
# but directly to the sandbox endpoint provisioned in tenant project.
1130+
# It relays on different auth token and doesn't need to fetch ADC.
1131+
data: Optional[Union[str, bytes]] = None
1132+
if http_request.data:
1133+
if not isinstance(http_request.data, bytes):
1134+
data = json.dumps(http_request.data) if http_request.data else None
1135+
else:
1136+
data = http_request.data
1137+
1138+
response = self._httpx_client.request(
1139+
method=http_request.method,
1140+
url=http_request.url,
1141+
headers=http_request.headers,
1142+
content=data,
1143+
timeout=http_request.timeout,
1144+
)
1145+
errors.APIError.raise_for_response(response)
1146+
return HttpResponse(
1147+
response.headers, [response.text]
1148+
)
1149+
1150+
def _request_sandbox(
1151+
self,
1152+
http_request: HttpRequest,
1153+
http_options: Optional[HttpOptionsOrDict] = None,
1154+
) -> HttpResponse:
1155+
if http_options:
1156+
parameter_model = (
1157+
HttpOptions(**http_options)
1158+
if isinstance(http_options, dict)
1159+
else http_options
1160+
)
1161+
# Support per request retry options.
1162+
if parameter_model.retry_options:
1163+
retry_kwargs = retry_args(parameter_model.retry_options)
1164+
retry = tenacity.Retrying(**retry_kwargs)
1165+
return retry(self._request_once_sandbox, http_request) # type: ignore[no-any-return]
1166+
1167+
return self._retry(self._request_once_sandbox, http_request) # type: ignore[no-any-return]
1168+
1169+
def request_sandbox(
1170+
self,
1171+
http_method: str,
1172+
url: str,
1173+
request_dict: dict[str, object],
1174+
http_options: HttpOptions,
1175+
) -> SdkHttpResponse:
1176+
if not http_options.headers:
1177+
raise ValueError('Request headers must be set.')
1178+
1179+
http_request = HttpRequest(
1180+
method=http_method,
1181+
url=url,
1182+
headers=http_options.headers,
1183+
data=request_dict,
1184+
)
1185+
response = self._request_sandbox(http_request, http_options)
1186+
response_body = (
1187+
response.response_stream[0] if response.response_stream else ''
1188+
)
1189+
return SdkHttpResponse(headers=response.headers, body=response_body)
1190+
11241191
def _request(
11251192
self,
11261193
http_request: HttpRequest,

google/genai/_replay_api_client.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
from ._api_client import HttpRequest
3535
from ._api_client import HttpResponse
3636
from ._common import BaseModel
37-
from .types import HttpOptions, HttpOptionsOrDict
37+
from .types import HttpOptions
38+
from .types import HttpOptionsOrDict
39+
from .types import HttpResponse as SdkHttpResponse
3840

3941

4042
def to_snake_case(name: str) -> str:
@@ -679,3 +681,25 @@ async def async_download_file(
679681
return result
680682
else:
681683
return self._build_response_from_replay(request).byte_stream[0]
684+
685+
def _request_sandbox(
686+
self,
687+
http_request: HttpRequest,
688+
http_options: Optional[HttpOptionsOrDict] = None,
689+
) -> HttpResponse:
690+
self._initialize_replay_session_if_not_loaded()
691+
if self._should_call_api():
692+
_debug_print('api mode request: %s' % http_request)
693+
try:
694+
result = super()._request_sandbox(
695+
http_request, http_options
696+
)
697+
except errors.APIError as e:
698+
self._record_interaction(http_request, e)
699+
raise e
700+
if result:
701+
self._record_interaction(http_request, result)
702+
_debug_print('api mode result: %s' % result)
703+
return result
704+
else:
705+
return self._build_response_from_replay(http_request)

google/genai/_test_api_client.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import time
1818
from unittest.mock import MagicMock, patch
1919
import pytest
20-
from .api_client import BaseApiClient
20+
from .api_client import BaseApiClient, HttpResponse
2121

2222

2323
@patch('genai.api_client.BaseApiClient._build_request')
@@ -105,7 +105,7 @@ async def delayed_response(http_request, stream):
105105
async def test_async_request_streamed_non_blocking(
106106
mock_async_request, mock_build_request
107107
):
108-
api_client = ApiClient(api_key='test_api_key')
108+
api_client = BaseApiClient(api_key='test_api_key')
109109
http_method = 'GET'
110110
path = 'test/path'
111111
request_dict = {'key': 'value'}
@@ -147,3 +147,28 @@ async def delayed_response(http_request, stream):
147147
)
148148
assert chunks == ['{"chunk": 1}', '{"chunk": 2}', '{"chunk": 3}']
149149
assert end_time - start_time > 0.3
150+
151+
152+
@patch('google.genai._api_client.BaseApiClient._request_sandbox')
153+
def test_request_sandbox(mock_request_sandbox):
154+
api_client = ApiClient(api_key='test_api_key')
155+
http_method = 'POST'
156+
url = 'http://test/url'
157+
request_dict = {'key': 'value'}
158+
159+
mock_request_sandbox.return_value = HttpResponse(
160+
headers={'Content-Type': 'application/json'},
161+
response_stream=['{"response_key": "response_value"}']
162+
)
163+
164+
response = api_client.request_sandbox(http_method, url, request_dict)
165+
166+
mock_request_sandbox.assert_called_once()
167+
assert response['headers'] == {'Content-Type': 'application/json'}
168+
assert response['body'] == '{"response_key": "response_value"}'
169+
170+
called_with_http_request = mock_request_sandbox.call_args[0][0]
171+
assert called_with_http_request.method == http_method
172+
assert called_with_http_request.url == url
173+
assert called_with_http_request.data == request_dict
174+

0 commit comments

Comments
 (0)