From cfb7e308393f2e912e959dd10d68096dd5b3ab9c Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:06:05 -0500 Subject: [PATCH 01/10] chore(internal): fix binary response tests (#983) --- tests/api_resources/audio/test_speech.py | 6 ++---- tests/api_resources/test_files.py | 12 ++++-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/api_resources/audio/test_speech.py b/tests/api_resources/audio/test_speech.py index 50b00b73b4..23f5303153 100644 --- a/tests/api_resources/audio/test_speech.py +++ b/tests/api_resources/audio/test_speech.py @@ -39,8 +39,7 @@ def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: @pytest.mark.respx(base_url=base_url) def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRouter) -> None: respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - speech = respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - client.audio.speech.create( + speech = client.audio.speech.create( input="string", model="string", voice="alloy", @@ -89,8 +88,7 @@ async def test_method_create(self, client: AsyncOpenAI, respx_mock: MockRouter) @pytest.mark.respx(base_url=base_url) async def test_method_create_with_all_params(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - speech = respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - await client.audio.speech.create( + speech = await client.audio.speech.create( input="string", model="string", voice="alloy", diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py index e4cf493319..13ffca9773 100644 --- a/tests/api_resources/test_files.py +++ b/tests/api_resources/test_files.py @@ -95,22 +95,20 @@ def test_raw_response_delete(self, client: OpenAI) -> None: file = response.parse() assert_matches_type(FileDeleted, file, path=["response"]) - @pytest.mark.skip(reason="mocked response isn't working yet") @parametrize @pytest.mark.respx(base_url=base_url) def test_method_content(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/{file_id}/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) file = client.files.content( "string", ) assert isinstance(file, BinaryResponseContent) assert file.json() == {"foo": "bar"} - @pytest.mark.skip(reason="mocked response isn't working yet") @parametrize @pytest.mark.respx(base_url=base_url) def test_raw_response_content(self, client: OpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/{file_id}/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) response = client.files.with_raw_response.content( "string", ) @@ -212,22 +210,20 @@ async def test_raw_response_delete(self, client: AsyncOpenAI) -> None: file = response.parse() assert_matches_type(FileDeleted, file, path=["response"]) - @pytest.mark.skip(reason="mocked response isn't working yet") @parametrize @pytest.mark.respx(base_url=base_url) async def test_method_content(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/{file_id}/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) file = await client.files.content( "string", ) assert isinstance(file, BinaryResponseContent) assert file.json() == {"foo": "bar"} - @pytest.mark.skip(reason="mocked response isn't working yet") @parametrize @pytest.mark.respx(base_url=base_url) async def test_raw_response_content(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: - respx_mock.get("/files/{file_id}/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) response = await client.files.with_raw_response.content( "string", ) From d1e9e8f24df366bb7b796c55a98247c025d229f5 Mon Sep 17 00:00:00 2001 From: Logan Kilpatrick Date: Tue, 19 Dec 2023 00:53:24 +0900 Subject: [PATCH 02/10] chore(cli): fix typo in completions (#985) --- src/openai/cli/_api/completions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openai/cli/_api/completions.py b/src/openai/cli/_api/completions.py index ce1036b224..cbdb35bf3a 100644 --- a/src/openai/cli/_api/completions.py +++ b/src/openai/cli/_api/completions.py @@ -57,7 +57,7 @@ def register(subparser: _SubParsersAction[ArgumentParser]) -> None: ) sub.add_argument( "--logprobs", - help="Include the log probabilites on the `logprobs` most likely tokens, as well the chosen tokens. So for example, if `logprobs` is 10, the API will return a list of the 10 most likely tokens. If `logprobs` is 0, only the chosen tokens will have logprobs returned.", + help="Include the log probabilities on the `logprobs` most likely tokens, as well the chosen tokens. So for example, if `logprobs` is 10, the API will return a list of the 10 most likely tokens. If `logprobs` is 0, only the chosen tokens will have logprobs returned.", type=int, ) sub.add_argument( From 626bc34d82a7057bac99f8b556f9e5f60c261ee7 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 18 Dec 2023 11:31:14 -0500 Subject: [PATCH 03/10] chore(cli): fix typo in completions (#986) --- examples/async_demo.py | 2 +- examples/streaming.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/async_demo.py b/examples/async_demo.py index 793b4e43fb..92c267c38f 100755 --- a/examples/async_demo.py +++ b/examples/async_demo.py @@ -10,7 +10,7 @@ async def main() -> None: stream = await client.completions.create( - model="gpt-3.5-turbo-instruct", + model="text-davinci-003", prompt="Say this is a test", stream=True, ) diff --git a/examples/streaming.py b/examples/streaming.py index 368fa5f911..168877dfc5 100755 --- a/examples/streaming.py +++ b/examples/streaming.py @@ -13,7 +13,7 @@ def sync_main() -> None: client = OpenAI() response = client.completions.create( - model="gpt-3.5-turbo-instruct", + model="text-davinci-002", prompt="1,2,3,", max_tokens=5, temperature=0, @@ -33,7 +33,7 @@ def sync_main() -> None: async def async_main() -> None: client = AsyncOpenAI() response = await client.completions.create( - model="gpt-3.5-turbo-instruct", + model="text-davinci-002", prompt="1,2,3,", max_tokens=5, temperature=0, From cedd574e5611f3e71e92b523a72ba87bcfe546f1 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 18 Dec 2023 22:33:10 -0500 Subject: [PATCH 04/10] docs: upgrade models in examples to latest version (#989) --- examples/async_demo.py | 2 +- examples/streaming.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/async_demo.py b/examples/async_demo.py index 92c267c38f..793b4e43fb 100755 --- a/examples/async_demo.py +++ b/examples/async_demo.py @@ -10,7 +10,7 @@ async def main() -> None: stream = await client.completions.create( - model="text-davinci-003", + model="gpt-3.5-turbo-instruct", prompt="Say this is a test", stream=True, ) diff --git a/examples/streaming.py b/examples/streaming.py index 168877dfc5..368fa5f911 100755 --- a/examples/streaming.py +++ b/examples/streaming.py @@ -13,7 +13,7 @@ def sync_main() -> None: client = OpenAI() response = client.completions.create( - model="text-davinci-002", + model="gpt-3.5-turbo-instruct", prompt="1,2,3,", max_tokens=5, temperature=0, @@ -33,7 +33,7 @@ def sync_main() -> None: async def async_main() -> None: client = AsyncOpenAI() response = await client.completions.create( - model="text-davinci-002", + model="gpt-3.5-turbo-instruct", prompt="1,2,3,", max_tokens=5, temperature=0, From 6c3427dac8c414658516aeb4caf5d5fd8b11097b Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Tue, 19 Dec 2023 07:30:56 -0500 Subject: [PATCH 05/10] chore(streaming): update constructor to use direct client names (#991) --- src/openai/_streaming.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openai/_streaming.py b/src/openai/_streaming.py index e48324fc78..e323c59ac0 100644 --- a/src/openai/_streaming.py +++ b/src/openai/_streaming.py @@ -12,7 +12,7 @@ from ._exceptions import APIError if TYPE_CHECKING: - from ._base_client import SyncAPIClient, AsyncAPIClient + from ._client import OpenAI, AsyncOpenAI class Stream(Generic[ResponseT]): @@ -25,7 +25,7 @@ def __init__( *, cast_to: type[ResponseT], response: httpx.Response, - client: SyncAPIClient, + client: OpenAI, ) -> None: self.response = response self._cast_to = cast_to @@ -79,7 +79,7 @@ def __init__( *, cast_to: type[ResponseT], response: httpx.Response, - client: AsyncAPIClient, + client: AsyncOpenAI, ) -> None: self.response = response self._cast_to = cast_to From 5ba576ae38d2c4c4d32a21933e0d68e0bc2f0d49 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Tue, 19 Dec 2023 11:23:44 -0500 Subject: [PATCH 06/10] chore(internal): minor utils restructuring (#992) --- src/openai/_response.py | 17 +++---- src/openai/_streaming.py | 71 ++++++++++++++++++++++------- src/openai/_types.py | 14 ++++++ src/openai/_utils/__init__.py | 15 ++++--- src/openai/_utils/_streams.py | 12 +++++ src/openai/_utils/_transform.py | 5 +-- src/openai/_utils/_typing.py | 80 +++++++++++++++++++++++++++++++++ src/openai/_utils/_utils.py | 35 +-------------- 8 files changed, 183 insertions(+), 66 deletions(-) create mode 100644 src/openai/_utils/_streams.py create mode 100644 src/openai/_utils/_typing.py diff --git a/src/openai/_response.py b/src/openai/_response.py index 933c37525e..6b7c86e544 100644 --- a/src/openai/_response.py +++ b/src/openai/_response.py @@ -5,12 +5,12 @@ import datetime import functools from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast -from typing_extensions import Awaitable, ParamSpec, get_args, override, get_origin +from typing_extensions import Awaitable, ParamSpec, override, get_origin import httpx from ._types import NoneType, UnknownResponse, BinaryResponseContent -from ._utils import is_given +from ._utils import is_given, extract_type_var_from_base from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER from ._exceptions import APIResponseValidationError @@ -221,12 +221,13 @@ def __init__(self) -> None: def _extract_stream_chunk_type(stream_cls: type) -> type: - args = get_args(stream_cls) - if not args: - raise TypeError( - f"Expected stream_cls to have been given a generic type argument, e.g. Stream[Foo] but received {stream_cls}", - ) - return cast(type, args[0]) + from ._base_client import Stream, AsyncStream + + return extract_type_var_from_base( + stream_cls, + index=0, + generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), + ) def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]: diff --git a/src/openai/_streaming.py b/src/openai/_streaming.py index e323c59ac0..f1896a242a 100644 --- a/src/openai/_streaming.py +++ b/src/openai/_streaming.py @@ -2,12 +2,12 @@ from __future__ import annotations import json -from typing import TYPE_CHECKING, Any, Generic, Iterator, AsyncIterator -from typing_extensions import override +from types import TracebackType +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing_extensions import Self, override import httpx -from ._types import ResponseT from ._utils import is_mapping from ._exceptions import APIError @@ -15,7 +15,10 @@ from ._client import OpenAI, AsyncOpenAI -class Stream(Generic[ResponseT]): +_T = TypeVar("_T") + + +class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response @@ -23,7 +26,7 @@ class Stream(Generic[ResponseT]): def __init__( self, *, - cast_to: type[ResponseT], + cast_to: type[_T], response: httpx.Response, client: OpenAI, ) -> None: @@ -33,18 +36,18 @@ def __init__( self._decoder = SSEDecoder() self._iterator = self.__stream__() - def __next__(self) -> ResponseT: + def __next__(self) -> _T: return self._iterator.__next__() - def __iter__(self) -> Iterator[ResponseT]: + def __iter__(self) -> Iterator[_T]: for item in self._iterator: yield item def _iter_events(self) -> Iterator[ServerSentEvent]: yield from self._decoder.iter(self.response.iter_lines()) - def __stream__(self) -> Iterator[ResponseT]: - cast_to = self._cast_to + def __stream__(self) -> Iterator[_T]: + cast_to = cast(Any, self._cast_to) response = self.response process_data = self._client._process_response_data iterator = self._iter_events() @@ -68,8 +71,27 @@ def __stream__(self) -> Iterator[ResponseT]: for _sse in iterator: ... + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.response.close() -class AsyncStream(Generic[ResponseT]): + +class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response @@ -77,7 +99,7 @@ class AsyncStream(Generic[ResponseT]): def __init__( self, *, - cast_to: type[ResponseT], + cast_to: type[_T], response: httpx.Response, client: AsyncOpenAI, ) -> None: @@ -87,10 +109,10 @@ def __init__( self._decoder = SSEDecoder() self._iterator = self.__stream__() - async def __anext__(self) -> ResponseT: + async def __anext__(self) -> _T: return await self._iterator.__anext__() - async def __aiter__(self) -> AsyncIterator[ResponseT]: + async def __aiter__(self) -> AsyncIterator[_T]: async for item in self._iterator: yield item @@ -98,8 +120,8 @@ async def _iter_events(self) -> AsyncIterator[ServerSentEvent]: async for sse in self._decoder.aiter(self.response.aiter_lines()): yield sse - async def __stream__(self) -> AsyncIterator[ResponseT]: - cast_to = self._cast_to + async def __stream__(self) -> AsyncIterator[_T]: + cast_to = cast(Any, self._cast_to) response = self.response process_data = self._client._process_response_data iterator = self._iter_events() @@ -123,6 +145,25 @@ async def __stream__(self) -> AsyncIterator[ResponseT]: async for _sse in iterator: ... + async def __aenter__(self) -> Self: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + + async def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.response.aclose() + class ServerSentEvent: def __init__( diff --git a/src/openai/_types.py b/src/openai/_types.py index 8d543171eb..a20a4b4c1b 100644 --- a/src/openai/_types.py +++ b/src/openai/_types.py @@ -353,3 +353,17 @@ def get(self, __key: str) -> str | None: IncEx: TypeAlias = "set[int] | set[str] | dict[int, Any] | dict[str, Any] | None" PostParser = Callable[[Any], Any] + + +@runtime_checkable +class InheritsGeneric(Protocol): + """Represents a type that has inherited from `Generic` + The `__orig_bases__` property can be used to determine the resolved + type variable for a given base class. + """ + + __orig_bases__: tuple[_GenericAlias] + + +class _GenericAlias(Protocol): + __origin__: type[object] diff --git a/src/openai/_utils/__init__.py b/src/openai/_utils/__init__.py index 400ca9b828..a43201d3c7 100644 --- a/src/openai/_utils/__init__.py +++ b/src/openai/_utils/__init__.py @@ -9,13 +9,11 @@ from ._utils import parse_date as parse_date from ._utils import is_sequence as is_sequence from ._utils import coerce_float as coerce_float -from ._utils import is_list_type as is_list_type from ._utils import is_mapping_t as is_mapping_t from ._utils import removeprefix as removeprefix from ._utils import removesuffix as removesuffix from ._utils import extract_files as extract_files from ._utils import is_sequence_t as is_sequence_t -from ._utils import is_union_type as is_union_type from ._utils import required_args as required_args from ._utils import coerce_boolean as coerce_boolean from ._utils import coerce_integer as coerce_integer @@ -23,15 +21,20 @@ from ._utils import parse_datetime as parse_datetime from ._utils import strip_not_given as strip_not_given from ._utils import deepcopy_minimal as deepcopy_minimal -from ._utils import extract_type_arg as extract_type_arg -from ._utils import is_required_type as is_required_type from ._utils import get_async_library as get_async_library -from ._utils import is_annotated_type as is_annotated_type from ._utils import maybe_coerce_float as maybe_coerce_float from ._utils import get_required_header as get_required_header from ._utils import maybe_coerce_boolean as maybe_coerce_boolean from ._utils import maybe_coerce_integer as maybe_coerce_integer -from ._utils import strip_annotated_type as strip_annotated_type +from ._typing import is_list_type as is_list_type +from ._typing import is_union_type as is_union_type +from ._typing import extract_type_arg as extract_type_arg +from ._typing import is_required_type as is_required_type +from ._typing import is_annotated_type as is_annotated_type +from ._typing import strip_annotated_type as strip_annotated_type +from ._typing import extract_type_var_from_base as extract_type_var_from_base +from ._streams import consume_sync_iterator as consume_sync_iterator +from ._streams import consume_async_iterator as consume_async_iterator from ._transform import PropertyInfo as PropertyInfo from ._transform import transform as transform from ._transform import maybe_transform as maybe_transform diff --git a/src/openai/_utils/_streams.py b/src/openai/_utils/_streams.py new file mode 100644 index 0000000000..f4a0208f01 --- /dev/null +++ b/src/openai/_utils/_streams.py @@ -0,0 +1,12 @@ +from typing import Any +from typing_extensions import Iterator, AsyncIterator + + +def consume_sync_iterator(iterator: Iterator[Any]) -> None: + for _ in iterator: + ... + + +async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None: + async for _ in iterator: + ... diff --git a/src/openai/_utils/_transform.py b/src/openai/_utils/_transform.py index 769f7362b9..9117559064 100644 --- a/src/openai/_utils/_transform.py +++ b/src/openai/_utils/_transform.py @@ -6,9 +6,8 @@ import pydantic -from ._utils import ( - is_list, - is_mapping, +from ._utils import is_list, is_mapping +from ._typing import ( is_list_type, is_union_type, extract_type_arg, diff --git a/src/openai/_utils/_typing.py b/src/openai/_utils/_typing.py new file mode 100644 index 0000000000..b5e2c2e397 --- /dev/null +++ b/src/openai/_utils/_typing.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from typing import Any, cast +from typing_extensions import Required, Annotated, get_args, get_origin + +from .._types import InheritsGeneric +from .._compat import is_union as _is_union + + +def is_annotated_type(typ: type) -> bool: + return get_origin(typ) == Annotated + + +def is_list_type(typ: type) -> bool: + return (get_origin(typ) or typ) == list + + +def is_union_type(typ: type) -> bool: + return _is_union(get_origin(typ)) + + +def is_required_type(typ: type) -> bool: + return get_origin(typ) == Required + + +# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +def strip_annotated_type(typ: type) -> type: + if is_required_type(typ) or is_annotated_type(typ): + return strip_annotated_type(cast(type, get_args(typ)[0])) + + return typ + + +def extract_type_arg(typ: type, index: int) -> type: + args = get_args(typ) + try: + return cast(type, args[index]) + except IndexError as err: + raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err + + +def extract_type_var_from_base(typ: type, *, generic_bases: tuple[type, ...], index: int) -> type: + """Given a type like `Foo[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(Foo[bytes]): + ... + + extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes + ``` + """ + cls = cast(object, get_origin(typ) or typ) + if cls in generic_bases: + # we're given the class directly + return extract_type_arg(typ, index) + + # if a subclass is given + # --- + # this is needed as __orig_bases__ is not present in the typeshed stubs + # because it is intended to be for internal use only, however there does + # not seem to be a way to resolve generic TypeVars for inherited subclasses + # without using it. + if isinstance(cls, InheritsGeneric): + target_base_class: Any | None = None + for base in cls.__orig_bases__: + if base.__origin__ in generic_bases: + target_base_class = base + break + + if target_base_class is None: + raise RuntimeError( + "Could not find the generic base class;\n" + "This should never happen;\n" + f"Does {cls} inherit from one of {generic_bases} ?" + ) + + return extract_type_arg(target_base_class, index) + + raise RuntimeError(f"Could not resolve inner type variable at index {index} for {typ}") diff --git a/src/openai/_utils/_utils.py b/src/openai/_utils/_utils.py index c874d3682d..993462a66b 100644 --- a/src/openai/_utils/_utils.py +++ b/src/openai/_utils/_utils.py @@ -16,12 +16,11 @@ overload, ) from pathlib import Path -from typing_extensions import Required, Annotated, TypeGuard, get_args, get_origin +from typing_extensions import TypeGuard import sniffio from .._types import Headers, NotGiven, FileTypes, NotGivenOr, HeadersLike -from .._compat import is_union as _is_union from .._compat import parse_date as parse_date from .._compat import parse_datetime as parse_datetime @@ -166,38 +165,6 @@ def is_list(obj: object) -> TypeGuard[list[object]]: return isinstance(obj, list) -def is_annotated_type(typ: type) -> bool: - return get_origin(typ) == Annotated - - -def is_list_type(typ: type) -> bool: - return (get_origin(typ) or typ) == list - - -def is_union_type(typ: type) -> bool: - return _is_union(get_origin(typ)) - - -def is_required_type(typ: type) -> bool: - return get_origin(typ) == Required - - -# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] -def strip_annotated_type(typ: type) -> type: - if is_required_type(typ) or is_annotated_type(typ): - return strip_annotated_type(cast(type, get_args(typ)[0])) - - return typ - - -def extract_type_arg(typ: type, index: int) -> type: - args = get_args(typ) - try: - return cast(type, args[index]) - except IndexError as err: - raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err - - def deepcopy_minimal(item: _T) -> _T: """Minimal reimplementation of copy.deepcopy() that will only copy certain object types: From 3b338a401b206618774291ff8137deb0cc5f6b4c Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:22:14 -0500 Subject: [PATCH 07/10] chore(internal): fix typos (#993) --- src/openai/_base_client.py | 2 +- src/openai/_streaming.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openai/_base_client.py b/src/openai/_base_client.py index 92189617b5..481171a447 100644 --- a/src/openai/_base_client.py +++ b/src/openai/_base_client.py @@ -107,7 +107,7 @@ class PageInfo: - """Stores the necesary information to build the request to retrieve the next page. + """Stores the necessary information to build the request to retrieve the next page. Either `url` or `params` must be set. """ diff --git a/src/openai/_streaming.py b/src/openai/_streaming.py index f1896a242a..85cec70c11 100644 --- a/src/openai/_streaming.py +++ b/src/openai/_streaming.py @@ -60,7 +60,7 @@ def __stream__(self) -> Iterator[_T]: data = sse.json() if is_mapping(data) and data.get("error"): raise APIError( - message="An error ocurred during streaming", + message="An error occurred during streaming", request=self.response.request, body=data["error"], ) @@ -134,7 +134,7 @@ async def __stream__(self) -> AsyncIterator[_T]: data = sse.json() if is_mapping(data) and data.get("error"): raise APIError( - message="An error ocurred during streaming", + message="An error occurred during streaming", request=self.response.request, body=data["error"], ) From 0c2da84badf416f8b2213983f68bd2b6f9e52f2b Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:05:05 -0500 Subject: [PATCH 08/10] chore(package): bump minimum typing-extensions to 4.7 (#994) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0cf709a726..24498b18fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [ dependencies = [ "httpx>=0.23.0, <1", "pydantic>=1.9.0, <3", - "typing-extensions>=4.5, <5", + "typing-extensions>=4.7, <5", "anyio>=3.5.0, <5", "distro>=1.7.0, <2", "sniffio", From 7bf9b75067905449e83e828c12eb384022cff6ca Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:17:29 -0500 Subject: [PATCH 09/10] feat(api): add additional instructions for runs (#995) --- .../resources/beta/threads/runs/runs.py | 22 +++++++++++++++---- .../types/beta/threads/run_create_params.py | 14 +++++++++--- tests/api_resources/beta/threads/test_runs.py | 2 ++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/openai/resources/beta/threads/runs/runs.py b/src/openai/resources/beta/threads/runs/runs.py index 969bfab70a..aea3b8cefc 100644 --- a/src/openai/resources/beta/threads/runs/runs.py +++ b/src/openai/resources/beta/threads/runs/runs.py @@ -42,6 +42,7 @@ def create( thread_id: str, *, assistant_id: str, + additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, metadata: Optional[object] | NotGiven = NOT_GIVEN, model: Optional[str] | NotGiven = NOT_GIVEN, @@ -61,8 +62,13 @@ def create( [assistant](https://platform.openai.com/docs/api-reference/assistants) to use to execute this run. - instructions: Override the default system message of the assistant. This is useful for - modifying the behavior on a per-run basis. + additional_instructions: Appends additional instructions at the end of the instructions for the run. This + is useful for modifying the behavior on a per-run basis without overriding other + instructions. + + instructions: Overrides the + [instructions](https://platform.openai.com/docs/api-reference/assistants/createAssistant) + of the assistant. This is useful for modifying the behavior on a per-run basis. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format. Keys @@ -91,6 +97,7 @@ def create( body=maybe_transform( { "assistant_id": assistant_id, + "additional_instructions": additional_instructions, "instructions": instructions, "metadata": metadata, "model": model, @@ -332,6 +339,7 @@ async def create( thread_id: str, *, assistant_id: str, + additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, metadata: Optional[object] | NotGiven = NOT_GIVEN, model: Optional[str] | NotGiven = NOT_GIVEN, @@ -351,8 +359,13 @@ async def create( [assistant](https://platform.openai.com/docs/api-reference/assistants) to use to execute this run. - instructions: Override the default system message of the assistant. This is useful for - modifying the behavior on a per-run basis. + additional_instructions: Appends additional instructions at the end of the instructions for the run. This + is useful for modifying the behavior on a per-run basis without overriding other + instructions. + + instructions: Overrides the + [instructions](https://platform.openai.com/docs/api-reference/assistants/createAssistant) + of the assistant. This is useful for modifying the behavior on a per-run basis. metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format. Keys @@ -381,6 +394,7 @@ async def create( body=maybe_transform( { "assistant_id": assistant_id, + "additional_instructions": additional_instructions, "instructions": instructions, "metadata": metadata, "model": model, diff --git a/src/openai/types/beta/threads/run_create_params.py b/src/openai/types/beta/threads/run_create_params.py index df92f4fd2c..a4f41a9338 100644 --- a/src/openai/types/beta/threads/run_create_params.py +++ b/src/openai/types/beta/threads/run_create_params.py @@ -24,10 +24,18 @@ class RunCreateParams(TypedDict, total=False): execute this run. """ - instructions: Optional[str] - """Override the default system message of the assistant. + additional_instructions: Optional[str] + """Appends additional instructions at the end of the instructions for the run. - This is useful for modifying the behavior on a per-run basis. + This is useful for modifying the behavior on a per-run basis without overriding + other instructions. + """ + + instructions: Optional[str] + """ + Overrides the + [instructions](https://platform.openai.com/docs/api-reference/assistants/createAssistant) + of the assistant. This is useful for modifying the behavior on a per-run basis. """ metadata: Optional[object] diff --git a/tests/api_resources/beta/threads/test_runs.py b/tests/api_resources/beta/threads/test_runs.py index d323dfc354..494cae2656 100644 --- a/tests/api_resources/beta/threads/test_runs.py +++ b/tests/api_resources/beta/threads/test_runs.py @@ -34,6 +34,7 @@ def test_method_create_with_all_params(self, client: OpenAI) -> None: run = client.beta.threads.runs.create( "string", assistant_id="string", + additional_instructions="string", instructions="string", metadata={}, model="string", @@ -180,6 +181,7 @@ async def test_method_create_with_all_params(self, client: AsyncOpenAI) -> None: run = await client.beta.threads.runs.create( "string", assistant_id="string", + additional_instructions="string", instructions="string", metadata={}, model="string", From 157d5580f34e365f797ee26bb7c99bbc5f24db10 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:18:08 -0500 Subject: [PATCH 10/10] release: 1.6.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 24 ++++++++++++++++++++++++ pyproject.toml | 2 +- src/openai/_version.py | 2 +- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fbd9082d71..7deae33804 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.5.0" + ".": "1.6.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 757d79af62..399e3aaebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 1.6.0 (2023-12-19) + +Full Changelog: [v1.5.0...v1.6.0](https://github.com/openai/openai-python/compare/v1.5.0...v1.6.0) + +### Features + +* **api:** add additional instructions for runs ([#995](https://github.com/openai/openai-python/issues/995)) ([7bf9b75](https://github.com/openai/openai-python/commit/7bf9b75067905449e83e828c12eb384022cff6ca)) + + +### Chores + +* **cli:** fix typo in completions ([#985](https://github.com/openai/openai-python/issues/985)) ([d1e9e8f](https://github.com/openai/openai-python/commit/d1e9e8f24df366bb7b796c55a98247c025d229f5)) +* **cli:** fix typo in completions ([#986](https://github.com/openai/openai-python/issues/986)) ([626bc34](https://github.com/openai/openai-python/commit/626bc34d82a7057bac99f8b556f9e5f60c261ee7)) +* **internal:** fix binary response tests ([#983](https://github.com/openai/openai-python/issues/983)) ([cfb7e30](https://github.com/openai/openai-python/commit/cfb7e308393f2e912e959dd10d68096dd5b3ab9c)) +* **internal:** fix typos ([#993](https://github.com/openai/openai-python/issues/993)) ([3b338a4](https://github.com/openai/openai-python/commit/3b338a401b206618774291ff8137deb0cc5f6b4c)) +* **internal:** minor utils restructuring ([#992](https://github.com/openai/openai-python/issues/992)) ([5ba576a](https://github.com/openai/openai-python/commit/5ba576ae38d2c4c4d32a21933e0d68e0bc2f0d49)) +* **package:** bump minimum typing-extensions to 4.7 ([#994](https://github.com/openai/openai-python/issues/994)) ([0c2da84](https://github.com/openai/openai-python/commit/0c2da84badf416f8b2213983f68bd2b6f9e52f2b)) +* **streaming:** update constructor to use direct client names ([#991](https://github.com/openai/openai-python/issues/991)) ([6c3427d](https://github.com/openai/openai-python/commit/6c3427dac8c414658516aeb4caf5d5fd8b11097b)) + + +### Documentation + +* upgrade models in examples to latest version ([#989](https://github.com/openai/openai-python/issues/989)) ([cedd574](https://github.com/openai/openai-python/commit/cedd574e5611f3e71e92b523a72ba87bcfe546f1)) + ## 1.5.0 (2023-12-17) Full Changelog: [v1.4.0...v1.5.0](https://github.com/openai/openai-python/compare/v1.4.0...v1.5.0) diff --git a/pyproject.toml b/pyproject.toml index 24498b18fe..91d3d79219 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openai" -version = "1.5.0" +version = "1.6.0" description = "The official Python library for the openai API" readme = "README.md" license = "Apache-2.0" diff --git a/src/openai/_version.py b/src/openai/_version.py index 9dbb5b1401..9b01b6fcb1 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.5.0" # x-release-please-version +__version__ = "1.6.0" # x-release-please-version