Skip to content

nodestep.chat.integrations.openai

The OpenAI chat integration: OpenAIChat for the OpenAI Chat Completions API and APIs compatible with it.

It has the Chat protocol, so it works wherever a chat model does; see Agents and tools.

OpenAIChat is in the openai extra:

uv add "nodestep[openai] @ git+https://github.com/nodestep-ai/nodestep"

import nodestep works without the extra. OpenAIChat is exported lazily, and using it without the extra raises IntegrationNotInstalledError, an ImportError whose message shows the command above.

OpenAIChat.from_env(model=...) reads the variables below. Empty ones count as unset, and keyword arguments override them.

Variable Setting Default
OPENAI_API_KEY api_key required
OPENAI_BASE_URL base_url the OpenAI API
OPENAI_ORG_ID organization not sent
OPENAI_PROJECT_ID project not sent
OPENAI_TIMEOUT timeout 60 seconds
OPENAI_MAX_RETRIES max_retries 3

These are the variables in the repository's .env.example. The OpenAI SDK handles the timeout and the retries; its own defaults are 600 seconds and 2 retries. Nothing loads a .env file, so pass it to the command:

cp .env.example .env
uv run --env-file .env python app.py

Without a key, from_env raises ModelProviderError:

from nodestep import ModelProviderError, OpenAIChat

try:
    OpenAIChat.from_env(model="gpt-6-luna")
except ModelProviderError as error:
    print(error)
OpenAIChat.from_env found no API key: OPENAI_API_KEY is not set or is empty; set it, or pass api_key=

In an agent, create the model where the graph is built, and keep ScriptedChat in tests:

-chat = ScriptedChat([...])
+chat = OpenAIChat.from_env(model="gpt-6-luna")

chat.requests and chat.responses exist only on ScriptedChat, so drop the lines that read them.

The OpenAI SDK reads some variables itself when it builds a client, with or without from_env. nodestep does not patch the SDK.

  • For a client you build, OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_ORG_ID and OPENAI_PROJECT_ID when you do not pass them.
  • OPENAI_CUSTOM_HEADERS sets headers sent with every request.
  • OPENAI_LOG sets the SDK's logging.
  • OPENAI_ADMIN_KEY and OPENAI_WEBHOOK_SECRET are read too.
  • httpx reads the proxy and CA certificate variables, such as HTTPS_PROXY and SSL_CERT_FILE.

How requests, responses and errors are mapped:

  • SDK errors are raised as ModelProviderError, with the SDK's error as the cause.
  • Retries happen inside the SDK and are not nodestep events.
  • output_schema= uses OpenAI's strict mode, and the schema is rewritten for it. Every property becomes required, so the model writes fields that have a default too. Objects forbid extra properties, "default": null entries are removed, $ref entries with siblings are inlined, and the schema is sent under the name final_output.
  • A message without content is sent as an empty string.
  • A response with several choices (n > 1), or a tool call that is not a function call, raises ModelProviderError.
  • ChatResponse.model is the model name the API reported; ctx.model in middleware is the model that was requested.

Chat model

OpenAIChat

OpenAIChat(model, *, api_key=None, base_url=None, organization=None, project=None, timeout=None, max_retries=None, client=None, stream_usage=True, **request_parameters)

Chat model for the OpenAI Chat Completions API, or a compatible one.

The constructor reads no environment variable; from_env does. The OpenAI SDK reads some variables itself; the module introduction lists them. model is always required:

from nodestep import OpenAIChat

chat = OpenAIChat(
    model="gpt-6-luna",
    api_key="sk-your-key",
    timeout=30,
    max_retries=2,
    temperature=0.2,
)
print(chat.model, chat.request_parameters)
gpt-6-luna {'temperature': 0.2}
  • Extra keyword arguments, such as temperature, are sent with every request.
  • Keys the request mapping sets itself (messages, tools, tool_choice, response_format, stream, stream_options, model) raise TypeError; use model_node(tool_choice=...) and output_schema=.
  • stream_usage=True (the default) asks for token usage at the end of streamed responses.

Pass your own AsyncOpenAI client as client=. The connection arguments (api_key, base_url, organization, project, timeout, max_retries) then raise TypeError, so set them on the client:

from openai import AsyncOpenAI

from nodestep import OpenAIChat

client = AsyncOpenAI(api_key="sk-your-key", base_url="http://localhost:8000/v1")
chat = OpenAIChat(model="gpt-6-luna", client=client)
print(chat.model)
gpt-6-luna

await chat.aclose(), or async with OpenAIChat(...) as chat:, closes the HTTP client.

Parameters
modelstr

Model name, e.g. "gpt-6-luna".

api_keystr | None

API key; required unless client is given.

base_urlstr | None

Base URL of an OpenAI-compatible API; None means the OpenAI API.

organizationstr | None

OpenAI organization id, sent only when given.

projectstr | None

OpenAI project id, sent only when given.

timeoutfloat | None

HTTP timeout in seconds; None means 60.

max_retriesint | None

Retries of transient HTTP errors, done by the SDK; None means 3.

clientAsyncOpenAI | None

Client to use instead of the connection arguments.

stream_usagebool

Ask for token usage at the end of streamed responses.

**request_parametersAny

Sent with every request, such as temperature.

Raises
IntegrationNotInstalledError

If the openai extra is not installed.

ModelProviderError

If client is not given and api_key is missing or empty.

TypeError

If client is combined with connection arguments, or request_parameters sets a key the request mapping sets: messages, tools, tool_choice, response_format, stream, stream_options or model.

Methods

from_env(model, ...)

class method Create a chat model whose connection comes from OPENAI_* variables.

from_env(model, **kwargs) -> OpenAIChat

Reads the variables in the table of the module introduction; empty ones count as unset and keyword arguments win.

Parameters
modelstr

Model name, e.g. "gpt-6-luna".

**kwargsAny

Passed to OpenAIChat.

Raises
TypeError

If client is passed; give it to the constructor instead.

ModelProviderError

If OPENAI_API_KEY is unset or empty and no api_key is passed.

Examples

>>> chat = OpenAIChat.from_env(model="gpt-6-luna")

async aclose()

Close the underlying HTTP client.

async aclose() -> None

async complete(request)

Send a request and wait for the full response.

async complete(request) -> ChatResponse

Parameters
requestChatRequest

Messages, tools and an optional output schema.

Raises
ModelProviderError

If the OpenAI SDK reports an error.

async stream(request)

Stream a response, with the token usage when stream_usage is set.

async stream(request) -> AsyncIterator[ChatStreamChunk]

Parameters
requestChatRequest

Messages, tools and an optional output schema.

Raises
ModelProviderError

If the OpenAI SDK reports an error.

Settings

OpenAISettings

OpenAI connection settings read from OPENAI_* variables; empty ones count as unset.

The table in the module introduction lists the variable and the default of each field.

Attributes
api_keystr | None

OpenAI API key.

base_urlstr | None

Base URL for the OpenAI-compatible API; None means the OpenAI API.

organizationstr | None

OpenAI organization ID.

projectstr | None

OpenAI project ID.

timeoutfloat

HTTP request timeout in seconds.

max_retriesint

Maximum retry attempts for transient failures.