Files
2026-07-13 13:32:05 +08:00

240 lines
8.4 KiB
Python

import pytest
import json
import os
from deepeval.dataset import EvaluationDataset, Golden, ConversationalGolden
from deepeval.test_case import ToolCall
from collections import Counter
def create_tool_calls_from_data(tools_data):
"""Convert JSON tool data to ToolCall objects"""
if not tools_data:
return None
tool_calls = []
for tool_data in tools_data:
if isinstance(tool_data, dict) and "name" in tool_data:
tool_call = ToolCall(
name=tool_data["name"], input=tool_data.get("input", None)
)
tool_calls.append(tool_call)
return tool_calls
def load_goldens_data(path: str):
"""Load golden data from JSON file"""
current_dir = os.path.dirname(os.path.abspath(__file__))
json_path = os.path.join(current_dir, path)
with open(json_path, "r") as f:
return json.load(f)
def deep_equal_unordered(a, b):
"""Compare two objects, handling Pydantic models and unordered lists"""
from pydantic import BaseModel
# Handle Pydantic models by converting to dict
if isinstance(a, BaseModel) and isinstance(b, BaseModel):
return deep_equal_unordered(a.model_dump(), b.model_dump())
elif isinstance(a, BaseModel):
return deep_equal_unordered(a.model_dump(), b)
elif isinstance(b, BaseModel):
return deep_equal_unordered(a, b.model_dump())
# Handle lists (order doesn't matter)
if isinstance(a, list) and isinstance(b, list):
if len(a) != len(b):
return False
# For small lists, use simple comparison
if len(a) <= 10:
a_sorted = sorted(a, key=lambda x: str(freeze_for_comparison(x)))
b_sorted = sorted(b, key=lambda x: str(freeze_for_comparison(x)))
return all(
deep_equal_unordered(x, y) for x, y in zip(a_sorted, b_sorted)
)
else:
# For larger lists, use Counter approach
return Counter(
map(lambda x: freeze_for_comparison(x), a)
) == Counter(map(lambda x: freeze_for_comparison(x), b))
# Handle dictionaries
if isinstance(a, dict) and isinstance(b, dict):
return a.keys() == b.keys() and all(
deep_equal_unordered(a[k], b[k]) for k in a
)
# Base case: direct comparison
return a == b
def freeze_for_comparison(obj):
"""Convert object to hashable form for comparison"""
from pydantic import BaseModel
if isinstance(obj, BaseModel):
return freeze_for_comparison(obj.model_dump())
elif isinstance(obj, dict):
return tuple(
sorted((k, freeze_for_comparison(v)) for k, v in obj.items())
)
elif isinstance(obj, list):
return tuple(freeze_for_comparison(x) for x in obj)
elif isinstance(obj, (str, int, float, bool, type(None))):
return obj
else:
# For other types, convert to string
return str(obj)
class TestSingleTurnDataset:
PUSH_ALIAS = "test_single_turn_realistic_push"
QUEUE_ALIAS = "test_single_turn_realistic_queue"
def create_golden_from_data(self, data):
"""Create a Golden object from JSON data"""
return Golden(
input=data.get("input", None),
actual_output=data.get("actual_output", None),
expected_output=data.get("expected_output", None),
context=data.get("context", None),
retrieval_context=data.get("retrieval_context", None),
additional_metadata=data.get("additional_metadata", None),
comments=data.get("comments", None),
tools_called=create_tool_calls_from_data(
data.get("tools_called", None)
),
expected_tools=create_tool_calls_from_data(
data.get("expected_tools", None)
),
custom_column_key_values=data.get("custom_column_key_values", None),
)
def test_dataset_push_pull(self):
goldens_data = load_goldens_data("goldens.json")
initial_goldens = []
for data in goldens_data:
golden = self.create_golden_from_data(data)
initial_goldens.append(golden)
dataset = EvaluationDataset(goldens=initial_goldens)
dataset.delete(alias=self.PUSH_ALIAS)
dataset.push(alias=self.PUSH_ALIAS)
dataset.goldens = []
dataset.pull(alias=self.PUSH_ALIAS)
assert len(dataset.goldens) == len(initial_goldens)
assert deep_equal_unordered(dataset.goldens, initial_goldens)
class TestDatasetVersioning:
PUSH_ALIAS = "test_dataset_versioning"
def create_golden_from_data(self, data):
return Golden(
input=data.get("input", None),
actual_output=data.get("actual_output", None),
expected_output=data.get("expected_output", None),
context=data.get("context", None),
retrieval_context=data.get("retrieval_context", None),
additional_metadata=data.get("additional_metadata", None),
comments=data.get("comments", None),
tools_called=create_tool_calls_from_data(
data.get("tools_called", None)
),
expected_tools=create_tool_calls_from_data(
data.get("expected_tools", None)
),
custom_column_key_values=data.get("custom_column_key_values", None),
)
def test_create_get_pull_push_version(self):
goldens_data = load_goldens_data("goldens.json")
initial_goldens = [
self.create_golden_from_data(d) for d in goldens_data
]
dataset = EvaluationDataset(goldens=initial_goldens)
dataset.delete(alias=self.PUSH_ALIAS)
dataset.push(alias=self.PUSH_ALIAS)
version = dataset.create_version(alias=self.PUSH_ALIAS)
assert isinstance(version, str) and version
assert dataset._version == version
versions = dataset.get_versions(alias=self.PUSH_ALIAS)
assert any(v.version == version for v in versions)
dataset.goldens = []
dataset.pull(alias=self.PUSH_ALIAS, version=version)
assert dataset._version == version
assert len(dataset.goldens) == len(initial_goldens)
dataset.pull(alias=self.PUSH_ALIAS)
assert dataset._version == version
new_dataset = EvaluationDataset(goldens=initial_goldens[:1])
new_dataset.push(alias=self.PUSH_ALIAS, version=version)
class TestMultiTurnDataset:
PUSH_ALIAS = "test_multi_turn_realistic_push"
QUEUE_ALIAS = "test_multi_turn_realistic_queue"
def create_golden_from_data(self, data):
"""Create a Golden object from JSON data"""
return ConversationalGolden(
scenario=data.get("scenario", None),
expected_outcome=data.get("expected_outcome", None),
user_description=data.get("user_description", None),
context=data.get("context", None),
additional_metadata=data.get("additional_metadata", None),
comments=data.get("comments", None),
turns=data.get("turns", None),
custom_column_key_values=data.get("custom_column_key_values", None),
)
def test_dataset_push_pull(self):
goldens_data = load_goldens_data("goldens_multi_turn.json")
initial_goldens = []
for data in goldens_data:
golden = self.create_golden_from_data(data)
initial_goldens.append(golden)
dataset = EvaluationDataset(goldens=initial_goldens)
dataset.delete(alias=self.PUSH_ALIAS)
dataset.push(alias=self.PUSH_ALIAS)
dataset.goldens = []
dataset.pull(alias=self.PUSH_ALIAS)
assert len(dataset.goldens) == len(initial_goldens)
assert deep_equal_unordered(dataset.goldens, initial_goldens)
# def test_dataset_queue(self):
# goldens_data = load_goldens_data("goldens_multi_turn.json")
# initial_goldens = []
# for data in goldens_data:
# golden = self.create_golden_from_data(data)
# initial_goldens.append(golden)
# dataset = EvaluationDataset()
# dataset.queue(alias=self.QUEUE_ALIAS, goldens=initial_goldens)
# dataset.goldens = []
# with pytest.raises(Exception):
# dataset.pull(alias=self.QUEUE_ALIAS)
# dataset.pull(alias=self.QUEUE_ALIAS, finalized=False)
# assert len(dataset.goldens) == len(initial_goldens)
# assert deep_equal_unordered(dataset.goldens, initial_goldens)