Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions aider/helpers/model_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,14 +449,24 @@ def _record_to_info(self, record: Dict, provider: str) -> Dict:
if max_output_tokens is None:
max_output_tokens = context_len

# Normalize pricing: detect if values are in $/M format vs $/token format
# If cost >= 0.001, it's likely in $/M format (e.g., "1.0" = $1/M tokens)
# If cost < 0.001, it's likely already in $/token format (e.g., "0.00000055")
def _normalize_cost(cost: Optional[float]) -> float:
if cost is None or cost == 0:
return 0.0
if cost >= 0.001:
# Likely in $/M format, convert to $/token
return cost / self.DEFAULT_TOKEN_PRICE_RATIO
# Already in $/token format
return cost

info = {
"max_input_tokens": context_len,
"max_tokens": max_tokens,
"max_output_tokens": max_output_tokens,
"input_cost_per_token": (
input_cost or 0
) / self.DEFAULT_TOKEN_PRICE_RATIO, # Might Only Apply to Chutes and Be a thing we configure per-provider
"output_cost_per_token": (output_cost or 0) / self.DEFAULT_TOKEN_PRICE_RATIO,
"input_cost_per_token": _normalize_cost(input_cost),
"output_cost_per_token": _normalize_cost(output_cost),
"litellm_provider": provider,
"mode": record.get("mode", "chat"),
}
Expand Down
68 changes: 68 additions & 0 deletions tests/basic/test_model_provider_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,74 @@ def _failing_fetch(*args, **kwargs):
assert info["input_cost_per_token"] == 0.5 / manager.DEFAULT_TOKEN_PRICE_RATIO


def test_pricing_normalization_detects_token_format(tmp_path):
"""Test that pricing < 0.001 is treated as $/token, not $/M."""
payload = {
"data": [
{
"id": "demo/model",
"context_length": 2048,
# Pricing in $/token format (like synthetic provider)
"pricing": {"prompt": "0.00000055", "completion": "0.00000219"},
}
]
}

config = {
"demo": {
"api_base": "https://example.com/v1",
"requires_api_key": False,
}
}

manager = _make_manager(tmp_path, config)
cache_file = manager._get_cache_file("demo")
cache_file.write_text(json.dumps(payload))
manager._cache_loaded["demo"] = True
manager._provider_cache["demo"] = payload

info = manager.get_model_info("demo/demo/model")

assert info["max_input_tokens"] == 2048
# Values < 0.001 should NOT be divided (already in $/token format)
assert info["input_cost_per_token"] == 0.00000055
assert info["output_cost_per_token"] == 0.00000219


def test_pricing_normalization_detects_million_format(tmp_path):
"""Test that pricing >= 0.001 is treated as $/M and converted to $/token."""
payload = {
"data": [
{
"id": "demo/model",
"context_length": 2048,
# Pricing in $/M format (like some providers)
"pricing": {"prompt": "1.0", "completion": "2.0"},
}
]
}

config = {
"demo": {
"api_base": "https://example.com/v1",
"requires_api_key": False,
}
}

manager = _make_manager(tmp_path, config)
cache_file = manager._get_cache_file("demo")
cache_file.write_text(json.dumps(payload))
manager._cache_loaded["demo"] = True
manager._provider_cache["demo"] = payload

info = manager.get_model_info("demo/demo/model")

assert info["max_input_tokens"] == 2048
# Values >= 0.001 should be divided by 1000000 (convert $/M to $/token)
assert info["input_cost_per_token"] == 1.0 / manager.DEFAULT_TOKEN_PRICE_RATIO
assert info["output_cost_per_token"] == 2.0 / manager.DEFAULT_TOKEN_PRICE_RATIO


def test_model_info_manager_delegates_to_provider(monkeypatch, tmp_path):
monkeypatch.setattr(
"aider.models.litellm",
Expand Down