The openai python sdk claude sonnet 4.5 pairing works without custom adapters if you route through an OpenAI-compatible inference gateway. This tutorial builds a minimal but production-minded integration: authenticated client, chat calls, streaming, multi-turn context, and usage inspection. You will end with code you can paste into a service and ship.
Prerequisites
- Python 3.10 or newer
openaiPython package (v1.40+) andpython-dotenvfor local env loading- An API key for a gateway that speaks the OpenAI protocol. We use n4n.ai as the example host, which fronts 240+ models behind one endpoint.
- Basic comfort with
asynciois helpful but not required; the synchronous client is enough for most batch jobs.
Set your key in the environment before running anything:
export N4N_API_KEY="sk-..."
Step 1: Install and configure the client
Install the SDK. The OpenAI client is agnostic about the backend as long as the base URL returns the expected response shapes.
pip install openai python-dotenv
Point the client at the gateway’s OpenAI-compatible base URL. n4n.ai exposes a single OpenAI-compatible endpoint that addresses 240+ models, so the same client instance can call Claude Sonnet 4.5 or any other listed model without reinstantiation.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_API_KEY"],
)
If you prefer a .env file, load it with dotenv.load_dotenv() before reading os.environ.
Step 2: Your first chat completion
The model identifier follows the gateway’s naming convention. For Anthropic’s Sonnet 4.5, use anthropic/claude-sonnet-4.5. The openai python sdk claude sonnet 4.5 call is then a standard chat.completions.create.
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
messages=[
{"role": "system", "content": "You are a terse senior engineer."},
{"role": "user", "content": "Write a Python function to retry on timeout."}
],
max_tokens=200,
)
print(response.choices[0].message.content)
Expected output (abridged):
def retry_on_timeout(func, attempts=3, timeout=5):
import time
for i in range(attempts):
try:
return func()
except TimeoutError:
if i == attempts - 1:
raise
time.sleep(timeout)
The response object carries token accounting:
print(response.usage)
# CompletionUsage(prompt_tokens=24, completion_tokens=82, total_tokens=106)
Treat max_tokens as a hard ceiling, not a target. Claude will stop early when it finishes the thought.
Step 3: Streaming responses
For interactive UIs, stream tokens. Set stream=True and iterate deltas. The openai python sdk claude sonnet 4.5 streaming path uses server-sent events under the hood; you do not parse them manually.
stream = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
messages=[{"role": "user", "content": "Explain asyncio in 3 bullet points."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
Terminal output accumulates live:
- asyncio lets you write concurrent code using coroutines instead of threads.
- The event loop schedules I/O-bound tasks, switching when one awaits.
- `async`/`await` syntax makes suspension points explicit and readable.
Always flush or use end="" to avoid buffered line delays. If you abort mid-stream, close the iterator to release the connection.
Step 4: Multi-turn context and system prompts
Maintain conversation state by appending messages. Do not re-send the system prompt on every turn unless you want to reset tone. The openai python sdk claude sonnet 4.5 message format is identical to OpenAI’s, so your existing helpers transfer.
messages = [
{"role": "system", "content": "You answer only with valid JSON."},
{"role": "user", "content": "List two European capitals."},
]
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
messages=messages,
)
assistant_msg = resp.choices[0].message
messages.append(assistant_msg)
messages.append({"role": "user", "content": "Now add population count."})
resp2 = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
messages=messages,
)
print(resp2.choices[0].message.content)
Expected second response:
{
"capitals": [
{"city": "Paris", "population": 2161000},
{"city": "Berlin", "population": 3669000}
]
}
Keep the messages list server-side only if you trust your storage. For long sessions, summarize older turns to stay under the context window.
Step 5: Handling rate limits and retries
Transient 429s happen. The SDK retries by default with exponential backoff, but tune it for your SLA.
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_API_KEY"],
max_retries=3,
timeout=30,
)
Catch explicit rate-limit errors to add metrics or dead-letter logic:
from openai import RateLimitError
try:
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
messages=[{"role": "user", "content": "Ping"}],
)
except RateLimitError as e:
print(f"Rate limited: {e.status_code} {e.response}")
# push to queue, back off, alert
Because the gateway may perform automatic fallback when a provider is degraded, many transient failures never surface to your process. Still, defensive retries at the edge are sane engineering.
Step 6: Inspect token usage and metering
Every completion returns a usage object. Forward it to your logging pipeline. The gateway returns standard usage objects; n4n.ai meters per-token usage so you can reconcile against your own billing records.
def log_usage(resp):
u = resp.usage
return {
"model": resp.model,
"prompt_tokens": u.prompt_tokens,
"completion_tokens": u.completion_tokens,
"total_tokens": u.total_tokens,
}
print(log_usage(response))
For streaming, usage is only available on the final chunk:
stream = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
messages=[{"role": "user", "content": "Short poem about TCP."}],
stream=True,
)
final = None
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
if chunk.usage:
final = chunk.usage
print("\n", final)
Cache-control hints from the provider are forwarded by the gateway. If you send extra_headers={"anthropic-cache-control": "max-age=300"} on repeated prefixes, you will see reduced prompt_tokens on cache hits in the usage breakdown where supported.
Step 7: A reusable wrapper
Wrap the client in a small function to standardize model, timeouts, and error logging. This keeps the openai python sdk claude sonnet 4.5 invocation consistent across your codebase.
from openai import OpenAI, RateLimitError
def ask_claude(system: str, user: str, max_tokens=200) -> str:
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_API_KEY"],
max_retries=2,
)
try:
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user},
],
max_tokens=max_tokens,
)
return resp.choices[0].message.content or ""
except RateLimitError:
# fallback to queued job or cached response
raise
if __name__ == "__main__":
print(ask_claude("You are helpful.", "What is idempotency?"))
Run it:
python claude_client.py
You now have a typed, retry-aware, usage-aware integration that does not depend on Anthropic’s native SDK. Swapping to another model is a one-line change in the model argument.