Two SDKs changed their HTTP stack in eight days
On 12 August 2026, openai 3.0.0 shipped requiring httpx2>=2.7.0,<3. On 20 August 2026, anthropic 1.0.0 shipped requiring httpx2>=2.0.0,<3. The day before that, anthropic 0.125.0 was still on httpx<1,>=0.25.0.
The HTTP client underneath the two most widely used Python LLM SDKs changed, under a different package name, roughly a week apart.
Most write-ups will file this under "routine dependency bump, update your lockfile." That reading misses what actually happened and what it costs you. This is a stewardship change to a library sitting in the critical path of nearly every Python AI service, and the migration path it creates has one failure mode that is loud and safe, and one that is silent and expensive.
The silent one is the reason I am writing this.
What httpx2 actually is
httpx2 is not a rewrite and it is not a competitor. From the project README:
With HTTPX itself seeing limited activity recently, Pydantic is picking up stewardship under the HTTPX2 name so that users have a reliably maintained path forward - including timely security updates for a library that sits in the critical path of so many production systems.
So: same design, broadly the same API, new maintainer, new distribution name. The repository lives at github.com/pydantic/httpx2. Version 2.12.0 landed on 18 August 2026, four days before Anthropic depended on it.
The API compatibility is real. httpx2.AsyncClient behaves like httpx.AsyncClient. If it were a drop-in replacement this article would not need to exist.
The problem is that it is not a replacement. It is an addition. Python identifies packages by import name, and httpx and httpx2 are two different names. Nothing forces you to pick one.
The loud failure: custom http_client injection
This one is fine. It fails immediately and tells you exactly what is wrong.
If you inject a custom client for proxying, mTLS, tuned connection pools or forced timeouts, you will hit this the moment you upgrade:
import httpx
import anthropic
# This is the pattern from every "production Anthropic setup" post
# written before 20 August 2026.
client = anthropic.AsyncAnthropic(
api_key="...",
http_client=httpx.AsyncClient(timeout=30.0),
)
Under anthropic 1.0.0 that raises at construction time:
TypeError: Invalid `http_client` argument; Expected an instance of
`httpx2.AsyncClient` but got <class 'httpx.AsyncClient'>
The fix is a one-line import change:
import httpx2 # not httpx
import anthropic
client = anthropic.AsyncAnthropic(
api_key="...",
http_client=httpx2.AsyncClient(timeout=30.0),
)
Credit where it is due: the SDK type-checks the argument and raises at construction, not at first request. That means it blows up in your import path or app factory, not in production at 3am under load. That is the correct design choice, and it is why this failure mode is the one you should worry about least.
If you support both SDK generations during a rollout, resolve the client module once rather than scattering try blocks through your code:
# compat.py - pick whichever HTTP stack the installed SDK expects.
try:
import httpx2 as httpclient # anthropic >= 1.0.0, openai >= 3.0.0
HTTP_STACK = "httpx2"
except ImportError: # pragma: no cover
import httpx as httpclient # anthropic < 1.0.0, openai < 3.0.0
HTTP_STACK = "httpx"
def build_async_client(timeout: float = 30.0):
"""One place that knows which HTTP library is in play."""
return httpclient.AsyncClient(
timeout=timeout,
limits=httpclient.Limits(max_connections=100, max_keepalive_connections=20),
)
That is the whole loud failure. Ten minutes of work. Now the expensive one.
The silent failure: you end up running both
Here is the install that should worry you. Nothing in it is unusual, and it is roughly what a multi-provider service looks like:
pip install openai==3.3.1 langchain-anthropic==1.6.1
No error. No warning. Here is what you actually get:
anthropic 0.125.0
httpx 0.28.1
httpx2 2.12.0
langchain-anthropic 1.6.1
openai 3.3.1
Read that carefully. Both HTTP stacks are installed. openai 3.3.1 pulled in httpx2. langchain-anthropic 1.6.1 pins anthropic<1.0.0, so pip quietly resolved Anthropic down to 0.125.0, which pulled in httpx.
You asked for two packages and got two independent HTTP client libraries, each with its own connection pool, its own timeout defaults, its own proxy handling and its own retry surface. Both import cleanly in the same interpreter:
import httpx, httpx2
print(httpx.__version__) # 0.28.1
print(httpx2.__version__) # 2.12.0
print(httpx is httpx2) # False - genuinely two libraries
If you do pin Anthropic explicitly, you at least get an honest error instead of a silent downgrade:
pip install anthropic==1.0.0 langchain-anthropic==1.6.1
# ERROR: Cannot install anthropic==1.0.0 and langchain-anthropic==1.6.1
# because these package versions have conflicting dependencies.
# ERROR: ResolutionImpossible
The timing is almost comic. langchain-anthropic 1.6.1 was published on 20 August 2026 at 14:06 UTC. anthropic 1.0.0 landed the same day at 19:58 UTC, about six hours later. The pin was correct when it was written and stale by dinner.
This is the practical lesson: pin your SDK versions explicitly, including the ones you only depend on transitively. An unpinned anthropic is not a convenience, it is a silent downgrade waiting for a resolver to choose for you. The same discipline that makes you pin model versions applies to the client libraries that reach them.
The consequence nobody mentions: your traces go quiet
This is the part that costs real money, because it does not surface as an error at all.
Almost every instrumented Python AI service has a line like this in its bootstrap:
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
HTTPXClientInstrumentor().instrument()
That call patches httpx. It does not patch httpx2. In opentelemetry-instrumentation-httpx 0.65b0 the two are separate classes with separate declared dependencies:
from opentelemetry.instrumentation.httpx import (
HTTPXClientInstrumentor,
HTTPX2ClientInstrumentor,
)
print(list(HTTPXClientInstrumentor().instrumentation_dependencies()))
# ['httpx >= 0.18.0']
print(list(HTTPX2ClientInstrumentor().instrumentation_dependencies()))
# ['httpx2 >= 2.0.0']
So the upgrade sequence is:
- 1You bump
anthropicto 1.0.0 oropenaito 3.x. - 2Your existing
HTTPXClientInstrumentor().instrument()call keeps working. No exception, no log line. - 3Your model calls now travel over httpx2, which nothing is patching.
- 4HTTP spans for your most important outbound traffic stop appearing.
You do not get an alert for spans that were never emitted. You find out weeks later when you go looking for a latency regression and the model calls are missing from the trace, which is precisely when you need them. If you are already thinking about what to track in an LLM system, note that this failure removes the transport layer from view while leaving your application spans intact, so the trace looks superficially healthy.
Instrument both, unconditionally. It costs nothing when a library is absent:
# telemetry.py - survive the transition in either direction.
def instrument_http() -> list[str]:
"""Patch whichever HTTP stacks are actually installed."""
patched: list[str] = []
try:
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
import httpx # noqa: F401 - only patch if the library is present
HTTPXClientInstrumentor().instrument()
patched.append("httpx")
except ImportError:
pass
try:
from opentelemetry.instrumentation.httpx import HTTPX2ClientInstrumentor
import httpx2 # noqa: F401
HTTPX2ClientInstrumentor().instrument()
patched.append("httpx2")
except ImportError:
pass
if not patched:
raise RuntimeError("No HTTP stack instrumented; traces will be blind")
return patched
Assert on the result in a startup check. "Which HTTP stacks am I instrumenting" should be an observable fact about your service, not an assumption. This is the same argument as asserting on your evaluation harness rather than trusting it: silent absence is the hardest failure class to debug.
One warning while you are here. There is a package on PyPI called opentelemetry-instrumentation-httpx2. Do not install it. The published version is 0.0.0, the summary is the uv default "Add your description here", and it declares no dependencies. It is a placeholder occupying an obvious name. Real httpx2 support ships inside the normal opentelemetry-instrumentation-httpx distribution. Reaching for the obvious name here gets you nothing and puts an unreviewed package in your image.
What this means for connection pooling
If you end up with both stacks, your tuning is now split in two.
Connection limits, keepalive expiry, HTTP/2 negotiation and timeout policy configured on your httpx client apply only to the SDKs still on httpx. Everything on httpx2 falls back to its own defaults until you configure it separately.
Teams that carefully tuned pool sizes for high-throughput pipelines should re-check them. A pool sized for all outbound model traffic is now sized for a subset of it, and the other subset is running on defaults you did not choose. If you are chasing tail latency, an unconfigured second connection pool is exactly the kind of thing that produces inconsistent p99 with no obvious cause.
This matters most in multi-provider setups, which are precisely the setups most likely to land in the two-stack state, because it takes two providers on different SDK generations to get there.
The migration I would actually run
Ranked by value per hour, assuming a service that calls more than one provider.
- 1Audit what you have. Run
pip list | grep -E "^(httpx|httpx2|openai|anthropic)"in your built image, not your dev machine. If both httpx and httpx2 appear, you are in the split state now. - 2Pin
anthropicandopenaiexplicitly in your dependency file even though a framework pulls them in. This converts silent downgrades into resolver errors you can see. - 3Fix instrumentation before upgrading SDKs, not after. Add
HTTPX2ClientInstrumentorwhile you can still confirm spans are flowing, so a gap is obviously caused by the change. - 4Move custom
http_clientconstruction into one module. One import to change instead of a search across the codebase. - 5Decide whether you are converging or splitting. If LangChain's Anthropic integration blocks you from
anthropic1.0.0, you either wait for it to unpin, or you call the Anthropic SDK directly for that path. Both are defensible. Doing neither and pretending you are on one stack is not.
Point 5 is the strategic one. A framework that pins a provider SDK below its current major version is now a constraint on your whole dependency graph. That is a normal cost of frameworks and not a reason to abandon them, but it should be a conscious trade rather than something you discover from a resolver error. The prototype-to-production gap is full of decisions like this one.
Why this keeps happening
The Python AI stack is young and it moves faster than its own dependency conventions. Two of the most-installed SDKs in the ecosystem changed their HTTP layer within eight days of each other, onto a library that was itself four days old at the time of the second migration.
That is not a criticism of anyone involved. Pydantic taking over a critical-path library that had gone quiet is a genuinely good outcome, and both SDKs handled the loud failure well by type-checking the injected client instead of failing at request time.
But it does tell you what to expect. If your dependency strategy assumes the foundations are stable, it will keep being surprised. Pin aggressively, instrument defensively, and make "what is actually installed in the image" a thing you can answer without guessing. Those habits cost very little and they are what separate a service you can reason about from one you cannot, which is the same conclusion I keep reaching about governance and operational discipline generally.
Frequently Asked Questions
What is httpx2 and why did my SDK switch to it?
httpx2 is a continuation of httpx maintained by Pydantic. Their README states that with httpx seeing limited activity recently, Pydantic is picking up stewardship under the httpx2 name so that a library sitting in the critical path of so many production systems keeps receiving timely security updates. The API is intentionally close to httpx. openai 3.0.0 moved to it on 12 August 2026 and anthropic 1.0.0 followed on 20 August 2026.
Why do I get "Invalid http_client argument; Expected an instance of httpx2.AsyncClient"?
You upgraded to anthropic 1.0.0 while still constructing your custom client from the old httpx package. The SDK type-checks the injected client and raises TypeError at construction. Change the import to httpx2 and add httpx2 to your dependencies. The check fires at startup rather than at first request, which is the good case.
Can httpx and httpx2 be installed at the same time?
Yes, and that is the real trap. They are separate distributions with separate import names, so pip installs both without complaint. Installing openai 3.3.1 alongside langchain-anthropic 1.6.1 produces an environment with httpx 0.28.1 and httpx2 2.12.0 side by side: two connection pools, two timeout configurations, and two things to instrument.
Why does pip refuse to install anthropic 1.0.0 with langchain-anthropic?
langchain-anthropic 1.6.1 pins anthropic<1.0.0. It was published on 20 August 2026 at 14:06 UTC, roughly six hours before anthropic 1.0.0 landed at 19:58 UTC. Requesting both gives ResolutionImpossible. If you do not pin anthropic explicitly, pip resolves it down to 0.125.0 instead of erroring, which is worse because it is silent.
Did my OpenTelemetry HTTP spans disappear after upgrading?
Probably. opentelemetry-instrumentation-httpx 0.65b0 ships two classes. HTTPXClientInstrumentor declares httpx >= 0.18.0 and does not patch httpx2. A separate HTTPX2ClientInstrumentor declares httpx2 >= 2.0.0. The instrument() call already in your bootstrap keeps succeeding, it simply no longer covers your model traffic.
Should I install opentelemetry-instrumentation-httpx2?
No. That name exists on PyPI but the published version is 0.0.0 with the summary "Add your description here" and no declared dependencies. It is a placeholder, not an official package. httpx2 support lives inside the normal opentelemetry-instrumentation-httpx distribution.
Where to go from here
Dependency hygiene is unglamorous and it is most of what keeps an AI service running. Pin explicitly, instrument both stacks, and check what is in the built image rather than what you think you asked for.
If you want to work through production patterns like this hands-on, I run a 2-hour live workshop most Sundays where we build in real time with real code and no slides. Rs 499 / $19 with a full refund guarantee. See what is coming up.
If you are earlier in the journey, the AI engineer roadmap lays out the order I would learn these things in, and the guide to building agents covers the layer that sits directly on top of these SDKs.
