diff options
Diffstat (limited to 'test_llamachat.py')
| -rwxr-xr-x | test_llamachat.py | 122 |
1 files changed, 97 insertions, 25 deletions
diff --git a/test_llamachat.py b/test_llamachat.py index 0251c78..1217176 100755 --- a/test_llamachat.py +++ b/test_llamachat.py @@ -14,6 +14,7 @@ # GNU General Public License for more details. """Self-checks for the non-GUI logic. Run: ./test_llamachat.py""" +import contextlib import json import os import subprocess @@ -1849,10 +1850,7 @@ def test_client_auth_header(): return _HttpxResponse({"data": [{"id": "m1"}]}) import httpx - original = httpx.get - try: - httpx.get = _recorder - + with _patched(httpx, "get", _recorder): assert backend.Client("http://x.example.org").models() == ["m1"] assert "Authorization" not in sent["headers"] @@ -1860,8 +1858,6 @@ def test_client_auth_header(): "http://x.example.org", api_key="sk-test-not-a-real-key" ).models() assert sent["headers"]["Authorization"] == "Bearer sk-test-not-a-real-key" - finally: - httpx.get = original # The streaming path is the one that carries every real turn, and no # other test reaches it: the search tests all stub _stream_once out. @@ -1881,10 +1877,7 @@ def test_client_auth_header(): sent["headers"] = headers or {} return _Stream() - original_stream = httpx.stream - try: - httpx.stream = _stream_recorder - + with _patched(httpx, "stream", _stream_recorder): client = backend.Client("http://x.example.org") assert list(client._stream_once("m", [], tools=None)) == [("content", "hi")] assert "Authorization" not in sent["headers"] @@ -1894,14 +1887,91 @@ def test_client_auth_header(): ) list(keyed._stream_once("m", [], tools=None)) assert sent["headers"]["Authorization"] == "Bearer sk-test-not-a-real-key" - finally: - httpx.stream = original_stream # The key is not on the client's repr, which reaches logs and tracebacks. assert "sk-test-not-a-real-key" not in repr(keyed) print("ok client authorization header") +def test_multi_client(): + """Models fan out across providers; requests route by model id.""" + from llamachat import providers + + table = providers.parse( + { + "providers": { + "local": {"base_url": "http://localhost:8181"}, + "together": { + "base_url": "https://api.example.org", + "api_key": "env:MULTI_TEST_KEY", + "filter": ["qwen"], + }, + "down": {"base_url": "https://dead.example.org"}, + } + } + ) + os.environ["MULTI_TEST_KEY"] = "sk-test-not-a-real-key" + + listings = { + "http://localhost:8181": ["gemma4", "qwen3.5-9b"], + "https://api.example.org": [ + "Qwen/Qwen2.5-72B", + "meta-llama/Llama-3.3-70B", + ], + } + built = [] + + class _StubClient: + def __init__(self, base_url, timeout=300, api_key=""): + self.base_url = base_url + self.api_key = api_key + built.append(self) + + def models(self, list_timeout=30): + if self.base_url not in listings: + raise backend.BackendError(f"cannot reach {self.base_url}") + return listings[self.base_url] + + multi = backend.MultiClient( + table, timeout=300, resolver=providers.KeyResolver(), + client_factory=_StubClient, + ) + listed, problems = multi.models() + + # Local models stay bare, cloud ones are prefixed, and the filter cut + # the Llama model out of together's listing. + assert listed == ["gemma4", "qwen3.5-9b", "together:Qwen/Qwen2.5-72B"] + + # The unreachable provider is reported, and did not break the rest. + assert any("down" in p for p in problems) + + # Routing: the client for a cloud model carries that provider's key. + client = multi.client_for("together:Qwen/Qwen2.5-72B") + assert client.base_url == "https://api.example.org" + assert client.api_key == "sk-test-not-a-real-key" + + # And a local model gets the local client with no key at all. + local = multi.client_for("gemma4") + assert local.base_url == "http://localhost:8181" + assert local.api_key == "" + + # The bare model name is what goes on the wire, not the prefixed id. + assert multi.wire_name("together:Qwen/Qwen2.5-72B") == "Qwen/Qwen2.5-72B" + assert multi.wire_name("gemma4") == "gemma4" + + # A filter that matches nothing is reported by name with counts. + table["together"].filter = ["zzz"] + empty = backend.MultiClient( + table, timeout=300, resolver=providers.KeyResolver(), + client_factory=_StubClient, + ) + _, notes = empty.models() + assert any("together: 0 of 2" in n for n in notes) + + del os.environ["MULTI_TEST_KEY"] + print("ok multi-provider client") + + class _FakeResponse: """Enough of an http.client response for urlopen's context manager.""" @@ -1940,6 +2010,17 @@ def _with_urlopen(body, capture=None): return original +@contextlib.contextmanager +def _patched(obj, name, value): + """Swap an attribute for the duration of the block, restored even on error.""" + original = getattr(obj, name) + setattr(obj, name, value) + try: + yield + finally: + setattr(obj, name, original) + + def test_search_tool_schema(): from llamachat import search @@ -2545,13 +2626,9 @@ def test_title_request(): import httpx - original = httpx.post - httpx.post = fake_post - try: + with _patched(httpx, "post", fake_post): client = backend.Client("http://router:8181") out = client.complete("m", [{"role": "user", "content": "hi"}], max_tokens=32) - finally: - httpx.post = original assert out == " A Title ", out assert seen["url"] == "http://router:8181/v1/chat/completions" @@ -2567,12 +2644,9 @@ def test_title_request(): # A keyed provider authenticates on this path too, not only on models(). # It runs after the body assertions above because it overwrites `seen`. - httpx.post = fake_post - try: + with _patched(httpx, "post", fake_post): keyed = backend.Client("http://router:8181", api_key="sk-test-not-a-real-key") assert keyed.complete("m", []) == " A Title " - finally: - httpx.post = original assert seen["headers"]["Authorization"] == "Bearer sk-test-not-a-real-key" # A reply with no choices is empty rather than an exception. @@ -2580,11 +2654,8 @@ def test_title_request(): def json(self): return {} - httpx.post = lambda url, json=None, timeout=None, headers=None: Empty() - try: + with _patched(httpx, "post", lambda url, json=None, timeout=None, headers=None: Empty()): assert backend.Client("http://x").complete("m", []) == "" - finally: - httpx.post = original print("ok title request") @@ -2662,6 +2733,7 @@ if __name__ == "__main__": test_metadata_and_cost() test_token_column_migration() test_client_auth_header() + test_multi_client() test_search_tool_schema() test_search_results_sanitising() test_tool_call_accumulation() |
