Python
Python examples with the Anthropic and OpenAI SDKs - a plain request, streaming, a tool call round trip, an image, counting tokens, listing models and handling errors.
When to use this page
You are calling EZAT Router from Python with the official Anthropic SDK (anthropic) or OpenAI SDK (openai). The raw HTTP requests are on curl.
Set up
Install the SDK you want (or both), and put your key in an environment variable:
pip install anthropic openai
export EZAT_API_KEY="ezat_sk_…"Each SDK needs only its base URL changed:
| SDK | base_url | Endpoint it calls |
|---|---|---|
anthropic | https://api.router.ezat.io | /v1/messages |
openai | https://api.router.ezat.io/openai/v1 | /openai/v1/chat/completions |
Both SDKs also read the base URL and key from the environment, which is handy for tools you don't write yourself: ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY, or OPENAI_BASE_URL and OPENAI_API_KEY.
A plain request
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])
message = client.messages.create(
model="anthropic/claude-sonnet-5-5",
max_tokens=64,
messages=[{"role": "user", "content": "Say hello in five words."}],
)
for block in message.content:
if block.type == "text":
print(block.text)
print(message.usage)
print("request-id:", message._request_id)usage has the token counts you are charged for; see Pricing.
Streaming
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])
with client.messages.stream(
model="anthropic/claude-sonnet-5-5",
max_tokens=64,
messages=[{"role": "user", "content": "Count from one to five."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
message = stream.get_final_message()
print()
print(message.usage)A tool call round trip
The model asks for a tool, your code runs it, and you send the result back with the conversation so far.
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])
tools = [
{
"name": "get_weather",
"description": "Get the current weather in a city.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
]
messages = [{"role": "user", "content": "What is the weather in Paris? Use the get_weather tool."}]
first = client.messages.create(model="anthropic/claude-sonnet-5-5", max_tokens=64, tools=tools, messages=messages)
call = next((block for block in first.content if block.type == "tool_use"), None)
if call is None:
raise SystemExit(f"No tool call (stop_reason: {first.stop_reason})")
print("Tool call:", call.name, call.input)
# Run the tool, then send its result back.
messages.append({"role": "assistant", "content": first.content})
messages.append(
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": call.id, "content": "Sunny and 18 degrees Celsius"}
],
}
)
second = client.messages.create(model="anthropic/claude-sonnet-5-5", max_tokens=64, tools=tools, messages=messages)
for block in second.content:
if block.type == "text":
print(block.text)An image
Images go inline as base64. This one is an 8×8 red PNG; for a file of your own, use base64.b64encode(open("photo.png", "rb").read()).decode().
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])
png = "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR42mP4z8CAFTEMLQkAKP8/wc53yE8AAAAASUVORK5CYII="
message = client.messages.create(
model="anthropic/claude-sonnet-5-5",
max_tokens=64,
messages=[
{
"role": "user",
"content": [
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": png}},
{"type": "text", "text": "What colour is this image? Answer in one word."},
],
}
],
)
for block in message.content:
if block.type == "text":
print(block.text)Count tokens
Anthropic SDK only. Counting is free, but it counts toward the key's requests per minute.
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])
count = client.messages.count_tokens(
model="anthropic/claude-sonnet-5-5",
messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(count.input_tokens)List models
These list the models your key can call. See Models for the public catalog with prices.
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])
for model in client.models.list():
print(model.id, model.display_name)Handle errors
Each SDK raises its own exception classes. The SDKs already retry connection errors, 429 and 5xx twice, so an exception you catch has run out of retries (raise max_retries on the client to try longer). This request asks for a model that doesn't exist, so it fails with 404.
import os
import anthropic
client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])
try:
client.messages.create(
model="anthropic/no-such-model",
max_tokens=16,
messages=[{"role": "user", "content": "Hi"}],
)
except anthropic.APIConnectionError as error:
raise SystemExit(f"Could not reach the API: {error}")
except anthropic.RateLimitError as error:
print("Rate limited. Retry after", error.response.headers.get("retry-after"), "seconds")
except anthropic.APIStatusError as error:
print("status:", error.status_code, "type:", error.type)
# request-id is set once a request reaches a model; x-request-id is on every response.
print("request-id:", error.request_id)
print("x-request-id:", error.response.headers.get("x-request-id"))| Anthropic SDK | OpenAI SDK | Status |
|---|---|---|
BadRequestError | BadRequestError | 400 |
AuthenticationError | AuthenticationError | 401 |
APIStatusError with status_code 402 | APIStatusError with status_code 402 | 402: add credit |
PermissionDeniedError | PermissionDeniedError | 403 |
NotFoundError | NotFoundError | 404 |
RequestTooLargeError | APIStatusError with status_code 413 | 413 |
RateLimitError | RateLimitError | 429 |
InternalServerError | InternalServerError | 500 and above |
OverloadedError | InternalServerError | Overloaded: 529 (Anthropic format), 503 (OpenAI format) |
APIConnectionError | APIConnectionError | No response: network problem or timeout |
What each status means and whether to retry it is on Errors.
For AI agents
- Set
base_urltohttps://api.router.ezat.ioforanthropic, orhttps://api.router.ezat.io/openai/v1foropenai, and read the key fromos.environ["EZAT_API_KEY"]. - Use model ids from
GET https://api.router.ezat.io/v1/models; don't invent them. max_tokensis required with the Anthropic SDK.- Don't add your own retry loop on top of the SDK's for 429 and 5xx unless you need more than its two retries; raise
max_retriesinstead. - Log
request-id(orx-request-id) from errors so the user can give it to support@ezat.io.