@@ -9563,6 +9563,230 @@ def test_resolve_dataset_preserves_conversation_history(
95639563 assert "conversation_history" in ptd_values
95649564
95659565
9566+ class TestResolveDatasetWithInteractions :
9567+ """Tests for resolving interactions_data_source in _resolve_dataset."""
9568+
9569+ def setup_method (self ):
9570+ self .mock_api_client = mock .Mock ()
9571+ self .mock_api_client .project = "test-project"
9572+ self .mock_api_client .location = "us-central1"
9573+
9574+ def test_has_interactions_data_source_true (self ):
9575+ cases = [
9576+ agentplatform_genai_types .EvalCase (
9577+ interactions_data_source = agentplatform_genai_types .InteractionsDataSource (
9578+ gemini_agent_config = agentplatform_genai_types .GeminiAgentConfig (
9579+ gemini_agent = "projects/p/locations/l/agents/a"
9580+ ),
9581+ interaction = "projects/p/locations/l/interactions/i1" ,
9582+ )
9583+ )
9584+ ]
9585+ assert _evals_common ._has_interactions_data_source (cases )
9586+
9587+ def test_has_interactions_data_source_false (self ):
9588+ cases = [
9589+ agentplatform_genai_types .EvalCase (
9590+ prompt = genai_types .Content (
9591+ parts = [genai_types .Part (text = "test" )]
9592+ ),
9593+ )
9594+ ]
9595+ assert not _evals_common ._has_interactions_data_source (cases )
9596+
9597+ def test_resolve_rejects_mixed_cases (self ):
9598+ """Mixing interaction-based and prompt-based cases raises ValueError."""
9599+ cases = [
9600+ agentplatform_genai_types .EvalCase (
9601+ interactions_data_source = agentplatform_genai_types .InteractionsDataSource (
9602+ interaction = "projects/p/locations/l/interactions/i1" ,
9603+ )
9604+ ),
9605+ agentplatform_genai_types .EvalCase (
9606+ prompt = genai_types .Content (
9607+ parts = [genai_types .Part (text = "test" )]
9608+ ),
9609+ ),
9610+ ]
9611+ with pytest .raises (ValueError , match = "interactions_data_source" ):
9612+ _evals_common ._resolve_interactions_to_eval_cases (
9613+ self .mock_api_client , cases
9614+ )
9615+
9616+ def test_resolve_rejects_missing_interaction (self ):
9617+ """EvalCase with interactions_data_source but no interaction raises."""
9618+ cases = [
9619+ agentplatform_genai_types .EvalCase (
9620+ interactions_data_source = agentplatform_genai_types .InteractionsDataSource (
9621+ gemini_agent_config = agentplatform_genai_types .GeminiAgentConfig (
9622+ gemini_agent = "projects/p/locations/l/agents/a"
9623+ ),
9624+ )
9625+ ),
9626+ ]
9627+ with pytest .raises (ValueError , match = "interaction is required" ):
9628+ _evals_common ._resolve_interactions_to_eval_cases (
9629+ self .mock_api_client , cases
9630+ )
9631+
9632+ def test_interaction_dict_to_agent_data_text_conversation (self ):
9633+ """Converts user_input + model_output steps to agent_data."""
9634+ interaction_dict = {
9635+ "status" : "completed" ,
9636+ "steps" : [
9637+ {
9638+ "type" : "user_input" ,
9639+ "content" : [{"type" : "text" , "text" : "Hello agent" }],
9640+ },
9641+ {
9642+ "type" : "model_output" ,
9643+ "content" : [
9644+ {"type" : "text" , "text" : "Hello! How can I help?" }
9645+ ],
9646+ },
9647+ ]
9648+ }
9649+
9650+ result = _evals_common ._interaction_dict_to_agent_data (
9651+ interaction_dict
9652+ )
9653+
9654+ assert len (result .turns ) == 1
9655+ events = result .turns [0 ].events
9656+ assert len (events ) == 2
9657+ assert events [0 ].author == "user"
9658+ assert events [0 ].content .parts [0 ].text == "Hello agent"
9659+ assert events [1 ].author == "agent"
9660+ assert events [1 ].content .parts [0 ].text == "Hello! How can I help?"
9661+
9662+ def test_interaction_dict_to_agent_data_with_tool_calls (self ):
9663+ """Converts function_call + function_result steps."""
9664+ interaction_dict = {
9665+ "status" : "completed" ,
9666+ "steps" : [
9667+ {
9668+ "type" : "function_call" ,
9669+ "name" : "get_weather" ,
9670+ "arguments" : {"city" : "NYC" },
9671+ "id" : "call_1" ,
9672+ },
9673+ {
9674+ "type" : "function_result" ,
9675+ "name" : "get_weather" ,
9676+ "call_id" : "call_1" ,
9677+ "result" : {"temp" : "72F" },
9678+ },
9679+ ]
9680+ }
9681+
9682+ result = _evals_common ._interaction_dict_to_agent_data (
9683+ interaction_dict
9684+ )
9685+
9686+ events = result .turns [0 ].events
9687+ assert len (events ) == 2
9688+ fc_event = events [0 ]
9689+ assert fc_event .author == "agent"
9690+ assert fc_event .content .parts [0 ].function_call .name == "get_weather"
9691+ fr_event = events [1 ]
9692+ assert fr_event .content .parts [0 ].function_response .id == "call_1"
9693+
9694+ def test_resolve_interactions_to_eval_cases_happy_path (self ):
9695+ """Fetches an interaction and populates agent_data on the EvalCase."""
9696+ interaction_body = json .dumps ({
9697+ "status" : "completed" ,
9698+ "steps" : [
9699+ {
9700+ "type" : "user_input" ,
9701+ "content" : [{"type" : "text" , "text" : "What is the weather?" }],
9702+ },
9703+ {
9704+ "type" : "model_output" ,
9705+ "content" : [{"type" : "text" , "text" : "It is sunny." }],
9706+ },
9707+ ]
9708+ })
9709+ agent_body = json .dumps ({
9710+ "system_instruction" : "You are helpful." ,
9711+ "base_agent" : "gemini-2.0-flash" ,
9712+ })
9713+
9714+ def mock_request (method , path , * args , ** kwargs ):
9715+ resp = mock .MagicMock ()
9716+ if path .startswith ("interactions/" ):
9717+ resp .body = interaction_body
9718+ elif path .startswith ("agents/" ):
9719+ resp .body = agent_body
9720+ else :
9721+ resp .body = None
9722+ return resp
9723+
9724+ self .mock_api_client .request .side_effect = mock_request
9725+
9726+ original_case = agentplatform_genai_types .EvalCase (
9727+ interactions_data_source = agentplatform_genai_types .InteractionsDataSource (
9728+ gemini_agent_config = agentplatform_genai_types .GeminiAgentConfig (
9729+ gemini_agent = "projects/p/locations/l/agents/my-agent" ,
9730+ ),
9731+ interaction = "projects/p/locations/l/interactions/i1" ,
9732+ ),
9733+ )
9734+
9735+ resolved = _evals_common ._resolve_interactions_to_eval_cases (
9736+ self .mock_api_client , [original_case ]
9737+ )
9738+
9739+ assert len (resolved ) == 1
9740+ result_case = resolved [0 ]
9741+
9742+ # interactions_data_source should be cleared after resolution.
9743+ assert result_case .interactions_data_source is None
9744+
9745+ # agent_data should be populated with the interaction content.
9746+ agent_data = result_case .agent_data
9747+ assert agent_data is not None
9748+ assert len (agent_data .turns ) == 1
9749+ events = agent_data .turns [0 ].events
9750+ assert len (events ) == 2
9751+ assert events [0 ].author == "user"
9752+ assert events [0 ].content .parts [0 ].text == "What is the weather?"
9753+ assert events [1 ].author == "agent"
9754+ assert events [1 ].content .parts [0 ].text == "It is sunny."
9755+
9756+ # agents map should be populated with config from the Agent API.
9757+ assert "my-agent" in agent_data .agents
9758+ agent_config = agent_data .agents ["my-agent" ]
9759+ assert agent_config .agent_id == "my-agent"
9760+ assert agent_config .instruction == "You are helpful."
9761+ assert agent_config .agent_type == "gemini-2.0-flash"
9762+
9763+ def test_resolve_preserves_original_eval_case_fields (self ):
9764+ """Fields other than agent_data are preserved on the resolved EvalCase."""
9765+ interaction_body = json .dumps ({"status" : "completed" , "steps" : []})
9766+
9767+ def mock_request (method , path , * args , ** kwargs ):
9768+ resp = mock .MagicMock ()
9769+ resp .body = interaction_body
9770+ return resp
9771+
9772+ self .mock_api_client .request .side_effect = mock_request
9773+
9774+ original_case = agentplatform_genai_types .EvalCase (
9775+ interactions_data_source = agentplatform_genai_types .InteractionsDataSource (
9776+ interaction = "projects/p/locations/l/interactions/i1" ,
9777+ ),
9778+ )
9779+
9780+ resolved = _evals_common ._resolve_interactions_to_eval_cases (
9781+ self .mock_api_client , [original_case ]
9782+ )
9783+
9784+ assert len (resolved ) == 1
9785+ # agent_data is populated and interactions_data_source cleared.
9786+ assert resolved [0 ].agent_data is not None
9787+ assert resolved [0 ].interactions_data_source is None
9788+
9789+
95669790class TestRateLimiter :
95679791 """Tests for the RateLimiter class in _evals_utils."""
95689792
0 commit comments