Merge pull request #725 from fabro-sh/codex/fireworks-kimi-k3-fast

Add Fireworks Kimi K3 and Kimi K3 Fast support
This commit is contained in:
Bryan Helmkamp 2026-08-04 14:03:23 -04:00 • committed by GitHub
commit 120f0fa80b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 202 additions and 15 deletions

View file

@ -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 <fabro-model-id>`. 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 <fabro-model-id>`. Remember that `GET /v1/models` only lists a featured subset, so absence from that list is not conclusive.
## Further reading

View file

@ -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![]);

View file

@ -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 =

View file

@ -3954,26 +3954,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);
@ -3982,15 +3991,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]
@ -4104,6 +4117,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",
@ -4264,6 +4301,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]
@ -4281,6 +4344,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",

View file

@ -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"