openai/openai-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.65.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

README.md

811lines · modecode

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