773 lines
23 KiB
Python
773 lines
23 KiB
Python
import json
|
|
from typing import Any
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
import mlflow
|
|
from mlflow.cli.scorers import commands
|
|
from mlflow.exceptions import MlflowException
|
|
from mlflow.genai.scorers import get_all_scorers, list_scorers, scorer
|
|
from mlflow.utils.string_utils import _create_table
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_databricks_environment():
|
|
with (
|
|
patch("mlflow.genai.scorers.base.is_databricks_uri", return_value=True),
|
|
):
|
|
yield
|
|
|
|
|
|
@pytest.fixture
|
|
def runner():
|
|
return CliRunner(catch_exceptions=False)
|
|
|
|
|
|
@pytest.fixture
|
|
def experiment():
|
|
"""Create a test experiment."""
|
|
experiment_id = mlflow.create_experiment(
|
|
f"test_scorers_cli_{mlflow.utils.time.get_current_time_millis()}"
|
|
)
|
|
yield experiment_id
|
|
mlflow.delete_experiment(experiment_id)
|
|
|
|
|
|
@pytest.fixture
|
|
def correctness_scorer():
|
|
"""Create a correctness scorer."""
|
|
|
|
@scorer
|
|
def _correctness_scorer(outputs) -> bool:
|
|
return len(outputs) > 0
|
|
|
|
return _correctness_scorer
|
|
|
|
|
|
@pytest.fixture
|
|
def safety_scorer():
|
|
"""Create a safety scorer."""
|
|
|
|
@scorer
|
|
def _safety_scorer(outputs) -> bool:
|
|
return len(outputs) > 0
|
|
|
|
return _safety_scorer
|
|
|
|
|
|
@pytest.fixture
|
|
def relevance_scorer():
|
|
"""Create a relevance scorer."""
|
|
|
|
@scorer
|
|
def _relevance_scorer(outputs) -> bool:
|
|
return len(outputs) > 0
|
|
|
|
return _relevance_scorer
|
|
|
|
|
|
@pytest.fixture
|
|
def generic_scorer():
|
|
"""Create a generic test scorer."""
|
|
|
|
@scorer
|
|
def _generic_scorer(outputs) -> bool:
|
|
return True
|
|
|
|
return _generic_scorer
|
|
|
|
|
|
def test_commands_group_exists():
|
|
assert commands.name == "scorers"
|
|
assert commands.help is not None
|
|
|
|
|
|
def test_list_command_params():
|
|
list_cmd = next((cmd for cmd in commands.commands.values() if cmd.name == "list"), None)
|
|
assert list_cmd is not None
|
|
param_names = {p.name for p in list_cmd.params}
|
|
assert param_names == {"experiment_id", "builtin", "output"}
|
|
|
|
|
|
def test_list_scorers_table_output(
|
|
runner: CliRunner,
|
|
experiment: str,
|
|
correctness_scorer: Any,
|
|
safety_scorer: Any,
|
|
relevance_scorer: Any,
|
|
mock_databricks_environment: Any,
|
|
):
|
|
correctness_scorer.register(experiment_id=experiment, name="Correctness")
|
|
safety_scorer.register(experiment_id=experiment, name="Safety")
|
|
relevance_scorer.register(experiment_id=experiment, name="RelevanceToQuery")
|
|
|
|
result = runner.invoke(commands, ["list", "--experiment-id", experiment])
|
|
|
|
assert result.exit_code == 0
|
|
|
|
# Construct expected table output (scorers are returned in alphabetical order)
|
|
# Note: click.echo() adds a trailing newline
|
|
expected_table = (
|
|
_create_table(
|
|
[["Correctness", ""], ["RelevanceToQuery", ""], ["Safety", ""]],
|
|
headers=["Scorer Name", "Description"],
|
|
)
|
|
+ "\n"
|
|
)
|
|
assert result.output == expected_table
|
|
|
|
|
|
def test_list_scorers_json_output(
|
|
runner: CliRunner,
|
|
experiment: str,
|
|
correctness_scorer: Any,
|
|
safety_scorer: Any,
|
|
relevance_scorer: Any,
|
|
mock_databricks_environment: Any,
|
|
):
|
|
correctness_scorer.register(experiment_id=experiment, name="Correctness")
|
|
safety_scorer.register(experiment_id=experiment, name="Safety")
|
|
relevance_scorer.register(experiment_id=experiment, name="RelevanceToQuery")
|
|
|
|
result = runner.invoke(commands, ["list", "--experiment-id", experiment, "--output", "json"])
|
|
|
|
assert result.exit_code == 0
|
|
output_json = json.loads(result.output)
|
|
expected_scorers = [
|
|
{"name": "Correctness", "description": None},
|
|
{"name": "RelevanceToQuery", "description": None},
|
|
{"name": "Safety", "description": None},
|
|
]
|
|
assert output_json["scorers"] == expected_scorers
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("output_format", "expected_output"),
|
|
[
|
|
("table", ""),
|
|
("json", {"scorers": []}),
|
|
],
|
|
)
|
|
def test_list_scorers_empty_experiment(
|
|
runner: CliRunner, experiment: str, output_format: str, expected_output: Any
|
|
):
|
|
args = ["list", "--experiment-id", experiment]
|
|
if output_format == "json":
|
|
args.extend(["--output", "json"])
|
|
|
|
result = runner.invoke(commands, args)
|
|
assert result.exit_code == 0
|
|
|
|
if output_format == "json":
|
|
output_json = json.loads(result.output)
|
|
assert output_json == expected_output
|
|
else:
|
|
# Empty table produces minimal output
|
|
assert result.output.strip() == expected_output
|
|
|
|
|
|
def test_list_scorers_with_experiment_id_env_var(
|
|
runner: CliRunner, experiment: str, correctness_scorer: Any, mock_databricks_environment: Any
|
|
):
|
|
correctness_scorer.register(experiment_id=experiment, name="Correctness")
|
|
|
|
result = runner.invoke(commands, ["list"], env={"MLFLOW_EXPERIMENT_ID": experiment})
|
|
|
|
assert result.exit_code == 0
|
|
assert "Correctness" in result.output
|
|
|
|
|
|
def test_list_scorers_missing_experiment_id(runner: CliRunner):
|
|
result = runner.invoke(commands, ["list"])
|
|
|
|
assert result.exit_code != 0
|
|
assert "experiment-id" in result.output.lower() or "experiment_id" in result.output.lower()
|
|
|
|
|
|
def test_list_scorers_invalid_output_format(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(commands, ["list", "--experiment-id", experiment, "--output", "invalid"])
|
|
|
|
assert result.exit_code != 0
|
|
assert "invalid" in result.output.lower() or "choice" in result.output.lower()
|
|
|
|
|
|
def test_list_scorers_special_characters_in_names(
|
|
runner: CliRunner, experiment: str, generic_scorer: Any, mock_databricks_environment: Any
|
|
):
|
|
generic_scorer.register(experiment_id=experiment, name="Scorer With Spaces")
|
|
generic_scorer.register(experiment_id=experiment, name="Scorer.With.Dots")
|
|
generic_scorer.register(experiment_id=experiment, name="Scorer-With-Dashes")
|
|
generic_scorer.register(experiment_id=experiment, name="Scorer_With_Underscores")
|
|
|
|
result = runner.invoke(commands, ["list", "--experiment-id", experiment])
|
|
|
|
assert result.exit_code == 0
|
|
assert "Scorer With Spaces" in result.output
|
|
assert "Scorer.With.Dots" in result.output
|
|
assert "Scorer-With-Dashes" in result.output
|
|
assert "Scorer_With_Underscores" in result.output
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"output_format",
|
|
["table", "json"],
|
|
)
|
|
def test_list_scorers_single_scorer(
|
|
runner: CliRunner,
|
|
experiment: str,
|
|
generic_scorer: Any,
|
|
output_format: str,
|
|
mock_databricks_environment: Any,
|
|
):
|
|
generic_scorer.register(experiment_id=experiment, name="OnlyScorer")
|
|
|
|
args = ["list", "--experiment-id", experiment]
|
|
if output_format == "json":
|
|
args.extend(["--output", "json"])
|
|
|
|
result = runner.invoke(commands, args)
|
|
assert result.exit_code == 0
|
|
|
|
if output_format == "json":
|
|
output_json = json.loads(result.output)
|
|
assert output_json == {"scorers": [{"name": "OnlyScorer", "description": None}]}
|
|
else:
|
|
assert "OnlyScorer" in result.output
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"output_format",
|
|
["table", "json"],
|
|
)
|
|
def test_list_scorers_long_names(
|
|
runner: CliRunner,
|
|
experiment: str,
|
|
generic_scorer: Any,
|
|
output_format: str,
|
|
mock_databricks_environment: Any,
|
|
):
|
|
long_name = "VeryLongScorerNameThatShouldNotBeTruncatedEvenIfItIsReallyReallyLong"
|
|
generic_scorer.register(experiment_id=experiment, name=long_name)
|
|
|
|
args = ["list", "--experiment-id", experiment]
|
|
if output_format == "json":
|
|
args.extend(["--output", "json"])
|
|
|
|
result = runner.invoke(commands, args)
|
|
assert result.exit_code == 0
|
|
|
|
if output_format == "json":
|
|
output_json = json.loads(result.output)
|
|
assert output_json == {"scorers": [{"name": long_name, "description": None}]}
|
|
else:
|
|
# Full name should be present
|
|
assert long_name in result.output
|
|
|
|
|
|
def test_list_scorers_with_descriptions(runner: CliRunner, experiment: str):
|
|
from mlflow.genai.judges import make_judge
|
|
|
|
judge1 = make_judge(
|
|
name="quality_judge",
|
|
instructions="Evaluate {{ outputs }}",
|
|
description="Evaluates response quality",
|
|
feedback_value_type=str,
|
|
)
|
|
judge1.register(experiment_id=experiment)
|
|
|
|
judge2 = make_judge(
|
|
name="safety_judge",
|
|
instructions="Check {{ outputs }}",
|
|
description="Checks for safety issues",
|
|
feedback_value_type=str,
|
|
)
|
|
judge2.register(experiment_id=experiment)
|
|
|
|
judge3 = make_judge(
|
|
name="no_desc_judge",
|
|
instructions="Evaluate {{ outputs }}",
|
|
feedback_value_type=str,
|
|
)
|
|
judge3.register(experiment_id=experiment)
|
|
|
|
result_json = runner.invoke(
|
|
commands, ["list", "--experiment-id", experiment, "--output", "json"]
|
|
)
|
|
assert result_json.exit_code == 0
|
|
output_json = json.loads(result_json.output)
|
|
|
|
assert len(output_json["scorers"]) == 3
|
|
scorers_by_name = {s["name"]: s for s in output_json["scorers"]}
|
|
|
|
assert scorers_by_name["no_desc_judge"]["description"] is None
|
|
assert scorers_by_name["quality_judge"]["description"] == "Evaluates response quality"
|
|
assert scorers_by_name["safety_judge"]["description"] == "Checks for safety issues"
|
|
|
|
result_table = runner.invoke(commands, ["list", "--experiment-id", experiment])
|
|
assert result_table.exit_code == 0
|
|
assert "Evaluates response quality" in result_table.output
|
|
assert "Checks for safety issues" in result_table.output
|
|
|
|
|
|
def test_create_judge_basic(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"test_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered judge scorer 'test_judge'" in result.output
|
|
assert experiment in result.output
|
|
|
|
# Verify judge was registered
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
scorer_names = [s.name for s in scorers]
|
|
assert "test_judge" in scorer_names
|
|
|
|
|
|
def test_create_judge_with_model(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"custom_model_judge",
|
|
"--instructions",
|
|
"Check {{ inputs }} and {{ outputs }}",
|
|
"--model",
|
|
"openai:/gpt-4",
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered" in result.output
|
|
|
|
# Verify judge was registered with correct model
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
scorer_names = [s.name for s in scorers]
|
|
assert "custom_model_judge" in scorer_names
|
|
|
|
# Get the judge and verify it uses the specified model
|
|
judge = next(s for s in scorers if s.name == "custom_model_judge")
|
|
assert judge.model == "openai:/gpt-4"
|
|
|
|
|
|
def test_create_judge_short_options(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"-n",
|
|
"short_options_judge",
|
|
"-i",
|
|
"Evaluate {{ outputs }}",
|
|
"-x",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered" in result.output
|
|
|
|
# Verify judge was registered
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
scorer_names = [s.name for s in scorers]
|
|
assert "short_options_judge" in scorer_names
|
|
|
|
|
|
def test_create_judge_with_env_var(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"env_var_judge",
|
|
"--instructions",
|
|
"Check {{ outputs }}",
|
|
],
|
|
env={"MLFLOW_EXPERIMENT_ID": experiment},
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered" in result.output
|
|
|
|
# Verify judge was registered
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
scorer_names = [s.name for s in scorers]
|
|
assert "env_var_judge" in scorer_names
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("args", "missing_param"),
|
|
[
|
|
(["--instructions", "test", "--experiment-id", "123"], "name"),
|
|
(["--name", "test", "--experiment-id", "123"], "instructions"),
|
|
(["--name", "test", "--instructions", "test"], "experiment-id"),
|
|
],
|
|
)
|
|
def test_create_judge_missing_required_params(
|
|
runner: CliRunner, args: list[str], missing_param: str
|
|
):
|
|
result = runner.invoke(commands, ["register-llm-judge"] + args)
|
|
|
|
assert result.exit_code != 0
|
|
# Click typically shows "Missing option" for required parameters
|
|
assert "missing" in result.output.lower() or "required" in result.output.lower()
|
|
|
|
|
|
def test_create_judge_invalid_prompt(runner: CliRunner, experiment: str):
|
|
# Should raise MlflowException because make_judge validates that instructions
|
|
# contain at least one variable
|
|
with pytest.raises(MlflowException, match="[Tt]emplate.*variable"):
|
|
runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"invalid_judge",
|
|
"--instructions",
|
|
"This has no template variables",
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
|
|
def test_create_judge_special_characters_in_name(runner: CliRunner, experiment: str):
|
|
# Verify experiment has no judges initially
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
assert len(scorers) == 0
|
|
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"judge-with_special.chars",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered" in result.output
|
|
|
|
# Verify experiment has exactly one judge
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
assert len(scorers) == 1
|
|
assert scorers[0].name == "judge-with_special.chars"
|
|
|
|
|
|
def test_create_judge_duplicate_registration(runner: CliRunner, experiment: str):
|
|
# Create a judge
|
|
result1 = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"duplicate_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
assert result1.exit_code == 0
|
|
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
assert len(scorers) == 1
|
|
assert scorers[0].name == "duplicate_judge"
|
|
|
|
# Register the same judge again with same name - should succeed (replaces the old one)
|
|
result2 = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"duplicate_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
assert result2.exit_code == 0
|
|
|
|
# Verify there is still only one judge (the new one replaced the old one)
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
assert len(scorers) == 1
|
|
assert scorers[0].name == "duplicate_judge"
|
|
|
|
|
|
def test_create_judge_with_description(runner: CliRunner, experiment: str):
|
|
description = "Evaluates response quality and relevance"
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"judge_with_desc",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--description",
|
|
description,
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered" in result.output
|
|
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
assert len(scorers) == 1
|
|
judge = scorers[0]
|
|
assert judge.name == "judge_with_desc"
|
|
assert judge.description == description
|
|
|
|
|
|
def test_create_judge_with_description_short_flag(runner: CliRunner, experiment: str):
|
|
description = "Checks for PII in outputs"
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"-n",
|
|
"pii_judge",
|
|
"-i",
|
|
"Check {{ outputs }}",
|
|
"-d",
|
|
description,
|
|
"-x",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
judge = next(s for s in scorers if s.name == "pii_judge")
|
|
assert judge.description == description
|
|
|
|
|
|
@pytest.mark.parametrize("output_format", ["table", "json"])
|
|
def test_list_builtin_scorers_output_formats(runner, output_format):
|
|
args = ["list", "--builtin"]
|
|
if output_format == "json":
|
|
args.extend(["--output", "json"])
|
|
|
|
result = runner.invoke(commands, args)
|
|
assert result.exit_code == 0
|
|
|
|
if output_format == "json":
|
|
data = json.loads(result.output)
|
|
assert "scorers" in data
|
|
assert isinstance(data["scorers"], list)
|
|
assert len(data["scorers"]) > 0
|
|
|
|
# Verify each scorer has required fields
|
|
for scorer_item in data["scorers"]:
|
|
assert "name" in scorer_item
|
|
assert "description" in scorer_item
|
|
|
|
# Verify some builtin scorer names appear
|
|
scorer_names = [s["name"] for s in data["scorers"]]
|
|
assert "correctness" in scorer_names
|
|
assert "relevance_to_query" in scorer_names
|
|
assert "completeness" in scorer_names
|
|
else:
|
|
# Verify table headers
|
|
assert "Scorer Name" in result.output
|
|
assert "Description" in result.output
|
|
|
|
# Verify some builtin scorer names appear
|
|
assert "correctness" in result.output
|
|
assert "relevance_to_query" in result.output
|
|
assert "completeness" in result.output
|
|
|
|
|
|
def test_list_builtin_scorers_short_flag(runner):
|
|
result = runner.invoke(commands, ["list", "-b"])
|
|
assert result.exit_code == 0
|
|
assert "Scorer Name" in result.output
|
|
|
|
|
|
def test_list_builtin_scorers_shows_all_available_scorers(runner):
|
|
result = runner.invoke(commands, ["list", "--builtin", "--output", "json"])
|
|
assert result.exit_code == 0
|
|
|
|
expected_scorers = get_all_scorers()
|
|
expected_names = {scorer.name for scorer in expected_scorers}
|
|
|
|
data = json.loads(result.output)
|
|
actual_names = {s["name"] for s in data["scorers"]}
|
|
|
|
assert actual_names == expected_names
|
|
|
|
|
|
def test_list_scorers_mutually_exclusive_flags(runner, experiment):
|
|
result = runner.invoke(commands, ["list", "--builtin", "--experiment-id", experiment])
|
|
assert result.exit_code != 0
|
|
assert "Cannot specify both --builtin and --experiment-id" in result.output
|
|
|
|
|
|
def test_list_scorers_requires_one_flag(runner):
|
|
result = runner.invoke(commands, ["list"])
|
|
assert result.exit_code != 0
|
|
assert "Must specify either --builtin or --experiment-id" in result.output
|
|
|
|
|
|
def test_list_scorers_env_var_still_works(runner, experiment, monkeypatch):
|
|
monkeypatch.setenv("MLFLOW_EXPERIMENT_ID", experiment)
|
|
result = runner.invoke(commands, ["list"])
|
|
assert result.exit_code == 0
|
|
|
|
|
|
def test_create_judge_with_base_url(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"proxy_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--model",
|
|
"openai:/gpt-4",
|
|
"--base-url",
|
|
"http://my-proxy:8080/v1",
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered" in result.output
|
|
|
|
# base_url is not persisted, so the registered judge won't have it
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
assert any(s.name == "proxy_judge" for s in scorers)
|
|
|
|
|
|
def test_create_judge_with_extra_headers(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"headers_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--model",
|
|
"openai:/gpt-4",
|
|
"--extra-headers",
|
|
'{"X-Api-Key": "secret", "X-Org": "my-org"}',
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered" in result.output
|
|
|
|
scorers = list_scorers(experiment_id=experiment)
|
|
assert any(s.name == "headers_judge" for s in scorers)
|
|
|
|
|
|
def test_create_judge_with_base_url_and_extra_headers(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"full_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--model",
|
|
"openai:/gpt-4",
|
|
"--base-url",
|
|
"http://proxy:9090",
|
|
"--extra-headers",
|
|
'{"Authorization": "Bearer token"}',
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully created and registered" in result.output
|
|
|
|
|
|
def test_create_judge_invalid_extra_headers_json(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"bad_json_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--extra-headers",
|
|
"not valid json",
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code != 0
|
|
assert "Invalid JSON" in result.output
|
|
|
|
|
|
def test_create_judge_extra_headers_not_dict(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"array_headers_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--extra-headers",
|
|
'["not", "a", "dict"]',
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code != 0
|
|
assert "Expected a JSON object" in result.output
|
|
|
|
|
|
def test_create_judge_extra_headers_non_string_values(runner: CliRunner, experiment: str):
|
|
result = runner.invoke(
|
|
commands,
|
|
[
|
|
"register-llm-judge",
|
|
"--name",
|
|
"non_string_headers_judge",
|
|
"--instructions",
|
|
"Evaluate {{ outputs }}",
|
|
"--extra-headers",
|
|
'{"Authorization": 123}',
|
|
"--experiment-id",
|
|
experiment,
|
|
],
|
|
)
|
|
|
|
assert result.exit_code != 0
|
|
assert "must all be strings" in result.output
|