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:
Xiangji
2026-06-09 23:22:10 -07:00
committed by Teknium
parent ab55008631
commit 19c07c4037
6 changed files with 316 additions and 11 deletions
+38
View File
@@ -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.