[proxy] Match dated Anthropic model ids against their undated form

shared/llm normalizes Bedrock and Vertex model ids so both sides of the
routing and pricing contract compare equal, but nothing did the same for a
first-party Anthropic id. A client pinning "claude-sonnet-4-5-20250929"
against a record registered as "claude-sonnet-4-5" denied as not-routable,
and where a catch-all route carried it through, the price lookup missed and
the request recorded no cost.

Add NormalizeAnthropicModel beside the existing two and consult it after an
exact match fails in the router's claim check, the pricing table, and the
per-record price map. Exact matches still win, so an operator who registers
two dated releases of the same family keeps them distinct.
This commit is contained in:
mlsmaycon
2026-08-11 02:50:43 +00:00
parent d928bcb630
commit 1ae352a08d
8 changed files with 123 additions and 3 deletions
+16
View File
@@ -46,6 +46,22 @@ func NormalizeBedrockModel(modelID string) string {
return bedrockVersionSuffix.ReplaceAllString(m, "")
}
// anthropicDateSuffix matches the trailing "-YYYYMMDD" release-date suffix
// Anthropic appends to a pinned model id. No other vendor in the catalog
// ends an id in eight consecutive digits, so the pattern is safe to apply
// before a lookup regardless of surface.
var anthropicDateSuffix = regexp.MustCompile(`-\d{8}$`)
// NormalizeAnthropicModel strips the trailing release-date suffix from a
// first-party Anthropic model id, e.g. "claude-sonnet-4-5-20250929" ->
// "claude-sonnet-4-5", so a dated id a client pins matches the undated one
// the operator registered. Callers try the verbatim id first and fall back
// to this, so two dated releases of the same family stay distinct wherever
// both are registered explicitly.
func NormalizeAnthropicModel(modelID string) string {
return anthropicDateSuffix.ReplaceAllString(modelID, "")
}
// NormalizeVertexModel strips the "@version" suffix from a Vertex AI model id
// (e.g. "claude-sonnet-4-5@20250929" -> "claude-sonnet-4-5") so it matches
// the catalog/pricing key. Vertex publisher models are priced under their
+18
View File
@@ -34,3 +34,21 @@ func TestNormalizeVertexModel(t *testing.T) {
require.Equal(t, want, NormalizeVertexModel(in), "normalize %q", in)
}
}
func TestNormalizeAnthropicModel(t *testing.T) {
cases := map[string]string{
"claude-sonnet-4-5-20250929": "claude-sonnet-4-5",
"claude-3-5-haiku-20241022": "claude-3-5-haiku",
"claude-sonnet-5": "claude-sonnet-5",
"claude-opus-4-8": "claude-opus-4-8",
// Other vendors' ids must survive untouched: none of them end in
// eight consecutive digits.
"gpt-4o": "gpt-4o",
"gpt-4o-2024-08-06": "gpt-4o-2024-08-06",
"anthropic.claude-haiku-4-5": "anthropic.claude-haiku-4-5",
"": "",
}
for in, want := range cases {
require.Equal(t, want, NormalizeAnthropicModel(in), "normalize %q", in)
}
}