mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-13 15:50:02 +00:00
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:
@@ -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"
|
||||
|
||||
# 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]:
|
||||
"""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.pop("stream_options")
|
||||
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
|
||||
|
||||
|
||||
@@ -179,11 +193,13 @@ class OpenAIProvider(ProviderClient):
|
||||
}
|
||||
if tools:
|
||||
kwargs["tools"] = tools
|
||||
kwargs.setdefault("max_tokens", DEFAULT_MAX_TOKENS)
|
||||
_pin_reasoning_effort(kwargs)
|
||||
|
||||
client = self._ensure_client()
|
||||
# Up to two param-fix retries: effort and max_tokens can BOTH need fixing.
|
||||
for _ in range(2):
|
||||
# Up to three param-fix retries: effort, the max_tokens rename, and the
|
||||
# max_tokens over-limit drop can ALL need fixing on one call.
|
||||
for _ in range(3):
|
||||
try:
|
||||
response = client.chat.completions.create(**kwargs)
|
||||
break
|
||||
@@ -227,6 +243,7 @@ class OpenAIProvider(ProviderClient):
|
||||
}
|
||||
if tools:
|
||||
kwargs["tools"] = tools
|
||||
kwargs.setdefault("max_tokens", DEFAULT_MAX_TOKENS)
|
||||
_pin_reasoning_effort(kwargs)
|
||||
client = self._ensure_client()
|
||||
|
||||
@@ -236,8 +253,9 @@ class OpenAIProvider(ProviderClient):
|
||||
finish_reason = None
|
||||
usage: Optional[TokenUsage] = None
|
||||
|
||||
# Up to two param-fix retries: effort and max_tokens can BOTH need fixing.
|
||||
for _ in range(2):
|
||||
# Up to three param-fix retries: effort, the max_tokens rename, and the
|
||||
# max_tokens over-limit drop can ALL need fixing on one call.
|
||||
for _ in range(3):
|
||||
try:
|
||||
chunks = client.chat.completions.create(**kwargs)
|
||||
break
|
||||
|
||||
@@ -456,3 +456,47 @@ def test_complete_picks_up_reasoning_content():
|
||||
provider = OpenAIProvider(client=_FakeClient(SimpleNamespace(choices=[choice])))
|
||||
turn = provider.complete(model="deepseek-v4-pro", messages=[{"role": "user", "content": "x"}])
|
||||
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]
|
||||
|
||||
Reference in New Issue
Block a user