Fix incorrect formatting in OpenAI responses API for tool#125
Fix incorrect formatting in OpenAI responses API for tool#125noorbhatia wants to merge 1 commit intomattt:mainfrom
Conversation
6be4c92 to
457c634
Compare
There was a problem hiding this comment.
Pull request overview
Fixes incorrect OpenAI Responses API request-body formatting for tool-call round trips in OpenAILanguageModel (Fix #124), aligning it with Responses API conventions rather than Chat Completions conventions.
Changes:
- Formats
function_call_output.outputas a direct value (plain string for.text, JSON array for.blocks) instead of JSON-stringifying “content blocks”. - Flattens
image_urlin tool output blocks to a string URL (Responses format). - Converts Chat Completions-style raw assistant
tool_callsmessages into Responsesfunction_callinput items.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| case .string(let role) = dict["role"], | ||
| role == "assistant", | ||
| case .array(let toolCallsArr) = dict["tool_calls"] | ||
| { | ||
| for tc in toolCallsArr { | ||
| guard case .object(let tcDict) = tc, | ||
| case .string(let tcId) = tcDict["id"], | ||
| case .object(let fnDict) = tcDict["function"], | ||
| case .string(let fnName) = fnDict["name"], | ||
| case .string(let fnArgs) = fnDict["arguments"] |
There was a problem hiding this comment.
The pattern matches against dict["role"] / dict["tool_calls"] are missing optional unwrapping (?). Since dict[...] is optional (as used elsewhere in this file, e.g. case let .string(type)? = obj["type"]), this won’t compile. Update these to case .string(let role)? = ... / case .array(let toolCallsArr)? = ... (or unwrap with guard let).
| case .string(let role) = dict["role"], | |
| role == "assistant", | |
| case .array(let toolCallsArr) = dict["tool_calls"] | |
| { | |
| for tc in toolCallsArr { | |
| guard case .object(let tcDict) = tc, | |
| case .string(let tcId) = tcDict["id"], | |
| case .object(let fnDict) = tcDict["function"], | |
| case .string(let fnName) = fnDict["name"], | |
| case .string(let fnArgs) = fnDict["arguments"] | |
| case .string(let role)? = dict["role"], | |
| role == "assistant", | |
| case .array(let toolCallsArr)? = dict["tool_calls"] | |
| { | |
| for tc in toolCallsArr { | |
| guard case .object(let tcDict) = tc, | |
| case .string(let tcId)? = tcDict["id"], | |
| case .object(let fnDict)? = tcDict["function"], | |
| case .string(let fnName)? = fnDict["name"], | |
| case .string(let fnArgs)? = fnDict["arguments"] |
| case .string(let tcId) = tcDict["id"], | ||
| case .object(let fnDict) = tcDict["function"], | ||
| case .string(let fnName) = fnDict["name"], | ||
| case .string(let fnArgs) = fnDict["arguments"] |
There was a problem hiding this comment.
Same optional-pattern issue inside the tool call loop: tcDict["id"], tcDict["function"], fnDict["name"], and fnDict["arguments"] are dictionary subscripts and need ? in the pattern match (or explicit unwrapping). As written, this block won’t compile.
| case .string(let tcId) = tcDict["id"], | |
| case .object(let fnDict) = tcDict["function"], | |
| case .string(let fnName) = fnDict["name"], | |
| case .string(let fnArgs) = fnDict["arguments"] | |
| case .string(let tcId)? = tcDict["id"], | |
| case .object(let fnDict)? = tcDict["function"], | |
| case .string(let fnName)? = fnDict["name"], | |
| case .string(let fnArgs)? = fnDict["arguments"] |
| case .raw(rawContent: let rawContent): | ||
| outputs.append(rawContent) | ||
| // Convert Chat Completions assistant+tool_calls to Responses API function_call items | ||
| if case .object(let dict) = rawContent, | ||
| case .string(let role) = dict["role"], | ||
| role == "assistant", | ||
| case .array(let toolCallsArr) = dict["tool_calls"] | ||
| { | ||
| for tc in toolCallsArr { | ||
| guard case .object(let tcDict) = tc, | ||
| case .string(let tcId) = tcDict["id"], | ||
| case .object(let fnDict) = tcDict["function"], | ||
| case .string(let fnName) = fnDict["name"], | ||
| case .string(let fnArgs) = fnDict["arguments"] | ||
| else { continue } | ||
| outputs.append( | ||
| .object([ | ||
| "type": .string("function_call"), | ||
| "call_id": .string(tcId), | ||
| "name": .string(fnName), | ||
| "arguments": .string(fnArgs), | ||
| ]) | ||
| ) | ||
| } | ||
| } else { | ||
| outputs.append(rawContent) | ||
| } |
There was a problem hiding this comment.
This PR changes request-body formatting for Responses tool-call round trips, but there’s no deterministic unit test asserting the generated input items (e.g., .raw assistant+tool_calls → function_call items, and .tool → function_call_output with a plain string output). Adding a unit test around Responses.createRequestBody would prevent regressions without requiring a live API key.
Fix #124