llm/backends/
deepseek.rs

1//! DeepSeek API client implementation for chat and completion functionality.
2//!
3//! This module provides integration with DeepSeek's models through their API.
4
5use crate::chat::{ChatResponse, Tool};
6#[cfg(feature = "deepseek")]
7use crate::{
8    chat::{ChatMessage, ChatProvider, ChatRole},
9    completion::{CompletionProvider, CompletionRequest, CompletionResponse},
10    embedding::EmbeddingProvider,
11    error::LLMError,
12    models::ModelsProvider,
13    stt::SpeechToTextProvider,
14    tts::TextToSpeechProvider,
15    LLMProvider,
16};
17use async_trait::async_trait;
18use reqwest::Client;
19use serde::{Deserialize, Serialize};
20
21use crate::ToolCall;
22
23pub struct DeepSeek {
24    pub api_key: String,
25    pub model: String,
26    pub max_tokens: Option<u32>,
27    pub temperature: Option<f32>,
28    pub system: Option<String>,
29    pub timeout_seconds: Option<u64>,
30    pub stream: Option<bool>,
31    client: Client,
32}
33
34#[derive(Serialize)]
35struct DeepSeekChatMessage<'a> {
36    role: &'a str,
37    content: &'a str,
38}
39
40#[derive(Serialize)]
41struct DeepSeekChatRequest<'a> {
42    model: &'a str,
43    messages: Vec<DeepSeekChatMessage<'a>>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    temperature: Option<f32>,
46    stream: bool,
47}
48
49#[derive(Deserialize, Debug)]
50struct DeepSeekChatResponse {
51    choices: Vec<DeepSeekChatChoice>,
52}
53
54impl std::fmt::Display for DeepSeekChatResponse {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(f, "{:?}", self)
57    }
58}
59
60#[derive(Deserialize, Debug)]
61struct DeepSeekChatChoice {
62    message: DeepSeekChatMsg,
63}
64
65#[derive(Deserialize, Debug)]
66struct DeepSeekChatMsg {
67    content: String,
68}
69impl ChatResponse for DeepSeekChatResponse {
70    fn text(&self) -> Option<String> {
71        self.choices.first().and_then(|c| {
72            if c.message.content.is_empty() {
73                None
74            } else {
75                Some(c.message.content.clone())
76            }
77        })
78    }
79
80    fn tool_calls(&self) -> Option<Vec<ToolCall>> {
81        None
82    }
83}
84
85impl DeepSeek {
86    pub fn new(
87        api_key: impl Into<String>,
88        model: Option<String>,
89        max_tokens: Option<u32>,
90        temperature: Option<f32>,
91        timeout_seconds: Option<u64>,
92        system: Option<String>,
93        stream: Option<bool>,
94    ) -> Self {
95        let mut builder = Client::builder();
96        if let Some(sec) = timeout_seconds {
97            builder = builder.timeout(std::time::Duration::from_secs(sec));
98        }
99        Self {
100            api_key: api_key.into(),
101            model: model.unwrap_or("deepseek-chat".to_string()),
102            max_tokens,
103            temperature,
104            system,
105            timeout_seconds,
106            stream,
107            client: builder.build().expect("Failed to build reqwest Client"),
108        }
109    }
110}
111
112#[async_trait]
113impl ChatProvider for DeepSeek {
114    /// Sends a chat request to DeepSeek's API.
115    ///
116    /// # Arguments
117    ///
118    /// * `messages` - The conversation history as a slice of chat messages
119    ///
120    /// # Returns
121    ///
122    /// The provider's response text or an error
123    async fn chat(&self, messages: &[ChatMessage]) -> Result<Box<dyn ChatResponse>, LLMError> {
124        if self.api_key.is_empty() {
125            return Err(LLMError::AuthError("Missing DeepSeek API key".to_string()));
126        }
127
128        let mut deepseek_msgs: Vec<DeepSeekChatMessage> = messages
129            .iter()
130            .map(|m| DeepSeekChatMessage {
131                role: match m.role {
132                    ChatRole::User => "user",
133                    ChatRole::Assistant => "assistant",
134                },
135                content: &m.content,
136            })
137            .collect();
138
139        if let Some(system) = &self.system {
140            deepseek_msgs.insert(
141                0,
142                DeepSeekChatMessage {
143                    role: "system",
144                    content: system,
145                },
146            );
147        }
148
149        let body = DeepSeekChatRequest {
150            model: &self.model,
151            messages: deepseek_msgs,
152            temperature: self.temperature,
153            stream: self.stream.unwrap_or(false),
154        };
155
156        if log::log_enabled!(log::Level::Trace) {
157            if let Ok(json) = serde_json::to_string(&body) {
158                log::trace!("DeepSeek request payload: {}", json);
159            }
160        }
161
162        let mut request = self
163            .client
164            .post("https://api.deepseek.com/v1/chat/completions")
165            .bearer_auth(&self.api_key)
166            .json(&body);
167
168        if let Some(timeout) = self.timeout_seconds {
169            request = request.timeout(std::time::Duration::from_secs(timeout));
170        }
171
172        let resp = request.send().await?;
173
174        log::debug!("DeepSeek HTTP status: {}", resp.status());
175
176        let resp = resp.error_for_status()?;
177
178        let json_resp: DeepSeekChatResponse = resp.json().await?;
179
180        Ok(Box::new(json_resp))
181    }
182
183    /// Sends a chat request to DeepSeek's API with tools.
184    ///
185    /// # Arguments
186    ///
187    /// * `messages` - The conversation history as a slice of chat messages
188    /// * `tools` - Optional slice of tools to use in the chat
189    ///
190    /// # Returns
191    ///
192    /// The provider's response text or an error
193    async fn chat_with_tools(
194        &self,
195        _messages: &[ChatMessage],
196        _tools: Option<&[Tool]>,
197    ) -> Result<Box<dyn ChatResponse>, LLMError> {
198        todo!()
199    }
200}
201
202#[async_trait]
203impl CompletionProvider for DeepSeek {
204    async fn complete(&self, _req: &CompletionRequest) -> Result<CompletionResponse, LLMError> {
205        Ok(CompletionResponse {
206            text: "DeepSeek completion not implemented.".into(),
207        })
208    }
209}
210
211#[async_trait]
212impl EmbeddingProvider for DeepSeek {
213    async fn embed(&self, _text: Vec<String>) -> Result<Vec<Vec<f32>>, LLMError> {
214        Err(LLMError::ProviderError(
215            "Embedding not supported".to_string(),
216        ))
217    }
218}
219
220#[async_trait]
221impl SpeechToTextProvider for DeepSeek {
222    async fn transcribe(&self, _audio: Vec<u8>) -> Result<String, LLMError> {
223        Err(LLMError::ProviderError(
224            "DeepSeek does not implement speech to text endpoint yet.".into(),
225        ))
226    }
227}
228
229#[async_trait]
230impl ModelsProvider for DeepSeek {}
231
232impl LLMProvider for DeepSeek {}
233
234#[async_trait]
235impl TextToSpeechProvider for DeepSeek {
236    async fn speech(&self, _text: &str) -> Result<Vec<u8>, LLMError> {
237        Err(LLMError::ProviderError(
238            "Text to speech not supported".to_string(),
239        ))
240    }
241}