Skip to main content
Lesson 2 of 22 · Responses API: OpenAI Fundamentals

Responses API vs Chat Completions: Why Migrate

Lesson 2 of 22 Updated July 29, 2026 Developer
On this page
  1. Responses API vs Chat Completions
  2. Fundamental Changes
  3. Server-Side Session Management
  4. Guaranteed JSON, Not Hopeful JSON
  5. Function Calling and Streaming
  6. Quick Migration Guide
  7. Should You Migrate Immediately?
  8. Key Takeaways

Responses API vs Chat Completions

The Chat Completions API served as the standard for years, and much of the code you find online still uses it. However, since 2025, OpenAI has designated it as legacy and recommends the Responses API for all new projects. This lesson compares the two interfaces, focusing on differences that actually impact your code.

Fundamental Changes

With Chat Completions, you built a list of messages and sent the entire list with every call. The conversation state lived in your application, never on the server:

from openai import OpenAI
client = OpenAI()

# Old model — Chat Completions (legacy)
response = client.chat.completions.create(
    model="gpt-5.6-terra",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "What is the capital of France?"},
    ]
)
print(response.choices[0].message.content)
# Result: "The capital of France is Paris."

The Responses API reduces this same call to two parameters and provides direct access to the generated text. The system message becomes the instructions parameter, and extracting content no longer requires navigating choices[0].message.content:

from openai import OpenAI
client = OpenAI()

# New model — Responses API
response = client.responses.create(
    model="gpt-5.6-terra",
    input="What is the capital of France?"
)
print(response.output_text)
# Result: "The capital of France is Paris."

Server-Side Session Management

This offers the most immediate benefit. The API maintains conversation state, so you only need to reference the previous response by its ID. Imagine a support assistant: by the tenth turn, the Chat Completions approach forced you to resend the previous nine exchanges, meaning you paid for those tokens again. Here, a single field suffices:

# First question
response = client.responses.create(
    model="gpt-5.6-terra",
    input="My name is Marie."
)

# Second question — context is preserved automatically
followup = client.responses.create(
    model="gpt-5.6-terra",
    input="What is my name?",
    previous_response_id=response.id
)
print(followup.output_text)
# Result: "Your name is Marie."

Guaranteed JSON, Not Hopeful JSON

The second major improvement concerns structured output. You describe the expected format, and the Responses API guarantees the response adheres to your JSON schema—allowing you to deserialize without safety nets. A registration form extracting information from free text no longer breaks just because the model decides to add an introductory sentence before its JSON:

from pydantic import BaseModel

class City(BaseModel):
    name: str
    country: str
    population: int

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Give me information about Paris.",
    text={"format": {"type": "json_schema", "json_schema": {
        "name": "city",
        "strict": True,
        "schema": City.model_json_schema()
    }}}
)
print(response.output_text)
# Result: {"name": "Paris", "country": "France", "population": 2161000}

Function Calling and Streaming

Tool declaration follows the same simplification logic: the function descriptor is declared flatly in tools, and the call requested by the model appears directly in response.output, without extra nesting levels.

response = client.responses.create(
    model="gpt-5.6-terra",
    input="What is the weather like in Lyon?",
    tools=[{
        "type": "function",
        "name": "get_meteo",
        "description": "Get the weather for a city",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "Name of the city"}
            },
            "required": ["city"]
        }
    }]
)

# The API returns the function call to execute directly
for item in response.output:
    if item.type == "function_call":
        print(f"Function: {item.name}, Arguments: {item.arguments}")
# Result: Function: get_meteo, Arguments: {"city": "Lyon"}

Streaming no longer returns a stream of undifferentiated chunks but typed events. You filter on event.type and know precisely what you are handling—a text fragment, a function argument, or the end of generation:

stream = client.responses.create(
    model="gpt-5.6-terra",
    input="Tell a short story.",
    stream=True
)

for event in stream:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)

Quick Migration Guide

Chat CompletionsResponses API
client.chat.completions.create()client.responses.create()
messages=[...]input="..." or input=[...]
response.choices[0].message.contentresponse.output_text
Manual historyprevious_response_id
response_formattext={"format": {...}}
tools + tool_choicetools (simplified)

Should You Migrate Immediately?

The Chat Completions API remains functional and is not being removed. Existing code that runs, requires no changes, and relies on a test pipeline built around the old format gains nothing from an urgent rewrite. Similarly, if you depend on a third-party library that does not yet support the Responses API, wait for its update. However, for any new project, the answer is clear: the Responses API is the starting point, and the rest of this course proceeds from that assumption.

Key Takeaways

  • The Responses API replaces Chat Completions with a simpler interface
  • Server-side session management eliminates the need to resend history
  • Structured Output and function calling are natively integrated
  • Migration is straightforward: concepts remain the same, only syntax changes