From 78e0594e5f330da9759d110136a9bc798b8235e8 Mon Sep 17 00:00:00 2001 From: Chummy Date: Thu, 19 Feb 2026 14:53:25 +0800 Subject: [PATCH] fix(openai): align chat_with_tools with http client and strict tool parsing --- src/providers/openai.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/providers/openai.rs b/src/providers/openai.rs index 35fef81..992372a 100644 --- a/src/providers/openai.rs +++ b/src/providers/openai.rs @@ -371,8 +371,10 @@ impl Provider for OpenAiProvider { Some( tools .iter() - .filter_map(|t| serde_json::from_value(t.clone()).ok()) - .collect(), + .cloned() + .map(serde_json::from_value::) + .collect::, _>>() + .map_err(|e| anyhow::anyhow!("Invalid OpenAI tool specification: {e}"))?, ) }; @@ -385,7 +387,7 @@ impl Provider for OpenAiProvider { }; let response = self - .client + .http_client() .post(format!("{}/chat/completions", self.base_url)) .header("Authorization", format!("Bearer {credential}")) .json(&native_request) @@ -613,6 +615,32 @@ mod tests { assert!(result.unwrap_err().to_string().contains("API key not set")); } + #[tokio::test] + async fn chat_with_tools_rejects_invalid_tool_shape() { + let p = OpenAiProvider::new(Some("openai-test-credential")); + let messages = vec![ChatMessage::user("hello".to_string())]; + let tools = vec![serde_json::json!({ + "type": "function", + "function": { + "name": "shell", + "parameters": { + "type": "object", + "properties": { + "command": { "type": "string" } + }, + "required": ["command"] + } + } + })]; + + let result = p.chat_with_tools(&messages, &tools, "gpt-4o", 0.7).await; + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("Invalid OpenAI tool specification")); + } + #[test] fn native_tool_spec_deserializes_from_openai_format() { let json = serde_json::json!({