C.W.K.
Stream
Lesson 05 of 07 · published

Custom Function Tools

~22 min · function-tools, tool-loop

Level 0Tokenizer
0 XP0/54 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

Custom function tools in the Responses API use a top-level name field (unlike Chat Completions which nests it under function). The multi-turn tool loop uses function_call output items.

The shape you see in production

A custom function tool is JSON Schema for the parameters plus three fields: type: "function", name, and description. On Responses, all three sit at the top level of each tool object. On Chat Completions, they sit under a nested function wrapper. The schema body is identical — only the wrapping differs.

Treat the description as part of the prompt. The model reads it; vague descriptions ('get the weather') route badly; explicit descriptions with negative examples ('do not call for forecasts beyond 24h') route well. Per-parameter descriptions inside parameters.properties[i].description matter too.

Code

Defining a function tool (Responses shape)·python
import json
from openai import OpenAI

client = OpenAI()

tools = [{
    "type": "function",
    "name": "get_weather",           # top-level, not nested
    "description": "Get current weather for a location.",
    "parameters": {
        "type": "object",
        "properties": {
            "location": {"type": "string", "description": "City name"},
            "units": {"type": ["string", "null"], "enum": ["celsius", "fahrenheit"]},
        },
        "required": ["location"],
        "additionalProperties": False,
    },
    "strict": True,
}]

# Multi-turn tool loop
input_items = [{"role": "user", "content": "What's the weather in Tokyo?"}]

while True:
    response = client.responses.create(
        model="gpt-5.4", tools=tools, input=input_items,
    )
    input_items.extend(response.output)
    tool_calls = [i for i in response.output if i.type == "function_call"]
    if not tool_calls:
        print(response.output_text)
        break
    for tc in tool_calls:
        result = get_weather(**json.loads(tc.arguments))
        input_items.append({
            "type": "function_call_output",
            "call_id": tc.call_id,
            "output": json.dumps(result),
        })

External links

Exercise

Define one function tool get_weather(location, units). Send a query that triggers it, parse the function_call output item, run a fake handler that returns a hardcoded result, and feed the result back via input=[function_call_output] to get the final answer.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.