diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-11 11:22:32 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-11 11:22:32 +0200 |
| commit | 2cae7f19ac90e6df9d4008e6e364bae30e8b389a (patch) | |
| tree | 045eae84caefd1ebd97836fe0a5b97ae181022a2 /test_llamachat.py | |
| parent | 3f984ccb8ec25ef3b06109d638eed4022db82d45 (diff) | |
| download | llamachat-2cae7f19ac90e6df9d4008e6e364bae30e8b389a.tar.gz llamachat-2cae7f19ac90e6df9d4008e6e364bae30e8b389a.zip | |
fix: prevent cloud reasoning models from exhausting default max_tokens
- Send max_tokens = 32768 for non-local providers so reasoning models have room for both chain-of-thought and answer.
- Add per-provider thinking_budget option for endpoints (currently SiliconFlow) that cap reasoning tokens separately.
- Accept bare JSON arrays from /v1/models; some OpenAI-compatible endpoints omit the {"data": [...]} envelope.
Diffstat (limited to 'test_llamachat.py')
| -rwxr-xr-x | test_llamachat.py | 74 |
1 files changed, 68 insertions, 6 deletions
diff --git a/test_llamachat.py b/test_llamachat.py index 98776b8..3f1f678 100755 --- a/test_llamachat.py +++ b/test_llamachat.py @@ -923,11 +923,12 @@ def test_provider_parsing(): "together": { "base_url": "https://api.example.org", "api_key": "env:TEST_KEY_NAME", - "filter": ["qwen", "deepseek"], - "ctx_size": 32768, - "price_in": 0.6, - "price_out": 0.9, - }, + "filter": ["qwen", "deepseek"], + "ctx_size": 32768, + "price_in": 0.6, + "price_out": 0.9, + "thinking_budget": 8192, + }, } } ) @@ -939,6 +940,7 @@ def test_provider_parsing(): assert parsed["together"].ctx_size == 32768 assert parsed["together"].price_in == 0.6 assert parsed["together"].price_out == 0.9 + assert parsed["together"].thinking_budget == 8192 # An old config: bare base_url, no providers table at all. legacy = providers.parse({"base_url": "http://localhost:8181"}) @@ -1395,12 +1397,14 @@ def test_config_providers(): "ctx_size = 32768\n" "price_in = 0.6\n" "price_out = 0.9\n" + "thinking_budget = 8192\n" ) cfg = config.load(path) assert set(cfg.providers) == {"local", "together"} assert cfg.providers["together"].api_key == "pass:api/together" assert cfg.providers["together"].filter == ["qwen"] assert cfg.providers["together"].price_out == 0.9 + assert cfg.providers["together"].thinking_budget == 8192 # A config with no base_url and no providers still loads, with the # built-in default synthesizing local. @@ -1863,6 +1867,13 @@ def test_client_auth_header(): ).models() assert sent["headers"]["Authorization"] == "Bearer sk-test-not-a-real-key" + # A provider that returns a bare array instead of {"data": [...]} + # must still parse: some OpenAI-compatible endpoints skip the envelope. + def _bare_list(url, timeout=None, headers=None): + return _HttpxResponse([{"id": "bare"}]) + with _patched(httpx, "get", _bare_list): + assert backend.Client("http://x.example.org").models() == ["bare"] + # 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. class _Stream: @@ -1897,6 +1908,56 @@ def test_client_auth_header(): print("ok client authorization header") +def test_stream_body_for_cloud(): + """Cloud providers get max_tokens and thinking_budget; local does not.""" + captured = {} + + class _Stream: + status_code = 200 + + def __enter__(self): + return self + + def __exit__(self, *exc): + return False + + def iter_lines(self): + return iter( + ['data: {"choices":[{"delta":{"content":"hi"}}]}'] + ) + + def _recorder(method, url, json=None, timeout=None, headers=None): + captured["json"] = json + return _Stream() + + import httpx + with _patched(httpx, "stream", _recorder): + # Local client: no max_tokens, no thinking_budget. + local = backend.Client("http://localhost:8181", is_local=True) + list(local._stream_once("m", [], tools=None)) + assert "max_tokens" not in captured["json"] + assert "thinking_budget" not in captured["json"] + + # Cloud client with thinking_budget set. + cloud = backend.Client( + "http://api.example.org", + is_local=False, + thinking_budget=8192, + ) + list(cloud._stream_once("m", [], tools=None)) + assert captured["json"]["max_tokens"] == 32768 + assert captured["json"]["thinking_budget"] == 8192 + + # Cloud client without thinking_budget: max_tokens still sent. + cloud_no_budget = backend.Client( + "http://api.example.org", is_local=False + ) + list(cloud_no_budget._stream_once("m", [], tools=None)) + assert captured["json"]["max_tokens"] == 32768 + assert "thinking_budget" not in captured["json"] + print("ok stream body for cloud providers") + + def test_multi_client(): """Models fan out across providers; requests route by model id.""" from llamachat import providers @@ -1926,7 +1987,7 @@ def test_multi_client(): built = [] class _StubClient: - def __init__(self, base_url, timeout=300, api_key=""): + def __init__(self, base_url, timeout=300, api_key="", **kwargs): self.base_url = base_url self.api_key = api_key built.append(self) @@ -2820,6 +2881,7 @@ if __name__ == "__main__": test_metadata_and_cost() test_token_column_migration() test_client_auth_header() + test_stream_body_for_cloud() test_multi_client() test_model_dialog_values() test_cost_label_text() |
