deepseek: Add deepseek-v4-pro & deepseek-v4-flash (#54731)

reference: https://api-docs.deepseek.com/

Release Notes:

- Added deepseek-v4-pro and deepseek-v4-flash models

---------

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: MrSubidubi <dev@bahn.sh>
This commit is contained in:
Xiaobo Liu 2026-04-27 21:50:00 +08:00 committed by GitHub
parent 0e5da4cbd4
commit 4c27cee963
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 33 deletions

View file

@ -48,11 +48,11 @@ impl From<Role> for String {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub enum Model {
#[serde(rename = "deepseek-chat")]
#[serde(rename = "deepseek-v4-flash")]
V4Flash,
#[serde(rename = "deepseek-v4-pro")]
#[default]
Chat,
#[serde(rename = "deepseek-reasoner")]
Reasoner,
V4Pro,
#[serde(rename = "custom")]
Custom {
name: String,
@ -65,29 +65,29 @@ pub enum Model {
impl Model {
pub fn default_fast() -> Self {
Model::Chat
Model::V4Flash
}
pub fn from_id(id: &str) -> Result<Self> {
match id {
"deepseek-chat" => Ok(Self::Chat),
"deepseek-reasoner" => Ok(Self::Reasoner),
"deepseek-v4-flash" => Ok(Self::V4Flash),
"deepseek-v4-pro" => Ok(Self::V4Pro),
_ => anyhow::bail!("invalid model id {id}"),
}
}
pub fn id(&self) -> &str {
match self {
Self::Chat => "deepseek-chat",
Self::Reasoner => "deepseek-reasoner",
Self::V4Flash => "deepseek-v4-flash",
Self::V4Pro => "deepseek-v4-pro",
Self::Custom { name, .. } => name,
}
}
pub fn display_name(&self) -> &str {
match self {
Self::Chat => "DeepSeek Chat",
Self::Reasoner => "DeepSeek Reasoner",
Self::V4Flash => "DeepSeek V4 Flash",
Self::V4Pro => "DeepSeek V4 Pro",
Self::Custom {
name, display_name, ..
} => display_name.as_ref().unwrap_or(name).as_str(),
@ -96,16 +96,14 @@ impl Model {
pub fn max_token_count(&self) -> u64 {
match self {
Self::Chat | Self::Reasoner => 128_000,
Self::V4Flash | Self::V4Pro => 1_000_000,
Self::Custom { max_tokens, .. } => *max_tokens,
}
}
pub fn max_output_tokens(&self) -> Option<u64> {
match self {
// Their API treats this max against the context window, which means we hit the limit a lot
// Using the default value of None in the API instead
Self::Chat | Self::Reasoner => None,
Self::V4Flash | Self::V4Pro => Some(384_000),
Self::Custom {
max_output_tokens, ..
} => *max_output_tokens,
@ -123,11 +121,35 @@ pub struct Request {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thinking: Option<Thinking>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<ReasoningEffort>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolDefinition>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Thinking {
#[serde(rename = "type")]
pub kind: ThinkingType,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ThinkingType {
Enabled,
Disabled,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningEffort {
High,
Max,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseFormat {

View file

@ -1,5 +1,5 @@
use anyhow::{Result, anyhow};
use collections::{BTreeMap, HashMap};
use collections::{HashMap, IndexMap};
use credentials_provider::CredentialsProvider;
use deepseek::DEEPSEEK_API_URL;
@ -9,10 +9,11 @@ use gpui::{AnyView, App, AsyncApp, Context, Entity, SharedString, Task, Window};
use http_client::HttpClient;
use language_model::{
ApiKeyState, AuthenticateError, EnvVar, IconOrSvg, LanguageModel, LanguageModelCompletionError,
LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolResultContent,
LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, env_var,
LanguageModelCompletionEvent, LanguageModelEffortLevel, LanguageModelId, LanguageModelName,
LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice,
LanguageModelToolResultContent, LanguageModelToolUse, MessageContent, RateLimiter, Role,
StopReason, TokenUsage, env_var,
};
pub use settings::DeepseekAvailableModel as AvailableModel;
use settings::{Settings, SettingsStore};
@ -164,10 +165,10 @@ impl LanguageModelProvider for DeepSeekLanguageModelProvider {
}
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models = BTreeMap::default();
let mut models = IndexMap::default();
models.insert("deepseek-chat", deepseek::Model::Chat);
models.insert("deepseek-reasoner", deepseek::Model::Reasoner);
models.insert("deepseek-v4-flash", deepseek::Model::V4Flash);
models.insert("deepseek-v4-pro", deepseek::Model::V4Pro);
for available_model in &Self::settings(cx).available_models {
models.insert(
@ -273,6 +274,32 @@ impl LanguageModel for DeepSeekLanguageModel {
true
}
fn supports_thinking(&self) -> bool {
matches!(
self.model,
deepseek::Model::V4Flash | deepseek::Model::V4Pro
)
}
fn supported_effort_levels(&self) -> Vec<LanguageModelEffortLevel> {
if !self.supports_thinking() {
return Vec::new();
}
vec![
LanguageModelEffortLevel {
name: "High".into(),
value: "high".into(),
is_default: true,
},
LanguageModelEffortLevel {
name: "Max".into(),
value: "max".into(),
is_default: false,
},
]
}
fn supports_tool_choice(&self, _choice: LanguageModelToolChoice) -> bool {
true
}
@ -320,7 +347,10 @@ pub fn into_deepseek(
model: &deepseek::Model,
max_output_tokens: Option<u64>,
) -> deepseek::Request {
let is_reasoner = model == &deepseek::Model::Reasoner;
let thinking = deepseek_thinking(model, request.thinking_allowed);
let thinking_enabled = thinking
.as_ref()
.is_some_and(|thinking| thinking.kind == deepseek::ThinkingType::Enabled);
let mut messages = Vec::new();
let mut current_reasoning: Option<String> = None;
@ -408,11 +438,17 @@ pub fn into_deepseek(
messages,
stream: true,
max_tokens: max_output_tokens,
temperature: if is_reasoner {
temperature: if thinking_enabled {
None
} else {
request.temperature
},
thinking,
reasoning_effort: if thinking_enabled {
into_deepseek_reasoning_effort(request.thinking_effort.as_deref())
} else {
None
},
response_format: None,
tools: request
.tools
@ -428,6 +464,32 @@ pub fn into_deepseek(
}
}
fn deepseek_thinking(
model: &deepseek::Model,
thinking_allowed: bool,
) -> Option<deepseek::Thinking> {
let kind = match model {
deepseek::Model::V4Flash | deepseek::Model::V4Pro => {
if thinking_allowed {
deepseek::ThinkingType::Enabled
} else {
deepseek::ThinkingType::Disabled
}
}
deepseek::Model::Custom { .. } => return None,
};
Some(deepseek::Thinking { kind })
}
fn into_deepseek_reasoning_effort(effort: Option<&str>) -> Option<deepseek::ReasoningEffort> {
match effort {
Some("high") => Some(deepseek::ReasoningEffort::High),
Some("max") => Some(deepseek::ReasoningEffort::Max),
_ => None,
}
}
pub struct DeepSeekEventMapper {
tool_calls_by_index: HashMap<usize, RawToolCall>,
}

View file

@ -242,7 +242,7 @@ Zed will also use the `DEEPSEEK_API_KEY` environment variable if it's defined.
#### Custom Models {#deepseek-custom-models}
The Zed agent comes pre-configured to use the latest version for common models (DeepSeek Chat, DeepSeek Reasoner).
The Zed agent comes pre-configured to use DeepSeek V4 Flash and DeepSeek V4 Pro.
If you wish to use alternate models or customize the API endpoint, you can do so by adding the following to your Zed settings file ([how to edit](../configuring-zed.md#settings-files)):
```json [settings]
@ -252,15 +252,16 @@ If you wish to use alternate models or customize the API endpoint, you can do so
"api_url": "https://api.deepseek.com",
"available_models": [
{
"name": "deepseek-chat",
"display_name": "DeepSeek Chat",
"max_tokens": 64000
"name": "deepseek-v4-flash",
"display_name": "DeepSeek V4 Flash",
"max_tokens": 1000000,
"max_output_tokens": 384000
},
{
"name": "deepseek-reasoner",
"display_name": "DeepSeek Reasoner",
"max_tokens": 64000,
"max_output_tokens": 4096
"name": "deepseek-v4-pro",
"display_name": "DeepSeek V4 Pro",
"max_tokens": 1000000,
"max_output_tokens": 384000
}
]
}