Settings

Language

Responses API vs Chat Completions for Agents: Choosing a Contract

CryptoCrypto
·July 14, 2026·7 min read·Updated July 25, 2026·284 views
#coding#ai-api#model-infrastructure#tokenlab
Responses API vs Chat Completions for Agents: Choosing a Contract

Questions this answers

  • What is Responses API vs Chat Completions?
  • How should developers evaluate Responses API vs Chat Completions?
  • What should teams verify before production?

For agent workloads, the Responses API is the better default: it gives you server-side conversation state via previous_response_id, typed output items instead of a single message blob, and semantic streaming events. These features reduce the bookkeeping your orchestration layer would otherwise own. Chat Completions remains a valid choice when you want full control over message history or are integrating with tooling built around the OpenAI chat message format, but for multi-turn tool-calling agents, Responses is the more direct fit.

Both endpoints are documented on the current model reference pages for GPT-5.6 and GPT-5.5, and the shared request/response contract for Responses is specified in the Responses create reference.

Key Takeaways

  • Chat Completions is caller-managed: you send the full messages array on every request and reconstruct history yourself.
  • Responses is server-assisted: you send input plus optional instructions, and can chain turns with previous_response_id instead of resending history.
  • Tool calling differs structurally: Chat Completions nests calls under choices[0].message.tool_calls; Responses emits them as typed items in a flat output array.
  • Tool results are matched by tool_call_id (Chat) versus call_id on a function_call_output item (Responses).
  • Streaming is chunk-based deltas in Chat Completions versus named semantic events in Responses.
  • Hosted tool support (web search, code interpreter, file search, etc.) is model-dependent in both APIs; check the model's page before assuming availability.

Field-Level Comparison

Concern Chat Completions Responses
Endpoint POST /v1/chat/completions POST /v1/responses
Primary input messages: [] (full array each call) input (string or array of items)
System-style guidance messages[0].role = "system" Top-level instructions field
Multi-turn continuation Caller resends entire messages history previous_response_id references prior turn server-side
Output shape choices[0].message (single message object) output: [], an array of typed items (message, function_call, etc.)
Tool call location choices[0].message.tool_calls[] Items in output with type: "function_call"
Tool result submission New message with role: "tool", tool_call_id Item with type: "function_call_output", call_id
Streaming chunk.choices[0].delta fragments Named events (response.output_text.delta, response.completed, etc.)

previous_response_id: What It Actually Does

In Chat Completions, conversation memory is entirely your responsibility. Every request must include the full message history, and the server has no notion of a prior turn. The Responses API instead returns an id on every response object. If your application persists that id and passes it back as previous_response_id on the next call, the server reconstructs the prior conversation state on its side. You only need to send the new input for the current turn plus (optionally) fresh instructions. This shifts state management from your application layer to OpenAI's infrastructure, which matters for agents making many sequential tool-calling turns because you avoid re-serializing and re-transmitting a growing history on every hop.

The tradeoff is that your app still needs to persist the id somewhere durable (a session store, a database row) between turns; the API doesn't give you infinite retention or search over past responses, it just lets you reference the immediately preceding one as a continuation point.

Current Request Examples (gpt-5.6)

Chat Completions: you own the full history:

{
  "model": "gpt-5.6",
  "messages": [
    { "role": "system", "content": "You are a support agent." },
    { "role": "user", "content": "Check order #4471 status." }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_order_status",
        "parameters": { "type": "object", "properties": { "order_id": { "type": "string" } } }
      }
    }
  ]
}

Responses: first turn with instructions and input:

{
  "model": "gpt-5.6",
  "instructions": "You are a support agent.",
  "input": "Check order #4471 status.",
  "tools": [
    {
      "type": "function",
      "name": "get_order_status",
      "parameters": { "type": "object", "properties": { "order_id": { "type": "string" } } }
    }
  ]
}

Responses: follow-up turn, no history resent:

{
  "model": "gpt-5.6",
  "previous_response_id": "resp_abc123",
  "input": "What about order #4472?"
}

Function-Call Lifecycle

Chat Completions:

  1. Model returns choices[0].message.tool_calls, each with an id and function name/arguments.
  2. You execute the function locally.
  3. You append the assistant message (with tool_calls) to your messages array, then append a new message: { "role": "tool", "tool_call_id": "<id>", "content": "<result>" }.
  4. You resend the entire updated messages array to continue.

Responses:

  1. output array contains an item with type: "function_call", including a call_id, name, and arguments.
  2. You execute the function locally.
  3. You send a new request with previous_response_id set to the prior response's id, and input containing an item with type: "function_call_output", matching call_id, and the result.
  4. The server has already retained the function-call context, so you don't resend prior turns.

The structural difference between flat typed output items and a single message with a nested array tends to simplify parsing logic in Responses, since you can iterate output and switch on type rather than digging into a message's optional fields.

Decision Checklist

  • Building a multi-turn agent with tool calls? Default to Responses; previous_response_id removes history bookkeeping.
  • Need exact control over what's in history (redaction, custom summarization, non-standard message injection)? Chat Completions gives you that control explicitly, since you assemble messages yourself.
  • Migrating an existing Chat Completions integration? Weigh the refactor cost against the state-management savings; for short-lived, single-turn calls the benefit is smaller.
  • Depending on hosted tools (search, code interpreter, file tools)? Verify support on the specific model's page before committing, since availability varies by model and endpoint.
  • Need streaming with fine-grained event semantics (e.g., distinguishing text deltas from tool-call deltas without inspecting delta shape)? Responses' named events are more explicit than Chat Completions' generic delta chunks.
  • Working within an existing framework or SDK built around chat messages? Confirm its Responses support maturity before switching contracts mid-project.

Multi-provider agents and contract translation

Agents rarely stay on a single provider for long. A coding agent might route to Claude Sonnet 5 or Kimi K2.7 Code for implementation work, fall back to DeepSeek V4 Flash or Gemini 3.5 Flash for cheap draft passes, and occasionally call GLM-5.2 or Qwen3.7 Plus for open-weight cost control. None of these providers necessarily expose OpenAI's Chat Completions or Responses contract natively.

This is where a routing layer earns its keep. TokenLab's documentation at docs.tokenlab.sh describes a single API surface and key used to reach multiple model providers, which removes the need to hand-write a separate client integration per provider contract. Our related article on header aliases for contract compatibility covers how request headers can be mapped so that code written against one contract shape can reach models that do not natively speak it. If you are building a chatbot or agent that needs to call more than one model family, our guide on building an AI chatbot with one API key walks through the setup in more concrete terms.

For the full current list of models reachable through TokenLab, including the frontier, coding, and low-cost routing options referenced above, see our models page. Confirm current availability and any contract-specific notes there before finalizing your architecture, since model line-ups change more often than API contracts do.

Limitations

This article does not restate OpenAI's exact field-level API reference for either contract, because those details are versioned and can change. Do not treat the request-shape example above as production-ready code. We also have not covered every provider's native contract in depth here; Claude, Gemini, DeepSeek, and GLM each publish their own API references, and none of them are obligated to match OpenAI's Chat Completions or Responses shapes. If your agent needs guarantees about tool-call ordering, streaming event formats, or batch processing behavior, verify those specifics against the named provider's current documentation, not against this article.

FAQ

Is the Responses API a replacement for Chat Completions? OpenAI's quickstart documentation positions the Responses API as the current path for new development, including agentic use cases, while Chat Completions remains part of their documented API surface. Whether Chat Completions is deprecated, sunset, or simply legacy at any given time is something you should confirm directly in OpenAI's current docs, since support status can change.

Do other providers like Claude, Gemini, or DeepSeek use the same contracts? Not natively. Each provider defines its own request and response shape. If you need to run an agent across OpenAI models and providers like Claude Sonnet 5 or DeepSeek V4 Pro, plan for a translation layer rather than assuming a shared contract.

Does switching contracts change model output quality? No. The contract is the transport and structure of the request and response, not the model itself. Output quality is governed by which model you call (for example GPT-5.5 versus Claude Sonnet 5), not by whether you used Chat Completions or the Responses API to call it.

If you are evaluating which contract and which models fit your agent, start with a small test build against TokenLab's documented endpoints and compare orchestration overhead directly. Get Started at docs.tokenlab.sh to run that comparison against your own workload.

Sources

Price observed 2026-07-14

Share:

Recent public models

Build with the models in this guide

Compare pricing, test routes, and move from article research to a working API call.