Skip to content

feat(openai): support openai 3.0 SDK - #39613

Merged
ccurme (ccurme) merged 14 commits into
masterfrom
cc/openai-3.0-compat
Aug 13, 2026
Merged

feat(openai): support openai 3.0 SDK#39613
ccurme (ccurme) merged 14 commits into
masterfrom
cc/openai-3.0-compat

Conversation

@ccurme

Copy link
Copy Markdown
Collaborator

Supports openai 3.x in langchain-openai.

The major change associated with openai 3.0 is a migration from httpx to httpx2: https://github.com/openai/openai-python/releases/tag/v3.0.0.

Here we support openai 2.x and 3.x simultaneously in langchain-openai. httpx is already a dependency of langchain-core and the langsmith sdk, so all environments running a langchain package install it.

We add a _compat.py that re-exports whatever httpx library the openai SDK uses. For any use of httpx that feeds into the openai SDK, we import from _compat. httpx continues to be used elsewhere in langchain-openai (langchain-core exports an SSRF-safe httpx client that we use for image token counting).

Manual specification of httpx clients remains supported via OpenAI's legacy escape hatch (langchain passes through), but support for this may be removed in a future release.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file (e.g. `pyproject.toml` or `uv.lock`) feature For PRs that implement a new feature; NOT A FEATURE REQUEST integration PR made that is related to a provider partner package integration internal openai `langchain-openai` package issues & PRs size: S 50-199 LOC labels Aug 12, 2026

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Open SWE Review: No issues found

Open SWE reviewed this PR and found no potential bugs to report.

Open in WebView Open SWE trace

@socket-security

socket-security Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedopenai@​3.0.089100100100100

View full report

Comment thread libs/partners/openai/pyproject.toml
@mdrxy

Copy link
Copy Markdown
Member

for CI do we really need to be running both? or can we conditionally run only the newest in CI?

@mdrxy

Copy link
Copy Markdown
Member

I think we'll need to preserve the public custom HTTP-client contract

openai>=3 validates http_client / http_async_client against httpx2 types, while our public fields are documented for ordinary httpx.Client and httpx.AsyncClient. As a result, constructing ChatOpenAI, embeddings, or Azure models with an existing custom client now fails at runtime.

Since supporting OpenAI v3 is required, we'll probably want to accept httpx clients and adapt them internally to httpx2 clients (while continuing to accept native httpx2 clients).

@mdrxy

Copy link
Copy Markdown
Member

I think we'll still need to keep httpx as a declared runtime dep as well (since we're keeping compat for both) ? Since we're importing it unconditionally still

Otherwise we can keep removed but import conditionally

@mdrxy

Copy link
Copy Markdown
Member

Unreachable try/except ImportError — embeddings/base.py:423-431, 440-448. _compat is imported eagerly at package import via chat_models → _client_utils, so the from-import cannot raise; if it could, import langchain_openai would already have failed. The new "reinstall with pip install openai" advice is also wrong under openai 3, which doesn't ship httpx.

`_sdk_uses_httpx2` inferred the backing HTTP library by parsing the first
integer out of `openai.__version__`. That parse can only ever under-report the
major version, so every failure resolved to classic `httpx` — the wrong answer
on `openai>=3`. Tag-derived strings such as `v3.0.0`, which build tooling
sometimes leaves unstripped, hit exactly that path.

A mis-resolution is close to a worst case: an `httpx` transport handed to an
`httpx2`-backed client constructs successfully, then fails on the first request
with a message-less `AssertionError` that the SDK retries and reports as
`APIConnectionError: Connection error.` Nothing names the cause, and every
lead it suggests (network, proxy, firewall, API key) is wrong.

Read the answer off the SDK instead: `openai.DefaultHttpxClient` subclasses the
client class of whichever library the SDK uses, and `_client_utils` subclasses
that same attribute, so a disagreement between the selection and the library our
transport objects must match is now structurally impossible.

The version heuristic is kept only as a fallback, in case the SDK stops exposing
a recognizable base class — raising there would make `import langchain_openai`
fail outright for everyone. It now warns when it fires, so the guess is visible
in logs rather than resurfacing later as an opaque `APIConnectionError`, and it
tolerates prefixed and pre-release version strings.

Also corrects three inaccurate comments in `_compat`: the SDK does accept a
fully built `http_client=` from either library (the constraint applies to the
parts we assemble ourselves); mypy runs under `--all-groups`, where both
libraries are installed, not a lint environment with only `httpx`; and the two
libraries are not interchangeable drop-ins, since their classes are distinct
types. Restores the dropped `pyproject.toml` rationale for the direct `certifi`
dependency, noting that `httpx` reaches us via langsmith rather than
`langchain-core` directly.

Tests: replaces the tautological version-parametrized assertions, which shared
their predicate with the code under test, with an oracle derived from the SDK's
own MRO, plus coverage for the mangled-version regression and the warning on
fallback.
@github-actions github-actions Bot added size: M 200-499 LOC and removed size: S 50-199 LOC labels Aug 12, 2026
@ccurme

Copy link
Copy Markdown
Collaborator Author

I think we'll need to preserve the public custom HTTP-client contract

openai>=3 validates http_client / http_async_client against httpx2 types, while our public fields are documented for ordinary httpx.Client and httpx.AsyncClient. As a result, constructing ChatOpenAI, embeddings, or Azure models with an existing custom client now fails at runtime.

Since supporting OpenAI v3 is required, we'll probably want to accept httpx clients and adapt them internally to httpx2 clients (while continuing to accept native httpx2 clients).

I think this works already:

import httpx
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="gpt-5.6-luna",
    http_client=httpx.Client(timeout=httpx.Timeout(30.0)),
)
model.invoke("Hello")

I mentioned this in the last line of the PR summary. lmk if I misunderstood the issue.

@ccurme

Copy link
Copy Markdown
Collaborator Author

for CI do we really need to be running both? or can we conditionally run only the newest in CI?

We don't need to run both. I just added two invoke calls to integration tests (one with openai 2.x and another with 3.x) so the cost seemed small.

@ccurme

Copy link
Copy Markdown
Collaborator Author

I think we'll still need to keep httpx as a declared runtime dep as well (since we're keeping compat for both) ? Since we're importing it unconditionally still

Otherwise we can keep removed but import conditionally

I moved it to langchain-core, I think it's OK to keep it implicit (it just depends on what we use in langchain-core). Open to changing this.

@github-actions github-actions Bot added size: S 50-199 LOC and removed size: M 200-499 LOC labels Aug 12, 2026

@mdrxy Mason Daugherty (mdrxy) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user passes a Timeout from httpx, it gets stored as-is inside the OpenAI 3.0 SDK's httpx2 client. But httpx.Timeout and httpx2.Timeout are different classes, and httpx2 doesn't know what to do with the foreign one. The first time anything reads or prints that timeout (e.g. a log line, retry, or per-request timeout merge) you'll get TypeError: unhashable type: 'Timeout'.

import httpx
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="some-model",
    timeout=httpx.Timeout(60.0, connect=5.0),
)

llm.invoke("Say hello")  # works — nothing has read the timeout yet
# later, the first code path that reads it raises
# TypeError: unhashable type: 'Timeout'

Should consider fixing this for correctness: before building the client, convert a plain httpx.Timeout into whichever Timeout class the active HTTP module uses. That way the object stored in the SDK is always the right type. This applies to the chat, embeddings, and LLM client paths.

Comment thread libs/partners/openai/tests/unit_tests/test_compat.py Outdated
Comment thread libs/partners/openai/langchain_openai/_compat.py Outdated
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Signed-off-by: ccurme <chester.curme@gmail.com>
@ccurme

Copy link
Copy Markdown
Collaborator Author

The first time anything reads or prints that timeout (e.g. a log line, retry, or per-request timeout merge) you'll get TypeError: unhashable type: 'Timeout'.

Do you have a repro for this? afaict the openai SDK normalizes the timeouts to httpx2: https://github.com/openai/openai-python/blob/a1eeab58db02de46717ccebaf1eb83e314fa86ff/src/openai/_httpx2.py#L65-L69

@ccurme
ccurme (ccurme) enabled auto-merge (squash) August 13, 2026 12:43
@ccurme
ccurme (ccurme) merged commit ee88091 into master Aug 13, 2026
94 checks passed
@ccurme
ccurme (ccurme) deleted the cc/openai-3.0-compat branch August 13, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file (e.g. `pyproject.toml` or `uv.lock`) feature For PRs that implement a new feature; NOT A FEATURE REQUEST integration PR made that is related to a provider partner package integration internal openai `langchain-openai` package issues & PRs size: S 50-199 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants