Skip to content

Commit 7a06d89

Browse files
authored
Merge pull request #344 from chrisnestrud/fix/pricing
fix: correct pricing calculation for providers using $/token format
2 parents a889508 + 0ae64d8 commit 7a06d89

2 files changed

Lines changed: 82 additions & 4 deletions

File tree

aider/helpers/model_providers.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,14 +449,24 @@ def _record_to_info(self, record: Dict, provider: str) -> Dict:
449449
if max_output_tokens is None:
450450
max_output_tokens = context_len
451451

452+
# Normalize pricing: detect if values are in $/M format vs $/token format
453+
# If cost >= 0.001, it's likely in $/M format (e.g., "1.0" = $1/M tokens)
454+
# If cost < 0.001, it's likely already in $/token format (e.g., "0.00000055")
455+
def _normalize_cost(cost: Optional[float]) -> float:
456+
if cost is None or cost == 0:
457+
return 0.0
458+
if cost >= 0.001:
459+
# Likely in $/M format, convert to $/token
460+
return cost / self.DEFAULT_TOKEN_PRICE_RATIO
461+
# Already in $/token format
462+
return cost
463+
452464
info = {
453465
"max_input_tokens": context_len,
454466
"max_tokens": max_tokens,
455467
"max_output_tokens": max_output_tokens,
456-
"input_cost_per_token": (
457-
input_cost or 0
458-
) / self.DEFAULT_TOKEN_PRICE_RATIO, # Might Only Apply to Chutes and Be a thing we configure per-provider
459-
"output_cost_per_token": (output_cost or 0) / self.DEFAULT_TOKEN_PRICE_RATIO,
468+
"input_cost_per_token": _normalize_cost(input_cost),
469+
"output_cost_per_token": _normalize_cost(output_cost),
460470
"litellm_provider": provider,
461471
"mode": record.get("mode", "chat"),
462472
}

tests/basic/test_model_provider_manager.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,74 @@ def _failing_fetch(*args, **kwargs):
282282
assert info["input_cost_per_token"] == 0.5 / manager.DEFAULT_TOKEN_PRICE_RATIO
283283

284284

285+
def test_pricing_normalization_detects_token_format(tmp_path):
286+
"""Test that pricing < 0.001 is treated as $/token, not $/M."""
287+
payload = {
288+
"data": [
289+
{
290+
"id": "demo/model",
291+
"context_length": 2048,
292+
# Pricing in $/token format (like synthetic provider)
293+
"pricing": {"prompt": "0.00000055", "completion": "0.00000219"},
294+
}
295+
]
296+
}
297+
298+
config = {
299+
"demo": {
300+
"api_base": "https://example.com/v1",
301+
"requires_api_key": False,
302+
}
303+
}
304+
305+
manager = _make_manager(tmp_path, config)
306+
cache_file = manager._get_cache_file("demo")
307+
cache_file.write_text(json.dumps(payload))
308+
manager._cache_loaded["demo"] = True
309+
manager._provider_cache["demo"] = payload
310+
311+
info = manager.get_model_info("demo/demo/model")
312+
313+
assert info["max_input_tokens"] == 2048
314+
# Values < 0.001 should NOT be divided (already in $/token format)
315+
assert info["input_cost_per_token"] == 0.00000055
316+
assert info["output_cost_per_token"] == 0.00000219
317+
318+
319+
def test_pricing_normalization_detects_million_format(tmp_path):
320+
"""Test that pricing >= 0.001 is treated as $/M and converted to $/token."""
321+
payload = {
322+
"data": [
323+
{
324+
"id": "demo/model",
325+
"context_length": 2048,
326+
# Pricing in $/M format (like some providers)
327+
"pricing": {"prompt": "1.0", "completion": "2.0"},
328+
}
329+
]
330+
}
331+
332+
config = {
333+
"demo": {
334+
"api_base": "https://example.com/v1",
335+
"requires_api_key": False,
336+
}
337+
}
338+
339+
manager = _make_manager(tmp_path, config)
340+
cache_file = manager._get_cache_file("demo")
341+
cache_file.write_text(json.dumps(payload))
342+
manager._cache_loaded["demo"] = True
343+
manager._provider_cache["demo"] = payload
344+
345+
info = manager.get_model_info("demo/demo/model")
346+
347+
assert info["max_input_tokens"] == 2048
348+
# Values >= 0.001 should be divided by 1000000 (convert $/M to $/token)
349+
assert info["input_cost_per_token"] == 1.0 / manager.DEFAULT_TOKEN_PRICE_RATIO
350+
assert info["output_cost_per_token"] == 2.0 / manager.DEFAULT_TOKEN_PRICE_RATIO
351+
352+
285353
def test_model_info_manager_delegates_to_provider(monkeypatch, tmp_path):
286354
monkeypatch.setattr(
287355
"aider.models.litellm",

0 commit comments

Comments
 (0)