fix(anthropic_adapter): only mangle dots in Claude-shaped model IDs (#17171)#17282
Conversation
…ousResearch#17171) `normalize_model_name` blindly converted dots to hyphens for any non-Bedrock model, which corrupted non-Anthropic vendor IDs whose dots are version separators in their own right. The dot→hyphen rewrite is an Anthropic-Messages-API quirk (OpenRouter `4.6` → Anthropic `4-6`); applying it to `gpt-5.4`, `glm-4.7`, `z-ai/glm-5.1`, or `minimax-m2.5-free` produces non-existent IDs that the downstream API 404s on, with an error message that names a model the user never typed. The reporter's repro (NousResearch#17171): webui's `resolve_model_provider` fails to infer `openai-codex` from a `gpt-*` model name, falls back to the default Anthropic provider, and the request reaches the Messages API as `gpt-5-4`. Same root cause as NousResearch#16417 (custom anthropic_messages providers) and NousResearch#13061 (zenmux + custom anthropic_messages); the opencode-zen subset (NousResearch#7421) was mitigated earlier via the `_anthropic_preserve_dots()` allowlist but the underlying invariant was never enforced. Narrow the rewrite to Claude-shaped IDs: after the prefix-strip and Bedrock-detection guards, skip mangling when the model name has no `claude` substring. Claude callers are unaffected; the explicit `preserve_dots=True` flag remains the documented contract for known non-Claude allowlist providers and now matches the default behavior for unlisted vendors. Test plan - Parametrized regression: gpt-5.4, glm-4.7, qwen3.5-plus, z-ai/glm-5.1, minimax-m2.5-free, openai/gpt-5.4, google/gemini-2.5-pro, gemini-2.5-pro, deepseek-v3.1 - Negative invariant: claude-opus-4.6 → claude-opus-4-6 still mangled; case-insensitive Claude detection covers `Claude-Opus-4.6` - Reporter repro (NousResearch#17171): `normalize_model_name("gpt-5.4")` and `normalize_model_name("anthropic/gpt-5.4")` both round-trip - `preserve_dots=True` short-circuits before the new check - Updated `test_minimax_provider.py::test_normalize_converts_without_preserve` — the assertion previously documented the bug; it now documents the fix (MiniMax-M2.7 survives even without preserve_dots=True) - Bedrock integration suite (55 tests) and Anthropic-adapter tests unrelated to OAuth credentials all pass - Regression guard: reverting the production change makes 10/12 invariant tests fail with the expected `gpt-5-4`/`MiniMax-M2-7` outputs Fixes NousResearch#17171 Related: NousResearch#16417, NousResearch#13061, NousResearch#7421 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes model-name normalization in the Anthropic adapter so that dot→hyphen rewriting only applies to Claude-shaped model IDs, preventing accidental corruption of non-Anthropic vendor model IDs (e.g., gpt-5.4, glm-4.7) when they are routed through the Anthropic Messages path.
Changes:
- Gate dot→hyphen rewriting in
normalize_model_namebehind a case-insensitive"claude"check (while still preserving Bedrock IDs). - Add/expand regression tests asserting non-Claude dotted IDs round-trip unchanged with default
preserve_dots=False. - Update the MiniMax provider test to reflect the new invariant (non-Claude IDs no longer require
preserve_dots=Trueto avoid mangling).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| agent/anthropic_adapter.py | Restricts dot→hyphen normalization to Claude-shaped IDs and refreshes lower after stripping anthropic/. |
| tests/agent/test_anthropic_adapter.py | Adds invariant/regression coverage for non-Claude dotted model IDs and ensures Claude dotted IDs still normalize. |
| tests/agent/test_minimax_provider.py | Updates MiniMax normalization test to assert dots are preserved even without preserve_dots=True for non-Claude IDs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@alt-glitch Thanks for the cross-link. Quick comparison vs #17290 — both fixes target the same line in
Happy to defer if a maintainer prefers the narrower allowlist shape — but the parametrized invariant + the stale-test fix are the parts I'd want to preserve either way. CI audit — the single
The |
|
Closing — superseded by @vominh1919's fix that landed on Quick honest comparison:
The landed version is more conservative — Thanks @vominh1919 — happy to see this land. Closing here. |
Summary
normalize_model_nameblindly rewrote dots to hyphens for every non-Bedrock model, corrupting non-Anthropic vendor IDs whose dots are version separators (gpt-5.4→gpt-5-4,glm-4.7→glm-4-7,z-ai/glm-5.1→z-ai/glm-5-1,minimax-m2.5-free→minimax-m2-5-free).claude(case-insensitive). Claude callers are unaffected; non-Claude IDs survive intact even whenpreserve_dots=False.The bug
The dot→hyphen rewrite is an Anthropic-Messages-API quirk: OpenRouter form
claude-opus-4.6must become Anthropic formclaude-opus-4-6. The function applied it unconditionally to every model except Bedrock IDs, so a non-Claude model accidentally routed through the Anthropic adapter — typically because the caller defaulted toprovider=anthropicafter failing to infer the right provider — got its name mangled into a non-existent ID and 404'd at the API. The user-facing failure named a model they never typed (gpt-5-4), making the misconfiguration hard to recognize.The reporter's repro (#17171):
{"model": "gpt-5.4"}and no provider hint.resolve_model_provider("gpt-5.4")returns(gpt-5.4, None, None)— no inference from thegpt-*shape.resolve_runtime_provider(requested=None)falls back to the default-detectedanthropicprovider.AIAgentis built withprovider="anthropic",api_mode="anthropic_messages",model="gpt-5.4".normalize_model_name("gpt-5.4", preserve_dots=False)returns"gpt-5-4".api.anthropic.com/v1/messageswithmodel: gpt-5-4→ HTTP 404.Same root cause underlies #16417 (custom
anthropic_messagesproviders) and #13061 (zenmux + customanthropic_messages). The opencode-zen subset (#7421) was previously mitigated by adding the provider to the_anthropic_preserve_dots()allowlist, but the underlying invariant — only Anthropic-shaped names need Anthropic-shaped normalization — was never enforced at the function itself.The fix
Narrow the rewrite to Claude-shaped IDs:
claude-opus-4.6→claude-opus-4-6still works, mixed caseClaude-Opus-4.6too).preserve_dots=Trueflag still short-circuits before the new check, so the documented contract for the alibaba / minimax / opencode-zen / zai / bedrock allowlist is preserved.preserve_dots=Falsepath becomes safe for non-Claude IDs that get routed through the Anthropic adapter — they round-trip verbatim instead of being silently corrupted.Contract Protected
Invariant: the dot→hyphen rewrite in
normalize_model_nameonly fires when both (a) the caller has not opted intopreserve_dots=Trueand (b) the model name shape is Claude-like (containsclaudeafter the optionalanthropic/prefix strip).claude-opus-4.6claude-opus-4-6claude-opus-4-6✅anthropic/claude-opus-4.6claude-opus-4-6claude-opus-4-6✅Claude-Opus-4.6Claude-Opus-4-6Claude-Opus-4-6✅global.anthropic.claude-opus-4-7global.anthropic.claude-opus-4-7global.anthropic.claude-opus-4-7✅gpt-5.4gpt-5-4❌gpt-5.4✅ (#17171)glm-4.7glm-4-7❌glm-4.7✅ (#16417)z-ai/glm-5.1z-ai/glm-5-1❌z-ai/glm-5.1✅ (#13061)MiniMax-M2.7MiniMax-M2-7❌MiniMax-M2.7✅google/gemini-2.5-progoogle/gemini-2-5-pro❌google/gemini-2.5-pro✅Test plan
TestNormalizeModelNameNonClaudeInvariant::test_non_claude_model_ids_preserve_dots[…]for nine known-bad IDs from openai-codex fallback sends 'gpt-5-4' instead of 'gpt-5.4' to Codex backend, returning HTTP 404 #17171, [Bug]: custom anthropic_messages providers can rewrite model names by replacing '.' with '-' #16417, [Bug] normalize_model_name rewrites custom provider model IDs, breaking models with dots (e.g. z-ai/glm-5.1 via zenmux) #13061, Bug: opencode-zen incorrectly converts dots to hyphens in model names #7421.test_negative_claude_dotted_still_mangled: Claude IDs continue to be rewritten; mixed-case detection works.test_repro_17171_default_anthropic_provider_with_gpt_model:gpt-5.4andanthropic/gpt-5.4both round-trip.preserve_dots=Truestill authoritative for non-Claude callers (test_preserve_dots_true_remains_authoritative_for_non_claude).test_minimax_provider.py::test_normalize_converts_without_preserve— its docstring previously labelled the behavior "broken for MiniMax"; it now documents the fix.tests/agent/test_bedrock_integration.py(55 tests) all pass — Bedrock detection still wins before the new Claude check.tests/agent/test_minimax_provider.py(41 tests) all pass.gpt-5-4/MiniMax-M2-7/etc. outputs; restoring the fix flipped them back to green.Related
Fixes #17171
Related: #16417, #13061, #7421 (mitigated earlier via
_anthropic_preserve_dots()allowlist; this PR enforces the same outcome at the normalization layer for vendors not on that allowlist)Positioning vs #17290
@vominh1919's #17290 (opened ~12 min after this one) is a narrower fix at the same line. Differences in scope:
if "claude" not in lower) applied after theanthropic/prefix has been stripped, with an explicit re-lower()so the stripped name's case is captured. fix: narrow Anthropic adapter dot-mangling to Claude models only (#17171) #17290 keeps an_lower.startswith("anthropic/")arm in its guard which is unreachable at that point in the function.TestNormalizeModelNameNonClaudeInvariant, 12 cases) covering each of openai-codex fallback sends 'gpt-5-4' instead of 'gpt-5.4' to Codex backend, returning HTTP 404 #17171, [Bug]: custom anthropic_messages providers can rewrite model names by replacing '.' with '-' #16417, [Bug] normalize_model_name rewrites custom provider model IDs, breaking models with dots (e.g. z-ai/glm-5.1 via zenmux) #13061, Bug: opencode-zen incorrectly converts dots to hyphens in model names #7421. fix: narrow Anthropic adapter dot-mangling to Claude models only (#17171) #17290 ships no new tests and relies on the existingclaude-*tests still passing.test_normalize_converts_without_preserveintests/agent/test_minimax_provider.py(which currently asserts the brokenMiniMax-M2-7output and labels it "broken for MiniMax") with a positive assertion that documents the fix. fix: narrow Anthropic adapter dot-mangling to Claude models only (#17171) #17290 leaves that test as-is, which would silently keep encoding the old bug as the expected behavior.Either fix unblocks the reporter's repro on #17171; this PR additionally protects the invariant against future regressions and cleans up the stale test.