Files
adk-python/tests/unittests/flows/llm_flows/test_code_execution.py
T
2025-10-31 09:38:53 -07:00

151 lines
5.3 KiB
Python

# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
"""Unit tests for Code Execution logic."""
import datetime
from unittest.mock import AsyncMock
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
import pytest
from ... import testing_utils
@pytest.mark.asyncio
@patch('google.adk.flows.llm_flows._code_execution.datetime')
async def test_builtin_code_executor_image_artifact_creation(mock_datetime):
"""Test BuiltInCodeExecutor creates artifacts for images in response."""
mock_now = datetime.datetime(2025, 1, 1, 12, 0, 0)
mock_datetime.datetime.now.return_value.astimezone.return_value = mock_now
code_executor = BuiltInCodeExecutor()
agent = Agent(name='test_agent', code_executor=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(
return_value='v1'
)
llm_response = LlmResponse(
content=types.Content(
parts=[
types.Part(
inline_data=types.Blob(
mime_type='image/png',
data=b'image1',
display_name='image_1.png',
)
),
types.Part(text='this is text'),
types.Part(
inline_data=types.Blob(mime_type='image/jpeg', data=b'image2')
),
]
)
)
events = []
async for event in response_processor.run_async(
invocation_context, llm_response
):
events.append(event)
expected_timestamp = mock_now.strftime('%Y%m%d_%H%M%S')
expected_filename2 = f'{expected_timestamp}.jpeg'
assert invocation_context.artifact_service.save_artifact.call_count == 2
invocation_context.artifact_service.save_artifact.assert_any_call(
app_name=invocation_context.app_name,
user_id=invocation_context.user_id,
session_id=invocation_context.session.id,
filename='image_1.png',
artifact=types.Part.from_bytes(data=b'image1', mime_type='image/png'),
)
invocation_context.artifact_service.save_artifact.assert_any_call(
app_name=invocation_context.app_name,
user_id=invocation_context.user_id,
session_id=invocation_context.session.id,
filename=expected_filename2,
artifact=types.Part.from_bytes(data=b'image2', mime_type='image/jpeg'),
)
assert len(events) == 1
assert events[0].actions.artifact_delta == {
'image_1.png': 'v1',
expected_filename2: 'v1',
}
assert not events[0].content
assert llm_response.content is not None
assert len(llm_response.content.parts) == 3
assert (
llm_response.content.parts[0].text == 'Saved as artifact: image_1.png. '
)
assert not llm_response.content.parts[0].inline_data
assert llm_response.content.parts[1].text == 'this is text'
assert (
llm_response.content.parts[2].text
== 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")'
)