Chat Completions Compatibility Reference
Chat Completions is the lingua franca of LLM serving — and because everyone
has implemented and extended it longest, it carries the MOST undocumented
divergence of the three open protocols. OpenAI keeps it fully supported but
second-choice ("we recommend trying Responses") — and since 2026-07-29
OpenAI's own publications call it "our legacy Chat Completions API"
(ARC-AGI-3 post; still no deprecation or sunset date); xAI, Groq, and Azure
declare it legacy too. Legacy /v1/completions loses its last
first-party OpenAI models 2026-09-28 and survives as a local/third-party
surface.
Fleet property: stateless. Full history resent every turn, like
Messages; no server-side session state to break load-balanced fleets
(contrast responses-api's previous_response_id).
Last refreshed: 2026-07-19 (source-examined at commits of 2026-07-16..18;
provenance in references/sources.md).
Critical Gotchas
- The reasoning-field schism:
reasoning_content (SGLang, llama.cpp,
mistral.rs, DeepSeek, xAI) vs reasoning (vLLM, Ollama, Together, Groq,
OpenRouter) vs inline <think> — and vLLM silently RENAMES incoming
reasoning_content→reasoning. Servers should emit both; clients should
read both. Full table: references/backend-implementations.md.
- Reasoning must be passed BACK in tool loops on DeepSeek v4 (with tool
calls) and OpenRouter (exact block sequence) — clients that strip
reasoning break agentic loops.
references/cloud-compat.md.
- Silent-drop vs hard-400 split: Ollama silently drops
tool_choice
(forced tool calls no-op!) and max_completion_tokens (unbounded
generation); Groq and xAI-reasoning hard-400 on specific params; LiteLLM
raises UnsupportedParamsError as HTTP 500. Identify the target's
failure mode before debugging.
- finish_reason is not a closed enum:
abort (vLLM, SGLang),
repetition, canceled/generated_image (mistral.rs); llama.cpp
DEFAULTS to length; named tool_choice returns stop on vLLM/OpenAI but
tool_calls on SGLang/mistral.rs. Parse tolerantly, key loops on
tool_calls.
seed is a lottery: honored (vLLM, llama.cpp), silent no-op unless
server flag (SGLang --enable-deterministic-inference), ignored
(mistral.rs) — and OpenAI has formally deprecated seed AND
system_fingerprint.
json_object ≠ json mode everywhere: SGLang and mistral.rs implement
it as schema {"type":"object"} — top-level arrays forbidden.
json_schema.strict is ignored by ALL local servers (always fully
enforced anyway).
- Tool-call streaming split: whole-blob single delta (Ollama,
mistral.rs) vs incremental argument diffs (vLLM, SGLang, llama.cpp).
First delta must carry
id+function.name or AI-SDK clients kill the
stream; loose parsers finalize args the moment they parse as JSON.
- Cached-token reporting is flag-gated: vLLM
--enable-prompt-tokens-details, SGLang --enable-cache-report;
llama.cpp reports always; Ollama never. OpenAI CC now has explicit
caching (prompt_cache_options + per-block breakpoints,
cache_write_tokens billed 1.25×): references/spec.md.
data: [DONE] is not universal — Llama Stack/OGX never sends it; AI
SDK clients ignore it, official-SDK clients expect it. Send it; don't
require it.
Quick Reference
When invoked with a topic argument (spec, backends, gateways, cloud,
clients), load that reference file first and answer from it. Without an
argument, pick by question shape: official params/deprecations/what-CC-gets
→ spec; which-local-server-does-what → backends; proxy/routing behavior →
gateways; hosted-provider compat → cloud; what-clients-send / parser
tolerance → clients.
- Spec:
references/spec.md — full request/response/chunk schema
highlights, explicit prompt caching, stored completions, legacy
/v1/completions + shutdown timeline, deprecation archaeology,
Responses-only feature delta
- Backends:
references/backend-implementations.md — divergence matrix
- per-server sections for the 7 local servers
- Gateways:
references/gateways.md — LiteLLM param cascade and prefix
routing, Bifrost, Superagent (CC outbound only), gateway-tax table
- Cloud:
references/cloud-compat.md — 10 providers' CC-compat
endpoints and the cross-provider gotcha matrix
- Clients:
references/clients.md — opencode's three CC paths, AI SDK
parser tolerance, server tolerance checklist
- Sources:
references/sources.md — dated per-URL index with commits
examined and the live-verification log
Translation between protocols is homed elsewhere: CC↔Messages mapping in
the messages-api skill, CC↔Responses mapping in the responses-api skill.
Procedures
Pointing an OpenAI-SDK client at a local backend
- Base URL
http://host:port/v1 (SDKs append /chat/completions); auth
Bearer anything unless the server enforces keys.
- Check the backend's row in the divergence matrix FIRST — especially
Ollama (
tool_choice and max_completion_tokens silently dropped) and
reasoning field naming.
- For agent clients (opencode etc.): use the openai-COMPATIBLE provider
path, not the openai provider — the openai path model-id-sniffs
("o3-…" gets temperature stripped), drops reasoning fields, and its
factory default is the Responses API (
references/clients.md).
- Reasoning models: confirm how thinking is toggled (
reasoning_effort
mapping vs chat_template_kwargs.enable_thinking vs vendor thinking
fields) and which field the CoT comes back in.
- Sanity curl:
curl -sS http://host:port/v1/chat/completions -H "Content-Type: application/json" \
-d '{"model":"<name>","max_tokens":32,"messages":[{"role":"user","content":"Say OK."}]}'
Expect object:"chat.completion", non-null choices[0].message.content,
finish_reason of stop OR length (llama.cpp defaults to length).
An error body here → check the server's error-envelope row in the matrix
before parsing (mistral.rs sends {"message"}, not {"error":{...}}).
Serving CC through LiteLLM to a fleet
- Prefix decides fidelity:
openai/<model> = full surface passthrough;
hosted_vllm/<model> = tool schemas silently edited (strict +
additionalProperties stripped). Pick deliberately.
- Set
drop_params: true (or per-model additional_drop_params) —
otherwise unsupported params surface as HTTP 500 UnsupportedParamsError.
- Statelessness makes any replica valid — no affinity needed.
Debugging a CC streaming issue
- Capture raw SSE:
curl -sN http://host:port/v1/chat/completions -H "Content-Type: application/json" \
-d '{"model":"<name>","max_tokens":64,"stream":true,"stream_options":{"include_usage":true},"messages":[{"role":"user","content":"hi"}]}' | head -40
- Expect data-only SSE: first chunk
delta.role, content/tool deltas keyed
by index, finish chunk, optional choices:[] usage chunk, [DONE].
- Check the backend's streaming row in the matrix for known deviations
(always-present null keys on SGLang, vendor
timings on llama.cpp,
role-on-every-chunk + whole-blob tools on Ollama, raw non-JSON error
lines on mistral.rs, missing [DONE] on OGX).
- If tool calls vanish client-side: check whether the first delta carried
id+function.name, and whether the client requires
tool_calls[].index (references/clients.md).
1---2name: chat-completions-api3description: Reference for the OpenAI Chat Completions API (/v1/chat/completions) and legacy /v1/completions as the lingua-franca compatibility protocol — the official spec incl. deprecation timeline and Responses-only feature delta, how 7 local servers (vLLM, SGLang, llama.cpp, Ollama, mistral.rs, Llama Stack/OGX, Lemonade) actually implement it, gateways (LiteLLM, Bifrost), 10 cloud providers' CC-compat endpoints (Anthropic, Gemini, DeepSeek, xAI, Groq, OpenRouter, Azure...), the reasoning_content/reasoning field schism, finish_reason divergences, and client wire behavior (opencode, Vercel AI SDK). NOT for the Responses API (responses-api skill) or Anthropic Messages protocol (messages-api skill).4---56# Chat Completions Compatibility Reference78Chat Completions is the lingua franca of LLM serving — and because everyone9has implemented and extended it longest, it carries the MOST undocumented10divergence of the three open protocols. OpenAI keeps it fully supported but11second-choice ("we recommend trying Responses") — and since 2026-07-2912OpenAI's own publications call it "our **legacy** Chat Completions API"13(ARC-AGI-3 post; still no deprecation or sunset date); xAI, Groq, and Azure14declare it legacy too. Legacy `/v1/completions` loses its last15first-party OpenAI models 2026-09-28 and survives as a local/third-party16surface.1718**Fleet property: stateless.** Full history resent every turn, like19Messages; no server-side session state to break load-balanced fleets20(contrast responses-api's `previous_response_id`).2122**Last refreshed**: 2026-07-19 (source-examined at commits of 2026-07-16..18;23provenance in `references/sources.md`).2425## Critical Gotchas2627- **The reasoning-field schism**: `reasoning_content` (SGLang, llama.cpp,28 mistral.rs, DeepSeek, xAI) vs `reasoning` (vLLM, Ollama, Together, Groq,29 OpenRouter) vs inline `<think>` — and vLLM silently RENAMES incoming30 `reasoning_content`→`reasoning`. Servers should emit both; clients should31 read both. Full table: `references/backend-implementations.md`.32- **Reasoning must be passed BACK in tool loops** on DeepSeek v4 (with tool33 calls) and OpenRouter (exact block sequence) — clients that strip34 reasoning break agentic loops. `references/cloud-compat.md`.35- **Silent-drop vs hard-400 split**: Ollama silently drops `tool_choice`36 (forced tool calls no-op!) and `max_completion_tokens` (unbounded37 generation); Groq and xAI-reasoning hard-400 on specific params; LiteLLM38 raises UnsupportedParamsError as HTTP **500**. Identify the target's39 failure mode before debugging.40- **finish_reason is not a closed enum**: `abort` (vLLM, SGLang),41 `repetition`, `canceled`/`generated_image` (mistral.rs); llama.cpp42 DEFAULTS to `length`; named tool_choice returns `stop` on vLLM/OpenAI but43 `tool_calls` on SGLang/mistral.rs. Parse tolerantly, key loops on44 `tool_calls`.45- **`seed` is a lottery**: honored (vLLM, llama.cpp), silent no-op unless46 server flag (SGLang `--enable-deterministic-inference`), ignored47 (mistral.rs) — and OpenAI has formally deprecated `seed` AND48 `system_fingerprint`.49- **`json_object` ≠ json mode everywhere**: SGLang and mistral.rs implement50 it as schema `{"type":"object"}` — top-level arrays forbidden.51 `json_schema.strict` is ignored by ALL local servers (always fully52 enforced anyway).53- **Tool-call streaming split**: whole-blob single delta (Ollama,54 mistral.rs) vs incremental argument diffs (vLLM, SGLang, llama.cpp).55 First delta must carry `id`+`function.name` or AI-SDK clients kill the56 stream; loose parsers finalize args the moment they parse as JSON.57- **Cached-token reporting is flag-gated**: vLLM58 `--enable-prompt-tokens-details`, SGLang `--enable-cache-report`;59 llama.cpp reports always; Ollama never. OpenAI CC now has explicit60 caching (`prompt_cache_options` + per-block breakpoints,61 `cache_write_tokens` billed 1.25×): `references/spec.md`.62- **`data: [DONE]` is not universal** — Llama Stack/OGX never sends it; AI63 SDK clients ignore it, official-SDK clients expect it. Send it; don't64 require it.6566## Quick Reference6768When invoked with a topic argument (`spec`, `backends`, `gateways`, `cloud`,69`clients`), load that reference file first and answer from it. Without an70argument, pick by question shape: official params/deprecations/what-CC-gets71→ spec; which-local-server-does-what → backends; proxy/routing behavior →72gateways; hosted-provider compat → cloud; what-clients-send / parser73tolerance → clients.7475- **Spec**: `references/spec.md` — full request/response/chunk schema76 highlights, explicit prompt caching, stored completions, legacy77 /v1/completions + shutdown timeline, deprecation archaeology,78 Responses-only feature delta79- **Backends**: `references/backend-implementations.md` — divergence matrix80 + per-server sections for the 7 local servers81- **Gateways**: `references/gateways.md` — LiteLLM param cascade and prefix82 routing, Bifrost, Superagent (CC outbound only), gateway-tax table83- **Cloud**: `references/cloud-compat.md` — 10 providers' CC-compat84 endpoints and the cross-provider gotcha matrix85- **Clients**: `references/clients.md` — opencode's three CC paths, AI SDK86 parser tolerance, server tolerance checklist87- **Sources**: `references/sources.md` — dated per-URL index with commits88 examined and the live-verification log8990Translation between protocols is homed elsewhere: CC↔Messages mapping in91the messages-api skill, CC↔Responses mapping in the responses-api skill.9293## Procedures9495### Pointing an OpenAI-SDK client at a local backend961. Base URL `http://host:port/v1` (SDKs append `/chat/completions`); auth97 `Bearer` anything unless the server enforces keys.982. Check the backend's row in the divergence matrix FIRST — especially99 Ollama (`tool_choice` and `max_completion_tokens` silently dropped) and100 reasoning field naming.1013. For agent clients (opencode etc.): use the openai-COMPATIBLE provider102 path, not the openai provider — the openai path model-id-sniffs103 ("o3-…" gets temperature stripped), drops reasoning fields, and its104 factory default is the Responses API (`references/clients.md`).1054. Reasoning models: confirm how thinking is toggled (`reasoning_effort`106 mapping vs `chat_template_kwargs.enable_thinking` vs vendor `thinking`107 fields) and which field the CoT comes back in.1085. Sanity curl:109 ```bash110 curl -sS http://host:port/v1/chat/completions -H "Content-Type: application/json" \111 -d '{"model":"<name>","max_tokens":32,"messages":[{"role":"user","content":"Say OK."}]}'112 ```113 Expect `object:"chat.completion"`, non-null `choices[0].message.content`,114 `finish_reason` of `stop` OR `length` (llama.cpp defaults to `length`).115 An error body here → check the server's error-envelope row in the matrix116 before parsing (mistral.rs sends `{"message"}`, not `{"error":{...}}`).117118### Serving CC through LiteLLM to a fleet1191. Prefix decides fidelity: `openai/<model>` = full surface passthrough;120 `hosted_vllm/<model>` = tool schemas silently edited (strict +121 additionalProperties stripped). Pick deliberately.1222. Set `drop_params: true` (or per-model `additional_drop_params`) —123 otherwise unsupported params surface as HTTP 500 UnsupportedParamsError.1243. Statelessness makes any replica valid — no affinity needed.125126### Debugging a CC streaming issue1271. Capture raw SSE:128 ```bash129 curl -sN http://host:port/v1/chat/completions -H "Content-Type: application/json" \130 -d '{"model":"<name>","max_tokens":64,"stream":true,"stream_options":{"include_usage":true},"messages":[{"role":"user","content":"hi"}]}' | head -40131 ```1322. Expect data-only SSE: first chunk `delta.role`, content/tool deltas keyed133 by `index`, finish chunk, optional `choices:[]` usage chunk, `[DONE]`.1343. Check the backend's streaming row in the matrix for known deviations135 (always-present null keys on SGLang, vendor `timings` on llama.cpp,136 role-on-every-chunk + whole-blob tools on Ollama, raw non-JSON error137 lines on mistral.rs, missing [DONE] on OGX).1384. If tool calls vanish client-side: check whether the first delta carried139 `id`+`function.name`, and whether the client requires140 `tool_calls[].index` (`references/clients.md`).