semantic_query/clients/
deepseek.rs1use 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#[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 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}