Skip to main content
LangChain’s ChatOpenAI wraps the official OpenAI SDK, so it inherits that SDK’s base URL behaviour and adds a naming layer of its own. The two language versions do not spell the setting the same way, which is the first thing to get right, and the framework carries a default model, which is the second.

Python

base_url and api_key are the canonical constructor arguments. Both are pydantic aliases: the underlying fields are openai_api_base and openai_api_key, and passing either spelling works. The environment fallbacks are OPENAI_API_BASE, which LangChain reads itself, and OPENAI_BASE_URL, which the OpenAI SDK underneath reads when LangChain has not.

JavaScript

The base URL is nested and the key is not. apiKey is a top-level option; baseURL lives inside configuration, which is passed through to the underlying OpenAI client. A top-level baseURL on this class is not an option and is ignored rather than rejected, which is why a JavaScript configuration that looks right can still reach OpenAI. If your calls are being answered by the wrong API, this is the line to look at.

The default model is the thing that bites

ChatOpenAI carries a default model, in both languages, and it is a bare OpenAI id. The field defaults to gpt-3.5-turbo, so a ChatOpenAI() constructed without a model argument is not sending nothing: it is sending a name with no provider in front of it. Against this API that is refused with 400 and the code model_id_not_pinned. This is the most common way a LangChain application fails on its first request here, and it is worth stating why it is confusing: the failure names the model field, and the developer never wrote a model field. Every construction site needs the argument, including the ones in library code and templates you copied. See the integrations overview for the id forms, and Models for finding the exact strings your key can call.

What else is specific to this framework

/v1 belongs in the value. LangChain passes your base URL straight to the OpenAI SDK, which appends /chat/completions and adds no /v1 of its own. This is inherited behaviour rather than a LangChain rule, and it is the same in both languages. Recent Python versions send stream_options even to a custom base URL. ChatOpenAI has a stream_usage setting that asks for token counts inside the stream. Its own documentation records that the behaviour changed in langchain-openai 0.3.35: it used to be switched off whenever a custom base URL or client was set, and it is now on for those too. Your request body is forwarded here as you sent it, so this reaches the provider rather than us, and what it does there is the provider’s answer to give. If a provider rejects it, setting stream_usage=False is the lever. Embeddings are a separate class and a separate problem. OpenAIEmbeddings takes the same base_url alias and defaults to an OpenAI embedding model, and this API does not serve embeddings. Anything in a LangChain application that builds a vector store, a retriever, or a memory from embeddings needs an embedding provider that is not this one. Point ChatOpenAI here and leave that class where it was. Nothing calls the catalog. ChatOpenAI does not ask GET /v1/models to validate a model name, in either language. The library warns you off itself for third-party endpoints. The Python module’s own documentation says ChatOpenAI targets the official OpenAI API specification only, and that non-standard response fields added by other providers are not extracted or preserved. That warning is about fields we do not add: what comes back from here is the OpenAI chat completion shape, and the extra things we do carry ride on response headers and an SSE comment line rather than inside the body. See OpenAI compatibility.

Where the request id is

LangChain’s Python package does not re-raise the OpenAI SDK’s exceptions untouched, but it does keep them reachable. It defines subclasses that inherit from both the SDK exception and a LangChain error type, so OpenAIAuthenticationError is an openai.AuthenticationError, and it constructs them with the original response and body. An except openai.APIError you already have keeps catching, and the request id is where it was:
On the success path, Python’s ChatOpenAI takes include_response_headers=True, which puts the response headers into response_metadata["headers"] on the message, so x-uniblock-request-id and x-request-id are readable there. That is off by default.

What we did not verify

Checked, and where. That Python’s canonical arguments are base_url and api_key and that they are aliases for openai_api_base and openai_api_key; that the environment fallbacks are OPENAI_API_BASE and OPENAI_BASE_URL; that the Python model field defaults to gpt-3.5-turbo; that JavaScript takes baseURL inside configuration and apiKey at the top level and carries the same default model; that stream_usage changed as described and in which release; that OpenAIEmbeddings takes the same alias and defaults to an OpenAI embedding model; that the wrapped exception classes inherit from the SDK’s and preserve response and body; that neither class calls the catalog. Read from the langchain-ai/langchain repository’s libs/partners/openai sources and the langchain-ai/langchainjs repository’s libs/providers/langchain-openai sources, on their default branches. Not checked. That a call configured this way succeeds against our hostname. Nothing on this page has been executed, because the base URL this site prints is not yet a host that answers. Not checked either. Which LangChain flows reach OpenAIEmbeddings by default. That the class exists, takes the same setting, and points at an embedding model was read from its source; which vector store, memory or retriever defaults pull it in without being asked was not traced, so the paragraph above tells you the class is the problem rather than naming the flows that reach it. And not checked. What a provider does with the stream_options that recent Python versions send. We forward the body as sent, so this is a question about the provider rather than about us, and we have not run it to find out.