From 783cc663ab4592ab665d4d0e25b360533a02fa3b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 02:22:35 +0000 Subject: [PATCH 01/49] chore(internal): bump pyright version --- pyproject.toml | 2 +- requirements-dev.lock | 2 +- src/agility/_base_client.py | 6 +++++- src/agility/_models.py | 1 - src/agility/_utils/_typing.py | 2 +- tests/conftest.py | 2 +- tests/test_models.py | 2 +- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2e3362e..0ead3e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ Repository = "https://github.com/stainless-sdks/agility-python" managed = true # version pins are in requirements-dev.lock dev-dependencies = [ - "pyright>=1.1.359", + "pyright==1.1.399", "mypy", "respx", "pytest", diff --git a/requirements-dev.lock b/requirements-dev.lock index 2ecab23..ae49b73 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -69,7 +69,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.392.post0 +pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 diff --git a/src/agility/_base_client.py b/src/agility/_base_client.py index 2de45b2..6c3d26b 100644 --- a/src/agility/_base_client.py +++ b/src/agility/_base_client.py @@ -98,7 +98,11 @@ _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) if TYPE_CHECKING: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG else: try: from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT diff --git a/src/agility/_models.py b/src/agility/_models.py index 3493571..58b9263 100644 --- a/src/agility/_models.py +++ b/src/agility/_models.py @@ -19,7 +19,6 @@ ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( diff --git a/src/agility/_utils/_typing.py b/src/agility/_utils/_typing.py index 1958820..1bac954 100644 --- a/src/agility/_utils/_typing.py +++ b/src/agility/_utils/_typing.py @@ -110,7 +110,7 @@ class MyResponse(Foo[_T]): ``` """ cls = cast(object, get_origin(typ) or typ) - if cls in generic_bases: + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] # we're given the class directly return extract_type_arg(typ, index) diff --git a/tests/conftest.py b/tests/conftest.py index 8b10331..4e30de8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ from agility import Agility, AsyncAgility if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest + from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] pytest.register_assert_rewrite("tests.utils") diff --git a/tests/test_models.py b/tests/test_models.py index 51b9989..8e3d194 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -832,7 +832,7 @@ class B(BaseModel): @pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: - Alias = TypeAliasType("Alias", str) + Alias = TypeAliasType("Alias", str) # pyright: ignore class Model(BaseModel): alias: Alias From f2a8f20b81490bc6115d1104ed20ff99fc274d81 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 02:23:06 +0000 Subject: [PATCH 02/49] chore(internal): base client updates --- src/agility/_base_client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/agility/_base_client.py b/src/agility/_base_client.py index 6c3d26b..97b020c 100644 --- a/src/agility/_base_client.py +++ b/src/agility/_base_client.py @@ -119,6 +119,7 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( @@ -134,19 +135,30 @@ def __init__( params: Query, ) -> None: ... + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... + def __init__( self, *, url: URL | NotGiven = NOT_GIVEN, + json: Body | NotGiven = NOT_GIVEN, params: Query | NotGiven = NOT_GIVEN, ) -> None: self.url = url + self.json = json self.params = params @override def __repr__(self) -> str: if self.url: return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" return f"{self.__class__.__name__}(params={self.params})" @@ -195,6 +207,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options.url = str(url) return options + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + raise ValueError("Unexpected PageInfo state") From 2323a23d5cc9a743d205652eb4d8d7bab4976383 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 02:25:22 +0000 Subject: [PATCH 03/49] chore(internal): update models test --- tests/test_models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index 8e3d194..45fa504 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -492,12 +492,15 @@ class Model(BaseModel): resource_id: Optional[str] = None m = Model.construct() + assert m.resource_id is None assert "resource_id" not in m.model_fields_set m = Model.construct(resource_id=None) + assert m.resource_id is None assert "resource_id" in m.model_fields_set m = Model.construct(resource_id="foo") + assert m.resource_id == "foo" assert "resource_id" in m.model_fields_set From 0d7d519357901b60864efb2619a81ed21fd76653 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:45:21 +0000 Subject: [PATCH 04/49] chore(ci): add timeout thresholds for CI jobs --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81f6dc2..04b083c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: jobs: lint: + timeout-minutes: 10 name: lint runs-on: ubuntu-latest steps: @@ -30,6 +31,7 @@ jobs: run: ./scripts/lint test: + timeout-minutes: 10 name: test runs-on: ubuntu-latest steps: From b801c7851f5582cbfcc8107f91cfb84a3571ee76 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:45:49 +0000 Subject: [PATCH 05/49] chore(internal): import reformatting --- src/agility/_client.py | 5 +---- src/agility/resources/assistants/access_keys.py | 5 +---- src/agility/resources/assistants/assistants.py | 5 +---- src/agility/resources/integrations/integrations.py | 5 +---- .../resources/knowledge_bases/knowledge_bases.py | 11 ++--------- .../resources/knowledge_bases/sources/sources.py | 5 +---- src/agility/resources/threads/messages.py | 5 +---- src/agility/resources/threads/runs.py | 5 +---- 8 files changed, 9 insertions(+), 37 deletions(-) diff --git a/src/agility/_client.py b/src/agility/_client.py index 818130a..a539dcd 100644 --- a/src/agility/_client.py +++ b/src/agility/_client.py @@ -19,10 +19,7 @@ ProxiesTypes, RequestOptions, ) -from ._utils import ( - is_given, - get_async_library, -) +from ._utils import is_given, get_async_library from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import AgilityError, APIStatusError diff --git a/src/agility/resources/assistants/access_keys.py b/src/agility/resources/assistants/access_keys.py index 2b154fe..d6d9733 100644 --- a/src/agility/resources/assistants/access_keys.py +++ b/src/agility/resources/assistants/access_keys.py @@ -8,10 +8,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/agility/resources/assistants/assistants.py b/src/agility/resources/assistants/assistants.py index 995d51d..21ca753 100644 --- a/src/agility/resources/assistants/assistants.py +++ b/src/agility/resources/assistants/assistants.py @@ -9,10 +9,7 @@ from ...types import assistant_list_params, assistant_create_params, assistant_update_params from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/agility/resources/integrations/integrations.py b/src/agility/resources/integrations/integrations.py index 536398a..ae3f689 100644 --- a/src/agility/resources/integrations/integrations.py +++ b/src/agility/resources/integrations/integrations.py @@ -16,10 +16,7 @@ ) from ...types import integration_list_params, integration_create_params from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from .available import ( AvailableResource, diff --git a/src/agility/resources/knowledge_bases/knowledge_bases.py b/src/agility/resources/knowledge_bases/knowledge_bases.py index 3d6cf25..6844a48 100644 --- a/src/agility/resources/knowledge_bases/knowledge_bases.py +++ b/src/agility/resources/knowledge_bases/knowledge_bases.py @@ -4,16 +4,9 @@ import httpx -from ...types import ( - knowledge_base_list_params, - knowledge_base_create_params, - knowledge_base_update_params, -) +from ...types import knowledge_base_list_params, knowledge_base_create_params, knowledge_base_update_params from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/agility/resources/knowledge_bases/sources/sources.py b/src/agility/resources/knowledge_bases/sources/sources.py index 06bc2a6..efa0fc0 100644 --- a/src/agility/resources/knowledge_bases/sources/sources.py +++ b/src/agility/resources/knowledge_bases/sources/sources.py @@ -5,10 +5,7 @@ import httpx from ...._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ...._utils import ( - maybe_transform, - async_maybe_transform, -) +from ...._utils import maybe_transform, async_maybe_transform from .documents import ( DocumentsResource, AsyncDocumentsResource, diff --git a/src/agility/resources/threads/messages.py b/src/agility/resources/threads/messages.py index c1e7fd9..ab6facc 100644 --- a/src/agility/resources/threads/messages.py +++ b/src/agility/resources/threads/messages.py @@ -8,10 +8,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/agility/resources/threads/runs.py b/src/agility/resources/threads/runs.py index 47c80b3..4a94d2c 100644 --- a/src/agility/resources/threads/runs.py +++ b/src/agility/resources/threads/runs.py @@ -8,10 +8,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( From e94a0e5c6738b620d4bc9e731149221a3f5dd373 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:47:04 +0000 Subject: [PATCH 06/49] chore(internal): fix list file params --- src/agility/_utils/_utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/agility/_utils/_utils.py b/src/agility/_utils/_utils.py index e5811bb..ea3cf3f 100644 --- a/src/agility/_utils/_utils.py +++ b/src/agility/_utils/_utils.py @@ -72,8 +72,16 @@ def _extract_items( from .._files import assert_is_file_content # We have exhausted the path, return the entry we found. - assert_is_file_content(obj, key=flattened_key) assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for entry in obj: + assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") + files.append((flattened_key + "[]", cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) return [(flattened_key, cast(FileTypes, obj))] index += 1 From 6ab66d155362bf1fbbc53654bb8f4e1332012203 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:47:27 +0000 Subject: [PATCH 07/49] chore(internal): refactor retries to not use recursion --- src/agility/_base_client.py | 414 +++++++++++++++--------------------- 1 file changed, 175 insertions(+), 239 deletions(-) diff --git a/src/agility/_base_client.py b/src/agility/_base_client.py index 97b020c..a942d3f 100644 --- a/src/agility/_base_client.py +++ b/src/agility/_base_client.py @@ -437,8 +437,7 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 headers = httpx.Headers(headers_dict) idempotency_header = self._idempotency_header - if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: - options.idempotency_key = options.idempotency_key or self._idempotency_key() + if idempotency_header and options.idempotency_key and idempotency_header not in headers: headers[idempotency_header] = options.idempotency_key # Don't set these headers if they were already set or removed by the caller. We check @@ -903,7 +902,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], @@ -914,7 +912,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, ) -> ResponseT: ... @@ -924,7 +921,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: Type[_StreamT] | None = None, @@ -934,125 +930,109 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) + cast_to = self._maybe_override_cast_to(cast_to, options) - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - retries_taken: int, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) - - cast_to = self._maybe_override_cast_to(cast_to, options) - options = self._prepare_options(options) - - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - self._prepare_request(request) - - if options.idempotency_key: + if input_options.idempotency_key is None and input_options.method.lower() != "get": # ensure the idempotency key is reused between requests - input_options.idempotency_key = options.idempotency_key + input_options.idempotency_key = self._idempotency_key() - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - log.debug("Sending HTTP Request: %s %s", request.method, request.url) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - log.debug( - 'HTTP Response: %s %s "%i %s" %s', - request.method, - request.url, - response.status_code, - response.reason_phrase, - response.headers, - ) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - err.response.close() - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - err.response.read() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return self._process_response( cast_to=cast_to, options=options, @@ -1062,37 +1042,20 @@ def _request( retries_taken=retries_taken, ) - def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) - # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a - # different thread if necessary. time.sleep(timeout) - return self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - def _process_response( self, *, @@ -1436,7 +1399,6 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, ) -> ResponseT: ... @overload @@ -1447,7 +1409,6 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, ) -> _AsyncStreamT: ... @overload @@ -1458,7 +1419,6 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: ... async def request( @@ -1468,120 +1428,111 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) - - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - retries_taken: int, ) -> ResponseT | _AsyncStreamT: if self._platform is None: # `get_platform` can make blocking IO calls so we # execute it earlier while we are in an async context self._platform = await asyncify(get_platform)() + cast_to = self._maybe_override_cast_to(cast_to, options) + # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) - - cast_to = self._maybe_override_cast_to(cast_to, options) - options = await self._prepare_options(options) - - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - await self._prepare_request(request) - - if options.idempotency_key: + if input_options.idempotency_key is None and input_options.method.lower() != "get": # ensure the idempotency key is reused between requests - input_options.idempotency_key = options.idempotency_key + input_options.idempotency_key = self._idempotency_key() - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - try: - response = await self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + response = None + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, + ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + await err.response.aclose() + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - await err.response.aclose() - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - await err.response.aread() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return await self._process_response( cast_to=cast_to, options=options, @@ -1591,35 +1542,20 @@ async def _request( retries_taken=retries_taken, ) - async def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - ) -> ResponseT | _AsyncStreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) await anyio.sleep(timeout) - return await self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - async def _process_response( self, *, From bfbb2fdd5c55ef0800a2d0ba87eeb553cc22571d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:47:54 +0000 Subject: [PATCH 08/49] fix(pydantic v1): more robust ModelField.annotation check --- src/agility/_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agility/_models.py b/src/agility/_models.py index 58b9263..798956f 100644 --- a/src/agility/_models.py +++ b/src/agility/_models.py @@ -626,8 +626,8 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, # Note: if one variant defines an alias then they all should discriminator_alias = field_info.alias - if field_info.annotation and is_literal_type(field_info.annotation): - for entry in get_args(field_info.annotation): + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant From 7f1729b6702d0ccd7eee30781c8d1c3eca9bc1e0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:21:17 +0000 Subject: [PATCH 09/49] chore(internal): codegen related update --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04b083c..3382042 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,18 @@ name: CI on: push: - branches: - - main - pull_request: - branches: - - main - - next + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: timeout-minutes: 10 name: lint - runs-on: ubuntu-latest + runs-on: depot-ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: ubuntu-latest + runs-on: depot-ubuntu-24.04 steps: - uses: actions/checkout@v4 From f256cb903bc9683f19a03c147e55116255838f30 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:21:47 +0000 Subject: [PATCH 10/49] chore(ci): only use depot for staging repos --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3382042..d561344 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: depot-ubuntu-24.04 + runs-on: ${{ github.repository == 'stainless-sdks/agility-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: depot-ubuntu-24.04 + runs-on: ${{ github.repository == 'stainless-sdks/agility-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 From 871ab413a298b1526032625c0808d978693e2b12 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:23:04 +0000 Subject: [PATCH 11/49] chore: broadly detect json family of content-type headers --- src/agility/_response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agility/_response.py b/src/agility/_response.py index 9c02d4e..af2e7d1 100644 --- a/src/agility/_response.py +++ b/src/agility/_response.py @@ -233,7 +233,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() From ef52679d596ead31502a9c2f9e39d305d57c84ad Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 8 May 2025 16:19:08 +0000 Subject: [PATCH 12/49] feat(api): api update --- .stats.yml | 4 +-- .../resources/assistants/assistants.py | 8 +++++ src/agility/resources/threads/runs.py | 8 +++++ src/agility/types/assistant_create_params.py | 21 +++++++++++- src/agility/types/assistant_list_response.py | 21 +++++++++++- src/agility/types/assistant_update_params.py | 21 +++++++++++- src/agility/types/assistant_with_config.py | 21 +++++++++++- src/agility/types/threads/run.py | 14 +++++++- .../types/threads/run_create_params.py | 13 ++++++++ .../types/threads/run_stream_params.py | 13 ++++++++ tests/api_resources/test_assistants.py | 32 +++++++++++++++++++ tests/api_resources/threads/test_runs.py | 32 +++++++++++++++++++ 12 files changed, 201 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index 57c3565..6b56c5f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-bb543bebe38d4cc889a3fa1ebc212458cd4321233d904357be98f8b22db82960.yml -openapi_spec_hash: 7a30a005e382a8db9fafa55903c3a977 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-c93c84419ab3208e9f1e14e1a1bf8af3ea8fd3ec23cc79d3719e69f38dd005a0.yml +openapi_spec_hash: f2bbb5d3d108f00bf009ee7f588cacf4 config_hash: 6d2156cfe279456cf3c35ba5c66be1c1 diff --git a/src/agility/resources/assistants/assistants.py b/src/agility/resources/assistants/assistants.py index 21ca753..41829ad 100644 --- a/src/agility/resources/assistants/assistants.py +++ b/src/agility/resources/assistants/assistants.py @@ -67,6 +67,7 @@ def create( name: str, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, + hard_coded_queries: Optional[Iterable[assistant_create_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, logo_s3_key: Optional[str] | NotGiven = NOT_GIVEN, logo_text: Optional[str] | NotGiven = NOT_GIVEN, @@ -118,6 +119,7 @@ def create( "name": name, "codex_access_key": codex_access_key, "context_limit": context_limit, + "hard_coded_queries": hard_coded_queries, "instructions": instructions, "logo_s3_key": logo_s3_key, "logo_text": logo_text, @@ -178,6 +180,7 @@ def update( name: str, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, + hard_coded_queries: Optional[Iterable[assistant_update_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, logo_s3_key: Optional[str] | NotGiven = NOT_GIVEN, logo_text: Optional[str] | NotGiven = NOT_GIVEN, @@ -232,6 +235,7 @@ def update( "name": name, "codex_access_key": codex_access_key, "context_limit": context_limit, + "hard_coded_queries": hard_coded_queries, "instructions": instructions, "logo_s3_key": logo_s3_key, "logo_text": logo_text, @@ -359,6 +363,7 @@ async def create( name: str, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, + hard_coded_queries: Optional[Iterable[assistant_create_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, logo_s3_key: Optional[str] | NotGiven = NOT_GIVEN, logo_text: Optional[str] | NotGiven = NOT_GIVEN, @@ -410,6 +415,7 @@ async def create( "name": name, "codex_access_key": codex_access_key, "context_limit": context_limit, + "hard_coded_queries": hard_coded_queries, "instructions": instructions, "logo_s3_key": logo_s3_key, "logo_text": logo_text, @@ -470,6 +476,7 @@ async def update( name: str, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, + hard_coded_queries: Optional[Iterable[assistant_update_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, logo_s3_key: Optional[str] | NotGiven = NOT_GIVEN, logo_text: Optional[str] | NotGiven = NOT_GIVEN, @@ -524,6 +531,7 @@ async def update( "name": name, "codex_access_key": codex_access_key, "context_limit": context_limit, + "hard_coded_queries": hard_coded_queries, "instructions": instructions, "logo_s3_key": logo_s3_key, "logo_text": logo_text, diff --git a/src/agility/resources/threads/runs.py b/src/agility/resources/threads/runs.py index 4a94d2c..01f1169 100644 --- a/src/agility/resources/threads/runs.py +++ b/src/agility/resources/threads/runs.py @@ -53,6 +53,7 @@ def create( additional_messages: Iterable[run_create_params.AdditionalMessage] | NotGiven = NOT_GIVEN, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, + hard_coded_queries: Optional[Iterable[run_create_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, knowledge_base_id: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, @@ -91,6 +92,7 @@ def create( "additional_messages": additional_messages, "codex_access_key": codex_access_key, "context_limit": context_limit, + "hard_coded_queries": hard_coded_queries, "instructions": instructions, "knowledge_base_id": knowledge_base_id, "model": model, @@ -187,6 +189,7 @@ def stream( additional_messages: Iterable[run_stream_params.AdditionalMessage] | NotGiven = NOT_GIVEN, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, + hard_coded_queries: Optional[Iterable[run_stream_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, knowledge_base_id: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, @@ -225,6 +228,7 @@ def stream( "additional_messages": additional_messages, "codex_access_key": codex_access_key, "context_limit": context_limit, + "hard_coded_queries": hard_coded_queries, "instructions": instructions, "knowledge_base_id": knowledge_base_id, "model": model, @@ -269,6 +273,7 @@ async def create( additional_messages: Iterable[run_create_params.AdditionalMessage] | NotGiven = NOT_GIVEN, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, + hard_coded_queries: Optional[Iterable[run_create_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, knowledge_base_id: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, @@ -307,6 +312,7 @@ async def create( "additional_messages": additional_messages, "codex_access_key": codex_access_key, "context_limit": context_limit, + "hard_coded_queries": hard_coded_queries, "instructions": instructions, "knowledge_base_id": knowledge_base_id, "model": model, @@ -403,6 +409,7 @@ async def stream( additional_messages: Iterable[run_stream_params.AdditionalMessage] | NotGiven = NOT_GIVEN, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, + hard_coded_queries: Optional[Iterable[run_stream_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, knowledge_base_id: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, @@ -441,6 +448,7 @@ async def stream( "additional_messages": additional_messages, "codex_access_key": codex_access_key, "context_limit": context_limit, + "hard_coded_queries": hard_coded_queries, "instructions": instructions, "knowledge_base_id": knowledge_base_id, "model": model, diff --git a/src/agility/types/assistant_create_params.py b/src/agility/types/assistant_create_params.py index 70e0eb5..b9310b8 100644 --- a/src/agility/types/assistant_create_params.py +++ b/src/agility/types/assistant_create_params.py @@ -5,7 +5,14 @@ from typing import List, Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict -__all__ = ["AssistantCreateParams", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", "ToolNoOpTool"] +__all__ = [ + "AssistantCreateParams", + "HardCodedQuery", + "ResponseValidationConfig", + "Tool", + "ToolCodexV0Tool", + "ToolNoOpTool", +] class AssistantCreateParams(TypedDict, total=False): @@ -22,6 +29,8 @@ class AssistantCreateParams(TypedDict, total=False): context_limit: Optional[int] """The maximum number of context chunks to include in a run.""" + hard_coded_queries: Optional[Iterable[HardCodedQuery]] + instructions: Optional[str] logo_s3_key: Optional[str] @@ -43,6 +52,16 @@ class AssistantCreateParams(TypedDict, total=False): """Optional URL suffix - unique identifier for the assistant's endpoint""" +class HardCodedQuery(TypedDict, total=False): + query: Required[str] + + response: Required[str] + + context: Optional[List[str]] + + prompt: Optional[str] + + class ResponseValidationConfig(TypedDict, total=False): is_bad_threshold: Required[float] diff --git a/src/agility/types/assistant_list_response.py b/src/agility/types/assistant_list_response.py index d263ea0..fb54273 100644 --- a/src/agility/types/assistant_list_response.py +++ b/src/agility/types/assistant_list_response.py @@ -6,7 +6,24 @@ from .._models import BaseModel -__all__ = ["AssistantListResponse", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", "ToolNoOpTool"] +__all__ = [ + "AssistantListResponse", + "HardCodedQuery", + "ResponseValidationConfig", + "Tool", + "ToolCodexV0Tool", + "ToolNoOpTool", +] + + +class HardCodedQuery(BaseModel): + query: str + + response: str + + context: Optional[List[str]] = None + + prompt: Optional[str] = None class ResponseValidationConfig(BaseModel): @@ -54,6 +71,8 @@ class AssistantListResponse(BaseModel): context_limit: Optional[int] = None """The maximum number of context chunks to include in a run.""" + hard_coded_queries: Optional[List[HardCodedQuery]] = None + instructions: Optional[str] = None logo_s3_key: Optional[str] = None diff --git a/src/agility/types/assistant_update_params.py b/src/agility/types/assistant_update_params.py index dba55cd..06346c2 100644 --- a/src/agility/types/assistant_update_params.py +++ b/src/agility/types/assistant_update_params.py @@ -5,7 +5,14 @@ from typing import List, Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict -__all__ = ["AssistantUpdateParams", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", "ToolNoOpTool"] +__all__ = [ + "AssistantUpdateParams", + "HardCodedQuery", + "ResponseValidationConfig", + "Tool", + "ToolCodexV0Tool", + "ToolNoOpTool", +] class AssistantUpdateParams(TypedDict, total=False): @@ -24,6 +31,8 @@ class AssistantUpdateParams(TypedDict, total=False): context_limit: Optional[int] """The maximum number of context chunks to include in a run.""" + hard_coded_queries: Optional[Iterable[HardCodedQuery]] + instructions: Optional[str] logo_s3_key: Optional[str] @@ -45,6 +54,16 @@ class AssistantUpdateParams(TypedDict, total=False): """Optional URL suffix - unique identifier for the assistant's endpoint""" +class HardCodedQuery(TypedDict, total=False): + query: Required[str] + + response: Required[str] + + context: Optional[List[str]] + + prompt: Optional[str] + + class ResponseValidationConfig(TypedDict, total=False): is_bad_threshold: Required[float] diff --git a/src/agility/types/assistant_with_config.py b/src/agility/types/assistant_with_config.py index 740a401..1886aa6 100644 --- a/src/agility/types/assistant_with_config.py +++ b/src/agility/types/assistant_with_config.py @@ -6,7 +6,24 @@ from .._models import BaseModel -__all__ = ["AssistantWithConfig", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", "ToolNoOpTool"] +__all__ = [ + "AssistantWithConfig", + "HardCodedQuery", + "ResponseValidationConfig", + "Tool", + "ToolCodexV0Tool", + "ToolNoOpTool", +] + + +class HardCodedQuery(BaseModel): + query: str + + response: str + + context: Optional[List[str]] = None + + prompt: Optional[str] = None class ResponseValidationConfig(BaseModel): @@ -52,6 +69,8 @@ class AssistantWithConfig(BaseModel): context_limit: Optional[int] = None """The maximum number of context chunks to include in a run.""" + hard_coded_queries: Optional[List[HardCodedQuery]] = None + instructions: Optional[str] = None logo_s3_key: Optional[str] = None diff --git a/src/agility/types/threads/run.py b/src/agility/types/threads/run.py index 55a43c1..deb977b 100644 --- a/src/agility/types/threads/run.py +++ b/src/agility/types/threads/run.py @@ -6,7 +6,17 @@ from ..._models import BaseModel -__all__ = ["Run", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", "ToolNoOpTool", "Usage"] +__all__ = ["Run", "HardCodedQuery", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", "ToolNoOpTool", "Usage"] + + +class HardCodedQuery(BaseModel): + query: str + + response: str + + context: Optional[List[str]] = None + + prompt: Optional[str] = None class ResponseValidationConfig(BaseModel): @@ -60,6 +70,8 @@ class Run(BaseModel): deleted_at: Optional[datetime] = None + hard_coded_queries: Optional[List[HardCodedQuery]] = None + instructions: Optional[str] = None knowledge_base_id: Optional[str] = None diff --git a/src/agility/types/threads/run_create_params.py b/src/agility/types/threads/run_create_params.py index e0f23da..8959daf 100644 --- a/src/agility/types/threads/run_create_params.py +++ b/src/agility/types/threads/run_create_params.py @@ -20,6 +20,7 @@ "AdditionalMessageMetadataScoresResponseHelpfulnessLog", "AdditionalMessageMetadataScoresTrustworthiness", "AdditionalMessageMetadataScoresTrustworthinessLog", + "HardCodedQuery", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", @@ -39,6 +40,8 @@ class RunCreateParams(TypedDict, total=False): context_limit: Optional[int] """The maximum number of context chunks to include.""" + hard_coded_queries: Optional[Iterable[HardCodedQuery]] + instructions: Optional[str] knowledge_base_id: Optional[str] @@ -146,6 +149,16 @@ class AdditionalMessage(TypedDict, total=False): thread_id: Required[str] +class HardCodedQuery(TypedDict, total=False): + query: Required[str] + + response: Required[str] + + context: Optional[List[str]] + + prompt: Optional[str] + + class ResponseValidationConfig(TypedDict, total=False): is_bad_threshold: Required[float] diff --git a/src/agility/types/threads/run_stream_params.py b/src/agility/types/threads/run_stream_params.py index b4036ca..995ea64 100644 --- a/src/agility/types/threads/run_stream_params.py +++ b/src/agility/types/threads/run_stream_params.py @@ -20,6 +20,7 @@ "AdditionalMessageMetadataScoresResponseHelpfulnessLog", "AdditionalMessageMetadataScoresTrustworthiness", "AdditionalMessageMetadataScoresTrustworthinessLog", + "HardCodedQuery", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", @@ -39,6 +40,8 @@ class RunStreamParams(TypedDict, total=False): context_limit: Optional[int] """The maximum number of context chunks to include.""" + hard_coded_queries: Optional[Iterable[HardCodedQuery]] + instructions: Optional[str] knowledge_base_id: Optional[str] @@ -146,6 +149,16 @@ class AdditionalMessage(TypedDict, total=False): thread_id: Required[str] +class HardCodedQuery(TypedDict, total=False): + query: Required[str] + + response: Required[str] + + context: Optional[List[str]] + + prompt: Optional[str] + + class ResponseValidationConfig(TypedDict, total=False): is_bad_threshold: Required[float] diff --git a/tests/api_resources/test_assistants.py b/tests/api_resources/test_assistants.py index 72441df..e5689d9 100644 --- a/tests/api_resources/test_assistants.py +++ b/tests/api_resources/test_assistants.py @@ -39,6 +39,14 @@ def test_method_create_with_all_params(self, client: Agility) -> None: name="name", codex_access_key="codex_access_key", context_limit=1, + hard_coded_queries=[ + { + "query": "query", + "response": "response", + "context": ["string"], + "prompt": "prompt", + } + ], instructions="instructions", logo_s3_key="logo_s3_key", logo_text="logo_text", @@ -147,6 +155,14 @@ def test_method_update_with_all_params(self, client: Agility) -> None: name="name", codex_access_key="codex_access_key", context_limit=1, + hard_coded_queries=[ + { + "query": "query", + "response": "response", + "context": ["string"], + "prompt": "prompt", + } + ], instructions="instructions", logo_s3_key="logo_s3_key", logo_text="logo_text", @@ -303,6 +319,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - name="name", codex_access_key="codex_access_key", context_limit=1, + hard_coded_queries=[ + { + "query": "query", + "response": "response", + "context": ["string"], + "prompt": "prompt", + } + ], instructions="instructions", logo_s3_key="logo_s3_key", logo_text="logo_text", @@ -411,6 +435,14 @@ async def test_method_update_with_all_params(self, async_client: AsyncAgility) - name="name", codex_access_key="codex_access_key", context_limit=1, + hard_coded_queries=[ + { + "query": "query", + "response": "response", + "context": ["string"], + "prompt": "prompt", + } + ], instructions="instructions", logo_s3_key="logo_s3_key", logo_text="logo_text", diff --git a/tests/api_resources/threads/test_runs.py b/tests/api_resources/threads/test_runs.py index cc18da6..9a0e881 100644 --- a/tests/api_resources/threads/test_runs.py +++ b/tests/api_resources/threads/test_runs.py @@ -74,6 +74,14 @@ def test_method_create_with_all_params(self, client: Agility) -> None: ], codex_access_key="codex_access_key", context_limit=1, + hard_coded_queries=[ + { + "query": "query", + "response": "response", + "context": ["string"], + "prompt": "prompt", + } + ], instructions="instructions", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", model="gpt-4o", @@ -279,6 +287,14 @@ def test_method_stream_with_all_params(self, client: Agility) -> None: ], codex_access_key="codex_access_key", context_limit=1, + hard_coded_queries=[ + { + "query": "query", + "response": "response", + "context": ["string"], + "prompt": "prompt", + } + ], instructions="instructions", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", model="gpt-4o", @@ -392,6 +408,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - ], codex_access_key="codex_access_key", context_limit=1, + hard_coded_queries=[ + { + "query": "query", + "response": "response", + "context": ["string"], + "prompt": "prompt", + } + ], instructions="instructions", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", model="gpt-4o", @@ -597,6 +621,14 @@ async def test_method_stream_with_all_params(self, async_client: AsyncAgility) - ], codex_access_key="codex_access_key", context_limit=1, + hard_coded_queries=[ + { + "query": "query", + "response": "response", + "context": ["string"], + "prompt": "prompt", + } + ], instructions="instructions", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", model="gpt-4o", From 3282a36a2a34457c83875806a9266701a0cb914e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 03:30:40 +0000 Subject: [PATCH 13/49] chore(internal): avoid errors for isinstance checks on proxies --- src/agility/_utils/_proxy.py | 5 ++++- tests/test_utils/test_proxy.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/agility/_utils/_proxy.py b/src/agility/_utils/_proxy.py index ffd883e..0f239a3 100644 --- a/src/agility/_utils/_proxy.py +++ b/src/agility/_utils/_proxy.py @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index 8da4dab..e2dc0cd 100644 --- a/tests/test_utils/test_proxy.py +++ b/tests/test_utils/test_proxy.py @@ -21,3 +21,14 @@ def test_recursive_proxy() -> None: assert dir(proxy) == [] assert type(proxy).__name__ == "RecursiveLazyProxy" assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" + + +def test_isinstance_does_not_error() -> None: + class AlwaysErrorProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + raise RuntimeError("Mocking missing dependency") + + proxy = AlwaysErrorProxy() + assert not isinstance(proxy, dict) + assert isinstance(proxy, LazyProxy) From 145581dd35326678cf185a24b58af284dc9b1ade Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 10 May 2025 03:06:19 +0000 Subject: [PATCH 14/49] fix(package): support direct resource imports --- src/agility/__init__.py | 5 +++++ src/agility/_utils/_resources_proxy.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/agility/_utils/_resources_proxy.py diff --git a/src/agility/__init__.py b/src/agility/__init__.py index ec35654..eaf7593 100644 --- a/src/agility/__init__.py +++ b/src/agility/__init__.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import typing as _t + from . import types from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path @@ -80,6 +82,9 @@ "DefaultAsyncHttpxClient", ] +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + _setup_logging() # Update the __module__ attribute for exported symbols so that diff --git a/src/agility/_utils/_resources_proxy.py b/src/agility/_utils/_resources_proxy.py new file mode 100644 index 0000000..0c2f171 --- /dev/null +++ b/src/agility/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `agility.resources` module. + + This is used so that we can lazily import `agility.resources` only when + needed *and* so that users can just import `agility` and reference `agility.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("agility.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() From 81ef208fbe5a15bc095c637cd8080fdc803d4c14 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 04:04:55 +0000 Subject: [PATCH 15/49] chore(ci): upload sdks to package manager --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ scripts/utils/upload-artifact.sh | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100755 scripts/utils/upload-artifact.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d561344..507f397 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,30 @@ jobs: - name: Run lints run: ./scripts/lint + upload: + if: github.repository == 'stainless-sdks/agility-python' + timeout-minutes: 10 + name: upload + permissions: + contents: read + id-token: write + runs-on: depot-ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Get GitHub OIDC Token + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + test: timeout-minutes: 10 name: test diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 0000000..39bf9c7 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -exuo pipefail + +RESPONSE=$(curl -X POST "$URL" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ + -H "Content-Type: application/gzip" \ + --data-binary @- "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/agility-python/$SHA'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi From 4baeb33b440d80fb633bb94d34e8db8217f6fb3e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 16:19:06 +0000 Subject: [PATCH 16/49] feat(api): api update --- .stats.yml | 4 ++-- src/agility/types/threads/message.py | 2 ++ src/agility/types/threads/message_create_params.py | 2 ++ src/agility/types/threads/run_create_params.py | 2 ++ src/agility/types/threads/run_stream_params.py | 2 ++ tests/api_resources/threads/test_messages.py | 2 ++ tests/api_resources/threads/test_runs.py | 4 ++++ 7 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6b56c5f..1f93b27 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-c93c84419ab3208e9f1e14e1a1bf8af3ea8fd3ec23cc79d3719e69f38dd005a0.yml -openapi_spec_hash: f2bbb5d3d108f00bf009ee7f588cacf4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-e485c1ea5e2b0d2c4aaf353ae7022eba3caf4b32d370587a6f9eda7397e5b1d9.yml +openapi_spec_hash: ea3e3f404d65bf1ea4c3558c063544d8 config_hash: 6d2156cfe279456cf3c35ba5c66be1c1 diff --git a/src/agility/types/threads/message.py b/src/agility/types/threads/message.py index 21cd6e2..b274637 100644 --- a/src/agility/types/threads/message.py +++ b/src/agility/types/threads/message.py @@ -102,6 +102,8 @@ class Metadata(BaseModel): is_expert_answer: Optional[bool] = None + original_llm_response: Optional[str] = None + scores: Optional[MetadataScores] = None trustworthiness_explanation: Optional[str] = None diff --git a/src/agility/types/threads/message_create_params.py b/src/agility/types/threads/message_create_params.py index e4848ad..9915a20 100644 --- a/src/agility/types/threads/message_create_params.py +++ b/src/agility/types/threads/message_create_params.py @@ -109,6 +109,8 @@ class Metadata(TypedDict, total=False): is_expert_answer: Optional[bool] + original_llm_response: Optional[str] + scores: Optional[MetadataScores] trustworthiness_explanation: Optional[str] diff --git a/src/agility/types/threads/run_create_params.py b/src/agility/types/threads/run_create_params.py index 8959daf..da95e46 100644 --- a/src/agility/types/threads/run_create_params.py +++ b/src/agility/types/threads/run_create_params.py @@ -132,6 +132,8 @@ class AdditionalMessageMetadata(TypedDict, total=False): is_expert_answer: Optional[bool] + original_llm_response: Optional[str] + scores: Optional[AdditionalMessageMetadataScores] trustworthiness_explanation: Optional[str] diff --git a/src/agility/types/threads/run_stream_params.py b/src/agility/types/threads/run_stream_params.py index 995ea64..a447f91 100644 --- a/src/agility/types/threads/run_stream_params.py +++ b/src/agility/types/threads/run_stream_params.py @@ -132,6 +132,8 @@ class AdditionalMessageMetadata(TypedDict, total=False): is_expert_answer: Optional[bool] + original_llm_response: Optional[str] + scores: Optional[AdditionalMessageMetadataScores] trustworthiness_explanation: Optional[str] diff --git a/tests/api_resources/threads/test_messages.py b/tests/api_resources/threads/test_messages.py index 9182e92..e280d84 100644 --- a/tests/api_resources/threads/test_messages.py +++ b/tests/api_resources/threads/test_messages.py @@ -37,6 +37,7 @@ def test_method_create_with_all_params(self, client: Agility) -> None: "citations": ["string"], "is_bad_response": True, "is_expert_answer": True, + "original_llm_response": "original_llm_response", "scores": { "context_sufficiency": { "is_bad": True, @@ -277,6 +278,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - "citations": ["string"], "is_bad_response": True, "is_expert_answer": True, + "original_llm_response": "original_llm_response", "scores": { "context_sufficiency": { "is_bad": True, diff --git a/tests/api_resources/threads/test_runs.py b/tests/api_resources/threads/test_runs.py index 9a0e881..2c1c390 100644 --- a/tests/api_resources/threads/test_runs.py +++ b/tests/api_resources/threads/test_runs.py @@ -38,6 +38,7 @@ def test_method_create_with_all_params(self, client: Agility) -> None: "citations": ["string"], "is_bad_response": True, "is_expert_answer": True, + "original_llm_response": "original_llm_response", "scores": { "context_sufficiency": { "is_bad": True, @@ -251,6 +252,7 @@ def test_method_stream_with_all_params(self, client: Agility) -> None: "citations": ["string"], "is_bad_response": True, "is_expert_answer": True, + "original_llm_response": "original_llm_response", "scores": { "context_sufficiency": { "is_bad": True, @@ -372,6 +374,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - "citations": ["string"], "is_bad_response": True, "is_expert_answer": True, + "original_llm_response": "original_llm_response", "scores": { "context_sufficiency": { "is_bad": True, @@ -585,6 +588,7 @@ async def test_method_stream_with_all_params(self, async_client: AsyncAgility) - "citations": ["string"], "is_bad_response": True, "is_expert_answer": True, + "original_llm_response": "original_llm_response", "scores": { "context_sufficiency": { "is_bad": True, From a1156ab735cc675ec918d30ab3d101ce3e35d648 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 03:21:19 +0000 Subject: [PATCH 17/49] chore(ci): fix installation instructions --- scripts/utils/upload-artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 39bf9c7..9ad0d45 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/agility-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/agility-python/$SHA'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From 8cac43f22039496c14c453a412e5c92cc81c2326 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 02:54:02 +0000 Subject: [PATCH 18/49] chore(internal): codegen related update --- scripts/utils/upload-artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 9ad0d45..145893f 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/agility-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install --pre 'https://pkg.stainless.com/s/agility-python/$SHA'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From 80169b17e95ad464483c1ae345e4eac8ed84282d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 02:33:30 +0000 Subject: [PATCH 19/49] chore(docs): grammar improvements --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 9c3e857..5ec6f47 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,11 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Agility please follow the respective company's security reporting guidelines. +or products provided by Agility, please follow the respective company's security reporting guidelines. ### Agility Terms and Policies -Please contact dev-feedback@agility.com for any questions or concerns regarding security of our services. +Please contact dev-feedback@agility.com for any questions or concerns regarding the security of our services. --- From aa736a52a6ba67b1c82c30f4b7c158cf55789622 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 02:23:21 +0000 Subject: [PATCH 20/49] chore(internal): codegen related update --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c67402b..aa87d63 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,11 @@ pip install git+ssh://git@github.com/stainless-sdks/agility-python.git The full API of this library can be found in [api.md](api.md). ```python +import os from agility import Agility client = Agility( + bearer_token=os.environ.get("BEARER_TOKEN"), # This is the default and can be omitted # or 'production' | 'dev' | 'local'; defaults to "production". environment="staging", ) @@ -52,10 +54,12 @@ so that your Bearer Token is not stored in source control. Simply import `AsyncAgility` instead of `Agility` and use `await` with each API call: ```python +import os import asyncio from agility import AsyncAgility client = AsyncAgility( + bearer_token=os.environ.get("BEARER_TOKEN"), # This is the default and can be omitted # or 'production' | 'dev' | 'local'; defaults to "production". environment="staging", ) From 5e03dcf42e325a14873bd76b43c74f7017287ef8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 03:30:07 +0000 Subject: [PATCH 21/49] fix(docs/api): remove references to nonexistent types --- api.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api.md b/api.md index 7b79cef..bdb6a1e 100644 --- a/api.md +++ b/api.md @@ -48,7 +48,7 @@ Methods: Types: ```python -from agility.types.knowledge_bases import Source, SourceStatusResponse, SourceSyncResponse +from agility.types.knowledge_bases import Source, SourceStatusResponse ``` Methods: @@ -59,7 +59,7 @@ Methods: - client.knowledge_bases.sources.list(knowledge_base_id, \*\*params) -> SyncMyOffsetPage[Source] - client.knowledge_bases.sources.delete(source_id, \*, knowledge_base_id) -> None - client.knowledge_bases.sources.status(source_id, \*, knowledge_base_id) -> SourceStatusResponse -- client.knowledge_bases.sources.sync(source_id, \*, knowledge_base_id) -> object +- client.knowledge_bases.sources.sync(source_id, \*, knowledge_base_id) -> object ### Documents @@ -134,7 +134,7 @@ Methods: Types: ```python -from agility.types.threads import Run, RunStreamResponse +from agility.types.threads import Run ``` Methods: @@ -142,7 +142,7 @@ Methods: - client.threads.runs.create(thread_id, \*\*params) -> Run - client.threads.runs.retrieve(run_id, \*, thread_id) -> Run - client.threads.runs.delete(run_id, \*, thread_id) -> None -- client.threads.runs.stream(thread_id, \*\*params) -> object +- client.threads.runs.stream(thread_id, \*\*params) -> object # Integrations From f839d18f840d337f03f63cd8bfc8ccdc61ebb235 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 02:25:16 +0000 Subject: [PATCH 22/49] chore(docs): remove reference to rye shell --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee8eff6..98455be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix From a007ecae5f5e90dee6e1544512c4be63abbefd51 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 02:37:30 +0000 Subject: [PATCH 23/49] chore(docs): remove unnecessary param examples --- README.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index aa87d63..44b64ff 100644 --- a/README.md +++ b/README.md @@ -161,18 +161,10 @@ client = Agility() knowledge_base_with_config = client.knowledge_bases.create( description="description", ingestion_pipeline_params={ - "curate": {"steps": {"foo": {"name": "remove_exact_duplicates.v0"}}}, - "curate_document_store": {"document_tags": {"foo": "string"}}, - "transform": { - "steps": { - "foo": { - "chunk_overlap": 0, - "chunk_size": 0, - "name": "splitters.recursive_character.v0", - } - } - }, - "vector_store": {"node_tags": {"foo": "string"}}, + "curate": {}, + "curate_document_store": {}, + "transform": {}, + "vector_store": {}, }, name="name", ) From 8b6e2323bd4c4154ab7568404eef8526ca93b92e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 03:40:52 +0000 Subject: [PATCH 24/49] feat(client): add follow_redirects request option --- src/agility/_base_client.py | 6 +++++ src/agility/_models.py | 2 ++ src/agility/_types.py | 2 ++ tests/test_client.py | 54 +++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/src/agility/_base_client.py b/src/agility/_base_client.py index a942d3f..2e5d064 100644 --- a/src/agility/_base_client.py +++ b/src/agility/_base_client.py @@ -960,6 +960,9 @@ def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1460,6 +1463,9 @@ async def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None diff --git a/src/agility/_models.py b/src/agility/_models.py index 798956f..4f21498 100644 --- a/src/agility/_models.py +++ b/src/agility/_models.py @@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): idempotency_key: str json_data: Body extra_json: AnyMapping + follow_redirects: bool @final @@ -750,6 +751,7 @@ class FinalRequestOptions(pydantic.BaseModel): files: Union[HttpxRequestFiles, None] = None idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. diff --git a/src/agility/_types.py b/src/agility/_types.py index caac259..d6c3a55 100644 --- a/src/agility/_types.py +++ b/src/agility/_types.py @@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool # Sentinel class used until PEP 0661 is accepted @@ -215,3 +216,4 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool diff --git a/tests/test_client.py b/tests/test_client.py index 68bc2b7..3bc763f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -846,6 +846,33 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects(self, respx_mock: MockRouter) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + self.client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" + class TestAsyncAgility: client = AsyncAgility(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -1701,3 +1728,30 @@ async def test_main() -> None: raise AssertionError("calling get_platform using asyncify resulted in a hung process") time.sleep(0.1) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + await self.client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" From ff1a52ef970a9b474bcd34a498acbda433452643 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 02:11:32 +0000 Subject: [PATCH 25/49] chore(tests): run tests in parallel --- pyproject.toml | 3 ++- requirements-dev.lock | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0ead3e5..9020bcf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ dev-dependencies = [ "importlib-metadata>=6.7.0", "rich>=13.7.1", "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -125,7 +126,7 @@ replacement = '[\1](https://github.com/stainless-sdks/agility-python/tree/main/\ [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" diff --git a/requirements-dev.lock b/requirements-dev.lock index ae49b73..d5840e2 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -30,6 +30,8 @@ distro==1.8.0 exceptiongroup==1.2.2 # via anyio # via pytest +execnet==2.1.1 + # via pytest-xdist filelock==3.12.4 # via virtualenv h11==0.14.0 @@ -72,7 +74,9 @@ pygments==2.18.0 pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio + # via pytest-xdist pytest-asyncio==0.24.0 +pytest-xdist==3.7.0 python-dateutil==2.8.2 # via time-machine pytz==2023.3.post1 From 6be6f597b64ed1853d5eb7844a8f80d57650ac2b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 02:35:55 +0000 Subject: [PATCH 26/49] fix(client): correctly parse binary response | stream --- src/agility/_base_client.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/agility/_base_client.py b/src/agility/_base_client.py index 2e5d064..7277e19 100644 --- a/src/agility/_base_client.py +++ b/src/agility/_base_client.py @@ -1071,7 +1071,14 @@ def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1574,7 +1581,14 @@ async def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") From c9bde314fcbf78df00e7982db8142fe67db80945 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:20:07 +0000 Subject: [PATCH 27/49] feat(api): api update --- .github/workflows/ci.yml | 4 ++ .stats.yml | 4 +- README.md | 4 +- tests/conftest.py | 2 + tests/test_client.py | 137 ++++++++++++++++++--------------------- 5 files changed, 74 insertions(+), 77 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 507f397..853bb32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: diff --git a/.stats.yml b/.stats.yml index 1f93b27..f06fa04 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-e485c1ea5e2b0d2c4aaf353ae7022eba3caf4b32d370587a6f9eda7397e5b1d9.yml -openapi_spec_hash: ea3e3f404d65bf1ea4c3558c063544d8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-ef930ce904d356e61e1d388237f529bb29aac3ea9a7023241be63edeedb8f61b.yml +openapi_spec_hash: 5df4fdbde09f44a6100698392d271ea5 config_hash: 6d2156cfe279456cf3c35ba5c66be1c1 diff --git a/README.md b/README.md index 44b64ff..c070c82 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Agility Python API library -[![PyPI version](https://img.shields.io/pypi/v/agility.svg)](https://pypi.org/project/agility/) +[![PyPI version]()](https://pypi.org/project/agility/) The Agility Python library provides convenient access to the Agility REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, @@ -244,7 +244,7 @@ client.with_options(max_retries=5).assistants.create( ### Timeouts By default requests time out after 1 minute. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python from agility import Agility diff --git a/tests/conftest.py b/tests/conftest.py index 4e30de8..e5a6bf7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os diff --git a/tests/test_client.py b/tests/test_client.py index 3bc763f..ea630ef 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,17 +23,16 @@ from agility import Agility, AsyncAgility, APIResponseValidationError from agility._types import Omit -from agility._utils import maybe_transform from agility._models import BaseModel, FinalRequestOptions -from agility._constants import RAW_RESPONSE_HEADER from agility._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError from agility._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + DefaultHttpxClient, + DefaultAsyncHttpxClient, make_request_options, ) -from agility.types.assistant_create_params import AssistantCreateParams from .utils import update_env @@ -709,52 +708,25 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("agility._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Agility) -> None: respx_mock.post("/api/assistants/").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.post( - "/api/assistants/", - body=cast( - object, - maybe_transform( - dict( - description="description", - knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - name="name", - ), - AssistantCreateParams, - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.assistants.with_streaming_response.create( + description="description", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name" + ).__enter__() assert _get_open_connections(self.client) == 0 @mock.patch("agility._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Agility) -> None: respx_mock.post("/api/assistants/").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.post( - "/api/assistants/", - body=cast( - object, - maybe_transform( - dict( - description="description", - knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - name="name", - ), - AssistantCreateParams, - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - + client.assistants.with_streaming_response.create( + description="description", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name" + ).__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -846,6 +818,28 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + @pytest.mark.respx(base_url=base_url) def test_follow_redirects(self, respx_mock: MockRouter) -> None: # Test that the default follow_redirects=True allows following redirects @@ -1544,52 +1538,27 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte @mock.patch("agility._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_timeout_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncAgility + ) -> None: respx_mock.post("/api/assistants/").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.post( - "/api/assistants/", - body=cast( - object, - maybe_transform( - dict( - description="description", - knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - name="name", - ), - AssistantCreateParams, - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + await async_client.assistants.with_streaming_response.create( + description="description", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name" + ).__aenter__() assert _get_open_connections(self.client) == 0 @mock.patch("agility._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncAgility) -> None: respx_mock.post("/api/assistants/").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.post( - "/api/assistants/", - body=cast( - object, - maybe_transform( - dict( - description="description", - knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - name="name", - ), - AssistantCreateParams, - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - + await async_client.assistants.with_streaming_response.create( + description="description", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name" + ).__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1729,6 +1698,28 @@ async def test_main() -> None: time.sleep(0.1) + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + @pytest.mark.respx(base_url=base_url) async def test_follow_redirects(self, respx_mock: MockRouter) -> None: # Test that the default follow_redirects=True allows following redirects From cea00063a745346c58d0596587685143a8419819 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:43:01 +0000 Subject: [PATCH 28/49] feat(api): manual updates --- .stats.yml | 4 +- api.md | 8 +- .../resources/assistants/assistants.py | 85 ++++++++++++++++ src/agility/types/__init__.py | 3 + ...ssistant_retrieve_run_metadata_response.py | 17 ++++ tests/api_resources/test_assistants.py | 97 +++++++++++++++++++ 6 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/agility/types/assistant_retrieve_run_metadata_response.py diff --git a/.stats.yml b/.stats.yml index f06fa04..070a938 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 42 +configured_endpoints: 43 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-ef930ce904d356e61e1d388237f529bb29aac3ea9a7023241be63edeedb8f61b.yml openapi_spec_hash: 5df4fdbde09f44a6100698392d271ea5 -config_hash: 6d2156cfe279456cf3c35ba5c66be1c1 +config_hash: 58f3e6b15392ca51b942e41597d56e7f diff --git a/api.md b/api.md index bdb6a1e..921bd9a 100644 --- a/api.md +++ b/api.md @@ -3,7 +3,12 @@ Types: ```python -from agility.types import Assistant, AssistantWithConfig, AssistantListResponse +from agility.types import ( + Assistant, + AssistantWithConfig, + AssistantListResponse, + AssistantRetrieveRunMetadataResponse, +) ``` Methods: @@ -13,6 +18,7 @@ Methods: - client.assistants.update(assistant_id, \*\*params) -> AssistantWithConfig - client.assistants.list(\*\*params) -> SyncMyOffsetPage[AssistantListResponse] - client.assistants.delete(assistant_id) -> None +- client.assistants.retrieve_run_metadata(run_id, \*, assistant_id) -> AssistantRetrieveRunMetadataResponse ## AccessKeys diff --git a/src/agility/resources/assistants/assistants.py b/src/agility/resources/assistants/assistants.py index 41829ad..319afb7 100644 --- a/src/agility/resources/assistants/assistants.py +++ b/src/agility/resources/assistants/assistants.py @@ -31,6 +31,7 @@ from ...types.assistant import Assistant from ...types.assistant_with_config import AssistantWithConfig from ...types.assistant_list_response import AssistantListResponse +from ...types.assistant_retrieve_run_metadata_response import AssistantRetrieveRunMetadataResponse __all__ = ["AssistantsResource", "AsyncAssistantsResource"] @@ -330,6 +331,42 @@ def delete( cast_to=NoneType, ) + def retrieve_run_metadata( + self, + run_id: str, + *, + assistant_id: str, + # 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AssistantRetrieveRunMetadataResponse: + """ + Get historical run metadata for an assistant. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not assistant_id: + raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._get( + f"/api/assistants/{assistant_id}/historical_run_metadata/{run_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AssistantRetrieveRunMetadataResponse, + ) + class AsyncAssistantsResource(AsyncAPIResource): @cached_property @@ -626,6 +663,42 @@ async def delete( cast_to=NoneType, ) + async def retrieve_run_metadata( + self, + run_id: str, + *, + assistant_id: str, + # 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AssistantRetrieveRunMetadataResponse: + """ + Get historical run metadata for an assistant. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not assistant_id: + raise ValueError(f"Expected a non-empty value for `assistant_id` but received {assistant_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return await self._get( + f"/api/assistants/{assistant_id}/historical_run_metadata/{run_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AssistantRetrieveRunMetadataResponse, + ) + class AssistantsResourceWithRawResponse: def __init__(self, assistants: AssistantsResource) -> None: @@ -646,6 +719,9 @@ def __init__(self, assistants: AssistantsResource) -> None: self.delete = to_raw_response_wrapper( assistants.delete, ) + self.retrieve_run_metadata = to_raw_response_wrapper( + assistants.retrieve_run_metadata, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithRawResponse: @@ -671,6 +747,9 @@ def __init__(self, assistants: AsyncAssistantsResource) -> None: self.delete = async_to_raw_response_wrapper( assistants.delete, ) + self.retrieve_run_metadata = async_to_raw_response_wrapper( + assistants.retrieve_run_metadata, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: @@ -696,6 +775,9 @@ def __init__(self, assistants: AssistantsResource) -> None: self.delete = to_streamed_response_wrapper( assistants.delete, ) + self.retrieve_run_metadata = to_streamed_response_wrapper( + assistants.retrieve_run_metadata, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithStreamingResponse: @@ -721,6 +803,9 @@ def __init__(self, assistants: AsyncAssistantsResource) -> None: self.delete = async_to_streamed_response_wrapper( assistants.delete, ) + self.retrieve_run_metadata = async_to_streamed_response_wrapper( + assistants.retrieve_run_metadata, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: diff --git a/src/agility/types/__init__.py b/src/agility/types/__init__.py index a9380ef..be25673 100644 --- a/src/agility/types/__init__.py +++ b/src/agility/types/__init__.py @@ -24,3 +24,6 @@ from .knowledge_base_list_response import KnowledgeBaseListResponse as KnowledgeBaseListResponse from .knowledge_base_update_params import KnowledgeBaseUpdateParams as KnowledgeBaseUpdateParams from .integration_retrieve_response import IntegrationRetrieveResponse as IntegrationRetrieveResponse +from .assistant_retrieve_run_metadata_response import ( + AssistantRetrieveRunMetadataResponse as AssistantRetrieveRunMetadataResponse, +) diff --git a/src/agility/types/assistant_retrieve_run_metadata_response.py b/src/agility/types/assistant_retrieve_run_metadata_response.py new file mode 100644 index 0000000..a58250f --- /dev/null +++ b/src/agility/types/assistant_retrieve_run_metadata_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["AssistantRetrieveRunMetadataResponse"] + + +class AssistantRetrieveRunMetadataResponse(BaseModel): + prompt: str + + query: str + + response: str + + context: Optional[List[str]] = None diff --git a/tests/api_resources/test_assistants.py b/tests/api_resources/test_assistants.py index e5689d9..c90d2ec 100644 --- a/tests/api_resources/test_assistants.py +++ b/tests/api_resources/test_assistants.py @@ -13,6 +13,7 @@ Assistant, AssistantWithConfig, AssistantListResponse, + AssistantRetrieveRunMetadataResponse, ) from agility.pagination import SyncMyOffsetPage, AsyncMyOffsetPage @@ -298,6 +299,54 @@ def test_path_params_delete(self, client: Agility) -> None: "", ) + @parametrize + def test_method_retrieve_run_metadata(self, client: Agility) -> None: + assistant = client.assistants.retrieve_run_metadata( + run_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assistant_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AssistantRetrieveRunMetadataResponse, assistant, path=["response"]) + + @parametrize + def test_raw_response_retrieve_run_metadata(self, client: Agility) -> None: + response = client.assistants.with_raw_response.retrieve_run_metadata( + run_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assistant_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + assistant = response.parse() + assert_matches_type(AssistantRetrieveRunMetadataResponse, assistant, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_run_metadata(self, client: Agility) -> None: + with client.assistants.with_streaming_response.retrieve_run_metadata( + run_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assistant_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = response.parse() + assert_matches_type(AssistantRetrieveRunMetadataResponse, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_run_metadata(self, client: Agility) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + client.assistants.with_raw_response.retrieve_run_metadata( + run_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assistant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.assistants.with_raw_response.retrieve_run_metadata( + run_id="", + assistant_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + class TestAsyncAssistants: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -577,3 +626,51 @@ async def test_path_params_delete(self, async_client: AsyncAgility) -> None: await async_client.assistants.with_raw_response.delete( "", ) + + @parametrize + async def test_method_retrieve_run_metadata(self, async_client: AsyncAgility) -> None: + assistant = await async_client.assistants.retrieve_run_metadata( + run_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assistant_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(AssistantRetrieveRunMetadataResponse, assistant, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_run_metadata(self, async_client: AsyncAgility) -> None: + response = await async_client.assistants.with_raw_response.retrieve_run_metadata( + run_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assistant_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + assistant = await response.parse() + assert_matches_type(AssistantRetrieveRunMetadataResponse, assistant, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_run_metadata(self, async_client: AsyncAgility) -> None: + async with async_client.assistants.with_streaming_response.retrieve_run_metadata( + run_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assistant_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = await response.parse() + assert_matches_type(AssistantRetrieveRunMetadataResponse, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_run_metadata(self, async_client: AsyncAgility) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `assistant_id` but received ''"): + await async_client.assistants.with_raw_response.retrieve_run_metadata( + run_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assistant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.assistants.with_raw_response.retrieve_run_metadata( + run_id="", + assistant_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) From 0d63906a55a1cb16dd004c5030826c214707f35c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 21 Jun 2025 03:25:06 +0000 Subject: [PATCH 29/49] chore: change publish docs url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c070c82..21b4a8c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ pip install git+ssh://git@github.com/stainless-sdks/agility-python.git ``` > [!NOTE] -> Once this package is [published to PyPI](https://app.stainless.com/docs/guides/publish), this will become: `pip install --pre agility` +> Once this package is [published to PyPI](https://www.stainless.com/docs/guides/publish), this will become: `pip install --pre agility` ## Usage From f21d4717bc0b04346824045bc5c534d6368efc39 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 21 Jun 2025 04:09:58 +0000 Subject: [PATCH 30/49] feat(client): add support for aiohttp --- README.md | 36 ++++++++++++++++ pyproject.toml | 2 + requirements-dev.lock | 27 ++++++++++++ requirements.lock | 27 ++++++++++++ src/agility/__init__.py | 3 +- src/agility/_base_client.py | 22 ++++++++++ .../assistants/test_access_keys.py | 4 +- .../integrations/test_available.py | 4 +- tests/api_resources/integrations/test_rbac.py | 4 +- .../knowledge_bases/sources/test_documents.py | 4 +- .../knowledge_bases/test_sources.py | 4 +- tests/api_resources/test_assistants.py | 4 +- tests/api_resources/test_integrations.py | 4 +- tests/api_resources/test_knowledge_bases.py | 4 +- tests/api_resources/test_threads.py | 4 +- tests/api_resources/test_users.py | 4 +- tests/api_resources/threads/test_messages.py | 4 +- tests/api_resources/threads/test_runs.py | 4 +- tests/api_resources/users/test_api_key.py | 4 +- tests/conftest.py | 43 ++++++++++++++++--- 20 files changed, 192 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 21b4a8c..262fe3a 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,42 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. +### With aiohttp + +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. + +You can enable this by installing `aiohttp`: + +```sh +# install from this staging repo +pip install 'agility[aiohttp] @ git+ssh://git@github.com/stainless-sdks/agility-python.git' +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from agility import DefaultAioHttpClient +from agility import AsyncAgility + + +async def main() -> None: + async with AsyncAgility( + bearer_token=os.environ.get("BEARER_TOKEN"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + assistant = await client.assistants.create( + description="description", + knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + print(assistant.id) + + +asyncio.run(main()) +``` + ## Using types Nested 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: diff --git a/pyproject.toml b/pyproject.toml index 9020bcf..b3fc393 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ classifiers = [ Homepage = "https://github.com/stainless-sdks/agility-python" Repository = "https://github.com/stainless-sdks/agility-python" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index d5840e2..50a7dcc 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,6 +10,13 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via agility + # via httpx-aiohttp +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 @@ -17,6 +24,10 @@ anyio==4.4.0 # via httpx argcomplete==3.1.2 # via nox +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -34,16 +45,23 @@ execnet==2.1.1 # via pytest-xdist filelock==3.12.4 # via virtualenv +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 # via agility + # via httpx-aiohttp # via respx +httpx-aiohttp==0.1.6 + # via agility idna==3.4 # via anyio # via httpx + # via yarl importlib-metadata==7.0.0 iniconfig==2.0.0 # via pytest @@ -51,6 +69,9 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py +multidict==6.4.4 + # via aiohttp + # via yarl mypy==1.14.1 mypy-extensions==1.0.0 # via mypy @@ -65,6 +86,9 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via agility pydantic-core==2.27.1 @@ -98,11 +122,14 @@ tomli==2.0.2 typing-extensions==4.12.2 # via agility # via anyio + # via multidict # via mypy # via pydantic # via pydantic-core # via pyright virtualenv==20.24.5 # via nox +yarl==1.20.0 + # via aiohttp zipp==3.17.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 8f0aee0..ad56a5d 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,11 +10,22 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via agility + # via httpx-aiohttp +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 # via agility # via httpx +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -22,15 +33,28 @@ distro==1.8.0 # via agility exceptiongroup==1.2.2 # via anyio +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 # via agility + # via httpx-aiohttp +httpx-aiohttp==0.1.6 + # via agility idna==3.4 # via anyio # via httpx + # via yarl +multidict==6.4.4 + # via aiohttp + # via yarl +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via agility pydantic-core==2.27.1 @@ -41,5 +65,8 @@ sniffio==1.3.0 typing-extensions==4.12.2 # via agility # via anyio + # via multidict # via pydantic # via pydantic-core +yarl==1.20.0 + # via aiohttp diff --git a/src/agility/__init__.py b/src/agility/__init__.py index eaf7593..9a1b783 100644 --- a/src/agility/__init__.py +++ b/src/agility/__init__.py @@ -37,7 +37,7 @@ UnprocessableEntityError, APIResponseValidationError, ) -from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging __all__ = [ @@ -80,6 +80,7 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] if not _t.TYPE_CHECKING: diff --git a/src/agility/_base_client.py b/src/agility/_base_client.py index 7277e19..b8d65b7 100644 --- a/src/agility/_base_client.py +++ b/src/agility/_base_client.py @@ -1289,6 +1289,24 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + if TYPE_CHECKING: DefaultAsyncHttpxClient = httpx.AsyncClient """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK @@ -1297,8 +1315,12 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): diff --git a/tests/api_resources/assistants/test_access_keys.py b/tests/api_resources/assistants/test_access_keys.py index 00774cb..0a9f6ea 100644 --- a/tests/api_resources/assistants/test_access_keys.py +++ b/tests/api_resources/assistants/test_access_keys.py @@ -120,7 +120,9 @@ def test_path_params_list(self, client: Agility) -> None: class TestAsyncAccessKeys: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/integrations/test_available.py b/tests/api_resources/integrations/test_available.py index 36a89f0..5e250a9 100644 --- a/tests/api_resources/integrations/test_available.py +++ b/tests/api_resources/integrations/test_available.py @@ -44,7 +44,9 @@ def test_streaming_response_list(self, client: Agility) -> None: class TestAsyncAvailable: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/integrations/test_rbac.py b/tests/api_resources/integrations/test_rbac.py index bb00ede..79a9e80 100644 --- a/tests/api_resources/integrations/test_rbac.py +++ b/tests/api_resources/integrations/test_rbac.py @@ -57,7 +57,9 @@ def test_path_params_verify(self, client: Agility) -> None: class TestAsyncRbac: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_verify(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/knowledge_bases/sources/test_documents.py b/tests/api_resources/knowledge_bases/sources/test_documents.py index 507f61a..a84493d 100644 --- a/tests/api_resources/knowledge_bases/sources/test_documents.py +++ b/tests/api_resources/knowledge_bases/sources/test_documents.py @@ -138,7 +138,9 @@ def test_path_params_list(self, client: Agility) -> None: class TestAsyncDocuments: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/knowledge_bases/test_sources.py b/tests/api_resources/knowledge_bases/test_sources.py index eb1ba6c..6b20e92 100644 --- a/tests/api_resources/knowledge_bases/test_sources.py +++ b/tests/api_resources/knowledge_bases/test_sources.py @@ -499,7 +499,9 @@ def test_path_params_sync(self, client: Agility) -> None: class TestAsyncSources: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/test_assistants.py b/tests/api_resources/test_assistants.py index c90d2ec..8af94bd 100644 --- a/tests/api_resources/test_assistants.py +++ b/tests/api_resources/test_assistants.py @@ -349,7 +349,9 @@ def test_path_params_retrieve_run_metadata(self, client: Agility) -> None: class TestAsyncAssistants: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/test_integrations.py b/tests/api_resources/test_integrations.py index 82d4400..8245159 100644 --- a/tests/api_resources/test_integrations.py +++ b/tests/api_resources/test_integrations.py @@ -197,7 +197,9 @@ def test_path_params_delete(self, client: Agility) -> None: class TestAsyncIntegrations: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/test_knowledge_bases.py b/tests/api_resources/test_knowledge_bases.py index 1f7410f..8b85392 100644 --- a/tests/api_resources/test_knowledge_bases.py +++ b/tests/api_resources/test_knowledge_bases.py @@ -254,7 +254,9 @@ def test_path_params_delete(self, client: Agility) -> None: class TestAsyncKnowledgeBases: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/test_threads.py b/tests/api_resources/test_threads.py index c83c69a..223d1d2 100644 --- a/tests/api_resources/test_threads.py +++ b/tests/api_resources/test_threads.py @@ -154,7 +154,9 @@ def test_path_params_delete(self, client: Agility) -> None: class TestAsyncThreads: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py index bfcbc6e..bd09bf2 100644 --- a/tests/api_resources/test_users.py +++ b/tests/api_resources/test_users.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: Agility) -> None: class TestAsyncUsers: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/threads/test_messages.py b/tests/api_resources/threads/test_messages.py index e280d84..e840ce8 100644 --- a/tests/api_resources/threads/test_messages.py +++ b/tests/api_resources/threads/test_messages.py @@ -257,7 +257,9 @@ def test_path_params_delete(self, client: Agility) -> None: class TestAsyncMessages: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/threads/test_runs.py b/tests/api_resources/threads/test_runs.py index 2c1c390..e6f8098 100644 --- a/tests/api_resources/threads/test_runs.py +++ b/tests/api_resources/threads/test_runs.py @@ -351,7 +351,9 @@ def test_path_params_stream(self, client: Agility) -> None: class TestAsyncRuns: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncAgility) -> None: diff --git a/tests/api_resources/users/test_api_key.py b/tests/api_resources/users/test_api_key.py index 6440582..0cfacb1 100644 --- a/tests/api_resources/users/test_api_key.py +++ b/tests/api_resources/users/test_api_key.py @@ -95,7 +95,9 @@ def test_path_params_refresh(self, client: Agility) -> None: class TestAsyncAPIKey: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncAgility) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index e5a6bf7..0c65545 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,10 +6,12 @@ import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from agility import Agility, AsyncAgility +from agility import Agility, AsyncAgility, DefaultAioHttpClient +from agility._utils import is_dict if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] @@ -27,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -45,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[Agility]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncAgility]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncAgility(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") + + async with AsyncAgility( + base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client + ) as client: yield client From aa78bfb848ca21d7d79416990a3289623c4088a0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 04:17:22 +0000 Subject: [PATCH 31/49] chore(tests): skip some failing tests on the latest python versions --- tests/test_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index ea630ef..c7e299a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -191,6 +191,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") @@ -1003,6 +1004,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") From 376dead6ec39a9d1dcd633d952ef3bffe1b46066 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:20:50 +0000 Subject: [PATCH 32/49] feat(api): api update --- .stats.yml | 4 ++-- src/agility/types/knowledge_bases/source.py | 2 ++ src/agility/types/knowledge_bases/source_create_params.py | 2 ++ src/agility/types/knowledge_bases/source_update_params.py | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 070a938..d95ddba 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-ef930ce904d356e61e1d388237f529bb29aac3ea9a7023241be63edeedb8f61b.yml -openapi_spec_hash: 5df4fdbde09f44a6100698392d271ea5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-77d86cb6a952a00530c4aade34a01d7fcc10076e796272605a534131ed8f27c5.yml +openapi_spec_hash: 94ebe05bbe07723b3624652d3230a8fd config_hash: 58f3e6b15392ca51b942e41597d56e7f diff --git a/src/agility/types/knowledge_bases/source.py b/src/agility/types/knowledge_bases/source.py index 77bb40e..37c0c17 100644 --- a/src/agility/types/knowledge_bases/source.py +++ b/src/agility/types/knowledge_bases/source.py @@ -80,6 +80,8 @@ class SourceParamsNotionV0Params(BaseModel): limit: Optional[int] = None + max_age_days: Optional[int] = None + name: Optional[Literal["notion_v0"]] = None diff --git a/src/agility/types/knowledge_bases/source_create_params.py b/src/agility/types/knowledge_bases/source_create_params.py index 5908821..2ffdf15 100644 --- a/src/agility/types/knowledge_bases/source_create_params.py +++ b/src/agility/types/knowledge_bases/source_create_params.py @@ -87,6 +87,8 @@ class SourceParamsNotionV0Params(TypedDict, total=False): limit: Optional[int] + max_age_days: int + name: Literal["notion_v0"] diff --git a/src/agility/types/knowledge_bases/source_update_params.py b/src/agility/types/knowledge_bases/source_update_params.py index 9d582e0..dbaf14a 100644 --- a/src/agility/types/knowledge_bases/source_update_params.py +++ b/src/agility/types/knowledge_bases/source_update_params.py @@ -89,6 +89,8 @@ class SourceParamsNotionV0Params(TypedDict, total=False): limit: Optional[int] + max_age_days: int + name: Literal["notion_v0"] From 2cf77fe50a140e89a4a20bf6e4be7f82abf19e9f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 22:20:27 +0000 Subject: [PATCH 33/49] feat(api): api update --- .stats.yml | 4 +-- .../resources/assistants/assistants.py | 8 ------ src/agility/resources/threads/runs.py | 8 ------ src/agility/types/assistant_create_params.py | 28 ++----------------- src/agility/types/assistant_list_response.py | 28 ++----------------- src/agility/types/assistant_update_params.py | 28 ++----------------- src/agility/types/assistant_with_config.py | 28 ++----------------- src/agility/types/threads/run.py | 21 ++------------ .../types/threads/run_create_params.py | 22 ++------------- .../types/threads/run_stream_params.py | 22 ++------------- tests/api_resources/test_assistants.py | 24 ---------------- tests/api_resources/threads/test_runs.py | 24 ---------------- 12 files changed, 21 insertions(+), 224 deletions(-) diff --git a/.stats.yml b/.stats.yml index d95ddba..4a60ed4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-77d86cb6a952a00530c4aade34a01d7fcc10076e796272605a534131ed8f27c5.yml -openapi_spec_hash: 94ebe05bbe07723b3624652d3230a8fd +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-90629fd36897557fbf4f7ea3000519d0a6afeb290aed8211ead5e381cd1e25df.yml +openapi_spec_hash: 5a91c2a2505b4fba3a9ecdd03fcddf20 config_hash: 58f3e6b15392ca51b942e41597d56e7f diff --git a/src/agility/resources/assistants/assistants.py b/src/agility/resources/assistants/assistants.py index 319afb7..9aebc23 100644 --- a/src/agility/resources/assistants/assistants.py +++ b/src/agility/resources/assistants/assistants.py @@ -76,7 +76,6 @@ def create( response_validation_config: Optional[Iterable[assistant_create_params.ResponseValidationConfig]] | NotGiven = NOT_GIVEN, suggested_questions: List[str] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[assistant_create_params.Tool]] | NotGiven = NOT_GIVEN, url_slug: Optional[str] | 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. @@ -127,7 +126,6 @@ def create( "model": model, "response_validation_config": response_validation_config, "suggested_questions": suggested_questions, - "tools": tools, "url_slug": url_slug, }, assistant_create_params.AssistantCreateParams, @@ -189,7 +187,6 @@ def update( response_validation_config: Optional[Iterable[assistant_update_params.ResponseValidationConfig]] | NotGiven = NOT_GIVEN, suggested_questions: List[str] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[assistant_update_params.Tool]] | NotGiven = NOT_GIVEN, url_slug: Optional[str] | 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. @@ -243,7 +240,6 @@ def update( "model": model, "response_validation_config": response_validation_config, "suggested_questions": suggested_questions, - "tools": tools, "url_slug": url_slug, }, assistant_update_params.AssistantUpdateParams, @@ -408,7 +404,6 @@ async def create( response_validation_config: Optional[Iterable[assistant_create_params.ResponseValidationConfig]] | NotGiven = NOT_GIVEN, suggested_questions: List[str] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[assistant_create_params.Tool]] | NotGiven = NOT_GIVEN, url_slug: Optional[str] | 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. @@ -459,7 +454,6 @@ async def create( "model": model, "response_validation_config": response_validation_config, "suggested_questions": suggested_questions, - "tools": tools, "url_slug": url_slug, }, assistant_create_params.AssistantCreateParams, @@ -521,7 +515,6 @@ async def update( response_validation_config: Optional[Iterable[assistant_update_params.ResponseValidationConfig]] | NotGiven = NOT_GIVEN, suggested_questions: List[str] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[assistant_update_params.Tool]] | NotGiven = NOT_GIVEN, url_slug: Optional[str] | 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. @@ -575,7 +568,6 @@ async def update( "model": model, "response_validation_config": response_validation_config, "suggested_questions": suggested_questions, - "tools": tools, "url_slug": url_slug, }, assistant_update_params.AssistantUpdateParams, diff --git a/src/agility/resources/threads/runs.py b/src/agility/resources/threads/runs.py index 01f1169..4e0cda1 100644 --- a/src/agility/resources/threads/runs.py +++ b/src/agility/resources/threads/runs.py @@ -59,7 +59,6 @@ def create( model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, response_validation_config: Optional[Iterable[run_create_params.ResponseValidationConfig]] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[run_create_params.Tool]] | 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. extra_headers: Headers | None = None, @@ -97,7 +96,6 @@ def create( "knowledge_base_id": knowledge_base_id, "model": model, "response_validation_config": response_validation_config, - "tools": tools, }, run_create_params.RunCreateParams, ), @@ -195,7 +193,6 @@ def stream( model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, response_validation_config: Optional[Iterable[run_stream_params.ResponseValidationConfig]] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[run_stream_params.Tool]] | 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. extra_headers: Headers | None = None, @@ -233,7 +230,6 @@ def stream( "knowledge_base_id": knowledge_base_id, "model": model, "response_validation_config": response_validation_config, - "tools": tools, }, run_stream_params.RunStreamParams, ), @@ -279,7 +275,6 @@ async def create( model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, response_validation_config: Optional[Iterable[run_create_params.ResponseValidationConfig]] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[run_create_params.Tool]] | 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. extra_headers: Headers | None = None, @@ -317,7 +312,6 @@ async def create( "knowledge_base_id": knowledge_base_id, "model": model, "response_validation_config": response_validation_config, - "tools": tools, }, run_create_params.RunCreateParams, ), @@ -415,7 +409,6 @@ async def stream( model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, response_validation_config: Optional[Iterable[run_stream_params.ResponseValidationConfig]] | NotGiven = NOT_GIVEN, - tools: Optional[Iterable[run_stream_params.Tool]] | 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. extra_headers: Headers | None = None, @@ -453,7 +446,6 @@ async def stream( "knowledge_base_id": knowledge_base_id, "model": model, "response_validation_config": response_validation_config, - "tools": tools, }, run_stream_params.RunStreamParams, ), diff --git a/src/agility/types/assistant_create_params.py b/src/agility/types/assistant_create_params.py index b9310b8..2aa8ed4 100644 --- a/src/agility/types/assistant_create_params.py +++ b/src/agility/types/assistant_create_params.py @@ -2,17 +2,10 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing import List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict -__all__ = [ - "AssistantCreateParams", - "HardCodedQuery", - "ResponseValidationConfig", - "Tool", - "ToolCodexV0Tool", - "ToolNoOpTool", -] +__all__ = ["AssistantCreateParams", "HardCodedQuery", "ResponseValidationConfig"] class AssistantCreateParams(TypedDict, total=False): @@ -46,8 +39,6 @@ class AssistantCreateParams(TypedDict, total=False): suggested_questions: List[str] """A list of suggested questions that can be asked to the assistant""" - tools: Optional[Iterable[Tool]] - url_slug: Optional[str] """Optional URL suffix - unique identifier for the assistant's endpoint""" @@ -68,16 +59,3 @@ class ResponseValidationConfig(TypedDict, total=False): name: Required[ Literal["trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease"] ] - - -class ToolCodexV0Tool(TypedDict, total=False): - access_key: Required[str] - - type: Literal["codex_v0"] - - -class ToolNoOpTool(TypedDict, total=False): - type: Literal["noop"] - - -Tool: TypeAlias = Union[ToolCodexV0Tool, ToolNoOpTool] diff --git a/src/agility/types/assistant_list_response.py b/src/agility/types/assistant_list_response.py index fb54273..5316f80 100644 --- a/src/agility/types/assistant_list_response.py +++ b/src/agility/types/assistant_list_response.py @@ -1,19 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Union, Optional +from typing import List, Optional from datetime import datetime -from typing_extensions import Literal, TypeAlias +from typing_extensions import Literal from .._models import BaseModel -__all__ = [ - "AssistantListResponse", - "HardCodedQuery", - "ResponseValidationConfig", - "Tool", - "ToolCodexV0Tool", - "ToolNoOpTool", -] +__all__ = ["AssistantListResponse", "HardCodedQuery", "ResponseValidationConfig"] class HardCodedQuery(BaseModel): @@ -34,19 +27,6 @@ class ResponseValidationConfig(BaseModel): ] -class ToolCodexV0Tool(BaseModel): - access_key: str - - type: Optional[Literal["codex_v0"]] = None - - -class ToolNoOpTool(BaseModel): - type: Optional[Literal["noop"]] = None - - -Tool: TypeAlias = Union[ToolCodexV0Tool, ToolNoOpTool] - - class AssistantListResponse(BaseModel): id: str @@ -88,7 +68,5 @@ class AssistantListResponse(BaseModel): suggested_questions: Optional[List[str]] = None """A list of suggested questions that can be asked to the assistant""" - tools: Optional[List[Tool]] = None - url_slug: Optional[str] = None """Optional URL suffix - unique identifier for the assistant's endpoint""" diff --git a/src/agility/types/assistant_update_params.py b/src/agility/types/assistant_update_params.py index 06346c2..cabe3ba 100644 --- a/src/agility/types/assistant_update_params.py +++ b/src/agility/types/assistant_update_params.py @@ -2,17 +2,10 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing import List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict -__all__ = [ - "AssistantUpdateParams", - "HardCodedQuery", - "ResponseValidationConfig", - "Tool", - "ToolCodexV0Tool", - "ToolNoOpTool", -] +__all__ = ["AssistantUpdateParams", "HardCodedQuery", "ResponseValidationConfig"] class AssistantUpdateParams(TypedDict, total=False): @@ -48,8 +41,6 @@ class AssistantUpdateParams(TypedDict, total=False): suggested_questions: List[str] """A list of suggested questions that can be asked to the assistant""" - tools: Optional[Iterable[Tool]] - url_slug: Optional[str] """Optional URL suffix - unique identifier for the assistant's endpoint""" @@ -70,16 +61,3 @@ class ResponseValidationConfig(TypedDict, total=False): name: Required[ Literal["trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease"] ] - - -class ToolCodexV0Tool(TypedDict, total=False): - access_key: Required[str] - - type: Literal["codex_v0"] - - -class ToolNoOpTool(TypedDict, total=False): - type: Literal["noop"] - - -Tool: TypeAlias = Union[ToolCodexV0Tool, ToolNoOpTool] diff --git a/src/agility/types/assistant_with_config.py b/src/agility/types/assistant_with_config.py index 1886aa6..4ff4c05 100644 --- a/src/agility/types/assistant_with_config.py +++ b/src/agility/types/assistant_with_config.py @@ -1,19 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Union, Optional +from typing import List, Optional from datetime import datetime -from typing_extensions import Literal, TypeAlias +from typing_extensions import Literal from .._models import BaseModel -__all__ = [ - "AssistantWithConfig", - "HardCodedQuery", - "ResponseValidationConfig", - "Tool", - "ToolCodexV0Tool", - "ToolNoOpTool", -] +__all__ = ["AssistantWithConfig", "HardCodedQuery", "ResponseValidationConfig"] class HardCodedQuery(BaseModel): @@ -34,19 +27,6 @@ class ResponseValidationConfig(BaseModel): ] -class ToolCodexV0Tool(BaseModel): - access_key: str - - type: Optional[Literal["codex_v0"]] = None - - -class ToolNoOpTool(BaseModel): - type: Optional[Literal["noop"]] = None - - -Tool: TypeAlias = Union[ToolCodexV0Tool, ToolNoOpTool] - - class AssistantWithConfig(BaseModel): id: str @@ -86,7 +66,5 @@ class AssistantWithConfig(BaseModel): suggested_questions: Optional[List[str]] = None """A list of suggested questions that can be asked to the assistant""" - tools: Optional[List[Tool]] = None - url_slug: Optional[str] = None """Optional URL suffix - unique identifier for the assistant's endpoint""" diff --git a/src/agility/types/threads/run.py b/src/agility/types/threads/run.py index deb977b..94680c4 100644 --- a/src/agility/types/threads/run.py +++ b/src/agility/types/threads/run.py @@ -1,12 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Union, Optional +from typing import List, Optional from datetime import datetime -from typing_extensions import Literal, TypeAlias +from typing_extensions import Literal from ..._models import BaseModel -__all__ = ["Run", "HardCodedQuery", "ResponseValidationConfig", "Tool", "ToolCodexV0Tool", "ToolNoOpTool", "Usage"] +__all__ = ["Run", "HardCodedQuery", "ResponseValidationConfig", "Usage"] class HardCodedQuery(BaseModel): @@ -27,19 +27,6 @@ class ResponseValidationConfig(BaseModel): ] -class ToolCodexV0Tool(BaseModel): - access_key: str - - type: Optional[Literal["codex_v0"]] = None - - -class ToolNoOpTool(BaseModel): - type: Optional[Literal["noop"]] = None - - -Tool: TypeAlias = Union[ToolCodexV0Tool, ToolNoOpTool] - - class Usage(BaseModel): completion_tokens: int @@ -82,6 +69,4 @@ class Run(BaseModel): response_validation_config: Optional[List[ResponseValidationConfig]] = None - tools: Optional[List[Tool]] = None - usage: Optional[Usage] = None diff --git a/src/agility/types/threads/run_create_params.py b/src/agility/types/threads/run_create_params.py index da95e46..cfdb756 100644 --- a/src/agility/types/threads/run_create_params.py +++ b/src/agility/types/threads/run_create_params.py @@ -2,8 +2,8 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing import List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict __all__ = [ "RunCreateParams", @@ -22,9 +22,6 @@ "AdditionalMessageMetadataScoresTrustworthinessLog", "HardCodedQuery", "ResponseValidationConfig", - "Tool", - "ToolCodexV0Tool", - "ToolNoOpTool", ] @@ -50,8 +47,6 @@ class RunCreateParams(TypedDict, total=False): response_validation_config: Optional[Iterable[ResponseValidationConfig]] - tools: Optional[Iterable[Tool]] - class AdditionalMessageMetadataScoresContextSufficiencyLog(TypedDict, total=False): explanation: Optional[str] @@ -167,16 +162,3 @@ class ResponseValidationConfig(TypedDict, total=False): name: Required[ Literal["trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease"] ] - - -class ToolCodexV0Tool(TypedDict, total=False): - access_key: Required[str] - - type: Literal["codex_v0"] - - -class ToolNoOpTool(TypedDict, total=False): - type: Literal["noop"] - - -Tool: TypeAlias = Union[ToolCodexV0Tool, ToolNoOpTool] diff --git a/src/agility/types/threads/run_stream_params.py b/src/agility/types/threads/run_stream_params.py index a447f91..099e90a 100644 --- a/src/agility/types/threads/run_stream_params.py +++ b/src/agility/types/threads/run_stream_params.py @@ -2,8 +2,8 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing import List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict __all__ = [ "RunStreamParams", @@ -22,9 +22,6 @@ "AdditionalMessageMetadataScoresTrustworthinessLog", "HardCodedQuery", "ResponseValidationConfig", - "Tool", - "ToolCodexV0Tool", - "ToolNoOpTool", ] @@ -50,8 +47,6 @@ class RunStreamParams(TypedDict, total=False): response_validation_config: Optional[Iterable[ResponseValidationConfig]] - tools: Optional[Iterable[Tool]] - class AdditionalMessageMetadataScoresContextSufficiencyLog(TypedDict, total=False): explanation: Optional[str] @@ -167,16 +162,3 @@ class ResponseValidationConfig(TypedDict, total=False): name: Required[ Literal["trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease"] ] - - -class ToolCodexV0Tool(TypedDict, total=False): - access_key: Required[str] - - type: Literal["codex_v0"] - - -class ToolNoOpTool(TypedDict, total=False): - type: Literal["noop"] - - -Tool: TypeAlias = Union[ToolCodexV0Tool, ToolNoOpTool] diff --git a/tests/api_resources/test_assistants.py b/tests/api_resources/test_assistants.py index 8af94bd..d14a8cf 100644 --- a/tests/api_resources/test_assistants.py +++ b/tests/api_resources/test_assistants.py @@ -59,12 +59,6 @@ def test_method_create_with_all_params(self, client: Agility) -> None: } ], suggested_questions=["string"], - tools=[ - { - "access_key": "access_key", - "type": "codex_v0", - } - ], url_slug="url_slug", ) assert_matches_type(Assistant, assistant, path=["response"]) @@ -175,12 +169,6 @@ def test_method_update_with_all_params(self, client: Agility) -> None: } ], suggested_questions=["string"], - tools=[ - { - "access_key": "access_key", - "type": "codex_v0", - } - ], url_slug="url_slug", ) assert_matches_type(AssistantWithConfig, assistant, path=["response"]) @@ -389,12 +377,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - } ], suggested_questions=["string"], - tools=[ - { - "access_key": "access_key", - "type": "codex_v0", - } - ], url_slug="url_slug", ) assert_matches_type(Assistant, assistant, path=["response"]) @@ -505,12 +487,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncAgility) - } ], suggested_questions=["string"], - tools=[ - { - "access_key": "access_key", - "type": "codex_v0", - } - ], url_slug="url_slug", ) assert_matches_type(AssistantWithConfig, assistant, path=["response"]) diff --git a/tests/api_resources/threads/test_runs.py b/tests/api_resources/threads/test_runs.py index e6f8098..f572e34 100644 --- a/tests/api_resources/threads/test_runs.py +++ b/tests/api_resources/threads/test_runs.py @@ -92,12 +92,6 @@ def test_method_create_with_all_params(self, client: Agility) -> None: "name": "trustworthiness", } ], - tools=[ - { - "access_key": "access_key", - "type": "codex_v0", - } - ], ) assert_matches_type(Run, run, path=["response"]) @@ -306,12 +300,6 @@ def test_method_stream_with_all_params(self, client: Agility) -> None: "name": "trustworthiness", } ], - tools=[ - { - "access_key": "access_key", - "type": "codex_v0", - } - ], ) assert_matches_type(object, run, path=["response"]) @@ -430,12 +418,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - "name": "trustworthiness", } ], - tools=[ - { - "access_key": "access_key", - "type": "codex_v0", - } - ], ) assert_matches_type(Run, run, path=["response"]) @@ -644,12 +626,6 @@ async def test_method_stream_with_all_params(self, async_client: AsyncAgility) - "name": "trustworthiness", } ], - tools=[ - { - "access_key": "access_key", - "type": "codex_v0", - } - ], ) assert_matches_type(object, run, path=["response"]) From fa28a626519d6a1723635fd9bfe63827ef50b425 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:01:10 +0000 Subject: [PATCH 34/49] chore(ci): only run for pushes and fork pull requests --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 853bb32..b1c8242 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/agility-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -42,6 +43,7 @@ jobs: contents: read id-token: write runs-on: depot-ubuntu-24.04 + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -62,6 +64,7 @@ jobs: timeout-minutes: 10 name: test runs-on: ${{ github.repository == 'stainless-sdks/agility-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 From 0da8bbec80718c0f46bcd36fdc17170b843af8d3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 02:44:19 +0000 Subject: [PATCH 35/49] fix(ci): correct conditional --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1c8242..fb4efe9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,14 +36,13 @@ jobs: run: ./scripts/lint upload: - if: github.repository == 'stainless-sdks/agility-python' + if: github.repository == 'stainless-sdks/agility-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 name: upload permissions: contents: read id-token: write runs-on: depot-ubuntu-24.04 - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 From 8a0a0a96e7919cd8bae433734c7c4bb1c744f45d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 05:30:27 +0000 Subject: [PATCH 36/49] chore(ci): change upload type --- .github/workflows/ci.yml | 18 ++++++++++++++++-- scripts/utils/upload-artifact.sh | 12 +++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb4efe9..27e937e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,10 +35,10 @@ jobs: - name: Run lints run: ./scripts/lint - upload: + build: if: github.repository == 'stainless-sdks/agility-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 - name: upload + name: build permissions: contents: read id-token: write @@ -46,6 +46,20 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Run build + run: rye build + - name: Get GitHub OIDC Token id: github-oidc uses: actions/github-script@v6 diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 145893f..e2b5a43 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash set -exuo pipefail -RESPONSE=$(curl -X POST "$URL" \ +FILENAME=$(basename dist/*.whl) + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ -H "Authorization: Bearer $AUTH" \ -H "Content-Type: application/json") @@ -12,13 +14,13 @@ if [[ "$SIGNED_URL" == "null" ]]; then exit 1 fi -UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ - -H "Content-Type: application/gzip" \ - --data-binary @- "$SIGNED_URL" 2>&1) +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: binary/octet-stream" \ + --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1) if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: pip install --pre 'https://pkg.stainless.com/s/agility-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/agility-python/$SHA/$FILENAME'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From 14c6bd366d04e2c5b3562efa147a199de6bcccae Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:21:06 +0000 Subject: [PATCH 37/49] feat(api): api update --- .stats.yml | 4 +- requirements-dev.lock | 2 +- requirements.lock | 2 +- .../resources/assistants/assistants.py | 12 -- src/agility/resources/threads/runs.py | 12 -- src/agility/types/assistant_create_params.py | 12 +- src/agility/types/assistant_list_response.py | 12 +- src/agility/types/assistant_update_params.py | 12 +- src/agility/types/assistant_with_config.py | 12 +- src/agility/types/threads/message.py | 90 ++--------- .../types/threads/message_create_params.py | 90 ++--------- src/agility/types/threads/run.py | 12 +- .../types/threads/run_create_params.py | 96 ++---------- .../types/threads/run_stream_params.py | 96 ++---------- tests/api_resources/test_assistants.py | 24 --- tests/api_resources/threads/test_messages.py | 58 ++------ tests/api_resources/threads/test_runs.py | 140 ++++-------------- 17 files changed, 103 insertions(+), 583 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4a60ed4..f1b3abc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-90629fd36897557fbf4f7ea3000519d0a6afeb290aed8211ead5e381cd1e25df.yml -openapi_spec_hash: 5a91c2a2505b4fba3a9ecdd03fcddf20 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-89a678a53675b9dd6a1879d6085aac26935840c3ba49510d100b7c4fcb678070.yml +openapi_spec_hash: fd5ae628ba4e39c96821215f704f0c91 config_hash: 58f3e6b15392ca51b942e41597d56e7f diff --git a/requirements-dev.lock b/requirements-dev.lock index 50a7dcc..d939eed 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -56,7 +56,7 @@ httpx==0.28.1 # via agility # via httpx-aiohttp # via respx -httpx-aiohttp==0.1.6 +httpx-aiohttp==0.1.8 # via agility idna==3.4 # via anyio diff --git a/requirements.lock b/requirements.lock index ad56a5d..373eb84 100644 --- a/requirements.lock +++ b/requirements.lock @@ -43,7 +43,7 @@ httpcore==1.0.2 httpx==0.28.1 # via agility # via httpx-aiohttp -httpx-aiohttp==0.1.6 +httpx-aiohttp==0.1.8 # via agility idna==3.4 # via anyio diff --git a/src/agility/resources/assistants/assistants.py b/src/agility/resources/assistants/assistants.py index 9aebc23..f9711b1 100644 --- a/src/agility/resources/assistants/assistants.py +++ b/src/agility/resources/assistants/assistants.py @@ -73,8 +73,6 @@ def create( logo_s3_key: Optional[str] | NotGiven = NOT_GIVEN, logo_text: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, - response_validation_config: Optional[Iterable[assistant_create_params.ResponseValidationConfig]] - | NotGiven = NOT_GIVEN, suggested_questions: List[str] | NotGiven = NOT_GIVEN, url_slug: Optional[str] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -124,7 +122,6 @@ def create( "logo_s3_key": logo_s3_key, "logo_text": logo_text, "model": model, - "response_validation_config": response_validation_config, "suggested_questions": suggested_questions, "url_slug": url_slug, }, @@ -184,8 +181,6 @@ def update( logo_s3_key: Optional[str] | NotGiven = NOT_GIVEN, logo_text: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, - response_validation_config: Optional[Iterable[assistant_update_params.ResponseValidationConfig]] - | NotGiven = NOT_GIVEN, suggested_questions: List[str] | NotGiven = NOT_GIVEN, url_slug: Optional[str] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -238,7 +233,6 @@ def update( "logo_s3_key": logo_s3_key, "logo_text": logo_text, "model": model, - "response_validation_config": response_validation_config, "suggested_questions": suggested_questions, "url_slug": url_slug, }, @@ -401,8 +395,6 @@ async def create( logo_s3_key: Optional[str] | NotGiven = NOT_GIVEN, logo_text: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, - response_validation_config: Optional[Iterable[assistant_create_params.ResponseValidationConfig]] - | NotGiven = NOT_GIVEN, suggested_questions: List[str] | NotGiven = NOT_GIVEN, url_slug: Optional[str] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -452,7 +444,6 @@ async def create( "logo_s3_key": logo_s3_key, "logo_text": logo_text, "model": model, - "response_validation_config": response_validation_config, "suggested_questions": suggested_questions, "url_slug": url_slug, }, @@ -512,8 +503,6 @@ async def update( logo_s3_key: Optional[str] | NotGiven = NOT_GIVEN, logo_text: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, - response_validation_config: Optional[Iterable[assistant_update_params.ResponseValidationConfig]] - | NotGiven = NOT_GIVEN, suggested_questions: List[str] | NotGiven = NOT_GIVEN, url_slug: Optional[str] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -566,7 +555,6 @@ async def update( "logo_s3_key": logo_s3_key, "logo_text": logo_text, "model": model, - "response_validation_config": response_validation_config, "suggested_questions": suggested_questions, "url_slug": url_slug, }, diff --git a/src/agility/resources/threads/runs.py b/src/agility/resources/threads/runs.py index 4e0cda1..149b4c0 100644 --- a/src/agility/resources/threads/runs.py +++ b/src/agility/resources/threads/runs.py @@ -57,8 +57,6 @@ def create( instructions: Optional[str] | NotGiven = NOT_GIVEN, knowledge_base_id: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, - response_validation_config: Optional[Iterable[run_create_params.ResponseValidationConfig]] - | 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. extra_headers: Headers | None = None, @@ -95,7 +93,6 @@ def create( "instructions": instructions, "knowledge_base_id": knowledge_base_id, "model": model, - "response_validation_config": response_validation_config, }, run_create_params.RunCreateParams, ), @@ -191,8 +188,6 @@ def stream( instructions: Optional[str] | NotGiven = NOT_GIVEN, knowledge_base_id: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, - response_validation_config: Optional[Iterable[run_stream_params.ResponseValidationConfig]] - | 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. extra_headers: Headers | None = None, @@ -229,7 +224,6 @@ def stream( "instructions": instructions, "knowledge_base_id": knowledge_base_id, "model": model, - "response_validation_config": response_validation_config, }, run_stream_params.RunStreamParams, ), @@ -273,8 +267,6 @@ async def create( instructions: Optional[str] | NotGiven = NOT_GIVEN, knowledge_base_id: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, - response_validation_config: Optional[Iterable[run_create_params.ResponseValidationConfig]] - | 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. extra_headers: Headers | None = None, @@ -311,7 +303,6 @@ async def create( "instructions": instructions, "knowledge_base_id": knowledge_base_id, "model": model, - "response_validation_config": response_validation_config, }, run_create_params.RunCreateParams, ), @@ -407,8 +398,6 @@ async def stream( instructions: Optional[str] | NotGiven = NOT_GIVEN, knowledge_base_id: Optional[str] | NotGiven = NOT_GIVEN, model: Optional[Literal["gpt-4o"]] | NotGiven = NOT_GIVEN, - response_validation_config: Optional[Iterable[run_stream_params.ResponseValidationConfig]] - | 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. extra_headers: Headers | None = None, @@ -445,7 +434,6 @@ async def stream( "instructions": instructions, "knowledge_base_id": knowledge_base_id, "model": model, - "response_validation_config": response_validation_config, }, run_stream_params.RunStreamParams, ), diff --git a/src/agility/types/assistant_create_params.py b/src/agility/types/assistant_create_params.py index 2aa8ed4..37c9b64 100644 --- a/src/agility/types/assistant_create_params.py +++ b/src/agility/types/assistant_create_params.py @@ -5,7 +5,7 @@ from typing import List, Iterable, Optional from typing_extensions import Literal, Required, TypedDict -__all__ = ["AssistantCreateParams", "HardCodedQuery", "ResponseValidationConfig"] +__all__ = ["AssistantCreateParams", "HardCodedQuery"] class AssistantCreateParams(TypedDict, total=False): @@ -34,8 +34,6 @@ class AssistantCreateParams(TypedDict, total=False): model: Optional[Literal["gpt-4o"]] - response_validation_config: Optional[Iterable[ResponseValidationConfig]] - suggested_questions: List[str] """A list of suggested questions that can be asked to the assistant""" @@ -51,11 +49,3 @@ class HardCodedQuery(TypedDict, total=False): context: Optional[List[str]] prompt: Optional[str] - - -class ResponseValidationConfig(TypedDict, total=False): - is_bad_threshold: Required[float] - - name: Required[ - Literal["trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease"] - ] diff --git a/src/agility/types/assistant_list_response.py b/src/agility/types/assistant_list_response.py index 5316f80..88b4932 100644 --- a/src/agility/types/assistant_list_response.py +++ b/src/agility/types/assistant_list_response.py @@ -6,7 +6,7 @@ from .._models import BaseModel -__all__ = ["AssistantListResponse", "HardCodedQuery", "ResponseValidationConfig"] +__all__ = ["AssistantListResponse", "HardCodedQuery"] class HardCodedQuery(BaseModel): @@ -19,14 +19,6 @@ class HardCodedQuery(BaseModel): prompt: Optional[str] = None -class ResponseValidationConfig(BaseModel): - is_bad_threshold: float - - name: Literal[ - "trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease" - ] - - class AssistantListResponse(BaseModel): id: str @@ -63,8 +55,6 @@ class AssistantListResponse(BaseModel): model: Optional[Literal["gpt-4o"]] = None - response_validation_config: Optional[List[ResponseValidationConfig]] = None - suggested_questions: Optional[List[str]] = None """A list of suggested questions that can be asked to the assistant""" diff --git a/src/agility/types/assistant_update_params.py b/src/agility/types/assistant_update_params.py index cabe3ba..fa58790 100644 --- a/src/agility/types/assistant_update_params.py +++ b/src/agility/types/assistant_update_params.py @@ -5,7 +5,7 @@ from typing import List, Iterable, Optional from typing_extensions import Literal, Required, TypedDict -__all__ = ["AssistantUpdateParams", "HardCodedQuery", "ResponseValidationConfig"] +__all__ = ["AssistantUpdateParams", "HardCodedQuery"] class AssistantUpdateParams(TypedDict, total=False): @@ -36,8 +36,6 @@ class AssistantUpdateParams(TypedDict, total=False): model: Optional[Literal["gpt-4o"]] - response_validation_config: Optional[Iterable[ResponseValidationConfig]] - suggested_questions: List[str] """A list of suggested questions that can be asked to the assistant""" @@ -53,11 +51,3 @@ class HardCodedQuery(TypedDict, total=False): context: Optional[List[str]] prompt: Optional[str] - - -class ResponseValidationConfig(TypedDict, total=False): - is_bad_threshold: Required[float] - - name: Required[ - Literal["trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease"] - ] diff --git a/src/agility/types/assistant_with_config.py b/src/agility/types/assistant_with_config.py index 4ff4c05..8e66336 100644 --- a/src/agility/types/assistant_with_config.py +++ b/src/agility/types/assistant_with_config.py @@ -6,7 +6,7 @@ from .._models import BaseModel -__all__ = ["AssistantWithConfig", "HardCodedQuery", "ResponseValidationConfig"] +__all__ = ["AssistantWithConfig", "HardCodedQuery"] class HardCodedQuery(BaseModel): @@ -19,14 +19,6 @@ class HardCodedQuery(BaseModel): prompt: Optional[str] = None -class ResponseValidationConfig(BaseModel): - is_bad_threshold: float - - name: Literal[ - "trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease" - ] - - class AssistantWithConfig(BaseModel): id: str @@ -61,8 +53,6 @@ class AssistantWithConfig(BaseModel): model: Optional[Literal["gpt-4o"]] = None - response_validation_config: Optional[List[ResponseValidationConfig]] = None - suggested_questions: Optional[List[str]] = None """A list of suggested questions that can be asked to the assistant""" diff --git a/src/agility/types/threads/message.py b/src/agility/types/threads/message.py index b274637..56b5474 100644 --- a/src/agility/types/threads/message.py +++ b/src/agility/types/threads/message.py @@ -1,110 +1,46 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from datetime import datetime from typing_extensions import Literal from ..._models import BaseModel -__all__ = [ - "Message", - "Metadata", - "MetadataScores", - "MetadataScoresContextSufficiency", - "MetadataScoresContextSufficiencyLog", - "MetadataScoresQueryEase", - "MetadataScoresQueryEaseLog", - "MetadataScoresResponseGroundedness", - "MetadataScoresResponseGroundednessLog", - "MetadataScoresResponseHelpfulness", - "MetadataScoresResponseHelpfulnessLog", - "MetadataScoresTrustworthiness", - "MetadataScoresTrustworthinessLog", -] - - -class MetadataScoresContextSufficiencyLog(BaseModel): - explanation: Optional[str] = None - - -class MetadataScoresContextSufficiency(BaseModel): - is_bad: Optional[bool] = None - - log: Optional[MetadataScoresContextSufficiencyLog] = None +__all__ = ["Message", "Metadata", "MetadataScores", "MetadataScoresLog"] - score: Optional[float] = None - -class MetadataScoresQueryEaseLog(BaseModel): +class MetadataScoresLog(BaseModel): explanation: Optional[str] = None -class MetadataScoresQueryEase(BaseModel): - is_bad: Optional[bool] = None - - log: Optional[MetadataScoresQueryEaseLog] = None - - score: Optional[float] = None - - -class MetadataScoresResponseGroundednessLog(BaseModel): - explanation: Optional[str] = None - - -class MetadataScoresResponseGroundedness(BaseModel): - is_bad: Optional[bool] = None - - log: Optional[MetadataScoresResponseGroundednessLog] = None - - score: Optional[float] = None - - -class MetadataScoresResponseHelpfulnessLog(BaseModel): - explanation: Optional[str] = None - - -class MetadataScoresResponseHelpfulness(BaseModel): - is_bad: Optional[bool] = None - - log: Optional[MetadataScoresResponseHelpfulnessLog] = None - - score: Optional[float] = None - - -class MetadataScoresTrustworthinessLog(BaseModel): - explanation: Optional[str] = None - - -class MetadataScoresTrustworthiness(BaseModel): +class MetadataScores(BaseModel): is_bad: Optional[bool] = None - log: Optional[MetadataScoresTrustworthinessLog] = None + log: Optional[MetadataScoresLog] = None score: Optional[float] = None + triggered: Optional[bool] = None -class MetadataScores(BaseModel): - context_sufficiency: Optional[MetadataScoresContextSufficiency] = None - - query_ease: Optional[MetadataScoresQueryEase] = None - - response_groundedness: Optional[MetadataScoresResponseGroundedness] = None - - response_helpfulness: Optional[MetadataScoresResponseHelpfulness] = None + triggered_escalation: Optional[bool] = None - trustworthiness: Optional[MetadataScoresTrustworthiness] = None + triggered_guardrail: Optional[bool] = None class Metadata(BaseModel): citations: Optional[List[str]] = None + escalated_to_sme: Optional[bool] = None + + guardrailed: Optional[bool] = None + is_bad_response: Optional[bool] = None is_expert_answer: Optional[bool] = None original_llm_response: Optional[str] = None - scores: Optional[MetadataScores] = None + scores: Optional[Dict[str, MetadataScores]] = None trustworthiness_explanation: Optional[str] = None diff --git a/src/agility/types/threads/message_create_params.py b/src/agility/types/threads/message_create_params.py index 9915a20..ae995c2 100644 --- a/src/agility/types/threads/message_create_params.py +++ b/src/agility/types/threads/message_create_params.py @@ -2,24 +2,10 @@ from __future__ import annotations -from typing import List, Optional +from typing import Dict, List, Optional from typing_extensions import Literal, Required, TypedDict -__all__ = [ - "MessageCreateParams", - "Metadata", - "MetadataScores", - "MetadataScoresContextSufficiency", - "MetadataScoresContextSufficiencyLog", - "MetadataScoresQueryEase", - "MetadataScoresQueryEaseLog", - "MetadataScoresResponseGroundedness", - "MetadataScoresResponseGroundednessLog", - "MetadataScoresResponseHelpfulness", - "MetadataScoresResponseHelpfulnessLog", - "MetadataScoresTrustworthiness", - "MetadataScoresTrustworthinessLog", -] +__all__ = ["MessageCreateParams", "Metadata", "MetadataScores", "MetadataScoresLog"] class MessageCreateParams(TypedDict, total=False): @@ -30,88 +16,38 @@ class MessageCreateParams(TypedDict, total=False): role: Required[Literal["user", "assistant"]] -class MetadataScoresContextSufficiencyLog(TypedDict, total=False): +class MetadataScoresLog(TypedDict, total=False): explanation: Optional[str] -class MetadataScoresContextSufficiency(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[MetadataScoresContextSufficiencyLog] - - score: Optional[float] - - -class MetadataScoresQueryEaseLog(TypedDict, total=False): - explanation: Optional[str] - - -class MetadataScoresQueryEase(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[MetadataScoresQueryEaseLog] - - score: Optional[float] - - -class MetadataScoresResponseGroundednessLog(TypedDict, total=False): - explanation: Optional[str] - - -class MetadataScoresResponseGroundedness(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[MetadataScoresResponseGroundednessLog] - - score: Optional[float] - - -class MetadataScoresResponseHelpfulnessLog(TypedDict, total=False): - explanation: Optional[str] - - -class MetadataScoresResponseHelpfulness(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[MetadataScoresResponseHelpfulnessLog] - - score: Optional[float] - - -class MetadataScoresTrustworthinessLog(TypedDict, total=False): - explanation: Optional[str] - - -class MetadataScoresTrustworthiness(TypedDict, total=False): +class MetadataScores(TypedDict, total=False): is_bad: Optional[bool] - log: Optional[MetadataScoresTrustworthinessLog] + log: Optional[MetadataScoresLog] score: Optional[float] + triggered: Optional[bool] -class MetadataScores(TypedDict, total=False): - context_sufficiency: Optional[MetadataScoresContextSufficiency] - - query_ease: Optional[MetadataScoresQueryEase] - - response_groundedness: Optional[MetadataScoresResponseGroundedness] - - response_helpfulness: Optional[MetadataScoresResponseHelpfulness] + triggered_escalation: Optional[bool] - trustworthiness: Optional[MetadataScoresTrustworthiness] + triggered_guardrail: Optional[bool] class Metadata(TypedDict, total=False): citations: Optional[List[str]] + escalated_to_sme: Optional[bool] + + guardrailed: Optional[bool] + is_bad_response: Optional[bool] is_expert_answer: Optional[bool] original_llm_response: Optional[str] - scores: Optional[MetadataScores] + scores: Optional[Dict[str, MetadataScores]] trustworthiness_explanation: Optional[str] diff --git a/src/agility/types/threads/run.py b/src/agility/types/threads/run.py index 94680c4..f227543 100644 --- a/src/agility/types/threads/run.py +++ b/src/agility/types/threads/run.py @@ -6,7 +6,7 @@ from ..._models import BaseModel -__all__ = ["Run", "HardCodedQuery", "ResponseValidationConfig", "Usage"] +__all__ = ["Run", "HardCodedQuery", "Usage"] class HardCodedQuery(BaseModel): @@ -19,14 +19,6 @@ class HardCodedQuery(BaseModel): prompt: Optional[str] = None -class ResponseValidationConfig(BaseModel): - is_bad_threshold: float - - name: Literal[ - "trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease" - ] - - class Usage(BaseModel): completion_tokens: int @@ -67,6 +59,4 @@ class Run(BaseModel): model: Optional[Literal["gpt-4o"]] = None - response_validation_config: Optional[List[ResponseValidationConfig]] = None - usage: Optional[Usage] = None diff --git a/src/agility/types/threads/run_create_params.py b/src/agility/types/threads/run_create_params.py index cfdb756..7c07c66 100644 --- a/src/agility/types/threads/run_create_params.py +++ b/src/agility/types/threads/run_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List, Iterable, Optional +from typing import Dict, List, Iterable, Optional from typing_extensions import Literal, Required, TypedDict __all__ = [ @@ -10,18 +10,8 @@ "AdditionalMessage", "AdditionalMessageMetadata", "AdditionalMessageMetadataScores", - "AdditionalMessageMetadataScoresContextSufficiency", - "AdditionalMessageMetadataScoresContextSufficiencyLog", - "AdditionalMessageMetadataScoresQueryEase", - "AdditionalMessageMetadataScoresQueryEaseLog", - "AdditionalMessageMetadataScoresResponseGroundedness", - "AdditionalMessageMetadataScoresResponseGroundednessLog", - "AdditionalMessageMetadataScoresResponseHelpfulness", - "AdditionalMessageMetadataScoresResponseHelpfulnessLog", - "AdditionalMessageMetadataScoresTrustworthiness", - "AdditionalMessageMetadataScoresTrustworthinessLog", + "AdditionalMessageMetadataScoresLog", "HardCodedQuery", - "ResponseValidationConfig", ] @@ -45,91 +35,39 @@ class RunCreateParams(TypedDict, total=False): model: Optional[Literal["gpt-4o"]] - response_validation_config: Optional[Iterable[ResponseValidationConfig]] - -class AdditionalMessageMetadataScoresContextSufficiencyLog(TypedDict, total=False): - explanation: Optional[str] - - -class AdditionalMessageMetadataScoresContextSufficiency(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[AdditionalMessageMetadataScoresContextSufficiencyLog] - - score: Optional[float] - - -class AdditionalMessageMetadataScoresQueryEaseLog(TypedDict, total=False): - explanation: Optional[str] - - -class AdditionalMessageMetadataScoresQueryEase(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[AdditionalMessageMetadataScoresQueryEaseLog] - - score: Optional[float] - - -class AdditionalMessageMetadataScoresResponseGroundednessLog(TypedDict, total=False): +class AdditionalMessageMetadataScoresLog(TypedDict, total=False): explanation: Optional[str] -class AdditionalMessageMetadataScoresResponseGroundedness(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[AdditionalMessageMetadataScoresResponseGroundednessLog] - - score: Optional[float] - - -class AdditionalMessageMetadataScoresResponseHelpfulnessLog(TypedDict, total=False): - explanation: Optional[str] - - -class AdditionalMessageMetadataScoresResponseHelpfulness(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[AdditionalMessageMetadataScoresResponseHelpfulnessLog] - - score: Optional[float] - - -class AdditionalMessageMetadataScoresTrustworthinessLog(TypedDict, total=False): - explanation: Optional[str] - - -class AdditionalMessageMetadataScoresTrustworthiness(TypedDict, total=False): +class AdditionalMessageMetadataScores(TypedDict, total=False): is_bad: Optional[bool] - log: Optional[AdditionalMessageMetadataScoresTrustworthinessLog] + log: Optional[AdditionalMessageMetadataScoresLog] score: Optional[float] + triggered: Optional[bool] -class AdditionalMessageMetadataScores(TypedDict, total=False): - context_sufficiency: Optional[AdditionalMessageMetadataScoresContextSufficiency] - - query_ease: Optional[AdditionalMessageMetadataScoresQueryEase] - - response_groundedness: Optional[AdditionalMessageMetadataScoresResponseGroundedness] - - response_helpfulness: Optional[AdditionalMessageMetadataScoresResponseHelpfulness] + triggered_escalation: Optional[bool] - trustworthiness: Optional[AdditionalMessageMetadataScoresTrustworthiness] + triggered_guardrail: Optional[bool] class AdditionalMessageMetadata(TypedDict, total=False): citations: Optional[List[str]] + escalated_to_sme: Optional[bool] + + guardrailed: Optional[bool] + is_bad_response: Optional[bool] is_expert_answer: Optional[bool] original_llm_response: Optional[str] - scores: Optional[AdditionalMessageMetadataScores] + scores: Optional[Dict[str, AdditionalMessageMetadataScores]] trustworthiness_explanation: Optional[str] @@ -154,11 +92,3 @@ class HardCodedQuery(TypedDict, total=False): context: Optional[List[str]] prompt: Optional[str] - - -class ResponseValidationConfig(TypedDict, total=False): - is_bad_threshold: Required[float] - - name: Required[ - Literal["trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease"] - ] diff --git a/src/agility/types/threads/run_stream_params.py b/src/agility/types/threads/run_stream_params.py index 099e90a..7300892 100644 --- a/src/agility/types/threads/run_stream_params.py +++ b/src/agility/types/threads/run_stream_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List, Iterable, Optional +from typing import Dict, List, Iterable, Optional from typing_extensions import Literal, Required, TypedDict __all__ = [ @@ -10,18 +10,8 @@ "AdditionalMessage", "AdditionalMessageMetadata", "AdditionalMessageMetadataScores", - "AdditionalMessageMetadataScoresContextSufficiency", - "AdditionalMessageMetadataScoresContextSufficiencyLog", - "AdditionalMessageMetadataScoresQueryEase", - "AdditionalMessageMetadataScoresQueryEaseLog", - "AdditionalMessageMetadataScoresResponseGroundedness", - "AdditionalMessageMetadataScoresResponseGroundednessLog", - "AdditionalMessageMetadataScoresResponseHelpfulness", - "AdditionalMessageMetadataScoresResponseHelpfulnessLog", - "AdditionalMessageMetadataScoresTrustworthiness", - "AdditionalMessageMetadataScoresTrustworthinessLog", + "AdditionalMessageMetadataScoresLog", "HardCodedQuery", - "ResponseValidationConfig", ] @@ -45,91 +35,39 @@ class RunStreamParams(TypedDict, total=False): model: Optional[Literal["gpt-4o"]] - response_validation_config: Optional[Iterable[ResponseValidationConfig]] - -class AdditionalMessageMetadataScoresContextSufficiencyLog(TypedDict, total=False): - explanation: Optional[str] - - -class AdditionalMessageMetadataScoresContextSufficiency(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[AdditionalMessageMetadataScoresContextSufficiencyLog] - - score: Optional[float] - - -class AdditionalMessageMetadataScoresQueryEaseLog(TypedDict, total=False): - explanation: Optional[str] - - -class AdditionalMessageMetadataScoresQueryEase(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[AdditionalMessageMetadataScoresQueryEaseLog] - - score: Optional[float] - - -class AdditionalMessageMetadataScoresResponseGroundednessLog(TypedDict, total=False): +class AdditionalMessageMetadataScoresLog(TypedDict, total=False): explanation: Optional[str] -class AdditionalMessageMetadataScoresResponseGroundedness(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[AdditionalMessageMetadataScoresResponseGroundednessLog] - - score: Optional[float] - - -class AdditionalMessageMetadataScoresResponseHelpfulnessLog(TypedDict, total=False): - explanation: Optional[str] - - -class AdditionalMessageMetadataScoresResponseHelpfulness(TypedDict, total=False): - is_bad: Optional[bool] - - log: Optional[AdditionalMessageMetadataScoresResponseHelpfulnessLog] - - score: Optional[float] - - -class AdditionalMessageMetadataScoresTrustworthinessLog(TypedDict, total=False): - explanation: Optional[str] - - -class AdditionalMessageMetadataScoresTrustworthiness(TypedDict, total=False): +class AdditionalMessageMetadataScores(TypedDict, total=False): is_bad: Optional[bool] - log: Optional[AdditionalMessageMetadataScoresTrustworthinessLog] + log: Optional[AdditionalMessageMetadataScoresLog] score: Optional[float] + triggered: Optional[bool] -class AdditionalMessageMetadataScores(TypedDict, total=False): - context_sufficiency: Optional[AdditionalMessageMetadataScoresContextSufficiency] - - query_ease: Optional[AdditionalMessageMetadataScoresQueryEase] - - response_groundedness: Optional[AdditionalMessageMetadataScoresResponseGroundedness] - - response_helpfulness: Optional[AdditionalMessageMetadataScoresResponseHelpfulness] + triggered_escalation: Optional[bool] - trustworthiness: Optional[AdditionalMessageMetadataScoresTrustworthiness] + triggered_guardrail: Optional[bool] class AdditionalMessageMetadata(TypedDict, total=False): citations: Optional[List[str]] + escalated_to_sme: Optional[bool] + + guardrailed: Optional[bool] + is_bad_response: Optional[bool] is_expert_answer: Optional[bool] original_llm_response: Optional[str] - scores: Optional[AdditionalMessageMetadataScores] + scores: Optional[Dict[str, AdditionalMessageMetadataScores]] trustworthiness_explanation: Optional[str] @@ -154,11 +92,3 @@ class HardCodedQuery(TypedDict, total=False): context: Optional[List[str]] prompt: Optional[str] - - -class ResponseValidationConfig(TypedDict, total=False): - is_bad_threshold: Required[float] - - name: Required[ - Literal["trustworthiness", "response_helpfulness", "context_sufficiency", "response_groundedness", "query_ease"] - ] diff --git a/tests/api_resources/test_assistants.py b/tests/api_resources/test_assistants.py index d14a8cf..26df791 100644 --- a/tests/api_resources/test_assistants.py +++ b/tests/api_resources/test_assistants.py @@ -52,12 +52,6 @@ def test_method_create_with_all_params(self, client: Agility) -> None: logo_s3_key="logo_s3_key", logo_text="logo_text", model="gpt-4o", - response_validation_config=[ - { - "is_bad_threshold": 0, - "name": "trustworthiness", - } - ], suggested_questions=["string"], url_slug="url_slug", ) @@ -162,12 +156,6 @@ def test_method_update_with_all_params(self, client: Agility) -> None: logo_s3_key="logo_s3_key", logo_text="logo_text", model="gpt-4o", - response_validation_config=[ - { - "is_bad_threshold": 0, - "name": "trustworthiness", - } - ], suggested_questions=["string"], url_slug="url_slug", ) @@ -370,12 +358,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - logo_s3_key="logo_s3_key", logo_text="logo_text", model="gpt-4o", - response_validation_config=[ - { - "is_bad_threshold": 0, - "name": "trustworthiness", - } - ], suggested_questions=["string"], url_slug="url_slug", ) @@ -480,12 +462,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncAgility) - logo_s3_key="logo_s3_key", logo_text="logo_text", model="gpt-4o", - response_validation_config=[ - { - "is_bad_threshold": 0, - "name": "trustworthiness", - } - ], suggested_questions=["string"], url_slug="url_slug", ) diff --git a/tests/api_resources/threads/test_messages.py b/tests/api_resources/threads/test_messages.py index e840ce8..dd0e8be 100644 --- a/tests/api_resources/threads/test_messages.py +++ b/tests/api_resources/threads/test_messages.py @@ -35,35 +35,20 @@ def test_method_create_with_all_params(self, client: Agility) -> None: content="content", metadata={ "citations": ["string"], + "escalated_to_sme": True, + "guardrailed": True, "is_bad_response": True, "is_expert_answer": True, "original_llm_response": "original_llm_response", "scores": { - "context_sufficiency": { + "foo": { "is_bad": True, "log": {"explanation": "explanation"}, "score": 0, - }, - "query_ease": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_groundedness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_helpfulness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "trustworthiness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, + "triggered": True, + "triggered_escalation": True, + "triggered_guardrail": True, + } }, "trustworthiness_explanation": "trustworthiness_explanation", "trustworthiness_score": 0, @@ -278,35 +263,20 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - content="content", metadata={ "citations": ["string"], + "escalated_to_sme": True, + "guardrailed": True, "is_bad_response": True, "is_expert_answer": True, "original_llm_response": "original_llm_response", "scores": { - "context_sufficiency": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "query_ease": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_groundedness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_helpfulness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "trustworthiness": { + "foo": { "is_bad": True, "log": {"explanation": "explanation"}, "score": 0, - }, + "triggered": True, + "triggered_escalation": True, + "triggered_guardrail": True, + } }, "trustworthiness_explanation": "trustworthiness_explanation", "trustworthiness_score": 0, diff --git a/tests/api_resources/threads/test_runs.py b/tests/api_resources/threads/test_runs.py index f572e34..293e1a5 100644 --- a/tests/api_resources/threads/test_runs.py +++ b/tests/api_resources/threads/test_runs.py @@ -36,35 +36,20 @@ def test_method_create_with_all_params(self, client: Agility) -> None: "content": "content", "metadata": { "citations": ["string"], + "escalated_to_sme": True, + "guardrailed": True, "is_bad_response": True, "is_expert_answer": True, "original_llm_response": "original_llm_response", "scores": { - "context_sufficiency": { + "foo": { "is_bad": True, "log": {"explanation": "explanation"}, "score": 0, - }, - "query_ease": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_groundedness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_helpfulness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "trustworthiness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, + "triggered": True, + "triggered_escalation": True, + "triggered_guardrail": True, + } }, "trustworthiness_explanation": "trustworthiness_explanation", "trustworthiness_score": 0, @@ -86,12 +71,6 @@ def test_method_create_with_all_params(self, client: Agility) -> None: instructions="instructions", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", model="gpt-4o", - response_validation_config=[ - { - "is_bad_threshold": 0, - "name": "trustworthiness", - } - ], ) assert_matches_type(Run, run, path=["response"]) @@ -244,35 +223,20 @@ def test_method_stream_with_all_params(self, client: Agility) -> None: "content": "content", "metadata": { "citations": ["string"], + "escalated_to_sme": True, + "guardrailed": True, "is_bad_response": True, "is_expert_answer": True, "original_llm_response": "original_llm_response", "scores": { - "context_sufficiency": { + "foo": { "is_bad": True, "log": {"explanation": "explanation"}, "score": 0, - }, - "query_ease": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_groundedness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_helpfulness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "trustworthiness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, + "triggered": True, + "triggered_escalation": True, + "triggered_guardrail": True, + } }, "trustworthiness_explanation": "trustworthiness_explanation", "trustworthiness_score": 0, @@ -294,12 +258,6 @@ def test_method_stream_with_all_params(self, client: Agility) -> None: instructions="instructions", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", model="gpt-4o", - response_validation_config=[ - { - "is_bad_threshold": 0, - "name": "trustworthiness", - } - ], ) assert_matches_type(object, run, path=["response"]) @@ -362,35 +320,20 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - "content": "content", "metadata": { "citations": ["string"], + "escalated_to_sme": True, + "guardrailed": True, "is_bad_response": True, "is_expert_answer": True, "original_llm_response": "original_llm_response", "scores": { - "context_sufficiency": { + "foo": { "is_bad": True, "log": {"explanation": "explanation"}, "score": 0, - }, - "query_ease": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_groundedness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_helpfulness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "trustworthiness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, + "triggered": True, + "triggered_escalation": True, + "triggered_guardrail": True, + } }, "trustworthiness_explanation": "trustworthiness_explanation", "trustworthiness_score": 0, @@ -412,12 +355,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - instructions="instructions", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", model="gpt-4o", - response_validation_config=[ - { - "is_bad_threshold": 0, - "name": "trustworthiness", - } - ], ) assert_matches_type(Run, run, path=["response"]) @@ -570,35 +507,20 @@ async def test_method_stream_with_all_params(self, async_client: AsyncAgility) - "content": "content", "metadata": { "citations": ["string"], + "escalated_to_sme": True, + "guardrailed": True, "is_bad_response": True, "is_expert_answer": True, "original_llm_response": "original_llm_response", "scores": { - "context_sufficiency": { + "foo": { "is_bad": True, "log": {"explanation": "explanation"}, "score": 0, - }, - "query_ease": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_groundedness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "response_helpfulness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, - "trustworthiness": { - "is_bad": True, - "log": {"explanation": "explanation"}, - "score": 0, - }, + "triggered": True, + "triggered_escalation": True, + "triggered_guardrail": True, + } }, "trustworthiness_explanation": "trustworthiness_explanation", "trustworthiness_score": 0, @@ -620,12 +542,6 @@ async def test_method_stream_with_all_params(self, async_client: AsyncAgility) - instructions="instructions", knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", model="gpt-4o", - response_validation_config=[ - { - "is_bad_threshold": 0, - "name": "trustworthiness", - } - ], ) assert_matches_type(object, run, path=["response"]) From 11e35d71c7b0f734e19da1a2fabe4033e4369380 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:31:24 +0000 Subject: [PATCH 38/49] chore(internal): bump pinned h11 dep --- requirements-dev.lock | 4 ++-- requirements.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index d939eed..d71d5f6 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,9 +48,9 @@ filelock==3.12.4 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via agility diff --git a/requirements.lock b/requirements.lock index 373eb84..cff9a9a 100644 --- a/requirements.lock +++ b/requirements.lock @@ -36,9 +36,9 @@ exceptiongroup==1.2.2 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via agility From d3ab16733e749a69ee30363ccbc46da11f149342 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:50:37 +0000 Subject: [PATCH 39/49] chore(package): mark python 3.13 as supported --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b3fc393..fb3ef0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", From f5ba0a7365b6464fb170c7cf2de93ffbdcebbf8b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 02:46:04 +0000 Subject: [PATCH 40/49] fix(parsing): correctly handle nested discriminated unions --- src/agility/_models.py | 13 +++++++----- tests/test_models.py | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/agility/_models.py b/src/agility/_models.py index 4f21498..528d568 100644 --- a/src/agility/_models.py +++ b/src/agility/_models.py @@ -2,9 +2,10 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, @@ -366,7 +367,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") - return construct_type(value=value, type_=type_) + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) def is_basemodel(type_: type) -> bool: @@ -420,7 +421,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: return cast(_T, construct_type(value=value, type_=type_)) -def construct_type(*, value: object, type_: object) -> object: +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. @@ -438,8 +439,10 @@ def construct_type(*, value: object, type_: object) -> object: type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - meta: tuple[Any, ...] = get_args(type_)[1:] + if metadata is not None: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) else: meta = tuple() diff --git a/tests/test_models.py b/tests/test_models.py index 45fa504..53952dc 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -889,3 +889,48 @@ class ModelB(BaseModel): ) assert isinstance(m, ModelB) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2) From 41785696d6442e4ab946acb9359d2e292d592a71 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 03:05:03 +0000 Subject: [PATCH 41/49] chore(readme): fix version rendering on pypi --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 262fe3a..4d0ac9f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Agility Python API library -[![PyPI version]()](https://pypi.org/project/agility/) + +[![PyPI version](https://img.shields.io/pypi/v/agility.svg?label=pypi%20(stable))](https://pypi.org/project/agility/) The Agility Python library provides convenient access to the Agility REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, From 29ac2d4bcef58799fee4932b923b3d03ed693bf4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:11:06 +0000 Subject: [PATCH 42/49] fix(client): don't send Content-Type header on GET requests --- pyproject.toml | 2 +- src/agility/_base_client.py | 11 +++++++++-- tests/test_client.py | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fb3ef0c..a386b01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ Homepage = "https://github.com/stainless-sdks/agility-python" Repository = "https://github.com/stainless-sdks/agility-python" [project.optional-dependencies] -aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"] [tool.rye] managed = true diff --git a/src/agility/_base_client.py b/src/agility/_base_client.py index b8d65b7..d180d1c 100644 --- a/src/agility/_base_client.py +++ b/src/agility/_base_client.py @@ -529,6 +529,15 @@ def _build_request( # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + kwargs["json"] = json_data if is_given(json_data) else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, @@ -540,8 +549,6 @@ 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 if is_given(json_data) else None, - files=files, **kwargs, ) diff --git a/tests/test_client.py b/tests/test_client.py index c7e299a..dc1aeab 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -452,7 +452,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: Agility) -> None: request = client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -1267,7 +1267,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncAgility) -> None: request = async_client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, From 08f1e351e9f4cc8953b90a8624d762d7c71feb57 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:21:01 +0000 Subject: [PATCH 43/49] feat(api): api update --- .stats.yml | 4 ++-- src/agility/resources/assistants/assistants.py | 8 ++++++++ src/agility/resources/threads/runs.py | 8 ++++++++ src/agility/types/assistant_create_params.py | 2 ++ src/agility/types/assistant_list_response.py | 2 ++ src/agility/types/assistant_update_params.py | 2 ++ src/agility/types/assistant_with_config.py | 2 ++ src/agility/types/threads/run.py | 2 ++ src/agility/types/threads/run_create_params.py | 2 ++ src/agility/types/threads/run_stream_params.py | 2 ++ tests/api_resources/test_assistants.py | 4 ++++ tests/api_resources/threads/test_runs.py | 4 ++++ 12 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index f1b3abc..5fe39fd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-89a678a53675b9dd6a1879d6085aac26935840c3ba49510d100b7c4fcb678070.yml -openapi_spec_hash: fd5ae628ba4e39c96821215f704f0c91 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-c4dba389c9fe8f2a0a3567e458b40c130c9eff615be73fc284613d0ca0d0914b.yml +openapi_spec_hash: f9b96eedb50e595160335923fa783cd0 config_hash: 58f3e6b15392ca51b942e41597d56e7f diff --git a/src/agility/resources/assistants/assistants.py b/src/agility/resources/assistants/assistants.py index f9711b1..bb7a442 100644 --- a/src/agility/resources/assistants/assistants.py +++ b/src/agility/resources/assistants/assistants.py @@ -67,6 +67,7 @@ def create( knowledge_base_id: Optional[str], name: str, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, + codex_as_cache: bool | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, hard_coded_queries: Optional[Iterable[assistant_create_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, @@ -116,6 +117,7 @@ def create( "knowledge_base_id": knowledge_base_id, "name": name, "codex_access_key": codex_access_key, + "codex_as_cache": codex_as_cache, "context_limit": context_limit, "hard_coded_queries": hard_coded_queries, "instructions": instructions, @@ -175,6 +177,7 @@ def update( knowledge_base_id: Optional[str], name: str, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, + codex_as_cache: bool | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, hard_coded_queries: Optional[Iterable[assistant_update_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, @@ -227,6 +230,7 @@ def update( "knowledge_base_id": knowledge_base_id, "name": name, "codex_access_key": codex_access_key, + "codex_as_cache": codex_as_cache, "context_limit": context_limit, "hard_coded_queries": hard_coded_queries, "instructions": instructions, @@ -389,6 +393,7 @@ async def create( knowledge_base_id: Optional[str], name: str, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, + codex_as_cache: bool | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, hard_coded_queries: Optional[Iterable[assistant_create_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, @@ -438,6 +443,7 @@ async def create( "knowledge_base_id": knowledge_base_id, "name": name, "codex_access_key": codex_access_key, + "codex_as_cache": codex_as_cache, "context_limit": context_limit, "hard_coded_queries": hard_coded_queries, "instructions": instructions, @@ -497,6 +503,7 @@ async def update( knowledge_base_id: Optional[str], name: str, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, + codex_as_cache: bool | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, hard_coded_queries: Optional[Iterable[assistant_update_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, @@ -549,6 +556,7 @@ async def update( "knowledge_base_id": knowledge_base_id, "name": name, "codex_access_key": codex_access_key, + "codex_as_cache": codex_as_cache, "context_limit": context_limit, "hard_coded_queries": hard_coded_queries, "instructions": instructions, diff --git a/src/agility/resources/threads/runs.py b/src/agility/resources/threads/runs.py index 149b4c0..6435812 100644 --- a/src/agility/resources/threads/runs.py +++ b/src/agility/resources/threads/runs.py @@ -52,6 +52,7 @@ def create( additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, additional_messages: Iterable[run_create_params.AdditionalMessage] | NotGiven = NOT_GIVEN, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, + codex_as_cache: Optional[bool] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, hard_coded_queries: Optional[Iterable[run_create_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, @@ -88,6 +89,7 @@ def create( "additional_instructions": additional_instructions, "additional_messages": additional_messages, "codex_access_key": codex_access_key, + "codex_as_cache": codex_as_cache, "context_limit": context_limit, "hard_coded_queries": hard_coded_queries, "instructions": instructions, @@ -183,6 +185,7 @@ def stream( additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, additional_messages: Iterable[run_stream_params.AdditionalMessage] | NotGiven = NOT_GIVEN, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, + codex_as_cache: Optional[bool] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, hard_coded_queries: Optional[Iterable[run_stream_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, @@ -219,6 +222,7 @@ def stream( "additional_instructions": additional_instructions, "additional_messages": additional_messages, "codex_access_key": codex_access_key, + "codex_as_cache": codex_as_cache, "context_limit": context_limit, "hard_coded_queries": hard_coded_queries, "instructions": instructions, @@ -262,6 +266,7 @@ async def create( additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, additional_messages: Iterable[run_create_params.AdditionalMessage] | NotGiven = NOT_GIVEN, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, + codex_as_cache: Optional[bool] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, hard_coded_queries: Optional[Iterable[run_create_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, @@ -298,6 +303,7 @@ async def create( "additional_instructions": additional_instructions, "additional_messages": additional_messages, "codex_access_key": codex_access_key, + "codex_as_cache": codex_as_cache, "context_limit": context_limit, "hard_coded_queries": hard_coded_queries, "instructions": instructions, @@ -393,6 +399,7 @@ async def stream( additional_instructions: Optional[str] | NotGiven = NOT_GIVEN, additional_messages: Iterable[run_stream_params.AdditionalMessage] | NotGiven = NOT_GIVEN, codex_access_key: Optional[str] | NotGiven = NOT_GIVEN, + codex_as_cache: Optional[bool] | NotGiven = NOT_GIVEN, context_limit: Optional[int] | NotGiven = NOT_GIVEN, hard_coded_queries: Optional[Iterable[run_stream_params.HardCodedQuery]] | NotGiven = NOT_GIVEN, instructions: Optional[str] | NotGiven = NOT_GIVEN, @@ -429,6 +436,7 @@ async def stream( "additional_instructions": additional_instructions, "additional_messages": additional_messages, "codex_access_key": codex_access_key, + "codex_as_cache": codex_as_cache, "context_limit": context_limit, "hard_coded_queries": hard_coded_queries, "instructions": instructions, diff --git a/src/agility/types/assistant_create_params.py b/src/agility/types/assistant_create_params.py index 37c9b64..ee56665 100644 --- a/src/agility/types/assistant_create_params.py +++ b/src/agility/types/assistant_create_params.py @@ -19,6 +19,8 @@ class AssistantCreateParams(TypedDict, total=False): codex_access_key: Optional[str] + codex_as_cache: bool + context_limit: Optional[int] """The maximum number of context chunks to include in a run.""" diff --git a/src/agility/types/assistant_list_response.py b/src/agility/types/assistant_list_response.py index 88b4932..94b1680 100644 --- a/src/agility/types/assistant_list_response.py +++ b/src/agility/types/assistant_list_response.py @@ -40,6 +40,8 @@ class AssistantListResponse(BaseModel): codex_access_key: Optional[str] = None + codex_as_cache: Optional[bool] = None + context_limit: Optional[int] = None """The maximum number of context chunks to include in a run.""" diff --git a/src/agility/types/assistant_update_params.py b/src/agility/types/assistant_update_params.py index fa58790..8f197c4 100644 --- a/src/agility/types/assistant_update_params.py +++ b/src/agility/types/assistant_update_params.py @@ -21,6 +21,8 @@ class AssistantUpdateParams(TypedDict, total=False): codex_access_key: Optional[str] + codex_as_cache: bool + context_limit: Optional[int] """The maximum number of context chunks to include in a run.""" diff --git a/src/agility/types/assistant_with_config.py b/src/agility/types/assistant_with_config.py index 8e66336..2b9717a 100644 --- a/src/agility/types/assistant_with_config.py +++ b/src/agility/types/assistant_with_config.py @@ -38,6 +38,8 @@ class AssistantWithConfig(BaseModel): codex_access_key: Optional[str] = None + codex_as_cache: Optional[bool] = None + context_limit: Optional[int] = None """The maximum number of context chunks to include in a run.""" diff --git a/src/agility/types/threads/run.py b/src/agility/types/threads/run.py index f227543..df3eadd 100644 --- a/src/agility/types/threads/run.py +++ b/src/agility/types/threads/run.py @@ -44,6 +44,8 @@ class Run(BaseModel): codex_access_key: Optional[str] = None + codex_as_cache: Optional[bool] = None + context_limit: Optional[int] = None """The maximum number of context chunks to include.""" diff --git a/src/agility/types/threads/run_create_params.py b/src/agility/types/threads/run_create_params.py index 7c07c66..4414bad 100644 --- a/src/agility/types/threads/run_create_params.py +++ b/src/agility/types/threads/run_create_params.py @@ -24,6 +24,8 @@ class RunCreateParams(TypedDict, total=False): codex_access_key: Optional[str] + codex_as_cache: Optional[bool] + context_limit: Optional[int] """The maximum number of context chunks to include.""" diff --git a/src/agility/types/threads/run_stream_params.py b/src/agility/types/threads/run_stream_params.py index 7300892..5f4ce08 100644 --- a/src/agility/types/threads/run_stream_params.py +++ b/src/agility/types/threads/run_stream_params.py @@ -24,6 +24,8 @@ class RunStreamParams(TypedDict, total=False): codex_access_key: Optional[str] + codex_as_cache: Optional[bool] + context_limit: Optional[int] """The maximum number of context chunks to include.""" diff --git a/tests/api_resources/test_assistants.py b/tests/api_resources/test_assistants.py index 26df791..8ce0554 100644 --- a/tests/api_resources/test_assistants.py +++ b/tests/api_resources/test_assistants.py @@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Agility) -> None: knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", codex_access_key="codex_access_key", + codex_as_cache=True, context_limit=1, hard_coded_queries=[ { @@ -143,6 +144,7 @@ def test_method_update_with_all_params(self, client: Agility) -> None: knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", codex_access_key="codex_access_key", + codex_as_cache=True, context_limit=1, hard_coded_queries=[ { @@ -345,6 +347,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", codex_access_key="codex_access_key", + codex_as_cache=True, context_limit=1, hard_coded_queries=[ { @@ -449,6 +452,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncAgility) - knowledge_base_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="name", codex_access_key="codex_access_key", + codex_as_cache=True, context_limit=1, hard_coded_queries=[ { diff --git a/tests/api_resources/threads/test_runs.py b/tests/api_resources/threads/test_runs.py index 293e1a5..f842692 100644 --- a/tests/api_resources/threads/test_runs.py +++ b/tests/api_resources/threads/test_runs.py @@ -59,6 +59,7 @@ def test_method_create_with_all_params(self, client: Agility) -> None: } ], codex_access_key="codex_access_key", + codex_as_cache=True, context_limit=1, hard_coded_queries=[ { @@ -246,6 +247,7 @@ def test_method_stream_with_all_params(self, client: Agility) -> None: } ], codex_access_key="codex_access_key", + codex_as_cache=True, context_limit=1, hard_coded_queries=[ { @@ -343,6 +345,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - } ], codex_access_key="codex_access_key", + codex_as_cache=True, context_limit=1, hard_coded_queries=[ { @@ -530,6 +533,7 @@ async def test_method_stream_with_all_params(self, async_client: AsyncAgility) - } ], codex_access_key="codex_access_key", + codex_as_cache=True, context_limit=1, hard_coded_queries=[ { From ff1fe4c4b021dd98c21f5fd7e56dc6a05c91239a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 02:23:56 +0000 Subject: [PATCH 44/49] feat: clean up environment call outs --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d0ac9f..9eed88e 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,6 @@ pip install 'agility[aiohttp] @ git+ssh://git@github.com/stainless-sdks/agility- Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python -import os import asyncio from agility import DefaultAioHttpClient from agility import AsyncAgility @@ -102,7 +101,7 @@ from agility import AsyncAgility async def main() -> None: async with AsyncAgility( - bearer_token=os.environ.get("BEARER_TOKEN"), # This is the default and can be omitted + bearer_token="My Bearer Token", http_client=DefaultAioHttpClient(), ) as client: assistant = await client.assistants.create( From eed76f3abdc5d181c77a9a7c493180f6814c5cc6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 01:21:05 +0000 Subject: [PATCH 45/49] feat(api): api update --- .stats.yml | 4 ++-- src/agility/types/assistant_create_params.py | 4 +++- src/agility/types/assistant_list_response.py | 4 +++- .../types/assistant_retrieve_run_metadata_response.py | 8 +++++--- src/agility/types/assistant_update_params.py | 4 +++- src/agility/types/assistant_with_config.py | 4 +++- src/agility/types/threads/run.py | 4 +++- src/agility/types/threads/run_create_params.py | 2 ++ src/agility/types/threads/run_stream_params.py | 2 ++ tests/api_resources/test_assistants.py | 4 ++++ tests/api_resources/threads/test_runs.py | 4 ++++ 11 files changed, 34 insertions(+), 10 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5fe39fd..29213f9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-c4dba389c9fe8f2a0a3567e458b40c130c9eff615be73fc284613d0ca0d0914b.yml -openapi_spec_hash: f9b96eedb50e595160335923fa783cd0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cleanlab%2Fagility-9e6009a931b636947a7410707ffb4db68f9a00d6f7e3ec6e55c365f883c50223.yml +openapi_spec_hash: 264626d871113465d14672d73e910c03 config_hash: 58f3e6b15392ca51b942e41597d56e7f diff --git a/src/agility/types/assistant_create_params.py b/src/agility/types/assistant_create_params.py index ee56665..80e782d 100644 --- a/src/agility/types/assistant_create_params.py +++ b/src/agility/types/assistant_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List, Iterable, Optional +from typing import Dict, List, Iterable, Optional from typing_extensions import Literal, Required, TypedDict __all__ = ["AssistantCreateParams", "HardCodedQuery"] @@ -50,4 +50,6 @@ class HardCodedQuery(TypedDict, total=False): context: Optional[List[str]] + messages: Optional[Iterable[Dict[str, object]]] + prompt: Optional[str] diff --git a/src/agility/types/assistant_list_response.py b/src/agility/types/assistant_list_response.py index 94b1680..241608f 100644 --- a/src/agility/types/assistant_list_response.py +++ b/src/agility/types/assistant_list_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from datetime import datetime from typing_extensions import Literal @@ -16,6 +16,8 @@ class HardCodedQuery(BaseModel): context: Optional[List[str]] = None + messages: Optional[List[Dict[str, object]]] = None + prompt: Optional[str] = None diff --git a/src/agility/types/assistant_retrieve_run_metadata_response.py b/src/agility/types/assistant_retrieve_run_metadata_response.py index a58250f..9a69a17 100644 --- a/src/agility/types/assistant_retrieve_run_metadata_response.py +++ b/src/agility/types/assistant_retrieve_run_metadata_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from .._models import BaseModel @@ -8,10 +8,12 @@ class AssistantRetrieveRunMetadataResponse(BaseModel): - prompt: str - query: str response: str context: Optional[List[str]] = None + + messages: Optional[List[Dict[str, object]]] = None + + prompt: Optional[str] = None diff --git a/src/agility/types/assistant_update_params.py b/src/agility/types/assistant_update_params.py index 8f197c4..f780669 100644 --- a/src/agility/types/assistant_update_params.py +++ b/src/agility/types/assistant_update_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List, Iterable, Optional +from typing import Dict, List, Iterable, Optional from typing_extensions import Literal, Required, TypedDict __all__ = ["AssistantUpdateParams", "HardCodedQuery"] @@ -52,4 +52,6 @@ class HardCodedQuery(TypedDict, total=False): context: Optional[List[str]] + messages: Optional[Iterable[Dict[str, object]]] + prompt: Optional[str] diff --git a/src/agility/types/assistant_with_config.py b/src/agility/types/assistant_with_config.py index 2b9717a..d6ee0b4 100644 --- a/src/agility/types/assistant_with_config.py +++ b/src/agility/types/assistant_with_config.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from datetime import datetime from typing_extensions import Literal @@ -16,6 +16,8 @@ class HardCodedQuery(BaseModel): context: Optional[List[str]] = None + messages: Optional[List[Dict[str, object]]] = None + prompt: Optional[str] = None diff --git a/src/agility/types/threads/run.py b/src/agility/types/threads/run.py index df3eadd..caa6942 100644 --- a/src/agility/types/threads/run.py +++ b/src/agility/types/threads/run.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from datetime import datetime from typing_extensions import Literal @@ -16,6 +16,8 @@ class HardCodedQuery(BaseModel): context: Optional[List[str]] = None + messages: Optional[List[Dict[str, object]]] = None + prompt: Optional[str] = None diff --git a/src/agility/types/threads/run_create_params.py b/src/agility/types/threads/run_create_params.py index 4414bad..8d0cc27 100644 --- a/src/agility/types/threads/run_create_params.py +++ b/src/agility/types/threads/run_create_params.py @@ -93,4 +93,6 @@ class HardCodedQuery(TypedDict, total=False): context: Optional[List[str]] + messages: Optional[Iterable[Dict[str, object]]] + prompt: Optional[str] diff --git a/src/agility/types/threads/run_stream_params.py b/src/agility/types/threads/run_stream_params.py index 5f4ce08..b5ae16c 100644 --- a/src/agility/types/threads/run_stream_params.py +++ b/src/agility/types/threads/run_stream_params.py @@ -93,4 +93,6 @@ class HardCodedQuery(TypedDict, total=False): context: Optional[List[str]] + messages: Optional[Iterable[Dict[str, object]]] + prompt: Optional[str] diff --git a/tests/api_resources/test_assistants.py b/tests/api_resources/test_assistants.py index 8ce0554..421cd2d 100644 --- a/tests/api_resources/test_assistants.py +++ b/tests/api_resources/test_assistants.py @@ -46,6 +46,7 @@ def test_method_create_with_all_params(self, client: Agility) -> None: "query": "query", "response": "response", "context": ["string"], + "messages": [{"foo": "bar"}], "prompt": "prompt", } ], @@ -151,6 +152,7 @@ def test_method_update_with_all_params(self, client: Agility) -> None: "query": "query", "response": "response", "context": ["string"], + "messages": [{"foo": "bar"}], "prompt": "prompt", } ], @@ -354,6 +356,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - "query": "query", "response": "response", "context": ["string"], + "messages": [{"foo": "bar"}], "prompt": "prompt", } ], @@ -459,6 +462,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncAgility) - "query": "query", "response": "response", "context": ["string"], + "messages": [{"foo": "bar"}], "prompt": "prompt", } ], diff --git a/tests/api_resources/threads/test_runs.py b/tests/api_resources/threads/test_runs.py index f842692..7968e2f 100644 --- a/tests/api_resources/threads/test_runs.py +++ b/tests/api_resources/threads/test_runs.py @@ -66,6 +66,7 @@ def test_method_create_with_all_params(self, client: Agility) -> None: "query": "query", "response": "response", "context": ["string"], + "messages": [{"foo": "bar"}], "prompt": "prompt", } ], @@ -254,6 +255,7 @@ def test_method_stream_with_all_params(self, client: Agility) -> None: "query": "query", "response": "response", "context": ["string"], + "messages": [{"foo": "bar"}], "prompt": "prompt", } ], @@ -352,6 +354,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAgility) - "query": "query", "response": "response", "context": ["string"], + "messages": [{"foo": "bar"}], "prompt": "prompt", } ], @@ -540,6 +543,7 @@ async def test_method_stream_with_all_params(self, async_client: AsyncAgility) - "query": "query", "response": "response", "context": ["string"], + "messages": [{"foo": "bar"}], "prompt": "prompt", } ], From 049f3610a06113ad96ad785f03a7c4fac1f6bb4a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 02:13:01 +0000 Subject: [PATCH 46/49] fix(parsing): ignore empty metadata --- src/agility/_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agility/_models.py b/src/agility/_models.py index 528d568..ffcbf67 100644 --- a/src/agility/_models.py +++ b/src/agility/_models.py @@ -439,7 +439,7 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any] type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if metadata is not None: + if metadata is not None and len(metadata) > 0: meta: tuple[Any, ...] = tuple(metadata) elif is_annotated_type(type_): meta = get_args(type_)[1:] From 5a6980c2d038b948b5b918552594fd06ac8fa2ab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 02:14:54 +0000 Subject: [PATCH 47/49] fix(parsing): parse extra field types --- src/agility/_models.py | 25 +++++++++++++++++++++++-- tests/test_models.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/agility/_models.py b/src/agility/_models.py index ffcbf67..b8387ce 100644 --- a/src/agility/_models.py +++ b/src/agility/_models.py @@ -208,14 +208,18 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] else: fields_values[name] = field_get_default(field) + extra_field_type = _get_extra_fields_type(__cls) + _extra = {} for key, value in values.items(): if key not in model_fields: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + if PYDANTIC_V2: - _extra[key] = value + _extra[key] = parsed else: _fields_set.add(key) - fields_values[key] = value + fields_values[key] = parsed object.__setattr__(m, "__dict__", fields_values) @@ -370,6 +374,23 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if not PYDANTIC_V2: + # TODO + return None + + schema = cls.__pydantic_core_schema__ + if schema["type"] == "model": + fields = schema["schema"] + if fields["type"] == "model-fields": + extras = fields.get("extras_schema") + if extras and "cls" in extras: + # mypy can't narrow the type + return extras["cls"] # type: ignore[no-any-return] + + return None + + def is_basemodel(type_: type) -> bool: """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" if is_union(type_): diff --git a/tests/test_models.py b/tests/test_models.py index 53952dc..4f636a4 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone from typing_extensions import Literal, Annotated, TypeAliasType @@ -934,3 +934,30 @@ class Type2(BaseModel): ) assert isinstance(model, Type1) assert isinstance(model.value, InnerType2) + + +@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now") +def test_extra_properties() -> None: + class Item(BaseModel): + prop: int + + class Model(BaseModel): + __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + other: str + + if TYPE_CHECKING: + + def __getattr__(self, attr: str) -> Item: ... + + model = construct_type( + type_=Model, + value={ + "a": {"prop": 1}, + "other": "foo", + }, + ) + assert isinstance(model, Model) + assert model.a.prop == 1 + assert isinstance(model.a, Item) + assert model.other == "foo" From 816b98a5f7533ceb28bb781e6e5fc6375993a303 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 03:53:19 +0000 Subject: [PATCH 48/49] chore(project): add settings file for vscode --- .gitignore | 1 - .vscode/settings.json | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 8779740..95ceb18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .prism.log -.vscode _dev __pycache__ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5b01030 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.importFormat": "relative", +} From 561ed464bc0ffe81482030573f0a3009941ad02f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 05:23:29 +0000 Subject: [PATCH 49/49] feat(client): support file upload requests --- src/agility/_base_client.py | 5 ++++- src/agility/_files.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/agility/_base_client.py b/src/agility/_base_client.py index d180d1c..9e078f1 100644 --- a/src/agility/_base_client.py +++ b/src/agility/_base_client.py @@ -532,7 +532,10 @@ def _build_request( is_body_allowed = options.method.lower() != "get" if is_body_allowed: - kwargs["json"] = json_data if is_given(json_data) else None + if isinstance(json_data, bytes): + kwargs["content"] = json_data + else: + kwargs["json"] = json_data if is_given(json_data) else None kwargs["files"] = files else: headers.pop("Content-Type", None) diff --git a/src/agility/_files.py b/src/agility/_files.py index 715cc20..cc14c14 100644 --- a/src/agility/_files.py +++ b/src/agility/_files.py @@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], _read_file_content(file[1]), *file[2:]) + return (file[0], read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -def _read_file_content(file: FileContent) -> HttpxFileContent: +def read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return pathlib.Path(file).read_bytes() return file @@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], await _async_read_file_content(file[1]), *file[2:]) + return (file[0], await async_read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -async def _async_read_file_content(file: FileContent) -> HttpxFileContent: +async def async_read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return await anyio.Path(file).read_bytes()