ChatCompletions API vs Responses API - function calling inconsistency

I am using this script with ChatCompletions:

import json
from typing import List
from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()

client = OpenAI(api_key=os.environ['OPEN_AI_KEY'])

instructions = """
Current time: 2025-06-19 09:26:37.885330+03:00

User timezone: Asia/Nicosia

User timezone offset: UTC+03:00
"""

data = client.chat.completions.create(
    model="o3-2025-04-16",
    tools=[
        {
            "type": "function",
            "function": {
                "name": "GOOGLECALENDAR_EVENTS_LIST",
                "description": "Returns events on the specified calendar.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "calendarId": {
                            "type": "string",
                            "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword."
                        },
                        "singleEvents": {
                            "type": [
                                "boolean",
                                "null"
                            ],
                            "description": "Whether to expand recurring events into instances and only return single one-off events and instances of recurring events. Optional. The default is False.",
                            "default": None
                        },
                        "timeMax": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "description": "Upper bound (exclusive) for an event's start time to filter by. Optional. The default is not to filter by start time. Must be an RFC3339 timestamp with mandatory time zone offset, for example, 2011-06-03T10:00:00-07:00, 2011-06-03T10:00:00Z. Milliseconds may be provided but are ignored. If timeMin is set, timeMax must be greater than timeMin.",
                            "default": None
                        },
                        "timeMin": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "description": "Lower bound (exclusive) for an event's end time to filter by. Optional. The default is not to filter by end time. Must be an RFC3339 timestamp with mandatory time zone offset, for example, 2011-06-03T10:00:00-07:00, 2011-06-03T10:00:00Z. Milliseconds may be provided but are ignored. If timeMax is set, timeMin must be smaller than timeMax.",
                            "default": None
                        },
                        "timeZone": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "description": "Time zone used in the response. Optional. The default is the user's primary time zone.",
                            "default": None
                        }
                    },
                    "required": [
                        "calendarId"
                    ],
                    "additionalProperties": False
                }
            }
        }
    ],
    messages=[{
        "role": "system",
        "content": instructions
    }, {
        "role": "user",
        "content": "meetings for today"
    }],
    reasoning_effort="high",
)

print(data.choices[0].message.tool_calls)

# Returns correct tool call:
# arguments='{"calendarId":"primary","singleEvents":true,"timeMin":"2025-06-19T00:00:00+03:00","timeMax":"2025-06-20T00:00:00+03:00","timeZone":"Asia/Nicosia"}

And this one with Responses API:

import json
from typing import List
from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()

client = OpenAI(api_key=os.environ['OPEN_AI_KEY'])

instructions = """
Current time: 2025-06-19 09:26:37.885330+03:00

User timezone: Asia/Nicosia

User timezone offset: UTC+03:00
"""

data = client.responses.create(
    model="o3-2025-04-16",
    tools=[
        {
            "type": "function",
            "name": "GOOGLECALENDAR_EVENTS_LIST",
            "description": "Returns events on the specified calendar.",
            "parameters": {
                "type": "object",
                "properties": {
                    "calendarId": {
                        "type": "string",
                        "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword."
                    },
                    "singleEvents": {
                        "type": [
                            "boolean",
                            "null"
                        ],
                        "description": "Whether to expand recurring events into instances and only return single one-off events and instances of recurring events. Optional. The default is False.",
                        "default": None
                    },
                    "timeMax": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Upper bound (exclusive) for an event's start time to filter by. Optional. The default is not to filter by start time. Must be an RFC3339 timestamp with mandatory time zone offset, for example, 2011-06-03T10:00:00-07:00, 2011-06-03T10:00:00Z. Milliseconds may be provided but are ignored. If timeMin is set, timeMax must be greater than timeMin.",
                        "default": None
                    },
                    "timeMin": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Lower bound (exclusive) for an event's end time to filter by. Optional. The default is not to filter by end time. Must be an RFC3339 timestamp with mandatory time zone offset, for example, 2011-06-03T10:00:00-07:00, 2011-06-03T10:00:00Z. Milliseconds may be provided but are ignored. If timeMax is set, timeMin must be smaller than timeMax.",
                        "default": None
                    },
                    "timeZone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Time zone used in the response. Optional. The default is the user's primary time zone.",
                        "default": None
                    }
                },
                "required": [
                    "calendarId"
                ],
                "additionalProperties": False
            }
        }
    ],
    instructions=instructions,
    input=[{
        "role": "user",
        "content": "meetings for today"
    }],
    reasoning={
        "effort": "high",
    }
)

for item in data.output:
    print(item, "\n\n")

# Invalid arguments, timeMax must be set:
# {"calendarId":"primary","singleEvents":true,"timeMin":"2025-06-18T21:00:00Z","timeZone":"Asia/Nicosia"}

This scripts are the same in terms of prompts, messages and tools. Both use the same model and the same reasoning effort.

The problem is that the Responses API calls the tool differently from ChatCompletions. And actually misses timeMax argument.

ChatCompletions (Expected behaviour):

{"calendarId":"primary","singleEvents":true,"timeMin":"2025-06-19T00:00:00+03:00","timeMax":"2025-06-20T00:00:00+03:00","timeZone":"Asia/Nicosia"}

vs

Responses API:

{"calendarId":"primary","singleEvents":true,"timeMin":"2025-06-18T21:00:00Z","timeZone":"Asia/Nicosia"}

Why the tool calls are different?

You’re not using strict mode, nor are you instructing the model to use all of the parameters. So, the model can pass whatever it wants, which may be one parameter, two, or ten. It’s not anything to do with the API of choice. I would recommend looking into strict mode for your functions.

2 Likes

Thanks, it was very helpful. I managed to archive similar behavior with setting all of the parameters as required. However, I am still wondering why it worked out of the box with the ChatCompletions API. Appreciate your help!