diff --git a/agent/usage_pricing.py b/agent/usage_pricing.py index 9825e4a27bd..84956b2dfff 100644 --- a/agent/usage_pricing.py +++ b/agent/usage_pricing.py @@ -511,15 +511,53 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], PricingEntry] = { pricing_version="deepseek-pricing-2026-07", ), # Google Gemini + ( + "google", + "gemini-3.5-flash", + ): PricingEntry( + input_cost_per_million=Decimal("1.50"), + output_cost_per_million=Decimal("9.00"), + cache_read_cost_per_million=Decimal("0.15"), + cache_write_cost_per_million=Decimal("1.50"), + source="official_docs_snapshot", + source_url="https://ai.google.dev/pricing", + pricing_version="google-pricing-2026-07-07", + ), + ( + "google", + "gemini-3.1-pro", + ): PricingEntry( + input_cost_per_million=Decimal("2.00"), + output_cost_per_million=Decimal("12.00"), + cache_read_cost_per_million=Decimal("0.20"), + cache_write_cost_per_million=Decimal("2.00"), + source="official_docs_snapshot", + source_url="https://ai.google.dev/pricing", + pricing_version="google-pricing-2026-07-07", + ), + ( + "google", + "gemini-3.1-flash-lite", + ): PricingEntry( + input_cost_per_million=Decimal("0.25"), + output_cost_per_million=Decimal("1.50"), + cache_read_cost_per_million=Decimal("0.025"), + cache_write_cost_per_million=Decimal("0.25"), + source="official_docs_snapshot", + source_url="https://ai.google.dev/pricing", + pricing_version="google-pricing-2026-07-07", + ), ( "google", "gemini-2.5-pro", ): PricingEntry( input_cost_per_million=Decimal("1.25"), output_cost_per_million=Decimal("10.00"), + cache_read_cost_per_million=Decimal("0.125"), + cache_write_cost_per_million=Decimal("1.25"), source="official_docs_snapshot", source_url="https://ai.google.dev/pricing", - pricing_version="google-pricing-2026-03-16", + pricing_version="google-pricing-2026-07-07", ), ( "google", @@ -527,9 +565,11 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], PricingEntry] = { ): PricingEntry( input_cost_per_million=Decimal("0.15"), output_cost_per_million=Decimal("0.60"), + cache_read_cost_per_million=Decimal("0.015"), + cache_write_cost_per_million=Decimal("0.15"), source="official_docs_snapshot", source_url="https://ai.google.dev/pricing", - pricing_version="google-pricing-2026-03-16", + pricing_version="google-pricing-2026-07-07", ), ( "google", @@ -537,9 +577,11 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], PricingEntry] = { ): PricingEntry( input_cost_per_million=Decimal("0.10"), output_cost_per_million=Decimal("0.40"), + cache_read_cost_per_million=Decimal("0.01"), + cache_write_cost_per_million=Decimal("0.10"), source="official_docs_snapshot", source_url="https://ai.google.dev/pricing", - pricing_version="google-pricing-2026-03-16", + pricing_version="google-pricing-2026-07-07", ), # AWS Bedrock — pricing per the Bedrock pricing page. # Bedrock charges the same per-token rates as the model provider but @@ -925,11 +967,17 @@ def resolve_billing_route( return BillingRoute(provider="openai", model=model.split("/")[-1], base_url=base_url or "", billing_mode="official_docs_snapshot") if provider_name in {"minimax", "minimax-cn"}: return BillingRoute(provider=provider_name, model=model.split("/")[-1], base_url=base_url or "", billing_mode="official_docs_snapshot") - # Vertex AI hosts the same Gemini models as Google AI Studio; price them - # off the gemini official-docs snapshot. Strip the "google/" vendor prefix - # the OpenAI-compat endpoint requires so the pricing key matches. - if provider_name == "vertex" or base_url_host_matches(base_url or "", "aiplatform.googleapis.com"): - return BillingRoute(provider="gemini", model=model.split("/")[-1], base_url=base_url or "", billing_mode="official_docs_snapshot") + # Google AI Studio (Gemini) and Vertex AI host the same Gemini models. + # Price them off the official docs snapshot — the pricing keys are + # keyed on provider='google', so normalize every Google-flavored + # provider name/host onto it. Strip the "google/" vendor prefix the + # Vertex OpenAI-compat endpoint requires so the pricing key matches. + if ( + provider_name in {"google", "gemini", "vertex", "google-gemini", "google-ai-studio", "google-vertex", "vertex-ai"} + or base_url_host_matches(base_url or "", "aiplatform.googleapis.com") + or base_url_host_matches(base_url or "", "generativelanguage.googleapis.com") + ): + return BillingRoute(provider="google", model=model.split("/")[-1], base_url=base_url or "", billing_mode="official_docs_snapshot") if provider_name == "fireworks" or base_url_host_matches(base_url or "", "api.fireworks.ai"): # Fireworks model ids look like accounts/fireworks/models/; # rsplit("/", 1)[-1] yields just which is what the dict keys on. diff --git a/tests/agent/test_usage_pricing.py b/tests/agent/test_usage_pricing.py index 3cfbbd231b1..b17755247e9 100644 --- a/tests/agent/test_usage_pricing.py +++ b/tests/agent/test_usage_pricing.py @@ -636,3 +636,29 @@ def test_deepseek_v4_flash_estimate_usage_cost(): assert result.amount_usd is not None # 1M input × $0.14/M + 500K output × $0.28/M = $0.14 + $0.14 = $0.28 assert float(result.amount_usd) == 0.28 + + +def test_gemini_3_5_flash_pricing_resolved(): + """Ensure gemini-3.5-flash pricing exists and resolves correctly.""" + entry = get_pricing_entry("gemini-3.5-flash", provider="google") + assert entry is not None + assert float(entry.input_cost_per_million) == 1.50 + assert float(entry.output_cost_per_million) == 9.00 + assert float(entry.cache_read_cost_per_million) == 0.15 + assert float(entry.cache_write_cost_per_million) == 1.50 + + +def test_gemini_provider_maps_to_google(): + """Ensure using 'gemini' as provider resolves to 'google' pricing.""" + entry = get_pricing_entry("gemini-3.5-flash", provider="gemini") + assert entry is not None + assert float(entry.input_cost_per_million) == 1.50 + + # Also check base URL matching + entry_base = get_pricing_entry( + "gemini-3.5-flash", + provider="custom", + base_url="https://generativelanguage.googleapis.com/v1beta", + ) + assert entry_base is not None + assert float(entry_base.input_cost_per_million) == 1.50