mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-06 02:48:25 +00:00
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:
commit
120f0fa80b
5 changed files with 202 additions and 15 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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![]);
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue