diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e50bfbdd7e..ec10edfe36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: if: github.repository == 'openai/openai-python' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rye run: | diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 690002df88..c4bf1b6c04 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.13.2" + ".": "1.13.3" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f640d2614..201757c90d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 1.13.3 (2024-02-28) + +Full Changelog: [v1.13.2...v1.13.3](https://github.com/openai/openai-python/compare/v1.13.2...v1.13.3) + +### Features + +* **api:** add wav and pcm to response_format ([#1189](https://github.com/openai/openai-python/issues/1189)) ([dbd20fc](https://github.com/openai/openai-python/commit/dbd20fc42e93358261f71b9aa0e5f955053c3825)) + + +### Chores + +* **client:** use anyio.sleep instead of asyncio.sleep ([#1198](https://github.com/openai/openai-python/issues/1198)) ([b6d025b](https://github.com/openai/openai-python/commit/b6d025b54f091e79f5d4a0a8923f29574fd66027)) +* **internal:** bump pyright ([#1193](https://github.com/openai/openai-python/issues/1193)) ([9202e04](https://github.com/openai/openai-python/commit/9202e04d07a7c47232f39196346c734869b8f55a)) +* **types:** extract run status to a named type ([#1178](https://github.com/openai/openai-python/issues/1178)) ([249ecbd](https://github.com/openai/openai-python/commit/249ecbdeb6566a385ec46dfd5000b4eaa03965f0)) + + +### Documentation + +* add note in azure_deployment docstring ([#1188](https://github.com/openai/openai-python/issues/1188)) ([96fa995](https://github.com/openai/openai-python/commit/96fa99572dd76ee708f2bae04d11b659cdd698b2)) +* **examples:** add pyaudio streaming example ([#1194](https://github.com/openai/openai-python/issues/1194)) ([3683c5e](https://github.com/openai/openai-python/commit/3683c5e3c7f07e4b789a0c4cc417b2c59539cae2)) + ## 1.13.2 (2024-02-20) Full Changelog: [v1.13.1...v1.13.2](https://github.com/openai/openai-python/compare/v1.13.1...v1.13.2) diff --git a/api.md b/api.md index 86b972d14e..34352e6e72 100644 --- a/api.md +++ b/api.md @@ -224,7 +224,7 @@ Methods: Types: ```python -from openai.types.beta.threads import RequiredActionFunctionToolCall, Run +from openai.types.beta.threads import RequiredActionFunctionToolCall, Run, RunStatus ``` Methods: diff --git a/examples/audio.py b/examples/audio.py index 73491090f5..85f47bfb06 100755 --- a/examples/audio.py +++ b/examples/audio.py @@ -1,5 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env rye run python +import time from pathlib import Path from openai import OpenAI @@ -11,6 +12,8 @@ def main() -> None: + stream_to_speakers() + # Create text-to-speech audio file with openai.audio.speech.with_streaming_response.create( model="tts-1", @@ -34,5 +37,28 @@ def main() -> None: print(translation.text) +def stream_to_speakers() -> None: + import pyaudio + + player_stream = pyaudio.PyAudio().open(format=pyaudio.paInt16, channels=1, rate=24000, output=True) + + start_time = time.time() + + with openai.audio.speech.with_streaming_response.create( + model="tts-1", + voice="alloy", + response_format="pcm", # similar to WAV, but without a header chunk at the start. + input="""I see skies of blue and clouds of white + The bright blessed days, the dark sacred nights + And I think to myself + What a wonderful world""", + ) as response: + print(f"Time to first byte: {int((time.time() - start_time) * 1000)}ms") + for chunk in response.iter_bytes(chunk_size=1024): + player_stream.write(chunk) + + print(f"Done in {int((time.time() - start_time) * 1000)}ms.") + + if __name__ == "__main__": main() diff --git a/pyproject.toml b/pyproject.toml index 50fac10e84..171ede0aa4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openai" -version = "1.13.2" +version = "1.13.3" description = "The official Python library for the openai API" readme = "README.md" license = "Apache-2.0" @@ -61,7 +61,8 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "azure-identity >=1.14.1", - "types-tqdm > 4" + "types-tqdm > 4", + "types-pyaudio > 0" ] [tool.rye.scripts] diff --git a/requirements-dev.lock b/requirements-dev.lock index a08b9b692c..fa95964d07 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -30,7 +30,7 @@ charset-normalizer==3.3.2 # via requests colorlog==6.7.0 # via nox -cryptography==42.0.3 +cryptography==42.0.5 # via azure-identity # via msal # via pyjwt @@ -57,7 +57,7 @@ idna==3.4 importlib-metadata==7.0.0 iniconfig==2.0.0 # via pytest -msal==1.26.0 +msal==1.27.0 # via azure-identity # via msal-extensions msal-extensions==1.1.0 @@ -96,7 +96,7 @@ pydantic-core==2.10.1 # via pydantic pyjwt==2.8.0 # via msal -pyright==1.1.332 +pyright==1.1.351 pytest==7.1.1 # via pytest-asyncio pytest-asyncio==0.21.1 @@ -126,6 +126,7 @@ tomli==2.0.1 # via pytest tqdm==4.66.1 # via openai +types-pyaudio==0.2.16.20240106 types-pytz==2024.1.0.20240203 # via pandas-stubs types-tqdm==4.66.0.2 diff --git a/src/openai/_models.py b/src/openai/_models.py index 48d5624f64..810891497a 100644 --- a/src/openai/_models.py +++ b/src/openai/_models.py @@ -283,7 +283,7 @@ def construct_type(*, value: object, type_: type) -> object: if is_union(origin): try: - return validate_type(type_=type_, value=value) + return validate_type(type_=cast("type[object]", type_), value=value) except Exception: pass diff --git a/src/openai/_resource.py b/src/openai/_resource.py index db1b0fa45a..0b0703bb72 100644 --- a/src/openai/_resource.py +++ b/src/openai/_resource.py @@ -3,9 +3,10 @@ from __future__ import annotations import time -import asyncio from typing import TYPE_CHECKING +import anyio + if TYPE_CHECKING: from ._client import OpenAI, AsyncOpenAI @@ -39,4 +40,4 @@ def __init__(self, client: AsyncOpenAI) -> None: self._get_api_list = client.get_api_list async def _sleep(self, seconds: float) -> None: - await asyncio.sleep(seconds) + await anyio.sleep(seconds) diff --git a/src/openai/_utils/_proxy.py b/src/openai/_utils/_proxy.py index 6f05efcd21..b9c12dc3f4 100644 --- a/src/openai/_utils/_proxy.py +++ b/src/openai/_utils/_proxy.py @@ -45,7 +45,7 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override - def __class__(self) -> type: + def __class__(self) -> type: # pyright: ignore proxied = self.__get_proxied__() if issubclass(type(proxied), LazyProxy): return type(proxied) diff --git a/src/openai/_version.py b/src/openai/_version.py index 7890e5b58c..503a06141f 100644 --- a/src/openai/_version.py +++ b/src/openai/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. __title__ = "openai" -__version__ = "1.13.2" # x-release-please-version +__version__ = "1.13.3" # x-release-please-version diff --git a/src/openai/cli/_tools/migrate.py b/src/openai/cli/_tools/migrate.py index 14773302e1..53073b866f 100644 --- a/src/openai/cli/_tools/migrate.py +++ b/src/openai/cli/_tools/migrate.py @@ -138,7 +138,7 @@ def install() -> Path: unpacked_dir.mkdir(parents=True, exist_ok=True) with tarfile.open(temp_file, "r:gz") as archive: - archive.extractall(unpacked_dir) + archive.extractall(unpacked_dir, filter="data") for item in unpacked_dir.iterdir(): item.rename(target_dir / item.name) diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index 2c8b4dcd88..b3b94de80e 100644 --- a/src/openai/lib/azure.py +++ b/src/openai/lib/azure.py @@ -155,7 +155,7 @@ def __init__( azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request. azure_deployment: A model deployment, if given sets the base client URL to include `/deployments/{azure_deployment}`. - Note: this means you won't be able to use non-deployment endpoints. + Note: this means you won't be able to use non-deployment endpoints. Not supported with Assistants APIs. """ if api_key is None: api_key = os.environ.get("AZURE_OPENAI_API_KEY") @@ -388,7 +388,7 @@ def __init__( azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request. azure_deployment: A model deployment, if given sets the base client URL to include `/deployments/{azure_deployment}`. - Note: this means you won't be able to use non-deployment endpoints. + Note: this means you won't be able to use non-deployment endpoints. Not supported with Assistants APIs. """ if api_key is None: api_key = os.environ.get("AZURE_OPENAI_API_KEY") diff --git a/src/openai/resources/audio/speech.py b/src/openai/resources/audio/speech.py index fbdc1ecff1..a569751ee5 100644 --- a/src/openai/resources/audio/speech.py +++ b/src/openai/resources/audio/speech.py @@ -41,7 +41,7 @@ def create( input: str, model: Union[str, Literal["tts-1", "tts-1-hd"]], voice: Literal["alloy", "echo", "fable", "onyx", "nova", "shimmer"], - response_format: Literal["mp3", "opus", "aac", "flac"] | NotGiven = NOT_GIVEN, + response_format: Literal["mp3", "opus", "aac", "flac", "pcm", "wav"] | NotGiven = NOT_GIVEN, speed: float | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -65,7 +65,11 @@ def create( available in the [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech/voice-options). - response_format: The format to audio in. Supported formats are `mp3`, `opus`, `aac`, and `flac`. + response_format: The format to return audio in. Supported formats are `mp3`, `opus`, `aac`, + `flac`, `pcm`, and `wav`. + + The `pcm` audio format, similar to `wav` but without a header, utilizes a 24kHz + sample rate, mono channel, and 16-bit depth in signed little-endian format. speed: The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. @@ -113,7 +117,7 @@ async def create( input: str, model: Union[str, Literal["tts-1", "tts-1-hd"]], voice: Literal["alloy", "echo", "fable", "onyx", "nova", "shimmer"], - response_format: Literal["mp3", "opus", "aac", "flac"] | NotGiven = NOT_GIVEN, + response_format: Literal["mp3", "opus", "aac", "flac", "pcm", "wav"] | NotGiven = NOT_GIVEN, speed: float | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -137,7 +141,11 @@ async def create( available in the [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech/voice-options). - response_format: The format to audio in. Supported formats are `mp3`, `opus`, `aac`, and `flac`. + response_format: The format to return audio in. Supported formats are `mp3`, `opus`, `aac`, + `flac`, `pcm`, and `wav`. + + The `pcm` audio format, similar to `wav` but without a header, utilizes a 24kHz + sample rate, mono channel, and 16-bit depth in signed little-endian format. speed: The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. diff --git a/src/openai/types/audio/speech_create_params.py b/src/openai/types/audio/speech_create_params.py index 6a302dd3c8..00f862272e 100644 --- a/src/openai/types/audio/speech_create_params.py +++ b/src/openai/types/audio/speech_create_params.py @@ -26,8 +26,14 @@ class SpeechCreateParams(TypedDict, total=False): [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech/voice-options). """ - response_format: Literal["mp3", "opus", "aac", "flac"] - """The format to audio in. Supported formats are `mp3`, `opus`, `aac`, and `flac`.""" + response_format: Literal["mp3", "opus", "aac", "flac", "pcm", "wav"] + """The format to return audio in. + + Supported formats are `mp3`, `opus`, `aac`, `flac`, `pcm`, and `wav`. + + The `pcm` audio format, similar to `wav` but without a header, utilizes a 24kHz + sample rate, mono channel, and 16-bit depth in signed little-endian format. + """ speed: float """The speed of the generated audio. diff --git a/src/openai/types/beta/threads/__init__.py b/src/openai/types/beta/threads/__init__.py index 8c77466dec..a71cbde3e3 100644 --- a/src/openai/types/beta/threads/__init__.py +++ b/src/openai/types/beta/threads/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from .run import Run as Run +from .run_status import RunStatus as RunStatus from .thread_message import ThreadMessage as ThreadMessage from .run_list_params import RunListParams as RunListParams from .run_create_params import RunCreateParams as RunCreateParams diff --git a/src/openai/types/beta/threads/run.py b/src/openai/types/beta/threads/run.py index 9c875a9242..79e4f6a444 100644 --- a/src/openai/types/beta/threads/run.py +++ b/src/openai/types/beta/threads/run.py @@ -5,6 +5,7 @@ from ...shared import FunctionDefinition from ...._models import BaseModel +from .run_status import RunStatus from .required_action_function_tool_call import RequiredActionFunctionToolCall __all__ = [ @@ -142,9 +143,7 @@ class Run(BaseModel): started_at: Optional[int] = None """The Unix timestamp (in seconds) for when the run was started.""" - status: Literal[ - "queued", "in_progress", "requires_action", "cancelling", "cancelled", "failed", "completed", "expired" - ] + status: RunStatus """ The status of the run, which can be either `queued`, `in_progress`, `requires_action`, `cancelling`, `cancelled`, `failed`, `completed`, or diff --git a/src/openai/types/beta/threads/run_status.py b/src/openai/types/beta/threads/run_status.py new file mode 100644 index 0000000000..587e3d7810 --- /dev/null +++ b/src/openai/types/beta/threads/run_status.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. + +from typing_extensions import Literal + +__all__ = ["RunStatus"] + +RunStatus = Literal[ + "queued", "in_progress", "requires_action", "cancelling", "cancelled", "failed", "completed", "expired" +]