项目文件夹

文件
2026-07-13 13:22:34 +08:00

224 行
6.7 KiB
Python

from typing import Any, Union
import pytest
from pydantic import BaseModel
from mlflow.entities.assessment import Feedback
from mlflow.exceptions import MlflowException
from mlflow.genai.judges import CategoricalRating
from mlflow.genai.optimize.util import (
create_metric_from_scorers,
infer_type_from_value,
validate_train_data,
)
from mlflow.genai.scorers import scorer
@pytest.mark.parametrize(
("input_value", "expected_type"),
[
(None, type(None)),
(True, bool),
(42, int),
(3.14, float),
("hello", str),
],
)
def test_infer_primitive_types(input_value, expected_type):
assert infer_type_from_value(input_value) == expected_type
@pytest.mark.parametrize(
("input_list", "expected_type"),
[
([], list[Any]),
([1, 2, 3], list[int]),
(["a", "b", "c"], list[str]),
([1.0, 2.0, 3.0], list[float]),
([True, False, True], list[bool]),
([1, "hello", True], list[Union[int, str, bool]]), # noqa: UP007
([1, "hello", True], list[int | str | bool]),
([1, 2.0], list[int | float]),
([[1, 2], [3, 4]], list[list[int]]),
([["a"], ["b", "c"]], list[list[str]]),
],
)
def test_infer_list_types(input_list, expected_type):
assert infer_type_from_value(input_list) == expected_type
@pytest.mark.parametrize(
("input_dict", "expected_fields"),
[
({"name": "John", "age": 30, "active": True}, {"name": str, "age": int, "active": bool}),
({"score": 95.5, "passed": True}, {"score": float, "passed": bool}),
],
)
def test_infer_simple_dict(input_dict, expected_fields):
result = infer_type_from_value(input_dict)
assert isinstance(result, type)
assert issubclass(result, BaseModel)
for field_name, expected_type in expected_fields.items():
assert result.__annotations__[field_name] == expected_type
def test_infer_nested_dict():
data = {
"user": {"name": "John", "scores": [85, 90, 95]},
"settings": {"enabled": True, "theme": "dark"},
}
result = infer_type_from_value(data)
assert isinstance(result, type)
assert issubclass(result, BaseModel)
# Check nested model types
user_model = result.__annotations__["user"]
settings_model = result.__annotations__["settings"]
assert issubclass(user_model, BaseModel)
assert issubclass(settings_model, BaseModel)
# Check nested field types
assert user_model.__annotations__["name"] == str
assert user_model.__annotations__["scores"] == list[int]
assert settings_model.__annotations__["enabled"] == bool
assert settings_model.__annotations__["theme"] == str
@pytest.mark.parametrize(
("model_class", "model_data"),
[
(
type("UserModel", (BaseModel,), {"__annotations__": {"name": str, "age": int}}),
{"name": "John", "age": 30},
),
(
type("ProductModel", (BaseModel,), {"__annotations__": {"id": int, "price": float}}),
{"id": 1, "price": 99.99},
),
],
)
def test_infer_pydantic_model(model_class, model_data):
model = model_class(**model_data)
result = infer_type_from_value(model)
assert result == model_class
@pytest.mark.parametrize(
"type_to_infer",
[
type("CustomClass", (), {}),
type("AnotherClass", (), {"custom_attr": 42}),
],
)
def test_infer_unsupported_type(type_to_infer):
obj = type_to_infer()
assert infer_type_from_value(obj) == Any
@pytest.mark.parametrize(
("input_dict", "model_name"),
[
({"name": "John", "age": 30}, "UserData"),
({"id": 1, "value": "test"}, "TestModel"),
],
)
def test_model_name_parameter(input_dict, model_name):
result = infer_type_from_value(input_dict, model_name=model_name)
assert result.__name__ == model_name
@pytest.mark.parametrize(
("score", "expected_score"),
[
(CategoricalRating.YES, 1.0),
(CategoricalRating.NO, 0.0),
("yes", 1.0),
("no", 0.0),
(True, 1.0),
(False, 0.0),
(1, 1.0),
(0, 0.0),
(1.0, 1.0),
(0.0, 0.0),
],
)
def test_create_metric_from_scorers_with_single_score(score, expected_score):
@scorer(name="test_scorer")
def test_scorer(inputs, outputs):
return Feedback(name="test_scorer", value=score, rationale="test rationale")
metric = create_metric_from_scorers([test_scorer])
result = metric({"input": "test"}, {"output": "result"}, {}, None)
assert result[0] == expected_score
assert result[1] == {"test_scorer": "test rationale"}
assert result[2] == {"test_scorer": expected_score}
def test_create_metric_from_scorers_with_multiple_categorical_ratings():
@scorer(name="scorer1")
def scorer1(inputs, outputs):
return Feedback(name="scorer1", value=CategoricalRating.YES, rationale="rationale1")
@scorer(name="scorer2")
def scorer2(inputs, outputs):
return Feedback(name="scorer2", value=CategoricalRating.YES, rationale="rationale2")
metric = create_metric_from_scorers([scorer1, scorer2])
# Should average: (1.0 + 1.0) / 2 = 1.0
result = metric({"input": "test"}, {"output": "result"}, {}, None)
assert result[0] == 1.0
assert result[1] == {"scorer1": "rationale1", "scorer2": "rationale2"}
assert result[2] == {"scorer1": 1.0, "scorer2": 1.0}
@pytest.mark.parametrize(
("train_data", "scorers", "expected_error"),
[
# Empty inputs
(
[{"inputs": {}, "outputs": "result"}],
[],
"Record 0 is missing required 'inputs' field or it is empty",
),
# Missing inputs
(
[{"outputs": "result"}],
[],
"Record 0 is missing required 'inputs' field or it is empty",
),
],
)
def test_validate_train_data_errors(train_data, scorers, expected_error):
import pandas as pd
with pytest.raises(MlflowException, match=expected_error):
validate_train_data(pd.DataFrame(train_data), scorers, lambda **kwargs: None)
@pytest.mark.parametrize(
"train_data",
[
# Valid with outputs
[{"inputs": {"text": "hello"}, "outputs": "result"}],
# Valid with expectations
[{"inputs": {"text": "hello"}, "expectations": {"expected": "result"}}],
# Multiple valid records
[
{"inputs": {"text": "hello"}, "outputs": "result1"},
{"inputs": {"text": "world"}, "expectations": {"expected": "result2"}},
],
# Falsy but valid values: False as output
[{"inputs": {"text": "hello"}, "outputs": False}],
],
)
def test_validate_train_data_success(train_data):
import pandas as pd
validate_train_data(pd.DataFrame(train_data), [], lambda **kwargs: None)