Webber through the boilerroom wrapper: flavor-adaptive tool_choice, session identity at rank 20, and the sanitized-client wiring fix that makes the choke point real. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
123 lines
4.3 KiB
Python
123 lines
4.3 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:
|
|
def __init__(self):
|
|
self.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:
|
|
def __init__(self):
|
|
self.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
|