From 964959503b6ca15d8d5d07d7406fee1feeb5eb78 Mon Sep 17 00:00:00 2001 From: "zed-zippy[bot]" <234243425+zed-zippy[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:51:46 +0000 Subject: [PATCH] deepseek: Fix empty text segments causing issues (#52199) (cherry-pick to preview) (#52206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry-pick of #52199 to preview ---- ## Context Deepseek seems to have started to return empty text deltas, which causes issues cause their upstream API does not like empty text blocks to be included in their request. In practice this caused issues like `An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'..., because the payload looked like this: ``` User { content: "Call the now tool a bunch of times", }, Assistant { content: Some( "", ), }, Tool { content: "The current datetime is 2026-03-23T13:21:24.084848+00:00.", tool_call_id: "call_00_iozCfHXJAPR13XwHiAwJ9OEw", }, ``` Now we filter out that empty text, which seems to fix the issue. This matches our implementation for OpenAI Closes #51854 ## How to Review ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - deepseek: Fixed issue with tool calling (`An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'...`) Co-authored-by: Bennet Bo Fenner --- .../language_models/src/provider/deepseek.rs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index 0bf86ef15c91b16dbc496ff732b087fedd0da0a9..0bd94b3c9ef741bb0aa35151c248dd40c5af5a58 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -331,15 +331,25 @@ pub fn into_deepseek( for message in request.messages { for content in message.content { match content { - MessageContent::Text(text) => messages.push(match message.role { - Role::User => deepseek::RequestMessage::User { content: text }, - Role::Assistant => deepseek::RequestMessage::Assistant { - content: Some(text), - tool_calls: Vec::new(), - reasoning_content: current_reasoning.take(), - }, - Role::System => deepseek::RequestMessage::System { content: text }, - }), + MessageContent::Text(text) => { + let should_add = if message.role == Role::User { + !text.trim().is_empty() + } else { + !text.is_empty() + }; + + if should_add { + messages.push(match message.role { + Role::User => deepseek::RequestMessage::User { content: text }, + Role::Assistant => deepseek::RequestMessage::Assistant { + content: Some(text), + tool_calls: Vec::new(), + reasoning_content: current_reasoning.take(), + }, + Role::System => deepseek::RequestMessage::System { content: text }, + }); + } + } MessageContent::Thinking { text, .. } => { // Accumulate reasoning content for next assistant message current_reasoning.get_or_insert_default().push_str(&text); @@ -445,7 +455,9 @@ impl DeepSeekEventMapper { }; let mut events = Vec::new(); - if let Some(content) = choice.delta.content.clone() { + if let Some(content) = choice.delta.content.clone() + && !content.is_empty() + { events.push(Ok(LanguageModelCompletionEvent::Text(content))); }