feat(openai): support openai 3.0 SDK - #39613
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
for CI do we really need to be running both? or can we conditionally run only the newest in CI? |
|
I think we'll need to preserve the public custom HTTP-client contract
Since supporting OpenAI v3 is required, we'll probably want to accept |
|
I think we'll still need to keep Otherwise we can keep removed but import conditionally |
|
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.
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. |
We don't need to run both. I just added two |
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. |
…ient` MRO" This reverts commit 989d49c.
Mason Daugherty (mdrxy)
left a comment
There was a problem hiding this comment.
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.
Co-authored-by: Mason Daugherty <github@mdrxy.com> Signed-off-by: ccurme <chester.curme@gmail.com>
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 |
Supports openai 3.x in langchain-openai.
The major change associated with openai 3.0 is a migration from
httpxtohttpx2: https://github.com/openai/openai-python/releases/tag/v3.0.0.Here we support
openai2.x and 3.x simultaneously inlangchain-openai.httpxis already a dependency oflangchain-coreand thelangsmithsdk, so all environments running a langchain package install it.We add a _compat.py that re-exports whatever
httpxlibrary the openai SDK uses. For any use of httpx that feeds into the openai SDK, we import from_compat.httpxcontinues to be used elsewhere inlangchain-openai(langchain-coreexports an SSRF-safe httpx client that we use for image token counting).Manual specification of
httpxclients remains supported via OpenAI's legacy escape hatch (langchain passes through), but support for this may be removed in a future release.