Skip to content
Skip to main content
Anthropic, Python and Pydantic logos on frosted tiles over a dark copper background, representing the Anthropic Python SDK v1.0 migration to httpx2
8 min readBy Carlos Aragon

Anthropic Python SDK v1.0: What Actually Breaks

The Anthropic Python SDK hit v1.0 on August 20, 2026, and five changes cause almost every broken build: httpx became httpx2, temperature / top_p / top_k were removed from the message methods, the legacy Text Completions API was deleted, async raw responses are now awaitable, and output_format as a schema dict became output_config. Minimum Python moved from 3.9 to 3.10. Below is each one with the actual fix, ordered by how likely it is to hit you.

The Two-Minute Triage

Before you read anything else, run this against your repo. Every hit is an edit you owe:

grep -rnE "completions\.create|HUMAN_PROMPT|AI_PROMPT|temperature=|top_p=|top_k=|output_format=|import httpx|anthropic\.Transport|ProxiesTypes" .

If that returns nothing and you're on Python 3.10+, your upgrade is probably a one-line version bump. That is genuinely the common case — most application code passes a model, a messages list and max_tokens, and none of that moved.

If it lights up, the good news is that the release is documented change by change in MIGRATION.md, which is more than most major bumps give you. The bad news is that two of the changes fail at runtime, not at import, so a green type-check tells you nothing.

httpx Became httpx2, and It's Stricter Than It Looks

This is the headline change and the reason for the major version. The SDK's HTTP layer moved off httpx, which is no longer actively maintained, onto httpx2 — an API-compatible fork maintained by the Pydantic team. Same classes, same behavior, same semantics, still getting security fixes.

If you never import httpx yourself, this change is invisible. Passing timeout=30.0 or max_retries=3 keeps working exactly as before. The SDK carries its own dependency and you never see it.

The moment you touch httpx objects, though, it stops being polite about it:

# before
import httpx
client = Anthropic(http_client=httpx.Client(proxy="http://localhost:8080"))

# after
import httpx2 as httpx
client = Anthropic(http_client=httpx.Client(proxy="http://localhost:8080"))

Hand it a real httpx.Client and you get a TypeError. Not a deprecation warning, not a shim — a hard failure. The import httpx2 as httpx aliasing trick is the cheapest fix because it leaves every downstream annotation alone.

The place this actually hurts is your test suite. Mocking libraries like respx, pytest-httpx and vcrpy patch httpx by module, so after the upgrade they're patching a module the SDK no longer uses. Every request escapes the mock. You call httpx2.alias_httpx()before those libraries import, and it works again. I'd bet more people lose an hour to this than to all the other changes combined, because the symptom — tests failing with real network errors — looks like the SDK broke rather than the harness.

Same story for observability: if you instrument httpx through OpenTelemetry or Sentry, those instrumentations need to be pointed at httpx2 or your Claude calls quietly vanish from your traces. Worth checking against whatever you use for agent observability — losing the HTTP span is the kind of gap nobody notices until an incident.

temperature, top_p and top_k Are Gone

This is the change that surprises people most, because those three parameters are muscle memory from every other SDK on the planet. In v1.0 they are no longer part of the typed signature of the message methods.

The reasoning holds up: current Claude models don't take the classic sampling knobs, so keeping them as first-class typed arguments was advertising a control that no longer does anything. Removing them from the signature makes the SDK honest about what the API accepts.

If you're calling an older model that doesstill accept them, there's an escape hatch:

# before
resp = client.messages.create(model=..., temperature=0.2, messages=[...])

# after
resp = client.messages.create(
    model=...,
    extra_body={"temperature": 0.2},
    messages=[...],
)

Nothing changes on the wire. The value still reaches the API in the same JSON body — it just isn't typed anymore, which means a typo in extra_body is now your problem instead of your type checker's. If you've been tuning temperature to control output determinism, that job has quietly migrated to structured outputs and to the effort parameter, which are the knobs that actually move the needle now.

Text Completions Is Deleted, Not Deprecated

client.completions.create() is gone. So are the Completion and CompletionCreateParams types, and the HUMAN_PROMPT / AI_PROMPT constants that everyone used to hand-build prompt strings with.

If any of that is still in your codebase, v1.0 won't import. And unlike the other items here, this one isn't a find-and-replace — it's a rewrite from a single concatenated prompt string to a structured messages list with roles. Do that port on 0.x first, ship it, verify it, and upgrade afterward. Doing both at once means every regression is ambiguous: was it the port or the SDK?

Realistically, most people reading this ported to Messages years ago. But legacy scripts hide in cron jobs and internal tools, and those are exactly the codebases nobody has re-run since the pin was set.

Async Raw Responses Are Now Awaitable

Raw responses were inconsistent across sync and async, and v1.0 unified them under APIResponse / AsyncAPIResponse. Two things changed at once:

  • Properties became methods. response.text is now response.text(), and response.content is now response.read().
  • On the async client they're coroutines. parse(), read(), text() and json() all need await. Sync code keeps parse() exactly as it was.
# before (async)
resp = await client.messages.with_raw_response.create(...)
msg = resp.parse()
body = resp.text

# after (async)
resp = await client.messages.with_raw_response.create(...)
msg = await resp.parse()
body = await resp.text()

You also get json(), iter_bytes(), iter_text() and iter_lines() as additions. Forgetting an await here fails loudly — you end up comparing a coroutine object to a string — so this one is annoying but not dangerous. It's mostly a concern if you built your own retry layer around raw responses, which people do when they're handling 429s and rate-limit headers themselves.

output_format Became output_config

Structured outputs got a nesting level. The schema-dict form moved:

# before
output_format={"type": "json_schema", "schema": {...}}

# after
output_config={"format": {"type": "json_schema", "schema": {...}}}

The helpers behave differently from the raw call, which is the part worth reading twice. parse() and stream() still take output_format=, but only for a type object — a Pydantic model class, not a dict. Hand a schema dict to a helper now and it raises TypeError.

One more helper change in the same neighborhood: messages.parse() lost its stream argument. Per the migration notes it never worked, so it was removed rather than fixed. Use messages.stream() if you want streaming structured output.

The Ones That Fail at Runtime, Not at Import

These are the dangerous ones. They pass a type check, pass a lint, and blow up in production.

  • Bedrock now requires a region. AnthropicBedrock() with no region raises ValueError. Resolution order is the aws_region= argument, then AWS_REGION / AWS_DEFAULT_REGION, then your boto3 profile. If you relied on an implicit default, set it explicitly.
  • isinstance(obj, Stream) now returns False for message streams. If you branch on that check, your stream-handling path silently stops executing. Switch to isinstance(obj, MessageStream).
  • Headers merge case-insensitively. Two spellings of the same header no longer both go out — the later one replaces the earlier. If you leaned on duplicate-casing behavior, that's gone. Also, bytes header values are rejected; decode to str first.
  • tool_runner compaction moved. compaction_control is replaced by context_management with an edits list, and the trigger threshold has a floor of 50,000 input tokens. If you were compacting at 20k, you can't anymore.
  • Raw body parameter renamed. client.post(..., body=b"...") is now content=b"...".
# before
compaction_control={"enabled": True, "context_token_threshold": 100_000}

# after
context_management={
    "edits": [{
        "type": "compact_20260112",
        "trigger": {"type": "input_tokens", "value": 100_000},
    }]
}

That compaction change matters more than it reads. If you run long agent loops, the threshold is the single number that decides how often you pay to summarize your own history — the same tradeoff I walked through in the piece on compaction for long conversations. Moving to the new shape is a good excuse to re-tune it rather than port your old value across unchanged.

Should You Upgrade Right Now?

Yes, but pin first and do it deliberately. Here's how I'd sequence it:

  • Pin anthropic<1 today. Not because 1.0 is risky, but because an unpinned dependency means the next clean CI build picks it up on a random Tuesday and you debug it under pressure instead of on purpose.
  • Confirm Python 3.10+ everywhere — including the slim container images and the Lambda runtimes nobody has looked at in a year. 3.9 is below the floor now.
  • Fix the test harness before the app code. If your mocks are patching the wrong module, you can't trust anything the suite tells you about the rest of the migration.
  • Ship it as its own PR. No feature work, no model change, no prompt edits. When something regresses a week later you want a one-line bisect, not a haystack.

The reason to actually move rather than sit on the pin: 0.x is now the branch that stops getting attention, and its HTTP layer is built on a library that isn't maintained. That's a security argument, not a features argument, and security arguments don't improve with age.

If you're also weighing where your agent logic should live while you're in here, the related decisions are Agent SDK versus raw API and whether prompt cachingis pulling its weight — both are cheaper to reason about once you're on a current SDK.

Key Takeaways

  • v1.0 shipped August 20, 2026. If you only pass model, messages and max_tokens, the upgrade is a version bump.
  • httpx became httpx2 — an API-compatible fork by the Pydantic team. Only code that imports httpx types or builds custom clients has to change.
  • The real httpx2 casualty is your test suite: respx, pytest-httpx and vcrpy need httpx2.alias_httpx() called before they import.
  • temperature, top_p and top_k were removed from the message methods. Pass them via extra_body if you're on a model that still honors them.
  • Text Completions is deleted, not deprecated — completions.create, the Completion types, and HUMAN_PROMPT/AI_PROMPT are all gone.
  • Async raw responses are awaitable now: await resp.parse(), await resp.text(). Sync is unchanged.
  • output_format schema dicts became output_config={'format': {...}}. The parse/stream helpers accept type objects only and raise TypeError on a dict.
  • Bedrock requires an explicit region, isinstance(obj, Stream) returns False for message streams, and tool_runner compaction has a 50,000-token floor — all runtime failures a type check won't catch.

Running Claude in Production and Dreading the Upgrade?

I build and maintain production systems on the Claude API — agent loops, tool runners, streaming, the retry and observability layer underneath. If you've got a codebase pinned to an old SDK because nobody wants to own the migration, that's the job.

Related Posts