mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
feat(llm): add direct DeepSeek provider
This commit is contained in:
parent
8864e0f0cf
commit
f613821bfb
15 changed files with 329 additions and 2 deletions
|
|
@ -1,6 +1,7 @@
|
|||
ANTHROPIC_API_KEY=
|
||||
BRAVE_SEARCH_API_KEY=
|
||||
DAYTONA_API_KEY=
|
||||
DEEPSEEK_API_KEY=
|
||||
FIREWORKS_API_KEY=
|
||||
GEMINI_API_KEY=
|
||||
INCEPTION_API_KEY=
|
||||
|
|
|
|||
3
apps/fabro-web/public/images/providers/deepseek.svg
Normal file
3
apps/fabro-web/public/images/providers/deepseek.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M35.6638 9.91965C35.3251 9.75432 35.1785 10.0703 34.9811 10.2316C34.9131 10.2836 34.8558 10.3516 34.7985 10.413C34.3025 10.9423 33.7238 11.289 32.9678 11.2476C31.8625 11.1863 30.9186 11.533 30.0839 12.3783C29.9066 11.3356 29.3173 10.7143 28.4213 10.3143C27.9519 10.1063 27.4773 9.89965 27.148 9.44766C26.9186 9.12633 26.856 8.76767 26.7413 8.41568C26.668 8.20235 26.5946 7.98502 26.3506 7.94902C26.084 7.90769 25.98 8.13035 25.876 8.31702C25.4587 9.07967 25.2973 9.91965 25.3133 10.7703C25.3493 12.6849 26.1573 14.2102 27.764 15.2942C27.9466 15.4182 27.9933 15.5435 27.9359 15.7249C27.8266 16.0982 27.696 16.4609 27.5813 16.8355C27.508 17.0742 27.3986 17.1248 27.1426 17.0222C26.2777 16.6504 25.4919 16.1164 24.828 15.4489C23.6854 14.3449 22.6534 13.1263 21.3654 12.1716C21.067 11.9511 20.7606 11.7416 20.4468 11.5436C19.1335 10.2676 20.6201 9.21967 20.9641 9.09567C21.3241 8.965 21.0881 8.51968 19.9254 8.52501C18.7628 8.53035 17.6988 8.91834 16.3428 9.43699C16.1413 9.51421 15.934 9.57529 15.7229 9.61966C14.4557 9.38091 13.1598 9.33506 11.8789 9.48366C9.36565 9.76365 7.35902 10.953 5.88305 12.9809C4.10975 15.4182 3.69243 18.1888 4.20308 21.0768C4.74041 24.122 6.29504 26.6433 8.683 28.6139C11.1603 30.6579 14.0122 31.6592 17.2668 31.4672C19.2428 31.3539 21.4441 31.0886 23.9254 28.9873C24.552 29.2993 25.208 29.4233 26.2986 29.5166C27.1386 29.5953 27.9466 29.4766 28.5719 29.3459C29.5519 29.1379 29.4839 28.23 29.1306 28.0646C26.2573 26.726 26.888 27.2713 26.3133 26.83C27.7746 25.102 29.9746 23.3074 30.8359 17.4928C30.9026 17.0302 30.8452 16.7395 30.8359 16.3662C30.8306 16.1395 30.8826 16.0502 31.1426 16.0249C31.8639 15.95 32.5637 15.7349 33.2025 15.3915C35.0638 14.3742 35.8158 12.7049 35.9931 10.7023C36.0198 10.3956 35.9878 10.081 35.6638 9.91965ZM19.4414 27.9433C16.6562 25.754 15.3055 25.0327 14.7482 25.0634C14.2256 25.0954 14.3202 25.6913 14.4349 26.0807C14.5549 26.4647 14.7109 26.7286 14.9295 27.066C15.0815 27.2886 15.1855 27.6206 14.7789 27.87C13.8816 28.4246 12.3229 27.6833 12.2496 27.6473C10.435 26.578 8.91632 25.1673 7.84834 23.2381C6.81637 21.3808 6.21638 19.3888 6.11771 17.2622C6.09105 16.7475 6.24171 16.5662 6.7537 16.4729C7.42583 16.3442 8.11451 16.3267 8.79233 16.4209C11.6349 16.8368 14.0536 18.1075 16.0828 20.1194C17.2402 21.2661 18.1161 22.6354 19.0188 23.974C19.9788 25.3953 21.0108 26.75 22.3254 27.8593C22.7894 28.2486 23.1587 28.5446 23.5134 28.7619C22.4441 28.8819 20.6601 28.9086 19.4414 27.9433ZM20.7748 19.3568C20.7745 19.2906 20.7904 19.2253 20.8211 19.1666C20.8517 19.1078 20.8962 19.0575 20.9507 19.0198C21.0052 18.9821 21.068 18.9583 21.1337 18.9503C21.1995 18.9424 21.2662 18.9505 21.3281 18.9741C21.407 19.0024 21.475 19.0546 21.5228 19.1235C21.5706 19.1923 21.5958 19.2743 21.5947 19.3581C21.5949 19.4123 21.5843 19.4659 21.5636 19.5159C21.5428 19.5659 21.5123 19.6113 21.4738 19.6494C21.4354 19.6875 21.3897 19.7176 21.3395 19.7378C21.2893 19.7581 21.2356 19.7682 21.1814 19.7675C21.1277 19.7676 21.0745 19.7571 21.0248 19.7365C20.9752 19.7158 20.9302 19.6855 20.8925 19.6473C20.8548 19.609 20.825 19.5636 20.805 19.5138C20.785 19.4639 20.7739 19.4105 20.7748 19.3568ZM24.9213 21.4848C24.6547 21.5928 24.3893 21.6861 24.1347 21.6981C23.7516 21.7114 23.3756 21.5918 23.0707 21.3594C22.7054 21.0528 22.4441 20.8821 22.3347 20.3488C22.297 20.0881 22.3042 19.823 22.3561 19.5648C22.4494 19.1288 22.3454 18.8488 22.0374 18.5955C21.7881 18.3875 21.4694 18.3302 21.1201 18.3302C21.0005 18.3232 20.8843 18.2875 20.7814 18.2262C20.6348 18.1542 20.5148 17.9728 20.6294 17.7488C20.6668 17.6768 20.8428 17.5008 20.8854 17.4688C21.3601 17.1995 21.9081 17.2875 22.4134 17.4902C22.8827 17.6822 23.2374 18.0342 23.748 18.5328C24.2694 19.1341 24.364 19.3008 24.6613 19.7515C24.896 20.1048 25.1093 20.4674 25.2547 20.8821C25.344 21.1421 25.2293 21.3541 24.9213 21.4848Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -388,6 +388,7 @@ fabro secret set GEMINI_API_KEY AI...
|
|||
| `MINIMAX_API_KEY` | Minimax |
|
||||
| `INCEPTION_API_KEY` | Inception (Mercury) |
|
||||
| `POOLSIDE_API_KEY` | Poolside (Laguna) |
|
||||
| `DEEPSEEK_API_KEY` | DeepSeek |
|
||||
| `OPENROUTER_API_KEY` | OpenRouter (when enabled) |
|
||||
| `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` | Modal (when enabled) |
|
||||
| `FIREWORKS_API_KEY` | Fireworks AI (when enabled) |
|
||||
|
|
|
|||
|
|
@ -59,13 +59,15 @@ Fabro performs this selection once when creating a run and persists the chosen p
|
|||
| `gemini-3.1-flash-lite` | gemini | `gemini-flash-lite`, `gemini-3.1-flash-lite-preview` | 1M | $0.25 / $1.50 | 200 tok/s |
|
||||
| `kimi-k2.5` | kimi | | 262K | $0.60 / $3.00 | 50 tok/s |
|
||||
| `kimi-k3` | kimi | `kimi` | 1M | $3.00 / $15.00 | n/a |
|
||||
| `deepseek-v4-flash` | deepseek | `deepseek`, `deepseek-v4`, `deepseek-flash` | 1,048,576 | $0.14 / $0.28 | n/a |
|
||||
| `deepseek-v4-pro` | deepseek | | 1,048,576 | $0.435 / $0.87 | n/a |
|
||||
| `laguna-s-2.1` | poolside | `laguna`, `laguna-s` | 1M | $0.10 / $0.20 | n/a |
|
||||
| `laguna-xs-2.1` | poolside | `laguna-xs` | 262K | $0.10 / $0.20 | n/a |
|
||||
| `glm-5.2` | zai | `glm`, `glm5`, `glm52`, `glm5.2` | 1M | $1.40 / $4.40 | n/a |
|
||||
| `minimax-m2.5` | minimax | `minimax` | 197K | $0.30 / $1.20 | 45 tok/s |
|
||||
| `mercury-2` | inception | `mercury` | 131K | $0.25 / $0.75 | 1000 tok/s |
|
||||
|
||||
Each provider requires its own API key. Server-backed workflows read provider credentials from the server vault (for example `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, or `POOLSIDE_API_KEY` set with `fabro secret set` or `fabro provider login`). Standalone SDK/CLI flows can opt into env-backed credential sources explicitly. See the [Quick Start](/getting-started/quick-start) for setup.
|
||||
Each provider requires its own API key. Server-backed workflows read provider credentials from the server vault (for example `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `DEEPSEEK_API_KEY`, or `POOLSIDE_API_KEY` set with `fabro secret set` or `fabro provider login`). Standalone SDK/CLI flows can opt into env-backed credential sources explicitly. See the [Quick Start](/getting-started/quick-start) for setup.
|
||||
|
||||
Claude Fable 5 is available as an explicit model but is not the default Anthropic model. If Fable refuses a request, Fabro reports the refusal as a content-filter LLM error and applies the configured `run.model.fallbacks` chain when one is present.
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@
|
|||
"integrations/daytona",
|
||||
"integrations/litellm",
|
||||
"integrations/bedrock",
|
||||
"integrations/deepseek",
|
||||
"integrations/poolside",
|
||||
"integrations/openrouter",
|
||||
"integrations/modal",
|
||||
|
|
|
|||
109
docs/public/integrations/deepseek.mdx
Normal file
109
docs/public/integrations/deepseek.mdx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
---
|
||||
title: "DeepSeek"
|
||||
description: "Run DeepSeek V4 Flash and Pro through DeepSeek's direct API or supported gateways"
|
||||
---
|
||||
|
||||
[DeepSeek](https://www.deepseek.com/) provides an OpenAI-compatible API for DeepSeek V4. Fabro includes direct access to V4 Flash and V4 Pro. The same Fabro model IDs also work through the optional Fireworks AI and OpenRouter providers.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A [DeepSeek Platform](https://platform.deepseek.com/) account
|
||||
- A DeepSeek API key from [platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys)
|
||||
- A running Fabro server
|
||||
|
||||
## Configure direct access
|
||||
|
||||
The direct `deepseek` provider is enabled in the built-in catalog. Store its key in the target Fabro server vault:
|
||||
|
||||
```bash
|
||||
fabro provider login --provider deepseek
|
||||
|
||||
# For a non-default remote server:
|
||||
fabro provider login --server https://your-fabro.example --provider deepseek
|
||||
|
||||
# Or set the vault token directly:
|
||||
fabro secret set DEEPSEEK_API_KEY
|
||||
fabro secret --server https://your-fabro.example set DEEPSEEK_API_KEY
|
||||
```
|
||||
|
||||
Standalone SDK usage outside a Fabro server can use an env-backed credential source explicitly:
|
||||
|
||||
```bash
|
||||
export DEEPSEEK_API_KEY=<api-key>
|
||||
```
|
||||
|
||||
Fabro sends bearer-authenticated Chat Completions requests to `https://api.deepseek.com`.
|
||||
|
||||
## Included models
|
||||
|
||||
| Fabro model ID | DeepSeek API model ID | Context | Max output | Role |
|
||||
|---|---|---:|---:|---|
|
||||
| `deepseek-v4-flash` | `deepseek-v4-flash` | 1,048,576 | 384,000 | Provider default, small default, and connectivity probe; aliases `deepseek`, `deepseek-v4`, `deepseek-flash` |
|
||||
| `deepseek-v4-pro` | `deepseek-v4-pro` | 1,048,576 | 384,000 | Higher-capability V4 model |
|
||||
|
||||
Both models support text input, tool calling, native reasoning, streaming, JSON output, and automatic prompt caching. They do not support image input. Thinking mode is enabled by default.
|
||||
|
||||
## Use DeepSeek models
|
||||
|
||||
```bash
|
||||
fabro model list --provider deepseek
|
||||
fabro model test --provider deepseek --model deepseek-v4-flash --deep
|
||||
fabro run workflow.fabro --provider deepseek --model deepseek
|
||||
```
|
||||
|
||||
In workflow stylesheets:
|
||||
|
||||
```dot title="workflow.fabro"
|
||||
digraph Example {
|
||||
graph [
|
||||
model_stylesheet="
|
||||
* { model: deepseek-v4-flash; }
|
||||
.difficult { model: deepseek-v4-pro; }
|
||||
"
|
||||
]
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
work [label="Implement", prompt="Implement and verify the requested change."]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
start -> work -> exit
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt caching and pricing
|
||||
|
||||
DeepSeek applies prefix caching automatically. Fabro reads DeepSeek's `prompt_cache_hit_tokens` usage field and reports cache-read tokens separately from uncached input tokens.
|
||||
|
||||
The built-in catalog uses DeepSeek's published prices per million tokens:
|
||||
|
||||
| Model | Uncached input | Cache hit | Output |
|
||||
|---|---:|---:|---:|
|
||||
| `deepseek-v4-flash` | $0.14 | $0.0028 | $0.28 |
|
||||
| `deepseek-v4-pro` | $0.435 | $0.003625 | $0.87 |
|
||||
|
||||
DeepSeek does not return an in-band dollar cost. Fabro calculates an estimated cost from these catalog rates and the reported token buckets.
|
||||
|
||||
## Use a gateway
|
||||
|
||||
The `deepseek-v4-flash`, `deepseek-v4-pro`, `deepseek`, `deepseek-v4`, and `deepseek-flash` selectors are portable across direct DeepSeek, Fireworks AI, and OpenRouter routes. Use `--provider` or a provider-qualified model selector when you need a specific route.
|
||||
|
||||
See the [Fireworks AI integration](/integrations/fireworks) and [OpenRouter integration](/integrations/openrouter) for gateway setup and provider-specific pricing.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"No API key configured"** — Store `DEEPSEEK_API_KEY` on the target server with `fabro provider login --provider deepseek`. The server runtime resolves the key from its vault, not from process env.
|
||||
|
||||
**Unknown model** — Use `deepseek-v4-flash` or `deepseek-v4-pro`. The retired `deepseek-chat` and `deepseek-reasoner` API IDs are not in the Fabro catalog.
|
||||
|
||||
**A tool continuation returns HTTP 400** — Keep the assistant thinking content in conversation history. Fabro does this automatically when it replays tool-call turns.
|
||||
|
||||
## Further reading
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="DeepSeek API" icon="code" href="https://api-docs.deepseek.com/">
|
||||
Official authentication, endpoints, and API reference.
|
||||
</Card>
|
||||
<Card title="Models and pricing" icon="money-bill" href="https://api-docs.deepseek.com/quick_start/pricing/">
|
||||
Official limits, features, and token prices.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
|
@ -371,6 +371,7 @@ For env-backed usage, `EnvCredentialSource` checks for API key environment varia
|
|||
| `MINIMAX_API_KEY` | Minimax |
|
||||
| `INCEPTION_API_KEY` | Inception |
|
||||
| `POOLSIDE_API_KEY` | Poolside |
|
||||
| `DEEPSEEK_API_KEY` | DeepSeek |
|
||||
| `OPENROUTER_API_KEY` | OpenRouter, when enabled in settings |
|
||||
|
||||
The first provider registered becomes the default. Provider base URLs come from the model catalog. For vault-backed usage inside Fabro, use `fabro_auth::VaultCredentialSource` instead.
|
||||
|
|
|
|||
|
|
@ -3521,6 +3521,7 @@ root = "{}"
|
|||
assert!(ids.contains(&ProviderId::new("inception")));
|
||||
assert!(ids.contains(&ProviderId::new("venice")));
|
||||
assert!(ids.contains(&ProviderId::new("poolside")));
|
||||
assert!(ids.contains(&ProviderId::new("deepseek")));
|
||||
assert!(!ids.contains(&ProviderId::new("fireworks")));
|
||||
assert!(!ids.contains(&ProviderId::new("ollama")));
|
||||
assert!(!ids.contains(&ProviderId::new("litellm")));
|
||||
|
|
|
|||
|
|
@ -303,6 +303,8 @@ mod tests {
|
|||
("claude-sonnet-4-5", "claude-sonnet-4-5", T::Anthropic, C::AnthropicMessages, B::Anthropic, P::Anthropic),
|
||||
("claude-sonnet-4-6", "claude-sonnet-4-6", T::Anthropic, C::AnthropicMessages, B::Anthropic, P::Anthropic),
|
||||
("claude-sonnet-5", "claude-sonnet-5", T::Anthropic, C::AnthropicMessages, B::Anthropic, P::Claude5),
|
||||
("deepseek-v4-flash", "deepseek-v4-flash", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
|
||||
("deepseek-v4-pro", "deepseek-v4-pro", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
|
||||
("gemini-3-flash-preview", "gemini-3-flash-preview", T::Gemini, C::GeminiGenerate, B::Gemini, P::Gemini),
|
||||
("gemini-3.1-flash-lite", "gemini-3.1-flash-lite", T::Gemini, C::GeminiGenerate, B::Gemini, P::Gemini),
|
||||
("gemini-3.1-pro-preview", "gemini-3.1-pro-preview", T::Gemini, C::GeminiGenerate, B::Gemini, P::Gemini),
|
||||
|
|
|
|||
|
|
@ -300,6 +300,10 @@ pub(super) struct ApiUsage {
|
|||
pub cost: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub prompt_tokens_details: Option<PromptTokensDetails>,
|
||||
/// DeepSeek-specific top-level count of prompt tokens served from its
|
||||
/// automatic context cache.
|
||||
#[serde(default)]
|
||||
pub prompt_cache_hit_tokens: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub completion_tokens_details: Option<CompletionTokensDetails>,
|
||||
}
|
||||
|
|
@ -329,6 +333,7 @@ impl ApiUsage {
|
|||
.prompt_tokens_details
|
||||
.as_ref()
|
||||
.and_then(|d| d.cached_tokens)
|
||||
.or(self.prompt_cache_hit_tokens)
|
||||
.unwrap_or(0);
|
||||
let cache_write_detail = self
|
||||
.prompt_tokens_details
|
||||
|
|
@ -538,6 +543,23 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_counts_accept_deepseek_cache_hit_field() {
|
||||
let usage: ApiUsage = serde_json::from_value(serde_json::json!({
|
||||
"prompt_tokens": 53,
|
||||
"completion_tokens": 11,
|
||||
"prompt_cache_hit_tokens": 41
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(usage.token_counts(), TokenCounts {
|
||||
input_tokens: 12,
|
||||
output_tokens: 11,
|
||||
cache_read_tokens: 41,
|
||||
..TokenCounts::default()
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_accepts_provider_and_openrouter_spellings() {
|
||||
let provider_response: ApiResponse = serde_json::from_value(serde_json::json!({
|
||||
|
|
|
|||
|
|
@ -599,6 +599,28 @@ async fn fireworks_complete() {
|
|||
assert_eq!(response.provider, "fireworks");
|
||||
}
|
||||
|
||||
#[fabro_macros::e2e_test(live("DEEPSEEK_API_KEY"))]
|
||||
async fn deepseek_complete() {
|
||||
let api_key = std::env::var(EnvVars::DEEPSEEK_API_KEY).expect("DEEPSEEK_API_KEY must be set");
|
||||
let adapter =
|
||||
OpenAiCompatibleAdapter::new(api_key, "https://api.deepseek.com").with_name("deepseek");
|
||||
let request = Request {
|
||||
// Thinking mode is enabled by default and shares this budget with the
|
||||
// visible answer.
|
||||
max_tokens: Some(1024),
|
||||
..make_request("deepseek-v4-flash")
|
||||
};
|
||||
let response = adapter.complete(&request).await.unwrap();
|
||||
|
||||
assert!(
|
||||
!response.text().is_empty(),
|
||||
"response text should not be empty"
|
||||
);
|
||||
assert!(response.usage.input_tokens > 0);
|
||||
assert!(response.usage.output_tokens > 0 || response.usage.reasoning_tokens > 0);
|
||||
assert_eq!(response.provider, "deepseek");
|
||||
}
|
||||
|
||||
#[fabro_macros::e2e_test(live("FIREWORKS_API_KEY"))]
|
||||
async fn fireworks_kimi_k2_7_code_deep_tool_round_trip() {
|
||||
let api_key = std::env::var(EnvVars::FIREWORKS_API_KEY).expect("FIREWORKS_API_KEY must be set");
|
||||
|
|
|
|||
|
|
@ -3028,6 +3028,77 @@ enabled = true
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_deepseek_provider_routes_v4_models() {
|
||||
let deepseek = ProviderId::new("deepseek");
|
||||
let catalog = Catalog::builtin();
|
||||
let provider = catalog
|
||||
.provider(&deepseek)
|
||||
.expect("DeepSeek provider should be active");
|
||||
|
||||
assert_eq!(provider.adapter, AdapterKind::OpenAiCompatible);
|
||||
assert_eq!(provider.codec, CodecKind::OpenAiCompatible);
|
||||
assert_eq!(provider.billing_policy, BillingPolicy::OpenAi);
|
||||
assert_eq!(
|
||||
provider.base_url.as_deref(),
|
||||
Some("https://api.deepseek.com")
|
||||
);
|
||||
assert_eq!(provider.priority, 75);
|
||||
assert_eq!(provider.auth.as_ref().unwrap().credentials, vec![
|
||||
CredentialRef::Env("DEEPSEEK_API_KEY".to_string()),
|
||||
CredentialRef::Vault("DEEPSEEK_API_KEY".to_string()),
|
||||
]);
|
||||
assert_eq!(
|
||||
catalog
|
||||
.default_for_provider(&deepseek)
|
||||
.map(|model| model.id.as_str()),
|
||||
Some("deepseek-v4-flash")
|
||||
);
|
||||
assert_eq!(
|
||||
catalog
|
||||
.small_default_for_provider(&deepseek)
|
||||
.map(|model| model.id.as_str()),
|
||||
Some("deepseek-v4-flash")
|
||||
);
|
||||
assert_eq!(
|
||||
catalog
|
||||
.probe_for_provider(&deepseek)
|
||||
.map(|model| model.id.as_str()),
|
||||
Some("deepseek-v4-flash")
|
||||
);
|
||||
|
||||
let expected = [
|
||||
("deepseek-v4-flash", 0.14, 0.28, 0.0028),
|
||||
("deepseek-v4-pro", 0.435, 0.87, 0.003625),
|
||||
];
|
||||
for (id, input, output, cache_read) in expected {
|
||||
let model = catalog
|
||||
.get_on_provider(&deepseek, id)
|
||||
.unwrap_or_else(|| panic!("DeepSeek model '{id}' should be present"));
|
||||
assert_eq!(model.family, "deepseek-v4", "{id}");
|
||||
assert_eq!(model.limits.context_window, 1_048_576, "{id}");
|
||||
assert_eq!(model.limits.max_output, Some(384_000), "{id}");
|
||||
assert!(model.features.tools, "{id}");
|
||||
assert!(!model.features.vision, "{id}");
|
||||
assert!(model.features.reasoning, "{id}");
|
||||
assert!(model.features.prompt_cache, "{id}");
|
||||
assert!(!model.features.sampling_params, "{id}");
|
||||
assert_eq!(model.costs.input_cost_per_mtok, Some(input), "{id}");
|
||||
assert_eq!(model.costs.output_cost_per_mtok, Some(output), "{id}");
|
||||
assert_eq!(
|
||||
model.costs.cache_input_cost_per_mtok,
|
||||
Some(cache_read),
|
||||
"{id}"
|
||||
);
|
||||
|
||||
let settings = catalog
|
||||
.model_settings_on_provider(&deepseek, id)
|
||||
.unwrap_or_else(|| panic!("DeepSeek settings for '{id}' should be present"));
|
||||
assert_eq!(settings.api_id, id, "{id}");
|
||||
assert!(settings.reasoning_by_default, "{id}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_openrouter_provider_is_opt_in() {
|
||||
let openrouter = ProviderId::new("openrouter");
|
||||
|
|
@ -4011,7 +4082,7 @@ enabled = true
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_fireworks_shared_slugs_are_portable_with_openrouter() {
|
||||
fn builtin_deepseek_shared_slugs_are_portable_across_providers() {
|
||||
let catalog = Catalog::from_builtin_with_overrides(&minimal_settings(
|
||||
r"
|
||||
[providers.fireworks]
|
||||
|
|
@ -4037,7 +4108,20 @@ enabled = true
|
|||
assert_eq!(model.id, id, "{provider}/{id}");
|
||||
assert_eq!(model.provider, provider, "{provider}/{id}");
|
||||
}
|
||||
}
|
||||
|
||||
for provider in [
|
||||
ProviderId::new("deepseek"),
|
||||
ProviderId::new("fireworks"),
|
||||
ProviderId::new("openrouter"),
|
||||
] {
|
||||
for id in ["deepseek-v4-pro", "deepseek-v4-flash"] {
|
||||
let model = catalog
|
||||
.get_on_provider(&provider, id)
|
||||
.unwrap_or_else(|| panic!("'{id}' should resolve on provider '{provider}'"));
|
||||
assert_eq!(model.id, id, "{provider}/{id}");
|
||||
assert_eq!(model.provider, provider, "{provider}/{id}");
|
||||
}
|
||||
for alias in ["deepseek", "deepseek-v4", "deepseek-flash"] {
|
||||
let model = catalog
|
||||
.resolve_on_provider(&provider, alias)
|
||||
|
|
@ -4048,6 +4132,19 @@ enabled = true
|
|||
assert_eq!(model.provider, provider, "{provider}/{alias}");
|
||||
}
|
||||
}
|
||||
|
||||
let selected = catalog
|
||||
.select(
|
||||
"deepseek",
|
||||
None,
|
||||
&HashSet::from([
|
||||
ProviderId::new("deepseek"),
|
||||
ProviderId::new("fireworks"),
|
||||
ProviderId::new("openrouter"),
|
||||
]),
|
||||
)
|
||||
.expect("direct DeepSeek should win portable DeepSeek selection");
|
||||
assert_eq!(selected.provider, ProviderId::new("deepseek"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
[providers.deepseek]
|
||||
display_name = "DeepSeek"
|
||||
adapter = "openai_compatible"
|
||||
api_key_url = "https://platform.deepseek.com/api_keys"
|
||||
base_url = "https://api.deepseek.com"
|
||||
priority = 75
|
||||
|
||||
[providers.deepseek.auth]
|
||||
credentials = ["env:DEEPSEEK_API_KEY", "vault:DEEPSEEK_API_KEY"]
|
||||
|
||||
# DeepSeek V4 uses thinking mode by default. The API accepts sampling
|
||||
# parameters in that mode but ignores them, so Fabro omits those parameters.
|
||||
# Prompt caching is automatic and usage reports prompt_cache_hit_tokens.
|
||||
# Prices are from api-docs.deepseek.com/quick_start/pricing, verified
|
||||
# 2026-07-31.
|
||||
|
||||
[providers.deepseek.models."deepseek-v4-flash"]
|
||||
display_name = "DeepSeek V4 Flash"
|
||||
family = "deepseek-v4"
|
||||
aliases = ["deepseek-v4", "deepseek", "deepseek-flash"]
|
||||
default = true
|
||||
small_default = true
|
||||
probe = true
|
||||
|
||||
[providers.deepseek.models."deepseek-v4-flash".limits]
|
||||
context_window = 1048576
|
||||
max_output = 384000
|
||||
|
||||
[providers.deepseek.models."deepseek-v4-flash".features]
|
||||
tools = true
|
||||
vision = false
|
||||
reasoning = true
|
||||
reasoning_by_default = true
|
||||
prompt_cache = true
|
||||
sampling_params = false
|
||||
|
||||
[providers.deepseek.models."deepseek-v4-flash".costs]
|
||||
input_cost_per_mtok = 0.14
|
||||
output_cost_per_mtok = 0.28
|
||||
cache_input_cost_per_mtok = 0.0028
|
||||
|
||||
[providers.deepseek.models."deepseek-v4-pro"]
|
||||
display_name = "DeepSeek V4 Pro"
|
||||
family = "deepseek-v4"
|
||||
|
||||
[providers.deepseek.models."deepseek-v4-pro".limits]
|
||||
context_window = 1048576
|
||||
max_output = 384000
|
||||
|
||||
[providers.deepseek.models."deepseek-v4-pro".features]
|
||||
tools = true
|
||||
vision = false
|
||||
reasoning = true
|
||||
reasoning_by_default = true
|
||||
prompt_cache = true
|
||||
sampling_params = false
|
||||
|
||||
[providers.deepseek.models."deepseek-v4-pro".costs]
|
||||
input_cost_per_mtok = 0.435
|
||||
output_cost_per_mtok = 0.87
|
||||
cache_input_cost_per_mtok = 0.003625
|
||||
|
|
@ -48,6 +48,7 @@ impl EnvVars {
|
|||
pub const BEDROCK_API_KEY: &'static str = "BEDROCK_API_KEY";
|
||||
pub const BRAVE_SEARCH_API_KEY: &'static str = "BRAVE_SEARCH_API_KEY";
|
||||
pub const CHATGPT_ACCOUNT_ID: &'static str = "CHATGPT_ACCOUNT_ID";
|
||||
pub const DEEPSEEK_API_KEY: &'static str = "DEEPSEEK_API_KEY";
|
||||
pub const FIREWORKS_API_KEY: &'static str = "FIREWORKS_API_KEY";
|
||||
pub const GEMINI_API_KEY: &'static str = "GEMINI_API_KEY";
|
||||
pub const GEMINI_BASE_URL: &'static str = "GEMINI_BASE_URL";
|
||||
|
|
@ -198,6 +199,7 @@ mod tests {
|
|||
EnvVars::BEDROCK_API_KEY,
|
||||
EnvVars::BRAVE_SEARCH_API_KEY,
|
||||
EnvVars::CHATGPT_ACCOUNT_ID,
|
||||
EnvVars::DEEPSEEK_API_KEY,
|
||||
EnvVars::FIREWORKS_API_KEY,
|
||||
EnvVars::GEMINI_API_KEY,
|
||||
EnvVars::GEMINI_BASE_URL,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const OPTIONAL_VAULT_SECRETS: &[&str] = &[
|
|||
EnvVars::AWS_BEARER_TOKEN_BEDROCK,
|
||||
EnvVars::BEDROCK_API_KEY,
|
||||
EnvVars::BRAVE_SEARCH_API_KEY,
|
||||
EnvVars::DEEPSEEK_API_KEY,
|
||||
EnvVars::FABRO_SLACK_APP_TOKEN,
|
||||
EnvVars::FABRO_SLACK_BOT_TOKEN,
|
||||
EnvVars::FIREWORKS_API_KEY,
|
||||
|
|
@ -92,6 +93,7 @@ mod tests {
|
|||
EnvVars::ANTHROPIC_API_KEY,
|
||||
EnvVars::AWS_BEARER_TOKEN_BEDROCK,
|
||||
EnvVars::BEDROCK_API_KEY,
|
||||
EnvVars::DEEPSEEK_API_KEY,
|
||||
EnvVars::FIREWORKS_API_KEY,
|
||||
EnvVars::GEMINI_API_KEY,
|
||||
EnvVars::INCEPTION_API_KEY,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue