Files
webber/webber-api/tests/test_provider_backend.py
T
jpmschweitzerandClaude Fable 5 1769ec2803 feat(backend): adapt every completion to the detected backend (workspace T-137)
One choke point in the sanitized client: the flavor is probed once
(the boilerroom wrapper names itself on /health, a bare llama-server
serves /props, Ollama answers neither) and every completion adapts.
The agents' tool_choice "required" survives only on Ollama — advisory
there, enforced by llama-server, an unbreakable tool loop through the
wrapper. Through the wrapper every completion carries webber's session
identity: session webber, eviction_order 20 in the decided ranking,
configurable via settings. The wrapper's balancing and compaction
signals are read from the response body's extra fields — an httpx
event-hook variant was tried and never fires under the openai SDK.

The enabling fix: the sanitized client was never in the request path.
The provider assigned self._openai_client, an attribute nobody reads —
OllamaProvider.client serves self._client — so every completion has
bypassed the null-content sanitizer since the class was introduced.
Exposed when the wrapper 503'd a session-less request the choke point
should have named; the client now goes through the constructor's
official openai_client parameter, and a wiring test pins
provider.client to the sanitized type. The same bug exists in tatlock
(its T-6, filed).

Verified against the live wrapper from the dev server: flavor
boilerroom detected, a tool-using explore run answered in 4.8 s with
no tool loop, webber resident at rank 20, and the wrapper parked
librarian and tatlock-experts to seat it — the ranking doing exactly
its job. Six new tests (227 green), five mutation-checked: the rank
default, the strip condition, the session-add condition, the
no-cache-on-failure rule, and the client wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-13 10:02:14 +02:00

118 lines
4.2 KiB
Python

"""
Backend adaptation at the provider choke point (workspace T-137).
The flavor globals are set directly so the tests are deterministic
regardless of which backend is reachable; the wire facts the probe
relies on are the wrapper's contract, pinned in its own repo.
"""
import pytest
from src.ollama import provider
from src.shared.config import get_settings
@pytest.fixture(autouse=True)
def reset_flavor(monkeypatch):
"""Each test states its flavor; nothing leaks between them."""
monkeypatch.setattr(provider, "_local_flavor", None)
@pytest.mark.anyio
async def test_ollama_keeps_the_advisory_nudge(monkeypatch):
monkeypatch.setattr(provider, "_local_flavor", "ollama")
kwargs = await provider._adapt_to_backend(
{"extra_body": {"tool_choice": "required"}}
)
assert kwargs["extra_body"] == {"tool_choice": "required"}
@pytest.mark.anyio
async def test_wrapper_strips_the_nudge_and_names_the_session(monkeypatch):
# tool_choice "required" is enforced by llama-server — through the
# wrapper it is the unbreakable tool loop, so it must not pass.
monkeypatch.setattr(provider, "_local_flavor", "boilerroom")
kwargs = await provider._adapt_to_backend(
{"extra_body": {"tool_choice": "required"}}
)
extra = kwargs["extra_body"]
assert "tool_choice" not in extra
assert extra["session"] == "webber"
assert extra["eviction_order"] == 20
@pytest.mark.anyio
async def test_bare_llama_server_strips_without_naming(monkeypatch):
monkeypatch.setattr(provider, "_local_flavor", "llama-server")
kwargs = await provider._adapt_to_backend(
{"extra_body": {"tool_choice": "required"}}
)
assert "extra_body" not in kwargs
@pytest.mark.anyio
async def test_rank_and_name_come_from_settings(monkeypatch):
monkeypatch.setattr(provider, "_local_flavor", "boilerroom")
settings = get_settings()
monkeypatch.setattr(settings, "backend_session_name", "webber-test")
monkeypatch.setattr(settings, "backend_session_rank", 7)
kwargs = await provider._adapt_to_backend({})
assert kwargs["extra_body"] == {"session": "webber-test", "eviction_order": 7}
@pytest.mark.anyio
async def test_other_extension_fields_survive_adaptation(monkeypatch):
monkeypatch.setattr(provider, "_local_flavor", "boilerroom")
kwargs = await provider._adapt_to_backend({"extra_body": {"marker": 1}})
assert kwargs["extra_body"]["marker"] == 1
assert kwargs["extra_body"]["session"] == "webber"
def test_body_signals_are_logged(monkeypatch):
# The httpx event-hook variant never fired under the SDK; the body
# read must demonstrably log, or the signal passes silently again.
calls = []
class StubLogger:
def info(self, msg):
calls.append(("info", msg))
def warning(self, msg):
calls.append(("warning", msg))
monkeypatch.setattr(provider, "logger", StubLogger())
class Busy:
model_extra = {"balancing": [{"parked": "librarian"}], "compaction_due": True}
provider._log_wrapper_signals(Busy())
assert any(kind == "info" and "parked" in msg for kind, msg in calls)
assert any(kind == "warning" for kind, msg in calls)
calls.clear()
class Quiet:
model_extra = {"balancing": [], "compaction_due": False}
provider._log_wrapper_signals(Quiet())
assert calls == []
def test_provider_serves_the_sanitized_client():
# The whole mechanism rides on `.client` returning OUR instance:
# assigning a lookalike attribute after super().__init__ built its
# own client is how the sanitizer silently died until 2026-09-13.
p = provider.WebberOllamaProvider(base_url="http://127.0.0.1:9/v1")
assert isinstance(p.client, provider._SanitizedAsyncOpenAI)
@pytest.mark.anyio
async def test_probe_failure_answers_ollama_without_caching(monkeypatch):
# A backend that is down at first call must be re-probed later —
# caching "ollama" forever would nudge llama-server into the tool
# loop the moment the wrapper came back.
settings = get_settings()
monkeypatch.setattr(settings, "ollama_url", "http://127.0.0.1:9")
flavor = await provider._detect_flavor()
assert flavor == "ollama"
assert provider._local_flavor is None