openai/openai-python

Public

mirrored from https://github.com/openai/openai-pythonAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.93.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

README.md

859lines · modecode

1# OpenAI Python API library
2
3[![PyPI version](<https://img.shields.io/pypi/v/openai.svg?label=pypi%20(stable)>)](https://pypi.org/project/openai/)
4
5The OpenAI Python library provides convenient access to the OpenAI REST API from any Python 3.8+
6application. The library includes type definitions for all request params and response fields,
7and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
8
9It is generated from our [OpenAPI specification](https://github.com/openai/openai-openapi) with [Stainless](https://stainlessapi.com/).
10
11## Documentation
12
13The 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).
14
15## Installation
16
17```sh
18# install from PyPI
19pip install openai
20```
21
22## Usage
23
24The full API of this library can be found in [api.md](api.md).
25
26The 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.
27
28```python
29import os
30from openai import OpenAI
31
32client = OpenAI(
33 # This is the default and can be omitted
34 api_key=os.environ.get("OPENAI_API_KEY"),
35)
36
37response = client.responses.create(
38 model="gpt-4o",
39 instructions="You are a coding assistant that talks like a pirate.",
40 input="How do I check if a Python object is an instance of a class?",
41)
42
43print(response.output_text)
44```
45
46The 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.
47
48```python
49from openai import OpenAI
50
51client = OpenAI()
52
53completion = client.chat.completions.create(
54 model="gpt-4o",
55 messages=[
56 {"role": "developer", "content": "Talk like a pirate."},
57 {
58 "role": "user",
59 "content": "How do I check if a Python object is an instance of a class?",
60 },
61 ],
62)
63
64print(completion.choices[0].message.content)
65```
66
67While you can provide an `api_key` keyword argument,
68we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
69to add `OPENAI_API_KEY="My API Key"` to your `.env` file
70so that your API key is not stored in source control.
71[Get an API key here](https://platform.openai.com/settings/organization/api-keys).
72
73### Vision
74
75With an image URL:
76
77```python
78prompt = "What is in this image?"
79img_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/2023_06_08_Raccoon1.jpg/1599px-2023_06_08_Raccoon1.jpg"
80
81response = client.responses.create(
82 model="gpt-4o-mini",
83 input=[
84 {
85 "role": "user",
86 "content": [
87 {"type": "input_text", "text": prompt},
88 {"type": "input_image", "image_url": f"{img_url}"},
89 ],
90 }
91 ],
92)
93```
94
95With the image as a base64 encoded string:
96
97```python
98import base64
99from openai import OpenAI
100
101client = OpenAI()
102
103prompt = "What is in this image?"
104with open("path/to/image.png", "rb") as image_file:
105 b64_image = base64.b64encode(image_file.read()).decode("utf-8")
106
107response = client.responses.create(
108 model="gpt-4o-mini",
109 input=[
110 {
111 "role": "user",
112 "content": [
113 {"type": "input_text", "text": prompt},
114 {"type": "input_image", "image_url": f"data:image/png;base64,{b64_image}"},
115 ],
116 }
117 ],
118)
119```
120
121## Async usage
122
123Simply import `AsyncOpenAI` instead of `OpenAI` and use `await` with each API call:
124
125```python
126import os
127import asyncio
128from openai import AsyncOpenAI
129
130client = AsyncOpenAI(
131 # This is the default and can be omitted
132 api_key=os.environ.get("OPENAI_API_KEY"),
133)
134
135
136async def main() -> None:
137 response = await client.responses.create(
138 model="gpt-4o", input="Explain disestablishmentarianism to a smart five year old."
139 )
140 print(response.output_text)
141
142
143asyncio.run(main())
144```
145
146Functionality between the synchronous and asynchronous clients is otherwise identical.
147
148### With aiohttp
149
150By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
151
152You can enable this by installing `aiohttp`:
153
154```sh
155# install from PyPI
156pip install openai[aiohttp]
157```
158
159Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
160
161```python
162import os
163import asyncio
164from openai import DefaultAioHttpClient
165from openai import AsyncOpenAI
166
167
168async def main() -> None:
169 async with AsyncOpenAI(
170 api_key=os.environ.get("OPENAI_API_KEY"), # This is the default and can be omitted
171 http_client=DefaultAioHttpClient(),
172 ) as client:
173 chat_completion = await client.chat.completions.create(
174 messages=[
175 {
176 "role": "user",
177 "content": "Say this is a test",
178 }
179 ],
180 model="gpt-4o",
181 )
182
183
184asyncio.run(main())
185```
186
187## Streaming responses
188
189We provide support for streaming responses using Server Side Events (SSE).
190
191```python
192from openai import OpenAI
193
194client = OpenAI()
195
196stream = client.responses.create(
197 model="gpt-4o",
198 input="Write a one-sentence bedtime story about a unicorn.",
199 stream=True,
200)
201
202for event in stream:
203 print(event)
204```
205
206The async client uses the exact same interface.
207
208```python
209import asyncio
210from openai import AsyncOpenAI
211
212client = AsyncOpenAI()
213
214
215async def main():
216 stream = await client.responses.create(
217 model="gpt-4o",
218 input="Write a one-sentence bedtime story about a unicorn.",
219 stream=True,
220 )
221
222 async for event in stream:
223 print(event)
224
225
226asyncio.run(main())
227```
228
229## Realtime API beta
230
231The 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.
232
233Under the hood the SDK uses the [`websockets`](https://websockets.readthedocs.io/en/stable/) library to manage connections.
234
235The 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).
236
237Basic text based example:
238
239```py
240import asyncio
241from openai import AsyncOpenAI
242
243async def main():
244 client = AsyncOpenAI()
245
246 async with client.beta.realtime.connect(model="gpt-4o-realtime-preview") as connection:
247 await connection.session.update(session={'modalities': ['text']})
248
249 await connection.conversation.item.create(
250 item={
251 "type": "message",
252 "role": "user",
253 "content": [{"type": "input_text", "text": "Say hello!"}],
254 }
255 )
256 await connection.response.create()
257
258 async for event in connection:
259 if event.type == 'response.text.delta':
260 print(event.delta, flush=True, end="")
261
262 elif event.type == 'response.text.done':
263 print()
264
265 elif event.type == "response.done":
266 break
267
268asyncio.run(main())
269```
270
271However 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.
272
273### Realtime error handling
274
275Whenever 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.
276
277```py
278client = AsyncOpenAI()
279
280async with client.beta.realtime.connect(model="gpt-4o-realtime-preview") as connection:
281 ...
282 async for event in connection:
283 if event.type == 'error':
284 print(event.error.type)
285 print(event.error.code)
286 print(event.error.event_id)
287 print(event.error.message)
288```
289
290## Using types
291
292Nested 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:
293
294- Serializing back into JSON, `model.to_json()`
295- Converting to a dictionary, `model.to_dict()`
296
297Typed 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`.
298
299## Pagination
300
301List methods in the OpenAI API are paginated.
302
303This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
304
305```python
306from openai import OpenAI
307
308client = OpenAI()
309
310all_jobs = []
311# Automatically fetches more pages as needed.
312for job in client.fine_tuning.jobs.list(
313 limit=20,
314):
315 # Do something with job here
316 all_jobs.append(job)
317print(all_jobs)
318```
319
320Or, asynchronously:
321
322```python
323import asyncio
324from openai import AsyncOpenAI
325
326client = AsyncOpenAI()
327
328
329async def main() -> None:
330 all_jobs = []
331 # Iterate through items across all pages, issuing requests as needed.
332 async for job in client.fine_tuning.jobs.list(
333 limit=20,
334 ):
335 all_jobs.append(job)
336 print(all_jobs)
337
338
339asyncio.run(main())
340```
341
342Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
343
344```python
345first_page = await client.fine_tuning.jobs.list(
346 limit=20,
347)
348if first_page.has_next_page():
349 print(f"will fetch next page using these details: {first_page.next_page_info()}")
350 next_page = await first_page.get_next_page()
351 print(f"number of items we just fetched: {len(next_page.data)}")
352
353# Remove `await` for non-async usage.
354```
355
356Or just work directly with the returned data:
357
358```python
359first_page = await client.fine_tuning.jobs.list(
360 limit=20,
361)
362
363print(f"next page cursor: {first_page.after}") # => "next page cursor: ..."
364for job in first_page.data:
365 print(job.id)
366
367# Remove `await` for non-async usage.
368```
369
370## Nested params
371
372Nested parameters are dictionaries, typed using `TypedDict`, for example:
373
374```python
375from openai import OpenAI
376
377client = OpenAI()
378
379response = client.chat.responses.create(
380 input=[
381 {
382 "role": "user",
383 "content": "How much ?",
384 }
385 ],
386 model="gpt-4o",
387 response_format={"type": "json_object"},
388)
389```
390
391## File uploads
392
393Request 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)`.
394
395```python
396from pathlib import Path
397from openai import OpenAI
398
399client = OpenAI()
400
401client.files.create(
402 file=Path("input.jsonl"),
403 purpose="fine-tune",
404)
405```
406
407The 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.
408
409## Webhook Verification
410
411Verifying webhook signatures is _optional but encouraged_.
412
413For more information about webhooks, see [the API docs](https://platform.openai.com/docs/guides/webhooks).
414
415### Parsing webhook payloads
416
417For 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.
418
419Note 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.
420
421```python
422from openai import OpenAI
423from flask import Flask, request
424
425app = Flask(__name__)
426client = OpenAI() # OPENAI_WEBHOOK_SECRET environment variable is used by default
427
428
429@app.route("/webhook", methods=["POST"])
430def webhook():
431 request_body = request.get_data(as_text=True)
432
433 try:
434 event = client.webhooks.unwrap(request_body, request.headers)
435
436 if event.type == "response.completed":
437 print("Response completed:", event.data)
438 elif event.type == "response.failed":
439 print("Response failed:", event.data)
440 else:
441 print("Unhandled event type:", event.type)
442
443 return "ok"
444 except Exception as e:
445 print("Invalid signature:", e)
446 return "Invalid signature", 400
447
448
449if __name__ == "__main__":
450 app.run(port=8000)
451```
452
453### Verifying webhook payloads directly
454
455In 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.
456
457Note 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.
458
459```python
460import json
461from openai import OpenAI
462from flask import Flask, request
463
464app = Flask(__name__)
465client = OpenAI() # OPENAI_WEBHOOK_SECRET environment variable is used by default
466
467
468@app.route("/webhook", methods=["POST"])
469def webhook():
470 request_body = request.get_data(as_text=True)
471
472 try:
473 client.webhooks.verify_signature(request_body, request.headers)
474
475 # Parse the body after verification
476 event = json.loads(request_body)
477 print("Verified event:", event)
478
479 return "ok"
480 except Exception as e:
481 print("Invalid signature:", e)
482 return "Invalid signature", 400
483
484
485if __name__ == "__main__":
486 app.run(port=8000)
487```
488
489## Handling errors
490
491When 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.
492
493When the API returns a non-success status code (that is, 4xx or 5xx
494response), a subclass of `openai.APIStatusError` is raised, containing `status_code` and `response` properties.
495
496All errors inherit from `openai.APIError`.
497
498```python
499import openai
500from openai import OpenAI
501
502client = OpenAI()
503
504try:
505 client.fine_tuning.jobs.create(
506 model="gpt-4o",
507 training_file="file-abc123",
508 )
509except openai.APIConnectionError as e:
510 print("The server could not be reached")
511 print(e.__cause__) # an underlying Exception, likely raised within httpx.
512except openai.RateLimitError as e:
513 print("A 429 status code was received; we should back off a bit.")
514except openai.APIStatusError as e:
515 print("Another non-200-range status code was received")
516 print(e.status_code)
517 print(e.response)
518```
519
520Error codes are as follows:
521
522| Status Code | Error Type |
523| ----------- | -------------------------- |
524| 400 | `BadRequestError` |
525| 401 | `AuthenticationError` |
526| 403 | `PermissionDeniedError` |
527| 404 | `NotFoundError` |
528| 422 | `UnprocessableEntityError` |
529| 429 | `RateLimitError` |
530| >=500 | `InternalServerError` |
531| N/A | `APIConnectionError` |
532
533## Request IDs
534
535> For more information on debugging requests, see [these docs](https://platform.openai.com/docs/api-reference/debugging-requests)
536
537All 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.
538
539```python
540response = await client.responses.create(
541 model="gpt-4o-mini",
542 input="Say 'this is a test'.",
543)
544print(response._request_id) # req_123
545```
546
547Note that unlike other properties that use an `_` prefix, the `_request_id` property
548_is_ public. Unless documented otherwise, _all_ other `_` prefix properties,
549methods and modules are _private_.
550
551> [!IMPORTANT]
552> If you need to access request IDs for failed requests you must catch the `APIStatusError` exception
553
554```python
555import openai
556
557try:
558 completion = await client.chat.completions.create(
559 messages=[{"role": "user", "content": "Say this is a test"}], model="gpt-4"
560 )
561except openai.APIStatusError as exc:
562 print(exc.request_id) # req_123
563 raise exc
564```
565
566## Retries
567
568Certain errors are automatically retried 2 times by default, with a short exponential backoff.
569Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
570429 Rate Limit, and >=500 Internal errors are all retried by default.
571
572You can use the `max_retries` option to configure or disable retry settings:
573
574```python
575from openai import OpenAI
576
577# Configure the default for all requests:
578client = OpenAI(
579 # default is 2
580 max_retries=0,
581)
582
583# Or, configure per-request:
584client.with_options(max_retries=5).chat.completions.create(
585 messages=[
586 {
587 "role": "user",
588 "content": "How can I get the name of the current day in JavaScript?",
589 }
590 ],
591 model="gpt-4o",
592)
593```
594
595## Timeouts
596
597By default requests time out after 10 minutes. You can configure this with a `timeout` option,
598which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
599
600```python
601from openai import OpenAI
602
603# Configure the default for all requests:
604client = OpenAI(
605 # 20 seconds (default is 10 minutes)
606 timeout=20.0,
607)
608
609# More granular control:
610client = OpenAI(
611 timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
612)
613
614# Override per-request:
615client.with_options(timeout=5.0).chat.completions.create(
616 messages=[
617 {
618 "role": "user",
619 "content": "How can I list all files in a directory using Python?",
620 }
621 ],
622 model="gpt-4o",
623)
624```
625
626On timeout, an `APITimeoutError` is thrown.
627
628Note that requests that time out are [retried twice by default](#retries).
629
630## Advanced
631
632### Logging
633
634We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.
635
636You can enable logging by setting the environment variable `OPENAI_LOG` to `info`.
637
638```shell
639$ export OPENAI_LOG=info
640```
641
642Or to `debug` for more verbose logging.
643
644### How to tell whether `None` means `null` or missing
645
646In 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`:
647
648```py
649if response.my_field is None:
650 if 'my_field' not in response.model_fields_set:
651 print('Got json like {}, without a "my_field" key present at all.')
652 else:
653 print('Got json like {"my_field": null}.')
654```
655
656### Accessing raw response data (e.g. headers)
657
658The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,
659
660```py
661from openai import OpenAI
662
663client = OpenAI()
664response = client.chat.completions.with_raw_response.create(
665 messages=[{
666 "role": "user",
667 "content": "Say this is a test",
668 }],
669 model="gpt-4o",
670)
671print(response.headers.get('X-My-Header'))
672
673completion = response.parse() # get the object that `chat.completions.create()` would have returned
674print(completion)
675```
676
677These 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.
678
679For the sync client this will mostly be the same with the exception
680of `content` & `text` will be methods instead of properties. In the
681async client, all methods will be async.
682
683A migration script will be provided & the migration in general should
684be smooth.
685
686#### `.with_streaming_response`
687
688The above interface eagerly reads the full response body when you make the request, which may not always be what you want.
689
690To 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.
691
692As 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.
693
694```python
695with client.chat.completions.with_streaming_response.create(
696 messages=[
697 {
698 "role": "user",
699 "content": "Say this is a test",
700 }
701 ],
702 model="gpt-4o",
703) as response:
704 print(response.headers.get("X-My-Header"))
705
706 for line in response.iter_lines():
707 print(line)
708```
709
710The context manager is required so that the response will reliably be closed.
711
712### Making custom/undocumented requests
713
714This library is typed for convenient access to the documented API.
715
716If you need to access undocumented endpoints, params, or response properties, the library can still be used.
717
718#### Undocumented endpoints
719
720To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other
721http verbs. Options on the client will be respected (such as retries) when making this request.
722
723```py
724import httpx
725
726response = client.post(
727 "/foo",
728 cast_to=httpx.Response,
729 body={"my_param": True},
730)
731
732print(response.headers.get("x-foo"))
733```
734
735#### Undocumented request params
736
737If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request
738options.
739
740#### Undocumented response properties
741
742To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You
743can also get all the extra fields on the Pydantic model as a dict with
744[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra).
745
746### Configuring the HTTP client
747
748You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including:
749
750- Support for [proxies](https://www.python-httpx.org/advanced/proxies/)
751- Custom [transports](https://www.python-httpx.org/advanced/transports/)
752- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality
753
754```python
755import httpx
756from openai import OpenAI, DefaultHttpxClient
757
758client = OpenAI(
759 # Or use the `OPENAI_BASE_URL` env var
760 base_url="http://my.test.server.example.com:8083/v1",
761 http_client=DefaultHttpxClient(
762 proxy="http://my.test.proxy.example.com",
763 transport=httpx.HTTPTransport(local_address="0.0.0.0"),
764 ),
765)
766```
767
768You can also customize the client on a per-request basis by using `with_options()`:
769
770```python
771client.with_options(http_client=DefaultHttpxClient(...))
772```
773
774### Managing HTTP resources
775
776By 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.
777
778```py
779from openai import OpenAI
780
781with OpenAI() as client:
782 # make requests here
783 ...
784
785# HTTP client is now closed
786```
787
788## Microsoft Azure OpenAI
789
790To use this library with [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview), use the `AzureOpenAI`
791class instead of the `OpenAI` class.
792
793> [!IMPORTANT]
794> The Azure API shape differs from the core API shape which means that the static types for responses / params
795> won't always be correct.
796
797```py
798from openai import AzureOpenAI
799
800# gets the API Key from environment variable AZURE_OPENAI_API_KEY
801client = AzureOpenAI(
802 # https://learn.microsoft.com/azure/ai-services/openai/reference#rest-api-versioning
803 api_version="2023-07-01-preview",
804 # https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource
805 azure_endpoint="https://example-endpoint.openai.azure.com",
806)
807
808completion = client.chat.completions.create(
809 model="deployment-name", # e.g. gpt-35-instant
810 messages=[
811 {
812 "role": "user",
813 "content": "How do I output all files in a directory using Python?",
814 },
815 ],
816)
817print(completion.to_json())
818```
819
820In addition to the options provided in the base `OpenAI` client, the following options are provided:
821
822- `azure_endpoint` (or the `AZURE_OPENAI_ENDPOINT` environment variable)
823- `azure_deployment`
824- `api_version` (or the `OPENAI_API_VERSION` environment variable)
825- `azure_ad_token` (or the `AZURE_OPENAI_AD_TOKEN` environment variable)
826- `azure_ad_token_provider`
827
828An 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).
829
830## Versioning
831
832This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
833
8341. Changes that only affect static types, without breaking runtime behavior.
8352. 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.)_
8363. Changes that we do not expect to impact the vast majority of users in practice.
837
838We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
839
840We are keen for your feedback; please open an [issue](https://www.github.com/openai/openai-python/issues) with questions, bugs, or suggestions.
841
842### Determining the installed version
843
844If 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.
845
846You can determine the version that is being used at runtime with:
847
848```py
849import openai
850print(openai.__version__)
851```
852
853## Requirements
854
855Python 3.8 or higher.
856
857## Contributing
858
859See [the contributing documentation](./CONTRIBUTING.md).
860