DS DevShelfHub Projects · AI tools
Cheatsheets / OpenAI API
Cheatsheet · AI frameworks

OpenAI API: Chat Completions, Function Calling, Structured Output Reference Guide

By DevShelfHub

Chat Completions, Responses API, structured output, function calling, streaming, vision, embeddings, batch, and assistants — OpenAI Python SDK reference for GPT-4o, o-series reasoning models, and real-time API.

115 items 8 min Responses Tools Structured

Start hereQuick start · 6 you’ll reach for daily

Simple callclient.responses.create(model, input)
Structured outputclient.responses.parse(text_format=Schema)
Stream tokensstream=True → iterate events
Tool callingtools=[{type:"function", …}]
Multi-turnprevious_response_id=…
Embeddingsclient.embeddings.create(model, input)

Target versions · paceVersions

Targets: openai-python ≥ 1.50 openai-node ≥ 4.60 API: Responses + Chat Completions python ≥ 3.9

The Responses API (/v1/responses) is the modern endpoint — built-in tools, server-side state via previous_response_id, typed streaming events. Chat Completions (/v1/chat/completions) still works and stays supported for now, but new features land on Responses first. The legacy Assistants API is being deprecated in favor of Responses + the conversations primitive. Default to Responses for anything new.

Install · envSetup

bash
# Python SDK — official client
pip install openai          # ≥ 1.50 for Responses API
pip install openai[aiohttp] # async HTTP transport

# Node / TS SDK
npm install openai

# env — picked up automatically
export OPENAI_API_KEY=sk-proj-...
export OPENAI_ORG_ID=org-...        # optional, for multi-org
export OPENAI_PROJECT_ID=proj_...   # optional, scoped key

Where things liveCommon imports

One client, one namespace. OpenAI() picks up OPENAI_API_KEY from env. Use AsyncOpenAI in async code.

from openai import OpenAISync client. The 90% case.
from openai import AsyncOpenAIAsync client for FastAPI / asyncio.
from openai import OpenAI; client = OpenAI(api_key="…", base_url="…")Override key + URL (Azure, proxies, OSS-compatible servers).
from openai import APIError, RateLimitError, BadRequestError, APITimeoutErrorException types you should catch.
from openai.types.responses import Response, ResponseStreamEventType hints for Responses API.
from openai.types.chat import ChatCompletion, ChatCompletionMessageParamType hints for Chat Completions.
from pydantic import BaseModelSchemas for responses.parse().

Pick the right oneModels

Text & multimodal

gpt-4.1Flagship reasoning + coding. 1M-token context.
gpt-4.1-miniStrong default. Cheaper, fast, still multimodal.
gpt-4.1-nanoCheapest 4.1. Use for classify / extract.
gpt-4o, gpt-4o-miniPrevious-gen multimodal. Still widely deployed.
o4-mini, o3Reasoning models. Slower, better at chains-of-thought.
gpt-3.5-turboLegacy Cheap but dated. Prefer 4.1-nano.

Embeddings, audio, image

text-embedding-3-small1536-dim. Default embedding model.
text-embedding-3-large3072-dim. Highest quality. Supports dim reduction.
gpt-4o-transcribe, gpt-4o-mini-transcribeSpeech-to-text. Replaces whisper-1 for most cases.
gpt-4o-mini-ttsText-to-speech with prompt steering.
gpt-image-1Image generation + edit. Replaces dall-e-3.
Model names move. List live IDs with client.models.list(). Pin your model in code — OpenAI rotates the -latest aliases without notice.

The modern endpointResponses API

Basic call

client.responses.create(model="gpt-4.1-mini", input="hi")Simplest form. String → one-shot response.
resp.output_textConcatenated text of all output items. The shortcut you want.
resp.outputList of typed items (message, function_call, reasoning…).
resp.idPass to previous_response_id on the next turn.
resp.usage.total_tokensInput + output token counts.

Inputs & system prompt

input="single user message"String shorthand. Equivalent to one user item.
input=[{"role":"user","content":"…"}]Structured input. Use for multi-message turns.
instructions="You are a tutor."System-prompt slot. Preferred over a role:system message.
input=[{"role":"user","content":[{"type":"input_image","image_url":"…"}]}]Vision input. URL or base64 data URI.

Stateful multi-turn

previous_response_id=resp.idServer stores prior turn — no need to resend history.
store=FalseOpt out of server-side storage. Default is True.
conversation="conv_…"New Conversations object — longer-lived than a single response chain.

Params worth knowing

temperature=0.0…2.00 for extraction / code, 0.7–1.0 for prose.
top_p=0.0…1.0Nucleus sampling. Tune temperature OR top_p, not both.
max_output_tokens=512Cap output length. Different field name than Chat Completions.
reasoning={"effort":"low"}Reasoning models only: low / medium / high.
metadata={"user_id":"u_42"}Arbitrary tags. Show up in dashboard + exports.
user="u_42"End-user hash for abuse signals.

The classic endpointChat Completions

Still supported, still ubiquitous. Reach for it when integrating with libraries that haven’t adopted Responses yet.

client.chat.completions.create(model, messages=[…])The classic call.
messages=[{"role":"system","content":"…"},{"role":"user","content":"…"}]Required list of role+content dicts.
resp.choices[0].message.contentThe assistant’s text. Note the deep path.
resp.choices[0].message.tool_callsList of tool calls when tools are bound.
max_tokens=512Legacy name Use max_completion_tokens on reasoning models.
response_format={"type":"json_object"}JSON mode. Less strict than Responses’ parse().
client.chat.completions.parse(…, response_format=Schema)Structured-output helper. Pydantic model → strict JSON schema.
n=3Generate N candidates. Costs N× output tokens.
seed=42Best-effort determinism. Still not byte-stable.
Field-name diffs that bite: max_tokens (Chat) vs max_output_tokens (Responses); messages vs input; choices[0].message.content vs output_text.

JSON that parsesStructured output

client.responses.parse(model, input, text_format=Schema)Preferred Pydantic → strict JSON schema. Returns output_parsed.
client.chat.completions.parse(…, response_format=Schema)Same idea on Chat Completions.
text={"format":{"type":"json_schema","schema":{…},"strict":True}}Raw JSON Schema (no Pydantic).
response_format={"type":"json_object"}Legacy JSON mode. No schema enforcement — model picks fields.
resp.output_parsedPydantic instance, validated. Use this not output_text.
resp.refusalSet when the model declined. Always check before reading parsed data.

Worked example

python
from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()

class Invoice(BaseModel):
    vendor: str
    total: float
    line_items: list[str]

# Responses API — strict schema, no parsing
resp = client.responses.parse(
    model="gpt-4.1-mini",
    input="Extract from: ACME, $129.50, 2x widget, 1x cable",
    text_format=Invoice,
)

invoice: Invoice = resp.output_parsed  # typed, validated
print(invoice.vendor, invoice.total)

Function callingTools

tools=[{"type":"function","name":"…","description":"…","parameters":{…}}]Responses API tool shape. Flat.
tools=[{"type":"function","function":{"name":"…","parameters":{…}}}]Chat Completions tool shape. Nested under function.
tool_choice="auto" | "required" | "none"Force-call any tool, force no tool, or let the model decide.
tool_choice={"type":"function","name":"get_weather"}Pin a specific tool.
parallel_tool_calls=FalseDisable parallel calls when your tools must be sequential.
strict=True (inside parameters schema)Reject hallucinated fields. Required keys must be present.

Built-in tools (Responses only)

tools=[{"type":"web_search"}]Server-side web search. Results spliced in automatically.
tools=[{"type":"file_search","vector_store_ids":["vs_…"]}]Hosted RAG over a vector store.
tools=[{"type":"code_interpreter","container":{"type":"auto"}}]Run Python in a sandbox.
tools=[{"type":"computer_use_preview"}]Browser / desktop control.

Custom function loop

python
from openai import OpenAI
import json

client = OpenAI()

tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Current weather for a city.",
    "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}]

resp = client.responses.create(
    model="gpt-4.1-mini",
    input=[{"role": "user", "content": "Weather in Mumbai?"}],
    tools=tools,
)

# Execute the model's tool calls and feed results back
for call in resp.output:
    if call.type == "function_call":
        result = {"temp_c": 31, "city": json.loads(call.arguments)["city"]}
        followup = client.responses.create(
            model="gpt-4.1-mini",
            previous_response_id=resp.id,
            input=[{
                "type": "function_call_output",
                "call_id": call.call_id,
                "output": json.dumps(result),
            }],
        )
        print(followup.output_text)

Token-by-tokenStreaming

stream=TrueFlip to streaming. Same call shape, iterator return.
event.type == "response.output_text.delta"Token chunks. event.delta is the new text.
event.type == "response.function_call_arguments.delta"Streaming tool-call arguments.
event.type == "response.completed"Final event. Has full response + usage.
event.type == "error"Mid-stream error. Stop iterating.
client.responses.stream(…) as stream:Context-manager form. Auto-closes the SSE connection.
stream.get_final_response()Block-wait the full Response after iterating.
chunk.choices[0].delta.contentChat Completions streaming — flat string deltas.

Worked example

python
from openai import OpenAI

client = OpenAI()

# Responses API — SSE stream of typed events
stream = client.responses.create(
    model="gpt-4.1-mini",
    input="Write a haiku about Mumbai monsoons.",
    stream=True,
)

for event in stream:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)
    elif event.type == "response.completed":
        print(f"\n[done · {event.response.usage.total_tokens} tokens]")

Vectorize textEmbeddings

client.embeddings.create(model="text-embedding-3-small", input="…")Single string → one vector.
client.embeddings.create(…, input=["a","b","c"])Batched. Up to ~2048 inputs / call.
resp.data[i].embeddinglist[float] per input.
dimensions=512Truncate vector length. Only on -3 models.
encoding_format="base64"Compact wire format. Decode to float32 client-side.
Normalize before cosine. OpenAI embeddings are already L2-normalized, so cosine == dot product — skip the extra divide.

Hosted RAGFiles & vector stores

client.files.create(file=open("doc.pdf","rb"), purpose="assistants")Upload a file. Returns file_id.
client.vector_stores.create(name="kb")Create a hosted vector store.
client.vector_stores.files.create(vector_store_id, file_id)Attach + auto-chunk + embed.
client.vector_stores.files.create_and_poll(…)Same, blocks until indexing finishes.
client.vector_stores.search(id, query="…")Direct vector search without going through a model.
tools=[{"type":"file_search","vector_store_ids":[…]}]Wire the store into a Responses call.

Beyond textImages · audio · batch

Image generation

client.images.generate(model="gpt-image-1", prompt="…", size="1024x1024")Generate. Returns b64-encoded image by default.
client.images.edit(image=open("in.png","rb"), prompt="…", mask=…)Inpaint with an alpha mask.
quality="high" | "medium" | "low"Trade quality for cost / latency.
response_format="url"Get a temporary URL instead of b64 bytes.

Audio

client.audio.transcriptions.create(model="gpt-4o-transcribe", file=open("a.mp3","rb"))Speech → text.
client.audio.translations.create(…)Translate non-English audio to English.
client.audio.speech.create(model="gpt-4o-mini-tts", voice="alloy", input="…")Text → speech. Stream-able.

Batch API

client.batches.create(input_file_id, endpoint="/v1/responses", completion_window="24h")50% cheaper. Up to 24h SLA.
client.batches.retrieve(batch_id)Poll for completion.
client.files.content(batch.output_file_id)Download JSONL results.

Production hygieneAuth, errors, retries

OpenAI(api_key=…, organization=…, project=…)Override env. Project-scoped keys are the default now.
OpenAI(max_retries=4, timeout=30.0)SDK-level retry + timeout. Defaults are 2 retries, 600s timeout.
OpenAI(base_url="https://…")Point at Azure / proxy / OSS-compatible (vLLM, Ollama).
RateLimitError429. Backoff. Watch x-ratelimit-reset-* headers.
BadRequestError400. Schema mismatch, content policy, model not found. Do not retry.
APITimeoutErrorClient-side deadline. Safe to retry.
APIConnectionErrorNetwork failure. Retry with backoff.
client.with_options(timeout=60).responses.create(…)Per-call override without rebuilding the client.
resp.response.headers["x-request-id"]Pass to OpenAI support when filing bugs.

Full pipeline · ~30 linesEnd-to-end · Minimal RAG

Embed a corpus, retrieve the top chunk by cosine, ground the answer in it. No vector DB, no framework — just the SDK.

python
from openai import OpenAI
import numpy as np

client = OpenAI()

# 1 · Embed your corpus (in real life, store these in a vector DB)
docs = [
    "Mumbai's monsoon runs June through September.",
    "The Bandra-Worli Sea Link opened in 2009.",
    "Vada pav originated in Mumbai in the 1960s.",
]
emb = client.embeddings.create(
    model="text-embedding-3-small",
    input=docs,
)
vectors = np.array([d.embedding for d in emb.data])

# 2 · Embed the query, cosine-rank
q = "When does the rainy season start?"
qv = np.array(client.embeddings.create(
    model="text-embedding-3-small", input=q,
).data[0].embedding)
top = docs[int(np.argmax(vectors @ qv))]

# 3 · Answer grounded in the top chunk
resp = client.responses.create(
    model="gpt-4.1-mini",
    instructions="Answer using only the context. Cite it verbatim.",
    input=f"Context: {top}\n\nQuestion: {q}",
)
print(resp.output_text)

Best practiceGood to know

Use previous_response_id over resending history. Cheaper, faster, and the model sees the exact prior turns — no token-count drift from your client-side trimming.
Pin the model. gpt-4.1-mini-2025-04-14 won’t change under you; gpt-4.1-mini-latest will. Pin in prod, float in dev.
Pydantic schemas beat hand-written JSON schemas. responses.parse() handles the schema conversion, strict-mode flags, and the parse-into-object step in one call.

Common trapsWatch out for

Responses and Chat Completions use different parameter names. max_tokens vs max_output_tokens, messages vs input, response_format vs text.format. Don’t copy-paste across endpoints.
Strict structured output rejects extra fields silently. If the model wants to add a property your schema doesn’t allow, you get a refusal, not a soft warning. Check resp.refusal.
Streaming + tool calls = reassembly required. Tool arguments arrive as a stream of JSON-string deltas. You must concatenate them before json.loads.

Go deeperSee also

OpenAI API FAQ

What is the OpenAI Chat Completions API?

The Chat Completions API (POST /v1/chat/completions) takes a messages array with alternating user and assistant turns and returns the model's next message. It supports GPT-4o, GPT-4o-mini, and o-series reasoning models. Pass temperature, max_tokens, and response_format to control output style and format.

What is the difference between the Responses API and Chat Completions?

The Responses API (POST /v1/responses, launched 2025) is a newer stateful API with built-in tools (web search, code interpreter, file search) and automatic conversation state management. Chat Completions is stateless — you manage the message history yourself. Use Responses for agentic workflows; Chat Completions for fine-grained control.

How do I use function calling in the OpenAI API?

Pass a tools array where each tool has a type: "function", a name, a description, and a parameters JSON Schema. When the model decides to call a function, it returns a tool_calls list instead of content. You execute the function and send the result back in a tool message. The model then generates its final response.

How do I get structured JSON output from the OpenAI API?

Set response_format to {"type": "json_schema", "json_schema": {"name": "...", "schema": {...}}} to constrain output to a specific JSON Schema — guaranteed valid JSON matching your schema. For simpler cases, {"type": "json_object"} ensures valid JSON without a schema constraint. Both modes work with Chat Completions.

How do I stream OpenAI API responses?

Pass stream=True to client.chat.completions.create(). The response becomes an iterator that yields ChatCompletionChunk objects. Each chunk has choices[0].delta.content with the next token fragment. Use the context manager with client.chat.completions.stream(...) for automatic cleanup and to access the final message after iteration.

Does the OpenAI API support vision and image inputs?

Yes. Pass an image_url content block with type: "image_url" and a URL or base64-encoded data URI in the messages array. GPT-4o and GPT-4o-mini support vision. You can send multiple images per message. Use detail: "low" to reduce token cost for images where fine detail is not needed.