Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 37 additions & 15 deletions crates/deepseek/src/deepseek.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(),
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
82 changes: 72 additions & 10 deletions crates/language_models/src/provider/deepseek.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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};
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -397,11 +427,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
Expand All @@ -417,6 +453,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>,
}
Expand Down
17 changes: 9 additions & 8 deletions docs/src/ai/llm-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,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]
Expand All @@ -251,15 +251,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
}
]
}
Expand Down
Loading