datarekha

OpenAI and Anthropic SDKs

The modern way to call frontier models from Python — the Responses API for OpenAI, the Messages API for Anthropic, side by side with the patterns you actually ship.

9 min read Intermediate Python Lesson 39 of 41

What you'll learn

  • The current shape of OpenAI and Anthropic SDK calls
  • Common arguments — model, messages, max_tokens, temperature, system
  • Tool calling at a glance, and the differences between providers
  • Error handling and retries with tenacity

Before you start

Two SDKs carry the large majority of production LLM traffic: openai and anthropic. The pip installs are unsurprising; the call sites have drifted just enough since 2023 that copy-pasting an old answer off the internet will quietly fail. So here is the current shape of both, side by side — and the reassuring conclusion up front is that the differences are surface quirks, not different ways of thinking.

OpenAI · responses.createinput=[ {role, content} ]system: a message in inputresp.output_textmax_tokens: optionalAnthropic · messages.createmessages=[ {role, content} ]system=… (top-level arg)msg.content[0].textmax_tokens: REQUIRED

OpenAI — the Responses API

chat.completions.create is now treated as legacy. The current entry point is client.responses.create, which unifies plain text, multimodal input, tool calls, and structured outputs behind one method:

from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY from the environment

resp = client.responses.create(
    model="gpt-5-mini",
    input=[
        {"role": "user", "content": "Summarize this paragraph in one sentence: ..."}
    ],
)

print(resp.output_text)

Three things to register. input= has replaced messages= (the role/content shape underneath is the same). resp.output_text is the convenience accessor — under the hood resp.output is a list of typed parts (text, tool calls, images). And you need not pass max_tokens for a trivial call; the API picks a sensible default, and you set it explicitly only when latency or cost matter.

Anthropic — the Messages API

Anthropic’s surface has moved much less — it is still client.messages.create, with system as a top-level argument rather than a message role:

from anthropic import Anthropic

client = Anthropic()  # reads ANTHROPIC_API_KEY from the environment

msg = client.messages.create(
    model="claude-sonnet-4.6",
    max_tokens=1024,
    system="You are a helpful expert in data engineering.",
    messages=[
        {"role": "user", "content": "Explain partitioning in one paragraph."}
    ],
)

print(msg.content[0].text)

Two differences are worth burning in. max_tokens is required on Anthropic — there is no implicit default. And msg.content is always a list of content blocks (text, tool_use, thinking), so you index into it to reach the text.

The same task, both SDKs

Extracting named entities makes the symmetry obvious — the real call sites on top, a runnable mock below so you can see the response shape:

# OpenAI
def extract_entities_openai(text: str) -> str:
    resp = client_openai.responses.create(
        model="gpt-5-mini",
        input=[
            {"role": "system", "content": "Extract named entities. Return JSON: people, orgs, places."},
            {"role": "user",   "content": text},
        ],
        temperature=0,
    )
    return resp.output_text

# Anthropic
def extract_entities_anthropic(text: str) -> str:
    msg = client_anthropic.messages.create(
        model="claude-sonnet-4.6",
        max_tokens=512,
        temperature=0,
        system="Extract named entities. Return JSON: people, orgs, places.",
        messages=[{"role": "user", "content": text}],
    )
    return msg.content[0].text

The bodies are nearly identical. To see the response shapes resolve without a real API key, here is a mock of each SDK’s surface:

# Mock SDK surfaces — they mirror the real response objects.
class _OpenAIResp:
    def __init__(self, text): self.output_text = text

class _AnthropicBlock:
    def __init__(self, text): self.text = text; self.type = "text"

class _AnthropicMsg:
    def __init__(self, text): self.content = [_AnthropicBlock(text)]

class MockOpenAI:
    class responses:
        @staticmethod
        def create(model, input, temperature=1, **kw):
            return _OpenAIResp('{"people": ["Ada Lovelace"], "orgs": ["Cambridge"], "places": ["London"]}')

class MockAnthropic:
    class messages:
        @staticmethod
        def create(model, max_tokens, messages, system=None, temperature=1, **kw):
            return _AnthropicMsg('{"people": ["Ada Lovelace"], "orgs": ["Cambridge"], "places": ["London"]}')

client_openai = MockOpenAI()
client_anthropic = MockAnthropic()

text = "Ada Lovelace studied mathematics at Cambridge and worked in London."
print("OpenAI    ->", extract_entities_openai(text))
print("Anthropic ->", extract_entities_anthropic(text))
OpenAI    -> {"people": ["Ada Lovelace"], "orgs": ["Cambridge"], "places": ["London"]}
Anthropic -> {"people": ["Ada Lovelace"], "orgs": ["Cambridge"], "places": ["London"]}

The only real differences are input versus messages, where system lives, and how you reach the text. Pick a provider per task — for latency, price, or reasoning quality — but do not pick a different programming model.

Common arguments you will actually use

ArgumentWhat it doesNotes
modelWhich model to callgpt-5-mini, gpt-5.1, claude-sonnet-4.6, claude-opus-4.7
temperatureRandomness0 for extraction/classification, 0.7+ for creative writing
max_tokensCap on outputRequired on Anthropic, optional on OpenAI
systemBehaviour instructionsSet once per conversation, not per turn
top_pNucleus samplingUse either temperature or top_p, not both
stop / stop_sequencesHard stop on a substringUseful for “stop at ---” patterns

Tool calling at a glance

Both providers let the model request a function call instead of (or alongside) text. The choreography is the same on either side — you declare tools, the model returns a structured tool-use block, you execute it, and you feed the result back. The Anthropic flavour:

tools = [{
    "name": "get_weather",
    "description": "Get current weather for a city",
    "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}]

msg = client.messages.create(
    model="claude-sonnet-4.6",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
)
# msg.content includes a tool_use block carrying the args the model chose.

OpenAI’s Responses API uses tools=[{"type": "function", ...}] and round-trips results via previous_response_id, but the pattern is identical: declare, the model picks the arguments, you execute, you respond.

Errors and retries

Rate limits — the provider’s cap on requests-per-minute and tokens-per-minute — and transient 5xx errors will hit you in production. The clean pattern is tenacity with exponential backoff (wait a little, then longer, then longer, so you do not hammer a struggling server) on the specific exception classes the SDK exposes:

from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
from openai import RateLimitError, APIConnectionError, APIStatusError

@retry(
    retry=retry_if_exception_type((RateLimitError, APIConnectionError, APIStatusError)),
    wait=wait_exponential(multiplier=1, min=1, max=30),
    stop=stop_after_attempt(5),
)
def call_with_retry(prompt: str) -> str:
    resp = client.responses.create(
        model="gpt-5-mini",
        input=[{"role": "user", "content": prompt}],
    )
    return resp.output_text

Anthropic exposes the same exceptions — anthropic.RateLimitError, anthropic.APIConnectionError, anthropic.APIStatusError. The one rule that matters: do not retry on BadRequestError. That is your bug, not the provider’s, and the same malformed payload will fail identically every time.

In one breath

  • OpenAI’s current entry point is responses.create with input=; Anthropic’s is messages.create with messages=.
  • system is a message in OpenAI’s input; a top-level argument in Anthropic. max_tokens is optional vs required.
  • temperature=0 for extraction/code; ~0.7 for creative — tune the prompt, not the in-between.
  • Tool calling is declare → model picks args → you execute → you respond, on both.
  • Retry rate limits, connection errors, and 5xx with exponential backoff; never retry a BadRequestError.

Practice

Quick check

0/3
Q1Which OpenAI method is the current canonical entry point for LLM calls?
Q2Where does the system prompt go in the Anthropic SDK?
Q3Which exception should you NOT retry on?

What’s next

You can call the models. Next is making the wait feel instant — streaming the response token by token instead of waiting for the whole thing to finish.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What is an AI agent, and how does it differ from a single LLM call?

An agent is an LLM placed in a loop where it reasons, chooses and calls tools or actions, observes the results, and repeats until a goal is met, rather than producing one response and stopping. The key differences are autonomy, tool use, memory and state, and multi-step control flow driven by the model's own decisions.

What is the Model Context Protocol (MCP) and what problem does it solve?

MCP is an open protocol from Anthropic that standardizes how LLM applications discover and connect to external tools, data sources, and prompts through a common client-server interface. It replaces bespoke per-integration glue with a single protocol, so any MCP-compatible host can use any MCP server, and has been adopted across the broader ecosystem.

How would you prevent an AI agent from leaking or misusing API credentials?

Keep raw credentials outside model context and traces. Let the model propose typed intent, authorize the final action and arguments deterministically, then have a trusted executor inject a short-lived, narrowly scoped, audience-restricted credential for one call. Re-authorize downstream and gate high-impact writes with explicit approval.

How do function/tool calling and LLM agents work at a high level?

Tool calling extends the LLM's output space to include structured function invocations. The model emits a JSON object naming a tool and its arguments; the runtime executes the tool and feeds the result back as a new message. An agent is a loop that repeats this cycle — observe, think, act — until the task is complete or a stopping condition is met.

Related lessons

Explore further

Skip to content