mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-14 09:11:54 +00:00
fix(openrouter): never send reasoning field for adaptive Anthropic models (#43012)
The previous fix (#42991) only omitted reasoning when it was being disabled. But reasoning-mandatory Anthropic models (Claude 4.6+, fable) 400 with thinking.type.disabled on EVERY tool-continuation turn even when reasoning is enabled: chat_completions never replays signed thinking blocks, so the prior assistant tool_call has no thinking, and OpenRouter resolves "reasoning requested but history has none" by emitting thinking.type.disabled — which these models reject. Result: first turn works, every turn after the first tool call dies (HTTP 400, non-retryable). OpenRouter ignores reasoning.effort for adaptive Anthropic models anyway (the model self-decides), so the reasoning field is pointless for them on every turn and harmful on tool-replay turns. Omit it entirely → adaptive default. - openrouter profile: drop the reasoning field for reasoning-mandatory Anthropic models regardless of enabled/disabled; legacy Anthropic + non-Anthropic models unchanged. - tests: assert omission across enabled/disabled/effort variants; parity tests switched to a non-Anthropic reasoning model (deepseek) since Anthropic 4.6+ no longer carries a reasoning field. Verified live end-to-end: a tool-replay turn on anthropic/claude-fable-5 with reasoning enabled now builds extra_body=None and returns HTTP 200 (was 400).
This commit is contained in:
parent
ba44de06da
commit
46fedef07f
4 changed files with 59 additions and 27 deletions
|
|
@ -117,19 +117,25 @@ class OpenRouterProfile(ProviderProfile):
|
|||
"""
|
||||
extra_body: dict[str, Any] = {}
|
||||
if supports_reasoning:
|
||||
if reasoning_config is not None:
|
||||
cfg = dict(reasoning_config)
|
||||
# Reasoning-mandatory Anthropic models (Claude 4.6+ / fable /
|
||||
# future named models) have no "off" switch. Forwarding
|
||||
# ``{enabled: false}`` makes OpenRouter emit Anthropic's manual
|
||||
# ``thinking: {type: "disabled"}``, which those models reject
|
||||
# with a non-retryable HTTP 400. Omit reasoning entirely so the
|
||||
# model falls back to its default (adaptive) thinking instead.
|
||||
disabling = cfg.get("enabled") is False or cfg.get("effort") == "none"
|
||||
if disabling and _anthropic_reasoning_is_mandatory(model):
|
||||
pass # leave reasoning unset → adaptive default
|
||||
else:
|
||||
extra_body["reasoning"] = cfg
|
||||
# Reasoning-mandatory Anthropic models (Claude 4.6+ / fable /
|
||||
# future named models) use *adaptive* thinking: the model decides
|
||||
# how much to think, and OpenRouter ignores ``reasoning.effort`` for
|
||||
# them entirely. Sending any ``reasoning`` field is therefore both
|
||||
# pointless and actively harmful:
|
||||
# - ``{enabled: false}`` → OpenRouter emits Anthropic's manual
|
||||
# ``thinking: {type: "disabled"}``, which these models 400 on.
|
||||
# - any enabled form, on a tool-continuation turn whose prior
|
||||
# assistant tool_call carries no thinking block (chat_completions
|
||||
# never replays signed thinking blocks), ALSO makes OpenRouter
|
||||
# emit ``thinking: {type: "disabled"}`` → the same 400 on every
|
||||
# turn after the first tool call.
|
||||
# The only reliable behavior is to omit ``reasoning`` and let the
|
||||
# model default to adaptive. See hermes-agent#42991 (disable case)
|
||||
# and the tool-replay follow-up.
|
||||
if _anthropic_reasoning_is_mandatory(model):
|
||||
pass # omit reasoning entirely → adaptive default
|
||||
elif reasoning_config is not None:
|
||||
extra_body["reasoning"] = dict(reasoning_config)
|
||||
else:
|
||||
extra_body["reasoning"] = {"enabled": True, "effort": "medium"}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue