(&self, texts: &[String])
| 230 | } |
| 231 | |
| 232 | async fn embed_openai(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, AiError> { |
| 233 | let api_key = self.api_key.as_ref().ok_or(AiError::NoApiKey)?; |
| 234 | |
| 235 | let body = serde_json::json!({ |
| 236 | "model": self.model, |
| 237 | "input": texts, |
| 238 | }); |
| 239 | |
| 240 | let client = reqwest::Client::new(); |
| 241 | let resp = client |
| 242 | .post("https://api.openai.com/v1/embeddings") |
| 243 | .header("Authorization", format!("Bearer {}", api_key)) |
| 244 | .header("content-type", "application/json") |
| 245 | .json(&body) |
| 246 | .send() |
| 247 | .await |
| 248 | .map_err(|e| AiError::Http(e.to_string()))?; |
| 249 | |
| 250 | if !resp.status().is_success() { |
| 251 | let status = resp.status(); |
| 252 | let text = resp.text().await.unwrap_or_default(); |
| 253 | return Err(AiError::ApiError { |
| 254 | status: status.as_u16(), |
| 255 | body: text, |
| 256 | }); |
| 257 | } |
| 258 | |
| 259 | let json: serde_json::Value = resp |
| 260 | .json() |
| 261 | .await |
| 262 | .map_err(|e| AiError::Http(e.to_string()))?; |
| 263 | |
| 264 | parse_embedding_response(&json) |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | /// Parse the standard embedding API response format (shared by OpenAI and Voyage). |
no test coverage detected