Skip to content

feat(cost): normalize query#3954

Merged
tothandras merged 2 commits intomainfrom
feat/llmcost-normalize
Mar 17, 2026
Merged

feat(cost): normalize query#3954
tothandras merged 2 commits intomainfrom
feat/llmcost-normalize

Conversation

@hekike
Copy link
Contributor

@hekike hekike commented Mar 17, 2026

Normalize LLM Model IDs to stripe dates.

Summary by CodeRabbit

  • Improvements

    • Canonicalizes provider and model identifiers before price lookup and caching to improve consistency and reduce mismatches.
  • Refactor

    • Centralized normalization logic so all components use the same provider/model canonicalization behavior.
  • Tests

    • Added tests covering provider alias mapping, whitespace/case handling, and stripping of date-based version suffixes from model IDs.

@hekike hekike added the release-note/feature Release note: Exciting New Features label Mar 17, 2026
@hekike hekike requested a review from a team as a code owner March 17, 2026 02:21
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds a centralized LLM provider/model normalization (provider alias mapping, lowercasing/trimming, and date-suffix stripping) and updates callers to use it, including adapter cache/key usage and the sync normalizer delegation. Tests added for normalization behavior.

Changes

Cohort / File(s) Summary
Normalization core
openmeter/llmcost/normalize.go, openmeter/llmcost/normalize_test.go
New public helpers NormalizeModelID and NormalizeProvider: canonicalize provider aliases, lowercase/trim inputs, and strip date-based version suffixes from model IDs. Includes tests covering mappings, case/whitespace handling, and suffix stripping.
Sync normalizer refactor
openmeter/llmcost/sync/normalizer.go, openmeter/llmcost/sync/normalizer_test.go
Replaces local normalization logic with calls to llmcost.NormalizeModelID; removes inline regex/provider mapping. Tests updated to expect hyphenated date-suffix stripping (e.g., gpt-4o-2024-08-06gpt-4o).
Adapter integration
openmeter/cost/adapter/adapter.go
Applies llmcost.NormalizeModelID in getLLMPrices before cache-key usage and in resolveLLMUnitCost before token-type/price lookup to ensure canonical keys and lookups.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • tothandras
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'feat(cost): normalize query' is vague and doesn't clearly convey what's being normalized or why. While it relates to the cost/LLM aspect, 'normalize query' is too generic and doesn't specify that this is about normalizing LLM model IDs. Consider a more descriptive title like 'feat(cost): normalize LLM model IDs to canonical forms' to clarify what normalization is happening and why.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/llmcost-normalize
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
openmeter/cost/adapter/adapter.go (1)

179-181: Optional cleanup: extract the repeated provider/model normalization into one helper.

This is working correctly; just a small DRY improvement so the normalization call shape stays consistent in one place.

♻️ Suggested refactor
+func normalizeProviderModel(provider, modelID string) (string, string) {
+	return llmcost.NormalizeModelID(modelID, provider)
+}
+
 // getLLMPrices ...
-		provider, modelID = llmcost.NormalizeModelID(modelID, provider)
+		provider, modelID = normalizeProviderModel(provider, modelID)

 // resolveLLMUnitCost ...
-	provider, modelID = llmcost.NormalizeModelID(modelID, provider)
+	provider, modelID = normalizeProviderModel(provider, modelID)

Also applies to: 280-282

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/cost/adapter/adapter.go` around lines 179 - 181, Extract the
repeated normalization call into a single helper to keep normalization logic
DRY: create a helper function (e.g., normalizeProviderModel(provider, modelID)
string,string) that internally calls llmcost.NormalizeModelID(modelID, provider)
and returns (provider, modelID) in canonical order, then replace the direct
calls to llmcost.NormalizeModelID in adapter.go (the occurrences around the
current provider, modelID assignment and the similar block at the other site)
with calls to normalizeProviderModel so both sites use the same wrapper; ensure
the helper name is unique (normalizeProviderModel or NormalizeModelPair) and
update any callers to use the new return ordering.
openmeter/llmcost/normalize.go (1)

30-54: Small robustness tweak: normalize input inside NormalizeProvider too.

At Line 30, direct usage like NormalizeProvider(" Azure ") currently won’t map as expected. Since this is exported, it’s safer if it handles trim/lowercase itself.

🛠️ Suggested change
 func NormalizeProvider(provider string) string {
+	provider = strings.ToLower(strings.TrimSpace(provider))
+
 	switch provider {
 	case "openai", "azure", "azure_ai":
 		return "openai"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/llmcost/normalize.go` around lines 30 - 54, NormalizeProvider
should sanitize its input before matching: update the NormalizeProvider function
to call strings.TrimSpace and strings.ToLower on the provider parameter (e.g.,
provider = strings.ToLower(strings.TrimSpace(provider))) so values like " Azure
" or "AZURE" map correctly; import the strings package if needed and then use
the normalized provider variable in the existing switch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@openmeter/cost/adapter/adapter.go`:
- Around line 179-181: Extract the repeated normalization call into a single
helper to keep normalization logic DRY: create a helper function (e.g.,
normalizeProviderModel(provider, modelID) string,string) that internally calls
llmcost.NormalizeModelID(modelID, provider) and returns (provider, modelID) in
canonical order, then replace the direct calls to llmcost.NormalizeModelID in
adapter.go (the occurrences around the current provider, modelID assignment and
the similar block at the other site) with calls to normalizeProviderModel so
both sites use the same wrapper; ensure the helper name is unique
(normalizeProviderModel or NormalizeModelPair) and update any callers to use the
new return ordering.

In `@openmeter/llmcost/normalize.go`:
- Around line 30-54: NormalizeProvider should sanitize its input before
matching: update the NormalizeProvider function to call strings.TrimSpace and
strings.ToLower on the provider parameter (e.g., provider =
strings.ToLower(strings.TrimSpace(provider))) so values like " Azure " or
"AZURE" map correctly; import the strings package if needed and then use the
normalized provider variable in the existing switch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d340e3b8-0ca2-47c5-8676-67be868acec4

📥 Commits

Reviewing files that changed from the base of the PR and between c23b3d5 and efe9d83.

📒 Files selected for processing (5)
  • openmeter/cost/adapter/adapter.go
  • openmeter/llmcost/normalize.go
  • openmeter/llmcost/normalize_test.go
  • openmeter/llmcost/sync/normalizer.go
  • openmeter/llmcost/sync/normalizer_test.go

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
openmeter/llmcost/normalize_test.go (1)

9-81: Add direct tests for exported NormalizeProvider

These tests validate aliases through NormalizeModelID, but NormalizeProvider is exported and should have its own direct contract tests (especially for case/whitespace behavior if you apply the fix above).

Suggested test addition
+func TestNormalizeProviderDirect(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    string
+		expected string
+	}{
+		{"alias", "azure", "openai"},
+		{"unknown passthrough", "unknown_provider", "unknown_provider"},
+		{"case and trim", "  Azure_AI  ", "openai"},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, tt.expected, NormalizeProvider(tt.input))
+		})
+	}
+}
As per coding guidelines "`**/*_test.go`: Make sure the tests are comprehensive and cover the changes."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/llmcost/normalize_test.go` around lines 9 - 81, Add direct unit
tests for the exported NormalizeProvider function (separate from
NormalizeModelID tests) that assert provider alias mapping, case-folding, and
whitespace trimming. Specifically create tests calling NormalizeProvider with
inputs like "OpenAI", "  openai  ", "azure", "vertex_ai", "aws", "facebook",
"mistralai", and "unknown_provider" and assert the returned provider equals
"openai", "openai", "openai", "google", "amazon", "meta", "mistral", and
"unknown_provider" respectively; also include a table-driven test for these
aliases and a small negative test for preserved unknown providers. Ensure tests
reference NormalizeProvider by name and use the same assertion style as existing
tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openmeter/llmcost/normalize.go`:
- Around line 30-54: NormalizeProvider currently treats input as case- and
whitespace-sensitive; update it to trim whitespace and lower-case the input
before the switch so calls like NormalizeProvider(" Azure ") map to "openai". At
the top of NormalizeProvider, compute a normalized variable (e.g., p :=
strings.ToLower(strings.TrimSpace(provider))) and use p in the switch cases
while keeping the same canonical return values; import the strings package if
not already present.

---

Nitpick comments:
In `@openmeter/llmcost/normalize_test.go`:
- Around line 9-81: Add direct unit tests for the exported NormalizeProvider
function (separate from NormalizeModelID tests) that assert provider alias
mapping, case-folding, and whitespace trimming. Specifically create tests
calling NormalizeProvider with inputs like "OpenAI", "  openai  ", "azure",
"vertex_ai", "aws", "facebook", "mistralai", and "unknown_provider" and assert
the returned provider equals "openai", "openai", "openai", "google", "amazon",
"meta", "mistral", and "unknown_provider" respectively; also include a
table-driven test for these aliases and a small negative test for preserved
unknown providers. Ensure tests reference NormalizeProvider by name and use the
same assertion style as existing tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: adcb46f0-f60c-441c-ab4a-075f895219a9

📥 Commits

Reviewing files that changed from the base of the PR and between efe9d83 and 5fb102a.

📒 Files selected for processing (4)
  • openmeter/cost/adapter/adapter.go
  • openmeter/llmcost/normalize.go
  • openmeter/llmcost/normalize_test.go
  • openmeter/llmcost/sync/normalizer.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • openmeter/cost/adapter/adapter.go
  • openmeter/llmcost/sync/normalizer.go

Comment on lines +30 to +54
func NormalizeProvider(provider string) string {
switch provider {
case "openai", "azure", "azure_ai":
return "openai"
case "anthropic":
return "anthropic"
case "google", "vertex_ai", "gemini":
return "google"
case "amazon", "aws", "bedrock":
return "amazon"
case "meta", "facebook":
return "meta"
case "deepseek":
return "deepseek"
case "mistral", "mistralai":
return "mistral"
case "cohere":
return "cohere"
case "xai":
return "xai"
case "minimax":
return "minimax"
default:
return provider
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

NormalizeProvider should normalize its own input

Right now NormalizeProvider is exported but case/whitespace-sensitive (e.g. NormalizeProvider(" Azure ") won’t map to openai). That makes direct usage easy to misuse and inconsistent with NormalizeModelID behavior (Line 16-27).

Suggested fix
 func NormalizeProvider(provider string) string {
+	provider = strings.ToLower(strings.TrimSpace(provider))
+
 	switch provider {
 	case "openai", "azure", "azure_ai":
 		return "openai"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func NormalizeProvider(provider string) string {
switch provider {
case "openai", "azure", "azure_ai":
return "openai"
case "anthropic":
return "anthropic"
case "google", "vertex_ai", "gemini":
return "google"
case "amazon", "aws", "bedrock":
return "amazon"
case "meta", "facebook":
return "meta"
case "deepseek":
return "deepseek"
case "mistral", "mistralai":
return "mistral"
case "cohere":
return "cohere"
case "xai":
return "xai"
case "minimax":
return "minimax"
default:
return provider
}
func NormalizeProvider(provider string) string {
provider = strings.ToLower(strings.TrimSpace(provider))
switch provider {
case "openai", "azure", "azure_ai":
return "openai"
case "anthropic":
return "anthropic"
case "google", "vertex_ai", "gemini":
return "google"
case "amazon", "aws", "bedrock":
return "amazon"
case "meta", "facebook":
return "meta"
case "deepseek":
return "deepseek"
case "mistral", "mistralai":
return "mistral"
case "cohere":
return "cohere"
case "xai":
return "xai"
case "minimax":
return "minimax"
default:
return provider
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/llmcost/normalize.go` around lines 30 - 54, NormalizeProvider
currently treats input as case- and whitespace-sensitive; update it to trim
whitespace and lower-case the input before the switch so calls like
NormalizeProvider(" Azure ") map to "openai". At the top of NormalizeProvider,
compute a normalized variable (e.g., p :=
strings.ToLower(strings.TrimSpace(provider))) and use p in the switch cases
while keeping the same canonical return values; import the strings package if
not already present.

@tothandras tothandras merged commit c29c017 into main Mar 17, 2026
26 checks passed
@tothandras tothandras deleted the feat/llmcost-normalize branch March 17, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants