OpenAI-compatible path names an output ceiling instead of trusting server defaults

max_tokens defaults to 32k (compat servers were capping at ~2k, truncating file
writes); a below-limit model 400 drops the param and retries on the server default.
This commit is contained in:
Rohit C Prasad
2026-08-15 13:47:34 -07:00
committed by Rohit P
parent a591f5b35c
commit 44e0e8566f
2 changed files with 66 additions and 4 deletions
+22 -4
View File
@@ -82,6 +82,12 @@ def _strip_foreign_sidecars(messages: list[dict[str, Any]]) -> list[dict[str, An
_MAX_TOKENS_ERROR = "'max_tokens' is not supported" _MAX_TOKENS_ERROR = "'max_tokens' is not supported"
# Ceiling, not a spend target — same rationale as the Anthropic provider's default: a
# coworker writing a report ships the whole file inside one tool call's arguments, and
# compat servers left to their OWN defaults cap completions absurdly low (observed
# 2026-08-15: Together defaulted Kimi K3 to ~2k tokens — every ~5KB write truncated).
DEFAULT_MAX_TOKENS = 32000
def _param_fix_retry(kwargs: dict[str, Any], exc: Exception) -> dict[str, Any]: def _param_fix_retry(kwargs: dict[str, Any], exc: Exception) -> dict[str, Any]:
"""Kwargs for the one retry an unsupported-parameter error earns, or re-raise. """Kwargs for the one retry an unsupported-parameter error earns, or re-raise.
@@ -103,6 +109,14 @@ def _param_fix_retry(kwargs: dict[str, Any], exc: Exception) -> dict[str, Any]:
fixed = dict(kwargs) fixed = dict(kwargs)
fixed.pop("stream_options") fixed.pop("stream_options")
return fixed return fixed
if ("max_tokens" in msg or "max_new_tokens" in msg) and "max_tokens" in kwargs:
# Our 32k default exceeded this model's completion limit (each server words the
# 400 differently, so no number parsing) — drop the param and retry on the
# server's own default rather than surfacing the 400. Worst case is exactly
# yesterday's behavior; best case the server allows far more once asked.
fixed = dict(kwargs)
fixed.pop("max_tokens")
return fixed
raise exc raise exc
@@ -179,11 +193,13 @@ class OpenAIProvider(ProviderClient):
} }
if tools: if tools:
kwargs["tools"] = tools kwargs["tools"] = tools
kwargs.setdefault("max_tokens", DEFAULT_MAX_TOKENS)
_pin_reasoning_effort(kwargs) _pin_reasoning_effort(kwargs)
client = self._ensure_client() client = self._ensure_client()
# Up to two param-fix retries: effort and max_tokens can BOTH need fixing. # Up to three param-fix retries: effort, the max_tokens rename, and the
for _ in range(2): # max_tokens over-limit drop can ALL need fixing on one call.
for _ in range(3):
try: try:
response = client.chat.completions.create(**kwargs) response = client.chat.completions.create(**kwargs)
break break
@@ -227,6 +243,7 @@ class OpenAIProvider(ProviderClient):
} }
if tools: if tools:
kwargs["tools"] = tools kwargs["tools"] = tools
kwargs.setdefault("max_tokens", DEFAULT_MAX_TOKENS)
_pin_reasoning_effort(kwargs) _pin_reasoning_effort(kwargs)
client = self._ensure_client() client = self._ensure_client()
@@ -236,8 +253,9 @@ class OpenAIProvider(ProviderClient):
finish_reason = None finish_reason = None
usage: Optional[TokenUsage] = None usage: Optional[TokenUsage] = None
# Up to two param-fix retries: effort and max_tokens can BOTH need fixing. # Up to three param-fix retries: effort, the max_tokens rename, and the
for _ in range(2): # max_tokens over-limit drop can ALL need fixing on one call.
for _ in range(3):
try: try:
chunks = client.chat.completions.create(**kwargs) chunks = client.chat.completions.create(**kwargs)
break break
+44
View File
@@ -456,3 +456,47 @@ def test_complete_picks_up_reasoning_content():
provider = OpenAIProvider(client=_FakeClient(SimpleNamespace(choices=[choice]))) provider = OpenAIProvider(client=_FakeClient(SimpleNamespace(choices=[choice])))
turn = provider.complete(model="deepseek-v4-pro", messages=[{"role": "user", "content": "x"}]) turn = provider.complete(model="deepseek-v4-pro", messages=[{"role": "user", "content": "x"}])
assert turn.text == "Answer" and turn.reasoning == "deep thought" assert turn.text == "Answer" and turn.reasoning == "deep thought"
def test_default_max_tokens_injected_and_caller_setting_wins():
"""Compat servers left to their OWN defaults cap completions absurdly low
(owner-hit 2026-08-15: Together defaulted Kimi K3 to ~2k tokens, so every report
write truncated mid-arguments). The request always names a ceiling now."""
from coworker.providers.openai_provider import DEFAULT_MAX_TOKENS
client = _FakeClient(_response(content="ok"))
provider = OpenAIProvider(client=client)
provider.complete(model="kimi-k3", messages=[])
assert client.chat.completions.calls[0]["max_tokens"] == DEFAULT_MAX_TOKENS
client2 = _FakeClient(_response(content="ok"))
provider2 = OpenAIProvider(client=client2)
provider2.complete(model="kimi-k3", messages=[], max_tokens=512)
assert client2.chat.completions.calls[0]["max_tokens"] == 512
def test_over_limit_max_tokens_is_dropped_and_retried():
"""A model whose completion limit sits below our default must not surface the 400:
drop the param, retry on the server's own default (yesterday's behavior, at worst)."""
class _LimitRejecting:
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
if "max_tokens" in kwargs:
raise RuntimeError(
"Error code: 400 - max_tokens must be at most 8193 for this model"
)
return self._response
client = _FakeClient(_response(content="ok"))
client.chat.completions = _LimitRejecting(_response(content="ok"))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="tiny-model", messages=[])
calls = client.chat.completions.calls
assert turn.text == "ok" and len(calls) == 2
assert "max_tokens" not in calls[1]