Skip to content

Commit 613258b

Browse files
authored
LLM: strip sampling params and retry when the model rejects them (#4241)
1 parent 3f0d944 commit 613258b

1 file changed

Lines changed: 31 additions & 1 deletion

File tree

changedetectionio/llm/client.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,24 @@ def completion(model: str, messages: list, api_key: str = None,
9090

9191
_retryable = (litellm.Timeout, litellm.APIConnectionError)
9292

93+
# Some models reject sampling params outright: Anthropic Claude Opus 4.7/4.8 and
94+
# Fable return HTTP 400 for 'temperature', and OpenAI reasoning models (o1/o3/gpt-5)
95+
# only accept the default. litellm's per-model param metadata lags new releases, so
96+
# drop_params can't be relied on for freshly released models — instead, if the provider
97+
# rejects a sampling param, strip them and retry once. Models that accept them are
98+
# unaffected (they still receive temperature=0).
99+
_sampling_params = ('temperature', 'top_p', 'top_k')
100+
_stripped_sampling = False
101+
93102
logger.debug(
94103
f"LLM client: calling model={model!r} api_base={api_base!r} "
95104
f"timeout={_timeout}s max_tokens={kwargs['max_tokens']}"
96105
)
97106
logger.trace(messages)
98107

99-
for attempt in range(1, DEFAULT_RETRIES + 1):
108+
attempt = 0
109+
while attempt < DEFAULT_RETRIES:
110+
attempt += 1
100111
try:
101112
response = litellm.completion(**kwargs)
102113
choice = response.choices[0]
@@ -157,6 +168,25 @@ def completion(model: str, messages: list, api_key: str = None,
157168
)
158169
raise
159170

171+
except litellm.BadRequestError as e:
172+
# If the provider rejected an unsupported sampling param (and we haven't
173+
# already stripped them), drop them and retry once. attempt-=1 keeps this
174+
# off the timeout-retry budget; _stripped_sampling prevents a loop.
175+
msg = str(e).lower()
176+
if (not _stripped_sampling
177+
and any(p in kwargs for p in _sampling_params)
178+
and any(p in msg for p in _sampling_params)):
179+
dropped = [p for p in _sampling_params if kwargs.pop(p, None) is not None]
180+
_stripped_sampling = True
181+
attempt -= 1
182+
logger.warning(
183+
f"LLM client: model={model!r} rejected sampling params {dropped} "
184+
f"({e}); retrying without them"
185+
)
186+
continue
187+
logger.warning(f"LLM call failed: model={model!r} error={e}")
188+
raise
189+
160190
except Exception as e:
161191
logger.warning(f"LLM call failed: model={model!r} error={e}")
162192
raise

0 commit comments

Comments
 (0)