From 2b29dddb33d9aa3e11632a25e6d47b114a1444d2 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Tue, 4 Aug 2026 13:55:48 -0400 Subject: [PATCH] feat(models): add Fireworks Kimi K3 Fast --- docs/public/integrations/fireworks.mdx | 14 ++-- lib/components/fabro-llm/src/client.rs | 56 +++++++++++++ lib/components/fabro-llm/tests/integration.rs | 11 +++ lib/foundation/fabro-model/src/catalog.rs | 78 +++++++++++++++++-- .../src/catalog/providers/fireworks.toml | 58 +++++++++++++- 5 files changed, 202 insertions(+), 15 deletions(-) diff --git a/docs/public/integrations/fireworks.mdx b/docs/public/integrations/fireworks.mdx index 26e873705..e18198851 100644 --- a/docs/public/integrations/fireworks.mdx +++ b/docs/public/integrations/fireworks.mdx @@ -48,6 +48,8 @@ The built-in catalog gives Fireworks offerings the same human-facing model slugs | Fabro model slug | Fireworks API ID / notes | | --- | --- | +| `kimi-k3` | `accounts/fireworks/models/kimi-k3` | +| `kimi-k3-fast` | `accounts/fireworks/routers/kimi-k3-fast`; Fast tier at 50% above standard rates | | `kimi-k2.7-code` | `accounts/fireworks/models/kimi-k2p7-code`; provider default | | `kimi-k2.6` | `accounts/fireworks/models/kimi-k2p6` | | `deepseek-v4-pro`, `deepseek-v4-flash` (`deepseek`, `deepseek-v4`, `deepseek-flash`) | `accounts/fireworks/models/deepseek-v4-...` | @@ -80,15 +82,15 @@ Note that Fireworks' `GET /v1/models` endpoint only returns a featured subset of ```bash fabro model list --provider fireworks -fabro model test --provider fireworks --model kimi-k2.7-code -fabro run workflow.fabro --provider fireworks --model deepseek-v4-flash +fabro model test --provider fireworks --model kimi-k3-fast +fabro run workflow.fabro --provider fireworks --model kimi-k3-fast ``` When targeting a non-default remote server, pass the same `--server` value to verification commands: ```bash fabro model list --server https://your-fabro.example --provider fireworks -fabro model test --server https://your-fabro.example --provider fireworks --model kimi-k2.7-code +fabro model test --server https://your-fabro.example --provider fireworks --model kimi-k3-fast ``` In workflow stylesheets: @@ -97,7 +99,7 @@ In workflow stylesheets: digraph Example { graph [ model_stylesheet=" - * { model: fireworks/kimi-k2.7-code; } + * { model: fireworks/kimi-k3-fast; } " ] @@ -115,7 +117,7 @@ Fireworks caches prompt prefixes automatically — no cache breakpoints or reque ## Costs -Catalog prices mirror [Fireworks serverless pricing](https://docs.fireworks.ai/serverless/pricing) (standard tier). Fireworks does not return in-band billing, so Fabro reports `cost_source = "estimated"` from catalog rates. Fireworks' "Fast" model variants and Priority service tier are not included in the built-in catalog. +Catalog prices mirror [Fireworks serverless pricing](https://docs.fireworks.ai/serverless/pricing). Fireworks does not return in-band billing, so Fabro reports `cost_source = "estimated"` from catalog rates. `kimi-k3-fast` uses the published 50% Fast tier premium. Other Fast model variants and the Priority service tier are not included in the built-in catalog. ## Troubleshooting @@ -125,7 +127,7 @@ Catalog prices mirror [Fireworks serverless pricing](https://docs.fireworks.ai/s **402 / insufficient credits** — Serverless inference requires prepaid credit; check your balance in the [Fireworks billing dashboard](https://app.fireworks.ai/settings/billing). -**Unknown model** — Confirm the model's `api_id` matches a Fireworks account-scoped path exactly (`accounts/fireworks/models/...`), then run `fabro model test --model `. Remember that `GET /v1/models` only lists a featured subset, so absence from that list is not conclusive. +**Unknown model** — Confirm the model's `api_id` matches a Fireworks account-scoped model or router path exactly (`accounts/fireworks/models/...` or `accounts/fireworks/routers/...`), then run `fabro model test --model `. Remember that `GET /v1/models` only lists a featured subset, so absence from that list is not conclusive. ## Further reading diff --git a/lib/components/fabro-llm/src/client.rs b/lib/components/fabro-llm/src/client.rs index 1c0d3e46e..fc2802be1 100644 --- a/lib/components/fabro-llm/src/client.rs +++ b/lib/components/fabro-llm/src/client.rs @@ -1230,6 +1230,62 @@ base_url = "{}/v1" completion.assert_async().await; } + #[tokio::test] + async fn fireworks_routes_kimi_k3_fast_to_router_model_id() { + let upstream = httpmock::MockServer::start_async().await; + let completion = upstream + .mock_async(|when, then| { + when.method(httpmock::Method::POST) + .path("/inference/v1/chat/completions") + .header("Authorization", "Bearer test-key") + .json_body_includes(r#"{"model":"accounts/fireworks/routers/kimi-k3-fast"}"#); + then.status(200) + .header("content-type", "application/json") + .json_body(serde_json::json!({ + "id": "chatcmpl-fireworks", + "model": "accounts/fireworks/routers/kimi-k3-fast", + "choices": [{ + "message": {"role": "assistant", "content": "OK"}, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 1, + "completion_tokens": 1, + "total_tokens": 2 + } + })); + }) + .await; + let catalog = catalog_with(&format!( + r#" +[providers.fireworks] +enabled = true +base_url = "{}/inference/v1" +"#, + upstream.base_url() + )); + let fireworks = ProviderId::new("fireworks"); + let client = Client::from_credentials( + vec![ + ApiCredential::from_api_key(fireworks.clone(), "test-key".to_string(), &catalog) + .unwrap(), + ], + catalog, + ) + .await + .unwrap(); + let mut request = test_request(); + request.model = "kimi-k3-fast".to_string(); + request.provider = Some(fireworks.to_string()); + + let response = client.complete(&request).await.unwrap(); + + assert_eq!(response.text(), "OK"); + assert_eq!(response.model, "kimi-k3-fast"); + assert_eq!(response.provider, "fireworks"); + completion.assert_async().await; + } + #[tokio::test] async fn complete_stamps_estimated_cost_from_catalog() { let mut client = Client::new(HashMap::new(), None, vec![]); diff --git a/lib/components/fabro-llm/tests/integration.rs b/lib/components/fabro-llm/tests/integration.rs index 507913d1b..a9187e709 100644 --- a/lib/components/fabro-llm/tests/integration.rs +++ b/lib/components/fabro-llm/tests/integration.rs @@ -643,6 +643,17 @@ async fn fireworks_kimi_k2_7_code_deep_tool_round_trip() { assert_deep_tool_round_trip(&catalog, &provider, "kimi-k2.7-code", credential).await; } +#[fabro_macros::e2e_test(live("FIREWORKS_API_KEY"))] +async fn fireworks_kimi_k3_fast_deep_tool_round_trip() { + let api_key = std::env::var(EnvVars::FIREWORKS_API_KEY).expect("FIREWORKS_API_KEY must be set"); + let provider = ProviderId::new("fireworks"); + let catalog = enabled_provider_catalog(&provider, None); + let credential = ApiCredential::from_api_key(provider.clone(), api_key, &catalog) + .expect("Fireworks credential should resolve from the catalog"); + + assert_deep_tool_round_trip(&catalog, &provider, "kimi-k3-fast", credential).await; +} + #[fabro_macros::e2e_test(live("OPENROUTER_API_KEY"))] async fn openrouter_kimi_k3_deep_tool_round_trip() { let api_key = diff --git a/lib/foundation/fabro-model/src/catalog.rs b/lib/foundation/fabro-model/src/catalog.rs index a941cc5b7..3fdd972aa 100644 --- a/lib/foundation/fabro-model/src/catalog.rs +++ b/lib/foundation/fabro-model/src/catalog.rs @@ -3878,26 +3878,35 @@ enabled = true } #[test] - fn builtin_kimi_k3_selection_prefers_modal_then_moonshot_over_openrouter() { + fn builtin_kimi_k3_selection_follows_provider_priority() { let moonshot = ProviderId::new("moonshot"); let modal = ProviderId::new("modal"); + let fireworks = ProviderId::new("fireworks"); let openrouter = ProviderId::new("openrouter"); let catalog = Catalog::from_builtin_with_overrides(&minimal_settings( r" [providers.modal] enabled = true +[providers.fireworks] +enabled = true + [providers.openrouter] enabled = true ", )) - .expect("enabled Modal and OpenRouter overrides should build"); + .expect("enabled Kimi K3 provider overrides should build"); let selected = catalog .select( "kimi-k3", None, - &HashSet::from([moonshot.clone(), modal.clone(), openrouter.clone()]), + &HashSet::from([ + moonshot.clone(), + modal.clone(), + fireworks.clone(), + openrouter.clone(), + ]), ) .expect("Modal should win portable Kimi K3 selection"); assert_eq!(selected.provider, modal); @@ -3906,15 +3915,19 @@ enabled = true .select( "kimi-k3", None, - &HashSet::from([moonshot.clone(), openrouter.clone()]), + &HashSet::from([moonshot.clone(), fireworks.clone(), openrouter.clone()]), ) .expect("Moonshot should win when Modal is unavailable"); assert_eq!(selected.provider, moonshot); let selected = catalog - .select("kimi-k3", None, &HashSet::from([modal.clone(), openrouter])) - .expect("Modal should win gateway-only Kimi K3 selection"); - assert_eq!(selected.provider, modal); + .select( + "kimi-k3", + None, + &HashSet::from([fireworks.clone(), openrouter]), + ) + .expect("Fireworks should win when only gateway routes are available"); + assert_eq!(selected.provider, fireworks); } #[test] @@ -4028,6 +4041,30 @@ enabled = true // (id, api_id, family, context_window, max_output, vision, reasoning, // input, output, cache_read) let expected = [ + ( + "kimi-k3", + "accounts/fireworks/models/kimi-k3", + "kimi-k3", + 1_048_576, + 131_072, + true, + true, + 3.0, + 15.0, + 0.3, + ), + ( + "kimi-k3-fast", + "accounts/fireworks/routers/kimi-k3-fast", + "kimi-k3", + 1_048_576, + 131_072, + true, + true, + 4.5, + 22.5, + 0.45, + ), ( "kimi-k2.7-code", "accounts/fireworks/models/kimi-k2p7-code", @@ -4188,6 +4225,32 @@ enabled = true assert_eq!(settings.api_id, api_id, "{id}"); assert_eq!(settings.billing_policy, BillingPolicy::OpenAi, "{id}"); } + + for id in ["kimi-k3", "kimi-k3-fast"] { + let model = catalog + .get_on_provider(&fireworks, id) + .unwrap_or_else(|| panic!("Fireworks model '{id}' should be present")); + assert_eq!( + model.features.reasoning_effort, + ReasoningEffortFeature::AlwaysAdaptive, + "{id}" + ); + assert!(!model.features.sampling_params, "{id}"); + + let settings = catalog + .model_settings_on_provider(&fireworks, id) + .unwrap_or_else(|| panic!("Fireworks settings for '{id}' should be present")); + assert_eq!(settings.agent_profile, AgentProfileKind::Kimi, "{id}"); + assert_eq!( + settings.controls.reasoning_effort, + [ + ReasoningEffort::Low, + ReasoningEffort::Medium, + ReasoningEffort::High, + ], + "{id}" + ); + } } #[test] @@ -4205,6 +4268,7 @@ enabled = true for provider in [ProviderId::new("fireworks"), ProviderId::new("openrouter")] { for id in [ + "kimi-k3", "kimi-k2.6", "deepseek-v4-pro", "deepseek-v4-flash", diff --git a/lib/foundation/fabro-model/src/catalog/providers/fireworks.toml b/lib/foundation/fabro-model/src/catalog/providers/fireworks.toml index 553fd58ed..48024f3ea 100644 --- a/lib/foundation/fabro-model/src/catalog/providers/fireworks.toml +++ b/lib/foundation/fabro-model/src/catalog/providers/fireworks.toml @@ -24,8 +24,62 @@ credentials = ["env:FIREWORKS_API_KEY", "vault:FIREWORKS_API_KEY"] # # Prompt caching is automatic prefix caching (no cache_control breakpoints); # serverless responses report prompt_tokens_details.cached_tokens in the -# usage body. Costs below are from docs.fireworks.ai/serverless/pricing -# (standard tier), verified 2026-07-31. +# usage body. Costs below are from docs.fireworks.ai/serverless/pricing, +# verified 2026-08-04. Rows use standard-tier prices unless noted otherwise. + +[providers.fireworks.models."kimi-k3"] +api_id = "accounts/fireworks/models/kimi-k3" +display_name = "Kimi K3" +family = "kimi-k3" +agent_profile = "kimi" + +[providers.fireworks.models."kimi-k3".limits] +context_window = 1048576 +max_output = 131072 + +[providers.fireworks.models."kimi-k3".features] +tools = true +vision = true +reasoning = true +reasoning_effort = "always_adaptive" +prompt_cache = true +sampling_params = false + +[providers.fireworks.models."kimi-k3".controls] +reasoning_effort = ["low", "medium", "high"] + +[providers.fireworks.models."kimi-k3".costs] +input_cost_per_mtok = 3.0 +output_cost_per_mtok = 15.0 +cache_input_cost_per_mtok = 0.3 + +# Fireworks exposes the Fast tier through a separate router model ID. Its +# published prices are 50% above the standard Kimi K3 rates. +[providers.fireworks.models."kimi-k3-fast"] +api_id = "accounts/fireworks/routers/kimi-k3-fast" +display_name = "Kimi K3 Fast" +family = "kimi-k3" +agent_profile = "kimi" + +[providers.fireworks.models."kimi-k3-fast".limits] +context_window = 1048576 +max_output = 131072 + +[providers.fireworks.models."kimi-k3-fast".features] +tools = true +vision = true +reasoning = true +reasoning_effort = "always_adaptive" +prompt_cache = true +sampling_params = false + +[providers.fireworks.models."kimi-k3-fast".controls] +reasoning_effort = ["low", "medium", "high"] + +[providers.fireworks.models."kimi-k3-fast".costs] +input_cost_per_mtok = 4.5 +output_cost_per_mtok = 22.5 +cache_input_cost_per_mtok = 0.45 [providers.fireworks.models."kimi-k2.7-code"] api_id = "accounts/fireworks/models/kimi-k2p7-code"