Text size
Dark mode
RecallRunGuides & benchmarks
Learn to build real systemsGuides•Benchmarks•Experiments•Community
Search the site/Free checklist

Guides/OpenAI SDK

OpenAI SDK: Responses API vs Chat Completions

OpenAI has two APIs for generating text. Chat Completions ( client.chat.completions.create ) is the older, widely copied format: a list of messages in, one message…

In this post

What this post covers
  1. 1. Roles: who is speaking
  2. 2. The same request in both APIs
  3. 3. Output items: why the Responses API is easier to build on
  4. 4. Which one should you use?
  5. Recap

OpenAI has two APIs for generating text. Chat Completions (client.chat.completions.create) is the older, widely copied format: a list of messages in, one message out. The Responses API (client.responses.create) is the newer, recommended one. It takes input items and returns a list of typed output items, which lets it represent tool calls, reasoning and built-in tools cleanly, and it can keep conversation state server-side. You will meet both, so learn to read both.

1. Roles: who is speaking

RoleWritten byUse for
developer (older: system)you, the applicationrules, persona, format, constraints. Highest priority after OpenAI's own policies
userthe end userthe request itself. Treat it as untrusted input
assistantthe model (earlier turns)replaying conversation history

In the Responses API, the instructions parameter is a shortcut for a developer message, and it is not carried over automatically when you chain responses with previous_response_id. Send it on every call.

2. The same request in both APIs

import json
from fake_openai import fake_client

client = fake_client(script=["Kochi has 214 customers.", "Kochi has 214 customers."])
history = [
    {"role": "user", "content": "Which city has the most customers?"},
    {"role": "assistant", "content": "Kochi."},
    {"role": "user", "content": "How many?"},
]

# Responses API: instructions + input items
r = client.responses.create(model="gpt-5.6-luna",
                            instructions="You are a retail analyst. Be brief.",
                            input=history)
print("responses:", r.output_text)

# Chat Completions: everything is a message, including the system/developer prompt
c = client.chat.completions.create(model="gpt-5.6-luna",
                                   messages=[{"role": "developer", "content": "You are a retail analyst. Be brief."},
                                             *history])
print("chat:     ", c.choices[0].message.content)

for req in client.fake.requests:
    print(req["path"], "->", sorted(req["body"]))

Output

responses: Kochi has 214 customers.
chat:      Kochi has 214 customers.
/v1/responses -> ['input', 'instructions', 'model']
/v1/chat/completions -> ['messages', 'model']

3. Output items: why the Responses API is easier to build on

Chat Completions choices[0].message .content (text or None) .tool_calls (list or None) one message; everything else squeezed into it you always resend the whole history Responses output = [ {type: "reasoning", …}, {type: "web_search_call", …}, {type: "function_call", …}, {type: "message", content: […]}] each step is its own typed item; state can live server-side (previous_response_id, conversations)

walking the output items

from fake_openai import fake_client

client = fake_client(script=[{"tool_calls": [("get_weather", {"city": "Kochi"}),
                                             ("get_weather", {"city": "Pune"})]}])
r = client.responses.create(model="gpt-5.6-luna", input="Weather in Kochi and Pune?",
                            tools=[{"type": "function", "name": "get_weather",
                                    "parameters": {"type": "object",
                                                   "properties": {"city": {"type": "string"}},
                                                   "required": ["city"]}}])
for item in r.output:
    if item.type == "message":
        print("message:", item.content[0].text)
    elif item.type == "function_call":
        print("function_call:", item.name, item.arguments, item.call_id)
print("output_text is empty when the model only asked for tools:", repr(r.output_text))

Output

function_call: get_weather {"city": "Kochi"} call_1
function_call: get_weather {"city": "Pune"} call_2
output_text is empty when the model only asked for tools: ''

4. Which one should you use?

Responses API

New projects; agents and tool use; built-in tools (web search, file search, code interpreter, MCP); reasoning models; server-side conversation state. OpenAI's recommended default.

Chat Completions

Existing code; libraries and other providers that implement "OpenAI-compatible" chat endpoints (many local model servers do). It is still supported, but new capabilities arrive in Responses first.

migration cheat sheet

chat.completions.create(messages=[...])       →  responses.create(input=[...], instructions="...")
system / developer message                     →  instructions="..." (or a developer input item)
choices[0].message.content                     →  response.output_text
tool_calls[i].function.name / .arguments        →  output item type "function_call": .name / .arguments
{"role": "tool", "tool_call_id": ..., ...}     →  {"type": "function_call_output", "call_id": ..., "output": ...}
response_format={"type": "json_schema", ...}   →  text={"format": {...}}  (or responses.parse, lesson 06)
max_tokens / max_completion_tokens             →  max_output_tokens

Recap

  • Roles: developer (your rules), user (untrusted request), assistant (earlier replies).
  • Responses API: input items in, typed output items out; instructions must be resent each call.
  • output_text is a convenience; loop over output to handle tool calls and other items.
  • Chat Completions remains common and "OpenAI-compatible"; new features land in Responses.

Get the free RAG production checklist

25 checks to run before real users see your RAG feature: exact-match misses, invented citations, prompt injection through documents, quality that slips after a prompt change. Confirm your email and the PDF comes with the welcome email, plus each new experiment.

One email to confirm. Unsubscribe any time. What's in it · Privacy

Want the production version of this? See ShipRAG: A production RAG + MCP starter kit for FastAPI: hybrid search, validated citations, evals in CI, Docker, 32 tests.

Written by

Alby Tomy

Python/FastAPI Engineer | Data Engineer | AI Engineer building RAG and MCP systems.

X · GitHub · Contact & enquiries