fix: align Gemini billing pricing with provider catalog

This commit is contained in:
Ivan Miao 2026-07-17 09:42:23 +02:00 committed by Teknium
parent ecebff82d2
commit 26dd976fbe
2 changed files with 88 additions and 27 deletions

View file

@ -518,7 +518,6 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], 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",
@ -530,7 +529,6 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], 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",
@ -542,7 +540,28 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], 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-3-pro-preview",
): PricingEntry(
input_cost_per_million=Decimal("2.00"),
output_cost_per_million=Decimal("12.00"),
cache_read_cost_per_million=Decimal("0.20"),
source="official_docs_snapshot",
source_url="https://ai.google.dev/pricing",
pricing_version="google-pricing-2026-07-07",
),
(
"google",
"gemini-3-flash-preview",
): PricingEntry(
input_cost_per_million=Decimal("0.50"),
output_cost_per_million=Decimal("3.00"),
cache_read_cost_per_million=Decimal("0.05"),
source="official_docs_snapshot",
source_url="https://ai.google.dev/pricing",
pricing_version="google-pricing-2026-07-07",
@ -554,7 +573,6 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], 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-07-07",
@ -566,7 +584,6 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], 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-07-07",
@ -578,7 +595,6 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], 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-07-07",
@ -920,6 +936,18 @@ for _base_56 in ("gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"):
]
del _base_56
# The direct Gemini provider currently exposes preview IDs for these two
# models. Keep the official snapshot keyed by both their documented stable
# names and the provider's emitted IDs so a catalog selection is billable.
for _alias, _canonical in {
"gemini-3.1-pro-preview": "gemini-3.1-pro",
"gemini-3.1-flash-lite-preview": "gemini-3.1-flash-lite",
}.items():
_OFFICIAL_DOCS_PRICING[("google", _alias)] = _OFFICIAL_DOCS_PRICING[
("google", _canonical)
]
del _alias, _canonical
def _to_decimal(value: Any) -> Optional[Decimal]:
if value is None:

View file

@ -5,6 +5,7 @@ from agent.usage_pricing import (
estimate_usage_cost,
get_pricing_entry,
normalize_usage,
resolve_billing_route,
)
@ -638,27 +639,59 @@ def test_deepseek_v4_flash_estimate_usage_cost():
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_catalog_models_estimate_cached_usage():
"""Every direct-Gemini catalog model with official pricing can estimate a
session that includes a cache hit, rather than reporting ``unknown``.
"""
from hermes_cli.models import _PROVIDER_MODELS
usage = CanonicalUsage(input_tokens=100, output_tokens=100, cache_read_tokens=100)
results = [
estimate_usage_cost(model, usage, provider="gemini")
for model in _PROVIDER_MODELS["gemini"]
]
assert results
assert all(result.status == "estimated" for result in results)
assert all(result.amount_usd is not None and result.amount_usd > 0 for result in results)
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",
def test_google_and_vertex_routes_share_official_pricing_snapshot():
"""Direct Gemini, Vertex, and Vertex's OpenAI-compatible hostname must
all normalize to the Google official-pricing route.
"""
routes = (
resolve_billing_route("model", provider="gemini"),
resolve_billing_route("google/model", provider="vertex"),
resolve_billing_route(
"google/model",
provider="custom",
base_url="https://aiplatform.googleapis.com/v1/projects/example",
),
)
assert entry_base is not None
assert float(entry_base.input_cost_per_million) == 1.50
assert all(route.provider == "google" for route in routes)
assert all(route.billing_mode == "official_docs_snapshot" for route in routes)
def test_vertex_default_model_estimates_cached_usage(monkeypatch):
"""The bundled Vertex profile's default auxiliary model must fall back to
Google snapshot pricing when the OpenAI-compatible endpoint has no model
metadata, including for cache-read accounting.
"""
from providers import get_provider_profile
monkeypatch.setattr(
"agent.usage_pricing.fetch_endpoint_model_metadata",
lambda *_args, **_kwargs: {},
)
vertex = get_provider_profile("vertex")
result = estimate_usage_cost(
vertex.default_aux_model,
CanonicalUsage(input_tokens=100, output_tokens=100, cache_read_tokens=100),
provider=vertex.name,
base_url=vertex.base_url,
)
assert result.status == "estimated"
assert result.amount_usd is not None and result.amount_usd > 0