From 365620ab28020b403124d4b1a33cf6af822fac60 Mon Sep 17 00:00:00 2001 From: Brendan DeBeasi Date: Thu, 21 May 2026 07:16:48 -0700 Subject: [PATCH] feat(agent): add Fireworks pricing entries + routing branch Fireworks-hosted sessions previously showed estimated_cost_usd = 0 because (a) _OFFICIAL_DOCS_PRICING had no Fireworks entries and (b) resolve_billing_route() had no branch for provider="fireworks", falling through to billing_mode="unknown". Adds entries for the three Fireworks models hermes operators are most likely to route through (Kimi K2.6, DeepSeek V4 Pro, Qwen3.6-Plus) and a routing branch that triggers on either explicit provider="fireworks" or api.fireworks.ai base_url match. Mirrors the recently-merged MiniMax addition pattern; pricing snapshot sourced from https://docs.fireworks.ai/serverless/pricing and the per-model pages on fireworks.ai. Tests cover: (a) full Fireworks model id resolves to the snapshot entry, (b) base_url alone is sufficient to route, (c) end-to-end estimate returns "estimated" status with the expected dollar amount. A follow-up upstream issue is open proposing a dynamic pricing source (e.g. litellm's pricing JSON) as a permanent fix to the PR-per-model treadmill that this snapshot keeps adding to. --- agent/usage_pricing.py | 41 ++++++++++++++++++++++++++++++ tests/agent/test_usage_pricing.py | 42 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/agent/usage_pricing.py b/agent/usage_pricing.py index aa306fa12f8c..663b20477c22 100644 --- a/agent/usage_pricing.py +++ b/agent/usage_pricing.py @@ -609,6 +609,43 @@ _OFFICIAL_DOCS_PRICING: Dict[tuple[str, str], PricingEntry] = { source="official_docs_snapshot", pricing_version="minimax-pricing-2026-04", ), + # Fireworks AI — serverless pricing for the models hermes typically routes + # through when configured with provider="fireworks". Fireworks publishes a + # cached_input rate per model alongside input/output, which maps to + # cache_read_cost_per_million. No separately published cache_write rate. + ( + "fireworks", + "kimi-k2p6", + ): PricingEntry( + input_cost_per_million=Decimal("0.95"), + output_cost_per_million=Decimal("4.00"), + cache_read_cost_per_million=Decimal("0.16"), + source="official_docs_snapshot", + source_url="https://docs.fireworks.ai/serverless/pricing", + pricing_version="fireworks-pricing-2026-05", + ), + ( + "fireworks", + "deepseek-v4-pro", + ): PricingEntry( + input_cost_per_million=Decimal("1.74"), + output_cost_per_million=Decimal("3.48"), + cache_read_cost_per_million=Decimal("0.145"), + source="official_docs_snapshot", + source_url="https://docs.fireworks.ai/serverless/pricing", + pricing_version="fireworks-pricing-2026-05", + ), + ( + "fireworks", + "qwen3p6-plus", + ): PricingEntry( + input_cost_per_million=Decimal("0.50"), + output_cost_per_million=Decimal("3.00"), + cache_read_cost_per_million=Decimal("0.10"), + source="official_docs_snapshot", + source_url="https://fireworks.ai/models/fireworks/qwen3p6-plus", + pricing_version="fireworks-pricing-2026-05", + ), } # GPT-5.6 "-pro" high-effort variants bill at the same per-token rates as @@ -672,6 +709,10 @@ def resolve_billing_route( # 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") + 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. + return BillingRoute(provider="fireworks", model=model.rsplit("/", 1)[-1], base_url=base_url or "", billing_mode="official_docs_snapshot") if provider_name in {"custom", "local"} or (base and "localhost" in base): return BillingRoute(provider=provider_name or "custom", model=model, base_url=base_url or "", billing_mode="unknown") return BillingRoute(provider=provider_name or "unknown", model=model.split("/")[-1] if model else "", base_url=base_url or "", billing_mode="unknown") diff --git a/tests/agent/test_usage_pricing.py b/tests/agent/test_usage_pricing.py index 3bd68ae2344d..7b584ebe0e9b 100644 --- a/tests/agent/test_usage_pricing.py +++ b/tests/agent/test_usage_pricing.py @@ -322,3 +322,45 @@ def test_bedrock_claude_cached_session_estimates_cost_not_unknown(): ) assert result.status == "estimated" assert result.amount_usd is not None + +def test_fireworks_kimi_k2p6_resolves_with_full_model_path(): + """Fireworks model ids look like accounts/fireworks/models/; + the routing layer must strip the prefix so the dict lookup succeeds.""" + entry = get_pricing_entry( + "accounts/fireworks/models/kimi-k2p6", + provider="fireworks", + base_url="https://api.fireworks.ai/inference/v1", + ) + + assert entry is not None + assert float(entry.input_cost_per_million) == 0.95 + assert float(entry.output_cost_per_million) == 4.00 + assert float(entry.cache_read_cost_per_million) == 0.16 + assert entry.source == "official_docs_snapshot" + + +def test_fireworks_base_url_host_match_alone_routes_to_pricing(): + """Provider not explicitly passed; routing infers fireworks from the host.""" + entry = get_pricing_entry( + "accounts/fireworks/models/deepseek-v4-pro", + base_url="https://api.fireworks.ai/inference/v1", + ) + + assert entry is not None + assert float(entry.input_cost_per_million) == 1.74 + assert float(entry.output_cost_per_million) == 3.48 + + +def test_fireworks_qwen3p6_plus_estimate_usage_cost(): + """End-to-end: Fireworks Qwen3.6-Plus sessions report a dollar estimate.""" + result = estimate_usage_cost( + "accounts/fireworks/models/qwen3p6-plus", + CanonicalUsage(input_tokens=1_000_000, output_tokens=500_000), + provider="fireworks", + base_url="https://api.fireworks.ai/inference/v1", + ) + + assert result.status == "estimated" + assert result.amount_usd is not None + # 1M input × $0.50/M + 500K output × $3.00/M = $0.50 + $1.50 = $2.00 + assert float(result.amount_usd) == 2.00