Skip to content

Commit 69fa777

Browse files
Amaad Martincopybara-github
authored andcommitted
fix: catch genai.ClientError when sandbox is missing
This fixes an issue where AgentEngineSandboxCodeExecutor catches the wrong exception class when attempting to recover from externally-deleted sandboxes. Fixes #5480 Co-authored-by: Amaad Martin <amaadmartin@google.com> PiperOrigin-RevId: 908965545
1 parent 22fae7e commit 69fa777

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

src/google/adk/code_executors/agent_engine_sandbox_code_executor.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ def execute_code(
131131
sandbox_name = self.sandbox_resource_name
132132
if self.sandbox_resource_name is None:
133133
from google.api_core import exceptions
134+
from google.genai import errors as genai_errors
134135
from vertexai import types
135136

136137
# use sandbox name stored in session if available.
@@ -148,6 +149,11 @@ def execute_code(
148149
create_new_sandbox = True
149150
except exceptions.NotFound:
150151
create_new_sandbox = True
152+
except genai_errors.ClientError as exc:
153+
if exc.code == 404:
154+
create_new_sandbox = True
155+
else:
156+
raise
151157

152158
if create_new_sandbox:
153159
# Create a new sandbox and assign it to sandbox_name.

tests/unittests/code_executors/test_agent_engine_sandbox_code_executor.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,76 @@ def test_execute_code_recreates_sandbox_when_get_returns_none(
155155
mock_json_output = MagicMock()
156156
mock_json_output.mime_type = "application/json"
157157
mock_json_output.data = json.dumps(
158-
{"stdout": "recreated sandbox run", "stderr": ""}
158+
{"msg_out": "recreated sandbox run", "msg_err": ""}
159+
).encode("utf-8")
160+
mock_json_output.metadata = None
161+
mock_response.outputs = [mock_json_output]
162+
mock_api_client.agent_engines.sandboxes.execute_code.return_value = (
163+
mock_response
164+
)
165+
166+
# Execute using agent_engine_resource_name so a sandbox can be created
167+
executor = AgentEngineSandboxCodeExecutor(
168+
agent_engine_resource_name=(
169+
"projects/123/locations/us-central1/reasoningEngines/456"
170+
)
171+
)
172+
code_input = CodeExecutionInput(code='print("hello world")')
173+
result = executor.execute_code(mock_invocation_context, code_input)
174+
175+
# Assert get was called for the existing sandbox
176+
mock_api_client.agent_engines.sandboxes.get.assert_called_once_with(
177+
name=existing_sandbox_name
178+
)
179+
180+
# Assert create was called and session updated with new sandbox
181+
mock_api_client.agent_engines.sandboxes.create.assert_called_once()
182+
assert (
183+
mock_invocation_context.session.state["sandbox_name"]
184+
== created_sandbox_name
185+
)
186+
187+
# Assert execute_code used the created sandbox name
188+
mock_api_client.agent_engines.sandboxes.execute_code.assert_called_once_with(
189+
name=created_sandbox_name,
190+
input_data={"code": 'print("hello world")'},
191+
)
192+
193+
@patch("vertexai.Client")
194+
def test_execute_code_recreates_sandbox_when_get_raises_client_error(
195+
self,
196+
mock_vertexai_client,
197+
mock_invocation_context,
198+
):
199+
# Setup Mocks
200+
mock_api_client = MagicMock()
201+
mock_vertexai_client.return_value = mock_api_client
202+
203+
# Existing sandbox name stored in session
204+
existing_sandbox_name = "projects/123/locations/us-central1/reasoningEngines/456/sandboxEnvironments/old"
205+
mock_invocation_context.session.state = {
206+
"sandbox_name": existing_sandbox_name
207+
}
208+
209+
# Mock get to raise ClientError with code 404
210+
from google.genai.errors import ClientError
211+
212+
mock_api_client.agent_engines.sandboxes.get.side_effect = ClientError(
213+
code=404, response_json={"message": "Not Found"}
214+
)
215+
216+
# Mock create operation to return a new sandbox resource name
217+
operation_mock = MagicMock()
218+
created_sandbox_name = "projects/123/locations/us-central1/reasoningEngines/456/sandboxEnvironments/789"
219+
operation_mock.response.name = created_sandbox_name
220+
mock_api_client.agent_engines.sandboxes.create.return_value = operation_mock
221+
222+
# Mock execute_code response
223+
mock_response = MagicMock()
224+
mock_json_output = MagicMock()
225+
mock_json_output.mime_type = "application/json"
226+
mock_json_output.data = json.dumps(
227+
{"msg_out": "recreated sandbox run", "msg_err": ""}
159228
).encode("utf-8")
160229
mock_json_output.metadata = None
161230
mock_response.outputs = [mock_json_output]

0 commit comments

Comments
 (0)