From 83e0ad02405ac89d09e3252bc7180361e6fb87ee Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Wed, 18 Mar 2026 14:00:49 +0000 Subject: [PATCH] fix(openai): flatten tools and tool_choice for Responses API - Map nested 'function' object to top-level fields - Support string and object-based 'tool_choice' formats - Fix 400 Bad Request 'Missing required parameter: tools[0].name' --- src/providers/openai.rs | 54 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/providers/openai.rs b/src/providers/openai.rs index e64d6ad7..ed654295 100644 --- a/src/providers/openai.rs +++ b/src/providers/openai.rs @@ -226,10 +226,33 @@ impl super::Provider for OpenAIProvider { } if let Some(tools) = &request.tools { - body["tools"] = serde_json::json!(tools); + let flattened: Vec = tools.iter().map(|t| { + let mut obj = serde_json::json!({ + "type": t.tool_type, + "name": t.function.name, + }); + if let Some(desc) = &t.function.description { + obj["description"] = serde_json::json!(desc); + } + if let Some(params) = &t.function.parameters { + obj["parameters"] = params.clone(); + } + obj + }).collect(); + body["tools"] = serde_json::json!(flattened); } if let Some(tool_choice) = &request.tool_choice { - body["tool_choice"] = serde_json::json!(tool_choice); + match tool_choice { + crate::models::ToolChoice::Mode(mode) => { + body["tool_choice"] = serde_json::json!(mode); + } + crate::models::ToolChoice::Specific(specific) => { + body["tool_choice"] = serde_json::json!({ + "type": specific.choice_type, + "name": specific.function.name, + }); + } + } } let resp = self @@ -574,10 +597,33 @@ impl super::Provider for OpenAIProvider { } if let Some(tools) = &request.tools { - body["tools"] = serde_json::json!(tools); + let flattened: Vec = tools.iter().map(|t| { + let mut obj = serde_json::json!({ + "type": t.tool_type, + "name": t.function.name, + }); + if let Some(desc) = &t.function.description { + obj["description"] = serde_json::json!(desc); + } + if let Some(params) = &t.function.parameters { + obj["parameters"] = params.clone(); + } + obj + }).collect(); + body["tools"] = serde_json::json!(flattened); } if let Some(tool_choice) = &request.tool_choice { - body["tool_choice"] = serde_json::json!(tool_choice); + match tool_choice { + crate::models::ToolChoice::Mode(mode) => { + body["tool_choice"] = serde_json::json!(mode); + } + crate::models::ToolChoice::Specific(specific) => { + body["tool_choice"] = serde_json::json!({ + "type": specific.choice_type, + "name": specific.function.name, + }); + } + } } let url = format!("{}/responses", self.config.base_url);