diff --git a/README.md b/README.md index 9ca8fc90a4..1e366a8615 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -# OpenAI Python API library +# OpenAI Python API Library - [![PyPI version](https://img.shields.io/pypi/v/openai.svg?label=pypi%20(stable))](https://pypi.org/project/openai/) +[![Python version](https://img.shields.io/pypi/pyversions/openai.svg)](https://pypi.org/project/openai/) +[![License](https://img.shields.io/github/license/openai/openai-python)](LICENSE) -The OpenAI Python library provides convenient access to the OpenAI REST API from any Python 3.9+ -application. The library includes type definitions for all request params and response fields, -and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). +The OpenAI Python library provides convenient access to the OpenAI REST and Realtime APIs from any Python 3.9+ application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). It is generated from our [OpenAPI specification](https://github.com/openai/openai-openapi) with [Stainless](https://stainlessapi.com/). @@ -13,16 +12,58 @@ It is generated from our [OpenAPI specification](https://github.com/openai/opena The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs/api-reference). The full API of this library can be found in [api.md](api.md). +## Table of Contents + +- [Installation](#installation) +- [Usage](#usage) + - [Responses API](#responses-api) + - [Chat Completions API](#chat-completions-api) + - [Authentication & Workload Identity](#authentication--workload-identity) +- [Core Capabilities](#core-capabilities) + - [Vision](#vision) + - [Async Usage](#async-usage) + - [Streaming Responses](#streaming-responses) + - [Realtime API](#realtime-api) + - [File Uploads](#file-uploads) + - [Nested Parameters](#nested-parameters) +- [Cloud Providers](#cloud-providers) + - [Microsoft Azure OpenAI](#microsoft-azure-openai) + - [Amazon Bedrock](#amazon-bedrock) +- [Advanced Usage](#advanced-usage) + - [Using Types & Null Checking](#using-types--null-checking) + - [Pagination](#pagination) + - [Webhook Verification](#webhook-verification) + - [Handling Errors](#handling-errors) + - [Request IDs](#request-ids) + - [Retries & Timeouts](#retries--timeouts) + - [Logging](#logging) + - [Accessing Raw Response Data](#accessing-raw-response-data) + - [Making Custom/Undocumented Requests](#making-customundocumented-requests) + - [Configuring the HTTP Client](#configuring-the-http-client) + - [Managing HTTP Resources](#managing-http-resources) +- [Versioning & Requirements](#versioning--requirements) +- [Contributing](#contributing) + +--- + ## Installation ```sh -# install from PyPI +# Install base package from PyPI pip install openai + +# Optional high-concurrency async backend +pip install "openai[aiohttp]" + +# Optional Amazon Bedrock support +pip install "openai[bedrock]" ``` +--- + ## Usage -The full API of this library can be found in [api.md](api.md). +### Responses API The primary API for interacting with OpenAI models is the [Responses API](https://platform.openai.com/docs/api-reference/responses). You can generate text from the model with the code below. @@ -44,7 +85,9 @@ response = client.responses.create( print(response.output_text) ``` -The previous standard (supported indefinitely) for generating text is the [Chat Completions API](https://platform.openai.com/docs/api-reference/chat). You can use that API to generate text from the model with the code below. +### Chat Completions API + +The previous standard (supported indefinitely) for generating text is the [Chat Completions API](https://platform.openai.com/docs/api-reference/chat): ```python from openai import OpenAI @@ -65,17 +108,14 @@ completion = client.chat.completions.create( print(completion.choices[0].message.content) ``` -While you can provide an `api_key` keyword argument, -we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `OPENAI_API_KEY="My API Key"` to your `.env` file -so that your API key is not stored in source control. -[Get an API key here](https://platform.openai.com/settings/organization/api-keys). +### Authentication & Workload Identity -### Workload Identity Authentication +While you can provide an `api_key` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) to add `OPENAI_API_KEY="My API Key"` to your `.env` file so that your API key is not stored in source control. [Get an API key here](https://platform.openai.com/settings/organization/api-keys). For secure, automated environments like cloud-managed Kubernetes, Azure, and Google Cloud Platform, you can use workload identity authentication with short-lived tokens from cloud identity providers instead of long-lived API keys. -#### Kubernetes (service account tokens) +
+Kubernetes (service account tokens) ```python from openai import OpenAI @@ -96,8 +136,10 @@ response = client.chat.completions.create( messages=[{"role": "user", "content": "Hello!"}], ) ``` +
-#### Azure (managed identity) +
+Azure (managed identity) ```python from openai import OpenAI @@ -113,8 +155,10 @@ client = OpenAI( }, ) ``` +
-#### Google Cloud Platform (compute engine metadata) +
+Google Cloud Platform (compute engine metadata) ```python from openai import OpenAI @@ -128,17 +172,17 @@ client = OpenAI( }, ) ``` +
-#### Custom subject token provider +
+Custom subject token provider & refresh buffer ```python from openai import OpenAI - def get_custom_token() -> str: return "your-jwt-token" - client = OpenAI( workload_identity={ "identity_provider_id": "idp-123", @@ -147,25 +191,15 @@ client = OpenAI( "token_type": "jwt", "get_token": get_custom_token, }, + "refresh_buffer_seconds": 120.0, # Default is 1200 seconds (20 minutes) } ) ``` +
-You can also customize the token refresh buffer (default is 1200 seconds (20 minutes) before expiration): +--- -```python -from openai import OpenAI -from openai.auth import k8s_service_account_token_provider - -client = OpenAI( - workload_identity={ - "identity_provider_id": "idp-123", - "service_account_id": "sa-456", - "provider": k8s_service_account_token_provider("/var/token"), - "refresh_buffer_seconds": 120.0, - } -) -``` +## Core Capabilities ### Vision @@ -215,7 +249,7 @@ response = client.responses.create( ) ``` -## Async usage +### Async Usage Simply import `AsyncOpenAI` instead of `OpenAI` and use `await` with each API call: @@ -229,91 +263,51 @@ client = AsyncOpenAI( api_key=os.environ.get("OPENAI_API_KEY"), ) - async def main() -> None: response = await client.responses.create( model="gpt-5.5", input="Explain disestablishmentarianism to a smart five year old." ) print(response.output_text) - asyncio.run(main()) ``` -Functionality between the synchronous and asynchronous clients is otherwise identical. - -### With aiohttp - -By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. - -The `aiohttp` backend requires Python 3.10 or later. +
+High Concurrency with aiohttp -You can enable this by installing `aiohttp`: +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend (requires Python 3.10 or later). ```sh -# install from PyPI pip install openai[aiohttp] ``` -Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: +Enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python import os import asyncio -from openai import DefaultAioHttpClient -from openai import AsyncOpenAI - +from openai import DefaultAioHttpClient, AsyncOpenAI async def main() -> None: async with AsyncOpenAI( - api_key=os.environ.get("OPENAI_API_KEY"), # This is the default and can be omitted + api_key=os.environ.get("OPENAI_API_KEY"), http_client=DefaultAioHttpClient(), ) as client: chat_completion = await client.chat.completions.create( - messages=[ - { - "role": "user", - "content": "Say this is a test", - } - ], + messages=[{"role": "user", "content": "Say this is a test"}], model="gpt-5.5", ) - asyncio.run(main()) ``` +
-### Experimental HTTPX2 support - -To opt in to experimental HTTPX2 support, install the optional extra on Python 3.10 or later: - -```sh -pip install 'openai[httpx2]' -``` - -```python -from openai import OpenAI, AsyncOpenAI, DefaultHttpx2Client, DefaultAsyncHttpx2Client - -client = OpenAI(http_client=DefaultHttpx2Client()) -async_client = AsyncOpenAI(http_client=DefaultAsyncHttpx2Client()) -``` - -See [`examples/httpx2_client.py`](examples/httpx2_client.py) for a minimal runnable example. - -The module-level client can be configured in the same way: - -```python -import openai - -openai.http_client = openai.DefaultHttpx2Client() -``` - -Parsed API models are unchanged, but requests, raw and streaming responses, and transport-level exceptions may be HTTPX2 objects at runtime. Code that catches HTTPX exceptions or relies on HTTPX-specific mocks, transports, authentication, hooks, or instrumentation may need to be updated. Transport-facing type annotations may still describe HTTPX. - -## Streaming responses +### Streaming Responses We provide support for streaming responses using Server Side Events (SSE). +Synchronous streaming: + ```python from openai import OpenAI @@ -329,7 +323,7 @@ for event in stream: print(event) ``` -The async client uses the exact same interface. +Asynchronous streaming: ```python import asyncio @@ -337,7 +331,6 @@ from openai import AsyncOpenAI client = AsyncOpenAI() - async def main(): stream = await client.responses.create( model="gpt-5.5", @@ -348,21 +341,16 @@ async def main(): async for event in stream: print(event) - asyncio.run(main()) ``` -## Realtime API - -The Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as [function calling](https://platform.openai.com/docs/guides/function-calling) through a WebSocket connection. +### Realtime API -Under the hood the SDK uses the [`websockets`](https://websockets.readthedocs.io/en/stable/) library to manage connections. +The Realtime API enables low-latency, multi-modal conversational experiences. It supports text and audio as input/output, as well as [function calling](https://platform.openai.com/docs/guides/function-calling) over a WebSocket connection using [`websockets`](https://websockets.readthedocs.io/en/stable/). -The Realtime API works through a combination of client-sent events and server-sent events. Clients can send events to do things like update session configuration or send text and audio inputs. Server events confirm when audio responses have completed, or when a text response from the model has been received. A full event reference can be found [here](https://platform.openai.com/docs/api-reference/realtime-client-events) and a guide can be found [here](https://platform.openai.com/docs/guides/realtime). +A full event reference can be found [here](https://platform.openai.com/docs/api-reference/realtime-client-events) and a guide can be found [here](https://platform.openai.com/docs/guides/realtime). -Basic text based example: - -```py +```python import asyncio from openai import AsyncOpenAI @@ -386,23 +374,22 @@ async def main(): async for event in connection: if event.type == "response.output_text.delta": print(event.delta, flush=True, end="") - elif event.type == "response.output_text.done": print() - elif event.type == "response.done": break asyncio.run(main()) ``` -However the real magic of the Realtime API is handling audio inputs / outputs, see this example [TUI script](https://github.com/openai/openai-python/blob/main/examples/realtime/push_to_talk_app.py) for a fully fledged example. +See this [TUI script example](https://github.com/openai/openai-python/blob/main/examples/realtime/push_to_talk_app.py) for a complete push-to-talk audio application. -### Realtime error handling +
+Realtime Error Handling -Whenever an error occurs, the Realtime API will send an [`error` event](https://platform.openai.com/docs/guides/realtime-model-capabilities#error-handling) and the connection will stay open and remain usable. This means you need to handle it yourself, as _no errors are raised directly_ by the SDK when an `error` event comes in. +Whenever an error occurs, the Realtime API sends an [`error` event](https://platform.openai.com/docs/guides/realtime-model-capabilities#error-handling) while keeping the connection open. Handle error events explicitly: -```py +```python client = AsyncOpenAI() async with client.realtime.connect(model="gpt-realtime-2") as connection: @@ -414,137 +401,217 @@ async with client.realtime.connect(model="gpt-realtime-2") as connection: print(event.error.event_id) print(event.error.message) ``` +
+ +### File Uploads -## Using types +Request parameters that correspond to file uploads can be passed as `bytes`, a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, or a tuple of `(filename, contents, media type)`: -Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: +```python +from pathlib import Path +from openai import OpenAI -- Serializing back into JSON, `model.to_json()` -- Converting to a dictionary, `model.to_dict()` +client = OpenAI() -Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +client.files.create( + file=Path("input.jsonl"), + purpose="fine-tune", +) +``` -## Pagination +The async client uses the exact same interface and reads `PathLike` instances asynchronously automatically. -List methods in the OpenAI API are paginated. +### Nested Parameters -This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually: +Nested parameters are dictionaries, typed using `TypedDict`: ```python from openai import OpenAI client = OpenAI() -all_jobs = [] -# Automatically fetches more pages as needed. -for job in client.fine_tuning.jobs.list( - limit=20, -): - # Do something with job here - all_jobs.append(job) -print(all_jobs) +response = client.responses.create( + input=[ + { + "role": "user", + "content": "How much ?", + } + ], + model="gpt-5.5", + text={"format": {"type": "json_object"}}, +) ``` -Or, asynchronously: +--- -```python -import asyncio -from openai import AsyncOpenAI +## Cloud Providers -client = AsyncOpenAI() +### Microsoft Azure OpenAI +To use this library with [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview), use the `AzureOpenAI` class instead of `OpenAI`: -async def main() -> None: - all_jobs = [] - # Iterate through items across all pages, issuing requests as needed. - async for job in client.fine_tuning.jobs.list( - limit=20, - ): - all_jobs.append(job) - print(all_jobs) +```python +from openai import AzureOpenAI +# Gets the API Key from environment variable AZURE_OPENAI_API_KEY +client = AzureOpenAI( + api_version="2023-07-01-preview", + azure_endpoint="https://example-endpoint.openai.azure.com", +) -asyncio.run(main()) +completion = client.chat.completions.create( + model="deployment-name", # e.g. gpt-35-instant + messages=[ + { + "role": "user", + "content": "How do I output all files in a directory using Python?", + }, + ], +) +print(completion.to_json()) ``` -Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages: +> [!IMPORTANT] +> The Azure API shape differs from the core API shape which means static types for responses/params won't always be correct. + +In addition to options provided in the base `OpenAI` client, Azure options include `azure_endpoint` (`AZURE_OPENAI_ENDPOINT`), `azure_deployment`, `api_version` (`OPENAI_API_VERSION`), `azure_ad_token` (`AZURE_OPENAI_AD_TOKEN`), and `azure_ad_token_provider`. An example using Microsoft Entra ID is available in [examples/azure_ad.py](https://github.com/openai/openai-python/blob/main/examples/azure_ad.py). + +### Amazon Bedrock + +To use this library with [Amazon Bedrock's OpenAI-compatible API](https://docs.aws.amazon.com/bedrock/latest/userguide/models-api-compatibility.html), configure the standard `OpenAI` client with the Bedrock provider. + +Install optional dependencies: +```sh +pip install 'openai[bedrock]' +``` ```python -first_page = await client.fine_tuning.jobs.list( - limit=20, +from openai import OpenAI +from openai.providers import bedrock + +client = OpenAI( + provider=bedrock( + region="us-west-2", + ) +) + +response = client.responses.create( + model="openai.gpt-5.4", + input="Say hello!", ) -if first_page.has_next_page(): - print(f"will fetch next page using these details: {first_page.next_page_info()}") - next_page = await first_page.get_next_page() - print(f"number of items we just fetched: {len(next_page.data)}") -# Remove `await` for non-async usage. +print(response.output_text) ``` -Or just work directly with the returned data: +
+Bedrock Credentials, Base URL, and Legacy Client Options + +The provider configures AWS authentication and the Bedrock Mantle endpoint while retaining standard SDK features. + +- **Credentials:** Supports standard AWS credential chain (environment, profile, named profile via `provider=bedrock(profile="my-profile")`, ECS/EKS/EC2 metadata). +- **Explicit Auth:** Pass `access_key_id`, `secret_access_key`, and optional `session_token`, or a refreshable `credential_provider`. +- **Base URL:** Pass `base_url` to `bedrock(...)` or set `AWS_BEDROCK_BASE_URL` to override `https://bedrock-mantle..api.aws/openai/v1`. +- **Bearer Tokens:** Set `AWS_BEARER_TOKEN_BEDROCK` to an [Amazon Bedrock API key](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html), pass `api_key`, or provide `token_provider=lambda: refresh_bedrock_token()`. + +**Legacy `BedrockOpenAI` Client:** + +`BedrockOpenAI` and `AsyncBedrockOpenAI` remain available for existing applications: ```python -first_page = await client.fine_tuning.jobs.list( - limit=20, +from openai import BedrockOpenAI + +client = BedrockOpenAI( + aws_region="us-west-2", + aws_profile="my-profile", ) +``` +The legacy client also continues to support `openai.api_type = "amazon-bedrock"` or `OPENAI_API_TYPE=amazon-bedrock`. +
-print(f"next page cursor: {first_page.after}") # => "next page cursor: ..." -for job in first_page.data: - print(job.id) +--- + +## Advanced Usage + +### Using Types & Null Checking + +Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which provide helper methods: +- Serializing to JSON: `model.to_json()` +- Converting to a dictionary: `model.to_dict()` -# Remove `await` for non-async usage. +Set `python.analysis.typeCheckingMode` to `basic` in VS Code to see type errors in your editor. + +In API responses, a field may be explicitly `null` or missing entirely; both yield `None` in Python. Differentiate the cases using `.model_fields_set`: + +```python +if response.my_field is None: + if 'my_field' not in response.model_fields_set: + print('Got json like {}, without a "my_field" key present at all.') + else: + print('Got json like {"my_field": null}.') ``` -## Nested params +### Pagination + +List methods provide auto-paginating iterators so you do not have to request successive pages manually. -Nested parameters are dictionaries, typed using `TypedDict`, for example: +Synchronous pagination: ```python from openai import OpenAI client = OpenAI() -response = client.responses.create( - input=[ - { - "role": "user", - "content": "How much ?", - } - ], - model="gpt-5.5", - text={"format": {"type": "json_object"}}, -) +all_jobs = [] +for job in client.fine_tuning.jobs.list(limit=20): + all_jobs.append(job) +print(all_jobs) ``` -## File uploads - -Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. +Asynchronous pagination: ```python -from pathlib import Path -from openai import OpenAI +import asyncio +from openai import AsyncOpenAI -client = OpenAI() +client = AsyncOpenAI() -client.files.create( - file=Path("input.jsonl"), - purpose="fine-tune", -) +async def main() -> None: + all_jobs = [] + async for job in client.fine_tuning.jobs.list(limit=20): + all_jobs.append(job) + print(all_jobs) + +asyncio.run(main()) ``` -The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. +
+Granular Page Control & Cursor Access -## Webhook Verification +Use `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` for granular control: -Verifying webhook signatures is _optional but encouraged_. +```python +first_page = await client.fine_tuning.jobs.list(limit=20) +if first_page.has_next_page(): + print(f"Will fetch next page using details: {first_page.next_page_info()}") + next_page = await first_page.get_next_page() + print(f"Number of items fetched: {len(next_page.data)}") +``` + +Or work directly with the returned data and cursor: -For more information about webhooks, see [the API docs](https://platform.openai.com/docs/guides/webhooks). +```python +first_page = await client.fine_tuning.jobs.list(limit=20) +print(f"Next page cursor: {first_page.after}") +for job in first_page.data: + print(job.id) +``` +
-### Parsing webhook payloads +### Webhook Verification -For most use cases, you will likely want to verify the webhook and parse the payload at the same time. To achieve this, we provide the method `client.webhooks.unwrap()`, which parses a webhook request and verifies that it was sent by OpenAI. This method will raise an error if the signature is invalid. +Verifying webhook signatures is optional but encouraged. See the [Webhook Documentation](https://platform.openai.com/docs/guides/webhooks). -Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). The `.unwrap()` method will parse this JSON for you into an event object after verifying the webhook was sent from OpenAI. +Parsing and verifying payloads simultaneously using `client.webhooks.unwrap()`: ```python from openai import OpenAI @@ -553,36 +620,26 @@ from flask import Flask, request app = Flask(__name__) client = OpenAI() # OPENAI_WEBHOOK_SECRET environment variable is used by default - @app.route("/webhook", methods=["POST"]) def webhook(): request_body = request.get_data(as_text=True) try: event = client.webhooks.unwrap(request_body, request.headers) - if event.type == "response.completed": print("Response completed:", event.data) elif event.type == "response.failed": print("Response failed:", event.data) - else: - print("Unhandled event type:", event.type) - return "ok" except Exception as e: print("Invalid signature:", e) return "Invalid signature", 400 - - -if __name__ == "__main__": - app.run(port=8000) ``` -### Verifying webhook payloads directly - -In some cases, you may want to verify the webhook separately from parsing the payload. If you prefer to handle these steps separately, we provide the method `client.webhooks.verify_signature()` to _only verify_ the signature of a webhook request. Like `.unwrap()`, this method will raise an error if the signature is invalid. +
+Verifying Webhook Signatures Directly -Note that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). You will then need to parse the body after verifying the signature. +If you prefer to verify the signature separately from parsing: ```python import json @@ -590,8 +647,7 @@ from openai import OpenAI from flask import Flask, request app = Flask(__name__) -client = OpenAI() # OPENAI_WEBHOOK_SECRET environment variable is used by default - +client = OpenAI() @app.route("/webhook", methods=["POST"]) def webhook(): @@ -599,29 +655,18 @@ def webhook(): try: client.webhooks.verify_signature(request_body, request.headers) - - # Parse the body after verification event = json.loads(request_body) print("Verified event:", event) - return "ok" except Exception as e: print("Invalid signature:", e) return "Invalid signature", 400 - - -if __name__ == "__main__": - app.run(port=8000) ``` +
-## Handling errors - -When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `openai.APIConnectionError` is raised. - -When the API returns a non-success status code (that is, 4xx or 5xx -response), a subclass of `openai.APIStatusError` is raised, containing `status_code` and `response` properties. +### Handling Errors -All errors inherit from `openai.APIError`. +When unable to connect to the API, a subclass of `openai.APIConnectionError` is raised. When the API returns a non-success status code (4xx or 5xx), a subclass of `openai.APIStatusError` is raised, containing `status_code` and `response` properties. All errors inherit from `openai.APIError`. ```python import openai @@ -636,7 +681,7 @@ try: ) except openai.APIConnectionError as e: print("The server could not be reached") - print(e.__cause__) # an underlying Exception, likely raised within httpx. + print(e.__cause__) # Underlying Exception raised within httpx. except openai.RateLimitError as e: print("A 429 status code was received; we should back off a bit.") except openai.APIStatusError as e: @@ -645,24 +690,22 @@ except openai.APIStatusError as e: print(e.response) ``` -Error codes are as follows: +Error status code mapping: -| Status Code | Error Type | -| ----------- | -------------------------- | -| 400 | `BadRequestError` | -| 401 | `AuthenticationError` | -| 403 | `PermissionDeniedError` | -| 404 | `NotFoundError` | -| 422 | `UnprocessableEntityError` | -| 429 | `RateLimitError` | -| >=500 | `InternalServerError` | -| N/A | `APIConnectionError` | +| Status Code | Error Type | +| :--- | :--- | +| 400 | `BadRequestError` | +| 401 | `AuthenticationError` | +| 403 | `PermissionDeniedError` | +| 404 | `NotFoundError` | +| 422 | `UnprocessableEntityError` | +| 429 | `RateLimitError` | +| >=500 | `InternalServerError` | +| N/A | `APIConnectionError` | -## Request IDs +### Request IDs -> For more information on debugging requests, see [these docs](https://platform.openai.com/docs/api-reference/debugging-requests) - -All object responses in the SDK provide a `_request_id` property which is added from the `x-request-id` response header so that you can quickly log failing requests and report them back to OpenAI. +All object responses provide a public `_request_id` property extracted from the `x-request-id` response header for logging and debugging: ```python response = await client.responses.create( @@ -672,12 +715,8 @@ response = await client.responses.create( print(response._request_id) # req_123 ``` -Note that unlike other properties that use an `_` prefix, the `_request_id` property -_is_ public. Unless documented otherwise, _all_ other `_` prefix properties, -methods and modules are _private_. - -> [!IMPORTANT] -> If you need to access request IDs for failed requests you must catch the `APIStatusError` exception +> [!IMPORTANT] +> To access request IDs for failed requests, catch the `APIStatusError` exception: ```python import openai @@ -691,200 +730,101 @@ except openai.APIStatusError as exc: raise exc ``` -## Retries - -Certain errors are automatically retried 2 times by default, with a short exponential backoff. -Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, -429 Rate Limit, and >=500 Internal errors are all retried by default. +### Retries & Timeouts -You can use the `max_retries` option to configure or disable retry settings: +**Retries:** +Certain errors are automatically retried 2 times by default with exponential backoff (connection errors, 408, 409, 429, >=500). Configure with `max_retries`: ```python from openai import OpenAI -# Configure the default for all requests: -client = OpenAI( - # default is 2 - max_retries=0, -) +# Configure default for all requests: +client = OpenAI(max_retries=0) -# Or, configure per-request: +# Configure per-request: client.with_options(max_retries=5).chat.completions.create( - messages=[ - { - "role": "user", - "content": "How can I get the name of the current day in JavaScript?", - } - ], + messages=[{"role": "user", "content": "How can I get the day name in JS?"}], model="gpt-5.5", ) ``` -## Timeouts - -By default requests time out after 10 minutes. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: +**Timeouts:** +Requests time out after 10 minutes by default. Configure with `timeout` (float or `httpx.Timeout`): ```python from openai import OpenAI +import httpx -# Configure the default for all requests: -client = OpenAI( - # 20 seconds (default is 10 minutes) - timeout=20.0, -) - -# More granular control: -client = OpenAI( - timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), -) - -# Override per-request: -client.with_options(timeout=5.0).chat.completions.create( - messages=[ - { - "role": "user", - "content": "How can I list all files in a directory using Python?", - } - ], - model="gpt-5.5", -) +client = OpenAI(timeout=20.0) +client = OpenAI(timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0)) +client.with_options(timeout=5.0).chat.completions.create(...) ``` - -On timeout, an `APITimeoutError` is thrown. - -Note that requests that time out are [retried twice by default](#retries). - -## Advanced +*(On timeout, `APITimeoutError` is thrown and retried twice by default).* ### Logging -We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. - -You can enable logging by setting the environment variable `OPENAI_LOG` to `info`. +Enable logging by setting the `OPENAI_LOG` environment variable: ```shell -$ export OPENAI_LOG=info -``` - -Or to `debug` for more verbose logging. - -### How to tell whether `None` means `null` or missing - -In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`: - -```py -if response.my_field is None: - if 'my_field' not in response.model_fields_set: - print('Got json like {}, without a "my_field" key present at all.') - else: - print('Got json like {"my_field": null}.') +$ export OPENAI_LOG=info # or OPENAI_LOG=debug ``` -### Accessing raw response data (e.g. headers) +### Accessing Raw Response Data -The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., +Access raw HTTP response objects and headers using `.with_raw_response` (`LegacyAPIResponse`): -```py +```python from openai import OpenAI client = OpenAI() response = client.chat.completions.with_raw_response.create( - messages=[{ - "role": "user", - "content": "Say this is a test", - }], + messages=[{"role": "user", "content": "Say this is a test"}], model="gpt-5.5", ) print(response.headers.get('X-My-Header')) - -completion = response.parse() # get the object that `chat.completions.create()` would have returned -print(completion) +completion = response.parse() # Get parsed response object ``` -These methods return a [`LegacyAPIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. - -For the sync client this will mostly be the same with the exception -of `content` & `text` will be methods instead of properties. In the -async client, all methods will be async. - -A migration script will be provided & the migration in general should -be smooth. - -#### `.with_streaming_response` - -The above interface eagerly reads the full response body when you make the request, which may not always be what you want. - -To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. - -As such, `.with_streaming_response` methods return a different [`APIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_response.py) object, and the async client returns an [`AsyncAPIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_response.py) object. +Use `.with_streaming_response` to stream raw body content (`APIResponse` / `AsyncAPIResponse`): ```python with client.chat.completions.with_streaming_response.create( - messages=[ - { - "role": "user", - "content": "Say this is a test", - } - ], + messages=[{"role": "user", "content": "Say this is a test"}], model="gpt-5.5", ) as response: print(response.headers.get("X-My-Header")) - for line in response.iter_lines(): print(line) ``` -The context manager is required so that the response will reliably be closed. - -### Making custom/undocumented requests - -This library is typed for convenient access to the documented API. +### Making Custom/Undocumented Requests -If you need to access undocumented endpoints, params, or response properties, the library can still be used. +To access undocumented endpoints or params, use request helpers: -#### Undocumented endpoints - -To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other -http verbs. Options on the client will be respected (such as retries) when making this request. - -```py +```python import httpx +# Undocumented endpoints response = client.post( "/foo", cast_to=httpx.Response, body={"my_param": True}, ) -print(response.headers.get("x-foo")) +# Undocumented request params & response fields +# Pass extra_query, extra_body, or extra_headers +# Access extra response properties or response.model_extra ``` -#### Undocumented request params - -If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request -options. - -#### Undocumented response properties +### Configuring the HTTP Client -To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You -can also get all the extra fields on the Pydantic model as a dict with -[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra). - -### Configuring the HTTP client - -You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: - -- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) -- Custom [transports](https://www.python-httpx.org/advanced/transports/) -- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality +Override the underlying [httpx client](https://www.python-httpx.org/api/#client) for custom proxies or transports: ```python import httpx from openai import OpenAI, DefaultHttpxClient client = OpenAI( - # Or use the `OPENAI_BASE_URL` env var base_url="http://my.test.server.example.com:8083/v1", http_client=DefaultHttpxClient( proxy="http://my.test.proxy.example.com", @@ -893,171 +833,33 @@ client = OpenAI( ) ``` -You can also customize the client on a per-request basis by using `with_options()`: - -```python -client.with_options(http_client=DefaultHttpxClient(...)) -``` +### Managing HTTP Resources -### Managing HTTP resources +By default, underlying connections close on garbage collection. Manually close via `.close()` or a context manager: -By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. - -```py +```python from openai import OpenAI with OpenAI() as client: - # make requests here - ... - + pass # Make requests here # HTTP client is now closed ``` -## Microsoft Azure OpenAI +--- -To use this library with [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview), use the `AzureOpenAI` -class instead of the `OpenAI` class. - -> [!IMPORTANT] -> The Azure API shape differs from the core API shape which means that the static types for responses / params -> won't always be correct. +## Versioning & Requirements -```py -from openai import AzureOpenAI - -# gets the API Key from environment variable AZURE_OPENAI_API_KEY -client = AzureOpenAI( - # https://learn.microsoft.com/azure/ai-services/openai/reference#rest-api-versioning - api_version="2023-07-01-preview", - # https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource - azure_endpoint="https://example-endpoint.openai.azure.com", -) - -completion = client.chat.completions.create( - model="deployment-name", # e.g. gpt-35-instant - messages=[ - { - "role": "user", - "content": "How do I output all files in a directory using Python?", - }, - ], -) -print(completion.to_json()) -``` - -In addition to the options provided in the base `OpenAI` client, the following options are provided: - -- `azure_endpoint` (or the `AZURE_OPENAI_ENDPOINT` environment variable) -- `azure_deployment` -- `api_version` (or the `OPENAI_API_VERSION` environment variable) -- `azure_ad_token` (or the `AZURE_OPENAI_AD_TOKEN` environment variable) -- `azure_ad_token_provider` - -An example of using the client with Microsoft Entra ID (formerly known as Azure Active Directory) can be found [here](https://github.com/openai/openai-python/blob/main/examples/azure_ad.py). - -## Amazon Bedrock - -To use this library with [Amazon Bedrock's OpenAI-compatible API](https://docs.aws.amazon.com/bedrock/latest/userguide/models-api-compatibility.html), configure the standard `OpenAI` client with the Bedrock provider. - -Install the optional Bedrock dependencies to use the standard AWS credential chain and SigV4 authentication: - -```sh -pip install 'openai[bedrock]' -``` - -```py -from openai import OpenAI -from openai.providers import bedrock - -# Uses your normal AWS credentials. You can omit region when it is -# configured through AWS_REGION, AWS_DEFAULT_REGION, or your AWS profile. -client = OpenAI( - provider=bedrock( - region="us-west-2", - ) -) - -response = client.responses.create( - model="openai.gpt-5.4", - input="Say hello!", -) - -print(response.output_text) -``` - -The provider configures AWS authentication and the Bedrock Mantle endpoint while retaining the normal SDK resources, retries, streaming, and error handling. AWS controls which endpoints and features are supported; unsupported calls surface the provider's normal HTTP errors through the SDK. - -The default AWS credential chain supports environment credentials, shared credentials and config files, named profiles, SSO and assume-role profiles, and workload credentials such as ECS, EKS, and EC2 metadata. To select a named profile: - -```py -client = OpenAI( - provider=bedrock( - profile="my-profile", - ) -) -``` - -You can also pass `access_key_id` and `secret_access_key`, with an optional `session_token`, or a refreshable `credential_provider` that returns botocore-compatible credentials. Explicit bearer and AWS credential options are mutually exclusive. - -Pass `base_url` to `bedrock(...)` or set `AWS_BEDROCK_BASE_URL` to override the derived `https://bedrock-mantle..api.aws/openai/v1` endpoint. - -SigV4 requests require replayable, fully serialized request bodies. Standard JSON requests already meet this requirement, and response streaming is unaffected. Low-level one-shot request streams must be buffered before sending, or sent with bearer authentication and retries disabled. - -Bearer tokens remain available as a compatibility or manual authentication mode. Set `AWS_BEARER_TOKEN_BEDROCK` to an [Amazon Bedrock API key](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html), pass `api_key`, or provide a refresh callback: - -```py -client = OpenAI( - provider=bedrock( - region="us-west-2", - token_provider=lambda: refresh_bedrock_token(), - ) -) -``` - -Without explicit authentication, `AWS_BEARER_TOKEN_BEDROCK` takes precedence over the default AWS credential chain for backwards compatibility. - -### Legacy `BedrockOpenAI` client - -`BedrockOpenAI` and `AsyncBedrockOpenAI` remain available for existing applications and delegate to the same provider implementation. New applications should prefer `OpenAI(provider=bedrock(...))`. - -```py -from openai import BedrockOpenAI - -client = BedrockOpenAI( - aws_region="us-west-2", - aws_profile="my-profile", -) -``` - -The legacy module client also continues to support `openai.api_type = "amazon-bedrock"` or `OPENAI_API_TYPE=amazon-bedrock`. - -## Versioning - -This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: - -1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ -3. Changes that we do not expect to impact the vast majority of users in practice. - -We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. - -We are keen for your feedback; please open an [issue](https://www.github.com/openai/openai-python/issues) with questions, bugs, or suggestions. - -### Determining the installed version - -If you've upgraded to the latest version but aren't seeing any new features you were expecting then your python environment is likely still using an older version. - -You can determine the version that is being used at runtime with: - -```py -import openai -print(openai.__version__) -``` +This package follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions. -## Requirements +- **Requirements:** Python 3.9 or higher. +- **Installed Version:** + ```python + import openai + print(openai.__version__) + ``` -Python 3.9 or higher. +--- ## Contributing -See [the contributing documentation](./CONTRIBUTING.md). +See the [contributing documentation](./CONTRIBUTING.md).