Files
vectorize-io__hindsight/hindsight-dev/tests/test_llm_role_config.py
Nicolò Boschi 20bd4f3618 fix(ci): compare OpenAPI against the merge-base; build benchmark role configs whole (#3349)
* fix(ci): compare OpenAPI against the merge-base; build benchmark role configs whole

Two unrelated CI failures, both of which fail without anything being wrong
with the code under test.

OpenAPI compatibility diffed the branch's spec against the LIVE tip of the
base branch, so every endpoint main gained after a branch was cut is
reported as "Endpoint removed (breaks old clients)" by that branch. Three
open PRs failed this way today on /health/live and /health/ready (added by
#3329), none of which touch the spec at all; the only cure was an unrelated
rebase. Compare against `git merge-base origin/$BASE_BRANCH HEAD` instead,
which asks the question the check means to ask: did *this branch* remove
something. Genuine removals still fail — verified both directions against
the real specs.

The scheduled LoComo benchmark has failed every night since at least Aug 8,
before its first question: `LoComoAnswerGenerator()` raised
"HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID is required". The workflow does export
that variable — but each benchmark role built its LLMConfig from exactly four
env vars (provider, api_key, base_url, model), and LLMConfig deliberately does
not read the environment for provider-specific settings (from_env is where the
API resolves them). Every Vertex AI value was therefore dropped. The same
four-var construction was copy-pasted at three sites — locomo, longmemeval and
the shared judge — so fixing only the crashing one would have moved the failure
down a line. Replace all three with a shared builder that carries the Vertex AI
project/region/service-account through.

hindsight-dev/tests had no CI job, which is why a plain construction bug was
left for a nightly benchmark to find hours later. Add one, so those tests
(and the new regression test) actually run on PRs.

* fix(ci): make the benchmark role-config tests hermetic

They passed locally off the developer's HINDSIGHT_API_LLM_API_KEY and failed
in the new test-dev job, where no key exists, with "API key is required for
openai" — the tests were reading ambient environment instead of declaring
what they need. Clear every HINDSIGHT_API_*LLM* var before each test and set
the ones under test explicitly.
2026-08-10 17:11:06 +02:00

75 lines
3.1 KiB
Python

"""Unit tests for benchmark role LLM config (no network, no credentials)."""
import os
import pytest
from benchmarks.common.llm_role_config import ANSWER_ROLE, JUDGE_ROLE, build_role_llm_config
@pytest.fixture(autouse=True)
def clean_llm_env(monkeypatch: pytest.MonkeyPatch) -> None:
"""Start every test from an empty LLM environment.
Otherwise these tests read whatever the developer's .env or shell happens to
export: they passed locally off a real HINDSIGHT_API_LLM_API_KEY and then
failed in CI, where no key exists, with "API key is required for openai".
Each test now declares the variables it depends on.
"""
for name in list(os.environ):
if name.startswith("HINDSIGHT_API_") and "LLM" in name:
monkeypatch.delenv(name, raising=False)
def test_role_vars_win_over_shared_vars(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("HINDSIGHT_API_LLM_PROVIDER", "openai")
monkeypatch.setenv("HINDSIGHT_API_LLM_API_KEY", "test-key")
monkeypatch.setenv("HINDSIGHT_API_LLM_MODEL", "shared-model")
monkeypatch.setenv("HINDSIGHT_API_ANSWER_LLM_MODEL", "answer-model")
config = build_role_llm_config(ANSWER_ROLE)
assert config.provider == "openai"
assert config.model == "answer-model"
def test_falls_back_to_shared_vars(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("HINDSIGHT_API_LLM_PROVIDER", "openai")
monkeypatch.setenv("HINDSIGHT_API_LLM_API_KEY", "test-key")
monkeypatch.setenv("HINDSIGHT_API_LLM_MODEL", "shared-model")
config = build_role_llm_config(JUDGE_ROLE)
assert config.model == "shared-model"
@pytest.mark.parametrize("role", [ANSWER_ROLE, JUDGE_ROLE])
def test_vertexai_project_id_reaches_the_config(role: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Regression: the scheduled LoComo run died before its first question.
Each benchmark role built its LLMConfig from provider/api_key/base_url/model
alone. LLMConfig does not read the environment for provider-specific
settings, so every Vertex AI value was dropped and the constructor raised
"HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID is required" — even though the
workflow exports it. Vertex AI authenticates with a project rather than an
api_key, so this asserts the passthrough, not just that construction works.
"""
monkeypatch.setenv(f"HINDSIGHT_API_{role}_LLM_PROVIDER", "vertexai")
monkeypatch.setenv(f"HINDSIGHT_API_{role}_LLM_MODEL", "google/gemini-2.5-flash")
monkeypatch.setenv("HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID", "test-project")
monkeypatch.setenv("HINDSIGHT_API_LLM_VERTEXAI_REGION", "europe-west4")
config = build_role_llm_config(role)
assert config.provider == "vertexai"
# LLMConfig strips the google/ prefix for the native Vertex SDK.
assert config.model == "gemini-2.5-flash"
def test_vertexai_without_project_id_still_raises(monkeypatch: pytest.MonkeyPatch) -> None:
"""The passthrough must not paper over genuinely missing configuration."""
monkeypatch.setenv("HINDSIGHT_API_ANSWER_LLM_PROVIDER", "vertexai")
with pytest.raises(ValueError, match="VERTEXAI_PROJECT_ID"):
build_role_llm_config(ANSWER_ROLE)