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>
This commit is contained in:
1 parent
e185bb7c88
commit
0c2021fced
8 files changed
+462
-2
No files matched your search
@@ -0,0 +1,46 @@
|
||||
"""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()
|
||||
Reference in new issue
Block a user