Responses API vs Chat Completions for Agents
Compare item-based response state with an application-managed message ledger, then map tool calls, retries, instructions, and compaction to explicit owners.
Responses API vs Chat Completions is a state-ownership decision for engineers building tool-using agents. This guide maps one agent turn through both APIs so you can choose a ledger, retry policy, and migration seam without losing durable work.
Responses API vs Chat Completions starts with custody
The useful question is not which endpoint looks newer. Ask which system carries model context between turns, which record your product can reconstruct, and which identifiers survive a failure. The OpenAI Responses API represents work as typed input and output items. A response can follow another through previous_response_id, or work can be associated with a conversation. Chat Completions accepts a message array selected and sent by the application for that request.
That difference changes bookkeeping, but it does not transfer business ownership. A support action, payment attempt, approval, or durable tool job still needs an application work ID and an effect receipt. Responses API vs Chat Completions should therefore be evaluated beside the durable AI agent execution boundary: provider context helps conversation state continue, while the product ledger decides whether a real-world action may continue.
The bounded synthetic corpus defines one support turn with a user message, an order lookup call, its result, and a final answer. It serializes the same domain events into both API shapes and prints preserved, regenerated, and application-owned identifiers. Those labels describe the local corpus, not observed provider latency, pricing, or production reliability.
- Solid river: response items may be chained by a response identifier or conversation.
- Dashed river: the application sends the Chat Completions ledger it chooses to retain.
- Both banks: policy, durable work IDs, and business effects remain application concerns.
Read Responses as an item stream
A response is more than an assistant string. Its output may include message items, tool calls, and other typed records that your loop must inspect. When the next request references a prior response, the provider can recover eligible model context without the client rebuilding an identical message list. That convenience is strongest when a turn naturally produces several item types and the application wants a direct continuation.
The Responses create reference is the source of truth for request fields and current item behavior. Store returned identifiers only for their documented scope; do not treat them as foreign keys for invoices, jobs, or audit events. Responses API vs Chat Completions becomes safer when response IDs are pointers in an inference log, never the only evidence that a tool effect happened.
Instructions deserve special care. A continuation link is not permission to assume every new instruction is inherited exactly as your product intends. Make the instruction policy explicit in the adapter, version it, and test a changed policy in the next turn. The fixture mutates its instruction version and shows that policy identity remains application-owned even when prior response items are available.
Treat Chat messages as an explicit projection
Chat Completions makes the request ledger visible because the application constructs messages before each call. That can be valuable when a team already owns compaction, redaction, replay, and provider portability. It can also be dangerous when code appends opportunistically: duplicated tool results, missing role boundaries, and stale instructions then become ordinary array bugs rather than obvious state transitions.
Build the message list from a canonical domain turn instead of editing an array in place. Select the system or developer policy, user content, assistant tool request, tool result, and any compacted summary through named projection rules. A Chat Completions migration should keep that projector available until matched fixtures prove the new adapter preserves accepted meaning. Responses API vs Chat Completions is then a comparison between projections, not between two unrelated implementations.
Explicit history also means explicit data governance. Decide which content may be retained, how long it persists, what is redacted, and which source events can regenerate it. The Structured Outputs semantic validation pattern belongs after decoding either API: syntactic conformance does not prove that a tool argument is allowed or still valid.
| Record | Responses | Chat Completions |
|---|---|---|
| Prior model context | Chain or resend | Resend selected messages |
| Tool result | Typed item | Tool message |
| Durable effect | Application owns | Application owns |
Map a tool round trip without collapsing roles
A tool round trip contains at least four facts: the model requested a named operation, the application validated it, an executor produced a result or error, and the model received a representation of that outcome. Responses uses typed call and result items. Chat Completions expresses the same relationship through assistant tool calls and tool-role messages. Your normalized turn should retain the call ID across either serialization.
Do not insert a raw executor object into the model ledger. Create a bounded result envelope with status, safe fields, provenance, and an application work ID. The bounded programmatic tool calling guide explains why validation and limits belong before execution. Responses API vs Chat Completions does not change that trust boundary; it changes where the inference-facing copy of the envelope is carried.
The local artifact assigns call call_lookup_1 to the frozen lookup and work work_order_17 to the application effect record. Its normalization proves that both adapters preserve the call relationship while only the application record owns the durable work key. These are bounded fixture identifiers emitted by committed code, not identifiers captured from a live OpenAI request.
This custody fixture changes instructions, retries work, and compacts source identifiers so both API adapters expose their ownership decisions.
Runnable artifact — responses-chat-state-fixture.mjs
import assert from "node:assert/strict";
import crypto from "node:crypto";
const turn = { messageId: "msg_1", callId: "call_lookup_1", resultId: "result_1", workId: "work_order_17" };
const instruction = (version) => ({ owner: "application", version, text: version === 1 ? "Quote inventory" : "Quote inventory and cite warehouse" });
const responsesAdapter = (attempt, version, previousResponseId = null) => ({
attempt, previousResponseId, instructions: instruction(version),
items: [{ type: "message", id: turn.messageId }, { type: "function_call", call_id: turn.callId }, { type: "function_call_output", call_id: turn.callId, id: turn.resultId }],
});
const chatAdapter = (attempt, version) => ({
attempt, instructions: instruction(version),
messages: [{ role: "user", id: turn.messageId }, { role: "assistant", tool_calls: [{ id: turn.callId }] }, { role: "tool", tool_call_id: turn.callId, id: turn.resultId }],
});
const sourceIds = (kind, payload) => [...new Set(kind === "responses"
? payload.items.map((item) => item.id || item.call_id)
: payload.messages.flatMap((item) => [item.id, item.tool_call_id, item.tool_calls?.[0]?.id]).filter(Boolean))];
const compact = (ids) => ({ owner: "application-generated", sourceIds: [...ids], summaryId: "summary_" + crypto.createHash("sha256").update(ids.join("|")).digest("hex").slice(0, 12) });
const originalResponses = responsesAdapter(1, 1);
const changedResponses = responsesAdapter(1, 2, "resp_fixture_1");
const retryResponses = responsesAdapter(2, 2, "resp_fixture_1");
const changedChat = chatAdapter(1, 2);
const retryChat = chatAdapter(2, 2);
assert.equal(originalResponses.instructions.version, 1);
assert.equal(changedResponses.instructions.version, 2);
assert.equal(changedResponses.instructions.owner, "application");
assert.equal(retryResponses.attempt, 2);
assert.equal(retryChat.attempt, 2);
assert.equal(changedResponses.previousResponseId, "resp_fixture_1");
const responseIds = sourceIds("responses", retryResponses);
const chatIds = sourceIds("chat", retryChat);
assert.deepEqual(responseIds, [turn.messageId, turn.callId, turn.resultId]);
assert.deepEqual(chatIds, responseIds);
const beforeCompaction = compact(responseIds);
const afterCompaction = compact([...responseIds, "msg_2"]);
assert.notEqual(beforeCompaction.summaryId, afterCompaction.summaryId);
assert.equal(beforeCompaction.owner, "application-generated");
const effects = new Map();
for (const attempt of [changedResponses, retryResponses]) effects.set(turn.workId, { workId: turn.workId, resultId: turn.resultId, latestAttempt: attempt.attempt });
assert.equal(effects.size, 1);
assert.deepEqual(effects.get(turn.workId), { workId: "work_order_17", resultId: "result_1", latestAttempt: 2 });
console.log(JSON.stringify({ instructionVersions: [1, 2], retryAttempts: [1, 2], compaction: beforeCompaction, workOwner: "application", providerPointerOwner: "provider-reference" }));
console.log("PASS: two adapters preserve one application work id");
Run node responses-chat-state-fixture.mjs. Expected receipt: PASS: two adapters preserve one application work id.
Design retries around effects, not requests
A network failure can leave three different uncertainties: the provider may not have accepted the inference request, the model may have emitted a tool call you did not receive, or the application may have committed a tool effect before its acknowledgement disappeared. Retrying the whole turn blindly can repeat inference and effects. The coordinator must reconcile each boundary independently.
For provider calls, preserve the request fingerprint and response pointer when documentation supports it. For tools, require idempotency or a lookup by application work ID. For the final answer, decide whether regeneration is acceptable or whether a stored accepted answer must be replayed. Responses API vs Chat Completions offers different context handles, yet neither endpoint can infer whether your warehouse reservation already committed.
The frozen retry case marks the model answer as regenerable, the lookup result as reusable, and the durable work receipt as authoritative. The fixture mutates a transport-attempt number while keeping the work ID stable. That separation is the practical reason to avoid making previous_response_id or the last messages-array index your product's resume cursor.
Compact only through a named boundary
Long-running agents eventually need less context. Compaction is not deletion by token count; it is a semantic projection with an input range, output summary, omissions, and a versioned policy. Keep durable facts and effect receipts outside the compacted prompt. Record which source event range a summary covers so later repair can rebuild or replace it.
With Chat Completions, the application commonly chooses the retained messages and inserts a summary. With Responses, provider-managed context can reduce client resending, but the application still decides when its own domain history is summarized or discarded. Responses API vs Chat Completions should be tested at a compaction boundary because ordinary happy-path turns conceal which assumptions are no longer reconstructible.
The teaching corpus compacts the first message and tool result into a summary item while retaining their source IDs in an application receipt. Its expected output lists the summary ID as regenerated and the source range as application-owned. The background mode vs Batch API comparison is useful when a turn also outlives an interactive request; execution lifetime is a separate axis from context custody.
- The domain turn names user input, requested tools, and accepted results.
- Each adapter serializes that turn into its API-specific representation.
- A comparison fixture checks what survives retries, changed instructions, and compaction.
Place the migration seam around a domain turn
Define one internal turn with policy version, user input, available tools, prior accepted facts, requested call, validated result, and completion status. Write one serializer for Responses and one for Chat Completions. The OpenAI migration guide can then inform field mapping without forcing product code to understand both wire formats everywhere.
Run matched cases for a simple answer, tool success, tool error, changed instructions, retry, compaction, and missing prior state. Compare normalized outcomes rather than raw JSON equality. A Responses API vs Chat Completions decision should be reversible at this seam until operational evidence supports removing the older adapter. Avoid claims about universal speed or cost unless both paths are benchmarked with committed inputs and equivalent service settings.
Log the adapter name, API model, policy version, domain-turn hash, provider response ID when present, and application work IDs. Exclude sensitive prompt content by default. This receipt lets a reviewer determine whether a difference came from state projection, model behavior, tool execution, or product policy.
Choose the ledger you can operate
Choose Responses when typed items, integrated tools, and provider-linked continuation reduce application ceremony while fitting your retention policy. Choose Chat Completions when an explicit application-supplied message projection is a deliberate requirement or an existing system already operates it well. Mixed estates are normal during migration, but a single run should have one authoritative adapter path and one application work ledger.
Before shipping, test expired or unavailable prior state, duplicate tool delivery, instruction replacement, rejected arguments, an effect committed before timeout, and compaction repair. Document what the provider stores, what the client resends, what the product persists, and which record allows recovery. Responses API vs Chat Completions is successful when an on-call engineer can answer those custody questions from the receipt without reconstructing an array by intuition.
Keep the conclusion narrow: the Responses state model is often the more direct fit for new item-oriented agents, while Chat Completions remains workable with disciplined message ownership. The right choice is the one whose context behavior and failure recovery your team can explain, test, and reverse.
Continue into opaque stateless continuity
Preserve encrypted reasoning items for stateless agents without claiming plaintext visibility or product ownership of hidden reasoning.