feat: Expose the Python code run by the code interpreter in the logs

Co-authored-by: Eliza Huang <heliza@google.com>
PiperOrigin-RevId: 826521391
This commit is contained in:
Eliza Huang
2025-10-31 09:38:19 -07:00
committed by Copybara-Service
parent 546c2a6816
commit a2c6a8a85c
7 changed files with 59 additions and 1 deletions
@@ -125,6 +125,7 @@ class AgentEngineSandboxCodeExecutor(BaseCodeExecutor):
input_data=input_data,
)
)
logger.debug('Executed code:\n```\n%s\n```', code_execution_input.code)
saved_files = []
stdout = ''
stderr = ''
@@ -131,6 +131,7 @@ class ContainerCodeExecutor(BaseCodeExecutor):
['python3', '-c', code_execution_input.code],
demux=True,
)
logger.debug('Executed code:\n```\n%s\n```', code_execution_input.code)
if exec_result.output and exec_result.output[0]:
output = exec_result.output[0].decode('utf-8')
@@ -162,6 +162,7 @@ class GkeCodeExecutor(BaseCodeExecutor):
logger.info(
f"Submitted Job '{job_name}' to namespace '{self.namespace}'."
)
logger.debug("Executing code:\n```\n%s\n```", code_execution_input.code)
return self._watch_job_completion(job_name)
except ApiException as e:
@@ -16,6 +16,7 @@ from __future__ import annotations
from contextlib import redirect_stdout
import io
import logging
import re
from typing import Any
@@ -27,6 +28,8 @@ from .base_code_executor import BaseCodeExecutor
from .code_execution_utils import CodeExecutionInput
from .code_execution_utils import CodeExecutionResult
logger = logging.getLogger('google_adk.' + __name__)
def _prepare_globals(code: str, globals_: dict[str, Any]) -> None:
"""Prepare globals for code execution, injecting __name__ if needed."""
@@ -60,6 +63,7 @@ class UnsafeLocalCodeExecutor(BaseCodeExecutor):
invocation_context: InvocationContext,
code_execution_input: CodeExecutionInput,
) -> CodeExecutionResult:
logger.debug('Executing code:\n```\n%s\n```', code_execution_input.code)
# Execute the code.
output = ''
error = ''
@@ -152,6 +152,7 @@ class VertexAiCodeExecutor(BaseCodeExecutor):
code_execution_input.input_files,
code_execution_input.execution_id,
)
logger.debug('Executed code:\n```\n%s\n```', code_execution_input.code)
# Save output file as artifacts.
saved_files = []
@@ -187,11 +188,13 @@ class VertexAiCodeExecutor(BaseCodeExecutor):
)
# Collect the final result.
return CodeExecutionResult(
result = CodeExecutionResult(
stdout=code_execution_result.get('execution_result', ''),
stderr=code_execution_result.get('execution_error', ''),
output_files=saved_files,
)
logger.debug('Code execution result: %s', result)
return result
def _execute_code_interpreter(
self,
@@ -20,6 +20,7 @@ import base64
import copy
import dataclasses
import datetime
import logging
import os
import re
from typing import AsyncGenerator
@@ -47,6 +48,8 @@ from ._base_llm_processor import BaseLlmResponseProcessor
if TYPE_CHECKING:
from ...models.llm_request import LlmRequest
logger = logging.getLogger('google_adk.' + __name__)
@dataclasses.dataclass
class DataFileUtil:
@@ -245,6 +248,7 @@ async def _run_pre_processor(
),
),
)
logger.debug('Executed code:\n```\n%s\n```', code_str)
# Update the processing results to code executor context.
code_executor_context.update_code_execution_result(
invocation_context.invocation_id,
@@ -352,6 +356,7 @@ async def _run_post_processor(
),
),
)
logger.debug('Executed code:\n```\n%s\n```', code_str)
code_executor_context.update_code_execution_result(
invocation_context.invocation_id,
code_str,
@@ -20,7 +20,9 @@ from unittest.mock import MagicMock
from unittest.mock import patch
from google.adk.agents.llm_agent import Agent
from google.adk.code_executors.base_code_executor import BaseCodeExecutor
from google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor
from google.adk.code_executors.code_execution_utils import CodeExecutionResult
from google.adk.flows.llm_flows._code_execution import response_processor
from google.adk.models.llm_response import LlmResponse
from google.genai import types
@@ -105,3 +107,44 @@ async def test_builtin_code_executor_image_artifact_creation(mock_datetime):
== f'Saved as artifact: {expected_filename2}. '
)
assert not llm_response.content.parts[2].inline_data
@pytest.mark.asyncio
@patch('google.adk.flows.llm_flows._code_execution.logger')
async def test_logs_executed_code(mock_logger):
"""Test that the response processor logs the code it executes."""
mock_code_executor = MagicMock(spec=BaseCodeExecutor)
mock_code_executor.code_block_delimiters = [('```python\n', '\n```')]
mock_code_executor.error_retry_attempts = 2
mock_code_executor.stateful = False
mock_code_executor.execute_code.return_value = CodeExecutionResult(
stdout='hello'
)
agent = Agent(name='test_agent', code_executor=mock_code_executor)
invocation_context = await testing_utils.create_invocation_context(
agent=agent, user_content='test message'
)
invocation_context.artifact_service = MagicMock()
invocation_context.artifact_service.save_artifact = AsyncMock()
llm_response = LlmResponse(
content=types.Content(
parts=[
types.Part(text='Here is some code:'),
types.Part(text='```python\nprint("hello")\n```'),
]
)
)
_ = [
event
async for event in response_processor.run_async(
invocation_context, llm_response
)
]
mock_code_executor.execute_code.assert_called_once()
mock_logger.debug.assert_called_once_with(
'Executed code:\n```\n%s\n```', 'print("hello")'
)