semantic_query/clients/
deepseek.rs

1use crate::core::{LowLevelClient};
2use crate::config::KeyFromEnv;
3use crate::error::{AIError, DeepSeekError};
4use async_trait::async_trait;
5use reqwest::Client;
6use serde::{Deserialize, Serialize};
7use tracing::{info, warn, error, debug, instrument};
8
9#[derive(Debug, Serialize)]
10struct DeepSeekRequest {
11    model: String,
12    messages: Vec<DeepSeekMessage>,
13    max_tokens: u32,
14    temperature: f32,
15}
16
17#[derive(Debug, Serialize)]
18struct DeepSeekMessage {
19    role: String,
20    content: String,
21}
22
23#[derive(Debug, Deserialize)]
24struct DeepSeekResponse {
25    choices: Vec<DeepSeekChoice>,
26}
27
28#[derive(Debug, Deserialize)]
29struct DeepSeekChoice {
30    message: DeepSeekResponseMessage,
31}
32
33#[derive(Debug, Deserialize)]
34struct DeepSeekResponseMessage {
35    content: String,
36}
37
38/// Configuration for DeepSeek client
39#[derive(Debug, Clone)]
40pub struct DeepSeekConfig {
41    pub api_key: String,
42    pub model: String,
43    pub max_tokens: u32,
44    pub temperature: f32,
45}
46
47impl Default for DeepSeekConfig {
48    fn default() -> Self {
49        Self {
50            api_key:DeepSeekConfig::find_key().unwrap_or(String::new()),
51            model: "deepseek-chat".to_string(),
52            max_tokens: 4096,
53            temperature: 0.3,
54        }
55    }
56}
57
58#[derive(Clone, Debug)]
59pub struct DeepSeekClient {
60    config: DeepSeekConfig,
61    client: Client,
62}
63
64impl KeyFromEnv for DeepSeekConfig {
65    const KEY_NAME: &'static str = "DEEPSEEK_API_KEY";
66}
67
68impl Default for DeepSeekClient {
69    fn default() -> Self {
70        let config = DeepSeekConfig::default();
71            
72        info!(model = %config.model, "Creating new DeepSeek client");
73        Self {
74            config,
75            client: Client::new(),
76        }
77    }
78}
79
80
81impl DeepSeekClient {
82    /// Create a new DeepSeek client with full configuration
83    pub fn new(config: DeepSeekConfig) -> Self {
84        info!(model = %config.model, "Creating new DeepSeek client");
85        Self {
86            config,
87            client: Client::new(),
88        }
89    }
90    
91}
92
93#[async_trait]
94impl LowLevelClient for DeepSeekClient {
95    #[instrument(skip(self, prompt), fields(prompt_len = prompt.len(), model = %self.config.model))]
96    async fn ask_raw(&self, prompt: String) -> Result<String, AIError> {
97        debug!(model = %self.config.model, prompt_len = prompt.len(), "Preparing DeepSeek API request");
98        
99        let request = DeepSeekRequest {
100            model: self.config.model.clone(),
101            messages: vec![DeepSeekMessage {
102                role: "user".to_string(),
103                content: prompt,
104            }],
105            max_tokens: self.config.max_tokens,
106            temperature: self.config.temperature,
107        };
108        
109        debug!("Sending request to DeepSeek API");
110        let response = self
111            .client
112            .post("https://api.deepseek.com/v1/chat/completions")
113            .header("Authorization", format!("Bearer {}", self.config.api_key))
114            .header("Content-Type", "application/json")
115            .json(&request)
116            .send()
117            .await
118            .map_err(|e| {
119                error!(error = %e, "HTTP request failed");
120                AIError::DeepSeek(DeepSeekError::Http(e.to_string()))
121            })?;
122            
123        debug!(status = %response.status(), "Received response from DeepSeek API");
124            
125        if response.status() == 429 {
126            warn!("DeepSeek API rate limit exceeded");
127            return Err(AIError::DeepSeek(DeepSeekError::RateLimit));
128        }
129        
130        if response.status() == 401 {
131            error!("DeepSeek API authentication failed");
132            return Err(AIError::DeepSeek(DeepSeekError::Authentication));
133        }
134        
135        if !response.status().is_success() {
136            let status = response.status();
137            let error_text = response.text().await
138                .unwrap_or_else(|_| "Unknown error".to_string());
139            error!(status = %status, error = %error_text, "DeepSeek API error");
140            return Err(AIError::DeepSeek(DeepSeekError::Api(error_text)));
141        }
142        
143        let deepseek_response: DeepSeekResponse = response
144            .json()
145            .await
146            .map_err(|e| {
147                error!(error = %e, "Failed to parse DeepSeek response JSON");
148                AIError::DeepSeek(DeepSeekError::Http(e.to_string()))
149            })?;
150            
151        debug!(choices_count = deepseek_response.choices.len(), "Parsed DeepSeek response");
152            
153        let result = deepseek_response
154            .choices
155            .first()
156            .map(|choice| choice.message.content.clone())
157            .ok_or_else(|| {
158                error!("No choices in DeepSeek response");
159                AIError::DeepSeek(DeepSeekError::Api("No choices in response".to_string()))
160            });
161            
162        match &result {
163            Ok(text) => info!(response_len = text.len(), "Successfully received DeepSeek response"),
164            Err(e) => error!(error = %e, "Failed to extract content from DeepSeek response"),
165        }
166        
167        result
168    }
169    
170    fn clone_box(&self) -> Box<dyn LowLevelClient> {
171        Box::new(self.clone())
172    }
173}