mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-25 09:01:29 +02:00
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.
75 lines
3.1 KiB
Go
75 lines
3.1 KiB
Go
// Package llm holds LLM model-identifier helpers shared by the proxy and
|
|
// the management server. The proxy normalizes model ids parsed off inbound
|
|
// requests; management normalizes the operator's registered model ids at
|
|
// synthesis time so both sides of the pricing / routing contract compare
|
|
// equal.
|
|
package llm
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// bedrockRegionPrefixes are the cross-region inference-profile prefixes that
|
|
// front a Bedrock model id (e.g. "eu.anthropic.claude-...").
|
|
var bedrockRegionPrefixes = []string{"us.", "eu.", "apac.", "global."}
|
|
|
|
// bedrockVersionSuffix matches the trailing "-vN[:N]" or "-YYYYMMDD-vN[:N]"
|
|
// version/throughput suffix of a Bedrock model id.
|
|
var bedrockVersionSuffix = regexp.MustCompile(`-(\d{8}-)?v\d+(:\d+)?$`)
|
|
|
|
// NormalizeBedrockModel strips an ARN wrapper, a cross-region inference-profile
|
|
// prefix, and the version/throughput suffix from a Bedrock model id so it
|
|
// matches the catalog/pricing key, e.g.
|
|
// "eu.anthropic.claude-sonnet-4-5-20250929-v1:0" -> "anthropic.claude-sonnet-4-5"
|
|
// and the inference-profile ARN's last segment likewise. It is the single
|
|
// source of truth shared by the proxy's request parser (which normalizes the
|
|
// request model from the URL path), the proxy's router (which normalizes the
|
|
// operator's registered Bedrock model ids so both sides compare equal), and
|
|
// the management synthesizer (which keys per-provider pricing entries by the
|
|
// normalized id the parser will emit at billing time).
|
|
func NormalizeBedrockModel(modelID string) string {
|
|
m := modelID
|
|
// A full ARN (inference-profile / provisioned-throughput / foundation-model)
|
|
// carries the model id in its last path segment.
|
|
if strings.HasPrefix(m, "arn:") {
|
|
if i := strings.LastIndex(m, "/"); i >= 0 {
|
|
m = m[i+1:]
|
|
}
|
|
}
|
|
for _, p := range bedrockRegionPrefixes {
|
|
if strings.HasPrefix(m, p) {
|
|
m = m[len(p):]
|
|
break
|
|
}
|
|
}
|
|
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
|
|
// vendor surface with the bare, unversioned id.
|
|
func NormalizeVertexModel(modelID string) string {
|
|
if at := strings.Index(modelID, "@"); at >= 0 {
|
|
return modelID[:at]
|
|
}
|
|
return modelID
|
|
}
|