@@ -122,15 +122,21 @@ def mock_api_client_fixture():
122122
123123@pytest .fixture
124124def mock_eval_dependencies (mock_api_client_fixture ):
125- with mock .patch ("google.cloud.storage.Client" ) as mock_storage_client , mock .patch (
126- "google.cloud.bigquery.Client"
127- ) as mock_bq_client , mock .patch (
128- "vertexai._genai.evals.Evals.evaluate_instances"
129- ) as mock_evaluate_instances , mock .patch (
130- "vertexai._genai._gcs_utils.GcsUtils.upload_json_to_prefix"
131- ) as mock_upload_to_gcs , mock .patch (
132- "vertexai._genai._evals_metric_loaders.LazyLoadedPrebuiltMetric._fetch_and_parse"
133- ) as mock_fetch_prebuilt_metric :
125+ # fmt: off
126+ with (
127+ mock .patch ("google.cloud.storage.Client" ) as mock_storage_client ,
128+ mock .patch ("google.cloud.bigquery.Client" ) as mock_bq_client ,
129+ mock .patch (
130+ "vertexai._genai.evals.Evals.evaluate_instances"
131+ ) as mock_evaluate_instances ,
132+ mock .patch (
133+ "vertexai._genai._gcs_utils.GcsUtils.upload_json_to_prefix"
134+ ) as mock_upload_to_gcs ,
135+ mock .patch (
136+ "vertexai._genai._evals_metric_loaders.LazyLoadedPrebuiltMetric._fetch_and_parse"
137+ ) as mock_fetch_prebuilt_metric ,
138+ ):
139+ # fmt: on
134140
135141 def mock_evaluate_instances_side_effect (* args , ** kwargs ):
136142 metric_config = kwargs .get ("metric_config" , {})
@@ -3386,14 +3392,8 @@ def test_run_inference_with_agent_engine_falls_back_to_managed_sessions_api(
33863392 assert inference_result .candidate_name == "agent_engine_0"
33873393
33883394 @mock .patch .object (_evals_utils , "EvalDatasetLoader" )
3389- @mock .patch ("vertexai._genai._evals_common.InMemorySessionService" ) # fmt: skip
3390- @mock .patch ("vertexai._genai._evals_common.Runner" )
3391- @mock .patch ("vertexai._genai._evals_common.LlmAgent" )
33923395 def test_run_inference_with_local_agent (
33933396 self ,
3394- mock_llm_agent ,
3395- mock_runner ,
3396- mock_session_service ,
33973397 mock_eval_dataset_loader ,
33983398 ):
33993399 mock_df = pd .DataFrame (
@@ -3421,8 +3421,15 @@ def test_run_inference_with_local_agent(
34213421 mock_agent_instance .instruction = "mock instruction"
34223422 mock_agent_instance .tools = []
34233423 mock_agent_instance .sub_agents = []
3424- mock_llm_agent .return_value = mock_agent_instance
3424+
3425+ # Mock ADK modules for lazy imports in _execute_local_agent_run_with_retry_async
3426+ mock_session_service = mock .MagicMock ()
34253427 mock_session_service .return_value .create_session = mock .AsyncMock ()
3428+ mock_runner = mock .MagicMock ()
3429+ mock_adk_sessions_module = mock .MagicMock ()
3430+ mock_adk_sessions_module .InMemorySessionService = mock_session_service
3431+ mock_adk_runners_module = mock .MagicMock ()
3432+ mock_adk_runners_module .Runner = mock_runner
34263433 mock_runner_instance = mock_runner .return_value
34273434 stream_run_return_value_1 = [
34283435 mock .Mock (
@@ -3473,10 +3480,19 @@ def run_async_side_effect(*args, **kwargs):
34733480
34743481 mock_runner_instance .run_async .side_effect = run_async_side_effect
34753482
3476- inference_result = self .client .evals .run_inference (
3477- agent = mock_agent_instance ,
3478- src = mock_df ,
3479- )
3483+ with mock .patch .dict (
3484+ sys .modules ,
3485+ {
3486+ "google.adk" : mock .MagicMock (),
3487+ "google.adk.sessions" : mock_adk_sessions_module ,
3488+ "google.adk.runners" : mock_adk_runners_module ,
3489+ "google.adk.agents" : mock .MagicMock (),
3490+ },
3491+ ):
3492+ inference_result = self .client .evals .run_inference (
3493+ agent = mock_agent_instance ,
3494+ src = mock_df ,
3495+ )
34803496
34813497 mock_eval_dataset_loader .return_value .load .assert_called_once_with (mock_df )
34823498 assert mock_session_service .call_count == 2
@@ -3602,11 +3618,13 @@ def test_run_inference_with_litellm_string_prompt_format(
36023618 mock_api_client_fixture ,
36033619 ):
36043620 """Tests inference with LiteLLM using a simple prompt string."""
3621+ # fmt: off
36053622 with mock .patch (
36063623 "vertexai._genai._evals_common.litellm"
36073624 ) as mock_litellm , mock .patch (
36083625 "vertexai._genai._evals_common._call_litellm_completion"
36093626 ) as mock_call_litellm_completion :
3627+ # fmt: on
36103628 mock_litellm .utils .get_valid_models .return_value = ["gpt-4o" ]
36113629 prompt_df = pd .DataFrame ([{"prompt" : "What is LiteLLM?" }])
36123630 expected_messages = [{"role" : "user" , "content" : "What is LiteLLM?" }]
@@ -3658,11 +3676,16 @@ def test_run_inference_with_litellm_openai_request_format(
36583676 mock_api_client_fixture ,
36593677 ):
36603678 """Tests inference with LiteLLM where the row contains a chat completion request body."""
3661- with mock .patch (
3662- "vertexai._genai._evals_common.litellm"
3663- ) as mock_litellm , mock .patch (
3664- "vertexai._genai._evals_common._call_litellm_completion"
3665- ) as mock_call_litellm_completion :
3679+ # fmt: off
3680+ with (
3681+ mock .patch (
3682+ "vertexai._genai._evals_common.litellm"
3683+ ) as mock_litellm ,
3684+ mock .patch (
3685+ "vertexai._genai._evals_common._call_litellm_completion"
3686+ ) as mock_call_litellm_completion ,
3687+ ):
3688+ # fmt: on
36663689 mock_litellm .utils .get_valid_models .return_value = ["gpt-4o" ]
36673690 prompt_df = pd .DataFrame (
36683691 [
@@ -4178,21 +4201,23 @@ def test_run_agent_internal_multi_turn_with_agent(self, mock_run_agent):
41784201 ]
41794202 assert "mock_agent" in agent_data ["agents" ]
41804203
4181- @mock .patch ("vertexai._genai._evals_common.ADK_SessionInput" ) # fmt: skip
4182- @mock .patch ("vertexai._genai._evals_common.EvaluationGenerator" ) # fmt: skip
4183- @mock .patch ("vertexai._genai._evals_common.LlmBackedUserSimulator" ) # fmt: skip
4184- @mock .patch ("vertexai._genai._evals_common.ConversationScenario" ) # fmt: skip
4185- @mock .patch ("vertexai._genai._evals_common.LlmBackedUserSimulatorConfig" ) # fmt: skip
41864204 @pytest .mark .asyncio
4187- async def test_run_adk_user_simulation_with_intermediate_events (
4188- self ,
4189- mock_config ,
4190- mock_scenario ,
4191- mock_simulator ,
4192- mock_generator ,
4193- mock_session_input ,
4194- ):
4205+ async def test_run_adk_user_simulation_with_intermediate_events (self ):
41954206 """Tests that intermediate invocation events (e.g. tool calls) are parsed successfully."""
4207+ mock_scenario = mock .MagicMock ()
4208+ mock_config = mock .MagicMock ()
4209+ mock_simulator = mock .MagicMock ()
4210+ mock_generator = mock .MagicMock ()
4211+ mock_session_input = mock .MagicMock ()
4212+ mock_adk_eval_scenarios = mock .MagicMock ()
4213+ mock_adk_eval_scenarios .ConversationScenario = mock_scenario
4214+ mock_adk_eval_case = mock .MagicMock ()
4215+ mock_adk_eval_case .SessionInput = mock_session_input
4216+ mock_adk_eval_generator = mock .MagicMock ()
4217+ mock_adk_eval_generator .EvaluationGenerator = mock_generator
4218+ mock_adk_simulator_module = mock .MagicMock ()
4219+ mock_adk_simulator_module .LlmBackedUserSimulator = mock_simulator
4220+ mock_adk_simulator_module .LlmBackedUserSimulatorConfig = mock_config
41964221 row = pd .Series (
41974222 {
41984223 "starting_prompt" : "I want a laptop." ,
@@ -4245,7 +4270,19 @@ async def test_run_adk_user_simulation_with_intermediate_events(
42454270 mock_generator ._generate_inferences_from_root_agent = mock .AsyncMock (
42464271 return_value = [mock_invocation ]
42474272 )
4248- turns = await _evals_common ._run_adk_user_simulation (row , mock_agent )
4273+ with mock .patch .dict (
4274+ sys .modules ,
4275+ {
4276+ "google.adk" : mock .MagicMock (),
4277+ "google.adk.evaluation" : mock .MagicMock (),
4278+ "google.adk.evaluation.conversation_scenarios" : mock_adk_eval_scenarios ,
4279+ "google.adk.evaluation.eval_case" : mock_adk_eval_case ,
4280+ "google.adk.evaluation.evaluation_generator" : mock_adk_eval_generator ,
4281+ "google.adk.evaluation.simulation" : mock .MagicMock (),
4282+ "google.adk.evaluation.simulation.llm_backed_user_simulator" : mock_adk_simulator_module ,
4283+ },
4284+ ):
4285+ turns = await _evals_common ._run_adk_user_simulation (row , mock_agent )
42494286
42504287 assert len (turns ) == 1
42514288 turn = turns [0 ]
@@ -7086,20 +7123,50 @@ def test_build_request_payload_tool_use_quality_v1_with_agent_data_tool_call(
70867123class TestRunAdkUserSimulation :
70877124 """Unit tests for the _run_adk_user_simulation function."""
70887125
7089- @mock .patch ("vertexai._genai._evals_common.ADK_SessionInput" ) # fmt: skip
7090- @mock .patch ("vertexai._genai._evals_common.EvaluationGenerator" ) # fmt: skip
7091- @mock .patch ("vertexai._genai._evals_common.LlmBackedUserSimulator" ) # fmt: skip
7092- @mock .patch ("vertexai._genai._evals_common.ConversationScenario" ) # fmt: skip
7093- @mock .patch ("vertexai._genai._evals_common.LlmBackedUserSimulatorConfig" ) # fmt: skip
7126+ def _build_adk_mock_modules (self ):
7127+ """Builds mock ADK modules for lazy imports in _run_adk_user_simulation."""
7128+ mock_scenario_cls = mock .MagicMock ()
7129+ mock_config_cls = mock .MagicMock ()
7130+ mock_simulator_cls = mock .MagicMock ()
7131+ mock_generator_cls = mock .MagicMock ()
7132+ mock_session_input_cls = mock .MagicMock ()
7133+ mock_modules = {
7134+ "google.adk" : mock .MagicMock (),
7135+ "google.adk.evaluation" : mock .MagicMock (),
7136+ "google.adk.evaluation.conversation_scenarios" : mock .MagicMock (
7137+ ConversationScenario = mock_scenario_cls
7138+ ),
7139+ "google.adk.evaluation.eval_case" : mock .MagicMock (
7140+ SessionInput = mock_session_input_cls
7141+ ),
7142+ "google.adk.evaluation.evaluation_generator" : mock .MagicMock (
7143+ EvaluationGenerator = mock_generator_cls
7144+ ),
7145+ "google.adk.evaluation.simulation" : mock .MagicMock (),
7146+ "google.adk.evaluation.simulation.llm_backed_user_simulator" : mock .MagicMock (
7147+ LlmBackedUserSimulator = mock_simulator_cls ,
7148+ LlmBackedUserSimulatorConfig = mock_config_cls ,
7149+ ),
7150+ }
7151+ return (
7152+ mock_modules ,
7153+ mock_scenario_cls ,
7154+ mock_config_cls ,
7155+ mock_simulator_cls ,
7156+ mock_generator_cls ,
7157+ mock_session_input_cls ,
7158+ )
7159+
70947160 @pytest .mark .asyncio
7095- async def test_run_adk_user_simulation_success (
7096- self ,
7097- mock_config_cls ,
7098- mock_scenario_cls ,
7099- mock_simulator_cls ,
7100- mock_generator_cls ,
7101- mock_session_input_cls ,
7102- ):
7161+ async def test_run_adk_user_simulation_success (self ):
7162+ (
7163+ mock_modules ,
7164+ mock_scenario_cls ,
7165+ _ ,
7166+ _ ,
7167+ mock_generator_cls ,
7168+ mock_session_input_cls ,
7169+ ) = self ._build_adk_mock_modules ()
71037170 row = pd .Series (
71047171 {
71057172 "starting_prompt" : "start" ,
@@ -7119,7 +7186,8 @@ async def test_run_adk_user_simulation_success(
71197186 return_value = [mock_invocation ]
71207187 )
71217188
7122- turns = await _evals_common ._run_adk_user_simulation (row , mock_agent )
7189+ with mock .patch .dict (sys .modules , mock_modules ):
7190+ turns = await _evals_common ._run_adk_user_simulation (row , mock_agent )
71237191
71247192 assert len (turns ) == 1
71257193 turn = turns [0 ]
@@ -7138,40 +7206,26 @@ async def test_run_adk_user_simulation_success(
71387206 )
71397207 mock_session_input_cls .assert_called_once ()
71407208
7141- @mock .patch ("vertexai._genai._evals_common.ADK_SessionInput" ) # fmt: skip
7142- @mock .patch ("vertexai._genai._evals_common.EvaluationGenerator" ) # fmt: skip
7143- @mock .patch ("vertexai._genai._evals_common.LlmBackedUserSimulator" ) # fmt: skip
7144- @mock .patch ("vertexai._genai._evals_common.ConversationScenario" ) # fmt: skip
7145- @mock .patch ("vertexai._genai._evals_common.LlmBackedUserSimulatorConfig" ) # fmt: skip
71467209 @pytest .mark .asyncio
7147- async def test_run_adk_user_simulation_missing_columns (
7148- self ,
7149- mock_config_cls ,
7150- mock_scenario_cls ,
7151- mock_simulator_cls ,
7152- mock_generator_cls ,
7153- mock_session_input_cls ,
7154- ):
7210+ async def test_run_adk_user_simulation_missing_columns (self ):
7211+ mock_modules , _ , _ , _ , _ , _ = self ._build_adk_mock_modules ()
71557212 row = pd .Series ({"conversation_plan" : "plan" })
71567213 mock_agent = mock .Mock ()
71577214
7158- with pytest .raises (ValueError , match = "User simulation requires" ):
7159- await _evals_common ._run_adk_user_simulation (row , mock_agent )
7215+ with mock .patch .dict (sys .modules , mock_modules ):
7216+ with pytest .raises (ValueError , match = "User simulation requires" ):
7217+ await _evals_common ._run_adk_user_simulation (row , mock_agent )
71607218
7161- @mock .patch ("vertexai._genai._evals_common.ADK_SessionInput" ) # fmt: skip
7162- @mock .patch ("vertexai._genai._evals_common.EvaluationGenerator" ) # fmt: skip
7163- @mock .patch ("vertexai._genai._evals_common.LlmBackedUserSimulator" ) # fmt: skip
7164- @mock .patch ("vertexai._genai._evals_common.ConversationScenario" ) # fmt: skip
7165- @mock .patch ("vertexai._genai._evals_common.LlmBackedUserSimulatorConfig" ) # fmt: skip
71667219 @pytest .mark .asyncio
7167- async def test_run_adk_user_simulation_missing_session_inputs (
7168- self ,
7169- mock_config_cls ,
7170- mock_scenario_cls ,
7171- mock_simulator_cls ,
7172- mock_generator_cls ,
7173- mock_session_input_cls ,
7174- ):
7220+ async def test_run_adk_user_simulation_missing_session_inputs (self ):
7221+ (
7222+ mock_modules ,
7223+ mock_scenario_cls ,
7224+ _ ,
7225+ _ ,
7226+ mock_generator_cls ,
7227+ mock_session_input_cls ,
7228+ ) = self ._build_adk_mock_modules ()
71757229 row = pd .Series (
71767230 {
71777231 "starting_prompt" : "start" ,
@@ -7190,7 +7244,8 @@ async def test_run_adk_user_simulation_missing_session_inputs(
71907244 return_value = [mock_invocation ]
71917245 )
71927246
7193- await _evals_common ._run_adk_user_simulation (row , mock_agent )
7247+ with mock .patch .dict (sys .modules , mock_modules ):
7248+ await _evals_common ._run_adk_user_simulation (row , mock_agent )
71947249
71957250 mock_scenario_cls .assert_called_once_with (
71967251 starting_prompt = "start" ,
0 commit comments