LLM: strip sampling params and retry when the model rejects them (#4241)
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-14 (push) Has been cancelled

This commit is contained in:
Corey Quinn
2026-06-28 05:25:43 -05:00
committed by GitHub
parent 3f0d944e97
commit 613258b773
+31 -1
View File
@@ -90,13 +90,24 @@ def completion(model: str, messages: list, api_key: str = None,
_retryable = (litellm.Timeout, litellm.APIConnectionError)
# Some models reject sampling params outright: Anthropic Claude Opus 4.7/4.8 and
# Fable return HTTP 400 for 'temperature', and OpenAI reasoning models (o1/o3/gpt-5)
# only accept the default. litellm's per-model param metadata lags new releases, so
# drop_params can't be relied on for freshly released models — instead, if the provider
# rejects a sampling param, strip them and retry once. Models that accept them are
# unaffected (they still receive temperature=0).
_sampling_params = ('temperature', 'top_p', 'top_k')
_stripped_sampling = False
logger.debug(
f"LLM client: calling model={model!r} api_base={api_base!r} "
f"timeout={_timeout}s max_tokens={kwargs['max_tokens']}"
)
logger.trace(messages)
for attempt in range(1, DEFAULT_RETRIES + 1):
attempt = 0
while attempt < DEFAULT_RETRIES:
attempt += 1
try:
response = litellm.completion(**kwargs)
choice = response.choices[0]
@@ -157,6 +168,25 @@ def completion(model: str, messages: list, api_key: str = None,
)
raise
except litellm.BadRequestError as e:
# If the provider rejected an unsupported sampling param (and we haven't
# already stripped them), drop them and retry once. attempt-=1 keeps this
# off the timeout-retry budget; _stripped_sampling prevents a loop.
msg = str(e).lower()
if (not _stripped_sampling
and any(p in kwargs for p in _sampling_params)
and any(p in msg for p in _sampling_params)):
dropped = [p for p in _sampling_params if kwargs.pop(p, None) is not None]
_stripped_sampling = True
attempt -= 1
logger.warning(
f"LLM client: model={model!r} rejected sampling params {dropped} "
f"({e}); retrying without them"
)
continue
logger.warning(f"LLM call failed: model={model!r} error={e}")
raise
except Exception as e:
logger.warning(f"LLM call failed: model={model!r} error={e}")
raise