Skip to content

Commit d588021

Browse files
jsondaicopybara-github
authored andcommitted
chore: add evals data converter to vertex_genai
PiperOrigin-RevId: 768417370
1 parent 22259db commit d588021

2 files changed

Lines changed: 396 additions & 0 deletions

File tree

tests/unit/vertexai/genai/test_evals.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import vertexai
2525
from google.cloud.aiplatform import initializer as aiplatform_initializer
2626
from vertexai import _genai
27+
from vertexai._genai import _evals_data_converters
2728
from vertexai._genai import evals
2829
from vertexai._genai import types as vertexai_genai_types
2930
from google.genai import client
@@ -955,3 +956,255 @@ def test_prompt_template_assemble_multimodal_variable_integration(self):
955956
assembled_content.parts[2].text
956957
== " and then answer: This is a simple text."
957958
)
959+
960+
961+
class TestGeminiEvalDataConverter:
962+
"""Unit tests for the _GeminiEvalDataConverter class."""
963+
964+
def setup_method(self):
965+
self.converter = _evals_data_converters._GeminiEvalDataConverter()
966+
967+
def test_convert_simple_prompt_response(self):
968+
raw_data = [
969+
{
970+
"request": {
971+
"contents": [{"role": "user", "parts": [{"text": "Hello"}]}]
972+
},
973+
"response": {
974+
"candidates": [
975+
{
976+
"content": {"role": "model", "parts": [{"text": "Hi"}]},
977+
"finish_reason": "STOP",
978+
}
979+
],
980+
"usage_metadata": {
981+
"prompt_token_count": 1,
982+
"candidates_token_count": 1,
983+
"total_token_count": 2,
984+
},
985+
},
986+
}
987+
]
988+
result_dataset = self.converter.convert(raw_data)
989+
assert isinstance(result_dataset, vertexai_genai_types.EvaluationDataset)
990+
assert len(result_dataset.eval_cases) == 1
991+
eval_case = result_dataset.eval_cases[0]
992+
993+
assert eval_case.prompt == genai_types.Content(
994+
parts=[genai_types.Part(text="Hello")], role="user"
995+
)
996+
assert len(eval_case.responses) == 1
997+
assert eval_case.responses[0].response == genai_types.Content(
998+
parts=[genai_types.Part(text="Hi")], role="model"
999+
)
1000+
assert eval_case.reference.response is None
1001+
assert eval_case.system_instruction.parts is None
1002+
assert eval_case.conversation_history == []
1003+
1004+
def test_convert_with_system_instruction(self):
1005+
raw_data = [
1006+
{
1007+
"request": {
1008+
"system_instruction": {
1009+
"role": "system",
1010+
"parts": [{"text": "Be nice."}],
1011+
},
1012+
"contents": [{"role": "user", "parts": [{"text": "Hello"}]}],
1013+
},
1014+
"response": {
1015+
"candidates": [
1016+
{
1017+
"content": {
1018+
"role": "model",
1019+
"parts": [{"text": "Hi there!"}],
1020+
}
1021+
}
1022+
]
1023+
},
1024+
}
1025+
]
1026+
result_dataset = self.converter.convert(raw_data)
1027+
eval_case = result_dataset.eval_cases[0]
1028+
assert eval_case.system_instruction == genai_types.Content(
1029+
parts=[genai_types.Part(text="Be nice.")], role="system"
1030+
)
1031+
assert eval_case.prompt == genai_types.Content(
1032+
parts=[genai_types.Part(text="Hello")], role="user"
1033+
)
1034+
1035+
def test_convert_with_conversation_history_and_reference(self):
1036+
raw_data_for_reference = [
1037+
{
1038+
"request": {
1039+
"contents": [
1040+
{
1041+
"role": "user",
1042+
"parts": [{"text": "Initial user"}],
1043+
}, # history
1044+
{
1045+
"role": "model",
1046+
"parts": [{"text": "Initial model"}],
1047+
}, # history
1048+
{
1049+
"role": "user",
1050+
"parts": [{"text": "Actual prompt"}],
1051+
}, # prompt
1052+
{
1053+
"role": "model",
1054+
"parts": [{"text": "This is reference"}],
1055+
}, # reference
1056+
]
1057+
},
1058+
"response": {
1059+
"candidates": [
1060+
{
1061+
"content": {
1062+
"role": "model",
1063+
"parts": [{"text": "Actual response"}],
1064+
}
1065+
}
1066+
]
1067+
},
1068+
}
1069+
]
1070+
result_dataset = self.converter.convert(raw_data_for_reference)
1071+
eval_case = result_dataset.eval_cases[0]
1072+
1073+
assert eval_case.prompt == genai_types.Content(
1074+
parts=[genai_types.Part(text="Actual prompt")], role="user"
1075+
)
1076+
assert eval_case.reference.response == genai_types.Content(
1077+
parts=[genai_types.Part(text="This is reference")], role="model"
1078+
)
1079+
assert len(eval_case.conversation_history) == 2
1080+
assert eval_case.conversation_history[0].content == genai_types.Content(
1081+
parts=[genai_types.Part(text="Initial user")], role="user"
1082+
)
1083+
assert eval_case.conversation_history[1].content == genai_types.Content(
1084+
parts=[genai_types.Part(text="Initial model")], role="model"
1085+
)
1086+
assert eval_case.responses[0].response == genai_types.Content(
1087+
parts=[genai_types.Part(text="Actual response")], role="model"
1088+
)
1089+
1090+
def test_convert_with_conversation_history_no_reference(self):
1091+
# Last message in contents is from user, so it becomes the prompt.
1092+
raw_data = [
1093+
{
1094+
"request": {
1095+
"contents": [
1096+
{"role": "user", "parts": [{"text": "Old user msg"}]},
1097+
{"role": "model", "parts": [{"text": "Old model msg"}]},
1098+
{"role": "user", "parts": [{"text": "Current prompt"}]},
1099+
]
1100+
},
1101+
"response": {
1102+
"candidates": [
1103+
{
1104+
"content": {
1105+
"role": "model",
1106+
"parts": [{"text": "A response"}],
1107+
}
1108+
}
1109+
]
1110+
},
1111+
}
1112+
]
1113+
result_dataset = self.converter.convert(raw_data)
1114+
eval_case = result_dataset.eval_cases[0]
1115+
1116+
assert eval_case.prompt == genai_types.Content(
1117+
parts=[genai_types.Part(text="Current prompt")], role="user"
1118+
)
1119+
assert eval_case.reference.response is None
1120+
assert len(eval_case.conversation_history) == 2
1121+
assert eval_case.conversation_history[0].content.parts[0].text == "Old user msg"
1122+
assert (
1123+
eval_case.conversation_history[1].content.parts[0].text == "Old model msg"
1124+
)
1125+
1126+
def test_convert_no_candidates_in_response(self):
1127+
raw_data = [
1128+
{
1129+
"request": {
1130+
"contents": [{"role": "user", "parts": [{"text": "Hello"}]}]
1131+
},
1132+
"response": {
1133+
"candidates": [],
1134+
"prompt_feedback": {"block_reason": "SAFETY"},
1135+
},
1136+
}
1137+
]
1138+
result_dataset = self.converter.convert(raw_data)
1139+
eval_case = result_dataset.eval_cases[0]
1140+
assert len(eval_case.responses) == 1
1141+
assert eval_case.responses[0].response is None
1142+
1143+
def test_convert_invalid_content_structure_raises_value_error(self):
1144+
raw_data = [
1145+
{
1146+
"request": {"contents": ["not a dict"]}, # Invalid content
1147+
"response": {
1148+
"candidates": [
1149+
{"content": {"role": "model", "parts": [{"text": "Hi"}]}}
1150+
]
1151+
},
1152+
}
1153+
]
1154+
with pytest.raises(
1155+
TypeError, match="Expected a dictionary for content at turn 0"
1156+
):
1157+
self.converter.convert(raw_data)
1158+
1159+
raw_data_missing_parts = [
1160+
{
1161+
"request": {"contents": [{"role": "user"}]}, # Missing 'parts'
1162+
"response": {
1163+
"candidates": [
1164+
{"content": {"role": "model", "parts": [{"text": "Hi"}]}}
1165+
]
1166+
},
1167+
}
1168+
]
1169+
with pytest.raises(
1170+
ValueError, match="Missing 'parts' key in content structure at turn 0"
1171+
):
1172+
self.converter.convert(raw_data_missing_parts)
1173+
1174+
def test_convert_multiple_items(self):
1175+
raw_data = [
1176+
{
1177+
"request": {
1178+
"contents": [{"role": "user", "parts": [{"text": "Item 1"}]}]
1179+
},
1180+
"response": {
1181+
"candidates": [
1182+
{
1183+
"content": {
1184+
"role": "model",
1185+
"parts": [{"text": "Resp 1"}],
1186+
}
1187+
}
1188+
]
1189+
},
1190+
},
1191+
{
1192+
"request": {
1193+
"contents": [{"role": "user", "parts": [{"text": "Item 2"}]}]
1194+
},
1195+
"response": {
1196+
"candidates": [
1197+
{
1198+
"content": {
1199+
"role": "model",
1200+
"parts": [{"text": "Resp 2"}],
1201+
}
1202+
}
1203+
]
1204+
},
1205+
},
1206+
]
1207+
result_dataset = self.converter.convert(raw_data)
1208+
assert len(result_dataset.eval_cases) == 2
1209+
assert result_dataset.eval_cases[0].prompt.parts[0].text == "Item 1"
1210+
assert result_dataset.eval_cases[1].prompt.parts[0].text == "Item 2"

0 commit comments

Comments
 (0)