openai/openai-python · error · Exception

Expected Chat Completions function tool shape to be created

Error message

Expected Chat Completions function tool shape to be created using `openai.pydantic_function_tool()`

What it means

Raised by the internal _make_tools helper (used by Responses.stream/parse) when a tool has the Chat Completions function-tool shape (a dict with a 'function' key) but that function object is not a PydanticFunctionTool produced by openai.pydantic_function_tool(). The SDK auto-converts such tools for the Responses API and requires the canonical constructor.

Source

Thrown at src/openai/resources/responses/responses.py:3978

def _make_tools(tools: Iterable[ParseableToolParam] | Omit) -> List[ToolParam] | Omit:
    if not is_given(tools):
        return omit

    converted_tools: List[ToolParam] = []
    for tool in tools:
        if tool["type"] != "function":
            converted_tools.append(tool)
            continue

        if "function" not in tool:
            # standard Responses API case
            converted_tools.append(tool)
            continue

        function = cast(Any, tool)["function"]  # pyright: ignore[reportUnnecessaryCast]
        if not isinstance(function, PydanticFunctionTool):
            raise Exception(
                "Expected Chat Completions function tool shape to be created using `openai.pydantic_function_tool()`"
            )

        assert "parameters" in function
        new_tool = ResponsesPydanticFunctionTool(
            {
                "type": "function",
                "name": function["name"],
                "description": function.get("description"),
                "parameters": function["parameters"],
                "strict": function.get("strict") or False,
            },
            function.model,
        )

        converted_tools.append(new_tool.cast())

    return converted_tools

View on GitHub (pinned to 9917c6e28e)

Solutions

  1. Build the tool with openai.pydantic_function_tool(MyPydanticModel)
  2. Or use the native Responses tool shape: {'type': 'function', 'name': 'x', 'description': ..., 'parameters': {...}}
  3. When migrating from Chat Completions, strip the outer 'function' wrapper — Responses tools are flat

Example fix

// before
client.responses.parse(model="gpt-4o", input="hi", tools=[{"type": "function", "function": {"name": "get_weather", "parameters": {...}}}])
// after
client.responses.parse(model="gpt-4o", input="hi", tools=[{"type": "function", "name": "get_weather", "parameters": {...}}])
Defensive patterns

Strategy: type-guard

Validate before calling

def to_responses_tool(tool: dict) -> dict:
    if tool.get('type') == 'function' and 'function' in tool:
        fn = tool['function']
        return {'type': 'function', **{k: v for k, v in fn.items() if k != 'strict'}}
    return tool
tools = [to_responses_tool(t) for t in tools]

Type guard

def is_responses_tool_shape(tool) -> bool:
    return not (isinstance(tool, dict) and tool.get('type') == 'function' and 'function' in tool)

Try / catch

try:
    client.responses.parse(model=m, input=i, tools=tools)
except Exception as e:
    if 'pydantic_function_tool' in str(e):
        tools = [to_responses_tool(t) for t in tools]
        client.responses.parse(model=m, input=i, tools=tools)
    else:
        raise

Prevention

When it happens

Trigger: Passing a hand-built tool like {'type': 'function', 'function': {'name': ..., 'parameters': {...}}} (Chat Completions style) to client.responses.stream(tools=[...]) or parse(), instead of tools=[{'type':'function','name':...,'parameters':...}] or openai.pydantic_function_tool(MyModel).

Common situations: Copy-pasting Chat Completions tool definitions into Responses API calls; migrating from chat.completions.create(tools=...) without reshaping the tool dicts; building tools dynamically from raw dicts.

Related errors


AI-assisted analysis of openai/openai-python@9917c6e28e (2026-08-28). Data as JSON: /api/errors/ce29c59810f1e3f7. Report an issue: GitHub.