Wire requests
One normalized request, rendered into both OpenAI dialects. They are implemented as separate builders sharing no object literals, because conflating them is the single largest source of 400s — and because a framework that claims to “craft requests correctly” should let you check.
The baseline. Note the tool shape: Responses puts name/parameters at the top level, Chat Completions nests them under `function`. Sending one to the other is a guaranteed 400.
Input — NormalizedRequest
{
"model": "gpt-5.2",
"system": "You are a research assistant.",
"messages": [
{
"id": "user_1",
"role": "user",
"parts": [
{
"type": "text",
"text": "Which vendor should we pick?"
}
]
}
],
"tools": [
{
"name": "compareCompetitors",
"description": "Positioning matrix.",
"parameters": {
"type": "object",
"properties": {
"against": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
],
"maxOutputTokens": 2000
}POST /v1/responses
{
"model": "gpt-5.2",
"input": [
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "Which vendor should we pick?"
}
]
}
],
"instructions": "You are a research assistant.",
"tools": [
{
"type": "function",
"name": "compareCompetitors",
"description": "Positioning matrix.",
"parameters": {
"type": "object",
"properties": {
"against": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
],
"tool_choice": "auto",
"parallel_tool_calls": true,
"max_output_tokens": 2000
}POST /v1/chat/completions
{
"model": "gpt-5.2",
"messages": [
{
"role": "system",
"content": "You are a research assistant."
},
{
"role": "user",
"content": "Which vendor should we pick?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "compareCompetitors",
"description": "Positioning matrix.",
"parameters": {
"type": "object",
"properties": {
"against": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
],
"parallel_tool_calls": true,
"tool_choice": "auto",
"max_completion_tokens": 2000
}Where they disagree
| Responses | Chat Completions | |
|---|---|---|
| history field | input[] | messages[] |
| system | instructions (top level) | a message with role system |
| user text part | input_text | text |
| image part | input_image | image_url |
| file part | input_file | file |
| assistant text | output_text | content: string |
| tool declaration | flat { type, name, parameters } | nested { type, function: {…} } |
| model tool call | input item function_call | assistant.tool_calls[] |
| tool output | input item function_call_output | message role:"tool" + tool_call_id |
| output cap | max_output_tokens | max_completion_tokens |
| JSON schema | text.format | response_format.json_schema |
| reasoning | reasoning.effort | reasoning_effort |
| streamed usage | always included | needs stream_options.include_usage |
| async | background: true + polling | — none |
| server history | previous_response_id | — none |