Welcome to the hands-on world of LLM programming! In this guide, you'll learn to work with the two most popular LLM providers: OpenAI (GPT models) and Anthropic (Claude models).
Let's get coding!
Coming from Software Engineering? Working with LLM SDKs is just like working with any REST API client — you configure a client, make requests with parameters, and handle responses. The OpenAI and Anthropic SDKs follow the same patterns as Stripe, Twilio, or AWS SDKs you've likely used before.
Getting Started
Installation
# Install both SDKs
pip install openai anthropic
# Or add to requirements.txt
# openai (use the latest)
# anthropic (use the latest — the messages API shown here needs a recent version)
API Keys Setup
Both providers require API keys. Never hardcode them!
# script_id: day_010_openai_anthropic_sdks_part1/api_keys_setup
# Best practice: Use environment variables
# In your terminal or .env file:
# export OPENAI_API_KEY="sk-..."
# export ANTHROPIC_API_KEY="sk-ant-..."
import os
from openai import OpenAI
from anthropic import Anthropic
# Clients automatically read from environment variables
openai_client = OpenAI() # Reads OPENAI_API_KEY
anthropic_client = Anthropic() # Reads ANTHROPIC_API_KEY
# Or explicitly pass the key (not recommended for production)
# openai_client = OpenAI(api_key="sk-...")
OpenAI SDK Basics
Your First API Call
# script_id: day_010_openai_anthropic_sdks_part1/openai_simple_chat
from openai import OpenAI
client = OpenAI()
def simple_chat(message: str) -> str:
"""Make a simple chat completion request."""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": message}
]
)
return response.choices[0].message.content
# Try it!
result = simple_chat("What is Python in one sentence?")
print(result)
# 💰 This call costs ~$0.00001 with GPT-4o-mini (~20 tokens in, ~20 out)
# With GPT-4o it would be ~$0.0003 — 30x more. Always know your model's pricing!
# Prices as of 2026-06; verify current rates in REFERENCE.md / at the provider.
Understanding the Response Object
# script_id: day_010_openai_anthropic_sdks_part1/openai_response_object
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Hello!"}]
)
# Let's explore the response structure
print("Full response type:", type(response))
print("ID:", response.id)
print("Model:", response.model)
print("Created:", response.created)
# The actual content
print("\nChoices:", len(response.choices))
choice = response.choices[0]
print("Finish reason:", choice.finish_reason)
# finish_reason = WHY it stopped: "stop" = finished normally;
# "length" = hit max_tokens and got cut off, like a truncated HTTP response.
print("Message role:", choice.message.role)
print("Message content:", choice.message.content)
# Token usage
print("\nToken usage:")
print(" Prompt tokens:", response.usage.prompt_tokens)
print(" Completion tokens:", response.usage.completion_tokens)
print(" Total tokens:", response.usage.total_tokens)
# 💰 Cost estimation (always track this!)
# GPT-4o: $2.50/1M input, $10.00/1M output
# GPT-4o-mini: $0.15/1M input, $0.60/1M output
# Prices as of 2026-06; verify current rates in REFERENCE.md / at the provider.
input_cost = response.usage.prompt_tokens * 2.50 / 1_000_000
output_cost = response.usage.completion_tokens * 10.00 / 1_000_000
print(f"\n Estimated cost (GPT-4o): ${input_cost + output_cost:.6f}")
print(f" At 10k requests/day: ${(input_cost + output_cost) * 10_000:.2f}/day")
Complete OpenAI Example with All Parameters
# script_id: day_010_openai_anthropic_sdks_part1/openai_advanced_chat
from openai import OpenAI
client = OpenAI()
def advanced_chat(
messages: list,
model: str = "gpt-4o-mini",
temperature: float = 0.7, # higher = more random (Day 4)
max_tokens: int = 1000, # caps reply length
top_p: float = 1.0, # alternative to temperature (Day 4)
frequency_penalty: float = 0, # discourage repeating words (Day 4)
presence_penalty: float = 0, # discourage repeating words (Day 4)
stop: list = None # strings that end generation
) -> dict:
"""
Make an advanced chat completion request with all common parameters.
"""
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=top_p,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
stop=stop
)
return {
"content": response.choices[0].message.content,
"finish_reason": response.choices[0].finish_reason,
"tokens_used": response.usage.total_tokens,
"model": response.model
}
# Usage example
messages = [
{"role": "system", "content": "You are a helpful coding assistant."},
{"role": "user", "content": "Write a Python function to reverse a string."}
]
result = advanced_chat(
messages=messages,
model="gpt-4o",
temperature=0, # Deterministic for code
max_tokens=500
)
print(result["content"])
print(f"\nTokens used: {result['tokens_used']}")
Anthropic SDK Basics
Your First Claude API Call
# script_id: day_010_openai_anthropic_sdks_part1/anthropic_simple_chat
from anthropic import Anthropic
client = Anthropic()
def simple_claude_chat(message: str) -> str:
"""Make a simple message request to Claude."""
response = client.messages.create(
model="claude-sonnet-4-6",
# max_tokens caps how long the reply can be. OpenAI defaults it for you;
# Anthropic makes you set it, so the API can never run away and bill you for a giant response.
max_tokens=1024,
messages=[
{"role": "user", "content": message}
]
)
return response.content[0].text
# Try it!
result = simple_claude_chat("What is Python in one sentence?")
print(result)
Understanding Claude's Response Object
# script_id: day_010_openai_anthropic_sdks_part1/anthropic_response_object
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
# Explore the response structure
print("Response type:", type(response))
print("ID:", response.id)
print("Model:", response.model)
print("Stop reason:", response.stop_reason)
# stop_reason: "end_turn" = done; "max_tokens" = truncated. Same idea, different label.
# The content (can be multiple blocks!)
# Anthropic always returns content as a LIST of blocks (text, images, tool calls).
# Today it is a single text block, so content[0].text works — like a response
# that is always an array even when it usually has one element.
print("\nContent blocks:", len(response.content))
for i, block in enumerate(response.content):
print(f" Block {i} type:", block.type)
print(f" Block {i} text:", block.text)
# Token usage
print("\nToken usage:")
print(" Input tokens:", response.usage.input_tokens)
print(" Output tokens:", response.usage.output_tokens)
Complete Anthropic Example
# script_id: day_010_openai_anthropic_sdks_part1/anthropic_advanced_chat
from anthropic import Anthropic
client = Anthropic()
def advanced_claude_chat(
messages: list,
system: str = None,
model: str = "claude-sonnet-4-6",
max_tokens: int = 1024,
temperature: float = 1.0,
top_p: float = None,
stop_sequences: list = None
) -> dict:
"""
Make an advanced message request to Claude.
"""
# Build kwargs
kwargs = {
"model": model,
"max_tokens": max_tokens,
"messages": messages,
}
# Add optional parameters
if system:
kwargs["system"] = system
# 1.0 is the API default, so we omit it to keep the request minimal; pass any other value to override.
if temperature != 1.0:
kwargs["temperature"] = temperature
if top_p is not None:
kwargs["top_p"] = top_p
if stop_sequences:
kwargs["stop_sequences"] = stop_sequences
response = client.messages.create(**kwargs)
return {
"content": response.content[0].text,
"stop_reason": response.stop_reason,
"input_tokens": response.usage.input_tokens,
"output_tokens": response.usage.output_tokens,
"model": response.model
}
# Usage example
result = advanced_claude_chat(
messages=[
{"role": "user", "content": "Write a Python function to reverse a string."}
],
system="You are a helpful coding assistant. Write clean, well-documented code.",
model="claude-sonnet-4-6",
temperature=0, # Deterministic for code
max_tokens=500
)
print(result["content"])
print(f"\nTokens - Input: {result['input_tokens']}, Output: {result['output_tokens']}")
Vision & Multimodal Input
Both OpenAI and Anthropic support image input alongside text in the same API call. This unlocks document understanding, screenshot analysis, chart reading, receipt processing, and more — all through the same chat completions interface you already know.
OpenAI Vision
Pass a list of content blocks instead of a plain string. Mix text and image_url blocks freely:
# script_id: day_010_openai_anthropic_sdks_part1/openai_vision
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": {"url": "https://example.com/image.png"}
}
]
}
]
)
Anthropic Vision
Anthropic requires base64-encoded image data (or a URL source). The content block uses type: "image" with a source object:
# script_id: day_010_openai_anthropic_sdks_part1/anthropic_vision
import base64
from anthropic import Anthropic
client = Anthropic()
with open("document.png", "rb") as f:
image_data = base64.standard_b64encode(f.read()).decode("utf-8")
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": image_data,
},
},
{"type": "text", "text": "Describe this document."}
],
}
],
)
When to Use Vision
- PDF/document understanding — faster and often more accurate than OCR + text extraction pipelines
- UI screenshot analysis — useful for automated testing and accessibility audits
- Chart/graph interpretation — extract data or summaries from visual reports
- Receipt/invoice processing — pull structured fields from unstructured images
Coming from Software Engineering? Traditional image-understanding pipelines required stitching together separate OCR services (Tesseract, AWS Textract), computer vision models, and custom post-processing. With multimodal LLMs, a single API call handles text + image understanding together — dramatically reducing integration complexity.
Checkpoint
Run advanced_chat (OpenAI) and advanced_claude_chat (Anthropic) on the same message and confirm: both return content plus a token count, and the two providers' usage fields differ (OpenAI's usage.total_tokens vs. Anthropic's separate input_tokens/output_tokens). If either call 401s, check that the matching API key env var is exported — each SDK reads its own (OPENAI_API_KEY vs. ANTHROPIC_API_KEY).
Summary
Quick Reference
| Task | OpenAI | Anthropic |
|---|---|---|
| Install | pip install openai |
pip install anthropic |
| Client | client = OpenAI() |
client = Anthropic() |
| Basic call | client.chat.completions.create(...) |
client.messages.create(..., max_tokens=...) |
| Get text | resp.choices[0].message.content |
resp.content[0].text |
| Token usage | resp.usage.prompt_tokens / .completion_tokens |
resp.usage.input_tokens / .output_tokens |
| Async client | AsyncOpenAI() + await ...create(...) |
AsyncAnthropic() + await ...create(...) |
| Image block | {"type":"image_url","image_url":{...}} |
{"type":"image","source":{...}} |
(Current model IDs and prices live in REFERENCE.md — verify before quoting numbers.)
Exercises
- Print the cost. Wrap
simple_chatso it also returns the dollar cost of the call usingresponse.usageand the per-1M prices from REFERENCE.md. - Port a call. Take the OpenAI
simple_chatand rewrite it against the Anthropic SDK, accounting formax_tokens(required) and the different response shape. - Go async. Convert one call to
AsyncOpenAIand useasyncio.gatherto send three prompts concurrently; compare wall-clock time to running them in a loop. - Read an image. Send a screenshot to
gpt-4oand ask it to extract any visible numbers; then do the same with Anthropic's base64 image block.
Solutions (approaches)
cost = u.prompt_tokens/1e6*in_price + u.completion_tokens/1e6*out_price. Forgpt-4o-miniuse ~$0.15 / ~$0.60 per 1M (verify in REFERENCE.md).- Swap to
client.messages.create(model="claude-sonnet-4-6", max_tokens=256, messages=[...])and returnresp.content[0].text. - Define
async def ask(p), thenawait asyncio.gather(*(ask(p) for p in prompts)). Concurrent latency ≈ the slowest single call, not the sum. - OpenAI wants an
image_urlblock (URL or data URI); Anthropic wants animageblock with a base64source. Same prompt text, different envelope.
What's Next?
Tomorrow (Day 11) is OpenAI and Anthropic SDKs Part 2 — the key differences between the two SDKs (system-message placement, response structure), a unified wrapper for multi-provider support, async batch patterns, error handling, and how to pick a model.