mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(anthropic): guard max_tokens against non-positive values
Port from openclaw/openclaw#66664. The build_anthropic_kwargs call site used 'max_tokens or _get_anthropic_max_output(model)', which correctly falls back when max_tokens is 0 or None (falsy) but lets negative ints (-1, -500), fractional floats (0.5, 8192.7), NaN, and infinity leak through to the Anthropic API. Anthropic rejects these with HTTP 400 ('max_tokens: must be greater than or equal to 1'), turning a local config error into a surprise mid-conversation failure. Add two resolver helpers matching OpenClaw's: _resolve_positive_anthropic_max_tokens — returns int(value) only if value is a finite positive number; excludes bools, strings, NaN, infinity, sub-one positives (floor to 0). _resolve_anthropic_messages_max_tokens — prefers a positive requested value, else falls back to the model's output ceiling; raises ValueError only if no positive budget can be resolved. The context-window clamp at the call site (max_tokens > context_length) is preserved unchanged — it handles oversized values; the new resolver handles non-positive values. These concerns are now cleanly separated. Tests: 17 new cases covering positive/zero/negative ints, fractional floats (both >1 and <1), NaN, infinity, booleans, strings, None, and integration via build_anthropic_kwargs. Refs: openclaw/openclaw#66664
This commit is contained in:
parent
8152de2a84
commit
c9c6182839
2 changed files with 151 additions and 1 deletions
|
|
@ -117,6 +117,63 @@ def _get_anthropic_max_output(model: str) -> int:
|
|||
return best_val
|
||||
|
||||
|
||||
def _resolve_positive_anthropic_max_tokens(value) -> Optional[int]:
|
||||
"""Return ``value`` floored to a positive int, or ``None`` if it is not a
|
||||
finite positive number. Ported from openclaw/openclaw#66664.
|
||||
|
||||
Anthropic's Messages API rejects ``max_tokens`` values that are 0,
|
||||
negative, non-integer, or non-finite with HTTP 400. Python's ``or``
|
||||
idiom (``max_tokens or fallback``) correctly catches ``0`` but lets
|
||||
negative ints and fractional floats (``-1``, ``0.5``) through to the
|
||||
API, producing a user-visible failure instead of a local error.
|
||||
"""
|
||||
# Booleans are a subclass of int — exclude explicitly so ``True`` doesn't
|
||||
# silently become 1 and ``False`` doesn't become 0.
|
||||
if isinstance(value, bool):
|
||||
return None
|
||||
if not isinstance(value, (int, float)):
|
||||
return None
|
||||
try:
|
||||
import math
|
||||
if not math.isfinite(value):
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
floored = int(value) # truncates toward zero for floats
|
||||
return floored if floored > 0 else None
|
||||
|
||||
|
||||
def _resolve_anthropic_messages_max_tokens(
|
||||
requested,
|
||||
model: str,
|
||||
context_length: Optional[int] = None,
|
||||
) -> int:
|
||||
"""Resolve the ``max_tokens`` budget for an Anthropic Messages call.
|
||||
|
||||
Prefers ``requested`` when it is a positive finite number; otherwise
|
||||
falls back to the model's output ceiling. Raises ``ValueError`` if no
|
||||
positive budget can be resolved (should not happen with current model
|
||||
table defaults, but guards against a future regression where
|
||||
``_get_anthropic_max_output`` could return ``0``).
|
||||
|
||||
Separately, callers apply a context-window clamp — this resolver does
|
||||
not, to keep the positive-value contract independent of endpoint
|
||||
specifics.
|
||||
|
||||
Ported from openclaw/openclaw#66664 (resolveAnthropicMessagesMaxTokens).
|
||||
"""
|
||||
resolved = _resolve_positive_anthropic_max_tokens(requested)
|
||||
if resolved is not None:
|
||||
return resolved
|
||||
fallback = _get_anthropic_max_output(model)
|
||||
if fallback > 0:
|
||||
return fallback
|
||||
raise ValueError(
|
||||
f"Anthropic Messages adapter requires a positive max_tokens value for "
|
||||
f"model {model!r}; got {requested!r} and no model default resolved."
|
||||
)
|
||||
|
||||
|
||||
def _supports_adaptive_thinking(model: str) -> bool:
|
||||
"""Return True for Claude 4.6+ models that support adaptive thinking."""
|
||||
return any(v in model for v in _ADAPTIVE_THINKING_SUBSTRINGS)
|
||||
|
|
@ -1391,7 +1448,12 @@ def build_anthropic_kwargs(
|
|||
|
||||
model = normalize_model_name(model, preserve_dots=preserve_dots)
|
||||
# effective_max_tokens = output cap for this call (≠ total context window)
|
||||
effective_max_tokens = max_tokens or _get_anthropic_max_output(model)
|
||||
# Use the resolver helper so non-positive values (negative ints,
|
||||
# fractional floats, NaN, non-numeric) fail locally with a clear error
|
||||
# rather than 400-ing at the Anthropic API. See openclaw/openclaw#66664.
|
||||
effective_max_tokens = _resolve_anthropic_messages_max_tokens(
|
||||
max_tokens, model, context_length=context_length
|
||||
)
|
||||
|
||||
# Clamp output cap to fit inside the total context window.
|
||||
# Only matters for small custom endpoints where context_length < native
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue