mirror of
https://github.com/vectorize-io/hindsight.git
synced 2026-09-14 19:31:49 +08:00
fix(llm): send max_completion_tokens for reasoning models and Azure OpenAI (#979)
PR #858 made the openai provider fall back to max_tokens whenever a custom base_url was set, to support Mistral/Together-style endpoints. This regressed two important setups: 1. Reasoning models (GPT-5, o1, o3) reject max_tokens outright with a 400 ("Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead."). 2. Azure OpenAI is fully OpenAI-API-compatible — it was only classified as "third-party compatible" because it requires a custom base_url. The combination of the two — Azure OpenAI + GPT-5 — is the exact setup the reporter hit in issue #978 and fails connection verification on startup. Fix _max_tokens_param_name() so it: - Always returns max_completion_tokens for reasoning models, regardless of base_url (they only support the new parameter name). - Detects Azure OpenAI endpoints by the *.openai.azure.com hostname and treats them as native OpenAI. The Mistral/Together behavior from #858 is preserved for non-reasoning models on non-Azure custom base URLs. Fixes #978
This commit is contained in:
@@ -196,16 +196,29 @@ class OpenAICompatibleLLM(LLMInterface):
|
||||
def _max_tokens_param_name(self) -> str:
|
||||
"""Return the correct parameter name for limiting response tokens.
|
||||
|
||||
Native OpenAI and Groq accept 'max_completion_tokens'. Mistral and other
|
||||
OpenAI-compatible endpoints that haven't adopted the newer parameter name
|
||||
require 'max_tokens'. Using a custom base_url with the openai provider
|
||||
signals a third-party compatible API, so fall back to 'max_tokens'.
|
||||
Native OpenAI, Azure OpenAI, Groq, and llamacpp accept 'max_completion_tokens'.
|
||||
Mistral and other OpenAI-compatible endpoints that haven't adopted the newer
|
||||
parameter name require 'max_tokens', so when the openai provider is configured
|
||||
with a non-Azure custom base_url we fall back to the widely-supported
|
||||
'max_tokens'.
|
||||
|
||||
Reasoning models (GPT-5, o1, o3) only accept 'max_completion_tokens' and reject
|
||||
'max_tokens' outright, so they always use the new parameter name regardless of
|
||||
base_url.
|
||||
"""
|
||||
# Reasoning models (GPT-5, o1, o3, ...) only accept max_completion_tokens.
|
||||
# Azure OpenAI + GPT-5 is the canonical example: issue #978.
|
||||
if self._supports_reasoning_model():
|
||||
return "max_completion_tokens"
|
||||
# Native OpenAI (no custom base URL), Groq, and llamacpp use max_completion_tokens
|
||||
if self.provider in ("groq", "llamacpp"):
|
||||
return "max_completion_tokens"
|
||||
if self.provider == "openai" and not self.base_url:
|
||||
return "max_completion_tokens"
|
||||
# Azure OpenAI is fully OpenAI-API-compatible — detect it by hostname so users
|
||||
# can keep provider=openai + an Azure base_url (the documented setup).
|
||||
if self.provider == "openai" and self.base_url and ".openai.azure.com" in self.base_url:
|
||||
return "max_completion_tokens"
|
||||
# openai with custom base_url, ollama, lmstudio, minimax, volcano —
|
||||
# use the widely-supported max_tokens
|
||||
return "max_tokens"
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Tests for OpenAICompatibleLLM._max_tokens_param_name.
|
||||
|
||||
Regression coverage for issue #978: Azure OpenAI + GPT-5 models were failing with
|
||||
"'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead."
|
||||
because PR #858 started sending 'max_tokens' whenever the openai provider had a
|
||||
custom base_url. Reasoning models only accept 'max_completion_tokens', and Azure
|
||||
OpenAI is fully OpenAI-API-compatible, so both cases must keep using the new
|
||||
parameter name.
|
||||
"""
|
||||
|
||||
from hindsight_api.engine.providers.openai_compatible_llm import OpenAICompatibleLLM
|
||||
|
||||
|
||||
def _make(provider: str, model: str, base_url: str = "") -> OpenAICompatibleLLM:
|
||||
return OpenAICompatibleLLM(
|
||||
provider=provider,
|
||||
api_key="test-key",
|
||||
base_url=base_url,
|
||||
model=model,
|
||||
)
|
||||
|
||||
|
||||
class TestMaxTokensParamName:
|
||||
def test_native_openai_uses_max_completion_tokens(self):
|
||||
llm = _make("openai", "gpt-4o-mini")
|
||||
assert llm._max_tokens_param_name() == "max_completion_tokens"
|
||||
|
||||
def test_openai_custom_base_url_falls_back_to_max_tokens(self):
|
||||
"""Mistral/Together-style OpenAI-compatible endpoints need max_tokens (PR #858)."""
|
||||
llm = _make("openai", "mistral-large-latest", base_url="https://api.mistral.ai/v1")
|
||||
assert llm._max_tokens_param_name() == "max_tokens"
|
||||
|
||||
def test_azure_openai_uses_max_completion_tokens(self):
|
||||
"""Regression for #978: Azure is fully OpenAI-API-compatible, not a third-party clone."""
|
||||
llm = _make(
|
||||
"openai",
|
||||
"gpt-4o-mini",
|
||||
base_url="https://my-resource.openai.azure.com/openai/v1/",
|
||||
)
|
||||
assert llm._max_tokens_param_name() == "max_completion_tokens"
|
||||
|
||||
def test_reasoning_model_always_uses_max_completion_tokens(self):
|
||||
"""Regression for #978: GPT-5/o1/o3 reject max_tokens outright, base_url must not matter."""
|
||||
# Azure + GPT-5 (exact reporter setup)
|
||||
azure_gpt5 = _make(
|
||||
"openai",
|
||||
"gpt-5.4-nano",
|
||||
base_url="https://my-resource.openai.azure.com/openai/v1/",
|
||||
)
|
||||
assert azure_gpt5._max_tokens_param_name() == "max_completion_tokens"
|
||||
|
||||
# Even a Mistral-style custom base_url must not downgrade a reasoning model
|
||||
for model in ("gpt-5", "gpt-5-mini", "o1-mini", "o3", "deepseek-r1"):
|
||||
llm = _make("openai", model, base_url="https://some-proxy.example.com/v1")
|
||||
assert llm._max_tokens_param_name() == "max_completion_tokens", model
|
||||
|
||||
def test_groq_uses_max_completion_tokens(self):
|
||||
llm = _make("groq", "openai/gpt-oss-120b", base_url="https://api.groq.com/openai/v1")
|
||||
assert llm._max_tokens_param_name() == "max_completion_tokens"
|
||||
|
||||
def test_llamacpp_uses_max_completion_tokens(self):
|
||||
llm = _make("llamacpp", "some-model", base_url="http://localhost:8080/v1")
|
||||
assert llm._max_tokens_param_name() == "max_completion_tokens"
|
||||
|
||||
def test_ollama_uses_max_tokens(self):
|
||||
llm = _make("ollama", "gemma3:12b", base_url="http://localhost:11434/v1")
|
||||
assert llm._max_tokens_param_name() == "max_tokens"
|
||||
|
||||
def test_lmstudio_uses_max_tokens(self):
|
||||
llm = _make("lmstudio", "openai/gpt-oss-20b", base_url="http://localhost:1234/v1")
|
||||
assert llm._max_tokens_param_name() == "max_tokens"
|
||||
Reference in New Issue
Block a user