Files
2026-07-13 13:22:34 +08:00

257 lines
9.5 KiB
Python

import json
from typing import Any
from unittest.mock import Mock
import pandas as pd
import pytest
from mlflow.data.dataset_source_registry import (
get_dataset_source_from_json,
register_dataset_source,
)
from mlflow.data.spark_dataset_source import SparkDatasetSource
from mlflow.entities.evaluation_dataset import DatasetGranularity
from mlflow.entities.evaluation_dataset import EvaluationDataset as MLflowEvaluationDataset
from mlflow.genai.datasets.databricks_evaluation_dataset_source import (
DatabricksEvaluationDatasetSource,
DatabricksUCTableDatasetSource,
)
from mlflow.genai.datasets.evaluation_dataset import EvaluationDataset
def create_test_source_json(table_name: str = "main.default.testtable") -> str:
"""Create a JSON string source value consistent with Databricks managed evaluation datasets.
This format matches the behavior of Databricks managed evaluation datasets as of July 2025.
"""
return json.dumps({"table_name": table_name})
def create_mock_managed_dataset(source_value: Any) -> Mock:
"""Create a mock Databricks Agent Evaluation ManagedDataset for testing"""
mock_dataset = Mock()
mock_dataset.dataset_id = getattr(source_value, "dataset_id", "test-dataset-id")
mock_dataset.name = getattr(source_value, "_table_name", "catalog.schema.table")
mock_dataset.digest = "test-digest"
mock_dataset.schema = "test-schema"
mock_dataset.profile = "test-profile"
mock_dataset.source = source_value
mock_dataset.source_type = "databricks-uc-table"
mock_dataset.create_time = "2024-01-01T00:00:00"
mock_dataset.created_by = "test-user"
mock_dataset.last_update_time = "2024-01-02T00:00:00"
mock_dataset.last_updated_by = "test-user-2"
# Mock methods
mock_dataset.to_df.return_value = pd.DataFrame({"col1": [1, 2, 3], "col2": ["a", "b", "c"]})
mock_dataset.set_profile.return_value = mock_dataset
mock_dataset.merge_records.return_value = mock_dataset
return mock_dataset
@pytest.fixture
def mock_managed_dataset() -> Mock:
"""Create a mock Databricks Agent Evaluation ManagedDataset for testing."""
return create_mock_managed_dataset(create_test_source_json())
def create_dataset_with_source(source_value: Any) -> EvaluationDataset:
"""Factory function to create EvaluationDataset with specific source value."""
mock_dataset = create_mock_managed_dataset(source_value)
return EvaluationDataset(mock_dataset)
def test_evaluation_dataset_properties(mock_managed_dataset):
dataset = EvaluationDataset(mock_managed_dataset)
assert dataset.dataset_id == "test-dataset-id"
assert dataset.name == "catalog.schema.table"
assert dataset.digest == "test-digest"
assert dataset.schema == "test-schema"
assert dataset.profile == "test-profile"
assert dataset.source_type == "databricks-uc-table"
assert dataset.create_time == "2024-01-01T00:00:00"
assert dataset.created_by == "test-user"
assert dataset.last_update_time == "2024-01-02T00:00:00"
assert dataset.last_updated_by == "test-user-2"
assert isinstance(dataset.source, DatabricksEvaluationDatasetSource)
assert dataset.source.table_name == "catalog.schema.table"
assert dataset.source.dataset_id == "test-dataset-id"
def test_evaluation_dataset_source_with_string_source():
dataset = create_dataset_with_source("string-value")
assert isinstance(dataset.source, DatabricksEvaluationDatasetSource)
assert dataset.source.table_name == "catalog.schema.table"
assert dataset.source.dataset_id == "test-dataset-id"
def test_evaluation_dataset_source_with_none():
dataset = create_dataset_with_source(None)
assert isinstance(dataset.source, DatabricksEvaluationDatasetSource)
assert dataset.source.table_name == "catalog.schema.table"
assert dataset.source.dataset_id == "test-dataset-id"
def test_evaluation_dataset_source_always_returns_databricks_evaluation_dataset_source():
existing_source = DatabricksEvaluationDatasetSource(
table_name="existing.table", dataset_id="existing-id"
)
dataset = create_dataset_with_source(existing_source)
assert isinstance(dataset.source, DatabricksEvaluationDatasetSource)
assert dataset.source.table_name == "existing.table"
assert dataset.source.dataset_id == "existing-id"
spark_source = SparkDatasetSource(table_name="spark.table")
dataset = create_dataset_with_source(spark_source)
assert isinstance(dataset.source, DatabricksEvaluationDatasetSource)
assert dataset.source.table_name == "spark.table"
assert dataset.source.dataset_id == "test-dataset-id"
def test_evaluation_dataset_to_df(mock_managed_dataset):
dataset = EvaluationDataset(mock_managed_dataset)
df = dataset.to_df()
assert isinstance(df, pd.DataFrame)
assert len(df) == 3
mock_managed_dataset.to_df.assert_called_once()
def test_evaluation_dataset_to_mlflow_entity(mock_managed_dataset):
dataset = EvaluationDataset(mock_managed_dataset)
entity = dataset._to_mlflow_entity()
assert entity.name == "catalog.schema.table"
assert entity.digest == "test-digest"
assert entity.source_type == "databricks-uc-table"
source_dict = json.loads(entity.source)
assert source_dict["table_name"] == "catalog.schema.table"
assert source_dict["dataset_id"] == "test-dataset-id"
assert entity.schema == "test-schema"
assert entity.profile == "test-profile"
def test_evaluation_dataset_to_mlflow_entity_with_existing_source():
existing_source = DatabricksEvaluationDatasetSource(
table_name="existing.table", dataset_id="existing-id"
)
dataset = create_dataset_with_source(existing_source)
entity = dataset._to_mlflow_entity()
assert entity.name == "existing.table"
assert entity.digest == "test-digest"
assert entity.source_type == "databricks-uc-table"
source_dict = json.loads(entity.source)
assert source_dict["table_name"] == "existing.table"
assert source_dict["dataset_id"] == "existing-id"
assert entity.schema == "test-schema"
assert entity.profile == "test-profile"
def test_evaluation_dataset_set_profile(mock_managed_dataset):
dataset = EvaluationDataset(mock_managed_dataset)
new_dataset = dataset.set_profile("new-profile")
assert isinstance(new_dataset, EvaluationDataset)
mock_managed_dataset.set_profile.assert_called_once_with("new-profile")
def test_evaluation_dataset_merge_records(mock_managed_dataset):
dataset = EvaluationDataset(mock_managed_dataset)
new_records = [{"col1": 4, "col2": "d"}]
new_dataset = dataset.merge_records(new_records)
assert isinstance(new_dataset, EvaluationDataset)
mock_managed_dataset.merge_records.assert_called_once_with(new_records)
def test_evaluation_dataset_delete_records(mock_managed_dataset):
dataset = EvaluationDataset(mock_managed_dataset)
record_ids = ["record-1", "record-2"]
dataset.delete_records(record_ids)
mock_managed_dataset.delete_records.assert_called_once_with(record_ids)
def test_evaluation_dataset_digest_computation(mock_managed_dataset):
# Test when managed dataset has no digest
mock_managed_dataset.digest = None
dataset = EvaluationDataset(mock_managed_dataset)
digest = dataset.digest
assert digest is not None
def test_evaluation_dataset_to_evaluation_dataset(mock_managed_dataset):
dataset = EvaluationDataset(mock_managed_dataset)
legacy_dataset = dataset.to_evaluation_dataset(
path="/path/to/data", feature_names=["col1", "col2"]
)
assert legacy_dataset._features_data.equals(dataset.to_df())
assert legacy_dataset._path == "/path/to/data"
assert legacy_dataset._feature_names == ["col1", "col2"]
assert legacy_dataset.name == "catalog.schema.table"
assert legacy_dataset.digest == "test-digest"
def test_databricks_uc_table_dataset_source():
register_dataset_source(DatabricksUCTableDatasetSource)
source_json = json.dumps({"table_name": "catalog.schema.table", "dataset_id": "test-id"})
source = get_dataset_source_from_json(source_json, "databricks-uc-table")
assert isinstance(source, DatabricksUCTableDatasetSource)
assert source._get_source_type() == "databricks-uc-table"
assert source.table_name == "catalog.schema.table"
assert source.dataset_id == "test-id"
def _create_mlflow_evaluation_dataset() -> MLflowEvaluationDataset:
return MLflowEvaluationDataset(
dataset_id="test-id",
name="test-dataset",
digest="test-digest",
created_time=0,
last_update_time=0,
)
@pytest.mark.parametrize(
("input_keys", "expected_granularity"),
[
# empty keys -> UNKNOWN
(set(), DatasetGranularity.UNKNOWN),
# no 'goal' field -> TRACE
({"request"}, DatasetGranularity.TRACE),
({"messages"}, DatasetGranularity.TRACE),
({"query", "context"}, DatasetGranularity.TRACE),
# 'goal' and only session fields -> SESSION
({"goal"}, DatasetGranularity.SESSION),
({"goal", "persona"}, DatasetGranularity.SESSION),
({"goal", "context"}, DatasetGranularity.SESSION),
({"goal", "persona", "context"}, DatasetGranularity.SESSION),
# 'goal' mixed with non-session fields -> UNKNOWN
({"goal", "request"}, DatasetGranularity.UNKNOWN),
({"goal", "messages"}, DatasetGranularity.UNKNOWN),
({"goal", "persona", "extra_field"}, DatasetGranularity.UNKNOWN),
],
)
def test_classify_input_fields(
input_keys,
expected_granularity,
):
dataset = _create_mlflow_evaluation_dataset()
result = dataset._classify_input_fields(input_keys)
assert result == expected_granularity