Files
nisse/tests/integration/test_llm_live.py
T
dongarandClaude Fable 5 0c2021fced
CI / checks (pull_request) Successful in 10s
CI / checks (push) Successful in 9s
Add LLM client interface with LiteLLM adapter (A5/A11)
PR 2 of the steps-3/4 plan.

- llm.py: LLMClient protocol — complete(events, tools, model) ->
  canonical events — present from the first real LLM call.
  LiteLLMClient renders requests via the A10 renderer, targets the
  proxy's OpenAI-compatible route, and maps provider tool-call IDs
  to locally-minted canonical IDs at the boundary
- RoleMap (A11): executor/planner/delegate/consultant resolved from
  env; pinned() returns the mapping to record at task start
- Config strictly from env: NISSE_LLM_BASE_URL, NISSE_LLM_TOKEN;
  envfile.py is a minimal .env loader where real env wins; .env now
  gitignored with a committed .env.example documenting all variables
  (Matrix ones included for the upcoming transport PR)
- Live smoke test against the proxy is integration-marked and skips
  without credentials — CI stays credential-free per operator
  decision; the test runs locally once .env exists
- httpx added as nisse-dev's HTTP client

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 20:57:51 +00:00

47 lines
1.4 KiB
Python

"""Live smoke test against the real LiteLLM proxy.
Skips unless NISSE_LLM_BASE_URL, NISSE_LLM_TOKEN, and NISSE_ROLE_EXECUTOR are
present (from the environment or the repo-root .env) — CI stays credential-free
by choice; this runs from the operator's machine.
"""
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")
REQUIRED = ("NISSE_LLM_BASE_URL", "NISSE_LLM_TOKEN", "NISSE_ROLE_EXECUTOR")
pytestmark = [
pytest.mark.integration,
pytest.mark.skipif(
not all(os.environ.get(var) for var in REQUIRED),
reason=f"live LLM config absent; set {', '.join(REQUIRED)}",
),
]
def test_executor_model_completes_via_litellm() -> None:
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()