mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-13 17:09:07 +02:00
[shared] Identify a Bedrock geography by the vendor that follows it
The cross-region inference-profile prefix was matched against a list of
four — us, eu, apac, global — so a profile issued under any other
geography kept its prefix through normalization. That form matches no
catalog key, which cost more than a blank price column:
- discovery returned those models unpriced, so a real account's listing
came back almost entirely at $0
- the cost meter keys its table by the same normalized id, and operators
are told to register a Bedrock id exactly as AWS issues it, region
prefix included — so the default entry never resolved and every cache
bucket, and any model priced only by catalog defaults, metered free
Identify the geography by what follows it instead: a leading segment is a
geography when a known Bedrock vendor namespace comes next. New
geographies then need no change at all, and a vendor missing from the map
fails safe by keeping the prefix — the behaviour of the list this
replaces. Over-stripping is the direction that must not happen, since the
normalized id also decides which route may claim a model.
Covered at all three seams the id passes through: the normalizer, the cost
meter config the proxy bills from, and the discovery listing the dashboard
renders. Each test fails against the old four-geography list.
This commit is contained in:
@@ -494,3 +494,39 @@ func TestRegionFromUpstream(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// bedrockGeoListing carries profiles from geographies the original prefix list
|
||||
// did not name. Every one reduces to a catalog key, so every one must arrive
|
||||
// priced — an unstripped geography is what made a real account's listing come
|
||||
// back almost entirely at zero.
|
||||
const bedrockGeoListing = `{"inferenceProfileSummaries":[
|
||||
{"inferenceProfileId":"jp.anthropic.claude-sonnet-5-20260514-v1:0",
|
||||
"inferenceProfileName":"JP Anthropic Claude Sonnet 5","status":"ACTIVE","type":"SYSTEM_DEFINED"},
|
||||
{"inferenceProfileId":"au.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
"inferenceProfileName":"AU Anthropic Claude Haiku 4.5","status":"ACTIVE","type":"SYSTEM_DEFINED"},
|
||||
{"inferenceProfileId":"us-gov.anthropic.claude-sonnet-5-20260514-v1:0",
|
||||
"inferenceProfileName":"GovCloud Anthropic Claude Sonnet 5","status":"ACTIVE","type":"SYSTEM_DEFINED"}
|
||||
]}`
|
||||
|
||||
func TestBedrockProfilesFromAnyGeographyArrivePriced(t *testing.T) {
|
||||
cl, _ := newStubClient(http.StatusOK, bedrockGeoListing)
|
||||
|
||||
models, err := cl.Fetch(context.Background(), Request{
|
||||
CatalogID: "bedrock_api",
|
||||
UpstreamURL: "https://bedrock-runtime.eu-central-1.amazonaws.com",
|
||||
APIKey: "aws-token",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, models, 3)
|
||||
|
||||
for _, m := range models {
|
||||
assert.True(t, m.PricingKnown, "%s must resolve to a catalog rate", m.ID)
|
||||
assert.Greater(t, m.InputPer1k, 0.0, "input rate for %s", m.ID)
|
||||
assert.Greater(t, m.OutputPer1k, 0.0, "output rate for %s", m.ID)
|
||||
assert.Greater(t, m.CacheReadPer1k, 0.0, "cache-read rate for %s", m.ID)
|
||||
}
|
||||
|
||||
// The wire id is preserved whatever the pricing key reduced to: it is the
|
||||
// only form that works at invoke time.
|
||||
assert.Equal(t, "jp.anthropic.claude-sonnet-5-20260514-v1:0", models[0].ID)
|
||||
}
|
||||
|
||||
@@ -103,3 +103,37 @@ func TestBuildCostMeterConfig_OrphanAndGatewayProviders(t *testing.T) {
|
||||
assert.NotContains(t, cfg.Pricing.Providers, "prov-litellm", "empty-models gateway needs no per-record entry")
|
||||
assert.NotEmpty(t, cfg.Pricing.Defaults["openai"], "defaults still ship so the gateway's catalog-model traffic is priced")
|
||||
}
|
||||
|
||||
// TestBuildCostMeterConfig_BedrockGeographyOutsideTheOriginalFour is the
|
||||
// accounting half of the geography bug. The docs tell operators to register a
|
||||
// Bedrock id exactly as AWS issues it, region prefix included, and the cost
|
||||
// meter keys its table by the normalized form. While the geography was matched
|
||||
// against a list of four, a profile issued anywhere else kept its prefix,
|
||||
// missed the catalog entry it was meant to inherit from, and billed with a
|
||||
// zero entry underneath the operator's own rates — so every cache bucket
|
||||
// metered free and a model priced only by catalog defaults metered at nothing
|
||||
// at all.
|
||||
func TestBuildCostMeterConfig_BedrockGeographyOutsideTheOriginalFour(t *testing.T) {
|
||||
for _, geo := range []string{"jp", "au", "ca", "sa", "us-gov"} {
|
||||
t.Run(geo, func(t *testing.T) {
|
||||
bedrock := &types.Provider{
|
||||
ID: "prov-bedrock",
|
||||
ProviderID: "bedrock_api",
|
||||
Enabled: true,
|
||||
Models: []types.ProviderModel{
|
||||
{ID: geo + ".anthropic.claude-sonnet-5-20260514-v1:0", InputPer1k: 0.003, OutputPer1k: 0.015},
|
||||
},
|
||||
}
|
||||
raw, err := buildCostMeterConfigJSON([]*types.Provider{bedrock}, map[string][]string{"prov-bedrock": {"grp"}})
|
||||
require.NoError(t, err)
|
||||
cfg := decodeCostMeterConfig(t, raw)
|
||||
|
||||
e, ok := cfg.Pricing.Providers["prov-bedrock"]["anthropic.claude-sonnet-5"]
|
||||
require.True(t, ok, "a %s profile must key by the same normalized id the parser emits", geo)
|
||||
assert.InDelta(t, 0.0003, e.CacheReadPer1k, 1e-9,
|
||||
"cache read must be inherited from the bedrock default entry, not left at zero")
|
||||
assert.InDelta(t, 0.00375, e.CacheCreationPer1k, 1e-9,
|
||||
"cache creation must be inherited from the bedrock default entry, not left at zero")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user