BadRequestError
openai.BadRequestError (HTTP 400)
Stack trace
openai.BadRequestError: Error code: 400 - {'error': {'message': "This model doesn't support function calling", 'type': 'invalid_request_error', 'param': 'tools', 'code': 'unsupported_feature'}} Why it happens
OpenAI's o1 and o1-mini reasoning models use a proprietary chain-of-thought process that is incompatible with function calling and tool use. The model's reasoning chain must remain opaque and uninterrupted. OpenAI deliberately removed support for tools, tool_choice, and parallel function calling to prevent developers from breaking the reasoning pipeline. If you pass the tools or tool_choice parameter to o1, the API rejects the request with a 400 error before the model ever runs.
Detection
Check your code for any tools=[], tool_choice parameter, or function definitions in messages before sending to o1/o1-mini. Use IDE search: grep -r 'tool_choice' and grep -r 'tools=' on your request builder. Add logging to print the full request before sending: print(json.dumps(client.chat.completions.create.__dict__)). Consider using model detection: if model in ['o1', 'o1-mini']: remove_tools_from_request().
Causes & fixes
Attempting to use tools or tool_choice parameter with o1/o1-mini models
Remove the tools=[] and tool_choice parameters entirely. o1 does not support function calling. Switch to gpt-4o or gpt-4o-mini if you need tool use, or use o1 with JSON schema validation in the prompt instead.
Using a wrapper library (LangChain, LiteLLM) that automatically adds tools to all requests
Configure your wrapper to explicitly skip tool injection for o1 models. In LangChain: model_name='o1' and add a check to disable bind_tools(). In LiteLLM: add o1 to the NO_TOOLS_MODELS list or use completion mode.
Reusing request builder code from gpt-4o that includes tools parameter
Create a separate code path for o1: def call_reasoning_model(prompt): client.chat.completions.create(model='o1', messages=[{'role': 'user', 'content': prompt}]): no tools, no tool_choice, no system message.
System messages are also not supported in o1/o1-mini for reasoning chain clarity
Move all system instructions into the user message. Instead of system='You are an expert...', use: messages=[{'role': 'user', 'content': 'You are an expert. ' + user_prompt}].
Code: broken vs fixed
import os
import json
from openai import OpenAI
client = OpenAI(api_key=os.environ.get('OPENAI_API_KEY'))
# BROKEN: Trying to use function calling with o1 — will fail with 400 error
tools = [
{
'type': 'function',
'function': {
'name': 'calculate_sum',
'description': 'Add two numbers',
'parameters': {
'type': 'object',
'properties': {
'a': {'type': 'number'},
'b': {'type': 'number'}
}
}
}
}
]
response = client.chat.completions.create(
model='o1', # This line will trigger 400 error because o1 doesn't support tools
messages=[{'role': 'user', 'content': 'What is 5 + 3?'}],
tools=tools, # BROKEN: o1 rejects this
tool_choice='auto' # BROKEN: o1 rejects this
)
print(response.choices[0].message.content) import os
import json
from openai import OpenAI
client = OpenAI(api_key=os.environ.get('OPENAI_API_KEY'))
# FIXED: Use o1 without function calling — include JSON schema in the prompt instead
user_message = """Answer the following and respond with ONLY valid JSON, no markdown fences.
{
"sum": <number>,
"explanation": <string>
}
Question: What is 5 + 3?"""
response = client.chat.completions.create(
model='o1', # FIXED: o1 works without tools/tool_choice
messages=[{'role': 'user', 'content': user_message}]
# REMOVED: tools=[], tool_choice='auto' — o1 doesn't support these
)
response_text = response.choices[0].message.content
print('Raw response:', response_text)
# Parse JSON from response
try:
result = json.loads(response_text)
print('Sum:', result['sum'])
print('Explanation:', result['explanation'])
except json.JSONDecodeError:
print('Failed to parse JSON from o1 response') Workaround
If you must keep the same request code for both gpt-4o and o1, wrap the tools parameter in a conditional: if model == 'o1': request_params = {'model': model, 'messages': messages} else: request_params = {'model': model, 'messages': messages, 'tools': tools, 'tool_choice': 'auto'}. Then call: response = client.chat.completions.create(**request_params). This defers tool use for o1 until a future API update (unlikely) while keeping gpt-4o working unchanged.
Prevention
Design your LLM request logic with model-specific code paths from the start. Create separate functions: def call_reasoning_model(prompt) for o1/o1-mini without tools, and def call_tool_model(prompt, tools) for gpt-4o. Add a model registry that maps model names to their capabilities: MODELS_WITH_TOOLS = {'gpt-4o', 'gpt-4o-mini'}, MODELS_WITHOUT_TOOLS = {'o1', 'o1-mini'}. Check this registry before building requests. Use TypedDicts or Pydantic to validate request structure and reject invalid parameter combinations at runtime before sending to API.