"""Live smoke test against the real LiteLLM proxy. NISSE_LLM_BASE_URL alone decides whether this runs: if it is set, the live path is expected to work, and anything else missing is a *failure*, not a skip. A half-configured environment must not pass quietly as a skip — that is how a broken CI credential hides. """ import os from pathlib import Path import pytest from nisse_dev.canonical import AssistantText, StoredEvent, UserMessage from nisse_dev.envfile import load_env_file from nisse_dev.llm import LiteLLMClient load_env_file(Path(__file__).resolve().parents[2] / ".env") SENTINEL = "NISSE_LLM_BASE_URL" ALSO_REQUIRED = ("NISSE_LLM_TOKEN", "NISSE_ROLE_EXECUTOR") pytestmark = [ pytest.mark.integration, pytest.mark.skipif( not os.environ.get(SENTINEL), reason=f"no live LLM configured ({SENTINEL} unset)", ), ] def test_executor_model_completes_via_litellm() -> None: missing = [var for var in ALSO_REQUIRED if not os.environ.get(var)] assert not missing, ( f"{SENTINEL} is set, so the live LLM path is expected to work, " f"but these are missing: {', '.join(missing)}" ) client = LiteLLMClient.from_env() events = [ StoredEvent( id=1, ts="t", event=UserMessage( text="Reply with exactly one word: pong", source="smoke-test" ), ) ] result = client.complete( events, tools=None, model=os.environ["NISSE_ROLE_EXECUTOR"] ) texts = [e.text for e in result if isinstance(e, AssistantText)] assert texts, f"no assistant text in {result!r}" assert "pong" in texts[0].lower()