From f98c652e5604a6f531244533e25bed208bf2803e Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Mon, 20 Oct 2025 10:58:24 +0530 Subject: [PATCH 01/10] added isolated code executor --- .../code_executors/isolated_code_executor.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/google/adk/code_executors/isolated_code_executor.py diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py new file mode 100644 index 0000000000..1a256b9433 --- /dev/null +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from contextlib import redirect_stdout +import io +import re +from typing import Any + +from pydantic import Field +from typing_extensions import override + +from ..agents.invocation_context import InvocationContext +from .base_code_executor import BaseCodeExecutor +from .code_execution_utils import CodeExecutionInput +from .code_execution_utils import CodeExecutionResult + +import sys +import subprocess + +#Don't think this is needed anymore but keeping it around just in case. + +# def _prepare_globals(code: str, globals_: dict[str, Any]) -> None: +# """Prepare globals for code execution, injecting __name__ if needed.""" +# if re.search(r"if\s+__name__\s*==\s*['\"]__main__['\"]", code): +# globals_['__name__'] = '__main__' + +class IsolatedCodeExecutor(BaseCodeExecutor): + """A code executor that safely executes code in an isolated environment through + the current local context.""" + + # Overrides the BaseCodeExecutor attribute: this executor cannot be stateful. + stateful: bool = Field(default=False, frozen=True, exclude=True) + + # Overrides the BaseCodeExecutor attribute: this executor cannot + # optimize_data_file. + optimize_data_file: bool = Field(default=False, frozen=True, exclude=True) + + def __init__(self, **data): + """Initializes the IsolatedCodeExecutor.""" + if 'stateful' in data and data['stateful']: + raise ValueError('Cannot set `stateful=True` in IsolatedCodeExecutor.') + if 'optimize_data_file' in data and data['optimize_data_file']: + raise ValueError( + 'Cannot set `optimize_data_file=True` in IsolatedCodeExecutor.' + ) + super().__init__(**data) + + @override + def execute_code( + self, + invocation_context: InvocationContext, + code_execution_input: CodeExecutionInput, + ) -> CodeExecutionResult: + # Executes code by spawning a new python interpreter process. + code = code_execution_input.code + process_result = subprocess.run( + [sys.executable, "-c", code], + capture_output=True, + text=True + ) + + # Collect the final result. + return CodeExecutionResult( + stdout=process_result.stdout, + stderr=process_result.stderr, + output_files=[], + ) From b3cdd32ba158609d03e1d8a6a42802404d466cea Mon Sep 17 00:00:00 2001 From: AlexFierro9 <95060707+AlexFierro9@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:40:01 +0530 Subject: [PATCH 02/10] Update src/google/adk/code_executors/isolated_code_executor.py Unused imports can be confusing Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/code_executors/isolated_code_executor.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py index 1a256b9433..f79ae130c6 100644 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -1,9 +1,7 @@ from __future__ import annotations -from contextlib import redirect_stdout -import io -import re -from typing import Any +import sys +import subprocess from pydantic import Field from typing_extensions import override @@ -13,9 +11,6 @@ from .code_execution_utils import CodeExecutionInput from .code_execution_utils import CodeExecutionResult -import sys -import subprocess - #Don't think this is needed anymore but keeping it around just in case. # def _prepare_globals(code: str, globals_: dict[str, Any]) -> None: From a77b0c200ec60da9fdb85bd03b3a6812c8fb229d Mon Sep 17 00:00:00 2001 From: AlexFierro9 <95060707+AlexFierro9@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:40:28 +0530 Subject: [PATCH 03/10] Update src/google/adk/code_executors/isolated_code_executor.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/code_executors/isolated_code_executor.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py index f79ae130c6..e46af150ba 100644 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -19,8 +19,12 @@ # globals_['__name__'] = '__main__' class IsolatedCodeExecutor(BaseCodeExecutor): - """A code executor that safely executes code in an isolated environment through - the current local context.""" + """A code executor that executes code in an isolated process. + + This provides memory isolation from the main application, but it is not a + full security sandbox. The executed code runs with the same permissions as the + main application and can access the filesystem, network, etc. + """ # Overrides the BaseCodeExecutor attribute: this executor cannot be stateful. stateful: bool = Field(default=False, frozen=True, exclude=True) From 8fbd73bc80da3340de408129895a1c2a5dad5af3 Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Mon, 20 Oct 2025 13:29:46 +0530 Subject: [PATCH 04/10] added the units tests --- .../code_executors/isolated_code_executor.py | 8 +- .../test_isolated_code_executor.py | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 tests/unittests/code_executors/test_isolated_code_executor.py diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py index e46af150ba..0e1b2c927d 100644 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -11,12 +11,6 @@ from .code_execution_utils import CodeExecutionInput from .code_execution_utils import CodeExecutionResult -#Don't think this is needed anymore but keeping it around just in case. - -# def _prepare_globals(code: str, globals_: dict[str, Any]) -> None: -# """Prepare globals for code execution, injecting __name__ if needed.""" -# if re.search(r"if\s+__name__\s*==\s*['\"]__main__['\"]", code): -# globals_['__name__'] = '__main__' class IsolatedCodeExecutor(BaseCodeExecutor): """A code executor that executes code in an isolated process. @@ -57,7 +51,7 @@ def execute_code( text=True ) - # Collect the final result. + return CodeExecutionResult( stdout=process_result.stdout, stderr=process_result.stderr, diff --git a/tests/unittests/code_executors/test_isolated_code_executor.py b/tests/unittests/code_executors/test_isolated_code_executor.py new file mode 100644 index 0000000000..36feebc5b1 --- /dev/null +++ b/tests/unittests/code_executors/test_isolated_code_executor.py @@ -0,0 +1,112 @@ +from unittest.mock import MagicMock + +from google.adk.agents.base_agent import BaseAgent +from google.adk.agents.invocation_context import InvocationContext +from google.adk.code_executors.code_execution_utils import CodeExecutionInput +from google.adk.code_executors.code_execution_utils import CodeExecutionResult +from google.adk.code_executors.isolated_code_executor import IsolatedCodeExecutor +from google.adk.sessions.base_session_service import BaseSessionService +from google.adk.sessions.session import Session +import pytest +import os + + +@pytest.fixture +def mock_invocation_context() -> InvocationContext: + """Provides a mock InvocationContext.""" + mock_agent = MagicMock(spec=BaseAgent) + mock_session = MagicMock(spec=Session) + mock_session_service = MagicMock(spec=BaseSessionService) + return InvocationContext( + invocation_id="test_invocation", + agent=mock_agent, + session=mock_session, + session_service=mock_session_service, + ) + + +class TestIsolatedCodeExecutor: + + def test_init_default(self): + executor = IsolatedCodeExecutor() + assert not executor.stateful + assert not executor.optimize_data_file + + def test_init_stateful_raises_error(self): + with pytest.raises( + ValueError, + match="Cannot set `stateful=True` in IsolatedCodeExecutor.", + ): + IsolatedCodeExecutor(stateful=True) + + def test_init_optimize_data_file_raises_error(self): + with pytest.raises( + ValueError, + match=( + "Cannot set `optimize_data_file=True` in IsolatedCodeExecutor." + ), + ): + IsolatedCodeExecutor(optimize_data_file=True) + + def test_execute_code_simple_print( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code_input = CodeExecutionInput(code='print("hello world")') + result = executor.execute_code(mock_invocation_context, code_input) + + assert isinstance(result, CodeExecutionResult) + assert result.stdout == "hello world\n" + assert result.stderr == "" + assert result.output_files == [] + + def test_execute_code_with_error( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code_input = CodeExecutionInput(code='raise ValueError("Test error")') + result = executor.execute_code(mock_invocation_context, code_input) + + assert isinstance(result, CodeExecutionResult) + assert result.stdout == "" + assert "Test error" in result.stderr + assert result.output_files == [] + + def test_execute_code_variable_assignment( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code_input = CodeExecutionInput(code="x = 10\nprint(x * 2)") + result = executor.execute_code(mock_invocation_context, code_input) + + assert result.stdout == "20\n" + assert result.stderr == "" + + def test_execute_code_empty(self, mock_invocation_context: InvocationContext): + executor = IsolatedCodeExecutor() + code_input = CodeExecutionInput(code="") + result = executor.execute_code(mock_invocation_context, code_input) + assert result.stdout == "" + assert result.stderr == "" + + def test_execute_code_with_import( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code = "import os; print(os.linesep)" + code_input = CodeExecutionInput(code=code) + result = executor.execute_code(mock_invocation_context, code_input) + + assert result.stdout.strip() == os.linesep.strip() + assert result.stderr == "" + + def test_execute_code_multiline_output( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code = 'print("line 1")\nprint("line 2")' + code_input = CodeExecutionInput(code=code) + result = executor.execute_code(mock_invocation_context, code_input) + + assert result.stdout == "line 1\nline 2\n" + assert result.stderr == "" From b0f3f6ca06fce7ebeb2bd7d9de5457db0bf0912b Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Fri, 24 Oct 2025 20:59:40 +0530 Subject: [PATCH 05/10] run autoformat.sh --- src/google/adk/code_executors/isolated_code_executor.py | 2 +- tests/unittests/code_executors/test_isolated_code_executor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py index 0e1b2c927d..7b74786c43 100644 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -1,7 +1,7 @@ from __future__ import annotations -import sys import subprocess +import sys from pydantic import Field from typing_extensions import override diff --git a/tests/unittests/code_executors/test_isolated_code_executor.py b/tests/unittests/code_executors/test_isolated_code_executor.py index 36feebc5b1..70a1553b8a 100644 --- a/tests/unittests/code_executors/test_isolated_code_executor.py +++ b/tests/unittests/code_executors/test_isolated_code_executor.py @@ -1,3 +1,4 @@ +import os from unittest.mock import MagicMock from google.adk.agents.base_agent import BaseAgent @@ -8,7 +9,6 @@ from google.adk.sessions.base_session_service import BaseSessionService from google.adk.sessions.session import Session import pytest -import os @pytest.fixture From 039b9b9567a0e76c51713a2e064dcb7eb0349a9b Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Sun, 9 Nov 2025 10:31:11 +0530 Subject: [PATCH 06/10] Added Isolated code executor as a config option for unsafe local code executor --- src/google/adk/code_executors/__init__.py | 2 ++ .../unsafe_local_code_executor.py | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/google/adk/code_executors/__init__.py b/src/google/adk/code_executors/__init__.py index edeaf5d272..8772fb3945 100644 --- a/src/google/adk/code_executors/__init__.py +++ b/src/google/adk/code_executors/__init__.py @@ -18,6 +18,7 @@ from .base_code_executor import BaseCodeExecutor from .built_in_code_executor import BuiltInCodeExecutor +from .isolated_code_executor import IsolatedCodeExecutor from .code_executor_context import CodeExecutorContext from .unsafe_local_code_executor import UnsafeLocalCodeExecutor @@ -26,6 +27,7 @@ __all__ = [ 'BaseCodeExecutor', 'BuiltInCodeExecutor', + 'IsolatedCodeExecutor', 'CodeExecutorContext', 'UnsafeLocalCodeExecutor', 'VertexAiCodeExecutor', diff --git a/src/google/adk/code_executors/unsafe_local_code_executor.py b/src/google/adk/code_executors/unsafe_local_code_executor.py index 416bf15446..472fd0249a 100644 --- a/src/google/adk/code_executors/unsafe_local_code_executor.py +++ b/src/google/adk/code_executors/unsafe_local_code_executor.py @@ -16,6 +16,8 @@ from contextlib import redirect_stdout import io +import subprocess +import sys import re from typing import Any @@ -26,6 +28,7 @@ from .base_code_executor import BaseCodeExecutor from .code_execution_utils import CodeExecutionInput from .code_execution_utils import CodeExecutionResult +from .isolated_code_executor import IsolatedCodeExecutor def _prepare_globals(code: str, globals_: dict[str, Any]) -> None: @@ -35,7 +38,10 @@ def _prepare_globals(code: str, globals_: dict[str, Any]) -> None: class UnsafeLocalCodeExecutor(BaseCodeExecutor): - """A code executor that unsafely execute code in the current local context.""" + """A code executor that unsafely execute code in the current local context. + + This executor can be configured to run code in an isolated process. + """ # Overrides the BaseCodeExecutor attribute: this executor cannot be stateful. stateful: bool = Field(default=False, frozen=True, exclude=True) @@ -44,7 +50,9 @@ class UnsafeLocalCodeExecutor(BaseCodeExecutor): # optimize_data_file. optimize_data_file: bool = Field(default=False, frozen=True, exclude=True) - def __init__(self, **data): + use_isolated_process: bool = False + + def __init__(self, use_isolated_process: bool = False, **data): """Initializes the UnsafeLocalCodeExecutor.""" if 'stateful' in data and data['stateful']: raise ValueError('Cannot set `stateful=True` in UnsafeLocalCodeExecutor.') @@ -52,7 +60,10 @@ def __init__(self, **data): raise ValueError( 'Cannot set `optimize_data_file=True` in UnsafeLocalCodeExecutor.' ) - super().__init__(**data) + super().__init__(use_isolated_process=use_isolated_process, **data) + self.use_isolated_process = use_isolated_process + if self.use_isolated_process: + self._isolated_executor = IsolatedCodeExecutor() @override def execute_code( @@ -60,6 +71,11 @@ def execute_code( invocation_context: InvocationContext, code_execution_input: CodeExecutionInput, ) -> CodeExecutionResult: + if self.use_isolated_process: + return self._isolated_executor.execute_code( + invocation_context, code_execution_input + ) + # Execute the code. output = '' error = '' From 6a9ee6b4084a4958b5a30d5c672654e8f0670d53 Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Sun, 9 Nov 2025 10:37:34 +0530 Subject: [PATCH 07/10] Added Isolated Code Executor as a separate config --- src/google/adk/code_executors/__init__.py | 2 +- src/google/adk/code_executors/unsafe_local_code_executor.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/google/adk/code_executors/__init__.py b/src/google/adk/code_executors/__init__.py index 8772fb3945..fd66bfbe6b 100644 --- a/src/google/adk/code_executors/__init__.py +++ b/src/google/adk/code_executors/__init__.py @@ -18,8 +18,8 @@ from .base_code_executor import BaseCodeExecutor from .built_in_code_executor import BuiltInCodeExecutor -from .isolated_code_executor import IsolatedCodeExecutor from .code_executor_context import CodeExecutorContext +from .isolated_code_executor import IsolatedCodeExecutor from .unsafe_local_code_executor import UnsafeLocalCodeExecutor logger = logging.getLogger('google_adk.' + __name__) diff --git a/src/google/adk/code_executors/unsafe_local_code_executor.py b/src/google/adk/code_executors/unsafe_local_code_executor.py index 932c606899..8fa4b1a60a 100644 --- a/src/google/adk/code_executors/unsafe_local_code_executor.py +++ b/src/google/adk/code_executors/unsafe_local_code_executor.py @@ -27,6 +27,7 @@ from .base_code_executor import BaseCodeExecutor from .code_execution_utils import CodeExecutionInput from .code_execution_utils import CodeExecutionResult +from .isolated_code_executor import IsolatedCodeExecutor logger = logging.getLogger('google_adk.' + __name__) From 7603e57e30e7468af423215a1c6bb7e662e4ac42 Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Sun, 9 Nov 2025 10:52:52 +0530 Subject: [PATCH 08/10] got rid of isolated code executor file, added tests cases (with assistance from gemini) --- src/google/adk/code_executors/__init__.py | 2 - .../code_executors/isolated_code_executor.py | 59 -------------- .../unsafe_local_code_executor.py | 39 ++++++--- .../test_unsafe_local_code_executor.py | 81 +++++++++++++++++++ 4 files changed, 107 insertions(+), 74 deletions(-) delete mode 100644 src/google/adk/code_executors/isolated_code_executor.py diff --git a/src/google/adk/code_executors/__init__.py b/src/google/adk/code_executors/__init__.py index fd66bfbe6b..edeaf5d272 100644 --- a/src/google/adk/code_executors/__init__.py +++ b/src/google/adk/code_executors/__init__.py @@ -19,7 +19,6 @@ from .base_code_executor import BaseCodeExecutor from .built_in_code_executor import BuiltInCodeExecutor from .code_executor_context import CodeExecutorContext -from .isolated_code_executor import IsolatedCodeExecutor from .unsafe_local_code_executor import UnsafeLocalCodeExecutor logger = logging.getLogger('google_adk.' + __name__) @@ -27,7 +26,6 @@ __all__ = [ 'BaseCodeExecutor', 'BuiltInCodeExecutor', - 'IsolatedCodeExecutor', 'CodeExecutorContext', 'UnsafeLocalCodeExecutor', 'VertexAiCodeExecutor', diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py deleted file mode 100644 index 7b74786c43..0000000000 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import annotations - -import subprocess -import sys - -from pydantic import Field -from typing_extensions import override - -from ..agents.invocation_context import InvocationContext -from .base_code_executor import BaseCodeExecutor -from .code_execution_utils import CodeExecutionInput -from .code_execution_utils import CodeExecutionResult - - -class IsolatedCodeExecutor(BaseCodeExecutor): - """A code executor that executes code in an isolated process. - - This provides memory isolation from the main application, but it is not a - full security sandbox. The executed code runs with the same permissions as the - main application and can access the filesystem, network, etc. - """ - - # Overrides the BaseCodeExecutor attribute: this executor cannot be stateful. - stateful: bool = Field(default=False, frozen=True, exclude=True) - - # Overrides the BaseCodeExecutor attribute: this executor cannot - # optimize_data_file. - optimize_data_file: bool = Field(default=False, frozen=True, exclude=True) - - def __init__(self, **data): - """Initializes the IsolatedCodeExecutor.""" - if 'stateful' in data and data['stateful']: - raise ValueError('Cannot set `stateful=True` in IsolatedCodeExecutor.') - if 'optimize_data_file' in data and data['optimize_data_file']: - raise ValueError( - 'Cannot set `optimize_data_file=True` in IsolatedCodeExecutor.' - ) - super().__init__(**data) - - @override - def execute_code( - self, - invocation_context: InvocationContext, - code_execution_input: CodeExecutionInput, - ) -> CodeExecutionResult: - # Executes code by spawning a new python interpreter process. - code = code_execution_input.code - process_result = subprocess.run( - [sys.executable, "-c", code], - capture_output=True, - text=True - ) - - - return CodeExecutionResult( - stdout=process_result.stdout, - stderr=process_result.stderr, - output_files=[], - ) diff --git a/src/google/adk/code_executors/unsafe_local_code_executor.py b/src/google/adk/code_executors/unsafe_local_code_executor.py index 8fa4b1a60a..d998943340 100644 --- a/src/google/adk/code_executors/unsafe_local_code_executor.py +++ b/src/google/adk/code_executors/unsafe_local_code_executor.py @@ -17,17 +17,17 @@ from contextlib import redirect_stdout import io import logging -import re +import subprocess +import sys from typing import Any from pydantic import Field from typing_extensions import override from ..agents.invocation_context import InvocationContext -from .base_code_executor import BaseCodeExecutor from .code_execution_utils import CodeExecutionInput from .code_execution_utils import CodeExecutionResult -from .isolated_code_executor import IsolatedCodeExecutor +from .base_code_executor import BaseCodeExecutor logger = logging.getLogger('google_adk.' + __name__) @@ -63,8 +63,6 @@ def __init__(self, use_isolated_process: bool = False, **data): ) super().__init__(use_isolated_process=use_isolated_process, **data) self.use_isolated_process = use_isolated_process - if self.use_isolated_process: - self._isolated_executor = IsolatedCodeExecutor() @override def execute_code( @@ -72,23 +70,38 @@ def execute_code( invocation_context: InvocationContext, code_execution_input: CodeExecutionInput, ) -> CodeExecutionResult: + if self.use_isolated_process: + logger.debug( + 'Executing code in isolated process:\n```\n%s\n```', + code_execution_input.code, + ) + process_result = subprocess.run( + [sys.executable, '-c', code_execution_input.code], + capture_output=True, + text=True, + ) + return CodeExecutionResult( + stdout=process_result.stdout, + stderr=process_result.stderr, + output_files=[], + ) + logger.debug('Executing code:\n```\n%s\n```', code_execution_input.code) # Execute the code. - output = '' - error = '' + stdout_capture = io.StringIO() + stderr_capture = io.StringIO() try: globals_ = {} _prepare_globals(code_execution_input.code, globals_) - stdout = io.StringIO() - with redirect_stdout(stdout): + with redirect_stdout(stdout_capture): exec(code_execution_input.code, globals_) - output = stdout.getvalue() except Exception as e: - error = str(e) + import traceback + stderr_capture.write(traceback.format_exc()) # Collect the final result. return CodeExecutionResult( - stdout=output, - stderr=error, + stdout=stdout_capture.getvalue(), + stderr=stderr_capture.getvalue(), output_files=[], ) diff --git a/tests/unittests/code_executors/test_unsafe_local_code_executor.py b/tests/unittests/code_executors/test_unsafe_local_code_executor.py index eeb10b34fa..990a32e2dd 100644 --- a/tests/unittests/code_executors/test_unsafe_local_code_executor.py +++ b/tests/unittests/code_executors/test_unsafe_local_code_executor.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import os from unittest.mock import MagicMock @@ -101,3 +102,83 @@ def test_execute_code_empty(self, mock_invocation_context: InvocationContext): result = executor.execute_code(mock_invocation_context, code_input) assert result.stdout == "" assert result.stderr == "" + + def test_execute_code_isolated_simple_print( + self, mock_invocation_context: InvocationContext + ): + """Test execution with use_isolated_process=True.""" + executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + code_input = CodeExecutionInput(code='print("hello isolated world")') + result = executor.execute_code(mock_invocation_context, code_input) + + assert isinstance(result, CodeExecutionResult) + assert result.stdout == "hello isolated world\n" + assert result.stderr == "" + assert result.output_files == [] + + def test_execute_code_isolated_with_error( + self, mock_invocation_context: InvocationContext + ): + """Test error handling with use_isolated_process=True.""" + executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + code_input = CodeExecutionInput(code='raise ValueError("Isolated error")') + result = executor.execute_code(mock_invocation_context, code_input) + + assert isinstance(result, CodeExecutionResult) + assert result.stdout == "" + assert "Isolated error" in result.stderr + assert result.output_files == [] + + def test_execute_code_isolated_with_import( + self, mock_invocation_context: InvocationContext + ): + """Test imports with use_isolated_process=True.""" + executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + code = "import os; print(os.linesep)" + code_input = CodeExecutionInput(code=code) + result = executor.execute_code(mock_invocation_context, code_input) + + assert result.stdout.strip() == os.linesep.strip() + assert result.stderr == "" + + def test_execute_code_if_main( + self, mock_invocation_context: InvocationContext + ): + """Test code with `if __name__ == '__main__'`.""" + executor = UnsafeLocalCodeExecutor() + code = 'if __name__ == "__main__":\n print("executed as main")' + code_input = CodeExecutionInput(code=code) + result = executor.execute_code(mock_invocation_context, code_input) + + assert result.stdout == "executed as main\n" + assert result.stderr == "" + + def test_execute_code_isolated_memory_isolation_variable( + self, mock_invocation_context: InvocationContext + ): + """Test that variables do not persist between isolated executions.""" + executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + + # First execution defines a variable. + code_input1 = CodeExecutionInput(code="x = 100") + executor.execute_code(mock_invocation_context, code_input1) + + # Second execution tries to access the variable. + code_input2 = CodeExecutionInput(code="print(x)") + result = executor.execute_code(mock_invocation_context, code_input2) + + assert "name 'x' is not defined" in result.stderr + + def test_execute_code_isolated_memory_isolation_function( + self, mock_invocation_context: InvocationContext + ): + """Test that functions do not persist between isolated executions.""" + executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + + code_input1 = CodeExecutionInput(code="def my_func(): return 'isolated'") + executor.execute_code(mock_invocation_context, code_input1) + + code_input2 = CodeExecutionInput(code="print(my_func())") + result = executor.execute_code(mock_invocation_context, code_input2) + + assert "name 'my_func' is not defined" in result.stderr From 453e78678dc47fa032dbc93a62735b0753bbe8f9 Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Tue, 11 Nov 2025 07:27:54 +0530 Subject: [PATCH 09/10] changed use_isolated_process to use_separate_process, ran isort for formatting consistency --- contributing/samples/gepa/experiment.py | 1 - contributing/samples/gepa/run_experiment.py | 1 - .../unsafe_local_code_executor.py | 12 ++++++------ .../test_unsafe_local_code_executor.py | 17 ++++++++--------- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/contributing/samples/gepa/experiment.py b/contributing/samples/gepa/experiment.py index 2f5d03a772..f68b349d9c 100644 --- a/contributing/samples/gepa/experiment.py +++ b/contributing/samples/gepa/experiment.py @@ -43,7 +43,6 @@ from tau_bench.types import EnvRunResult from tau_bench.types import RunConfig import tau_bench_agent as tau_bench_agent_lib - import utils diff --git a/contributing/samples/gepa/run_experiment.py b/contributing/samples/gepa/run_experiment.py index cfd850b3a3..1bc4ee58c8 100644 --- a/contributing/samples/gepa/run_experiment.py +++ b/contributing/samples/gepa/run_experiment.py @@ -25,7 +25,6 @@ from absl import flags import experiment from google.genai import types - import utils _OUTPUT_DIR = flags.DEFINE_string( diff --git a/src/google/adk/code_executors/unsafe_local_code_executor.py b/src/google/adk/code_executors/unsafe_local_code_executor.py index d998943340..9d111b6d32 100644 --- a/src/google/adk/code_executors/unsafe_local_code_executor.py +++ b/src/google/adk/code_executors/unsafe_local_code_executor.py @@ -25,9 +25,9 @@ from typing_extensions import override from ..agents.invocation_context import InvocationContext +from .base_code_executor import BaseCodeExecutor from .code_execution_utils import CodeExecutionInput from .code_execution_utils import CodeExecutionResult -from .base_code_executor import BaseCodeExecutor logger = logging.getLogger('google_adk.' + __name__) @@ -51,9 +51,9 @@ class UnsafeLocalCodeExecutor(BaseCodeExecutor): # optimize_data_file. optimize_data_file: bool = Field(default=False, frozen=True, exclude=True) - use_isolated_process: bool = False + use_separate_process: bool = False - def __init__(self, use_isolated_process: bool = False, **data): + def __init__(self, use_separate_process: bool = False, **data): """Initializes the UnsafeLocalCodeExecutor.""" if 'stateful' in data and data['stateful']: raise ValueError('Cannot set `stateful=True` in UnsafeLocalCodeExecutor.') @@ -61,8 +61,8 @@ def __init__(self, use_isolated_process: bool = False, **data): raise ValueError( 'Cannot set `optimize_data_file=True` in UnsafeLocalCodeExecutor.' ) - super().__init__(use_isolated_process=use_isolated_process, **data) - self.use_isolated_process = use_isolated_process + super().__init__(use_separate_process=use_separate_process, **data) + self.use_separate_process = use_separate_process @override def execute_code( @@ -70,7 +70,7 @@ def execute_code( invocation_context: InvocationContext, code_execution_input: CodeExecutionInput, ) -> CodeExecutionResult: - if self.use_isolated_process: + if self.use_separate_process: logger.debug( 'Executing code in isolated process:\n```\n%s\n```', code_execution_input.code, diff --git a/tests/unittests/code_executors/test_unsafe_local_code_executor.py b/tests/unittests/code_executors/test_unsafe_local_code_executor.py index 990a32e2dd..a5ba040a7a 100644 --- a/tests/unittests/code_executors/test_unsafe_local_code_executor.py +++ b/tests/unittests/code_executors/test_unsafe_local_code_executor.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import os - from unittest.mock import MagicMock from google.adk.agents.base_agent import BaseAgent @@ -106,8 +105,8 @@ def test_execute_code_empty(self, mock_invocation_context: InvocationContext): def test_execute_code_isolated_simple_print( self, mock_invocation_context: InvocationContext ): - """Test execution with use_isolated_process=True.""" - executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + """Test execution with use_separate_process=True.""" + executor = UnsafeLocalCodeExecutor(use_separate_process=True) code_input = CodeExecutionInput(code='print("hello isolated world")') result = executor.execute_code(mock_invocation_context, code_input) @@ -119,8 +118,8 @@ def test_execute_code_isolated_simple_print( def test_execute_code_isolated_with_error( self, mock_invocation_context: InvocationContext ): - """Test error handling with use_isolated_process=True.""" - executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + """Test error handling with use_separate_process=True.""" + executor = UnsafeLocalCodeExecutor(use_separate_process=True) code_input = CodeExecutionInput(code='raise ValueError("Isolated error")') result = executor.execute_code(mock_invocation_context, code_input) @@ -132,8 +131,8 @@ def test_execute_code_isolated_with_error( def test_execute_code_isolated_with_import( self, mock_invocation_context: InvocationContext ): - """Test imports with use_isolated_process=True.""" - executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + """Test imports with use_separate_process=True.""" + executor = UnsafeLocalCodeExecutor(use_separate_process=True) code = "import os; print(os.linesep)" code_input = CodeExecutionInput(code=code) result = executor.execute_code(mock_invocation_context, code_input) @@ -157,7 +156,7 @@ def test_execute_code_isolated_memory_isolation_variable( self, mock_invocation_context: InvocationContext ): """Test that variables do not persist between isolated executions.""" - executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + executor = UnsafeLocalCodeExecutor(use_separate_process=True) # First execution defines a variable. code_input1 = CodeExecutionInput(code="x = 100") @@ -173,7 +172,7 @@ def test_execute_code_isolated_memory_isolation_function( self, mock_invocation_context: InvocationContext ): """Test that functions do not persist between isolated executions.""" - executor = UnsafeLocalCodeExecutor(use_isolated_process=True) + executor = UnsafeLocalCodeExecutor(use_separate_process=True) code_input1 = CodeExecutionInput(code="def my_func(): return 'isolated'") executor.execute_code(mock_invocation_context, code_input1) From 1c07fa01d21891c12f6f5d78f7cfe937fd8bd86a Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Wed, 12 Nov 2025 19:13:39 +0530 Subject: [PATCH 10/10] Revert "Added Isolated Code Executor as a separate config" This reverts commit 6a9ee6b4084a4958b5a30d5c672654e8f0670d53. --- src/google/adk/code_executors/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/google/adk/code_executors/__init__.py b/src/google/adk/code_executors/__init__.py index edeaf5d272..b3ff33f960 100644 --- a/src/google/adk/code_executors/__init__.py +++ b/src/google/adk/code_executors/__init__.py @@ -18,6 +18,7 @@ from .base_code_executor import BaseCodeExecutor from .built_in_code_executor import BuiltInCodeExecutor +from .isolated_code_executor import IsolatedCodeExecutor from .code_executor_context import CodeExecutorContext from .unsafe_local_code_executor import UnsafeLocalCodeExecutor