mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-12 23:29:55 +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"
|
_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
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user