1use 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 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 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}