{"record":{"id":"ce29c59810f1e3f7","repo":"openai/openai-python","slug":"expected-chat-completions-function-tool-shape-to-b","errorCode":null,"errorMessage":"Expected Chat Completions function tool shape to be created using `openai.pydantic_function_tool()`","messagePattern":"Expected Chat Completions function tool shape to be created using `openai\\.pydantic_function_tool\\(\\)`","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/openai/resources/responses/responses.py","lineNumber":3978,"sourceCode":"\ndef _make_tools(tools: Iterable[ParseableToolParam] | Omit) -> List[ToolParam] | Omit:\n    if not is_given(tools):\n        return omit\n\n    converted_tools: List[ToolParam] = []\n    for tool in tools:\n        if tool[\"type\"] != \"function\":\n            converted_tools.append(tool)\n            continue\n\n        if \"function\" not in tool:\n            # standard Responses API case\n            converted_tools.append(tool)\n            continue\n\n        function = cast(Any, tool)[\"function\"]  # pyright: ignore[reportUnnecessaryCast]\n        if not isinstance(function, PydanticFunctionTool):\n            raise Exception(\n                \"Expected Chat Completions function tool shape to be created using `openai.pydantic_function_tool()`\"\n            )\n\n        assert \"parameters\" in function\n        new_tool = ResponsesPydanticFunctionTool(\n            {\n                \"type\": \"function\",\n                \"name\": function[\"name\"],\n                \"description\": function.get(\"description\"),\n                \"parameters\": function[\"parameters\"],\n                \"strict\": function.get(\"strict\") or False,\n            },\n            function.model,\n        )\n\n        converted_tools.append(new_tool.cast())\n\n    return converted_tools","sourceCodeStart":3960,"sourceCodeEnd":3996,"githubUrl":"https://github.com/openai/openai-python/blob/9917c6e28e66e90e1227b3d223c06a8c5441515a/src/openai/resources/responses/responses.py#L3960-L3996","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Build the tool with openai.pydantic_function_tool(MyPydanticModel)","Or use the native Responses tool shape: {'type': 'function', 'name': 'x', 'description': ..., 'parameters': {...}}","When migrating from Chat Completions, strip the outer 'function' wrapper — Responses tools are flat"],"exampleFix":"// before\nclient.responses.parse(model=\"gpt-4o\", input=\"hi\", tools=[{\"type\": \"function\", \"function\": {\"name\": \"get_weather\", \"parameters\": {...}}}])\n// after\nclient.responses.parse(model=\"gpt-4o\", input=\"hi\", tools=[{\"type\": \"function\", \"name\": \"get_weather\", \"parameters\": {...}}])","handlingStrategy":"type-guard","validationCode":"def to_responses_tool(tool: dict) -> dict:\n    if tool.get('type') == 'function' and 'function' in tool:\n        fn = tool['function']\n        return {'type': 'function', **{k: v for k, v in fn.items() if k != 'strict'}}\n    return tool\ntools = [to_responses_tool(t) for t in tools]","typeGuard":"def is_responses_tool_shape(tool) -> bool:\n    return not (isinstance(tool, dict) and tool.get('type') == 'function' and 'function' in tool)","tryCatchPattern":"try:\n    client.responses.parse(model=m, input=i, tools=tools)\nexcept Exception as e:\n    if 'pydantic_function_tool' in str(e):\n        tools = [to_responses_tool(t) for t in tools]\n        client.responses.parse(model=m, input=i, tools=tools)\n    else:\n        raise","preventionTips":["Build tools with openai.pydantic_function_tool(MyModel) for pydantic schemas","Use the flat Responses tool shape: {'type':'function','name':...,'parameters':...}","Keep separate tool definitions for chat.completions and responses APIs"],"tags":["responses","tools","chat-completions","migration","python"],"backgroundTag":"invalid-tool-definition","analyzedSha":"9917c6e28e66e90e1227b3d223c06a8c5441515a","analyzedAt":"2026-08-28T11:46:34.183Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}