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

OpenAI Responses API

~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()

External links

Exercise

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.
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.