Skip to content

Instantly share code, notes, and snippets.

@luisjunco
Created July 15, 2026 09:43
Show Gist options
  • Select an option

  • Save luisjunco/104f9ce66805a163608b65f249ec1a35 to your computer and use it in GitHub Desktop.

Select an option

Save luisjunco/104f9ce66805a163608b65f249ec1a35 to your computer and use it in GitHub Desktop.
Exercise to discover and practice "tool calling" with the OpenAI API (Python)

Discover: Tool Calling (Function Calling) with the OpenAI Responses API

In this exercise, you'll research and discover how to allow an LLM to invoke your own Python functions.

Iteration 0 — Research

Do some quick research:

  • What is tool calling
  • How to implement tool calling with the responses API

Iteration 1 — Initial setup

Starter code — two hard-coded functions:

from openai import OpenAI
client = OpenAI()

def get_weather(city: str) -> str:
    fake_data = {"Barcelona": "22°C, sunny", "Berlin": "15°C, rainy"}
    return fake_data.get(city, "Unknown city")

def get_capital(country: str) -> str:
    fake_data = {"Spain": "Madrid", "Germany": "Berlin"}
    return fake_data.get(country, "Unknown country")

Note: make sure you have openai installed and your OPENAI_API_KEY set as an environment variable.

Iteration 2 — One tool

Give the model access to get_weather as a tool. Ask it: "What's the weather in Barcelona?"

Hints:

  • Tools are described with a JSON schema (name, description, parameters) passed in the tools argument of client.responses.create().
  • The model's response won't contain the answer directly — it will contain a function call (name + arguments) that you need to detect, run yourself, and return.

Iteration 3 — Two tools

Add get_capital as a second tool. Ask a question that requires the model to pick the right one, e.g. "What's the capital of Germany?"

Hint: the model decides which tool to call (or none) — you don't tell it which one to use.

Iteration 4 — Send the result back

After running the function, send its output back to the model (as a function_call_output) so it can give a final natural-language answer to the user, instead of just returning raw data.

Hint: you need the call_id from the model's function call to link your result back to it.


Bonus iterations

Bonus 1 — Web search tool Research the Responses API's built-in web_search tool (no function you need to write — OpenAI hosts it). Add it alongside your custom tools and ask a question that requires up-to-date info from the web.

Bonus 2 — Multi-tool loop Ask a question that requires calling both tools (e.g. "What's the weather in the capital of Germany?"). Write a loop that keeps handling function calls automatically until the model returns a final text answer.


Solution

Iteration 2
tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Get current weather for a city",
    "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
    }
}]

response = client.responses.create(
    model="gpt-5.4-nano",
    input="What's the weather in Barcelona?",
    tools=tools
)

print(response.output)  # contains a function_call item
Iteration 3
tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"]
        }
    },
    {
        "type": "function",
        "name": "get_capital",
        "description": "Get the capital city of a country",
        "parameters": {
            "type": "object",
            "properties": {"country": {"type": "string"}},
            "required": ["country"]
        }
    }
]

response = client.responses.create(
    model="gpt-5.4-nano",
    input="What's the capital of Germany?",
    tools=tools
)

print(response.output)
Iteration 4
response = client.responses.create(
    model="gpt-5.4-nano",
    input="What's the weather in Barcelona?",
    tools=tools
)

call = response.output[0]  # the function_call item
args = json.loads(call.arguments)
result = get_weather(args["city"])

followup = client.responses.create(
    model="gpt-5.4-nano",
    previous_response_id=response.id,
    input=[{
        "type": "function_call_output",
        "call_id": call.call_id,
        "output": result
    }]
)

print(followup.output_text)
Bonus 1 — Web search tool
response = client.responses.create(
    model="gpt-5.4-nano",
    input="What's the latest news about the Mars rover?",
    tools=tools + [{"type": "web_search"}]
)

print(response.output_text)
Bonus 2 — Multi-tool loop
import json

available_functions = {
    "get_weather": get_weather,
    "get_capital": get_capital
}

response = client.responses.create(
    model="gpt-5.4-nano",
    input="What's the weather in the capital of Germany?",
    tools=tools
)

while any(item.type == "function_call" for item in response.output):
    outputs = []
    for item in response.output:
        if item.type == "function_call":
            args = json.loads(item.arguments)
            fn = available_functions[item.name]
            result = fn(**args)
            outputs.append({
                "type": "function_call_output",
                "call_id": item.call_id,
                "output": result
            })
    response = client.responses.create(
        model="gpt-5.4-nano",
        previous_response_id=response.id,
        input=outputs,
        tools=tools
    )

print(response.output_text)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment