diff --git a/crates/deepseek/src/deepseek.rs b/crates/deepseek/src/deepseek.rs index 19e1f6c246..478195c68e 100644 --- a/crates/deepseek/src/deepseek.rs +++ b/crates/deepseek/src/deepseek.rs @@ -48,11 +48,11 @@ impl From 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 { 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 { 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, #[serde(default, skip_serializing_if = "Option::is_none")] + pub thinking: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub response_format: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub tools: Vec, } +#[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 { diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index a08cc25c7b..9f10da20c1 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -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> { - 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 { + 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, ) -> 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 = 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 { + 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 { + match effort { + Some("high") => Some(deepseek::ReasoningEffort::High), + Some("max") => Some(deepseek::ReasoningEffort::Max), + _ => None, + } +} + pub struct DeepSeekEventMapper { tool_calls_by_index: HashMap, } diff --git a/docs/src/ai/llm-providers.md b/docs/src/ai/llm-providers.md index f5d6f34596..fad9ace28a 100644 --- a/docs/src/ai/llm-providers.md +++ b/docs/src/ai/llm-providers.md @@ -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 } ] }