fix(params): send max_completion_tokens for newer OpenAI families on custom endpoints
Third-party OpenAI-compatible endpoints (self-hosted gateways, OpenRouter,
Azure proxies) fronting gpt-4o / gpt-4.1 / gpt-5+ / o1-o4 models silently
received max_tokens and 400'd with unsupported_parameter, because the three
kwarg-selection sites only checked base_url_hostname(...) == "api.openai.com"
and fell through to max_tokens on every other host. The constraint is
enforced server-side by the model family, not by the URL, so name-based
detection is required as a fallback.
Changes:
- utils.py: new shared helper model_forces_max_completion_tokens(model) that
prefix-matches gpt-4o, gpt-4.1, gpt-5, o1, o3, o4 families on normalized
(lowercased, vendor-prefix-stripped) names.
- run_agent.py: _max_tokens_param ORs the helper into the URL check.
- agent/auxiliary_client.py:
- auxiliary_max_tokens_param gains an optional keyword-only model arg.
- _build_call_kwargs inline branch applies the same check for both
provider == "custom" and non-custom paths.
Tests:
- tests/test_model_forces_max_completion_tokens.py: 31 new cases covering
positive families, negatives (classic gpt-4, claude, llama, mistral, qwen,
deepseek), vendor prefixes, case-insensitivity, whitespace, None/empty,
and substring-not-prefix guards.
- tests/run_agent/test_run_agent.py::TestMaxTokensParam: 5 new model-based
cases (custom + gpt-5.4, openrouter + gpt-4o-mini, custom + o1-preview,
classic gpt-4-turbo keeps max_tokens, llama3 keeps max_tokens).
- tests/agent/test_auxiliary_client.py::TestAuxiliaryMaxTokensParam: new
class, 7 tests covering the URL x model matrix.
This commit is contained in:
@@ -355,6 +355,44 @@ def base_url_hostname(base_url: str) -> str:
|
||||
return (parsed.hostname or "").lower().rstrip(".")
|
||||
|
||||
|
||||
# ─── Model Capability Detection ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def model_forces_max_completion_tokens(model: str) -> bool:
|
||||
"""Return True for model families that require ``max_completion_tokens``.
|
||||
|
||||
OpenAI's newer families reject ``max_tokens`` on /v1/chat/completions with
|
||||
HTTP 400 ``unsupported_parameter`` — the caller must send
|
||||
``max_completion_tokens`` instead. This covers:
|
||||
|
||||
- ``gpt-4o`` / ``gpt-4o-mini`` / ``gpt-4o-*``
|
||||
- ``gpt-4.1`` / ``gpt-4.1-*``
|
||||
- ``gpt-5`` / ``gpt-5.x`` / ``gpt-5-*``
|
||||
- ``o1`` / ``o1-*``
|
||||
- ``o3`` / ``o3-*``
|
||||
- ``o4`` / ``o4-*``
|
||||
|
||||
Handles vendor prefixes like ``openai/gpt-5.4`` by stripping to the tail.
|
||||
The URL-based check (``base_url_hostname == "api.openai.com"``) misses
|
||||
third-party OpenAI-compatible endpoints (custom OpenAI gateways,
|
||||
OpenRouter) that front these models and enforce the same parameter
|
||||
constraint, so name-based detection is required as a fallback.
|
||||
"""
|
||||
m = (model or "").strip().lower()
|
||||
if not m:
|
||||
return False
|
||||
if "/" in m:
|
||||
m = m.rsplit("/", 1)[-1]
|
||||
return (
|
||||
m.startswith("gpt-4o")
|
||||
or m.startswith("gpt-4.1")
|
||||
or m.startswith("gpt-5")
|
||||
or m.startswith("o1")
|
||||
or m.startswith("o3")
|
||||
or m.startswith("o4")
|
||||
)
|
||||
|
||||
|
||||
def base_url_host_matches(base_url: str, domain: str) -> bool:
|
||||
"""Return True when the base URL's hostname is ``domain`` or a subdomain.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user