Files
ECC/tests/test_model_config.py
T

120 lines
4.9 KiB
Python

from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Literal
import pytest
from llm.cli.selector import save_config
from llm.core.interface import LLMProvider
from llm.core.types import ProviderType
from llm.providers.astraflow import AstraflowCNProvider, AstraflowProvider
from llm.providers.atlas import AtlasProvider
from llm.providers.ollama import OllamaProvider
from llm.providers.resolver import get_provider
pytestmark = pytest.mark.unit
@dataclass(frozen=True)
class ProviderCase:
provider_type: ProviderType
constructor: Callable[..., LLMProvider]
model_env: str
builtin_model: str
PROVIDERS = (
ProviderCase(ProviderType.OLLAMA, OllamaProvider, "OLLAMA_MODEL", "llama3.2"),
ProviderCase(ProviderType.ATLAS, AtlasProvider, "ATLAS_MODEL", "deepseek-ai/deepseek-v4-pro"),
ProviderCase(ProviderType.ASTRAFLOW, AstraflowProvider, "ASTRAFLOW_MODEL", "gpt-4o-mini"),
ProviderCase(ProviderType.ASTRAFLOW_CN, AstraflowCNProvider, "ASTRAFLOW_CN_MODEL", "gpt-4o-mini"),
)
PROVIDER_IDS = tuple(case.provider_type.value for case in PROVIDERS)
BLANK_MODELS = (" \t ", ' " \t " ')
@pytest.fixture(autouse=True)
def isolated_model_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
monkeypatch.chdir(tmp_path)
for name in (
"LLM_PROVIDER", "LLM_MODEL", "OLLAMA_MODEL", "ATLAS_MODEL",
"ASTRAFLOW_MODEL", "ASTRAFLOW_CN_MODEL", "OLLAMA_BASE_URL",
"ATLAS_BASE_URL", "ASTRAFLOW_BASE_URL", "ASTRAFLOW_CN_BASE_URL",
):
# Record even absent variables: save_config(persist=True) writes them directly.
monkeypatch.setenv(name, "")
monkeypatch.delenv(name)
for name in ("ATLAS_API_KEY", "ASTRAFLOW_API_KEY", "ASTRAFLOW_CN_API_KEY"):
monkeypatch.setenv(name, "test-key")
def set_provider_model(
monkeypatch: pytest.MonkeyPatch, case: ProviderCase, value: str
) -> None:
monkeypatch.setenv(case.model_env, value)
if case.provider_type == ProviderType.ASTRAFLOW_CN:
monkeypatch.setenv("ASTRAFLOW_MODEL", value)
@pytest.mark.parametrize("case", PROVIDERS, ids=PROVIDER_IDS)
@pytest.mark.parametrize("blank_model", BLANK_MODELS, ids=("whitespace", "quoted-whitespace"))
@pytest.mark.parametrize("source", ("environment", "saved"))
def test_blank_provider_model_falls_back_to_selected_model(
monkeypatch: pytest.MonkeyPatch,
case: ProviderCase,
blank_model: str,
source: Literal["environment", "saved"],
) -> None:
save_config(case.provider_type.value, "Selected/MixedCase-model", persist=source == "environment")
set_provider_model(monkeypatch, case, blank_model)
assert get_provider().get_default_model() == "Selected/MixedCase-model"
@pytest.mark.parametrize("case", PROVIDERS, ids=PROVIDER_IDS)
@pytest.mark.parametrize("blank_model", BLANK_MODELS, ids=("whitespace", "quoted-whitespace"))
def test_blank_provider_model_without_selection_retains_builtin_default(
monkeypatch: pytest.MonkeyPatch, case: ProviderCase, blank_model: str
) -> None:
set_provider_model(monkeypatch, case, blank_model)
assert get_provider(case.provider_type).get_default_model() == case.builtin_model
assert case.constructor().get_default_model() == case.builtin_model
@pytest.mark.parametrize("case", PROVIDERS, ids=PROVIDER_IDS)
@pytest.mark.parametrize("model", (' "Provider/MixedCase-model" ', " 'Provider/MixedCase-model' "))
def test_quoted_provider_model_is_normalized_without_changing_case(
monkeypatch: pytest.MonkeyPatch, case: ProviderCase, model: str
) -> None:
monkeypatch.setenv("LLM_MODEL", "selected-model")
monkeypatch.setenv(case.model_env, model)
assert get_provider(case.provider_type).get_default_model() == "Provider/MixedCase-model"
assert case.constructor().get_default_model() == "Provider/MixedCase-model"
@pytest.mark.parametrize("case", PROVIDERS, ids=PROVIDER_IDS)
def test_explicit_default_model_keeps_precedence_and_original_value(
monkeypatch: pytest.MonkeyPatch, case: ProviderCase
) -> None:
save_config(case.provider_type.value, "selected-model", persist=True)
set_provider_model(monkeypatch, case, ' "Provider/MixedCase-model" ')
explicit_model = ' "Explicit/MixedCase-model" '
assert get_provider(default_model=explicit_model).get_default_model() == explicit_model
assert case.constructor(default_model=explicit_model).get_default_model() == explicit_model
@pytest.mark.parametrize("blank_model", BLANK_MODELS, ids=("whitespace", "quoted-whitespace"))
def test_cn_blank_specific_model_uses_normalized_shared_model(
monkeypatch: pytest.MonkeyPatch, blank_model: str
) -> None:
monkeypatch.setenv("ASTRAFLOW_CN_MODEL", blank_model)
monkeypatch.setenv("ASTRAFLOW_MODEL", ' "Shared/MixedCase-model" ')
monkeypatch.setenv("LLM_MODEL", "selected-model")
assert get_provider(ProviderType.ASTRAFLOW_CN).get_default_model() == "Shared/MixedCase-model"
assert AstraflowCNProvider().get_default_model() == "Shared/MixedCase-model"