diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac9a2e75..55d20255 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -6,4 +6,4 @@ USER vscode RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH -RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc +RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbeb30b1..c17fdc16 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,6 +24,9 @@ } } } + }, + "features": { + "ghcr.io/devcontainers/features/node:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4ad3fef3..e7562934 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.18.0" + ".": "0.19.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 516dbe09..3913b8d8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/groqcloud%2Fgroqcloud-d1588e103a6ae0234752b8e54a746fb1e4c93a0ee51ede294017bcd4f0ee4ac0.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/groqcloud%2Fgroqcloud-21e2668d8b211239f9b1019b09a89fcbc00855284b2434a52d80abf32de2e8f7.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3988a1..9bf35847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +## 0.19.0 (2025-03-11) + +Full Changelog: [v0.18.0...v0.19.0](https://github.com/groq/groq-python/compare/v0.18.0...v0.19.0) + +### Features + +* **api:** manual updates ([#209](https://github.com/groq/groq-python/issues/209)) ([15e2dca](https://github.com/groq/groq-python/commit/15e2dca833561464c7f56b3b5ce4de2bb4a90dfe)) +* **client:** allow passing `NotGiven` for body ([#200](https://github.com/groq/groq-python/issues/200)) ([afa6c0f](https://github.com/groq/groq-python/commit/afa6c0fc0191cedbacf99a7c6ac662d888ba9ffd)) +* **client:** send `X-Stainless-Read-Timeout` header ([#193](https://github.com/groq/groq-python/issues/193)) ([e8911a4](https://github.com/groq/groq-python/commit/e8911a43d64861153306bd03cfdaa06670b335f5)) + + +### Bug Fixes + +* add reasoning field to ChoiceDelta class ([edfee3b](https://github.com/groq/groq-python/commit/edfee3b6c5976da372908fcb0cd02e91f5b0cea3)) +* asyncify on non-asyncio runtimes ([#198](https://github.com/groq/groq-python/issues/198)) ([49387fe](https://github.com/groq/groq-python/commit/49387fe83c2886f6c623f7718f041c0065829a2b)) +* **client:** mark some request bodies as optional ([afa6c0f](https://github.com/groq/groq-python/commit/afa6c0fc0191cedbacf99a7c6ac662d888ba9ffd)) +* GitHub Terraform: Create/Update .github/workflows/stale.yaml [skip ci] ([662763a](https://github.com/groq/groq-python/commit/662763a5eaf833226772dedb36f64cfd460901a1)) +* GitHub Terraform: Create/Update .github/workflows/stale.yaml [skip ci] ([5298ec1](https://github.com/groq/groq-python/commit/5298ec1a8c6f1b6958a217c95f6a8bb19e90bd28)) + + +### Chores + +* **api:** remove chat_completion_chunk to force a rebuild of it ([#208](https://github.com/groq/groq-python/issues/208)) ([01fb0d1](https://github.com/groq/groq-python/commit/01fb0d14e438eeaef6ab1518a0c49e5b5b8e7197)) +* **docs:** update client docstring ([#204](https://github.com/groq/groq-python/issues/204)) ([a0f4599](https://github.com/groq/groq-python/commit/a0f45996ff149e0ed6f1fc262eb7041297b4bd68)) +* **internal:** codegen related update ([#199](https://github.com/groq/groq-python/issues/199)) ([de2ac71](https://github.com/groq/groq-python/commit/de2ac71d68109c3b29e6de1ba97f2c7092881c42)) +* **internal:** fix devcontainers setup ([#201](https://github.com/groq/groq-python/issues/201)) ([af101ee](https://github.com/groq/groq-python/commit/af101ee282a335d9f7970a92a99f6b63db9aebd8)) +* **internal:** fix type traversing dictionary params ([#195](https://github.com/groq/groq-python/issues/195)) ([bcb0256](https://github.com/groq/groq-python/commit/bcb025668a8b7279b8dbfb79384b1a20b95cd57f)) +* **internal:** minor type handling changes ([#196](https://github.com/groq/groq-python/issues/196)) ([3ff53df](https://github.com/groq/groq-python/commit/3ff53df5cc754090a645babff1f3e5d636f9e71c)) +* **internal:** properly set __pydantic_private__ ([#202](https://github.com/groq/groq-python/issues/202)) ([07ec0c8](https://github.com/groq/groq-python/commit/07ec0c885d7998717a2702de08dd5f1a4ee397ec)) +* **internal:** remove unused http client options forwarding ([#205](https://github.com/groq/groq-python/issues/205)) ([12fdb59](https://github.com/groq/groq-python/commit/12fdb59dbcaa121710f5c5ea710ff0f14c8c6dce)) +* **internal:** update client tests ([#197](https://github.com/groq/groq-python/issues/197)) ([2f0d2c4](https://github.com/groq/groq-python/commit/2f0d2c475a11d7162dffbb31008f01b17b2c8ef4)) + + +### Documentation + +* revise readme docs about nested params ([#206](https://github.com/groq/groq-python/issues/206)) ([7b04f47](https://github.com/groq/groq-python/commit/7b04f472fa2a203cda9747308920974ed427f0ed)) +* update URLs from stainlessapi.com to stainless.com ([#203](https://github.com/groq/groq-python/issues/203)) ([a6c6fde](https://github.com/groq/groq-python/commit/a6c6fde2ae256469481a28b59beaed1ab1f61c96)) + ## 0.18.0 (2025-02-05) Full Changelog: [v0.17.0...v0.18.0](https://github.com/groq/groq-python/compare/v0.17.0...v0.18.0) diff --git a/README.md b/README.md index fa683cb3..fda535e4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Groq Python library provides convenient access to the Groq REST API from any application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). -It is generated with [Stainless](https://www.stainlessapi.com/). +It is generated with [Stainless](https://www.stainless.com/). ## Documentation @@ -89,6 +89,46 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +## Nested params + +Nested parameters are dictionaries, typed using `TypedDict`, for example: + +```python +from groq import Groq + +client = Groq() + +chat_completion = client.chat.completions.create( + messages=[ + { + "content": "content", + "role": "system", + } + ], + model="string", + response_format={"type": "text"}, +) +print(chat_completion.response_format) +``` + +## File uploads + +Request parameters that correspond to file uploads can be passed as `bytes`, a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. + +```python +from pathlib import Path +from groq import Groq + +client = Groq() + +client.audio.transcriptions.create( + file=Path("/path/to/file"), + model="whisper-large-v3", +) +``` + +The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `groq.APIConnectionError` is raised. diff --git a/SECURITY.md b/SECURITY.md index 9550f353..c77277c1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Reporting Security Issues -This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. -To report a security issue, please contact the Stainless team at security@stainlessapi.com. +To report a security issue, please contact the Stainless team at security@stainless.com. ## Responsible Disclosure diff --git a/pyproject.toml b/pyproject.toml index d2f35868..f5613cf1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "groq" -version = "0.18.0" +version = "0.19.0" description = "The official Python library for the groq API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/scripts/test b/scripts/test index 4fa5698b..2b878456 100755 --- a/scripts/test +++ b/scripts/test @@ -52,6 +52,8 @@ else echo fi +export DEFER_PYDANTIC_BUILD=false + echo "==> Running tests" rye run pytest "$@" diff --git a/src/groq/_base_client.py b/src/groq/_base_client.py index c3f36095..531df7e6 100644 --- a/src/groq/_base_client.py +++ b/src/groq/_base_client.py @@ -9,7 +9,6 @@ import inspect import logging import platform -import warnings import email.utils from types import TracebackType from random import random @@ -36,7 +35,7 @@ import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions @@ -51,19 +50,16 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import model_copy, model_dump +from ._compat import PYDANTIC_V2, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -207,6 +203,9 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: + if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -292,6 +291,9 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: + if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -331,9 +333,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _base_url: URL max_retries: int timeout: Union[float, Timeout, None] - _limits: httpx.Limits - _proxies: ProxiesTypes | None - _transport: Transport | AsyncTransport | None _strict_response_validation: bool _idempotency_header: str | None _default_stream_cls: type[_DefaultStreamT] | None = None @@ -346,9 +345,6 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, - limits: httpx.Limits, - transport: Transport | AsyncTransport | None, - proxies: ProxiesTypes | None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -356,9 +352,6 @@ def __init__( self._base_url = self._enforce_trailing_slash(URL(base_url)) self.max_retries = max_retries self.timeout = timeout - self._limits = limits - self._proxies = proxies - self._transport = transport self._custom_headers = custom_headers or {} self._custom_query = custom_query or {} self._strict_response_validation = _strict_response_validation @@ -418,10 +411,17 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: headers[idempotency_header] = options.idempotency_key or self._idempotency_key() - # Don't set the retry count header if it was already set or removed by the caller. We check + # Don't set these headers if they were already set or removed by the caller. We check # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. - if "x-stainless-retry-count" not in (header.lower() for header in custom_headers): + lower_custom_headers = [header.lower() for header in custom_headers] + if "x-stainless-retry-count" not in lower_custom_headers: headers["x-stainless-retry-count"] = str(retries_taken) + if "x-stainless-read-timeout" not in lower_custom_headers: + timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout + if isinstance(timeout, Timeout): + timeout = timeout.read + if timeout is not None: + headers["x-stainless-read-timeout"] = str(timeout) return headers @@ -511,7 +511,7 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data, + json=json_data if is_given(json_data) else None, files=files, **kwargs, ) @@ -787,46 +787,11 @@ def __init__( base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: Transport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, ) -> None: - kwargs: dict[str, Any] = {} - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - kwargs["transport"] = transport - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - kwargs["proxies"] = proxies - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -847,12 +812,9 @@ def __init__( super().__init__( version=version, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, base_url=base_url, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -862,9 +824,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: @@ -1359,45 +1318,10 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: AsyncTransport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: - kwargs: dict[str, Any] = {} - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - kwargs["transport"] = transport - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - kwargs["proxies"] = proxies - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -1419,11 +1343,8 @@ def __init__( super().__init__( version=version, base_url=base_url, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -1433,9 +1354,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: diff --git a/src/groq/_client.py b/src/groq/_client.py index d77b872a..8c393bd3 100644 --- a/src/groq/_client.py +++ b/src/groq/_client.py @@ -74,7 +74,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous groq client instance. + """Construct a new synchronous Groq client instance. This automatically infers the `api_key` argument from the `GROQ_API_KEY` environment variable if it is not provided. """ @@ -252,7 +252,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async groq client instance. + """Construct a new async AsyncGroq client instance. This automatically infers the `api_key` argument from the `GROQ_API_KEY` environment variable if it is not provided. """ diff --git a/src/groq/_files.py b/src/groq/_files.py index 715cc207..68da8098 100644 --- a/src/groq/_files.py +++ b/src/groq/_files.py @@ -34,7 +34,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: if not is_file_content(obj): prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/groq/groq-python/tree/main#file-uploads" ) from None diff --git a/src/groq/_models.py b/src/groq/_models.py index 12c34b7d..c4401ff8 100644 --- a/src/groq/_models.py +++ b/src/groq/_models.py @@ -426,10 +426,16 @@ def construct_type(*, value: object, type_: object) -> object: If the given value does not match the expected type then it is returned as-is. """ + + # store a reference to the original type we were given before we extract any inner + # types so that we can properly resolve forward references in `TypeAliasType` annotations + original_type = None + # we allow `object` as the input type because otherwise, passing things like # `Literal['value']` will be reported as a type error by type checkers type_ = cast("type[object]", type_) if is_type_alias_type(type_): + original_type = type_ # type: ignore[unreachable] type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` @@ -446,7 +452,7 @@ def construct_type(*, value: object, type_: object) -> object: if is_union(origin): try: - return validate_type(type_=cast("type[object]", type_), value=value) + return validate_type(type_=cast("type[object]", original_type or type_), value=value) except Exception: pass diff --git a/src/groq/_utils/_sync.py b/src/groq/_utils/_sync.py index 8b3aaf2b..ad7ec71b 100644 --- a/src/groq/_utils/_sync.py +++ b/src/groq/_utils/_sync.py @@ -7,16 +7,20 @@ from typing import Any, TypeVar, Callable, Awaitable from typing_extensions import ParamSpec +import anyio +import sniffio +import anyio.to_thread + T_Retval = TypeVar("T_Retval") T_ParamSpec = ParamSpec("T_ParamSpec") if sys.version_info >= (3, 9): - to_thread = asyncio.to_thread + _asyncio_to_thread = asyncio.to_thread else: # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread # for Python 3.8 support - async def to_thread( + async def _asyncio_to_thread( func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs ) -> Any: """Asynchronously run function *func* in a separate thread. @@ -34,6 +38,17 @@ async def to_thread( return await loop.run_in_executor(None, func_call) +async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs +) -> T_Retval: + if sniffio.current_async_library() == "asyncio": + return await _asyncio_to_thread(func, *args, **kwargs) + + return await anyio.to_thread.run_sync( + functools.partial(func, *args, **kwargs), + ) + + # inspired by `asyncer`, https://github.com/tiangolo/asyncer def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ diff --git a/src/groq/_utils/_transform.py b/src/groq/_utils/_transform.py index a6b62cad..18afd9d8 100644 --- a/src/groq/_utils/_transform.py +++ b/src/groq/_utils/_transform.py @@ -25,7 +25,7 @@ is_annotated_type, strip_annotated_type, ) -from .._compat import model_dump, is_typeddict +from .._compat import get_origin, model_dump, is_typeddict _T = TypeVar("_T") @@ -164,9 +164,14 @@ def _transform_recursive( inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return _transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) @@ -307,9 +312,14 @@ async def _async_transform_recursive( inner_type = annotation stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return await _async_transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (is_list_type(stripped_type) and is_list(data)) diff --git a/src/groq/_version.py b/src/groq/_version.py index dbdf6580..f4939f22 100644 --- a/src/groq/_version.py +++ b/src/groq/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "groq" -__version__ = "0.18.0" # x-release-please-version +__version__ = "0.19.0" # x-release-please-version diff --git a/src/groq/types/chat/chat_completion_chunk.py b/src/groq/types/chat/chat_completion_chunk.py index 06d2a866..a84bfea8 100644 --- a/src/groq/types/chat/chat_completion_chunk.py +++ b/src/groq/types/chat/chat_completion_chunk.py @@ -68,6 +68,13 @@ class ChoiceDelta(BaseModel): model. """ + reasoning: Optional[str] = None + """The model's reasoning for a response. + + Only available for reasoning models when requests parameter reasoning_format has + value `parsed. + """ + role: Optional[Literal["system", "user", "assistant", "tool"]] = None """The role of the author of this message.""" diff --git a/tests/test_client.py b/tests/test_client.py index 1615f63b..1dc985ab 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,10 +23,12 @@ from groq import Groq, AsyncGroq, APIResponseValidationError from groq._types import Omit +from groq._utils import maybe_transform from groq._models import BaseModel, FinalRequestOptions from groq._constants import RAW_RESPONSE_HEADER from groq._exceptions import GroqError, APIStatusError, APITimeoutError, APIResponseValidationError from groq._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options +from groq.types.chat.completion_create_params import CompletionCreateParams from .utils import update_env @@ -706,18 +708,21 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No "/openai/v1/chat/completions", body=cast( object, - dict( - messages=[ - { - "role": "system", - "content": "You are a helpful assistant.", - }, - { - "role": "user", - "content": "Explain the importance of low latency LLMs", - }, - ], - model="llama3-8b-8192", + maybe_transform( + dict( + messages=[ + { + "role": "system", + "content": "You are a helpful assistant.", + }, + { + "role": "user", + "content": "Explain the importance of low latency LLMs", + }, + ], + model="llama3-8b-8192", + ), + CompletionCreateParams, ), ), cast_to=httpx.Response, @@ -736,18 +741,21 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non "/openai/v1/chat/completions", body=cast( object, - dict( - messages=[ - { - "role": "system", - "content": "You are a helpful assistant.", - }, - { - "role": "user", - "content": "Explain the importance of low latency LLMs", - }, - ], - model="llama3-8b-8192", + maybe_transform( + dict( + messages=[ + { + "role": "system", + "content": "You are a helpful assistant.", + }, + { + "role": "user", + "content": "Explain the importance of low latency LLMs", + }, + ], + model="llama3-8b-8192", + ), + CompletionCreateParams, ), ), cast_to=httpx.Response, @@ -1526,18 +1534,21 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) "/openai/v1/chat/completions", body=cast( object, - dict( - messages=[ - { - "role": "system", - "content": "You are a helpful assistant.", - }, - { - "role": "user", - "content": "Explain the importance of low latency LLMs", - }, - ], - model="llama3-8b-8192", + maybe_transform( + dict( + messages=[ + { + "role": "system", + "content": "You are a helpful assistant.", + }, + { + "role": "user", + "content": "Explain the importance of low latency LLMs", + }, + ], + model="llama3-8b-8192", + ), + CompletionCreateParams, ), ), cast_to=httpx.Response, @@ -1556,18 +1567,21 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) "/openai/v1/chat/completions", body=cast( object, - dict( - messages=[ - { - "role": "system", - "content": "You are a helpful assistant.", - }, - { - "role": "user", - "content": "Explain the importance of low latency LLMs", - }, - ], - model="llama3-8b-8192", + maybe_transform( + dict( + messages=[ + { + "role": "system", + "content": "You are a helpful assistant.", + }, + { + "role": "user", + "content": "Explain the importance of low latency LLMs", + }, + ], + model="llama3-8b-8192", + ), + CompletionCreateParams, ), ), cast_to=httpx.Response, diff --git a/tests/test_transform.py b/tests/test_transform.py index e29e15cf..af72c409 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -2,7 +2,7 @@ import io import pathlib -from typing import Any, List, Union, TypeVar, Iterable, Optional, cast +from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast from datetime import date, datetime from typing_extensions import Required, Annotated, TypedDict @@ -388,6 +388,15 @@ def my_iter() -> Iterable[Baz8]: } +@parametrize +@pytest.mark.asyncio +async def test_dictionary_items(use_async: bool) -> None: + class DictItems(TypedDict): + foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] + + assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}} + + class TypedDictIterableUnionStr(TypedDict): foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")]