Pass the capability matrix's models as a pytest option
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 51s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 51s
NISSE_CAPABILITY_MODELS was neither a secret nor configuration: which models to measure is a question for one run, so it is now an argument, --capability-models=a,b, declared in tests/conftest.py where pytest reads options at startup. With no models the matrix skips as before, so CI is unchanged. The last non-secret NISSE_* variable other than NISSE_CONFIG and NISSE_ENV is gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
15d69799bf
commit
58b439088f
5 files changed
+53
-25
No files matched your search
@@ -12,7 +12,3 @@ NISSE_GITEA_TOKEN=your-scoped-service-account-token
|
|||||||
# Where the config file is, if not ./nisse-dev.yaml. Where this file is, if
|
# Where the config file is, if not ./nisse-dev.yaml. Where this file is, if
|
||||||
# not ./.env, is NISSE_ENV, which has to be set in the real environment.
|
# not ./.env, is NISSE_ENV, which has to be set in the real environment.
|
||||||
# NISSE_CONFIG=~/nisse-demo-workspace/nisse-dev.yaml
|
# NISSE_CONFIG=~/nisse-demo-workspace/nisse-dev.yaml
|
||||||
|
|
||||||
# Opt-in for the capability matrix: models to measure, comma separated.
|
|
||||||
# Spends real tokens across every model listed. See docs/Tuning-Guide.md.
|
|
||||||
# NISSE_CAPABILITY_MODELS=local-fast,local-coder
|
|
||||||
@@ -248,8 +248,8 @@ change one thing at a time.
|
|||||||
1. **Model and quantization.** Start with whether a candidate holds the
|
1. **Model and quantization.** Start with whether a candidate holds the
|
||||||
tool-call contract at all:
|
tool-call contract at all:
|
||||||
|
|
||||||
NISSE_CAPABILITY_MODELS=alias-a,alias-b \
|
uv run pytest -m integration tests/integration/test_model_capability.py \
|
||||||
uv run pytest -m integration tests/integration/test_model_capability.py
|
--capability-models=alias-a,alias-b
|
||||||
|
|
||||||
A model that falls back to prose instead of calling tools is disqualified,
|
A model that falls back to prose instead of calling tools is disqualified,
|
||||||
however good its benchmarks look. Before comparing aliases, check what
|
however good its benchmarks look. Before comparing aliases, check what
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""Command-line options for the repository-level test suites.
|
||||||
|
|
||||||
|
Declared here rather than beside the tests that use them: pytest only reads
|
||||||
|
options from conftest files it loads at startup, and this directory is one
|
||||||
|
of the configured test paths.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser: pytest.Parser) -> None:
|
||||||
|
parser.addoption(
|
||||||
|
"--capability-models",
|
||||||
|
default="",
|
||||||
|
help=(
|
||||||
|
"comma-separated LiteLLM aliases to measure with the capability "
|
||||||
|
"matrix (tests/integration/test_model_capability.py). Spends real "
|
||||||
|
"tokens on every model listed; the matrix skips when this is empty."
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -21,8 +21,8 @@ from nisse_dev.llm import AnthropicPassthroughClient, LiteLLMClient
|
|||||||
REPO = Path(__file__).resolve().parents[2]
|
REPO = Path(__file__).resolve().parents[2]
|
||||||
TEST_CONFIG = Path(__file__).parent / "nisse-test.yaml"
|
TEST_CONFIG = Path(__file__).parent / "nisse-test.yaml"
|
||||||
|
|
||||||
# At import, so per-run switches such as NISSE_CAPABILITY_MODELS can be set
|
# The secrets nisse-test.yaml refers to, for local runs. CI sets them as real
|
||||||
# in .env too. Real environment variables win over the file.
|
# environment variables, which win over the file.
|
||||||
load_env_file(REPO / ".env")
|
load_env_file(REPO / ".env")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ What it measures is the thing that actually breaks: whether a model can hold
|
|||||||
the tool-call contract our loops depend on. llama3.2 could not — it returned
|
the tool-call contract our loops depend on. llama3.2 could not — it returned
|
||||||
`{` as an entire plan — and no amount of prompt work fixed it.
|
`{` as an entire plan — and no amount of prompt work fixed it.
|
||||||
|
|
||||||
Opt in with NISSE_CAPABILITY_MODELS, comma separated. Skipped otherwise,
|
Opt in by naming the models to measure, for one run:
|
||||||
because it spends real tokens across several models.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
uv run pytest -m integration tests/integration/test_model_capability.py \
|
||||||
|
--capability-models=local-fast,local-coder
|
||||||
|
|
||||||
|
Skipped otherwise, because it spends real tokens across every model listed.
|
||||||
|
Which models to measure is a question for a particular run, so it is an
|
||||||
|
argument, not configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -31,15 +35,28 @@ from nisse_dev.llm import AnthropicPassthroughClient, LiteLLMClient, LLMError
|
|||||||
from nisse_dev.planner import PLANNER_TOOLS
|
from nisse_dev.planner import PLANNER_TOOLS
|
||||||
from nisse_dev.tools import EXECUTOR_TOOLS
|
from nisse_dev.tools import EXECUTOR_TOOLS
|
||||||
|
|
||||||
SENTINEL = "NISSE_CAPABILITY_MODELS"
|
pytestmark = pytest.mark.integration
|
||||||
MODELS = [m.strip() for m in os.environ.get(SENTINEL, "").split(",") if m.strip()]
|
|
||||||
|
|
||||||
pytestmark = [
|
|
||||||
pytest.mark.integration,
|
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
|
||||||
pytest.mark.skipif(
|
if "model" not in metafunc.fixturenames:
|
||||||
not MODELS, reason=f"set {SENTINEL} to a comma-separated model list"
|
return
|
||||||
),
|
raw = metafunc.config.getoption("--capability-models")
|
||||||
]
|
models = [m.strip() for m in raw.split(",") if m.strip()]
|
||||||
|
if models:
|
||||||
|
metafunc.parametrize("model", models)
|
||||||
|
else:
|
||||||
|
metafunc.parametrize(
|
||||||
|
"model",
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
None,
|
||||||
|
marks=pytest.mark.skip(
|
||||||
|
reason="no models to measure; pass --capability-models"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -60,7 +77,6 @@ def user(text: str) -> list[StoredEvent]:
|
|||||||
return [StoredEvent(id=1, ts="t", event=UserMessage(text=text, source="probe"))]
|
return [StoredEvent(id=1, ts="t", event=UserMessage(text=text, source="probe"))]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("model", MODELS)
|
|
||||||
def test_model_answers_at_all(model: str, catalog: ModelCatalog, routes) -> None:
|
def test_model_answers_at_all(model: str, catalog: ModelCatalog, routes) -> None:
|
||||||
normalized, passthrough = routes
|
normalized, passthrough = routes
|
||||||
client = select_client(model, normalized, passthrough, catalog)
|
client = select_client(model, normalized, passthrough, catalog)
|
||||||
@@ -71,7 +87,6 @@ def test_model_answers_at_all(model: str, catalog: ModelCatalog, routes) -> None
|
|||||||
assert texts, f"{model} produced no text: {produced!r}"
|
assert texts, f"{model} produced no text: {produced!r}"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("model", MODELS)
|
|
||||||
def test_model_holds_the_planner_contract(
|
def test_model_holds_the_planner_contract(
|
||||||
model: str, catalog: ModelCatalog, routes
|
model: str, catalog: ModelCatalog, routes
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -100,7 +115,6 @@ def test_model_holds_the_planner_contract(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("model", MODELS)
|
|
||||||
def test_model_round_trips_a_tool_result(
|
def test_model_round_trips_a_tool_result(
|
||||||
model: str, catalog: ModelCatalog, routes
|
model: str, catalog: ModelCatalog, routes
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -130,7 +144,6 @@ def test_model_round_trips_a_tool_result(
|
|||||||
assert follow_up, f"{model} produced nothing after its tool result"
|
assert follow_up, f"{model} produced nothing after its tool result"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("model", MODELS)
|
|
||||||
def test_model_holds_the_prompt_level_json_contract(
|
def test_model_holds_the_prompt_level_json_contract(
|
||||||
model: str, catalog: ModelCatalog, routes
|
model: str, catalog: ModelCatalog, routes
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -154,7 +167,6 @@ def test_model_holds_the_prompt_level_json_contract(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("model", MODELS)
|
|
||||||
def test_route_selection_matches_the_model(
|
def test_route_selection_matches_the_model(
|
||||||
model: str, catalog: ModelCatalog, routes
|
model: str, catalog: ModelCatalog, routes
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
Reference in new issue
Block a user