(
&self,
messages: &[ToolMessage],
tools: &[ToolDefinition],
max_tokens: u32,
)
| 310 | // ── OpenAI ────────────────────────────────────────────────── |
| 311 | |
| 312 | async fn chat_with_tools_openai( |
| 313 | &self, |
| 314 | messages: &[ToolMessage], |
| 315 | tools: &[ToolDefinition], |
| 316 | max_tokens: u32, |
| 317 | ) -> Result<ToolAwareResponse, AiError> { |
| 318 | let url = format!("{}/chat/completions", self.base_url); |
| 319 | |
| 320 | let api_messages = openai_messages(messages); |
| 321 | |
| 322 | let api_tools: Vec<serde_json::Value> = tools |
| 323 | .iter() |
| 324 | .map(|t| { |
| 325 | json!({ |
| 326 | "type": "function", |
| 327 | "function": { |
| 328 | "name": t.name, |
| 329 | "description": t.description, |
| 330 | "parameters": t.parameters, |
| 331 | } |
| 332 | }) |
| 333 | }) |
| 334 | .collect(); |
| 335 | |
| 336 | let mut body = json!({ |
| 337 | "model": self.model, |
| 338 | "max_tokens": max_tokens, |
| 339 | "messages": api_messages, |
| 340 | }); |
| 341 | |
| 342 | if !api_tools.is_empty() { |
| 343 | body["tools"] = json!(api_tools); |
| 344 | } |
| 345 | |
| 346 | let client = reqwest::Client::new(); |
| 347 | let resp = client |
| 348 | .post(&url) |
| 349 | .header("Authorization", format!("Bearer {}", self.api_key)) |
| 350 | .header("content-type", "application/json") |
| 351 | .json(&body) |
| 352 | .send() |
| 353 | .await |
| 354 | .map_err(|e| AiError::Http(e.to_string()))?; |
| 355 | |
| 356 | if !resp.status().is_success() { |
| 357 | let status = resp.status().as_u16(); |
| 358 | let text = resp.text().await.unwrap_or_default(); |
| 359 | return Err(AiError::ApiError { status, body: text }); |
| 360 | } |
| 361 | |
| 362 | let json: serde_json::Value = resp |
| 363 | .json() |
| 364 | .await |
| 365 | .map_err(|e| AiError::Http(e.to_string()))?; |
| 366 | |
| 367 | let choice = &json["choices"][0]; |
| 368 | let msg = &choice["message"]; |
| 369 |
no test coverage detected