aboutsummaryrefslogtreecommitdiffstats
path: root/test_llamachat.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-11 11:22:32 +0200
committerDanilo M. <danix@danix.xyz>2026-08-11 11:22:32 +0200
commit2cae7f19ac90e6df9d4008e6e364bae30e8b389a (patch)
tree045eae84caefd1ebd97836fe0a5b97ae181022a2 /test_llamachat.py
parent3f984ccb8ec25ef3b06109d638eed4022db82d45 (diff)
downloadllamachat-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-xtest_llamachat.py74
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()