~22 min · openai, responses-api, tool-calls, input
Level 0Curious Reader
0 XP0/48 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete
OpenAI's modern API is the Responses API. The older Chat Completions endpoint still works, but new code should target Responses — it has a more uniform shape across text, tools, vision, and structured outputs. The single most common mistake when reading old tutorials is using messages when the new endpoint expects input.
The flow is: build a list of tools, call client.responses.create with the user's message in input, then iterate over the response's output. Tool calls show up as items with type: "function_call" carrying name, arguments (a JSON string — yes, a string), and a call_id. You execute the function, then send the result back as a function_call_output item in the next call's input, referencing the same call_id.
OpenAI's tool definitions live at the top level of the tool object: type: "function", name, description, parameters. (Older Chat Completions nested name and description inside a function sub-object; do not let an old example mislead you.) tool_choice accepts the literals "auto", "none", "required", or an explicit object naming a specific function.
Streaming is event-based: each chunk has a type such as response.created, response.output_text.delta, response.tool_calls.delta, and finally response.completed. For tool-call streaming, accumulate the arguments deltas into a string and parse it as JSON when the call finishes.
Code
Full round-trip in OpenAI Responses·python
import json
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"name": "get_weather",
"description": "Get current weather for a city.",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
}]
def get_weather(location: str) -> str:
return f"68°F and clear in {location}"
# Turn 1: ask the model
input_items = [{"role": "user", "content": "Weather in Seoul?"}]
resp = client.responses.create(model="gpt-4.1", tools=tools, input=input_items)
# Pull every tool call from the output
tool_calls = [item for item in resp.output if item.type == "function_call"]
for tc in tool_calls:
args = json.loads(tc.arguments)
result = get_weather(**args)
input_items.append({"type": "function_call", "call_id": tc.call_id, "name": tc.name, "arguments": tc.arguments})
input_items.append({"type": "function_call_output", "call_id": tc.call_id, "output": result})
# Turn 2: continue with the result
final = client.responses.create(model="gpt-4.1", tools=tools, input=input_items)
print(final.output_text)
Streaming with tool calls·python
stream = client.responses.create(model="gpt-4.1", tools=tools, input=[...], stream=True)
buf = ""
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type == "response.tool_calls.delta":
# arguments arrive as a JSON string in pieces
buf += event.delta.arguments
elif event.type == "response.completed":
print()
Implement the full two-turn flow above against gpt-4.1 (or whatever the current OpenAI flagship is when you read this). Print every event from a streaming response. Watch the tool_calls.delta events accumulate before the call fires. Note where in the stream you actually have parseable JSON.
Progress
Progress is local-only — sign in to sync across devices.