From 07eeda8ae3e1c7bdfe3fcc51a8786f35682f4c3f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 10 Feb 2026 22:15:42 +0000
Subject: [PATCH 1/8] chore(internal): codegen related update
From bf5fa9f93b6443ac0b41d5fa134aab8ee12cfbc3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 11 Feb 2026 18:33:37 +0000
Subject: [PATCH 2/8] fix(client): loosen auth header validation
---
src/gradient/_client.py | 52 +--
.../knowledge_bases/knowledge_bases.py | 48 +--
.../types/retrieve_documents_params.py | 1 -
tests/test_client.py | 322 +++++-------------
4 files changed, 105 insertions(+), 318 deletions(-)
diff --git a/src/gradient/_client.py b/src/gradient/_client.py
index 847121b1..fc58b291 100644
--- a/src/gradient/_client.py
+++ b/src/gradient/_client.py
@@ -353,14 +353,10 @@ def copy(
Create a new client instance re-using the same options given to the current client with optional overriding.
"""
if default_headers is not None and set_default_headers is not None:
- raise ValueError(
- "The `default_headers` and `set_default_headers` arguments are mutually exclusive"
- )
+ raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
if default_query is not None and set_default_query is not None:
- raise ValueError(
- "The `default_query` and `set_default_query` arguments are mutually exclusive"
- )
+ raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
headers = self._custom_headers
if default_headers is not None:
@@ -411,14 +407,10 @@ def _make_status_error(
return _exceptions.BadRequestError(err_msg, response=response, body=body)
if response.status_code == 401:
- return _exceptions.AuthenticationError(
- err_msg, response=response, body=body
- )
+ return _exceptions.AuthenticationError(err_msg, response=response, body=body)
if response.status_code == 403:
- return _exceptions.PermissionDeniedError(
- err_msg, response=response, body=body
- )
+ return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
if response.status_code == 404:
return _exceptions.NotFoundError(err_msg, response=response, body=body)
@@ -427,17 +419,13 @@ def _make_status_error(
return _exceptions.ConflictError(err_msg, response=response, body=body)
if response.status_code == 422:
- return _exceptions.UnprocessableEntityError(
- err_msg, response=response, body=body
- )
+ return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
if response.status_code == 429:
return _exceptions.RateLimitError(err_msg, response=response, body=body)
if response.status_code >= 500:
- return _exceptions.InternalServerError(
- err_msg, response=response, body=body
- )
+ return _exceptions.InternalServerError(err_msg, response=response, body=body)
return APIStatusError(err_msg, response=response, body=body)
@@ -719,14 +707,10 @@ def copy(
Create a new client instance re-using the same options given to the current client with optional overriding.
"""
if default_headers is not None and set_default_headers is not None:
- raise ValueError(
- "The `default_headers` and `set_default_headers` arguments are mutually exclusive"
- )
+ raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
if default_query is not None and set_default_query is not None:
- raise ValueError(
- "The `default_query` and `set_default_query` arguments are mutually exclusive"
- )
+ raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
headers = self._custom_headers
if default_headers is not None:
@@ -777,14 +761,10 @@ def _make_status_error(
return _exceptions.BadRequestError(err_msg, response=response, body=body)
if response.status_code == 401:
- return _exceptions.AuthenticationError(
- err_msg, response=response, body=body
- )
+ return _exceptions.AuthenticationError(err_msg, response=response, body=body)
if response.status_code == 403:
- return _exceptions.PermissionDeniedError(
- err_msg, response=response, body=body
- )
+ return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
if response.status_code == 404:
return _exceptions.NotFoundError(err_msg, response=response, body=body)
@@ -793,17 +773,13 @@ def _make_status_error(
return _exceptions.ConflictError(err_msg, response=response, body=body)
if response.status_code == 422:
- return _exceptions.UnprocessableEntityError(
- err_msg, response=response, body=body
- )
+ return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
if response.status_code == 429:
return _exceptions.RateLimitError(err_msg, response=response, body=body)
if response.status_code >= 500:
- return _exceptions.InternalServerError(
- err_msg, response=response, body=body
- )
+ return _exceptions.InternalServerError(err_msg, response=response, body=body)
return APIStatusError(err_msg, response=response, body=body)
@@ -1082,9 +1058,7 @@ def knowledge_bases(
AsyncKnowledgeBasesResourceWithStreamingResponse,
)
- return AsyncKnowledgeBasesResourceWithStreamingResponse(
- self._client.knowledge_bases
- )
+ return AsyncKnowledgeBasesResourceWithStreamingResponse(self._client.knowledge_bases)
@cached_property
def models(self) -> models.AsyncModelsResourceWithStreamingResponse:
diff --git a/src/gradient/resources/knowledge_bases/knowledge_bases.py b/src/gradient/resources/knowledge_bases/knowledge_bases.py
index 04c19b32..9c22a60c 100644
--- a/src/gradient/resources/knowledge_bases/knowledge_bases.py
+++ b/src/gradient/resources/knowledge_bases/knowledge_bases.py
@@ -210,9 +210,7 @@ def retrieve(
timeout: Override the client-level default timeout for this request, in seconds
"""
if not uuid:
- raise ValueError(
- f"Expected a non-empty value for `uuid` but received {uuid!r}"
- )
+ raise ValueError(f"Expected a non-empty value for `uuid` but received {uuid!r}")
return self._get(
(
f"/v2/gen-ai/knowledge_bases/{uuid}"
@@ -271,9 +269,7 @@ def update(
timeout: Override the client-level default timeout for this request, in seconds
"""
if not path_uuid:
- raise ValueError(
- f"Expected a non-empty value for `path_uuid` but received {path_uuid!r}"
- )
+ raise ValueError(f"Expected a non-empty value for `path_uuid` but received {path_uuid!r}")
return self._put(
(
f"/v2/gen-ai/knowledge_bases/{path_uuid}"
@@ -375,9 +371,7 @@ def delete(
timeout: Override the client-level default timeout for this request, in seconds
"""
if not uuid:
- raise ValueError(
- f"Expected a non-empty value for `uuid` but received {uuid!r}"
- )
+ raise ValueError(f"Expected a non-empty value for `uuid` but received {uuid!r}")
return self._delete(
(
f"/v2/gen-ai/knowledge_bases/{uuid}"
@@ -646,9 +640,7 @@ async def retrieve(
timeout: Override the client-level default timeout for this request, in seconds
"""
if not uuid:
- raise ValueError(
- f"Expected a non-empty value for `uuid` but received {uuid!r}"
- )
+ raise ValueError(f"Expected a non-empty value for `uuid` but received {uuid!r}")
return await self._get(
(
f"/v2/gen-ai/knowledge_bases/{uuid}"
@@ -707,9 +699,7 @@ async def update(
timeout: Override the client-level default timeout for this request, in seconds
"""
if not path_uuid:
- raise ValueError(
- f"Expected a non-empty value for `path_uuid` but received {path_uuid!r}"
- )
+ raise ValueError(f"Expected a non-empty value for `path_uuid` but received {path_uuid!r}")
return await self._put(
(
f"/v2/gen-ai/knowledge_bases/{path_uuid}"
@@ -811,9 +801,7 @@ async def delete(
timeout: Override the client-level default timeout for this request, in seconds
"""
if not uuid:
- raise ValueError(
- f"Expected a non-empty value for `uuid` but received {uuid!r}"
- )
+ raise ValueError(f"Expected a non-empty value for `uuid` but received {uuid!r}")
return await self._delete(
(
f"/v2/gen-ai/knowledge_bases/{uuid}"
@@ -1011,15 +999,11 @@ def __init__(self, knowledge_bases: AsyncKnowledgeBasesResource) -> None:
@cached_property
def data_sources(self) -> AsyncDataSourcesResourceWithRawResponse:
- return AsyncDataSourcesResourceWithRawResponse(
- self._knowledge_bases.data_sources
- )
+ return AsyncDataSourcesResourceWithRawResponse(self._knowledge_bases.data_sources)
@cached_property
def indexing_jobs(self) -> AsyncIndexingJobsResourceWithRawResponse:
- return AsyncIndexingJobsResourceWithRawResponse(
- self._knowledge_bases.indexing_jobs
- )
+ return AsyncIndexingJobsResourceWithRawResponse(self._knowledge_bases.indexing_jobs)
class KnowledgeBasesResourceWithStreamingResponse:
@@ -1050,15 +1034,11 @@ def __init__(self, knowledge_bases: KnowledgeBasesResource) -> None:
@cached_property
def data_sources(self) -> DataSourcesResourceWithStreamingResponse:
- return DataSourcesResourceWithStreamingResponse(
- self._knowledge_bases.data_sources
- )
+ return DataSourcesResourceWithStreamingResponse(self._knowledge_bases.data_sources)
@cached_property
def indexing_jobs(self) -> IndexingJobsResourceWithStreamingResponse:
- return IndexingJobsResourceWithStreamingResponse(
- self._knowledge_bases.indexing_jobs
- )
+ return IndexingJobsResourceWithStreamingResponse(self._knowledge_bases.indexing_jobs)
class AsyncKnowledgeBasesResourceWithStreamingResponse:
@@ -1089,12 +1069,8 @@ def __init__(self, knowledge_bases: AsyncKnowledgeBasesResource) -> None:
@cached_property
def data_sources(self) -> AsyncDataSourcesResourceWithStreamingResponse:
- return AsyncDataSourcesResourceWithStreamingResponse(
- self._knowledge_bases.data_sources
- )
+ return AsyncDataSourcesResourceWithStreamingResponse(self._knowledge_bases.data_sources)
@cached_property
def indexing_jobs(self) -> AsyncIndexingJobsResourceWithStreamingResponse:
- return AsyncIndexingJobsResourceWithStreamingResponse(
- self._knowledge_bases.indexing_jobs
- )
+ return AsyncIndexingJobsResourceWithStreamingResponse(self._knowledge_bases.indexing_jobs)
diff --git a/src/gradient/types/retrieve_documents_params.py b/src/gradient/types/retrieve_documents_params.py
index bad99ad3..359d8a07 100644
--- a/src/gradient/types/retrieve_documents_params.py
+++ b/src/gradient/types/retrieve_documents_params.py
@@ -73,4 +73,3 @@ class Filters(TypedDict, total=False):
should: Iterable[FiltersShould]
"""At least one condition must match (OR)"""
-
diff --git a/tests/test_client.py b/tests/test_client.py
index 4b645c08..c78fa9f8 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -59,9 +59,7 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
def _get_open_connections(client: Gradient | AsyncGradient) -> int:
transport = client._client._transport
- assert isinstance(transport, httpx.HTTPTransport) or isinstance(
- transport, httpx.AsyncHTTPTransport
- )
+ assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
pool = transport._pool
return len(pool._requests)
@@ -70,9 +68,7 @@ def _get_open_connections(client: Gradient | AsyncGradient) -> int:
class TestGradient:
@pytest.mark.respx(base_url=base_url)
def test_raw_response(self, respx_mock: MockRouter, client: Gradient) -> None:
- respx_mock.post("/foo").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
+ respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
response = client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
@@ -80,9 +76,7 @@ def test_raw_response(self, respx_mock: MockRouter, client: Gradient) -> None:
assert response.json() == {"foo": "bar"}
@pytest.mark.respx(base_url=base_url)
- def test_raw_response_for_binary(
- self, respx_mock: MockRouter, client: Gradient
- ) -> None:
+ def test_raw_response_for_binary(self, respx_mock: MockRouter, client: Gradient) -> None:
respx_mock.post("/foo").mock(
return_value=httpx.Response(
200,
@@ -231,9 +225,7 @@ def test_copy_signature(self, client: Gradient) -> None:
continue
copy_param = copy_signature.parameters.get(name)
- assert (
- copy_param is not None
- ), f"copy() signature is missing the {name} param"
+ assert copy_param is not None, f"copy() signature is missing the {name} param"
@pytest.mark.skipif(
sys.version_info >= (3, 10),
@@ -269,9 +261,7 @@ def build_request(options: FinalRequestOptions) -> None:
tracemalloc.stop()
- def add_leak(
- leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff
- ) -> None:
+ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None:
if diff.count == 0:
# Avoid false positives by considering only leaks (i.e. allocations that persist).
return
@@ -314,9 +304,7 @@ def test_request_timeout(self, client: Gradient) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT
- request = client._build_request(
- FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))
- )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == httpx.Timeout(100.0)
@@ -348,9 +336,7 @@ def test_http_client_timeout_option(self) -> None:
http_client=http_client,
)
- request = client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == httpx.Timeout(None)
@@ -367,9 +353,7 @@ def test_http_client_timeout_option(self) -> None:
http_client=http_client,
)
- request = client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT
@@ -386,9 +370,7 @@ def test_http_client_timeout_option(self) -> None:
http_client=http_client,
)
- request = client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT # our default
@@ -415,9 +397,7 @@ def test_default_headers_option(self) -> None:
_strict_response_validation=True,
default_headers={"X-Foo": "bar"},
)
- request = test_client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "bar"
assert request.headers.get("x-stainless-lang") == "python"
@@ -432,9 +412,7 @@ def test_default_headers_option(self) -> None:
"X-Stainless-Lang": "my-overriding-header",
},
)
- request = test_client2._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "stainless"
assert request.headers.get("x-stainless-lang") == "my-overriding-header"
@@ -474,9 +452,7 @@ def test_validate_headers(self) -> None:
client2._build_request(FinalRequestOptions(method="get", url="/foo"))
request2 = client2._build_request(
- FinalRequestOptions(
- method="get", url="/foo", headers={"Authorization": Omit()}
- )
+ FinalRequestOptions(method="get", url="/foo", headers={"Authorization": Omit()})
)
assert request2.headers.get("Authorization") is None
@@ -607,9 +583,7 @@ def test_multipart_repeating_array(self, client: Gradient) -> None:
FinalRequestOptions.construct(
method="post",
url="/foo",
- headers={
- "Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"
- },
+ headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
json_data={"array": ["foo", "bar"]},
files=[("foo.txt", b"hello world")],
)
@@ -634,27 +608,21 @@ def test_multipart_repeating_array(self, client: Gradient) -> None:
]
@pytest.mark.respx(base_url=base_url)
- def test_basic_union_response(
- self, respx_mock: MockRouter, client: Gradient
- ) -> None:
+ def test_basic_union_response(self, respx_mock: MockRouter, client: Gradient) -> None:
class Model1(BaseModel):
name: str
class Model2(BaseModel):
foo: str
- respx_mock.get("/foo").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model2)
assert response.foo == "bar"
@pytest.mark.respx(base_url=base_url)
- def test_union_response_different_types(
- self, respx_mock: MockRouter, client: Gradient
- ) -> None:
+ def test_union_response_different_types(self, respx_mock: MockRouter, client: Gradient) -> None:
"""Union of objects with the same field name using a different type"""
class Model1(BaseModel):
@@ -663,9 +631,7 @@ class Model1(BaseModel):
class Model2(BaseModel):
foo: str
- respx_mock.get("/foo").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model2)
@@ -678,9 +644,7 @@ class Model2(BaseModel):
assert response.foo == 1
@pytest.mark.respx(base_url=base_url)
- def test_non_application_json_content_type_for_json_data(
- self, respx_mock: MockRouter, client: Gradient
- ) -> None:
+ def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: Gradient) -> None:
"""
Response that sets Content-Type to something other than application/json but returns json data
"""
@@ -850,15 +814,11 @@ def test_client_context_manager(self) -> None:
assert test_client.is_closed()
@pytest.mark.respx(base_url=base_url)
- def test_client_response_validation_error(
- self, respx_mock: MockRouter, client: Gradient
- ) -> None:
+ def test_client_response_validation_error(self, respx_mock: MockRouter, client: Gradient) -> None:
class Model(BaseModel):
foo: str
- respx_mock.get("/foo").mock(
- return_value=httpx.Response(200, json={"foo": {"invalid": True}})
- )
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
with pytest.raises(APIResponseValidationError) as exc:
client.get("/foo", cast_to=Model)
@@ -881,13 +841,9 @@ def test_default_stream_cls(self, respx_mock: MockRouter, client: Gradient) -> N
class Model(BaseModel):
name: str
- respx_mock.post("/foo").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
+ respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- stream = client.post(
- "/foo", cast_to=Model, stream=True, stream_cls=Stream[Model]
- )
+ stream = client.post("/foo", cast_to=Model, stream=True, stream_cls=Stream[Model])
assert isinstance(stream, Stream)
stream.response.close()
@@ -896,9 +852,7 @@ def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
class Model(BaseModel):
name: str
- respx_mock.get("/foo").mock(
- return_value=httpx.Response(200, text="my-custom-format")
- )
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
strict_client = Gradient(
base_url=base_url,
@@ -959,16 +913,10 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
assert calculated == pytest.approx(timeout, rel=0.5 * 0.875) # type: ignore[misc]
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._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, client: Gradient
- ) -> None:
- respx_mock.post("/chat/completions").mock(
- side_effect=httpx.TimeoutException("Test timeout error")
- )
+ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Gradient) -> None:
+ respx_mock.post("/chat/completions").mock(side_effect=httpx.TimeoutException("Test timeout error"))
with pytest.raises(APITimeoutError):
client.chat.completions.with_streaming_response.create(
@@ -983,13 +931,9 @@ def test_retrying_timeout_errors_doesnt_leak(
assert _get_open_connections(client) == 0
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._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, client: Gradient
- ) -> None:
+ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Gradient) -> None:
respx_mock.post("/chat/completions").mock(return_value=httpx.Response(500))
with pytest.raises(APIStatusError):
@@ -1005,9 +949,7 @@ def test_retrying_status_errors_doesnt_leak(
assert _get_open_connections(client) == 0
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
@pytest.mark.parametrize("failure_mode", ["status", "exception"])
def test_retries_taken(
@@ -1043,15 +985,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
)
assert response.retries_taken == failures_before_success
- assert (
- int(response.http_request.headers.get("x-stainless-retry-count"))
- == failures_before_success
- )
+ assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
def test_omit_retry_count_header(
self, client: Gradient, failures_before_success: int, respx_mock: MockRouter
@@ -1080,14 +1017,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
extra_headers={"x-stainless-retry-count": Omit()},
)
- assert (
- len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
- )
+ assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
def test_overwrite_retry_count_header(
self, client: Gradient, failures_before_success: int, respx_mock: MockRouter
@@ -1144,29 +1077,19 @@ def test_default_client_creation(self) -> None:
def test_follow_redirects(self, respx_mock: MockRouter, client: Gradient) -> 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"})
+ 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 = client.post(
- "/redirect", body={"key": "value"}, cast_to=httpx.Response
- )
+ response = 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, client: Gradient
- ) -> None:
+ def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: Gradient) -> 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"}
- )
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)
with pytest.raises(APIStatusError) as exc_info:
@@ -1183,12 +1106,8 @@ def test_follow_redirects_disabled(
class TestAsyncGradient:
@pytest.mark.respx(base_url=base_url)
- async def test_raw_response(
- self, respx_mock: MockRouter, async_client: AsyncGradient
- ) -> None:
- respx_mock.post("/foo").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
+ async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncGradient) -> None:
+ respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
response = await async_client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
@@ -1196,9 +1115,7 @@ async def test_raw_response(
assert response.json() == {"foo": "bar"}
@pytest.mark.respx(base_url=base_url)
- async def test_raw_response_for_binary(
- self, respx_mock: MockRouter, async_client: AsyncGradient
- ) -> None:
+ async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncGradient) -> None:
respx_mock.post("/foo").mock(
return_value=httpx.Response(
200,
@@ -1347,9 +1264,7 @@ def test_copy_signature(self, async_client: AsyncGradient) -> None:
continue
copy_param = copy_signature.parameters.get(name)
- assert (
- copy_param is not None
- ), f"copy() signature is missing the {name} param"
+ assert copy_param is not None, f"copy() signature is missing the {name} param"
@pytest.mark.skipif(
sys.version_info >= (3, 10),
@@ -1385,9 +1300,7 @@ def build_request(options: FinalRequestOptions) -> None:
tracemalloc.stop()
- def add_leak(
- leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff
- ) -> None:
+ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None:
if diff.count == 0:
# Avoid false positives by considering only leaks (i.e. allocations that persist).
return
@@ -1426,9 +1339,7 @@ def add_leak(
raise AssertionError()
async def test_request_timeout(self, async_client: AsyncGradient) -> None:
- request = async_client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = async_client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT
@@ -1466,9 +1377,7 @@ async def test_http_client_timeout_option(self) -> None:
http_client=http_client,
)
- request = client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == httpx.Timeout(None)
@@ -1485,9 +1394,7 @@ async def test_http_client_timeout_option(self) -> None:
http_client=http_client,
)
- request = client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT
@@ -1504,9 +1411,7 @@ async def test_http_client_timeout_option(self) -> None:
http_client=http_client,
)
- request = client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT # our default
@@ -1533,9 +1438,7 @@ async def test_default_headers_option(self) -> None:
_strict_response_validation=True,
default_headers={"X-Foo": "bar"},
)
- request = test_client._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "bar"
assert request.headers.get("x-stainless-lang") == "python"
@@ -1550,9 +1453,7 @@ async def test_default_headers_option(self) -> None:
"X-Stainless-Lang": "my-overriding-header",
},
)
- request = test_client2._build_request(
- FinalRequestOptions(method="get", url="/foo")
- )
+ request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "stainless"
assert request.headers.get("x-stainless-lang") == "my-overriding-header"
@@ -1592,9 +1493,7 @@ def test_validate_headers(self) -> None:
client2._build_request(FinalRequestOptions(method="get", url="/foo"))
request2 = client2._build_request(
- FinalRequestOptions(
- method="get", url="/foo", headers={"Authorization": Omit()}
- )
+ FinalRequestOptions(method="get", url="/foo", headers={"Authorization": Omit()})
)
assert request2.headers.get("Authorization") is None
@@ -1725,9 +1624,7 @@ def test_multipart_repeating_array(self, async_client: AsyncGradient) -> None:
FinalRequestOptions.construct(
method="post",
url="/foo",
- headers={
- "Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"
- },
+ headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
json_data={"array": ["foo", "bar"]},
files=[("foo.txt", b"hello world")],
)
@@ -1752,29 +1649,21 @@ def test_multipart_repeating_array(self, async_client: AsyncGradient) -> None:
]
@pytest.mark.respx(base_url=base_url)
- async def test_basic_union_response(
- self, respx_mock: MockRouter, async_client: AsyncGradient
- ) -> None:
+ async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncGradient) -> None:
class Model1(BaseModel):
name: str
class Model2(BaseModel):
foo: str
- respx_mock.get("/foo").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- response = await async_client.get(
- "/foo", cast_to=cast(Any, Union[Model1, Model2])
- )
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model2)
assert response.foo == "bar"
@pytest.mark.respx(base_url=base_url)
- async def test_union_response_different_types(
- self, respx_mock: MockRouter, async_client: AsyncGradient
- ) -> None:
+ async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncGradient) -> None:
"""Union of objects with the same field name using a different type"""
class Model1(BaseModel):
@@ -1783,21 +1672,15 @@ class Model1(BaseModel):
class Model2(BaseModel):
foo: str
- respx_mock.get("/foo").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- response = await async_client.get(
- "/foo", cast_to=cast(Any, Union[Model1, Model2])
- )
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model2)
assert response.foo == "bar"
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
- response = await async_client.get(
- "/foo", cast_to=cast(Any, Union[Model1, Model2])
- )
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model1)
assert response.foo == 1
@@ -1979,15 +1862,11 @@ async def test_client_context_manager(self) -> None:
assert test_client.is_closed()
@pytest.mark.respx(base_url=base_url)
- async def test_client_response_validation_error(
- self, respx_mock: MockRouter, async_client: AsyncGradient
- ) -> None:
+ async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncGradient) -> None:
class Model(BaseModel):
foo: str
- respx_mock.get("/foo").mock(
- return_value=httpx.Response(200, json={"foo": {"invalid": True}})
- )
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
with pytest.raises(APIResponseValidationError) as exc:
await async_client.get("/foo", cast_to=Model)
@@ -2006,32 +1885,22 @@ async def test_client_max_retries_validation(self) -> None:
)
@pytest.mark.respx(base_url=base_url)
- async def test_default_stream_cls(
- self, respx_mock: MockRouter, async_client: AsyncGradient
- ) -> None:
+ async def test_default_stream_cls(self, respx_mock: MockRouter, async_client: AsyncGradient) -> None:
class Model(BaseModel):
name: str
- respx_mock.post("/foo").mock(
- return_value=httpx.Response(200, json={"foo": "bar"})
- )
+ respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- stream = await async_client.post(
- "/foo", cast_to=Model, stream=True, stream_cls=AsyncStream[Model]
- )
+ stream = await async_client.post("/foo", cast_to=Model, stream=True, stream_cls=AsyncStream[Model])
assert isinstance(stream, AsyncStream)
await stream.response.aclose()
@pytest.mark.respx(base_url=base_url)
- async def test_received_text_for_expected_json(
- self, respx_mock: MockRouter
- ) -> None:
+ async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
class Model(BaseModel):
name: str
- respx_mock.get("/foo").mock(
- return_value=httpx.Response(200, text="my-custom-format")
- )
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
strict_client = AsyncGradient(
base_url=base_url,
@@ -2095,16 +1964,12 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte
calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers)
assert calculated == pytest.approx(timeout, rel=0.5 * 0.875) # type: ignore[misc]
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._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, async_client: AsyncGradient
) -> None:
- respx_mock.post("/chat/completions").mock(
- side_effect=httpx.TimeoutException("Test timeout error")
- )
+ respx_mock.post("/chat/completions").mock(side_effect=httpx.TimeoutException("Test timeout error"))
with pytest.raises(APITimeoutError):
await async_client.chat.completions.with_streaming_response.create(
@@ -2119,9 +1984,7 @@ async def test_retrying_timeout_errors_doesnt_leak(
assert _get_open_connections(async_client) == 0
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._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, async_client: AsyncGradient
@@ -2141,9 +2004,7 @@ async def test_retrying_status_errors_doesnt_leak(
assert _get_open_connections(async_client) == 0
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
@pytest.mark.parametrize("failure_mode", ["status", "exception"])
async def test_retries_taken(
@@ -2179,15 +2040,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
)
assert response.retries_taken == failures_before_success
- assert (
- int(response.http_request.headers.get("x-stainless-retry-count"))
- == failures_before_success
- )
+ assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
async def test_omit_retry_count_header(
self,
@@ -2219,14 +2075,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
extra_headers={"x-stainless-retry-count": Omit()},
)
- assert (
- len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
- )
+ assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
- @mock.patch(
- "gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout
- )
+ @mock.patch("gradient._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
async def test_overwrite_retry_count_header(
self,
@@ -2264,9 +2116,7 @@ async def test_get_platform(self) -> None:
platform = await asyncify(get_platform)()
assert isinstance(platform, (str, OtherPlatform))
- async def test_proxy_environment_variables(
- self, monkeypatch: pytest.MonkeyPatch
- ) -> None:
+ 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")
@@ -2289,34 +2139,22 @@ async def test_default_client_creation(self) -> None:
)
@pytest.mark.respx(base_url=base_url)
- async def test_follow_redirects(
- self, respx_mock: MockRouter, async_client: AsyncGradient
- ) -> None:
+ async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncGradient) -> 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"})
+ 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 async_client.post(
- "/redirect", body={"key": "value"}, cast_to=httpx.Response
- )
+ response = await async_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, async_client: AsyncGradient
- ) -> None:
+ async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncGradient) -> 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"}
- )
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)
with pytest.raises(APIStatusError) as exc_info:
From f67228f0bd8de96342d15b1b7c3096e4b03c7aaa Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 11 Feb 2026 22:42:20 +0000
Subject: [PATCH 3/8] feat(api): api update
---
.github/workflows/ci.yml | 6 +-
.github/workflows/publish-pypi.yml | 2 +-
.github/workflows/release-doctor.yml | 2 +-
.stats.yml | 8 +-
api.md | 36 +
requirements-dev.lock | 20 +-
requirements.lock | 8 +-
src/gradient/_base_client.py | 168 +++-
src/gradient/_client.py | 118 ++-
src/gradient/_compat.py | 6 +-
src/gradient/_models.py | 17 +-
src/gradient/_types.py | 9 +
src/gradient/_utils/_compat.py | 2 +-
src/gradient/_utils/_json.py | 35 +
src/gradient/resources/__init__.py | 42 +
.../resources/agents/evaluation_datasets.py | 11 +
.../resources/agents/evaluation_runs.py | 12 +-
.../resources/agents/evaluation_test_cases.py | 4 +
src/gradient/resources/apps/__init__.py | 33 +
src/gradient/resources/apps/apps.py | 102 +++
.../resources/apps/job_invocations.py | 191 ++++
src/gradient/resources/billing.py | 226 +++++
.../resources/knowledge_bases/data_sources.py | 213 +++++
src/gradient/resources/responses.py | 860 ++++++++++++++++++
src/gradient/resources/retrieve.py | 12 +
src/gradient/types/__init__.py | 7 +
.../types/agents/api_evaluation_prompt.py | 57 +-
.../types/agents/api_evaluation_run.py | 3 +
.../agents/chat/completion_create_params.py | 315 ++++++-
.../evaluation_dataset_create_params.py | 6 +-
.../agents/evaluation_run_create_params.py | 5 +-
.../evaluation_test_case_create_params.py | 2 +
src/gradient/types/api_agent_model.py | 9 +
src/gradient/types/api_model.py | 9 +
src/gradient/types/apps/__init__.py | 6 +
.../apps/job_invocation_cancel_params.py | 14 +
.../apps/job_invocation_cancel_response.py | 77 ++
.../types/billing_list_insights_params.py | 23 +
.../types/billing_list_insights_response.py | 51 ++
.../types/chat/completion_create_params.py | 315 ++++++-
.../types/knowledge_base_create_params.py | 49 +-
.../types/knowledge_bases/__init__.py | 2 +
.../api_knowledge_base_data_source.py | 50 +-
.../data_source_create_params.py | 43 +-
.../data_source_update_params.py | 58 ++
.../data_source_update_response.py | 17 +
src/gradient/types/response_create_params.py | 331 +++++++
src/gradient/types/shared/__init__.py | 2 +
src/gradient/types/shared/completion_usage.py | 21 +-
.../types/shared/create_response_response.py | 332 +++++++
.../shared/create_response_stream_response.py | 139 +++
.../agents/test_evaluation_datasets.py | 2 +
.../agents/test_evaluation_runs.py | 2 +
.../agents/test_evaluation_test_cases.py | 2 +
tests/api_resources/apps/__init__.py | 1 +
.../apps/test_job_invocations.py | 148 +++
.../api_resources/gpu_droplets/test_images.py | 8 +-
.../knowledge_bases/test_data_sources.py | 159 ++++
tests/api_resources/test_billing.py | 177 ++++
tests/api_resources/test_knowledge_bases.py | 14 +
tests/api_resources/test_responses.py | 296 ++++++
tests/test_client.py | 187 +++-
tests/test_utils/test_json.py | 126 +++
63 files changed, 5096 insertions(+), 112 deletions(-)
create mode 100644 src/gradient/_utils/_json.py
create mode 100644 src/gradient/resources/apps/__init__.py
create mode 100644 src/gradient/resources/apps/apps.py
create mode 100644 src/gradient/resources/apps/job_invocations.py
create mode 100644 src/gradient/resources/billing.py
create mode 100644 src/gradient/resources/responses.py
create mode 100644 src/gradient/types/apps/__init__.py
create mode 100644 src/gradient/types/apps/job_invocation_cancel_params.py
create mode 100644 src/gradient/types/apps/job_invocation_cancel_response.py
create mode 100644 src/gradient/types/billing_list_insights_params.py
create mode 100644 src/gradient/types/billing_list_insights_response.py
create mode 100644 src/gradient/types/knowledge_bases/data_source_update_params.py
create mode 100644 src/gradient/types/knowledge_bases/data_source_update_response.py
create mode 100644 src/gradient/types/response_create_params.py
create mode 100644 src/gradient/types/shared/create_response_response.py
create mode 100644 src/gradient/types/shared/create_response_stream_response.py
create mode 100644 tests/api_resources/apps/__init__.py
create mode 100644 tests/api_resources/apps/test_job_invocations.py
create mode 100644 tests/api_resources/test_billing.py
create mode 100644 tests/api_resources/test_responses.py
create mode 100644 tests/test_utils/test_json.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 42a31c2a..9fb3fd00 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/gradient-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
@@ -44,7 +44,7 @@ jobs:
id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/gradient-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
@@ -81,7 +81,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/gradient-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index cb2d6509..2b8edb2f 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 9c8912bc..bde3b3ed 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'digitalocean/gradient-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Check release environment
run: |
diff --git a/.stats.yml b/.stats.yml
index d321100e..0d591538 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 189
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/digitalocean%2Fgradient-0778b2e9d56c826f92ee69ef081d8d73fd94c139b85e11becaa88bf1cbe95fb9.yml
-openapi_spec_hash: 49daca0dd735cad7200ca1c741a5dd43
-config_hash: fad48c8ac796b240fe3b90181586d1a4
+configured_endpoints: 193
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/digitalocean%2Fgradient-2344b44246a44d39ad5b74d3077bd2958745aad67feb15970756532fa0b3f9d6.yml
+openapi_spec_hash: a1913979235ce152a8dc380fabe5362e
+config_hash: 6c9a04f3cc5dd88e1e4f0ae42d98ba9a
diff --git a/api.md b/api.md
index caf241a4..45e4eaeb 100644
--- a/api.md
+++ b/api.md
@@ -10,6 +10,8 @@ from gradient.types import (
ChatCompletionChunk,
ChatCompletionTokenLogprob,
CompletionUsage,
+ CreateResponseResponse,
+ CreateResponseStreamResponse,
DiskInfo,
Droplet,
DropletNextBackupWindow,
@@ -404,6 +406,12 @@ Methods:
- client.images.generate(\*\*params) -> ImageGenerateResponse
+# Responses
+
+Methods:
+
+- client.responses.create(\*\*params) -> CreateResponseResponse
+
# GPUDroplets
Types:
@@ -856,6 +864,7 @@ from gradient.types.knowledge_bases import (
APIWebCrawlerDataSource,
AwsDataSource,
DataSourceCreateResponse,
+ DataSourceUpdateResponse,
DataSourceListResponse,
DataSourceDeleteResponse,
DataSourceCreatePresignedURLsResponse,
@@ -865,6 +874,7 @@ from gradient.types.knowledge_bases import (
Methods:
- client.knowledge_bases.data_sources.create(path_knowledge_base_uuid, \*\*params) -> DataSourceCreateResponse
+- client.knowledge_bases.data_sources.update(path_data_source_uuid, \*, path_knowledge_base_uuid, \*\*params) -> DataSourceUpdateResponse
- client.knowledge_bases.data_sources.list(knowledge_base_uuid, \*\*params) -> DataSourceListResponse
- client.knowledge_bases.data_sources.delete(data_source_uuid, \*, knowledge_base_uuid) -> DataSourceDeleteResponse
- client.knowledge_bases.data_sources.create_presigned_urls(\*\*params) -> DataSourceCreatePresignedURLsResponse
@@ -1039,3 +1049,29 @@ from gradient.types import RetrieveDocumentsResponse
Methods:
- client.retrieve.documents(knowledge_base_id, \*\*params) -> RetrieveDocumentsResponse
+
+# Apps
+
+## JobInvocations
+
+Types:
+
+```python
+from gradient.types.apps import JobInvocationCancelResponse
+```
+
+Methods:
+
+- client.apps.job_invocations.cancel(job_invocation_id, \*, app_id, \*\*params) -> JobInvocationCancelResponse
+
+# Billing
+
+Types:
+
+```python
+from gradient.types import BillingListInsightsResponse
+```
+
+Methods:
+
+- client.billing.list_insights(end_date, \*, account_urn, start_date, \*\*params) -> BillingListInsightsResponse
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 63b7bd64..667e0dff 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -12,14 +12,14 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
-aiohttp==3.13.2
+aiohttp==3.13.3
# via gradient
# via httpx-aiohttp
aiosignal==1.4.0
# via aiohttp
annotated-types==0.7.0
# via pydantic
-anyio==4.12.0
+anyio==4.12.1
# via gradient
# via httpx
argcomplete==3.6.3
@@ -31,7 +31,7 @@ attrs==25.4.0
# via nox
backports-asyncio-runner==1.2.0
# via pytest-asyncio
-certifi==2025.11.12
+certifi==2026.1.4
# via httpcore
# via httpx
colorlog==6.10.1
@@ -61,7 +61,7 @@ httpx==0.28.1
# via gradient
# via httpx-aiohttp
# via respx
-httpx-aiohttp==0.1.9
+httpx-aiohttp==0.1.12
# via gradient
humanize==4.13.0
# via nox
@@ -69,7 +69,7 @@ idna==3.11
# via anyio
# via httpx
# via yarl
-importlib-metadata==8.7.0
+importlib-metadata==8.7.1
iniconfig==2.1.0
# via pytest
markdown-it-py==3.0.0
@@ -82,14 +82,14 @@ multidict==6.7.0
mypy==1.17.0
mypy-extensions==1.1.0
# via mypy
-nodeenv==1.9.1
+nodeenv==1.10.0
# via pyright
nox==2025.11.12
packaging==25.0
# via dependency-groups
# via nox
# via pytest
-pathspec==0.12.1
+pathspec==1.0.3
# via mypy
platformdirs==4.4.0
# via virtualenv
@@ -115,13 +115,13 @@ python-dateutil==2.9.0.post0
# via time-machine
respx==0.22.0
rich==14.2.0
-ruff==0.14.7
+ruff==0.14.13
six==1.17.0
# via python-dateutil
sniffio==1.3.1
# via gradient
time-machine==2.19.0
-tomli==2.3.0
+tomli==2.4.0
# via dependency-groups
# via mypy
# via nox
@@ -141,7 +141,7 @@ typing-extensions==4.15.0
# via virtualenv
typing-inspection==0.4.2
# via pydantic
-virtualenv==20.35.4
+virtualenv==20.36.1
# via nox
yarl==1.22.0
# via aiohttp
diff --git a/requirements.lock b/requirements.lock
index b2623a7b..b48c65ea 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -12,21 +12,21 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
-aiohttp==3.13.2
+aiohttp==3.13.3
# via gradient
# via httpx-aiohttp
aiosignal==1.4.0
# via aiohttp
annotated-types==0.7.0
# via pydantic
-anyio==4.12.0
+anyio==4.12.1
# via gradient
# via httpx
async-timeout==5.0.1
# via aiohttp
attrs==25.4.0
# via aiohttp
-certifi==2025.11.12
+certifi==2026.1.4
# via httpcore
# via httpx
distro==1.9.0
@@ -43,7 +43,7 @@ httpcore==1.0.9
httpx==0.28.1
# via gradient
# via httpx-aiohttp
-httpx-aiohttp==0.1.9
+httpx-aiohttp==0.1.12
# via gradient
idna==3.11
# via anyio
diff --git a/src/gradient/_base_client.py b/src/gradient/_base_client.py
index f038b215..e2135952 100644
--- a/src/gradient/_base_client.py
+++ b/src/gradient/_base_client.py
@@ -9,6 +9,7 @@
import inspect
import logging
import platform
+import warnings
import email.utils
from types import TracebackType
from random import random
@@ -51,9 +52,11 @@
ResponseT,
AnyMapping,
PostParser,
+ BinaryTypes,
RequestFiles,
HttpxSendArgs,
RequestOptions,
+ AsyncBinaryTypes,
HttpxRequestFiles,
ModelBuilderProtocol,
not_given,
@@ -83,6 +86,7 @@
APIConnectionError,
APIResponseValidationError,
)
+from ._utils._json import openapi_dumps
log: logging.Logger = logging.getLogger(__name__)
@@ -481,8 +485,19 @@ def _build_request(
retries_taken: int = 0,
) -> httpx.Request:
if log.isEnabledFor(logging.DEBUG):
- log.debug("Request options: %s", model_dump(options, exclude_unset=True))
-
+ log.debug(
+ "Request options: %s",
+ model_dump(
+ options,
+ exclude_unset=True,
+ # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
+ exclude={
+ "content",
+ }
+ if PYDANTIC_V1
+ else {},
+ ),
+ )
kwargs: dict[str, Any] = {}
json_data = options.json_data
@@ -536,10 +551,18 @@ def _build_request(
is_body_allowed = options.method.lower() != "get"
if is_body_allowed:
- if isinstance(json_data, bytes):
+ if options.content is not None and json_data is not None:
+ raise TypeError("Passing both `content` and `json_data` is not supported")
+ if options.content is not None and files is not None:
+ raise TypeError("Passing both `content` and `files` is not supported")
+ if options.content is not None:
+ kwargs["content"] = options.content
+ elif isinstance(json_data, bytes):
kwargs["content"] = json_data
- else:
- kwargs["json"] = json_data if is_given(json_data) else None
+ elif not files:
+ # Don't set content when JSON is sent as multipart/form-data,
+ # since httpx's content param overrides other body arguments
+ kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
@@ -1210,6 +1233,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[False] = False,
@@ -1222,6 +1246,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[True],
@@ -1235,6 +1260,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool,
@@ -1247,17 +1273,25 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool = False,
stream_cls: type[_StreamT] | None = None,
) -> ResponseT | _StreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="post",
- url=path,
- json_data=body,
- files=to_httpx_files(files),
- **options,
+ method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
@@ -1267,11 +1301,23 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)
@@ -1281,15 +1327,23 @@ def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="put",
- url=path,
- json_data=body,
- files=to_httpx_files(files),
- **options,
+ method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)
@@ -1299,9 +1353,19 @@ def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return self.request(cast_to, opts)
def get_api_list(
@@ -1751,6 +1815,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[False] = False,
@@ -1763,6 +1828,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[True],
@@ -1776,6 +1842,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool,
@@ -1788,17 +1855,25 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool = False,
stream_cls: type[_AsyncStreamT] | None = None,
) -> ResponseT | _AsyncStreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="post",
- url=path,
- json_data=body,
- files=await async_to_httpx_files(files),
- **options,
+ method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
@@ -1808,11 +1883,28 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="patch",
+ url=path,
+ json_data=body,
+ content=content,
+ files=await async_to_httpx_files(files),
+ **options,
)
return await self.request(cast_to, opts)
@@ -1822,15 +1914,23 @@ async def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="put",
- url=path,
- json_data=body,
- files=await async_to_httpx_files(files),
- **options,
+ method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts)
@@ -1840,9 +1940,19 @@ async def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return await self.request(cast_to, opts)
def get_api_list(
diff --git a/src/gradient/_client.py b/src/gradient/_client.py
index fc58b291..18ef0c7a 100644
--- a/src/gradient/_client.py
+++ b/src/gradient/_client.py
@@ -34,26 +34,28 @@
if TYPE_CHECKING:
from .resources import (
nfs,
+ apps,
chat,
agents,
images,
models,
+ billing,
regions,
retrieve,
databases,
inference,
+ responses,
gpu_droplets,
knowledge_bases,
)
from .resources.images import ImagesResource, AsyncImagesResource
+ from .resources.billing import BillingResource, AsyncBillingResource
from .resources.nfs.nfs import NfsResource, AsyncNfsResource
from .resources.regions import RegionsResource, AsyncRegionsResource
from .resources.retrieve import RetrieveResource, AsyncRetrieveResource
+ from .resources.apps.apps import AppsResource, AsyncAppsResource
from .resources.chat.chat import ChatResource, AsyncChatResource
- from .resources.gpu_droplets import (
- GPUDropletsResource,
- AsyncGPUDropletsResource,
- )
+ from .resources.responses import ResponsesResource, AsyncResponsesResource
from .resources.agents.agents import AgentsResource, AsyncAgentsResource
from .resources.models.models import ModelsResource, AsyncModelsResource
from .resources.databases.databases import DatabasesResource, AsyncDatabasesResource
@@ -201,6 +203,12 @@ def images(self) -> ImagesResource:
return ImagesResource(self)
+ @cached_property
+ def responses(self) -> ResponsesResource:
+ from .resources.responses import ResponsesResource
+
+ return ResponsesResource(self)
+
@cached_property
def gpu_droplets(self) -> GPUDropletsResource:
from .resources.gpu_droplets import GPUDropletsResource
@@ -249,6 +257,18 @@ def retrieve(self) -> RetrieveResource:
return RetrieveResource(self)
+ @cached_property
+ def apps(self) -> AppsResource:
+ from .resources.apps import AppsResource
+
+ return AppsResource(self)
+
+ @cached_property
+ def billing(self) -> BillingResource:
+ from .resources.billing import BillingResource
+
+ return BillingResource(self)
+
@cached_property
def with_raw_response(self) -> GradientWithRawResponse:
return GradientWithRawResponse(self)
@@ -555,6 +575,12 @@ def images(self) -> AsyncImagesResource:
return AsyncImagesResource(self)
+ @cached_property
+ def responses(self) -> AsyncResponsesResource:
+ from .resources.responses import AsyncResponsesResource
+
+ return AsyncResponsesResource(self)
+
@cached_property
def gpu_droplets(self) -> AsyncGPUDropletsResource:
from .resources.gpu_droplets import AsyncGPUDropletsResource
@@ -603,6 +629,18 @@ def retrieve(self) -> AsyncRetrieveResource:
return AsyncRetrieveResource(self)
+ @cached_property
+ def apps(self) -> AsyncAppsResource:
+ from .resources.apps import AsyncAppsResource
+
+ return AsyncAppsResource(self)
+
+ @cached_property
+ def billing(self) -> AsyncBillingResource:
+ from .resources.billing import AsyncBillingResource
+
+ return AsyncBillingResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncGradientWithRawResponse:
return AsyncGradientWithRawResponse(self)
@@ -807,6 +845,12 @@ def images(self) -> images.ImagesResourceWithRawResponse:
return ImagesResourceWithRawResponse(self._client.images)
+ @cached_property
+ def responses(self) -> responses.ResponsesResourceWithRawResponse:
+ from .resources.responses import ResponsesResourceWithRawResponse
+
+ return ResponsesResourceWithRawResponse(self._client.responses)
+
@cached_property
def gpu_droplets(self) -> gpu_droplets.GPUDropletsResourceWithRawResponse:
from .resources.gpu_droplets import GPUDropletsResourceWithRawResponse
@@ -855,6 +899,18 @@ def retrieve(self) -> retrieve.RetrieveResourceWithRawResponse:
return RetrieveResourceWithRawResponse(self._client.retrieve)
+ @cached_property
+ def apps(self) -> apps.AppsResourceWithRawResponse:
+ from .resources.apps import AppsResourceWithRawResponse
+
+ return AppsResourceWithRawResponse(self._client.apps)
+
+ @cached_property
+ def billing(self) -> billing.BillingResourceWithRawResponse:
+ from .resources.billing import BillingResourceWithRawResponse
+
+ return BillingResourceWithRawResponse(self._client.billing)
+
class AsyncGradientWithRawResponse:
_client: AsyncGradient
@@ -880,6 +936,12 @@ def images(self) -> images.AsyncImagesResourceWithRawResponse:
return AsyncImagesResourceWithRawResponse(self._client.images)
+ @cached_property
+ def responses(self) -> responses.AsyncResponsesResourceWithRawResponse:
+ from .resources.responses import AsyncResponsesResourceWithRawResponse
+
+ return AsyncResponsesResourceWithRawResponse(self._client.responses)
+
@cached_property
def gpu_droplets(self) -> gpu_droplets.AsyncGPUDropletsResourceWithRawResponse:
from .resources.gpu_droplets import AsyncGPUDropletsResourceWithRawResponse
@@ -932,6 +994,18 @@ def retrieve(self) -> retrieve.AsyncRetrieveResourceWithRawResponse:
return AsyncRetrieveResourceWithRawResponse(self._client.retrieve)
+ @cached_property
+ def apps(self) -> apps.AsyncAppsResourceWithRawResponse:
+ from .resources.apps import AsyncAppsResourceWithRawResponse
+
+ return AsyncAppsResourceWithRawResponse(self._client.apps)
+
+ @cached_property
+ def billing(self) -> billing.AsyncBillingResourceWithRawResponse:
+ from .resources.billing import AsyncBillingResourceWithRawResponse
+
+ return AsyncBillingResourceWithRawResponse(self._client.billing)
+
class GradientWithStreamedResponse:
_client: Gradient
@@ -957,6 +1031,12 @@ def images(self) -> images.ImagesResourceWithStreamingResponse:
return ImagesResourceWithStreamingResponse(self._client.images)
+ @cached_property
+ def responses(self) -> responses.ResponsesResourceWithStreamingResponse:
+ from .resources.responses import ResponsesResourceWithStreamingResponse
+
+ return ResponsesResourceWithStreamingResponse(self._client.responses)
+
@cached_property
def gpu_droplets(self) -> gpu_droplets.GPUDropletsResourceWithStreamingResponse:
from .resources.gpu_droplets import GPUDropletsResourceWithStreamingResponse
@@ -1009,6 +1089,18 @@ def retrieve(self) -> retrieve.RetrieveResourceWithStreamingResponse:
return RetrieveResourceWithStreamingResponse(self._client.retrieve)
+ @cached_property
+ def apps(self) -> apps.AppsResourceWithStreamingResponse:
+ from .resources.apps import AppsResourceWithStreamingResponse
+
+ return AppsResourceWithStreamingResponse(self._client.apps)
+
+ @cached_property
+ def billing(self) -> billing.BillingResourceWithStreamingResponse:
+ from .resources.billing import BillingResourceWithStreamingResponse
+
+ return BillingResourceWithStreamingResponse(self._client.billing)
+
class AsyncGradientWithStreamedResponse:
_client: AsyncGradient
@@ -1034,6 +1126,12 @@ def images(self) -> images.AsyncImagesResourceWithStreamingResponse:
return AsyncImagesResourceWithStreamingResponse(self._client.images)
+ @cached_property
+ def responses(self) -> responses.AsyncResponsesResourceWithStreamingResponse:
+ from .resources.responses import AsyncResponsesResourceWithStreamingResponse
+
+ return AsyncResponsesResourceWithStreamingResponse(self._client.responses)
+
@cached_property
def gpu_droplets(
self,
@@ -1090,6 +1188,18 @@ def retrieve(self) -> retrieve.AsyncRetrieveResourceWithStreamingResponse:
return AsyncRetrieveResourceWithStreamingResponse(self._client.retrieve)
+ @cached_property
+ def apps(self) -> apps.AsyncAppsResourceWithStreamingResponse:
+ from .resources.apps import AsyncAppsResourceWithStreamingResponse
+
+ return AsyncAppsResourceWithStreamingResponse(self._client.apps)
+
+ @cached_property
+ def billing(self) -> billing.AsyncBillingResourceWithStreamingResponse:
+ from .resources.billing import AsyncBillingResourceWithStreamingResponse
+
+ return AsyncBillingResourceWithStreamingResponse(self._client.billing)
+
Client = Gradient
diff --git a/src/gradient/_compat.py b/src/gradient/_compat.py
index bdef67f0..786ff42a 100644
--- a/src/gradient/_compat.py
+++ b/src/gradient/_compat.py
@@ -139,6 +139,7 @@ def model_dump(
exclude_defaults: bool = False,
warnings: bool = True,
mode: Literal["json", "python"] = "python",
+ by_alias: bool | None = None,
) -> dict[str, Any]:
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
return model.model_dump(
@@ -148,13 +149,12 @@ def model_dump(
exclude_defaults=exclude_defaults,
# warnings are not supported in Pydantic v1
warnings=True if PYDANTIC_V1 else warnings,
+ by_alias=by_alias,
)
return cast(
"dict[str, Any]",
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
- exclude=exclude,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
+ exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
),
)
diff --git a/src/gradient/_models.py b/src/gradient/_models.py
index ca9500b2..29070e05 100644
--- a/src/gradient/_models.py
+++ b/src/gradient/_models.py
@@ -3,7 +3,20 @@
import os
import inspect
import weakref
-from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Type,
+ Union,
+ Generic,
+ TypeVar,
+ Callable,
+ Iterable,
+ Optional,
+ AsyncIterable,
+ cast,
+)
from datetime import date, datetime
from typing_extensions import (
List,
@@ -787,6 +800,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
timeout: float | Timeout | None
files: HttpxRequestFiles | None
idempotency_key: str
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None]
json_data: Body
extra_json: AnyMapping
follow_redirects: bool
@@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel):
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
follow_redirects: Union[bool, None] = None
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None
# It should be noted that we cannot use `json` here as that would override
# a BaseModel method in an incompatible fashion.
json_data: Union[Body, None] = None
diff --git a/src/gradient/_types.py b/src/gradient/_types.py
index 65831fee..338a463d 100644
--- a/src/gradient/_types.py
+++ b/src/gradient/_types.py
@@ -13,9 +13,11 @@
Mapping,
TypeVar,
Callable,
+ Iterable,
Iterator,
Optional,
Sequence,
+ AsyncIterable,
)
from typing_extensions import (
Set,
@@ -56,6 +58,13 @@
else:
Base64FileInput = Union[IO[bytes], PathLike]
FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8.
+
+
+# Used for sending raw binary data / streaming data in request bodies
+# e.g. for file uploads without multipart encoding
+BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
+AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]
+
FileTypes = Union[
# file (or bytes)
FileContent,
diff --git a/src/gradient/_utils/_compat.py b/src/gradient/_utils/_compat.py
index dd703233..2c70b299 100644
--- a/src/gradient/_utils/_compat.py
+++ b/src/gradient/_utils/_compat.py
@@ -26,7 +26,7 @@ def is_union(tp: Optional[Type[Any]]) -> bool:
else:
import types
- return tp is Union or tp is types.UnionType
+ return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap]
def is_typeddict(tp: Type[Any]) -> bool:
diff --git a/src/gradient/_utils/_json.py b/src/gradient/_utils/_json.py
new file mode 100644
index 00000000..60584214
--- /dev/null
+++ b/src/gradient/_utils/_json.py
@@ -0,0 +1,35 @@
+import json
+from typing import Any
+from datetime import datetime
+from typing_extensions import override
+
+import pydantic
+
+from .._compat import model_dump
+
+
+def openapi_dumps(obj: Any) -> bytes:
+ """
+ Serialize an object to UTF-8 encoded JSON bytes.
+
+ Extends the standard json.dumps with support for additional types
+ commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
+ """
+ return json.dumps(
+ obj,
+ cls=_CustomEncoder,
+ # Uses the same defaults as httpx's JSON serialization
+ ensure_ascii=False,
+ separators=(",", ":"),
+ allow_nan=False,
+ ).encode()
+
+
+class _CustomEncoder(json.JSONEncoder):
+ @override
+ def default(self, o: Any) -> Any:
+ if isinstance(o, datetime):
+ return o.isoformat()
+ if isinstance(o, pydantic.BaseModel):
+ return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
+ return super().default(o)
diff --git a/src/gradient/resources/__init__.py b/src/gradient/resources/__init__.py
index f668bb06..6f218273 100644
--- a/src/gradient/resources/__init__.py
+++ b/src/gradient/resources/__init__.py
@@ -8,6 +8,14 @@
NfsResourceWithStreamingResponse,
AsyncNfsResourceWithStreamingResponse,
)
+from .apps import (
+ AppsResource,
+ AsyncAppsResource,
+ AppsResourceWithRawResponse,
+ AsyncAppsResourceWithRawResponse,
+ AppsResourceWithStreamingResponse,
+ AsyncAppsResourceWithStreamingResponse,
+)
from .chat import (
ChatResource,
AsyncChatResource,
@@ -40,6 +48,14 @@
ModelsResourceWithStreamingResponse,
AsyncModelsResourceWithStreamingResponse,
)
+from .billing import (
+ BillingResource,
+ AsyncBillingResource,
+ BillingResourceWithRawResponse,
+ AsyncBillingResourceWithRawResponse,
+ BillingResourceWithStreamingResponse,
+ AsyncBillingResourceWithStreamingResponse,
+)
from .regions import (
RegionsResource,
AsyncRegionsResource,
@@ -72,6 +88,14 @@
InferenceResourceWithStreamingResponse,
AsyncInferenceResourceWithStreamingResponse,
)
+from .responses import (
+ ResponsesResource,
+ AsyncResponsesResource,
+ ResponsesResourceWithRawResponse,
+ AsyncResponsesResourceWithRawResponse,
+ ResponsesResourceWithStreamingResponse,
+ AsyncResponsesResourceWithStreamingResponse,
+)
from .gpu_droplets import (
GPUDropletsResource,
AsyncGPUDropletsResource,
@@ -108,6 +132,12 @@
"AsyncImagesResourceWithRawResponse",
"ImagesResourceWithStreamingResponse",
"AsyncImagesResourceWithStreamingResponse",
+ "ResponsesResource",
+ "AsyncResponsesResource",
+ "ResponsesResourceWithRawResponse",
+ "AsyncResponsesResourceWithRawResponse",
+ "ResponsesResourceWithStreamingResponse",
+ "AsyncResponsesResourceWithStreamingResponse",
"GPUDropletsResource",
"AsyncGPUDropletsResource",
"GPUDropletsResourceWithRawResponse",
@@ -156,4 +186,16 @@
"AsyncRetrieveResourceWithRawResponse",
"RetrieveResourceWithStreamingResponse",
"AsyncRetrieveResourceWithStreamingResponse",
+ "AppsResource",
+ "AsyncAppsResource",
+ "AppsResourceWithRawResponse",
+ "AsyncAppsResourceWithRawResponse",
+ "AppsResourceWithStreamingResponse",
+ "AsyncAppsResourceWithStreamingResponse",
+ "BillingResource",
+ "AsyncBillingResource",
+ "BillingResourceWithRawResponse",
+ "AsyncBillingResourceWithRawResponse",
+ "BillingResourceWithStreamingResponse",
+ "AsyncBillingResourceWithStreamingResponse",
]
diff --git a/src/gradient/resources/agents/evaluation_datasets.py b/src/gradient/resources/agents/evaluation_datasets.py
index 0f9631ba..db9b473d 100644
--- a/src/gradient/resources/agents/evaluation_datasets.py
+++ b/src/gradient/resources/agents/evaluation_datasets.py
@@ -3,6 +3,7 @@
from __future__ import annotations
from typing import Iterable
+from typing_extensions import Literal
import httpx
@@ -53,6 +54,10 @@ def with_streaming_response(self) -> EvaluationDatasetsResourceWithStreamingResp
def create(
self,
*,
+ dataset_type: Literal[
+ "EVALUATION_DATASET_TYPE_UNKNOWN", "EVALUATION_DATASET_TYPE_ADK", "EVALUATION_DATASET_TYPE_NON_ADK"
+ ]
+ | Omit = omit,
file_upload_dataset: APIFileUploadDataSourceParam | Omit = omit,
name: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -85,6 +90,7 @@ def create(
else "https://api.digitalocean.com/v2/gen-ai/evaluation_datasets",
body=maybe_transform(
{
+ "dataset_type": dataset_type,
"file_upload_dataset": file_upload_dataset,
"name": name,
},
@@ -160,6 +166,10 @@ def with_streaming_response(self) -> AsyncEvaluationDatasetsResourceWithStreamin
async def create(
self,
*,
+ dataset_type: Literal[
+ "EVALUATION_DATASET_TYPE_UNKNOWN", "EVALUATION_DATASET_TYPE_ADK", "EVALUATION_DATASET_TYPE_NON_ADK"
+ ]
+ | Omit = omit,
file_upload_dataset: APIFileUploadDataSourceParam | Omit = omit,
name: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -192,6 +202,7 @@ async def create(
else "https://api.digitalocean.com/v2/gen-ai/evaluation_datasets",
body=await async_maybe_transform(
{
+ "dataset_type": dataset_type,
"file_upload_dataset": file_upload_dataset,
"name": name,
},
diff --git a/src/gradient/resources/agents/evaluation_runs.py b/src/gradient/resources/agents/evaluation_runs.py
index 8506b00f..2b5745af 100644
--- a/src/gradient/resources/agents/evaluation_runs.py
+++ b/src/gradient/resources/agents/evaluation_runs.py
@@ -47,6 +47,7 @@ def with_streaming_response(self) -> EvaluationRunsResourceWithStreamingResponse
def create(
self,
*,
+ agent_deployment_names: SequenceNotStr[str] | Omit = omit,
agent_uuids: SequenceNotStr[str] | Omit = omit,
run_name: str | Omit = omit,
test_case_uuid: str | Omit = omit,
@@ -62,7 +63,9 @@ def create(
`/v2/gen-ai/evaluation_runs`.
Args:
- agent_uuids: Agent UUIDs to run the test case against.
+ agent_deployment_names: Agent deployment names to run the test case against (ADK agent workspaces).
+
+ agent_uuids: Agent UUIDs to run the test case against (legacy agents).
run_name: The name of the run.
@@ -82,6 +85,7 @@ def create(
else "https://api.digitalocean.com/v2/gen-ai/evaluation_runs",
body=maybe_transform(
{
+ "agent_deployment_names": agent_deployment_names,
"agent_uuids": agent_uuids,
"run_name": run_name,
"test_case_uuid": test_case_uuid,
@@ -249,6 +253,7 @@ def with_streaming_response(self) -> AsyncEvaluationRunsResourceWithStreamingRes
async def create(
self,
*,
+ agent_deployment_names: SequenceNotStr[str] | Omit = omit,
agent_uuids: SequenceNotStr[str] | Omit = omit,
run_name: str | Omit = omit,
test_case_uuid: str | Omit = omit,
@@ -264,7 +269,9 @@ async def create(
`/v2/gen-ai/evaluation_runs`.
Args:
- agent_uuids: Agent UUIDs to run the test case against.
+ agent_deployment_names: Agent deployment names to run the test case against (ADK agent workspaces).
+
+ agent_uuids: Agent UUIDs to run the test case against (legacy agents).
run_name: The name of the run.
@@ -284,6 +291,7 @@ async def create(
else "https://api.digitalocean.com/v2/gen-ai/evaluation_runs",
body=await async_maybe_transform(
{
+ "agent_deployment_names": agent_deployment_names,
"agent_uuids": agent_uuids,
"run_name": run_name,
"test_case_uuid": test_case_uuid,
diff --git a/src/gradient/resources/agents/evaluation_test_cases.py b/src/gradient/resources/agents/evaluation_test_cases.py
index d53b8c26..0e8cce03 100644
--- a/src/gradient/resources/agents/evaluation_test_cases.py
+++ b/src/gradient/resources/agents/evaluation_test_cases.py
@@ -56,6 +56,7 @@ def with_streaming_response(self) -> EvaluationTestCasesResourceWithStreamingRes
def create(
self,
*,
+ agent_workspace_name: str | Omit = omit,
dataset_uuid: str | Omit = omit,
description: str | Omit = omit,
metrics: SequenceNotStr[str] | Omit = omit,
@@ -98,6 +99,7 @@ def create(
else "https://api.digitalocean.com/v2/gen-ai/evaluation_test_cases",
body=maybe_transform(
{
+ "agent_workspace_name": agent_workspace_name,
"dataset_uuid": dataset_uuid,
"description": description,
"metrics": metrics,
@@ -318,6 +320,7 @@ def with_streaming_response(self) -> AsyncEvaluationTestCasesResourceWithStreami
async def create(
self,
*,
+ agent_workspace_name: str | Omit = omit,
dataset_uuid: str | Omit = omit,
description: str | Omit = omit,
metrics: SequenceNotStr[str] | Omit = omit,
@@ -360,6 +363,7 @@ async def create(
else "https://api.digitalocean.com/v2/gen-ai/evaluation_test_cases",
body=await async_maybe_transform(
{
+ "agent_workspace_name": agent_workspace_name,
"dataset_uuid": dataset_uuid,
"description": description,
"metrics": metrics,
diff --git a/src/gradient/resources/apps/__init__.py b/src/gradient/resources/apps/__init__.py
new file mode 100644
index 00000000..3033a599
--- /dev/null
+++ b/src/gradient/resources/apps/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .apps import (
+ AppsResource,
+ AsyncAppsResource,
+ AppsResourceWithRawResponse,
+ AsyncAppsResourceWithRawResponse,
+ AppsResourceWithStreamingResponse,
+ AsyncAppsResourceWithStreamingResponse,
+)
+from .job_invocations import (
+ JobInvocationsResource,
+ AsyncJobInvocationsResource,
+ JobInvocationsResourceWithRawResponse,
+ AsyncJobInvocationsResourceWithRawResponse,
+ JobInvocationsResourceWithStreamingResponse,
+ AsyncJobInvocationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "JobInvocationsResource",
+ "AsyncJobInvocationsResource",
+ "JobInvocationsResourceWithRawResponse",
+ "AsyncJobInvocationsResourceWithRawResponse",
+ "JobInvocationsResourceWithStreamingResponse",
+ "AsyncJobInvocationsResourceWithStreamingResponse",
+ "AppsResource",
+ "AsyncAppsResource",
+ "AppsResourceWithRawResponse",
+ "AsyncAppsResourceWithRawResponse",
+ "AppsResourceWithStreamingResponse",
+ "AsyncAppsResourceWithStreamingResponse",
+]
diff --git a/src/gradient/resources/apps/apps.py b/src/gradient/resources/apps/apps.py
new file mode 100644
index 00000000..889f2406
--- /dev/null
+++ b/src/gradient/resources/apps/apps.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from .job_invocations import (
+ JobInvocationsResource,
+ AsyncJobInvocationsResource,
+ JobInvocationsResourceWithRawResponse,
+ AsyncJobInvocationsResourceWithRawResponse,
+ JobInvocationsResourceWithStreamingResponse,
+ AsyncJobInvocationsResourceWithStreamingResponse,
+)
+
+__all__ = ["AppsResource", "AsyncAppsResource"]
+
+
+class AppsResource(SyncAPIResource):
+ @cached_property
+ def job_invocations(self) -> JobInvocationsResource:
+ return JobInvocationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AppsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#accessing-raw-response-data-eg-headers
+ """
+ return AppsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AppsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#with_streaming_response
+ """
+ return AppsResourceWithStreamingResponse(self)
+
+
+class AsyncAppsResource(AsyncAPIResource):
+ @cached_property
+ def job_invocations(self) -> AsyncJobInvocationsResource:
+ return AsyncJobInvocationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncAppsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncAppsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncAppsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#with_streaming_response
+ """
+ return AsyncAppsResourceWithStreamingResponse(self)
+
+
+class AppsResourceWithRawResponse:
+ def __init__(self, apps: AppsResource) -> None:
+ self._apps = apps
+
+ @cached_property
+ def job_invocations(self) -> JobInvocationsResourceWithRawResponse:
+ return JobInvocationsResourceWithRawResponse(self._apps.job_invocations)
+
+
+class AsyncAppsResourceWithRawResponse:
+ def __init__(self, apps: AsyncAppsResource) -> None:
+ self._apps = apps
+
+ @cached_property
+ def job_invocations(self) -> AsyncJobInvocationsResourceWithRawResponse:
+ return AsyncJobInvocationsResourceWithRawResponse(self._apps.job_invocations)
+
+
+class AppsResourceWithStreamingResponse:
+ def __init__(self, apps: AppsResource) -> None:
+ self._apps = apps
+
+ @cached_property
+ def job_invocations(self) -> JobInvocationsResourceWithStreamingResponse:
+ return JobInvocationsResourceWithStreamingResponse(self._apps.job_invocations)
+
+
+class AsyncAppsResourceWithStreamingResponse:
+ def __init__(self, apps: AsyncAppsResource) -> None:
+ self._apps = apps
+
+ @cached_property
+ def job_invocations(self) -> AsyncJobInvocationsResourceWithStreamingResponse:
+ return AsyncJobInvocationsResourceWithStreamingResponse(self._apps.job_invocations)
diff --git a/src/gradient/resources/apps/job_invocations.py b/src/gradient/resources/apps/job_invocations.py
new file mode 100644
index 00000000..449dd829
--- /dev/null
+++ b/src/gradient/resources/apps/job_invocations.py
@@ -0,0 +1,191 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...types.apps import job_invocation_cancel_params
+from ..._base_client import make_request_options
+from ...types.apps.job_invocation_cancel_response import JobInvocationCancelResponse
+
+__all__ = ["JobInvocationsResource", "AsyncJobInvocationsResource"]
+
+
+class JobInvocationsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> JobInvocationsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#accessing-raw-response-data-eg-headers
+ """
+ return JobInvocationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> JobInvocationsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#with_streaming_response
+ """
+ return JobInvocationsResourceWithStreamingResponse(self)
+
+ def cancel(
+ self,
+ job_invocation_id: str,
+ *,
+ app_id: str,
+ job_name: str | Omit = omit,
+ # 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,
+ ) -> JobInvocationCancelResponse:
+ """
+ Cancel a specific job invocation for an app.
+
+ Args:
+ job_name: The job name to list job invocations for.
+
+ 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 app_id:
+ raise ValueError(f"Expected a non-empty value for `app_id` but received {app_id!r}")
+ if not job_invocation_id:
+ raise ValueError(f"Expected a non-empty value for `job_invocation_id` but received {job_invocation_id!r}")
+ return self._post(
+ f"/v2/apps/{app_id}/job-invocations/{job_invocation_id}/cancel"
+ if self._client._base_url_overridden
+ else f"https://api.digitalocean.com/v2/apps/{app_id}/job-invocations/{job_invocation_id}/cancel",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"job_name": job_name}, job_invocation_cancel_params.JobInvocationCancelParams),
+ ),
+ cast_to=JobInvocationCancelResponse,
+ )
+
+
+class AsyncJobInvocationsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncJobInvocationsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncJobInvocationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncJobInvocationsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#with_streaming_response
+ """
+ return AsyncJobInvocationsResourceWithStreamingResponse(self)
+
+ async def cancel(
+ self,
+ job_invocation_id: str,
+ *,
+ app_id: str,
+ job_name: str | Omit = omit,
+ # 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,
+ ) -> JobInvocationCancelResponse:
+ """
+ Cancel a specific job invocation for an app.
+
+ Args:
+ job_name: The job name to list job invocations for.
+
+ 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 app_id:
+ raise ValueError(f"Expected a non-empty value for `app_id` but received {app_id!r}")
+ if not job_invocation_id:
+ raise ValueError(f"Expected a non-empty value for `job_invocation_id` but received {job_invocation_id!r}")
+ return await self._post(
+ f"/v2/apps/{app_id}/job-invocations/{job_invocation_id}/cancel"
+ if self._client._base_url_overridden
+ else f"https://api.digitalocean.com/v2/apps/{app_id}/job-invocations/{job_invocation_id}/cancel",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"job_name": job_name}, job_invocation_cancel_params.JobInvocationCancelParams
+ ),
+ ),
+ cast_to=JobInvocationCancelResponse,
+ )
+
+
+class JobInvocationsResourceWithRawResponse:
+ def __init__(self, job_invocations: JobInvocationsResource) -> None:
+ self._job_invocations = job_invocations
+
+ self.cancel = to_raw_response_wrapper(
+ job_invocations.cancel,
+ )
+
+
+class AsyncJobInvocationsResourceWithRawResponse:
+ def __init__(self, job_invocations: AsyncJobInvocationsResource) -> None:
+ self._job_invocations = job_invocations
+
+ self.cancel = async_to_raw_response_wrapper(
+ job_invocations.cancel,
+ )
+
+
+class JobInvocationsResourceWithStreamingResponse:
+ def __init__(self, job_invocations: JobInvocationsResource) -> None:
+ self._job_invocations = job_invocations
+
+ self.cancel = to_streamed_response_wrapper(
+ job_invocations.cancel,
+ )
+
+
+class AsyncJobInvocationsResourceWithStreamingResponse:
+ def __init__(self, job_invocations: AsyncJobInvocationsResource) -> None:
+ self._job_invocations = job_invocations
+
+ self.cancel = async_to_streamed_response_wrapper(
+ job_invocations.cancel,
+ )
diff --git a/src/gradient/resources/billing.py b/src/gradient/resources/billing.py
new file mode 100644
index 00000000..f9f42aad
--- /dev/null
+++ b/src/gradient/resources/billing.py
@@ -0,0 +1,226 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import date
+
+import httpx
+
+from ..types import billing_list_insights_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.billing_list_insights_response import BillingListInsightsResponse
+
+__all__ = ["BillingResource", "AsyncBillingResource"]
+
+
+class BillingResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> BillingResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#accessing-raw-response-data-eg-headers
+ """
+ return BillingResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> BillingResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#with_streaming_response
+ """
+ return BillingResourceWithStreamingResponse(self)
+
+ def list_insights(
+ self,
+ end_date: Union[str, date],
+ *,
+ account_urn: str,
+ start_date: Union[str, date],
+ page: int | Omit = omit,
+ per_page: int | Omit = omit,
+ # 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,
+ ) -> BillingListInsightsResponse:
+ """
+ This endpoint returns day-over-day changes in billing resource usage based on
+ nightly invoice items, including total amount, region, SKU, and description for
+ a specified date range. It is important to note that the daily resource usage
+ may not reflect month-end billing totals when totaled for a given month as
+ nightly invoice item estimates do not necessarily encompass all invoicing
+ factors for the entire month.
+
+ Args:
+ page: Which 'page' of paginated results to return.
+
+ per_page: Number of items returned per page
+
+ 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 account_urn:
+ raise ValueError(f"Expected a non-empty value for `account_urn` but received {account_urn!r}")
+ if not start_date:
+ raise ValueError(f"Expected a non-empty value for `start_date` but received {start_date!r}")
+ if not end_date:
+ raise ValueError(f"Expected a non-empty value for `end_date` but received {end_date!r}")
+ return self._get(
+ f"/v2/billing/{account_urn}/insights/{start_date}/{end_date}"
+ if self._client._base_url_overridden
+ else f"https://api.digitalocean.com/v2/billing/{account_urn}/insights/{start_date}/{end_date}",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "page": page,
+ "per_page": per_page,
+ },
+ billing_list_insights_params.BillingListInsightsParams,
+ ),
+ ),
+ cast_to=BillingListInsightsResponse,
+ )
+
+
+class AsyncBillingResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncBillingResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncBillingResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncBillingResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#with_streaming_response
+ """
+ return AsyncBillingResourceWithStreamingResponse(self)
+
+ async def list_insights(
+ self,
+ end_date: Union[str, date],
+ *,
+ account_urn: str,
+ start_date: Union[str, date],
+ page: int | Omit = omit,
+ per_page: int | Omit = omit,
+ # 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,
+ ) -> BillingListInsightsResponse:
+ """
+ This endpoint returns day-over-day changes in billing resource usage based on
+ nightly invoice items, including total amount, region, SKU, and description for
+ a specified date range. It is important to note that the daily resource usage
+ may not reflect month-end billing totals when totaled for a given month as
+ nightly invoice item estimates do not necessarily encompass all invoicing
+ factors for the entire month.
+
+ Args:
+ page: Which 'page' of paginated results to return.
+
+ per_page: Number of items returned per page
+
+ 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 account_urn:
+ raise ValueError(f"Expected a non-empty value for `account_urn` but received {account_urn!r}")
+ if not start_date:
+ raise ValueError(f"Expected a non-empty value for `start_date` but received {start_date!r}")
+ if not end_date:
+ raise ValueError(f"Expected a non-empty value for `end_date` but received {end_date!r}")
+ return await self._get(
+ f"/v2/billing/{account_urn}/insights/{start_date}/{end_date}"
+ if self._client._base_url_overridden
+ else f"https://api.digitalocean.com/v2/billing/{account_urn}/insights/{start_date}/{end_date}",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "page": page,
+ "per_page": per_page,
+ },
+ billing_list_insights_params.BillingListInsightsParams,
+ ),
+ ),
+ cast_to=BillingListInsightsResponse,
+ )
+
+
+class BillingResourceWithRawResponse:
+ def __init__(self, billing: BillingResource) -> None:
+ self._billing = billing
+
+ self.list_insights = to_raw_response_wrapper(
+ billing.list_insights,
+ )
+
+
+class AsyncBillingResourceWithRawResponse:
+ def __init__(self, billing: AsyncBillingResource) -> None:
+ self._billing = billing
+
+ self.list_insights = async_to_raw_response_wrapper(
+ billing.list_insights,
+ )
+
+
+class BillingResourceWithStreamingResponse:
+ def __init__(self, billing: BillingResource) -> None:
+ self._billing = billing
+
+ self.list_insights = to_streamed_response_wrapper(
+ billing.list_insights,
+ )
+
+
+class AsyncBillingResourceWithStreamingResponse:
+ def __init__(self, billing: AsyncBillingResource) -> None:
+ self._billing = billing
+
+ self.list_insights = async_to_streamed_response_wrapper(
+ billing.list_insights,
+ )
diff --git a/src/gradient/resources/knowledge_bases/data_sources.py b/src/gradient/resources/knowledge_bases/data_sources.py
index a00d93f5..6c339108 100644
--- a/src/gradient/resources/knowledge_bases/data_sources.py
+++ b/src/gradient/resources/knowledge_bases/data_sources.py
@@ -3,6 +3,7 @@
from __future__ import annotations
from typing import Iterable
+from typing_extensions import Literal
import httpx
@@ -20,12 +21,14 @@
from ...types.knowledge_bases import (
data_source_list_params,
data_source_create_params,
+ data_source_update_params,
data_source_create_presigned_urls_params,
)
from ...types.knowledge_bases.aws_data_source_param import AwsDataSourceParam
from ...types.knowledge_bases.data_source_list_response import DataSourceListResponse
from ...types.knowledge_bases.data_source_create_response import DataSourceCreateResponse
from ...types.knowledge_bases.data_source_delete_response import DataSourceDeleteResponse
+from ...types.knowledge_bases.data_source_update_response import DataSourceUpdateResponse
from ...types.knowledge_bases.api_spaces_data_source_param import APISpacesDataSourceParam
from ...types.knowledge_bases.api_web_crawler_data_source_param import APIWebCrawlerDataSourceParam
from ...types.knowledge_bases.data_source_create_presigned_urls_response import DataSourceCreatePresignedURLsResponse
@@ -58,6 +61,15 @@ def create(
path_knowledge_base_uuid: str,
*,
aws_data_source: AwsDataSourceParam | Omit = omit,
+ chunking_algorithm: Literal[
+ "CHUNKING_ALGORITHM_UNKNOWN",
+ "CHUNKING_ALGORITHM_SECTION_BASED",
+ "CHUNKING_ALGORITHM_HIERARCHICAL",
+ "CHUNKING_ALGORITHM_SEMANTIC",
+ "CHUNKING_ALGORITHM_FIXED_LENGTH",
+ ]
+ | Omit = omit,
+ chunking_options: data_source_create_params.ChunkingOptions | Omit = omit,
body_knowledge_base_uuid: str | Omit = omit,
spaces_data_source: APISpacesDataSourceParam | Omit = omit,
web_crawler_data_source: APIWebCrawlerDataSourceParam | Omit = omit,
@@ -75,6 +87,16 @@ def create(
Args:
aws_data_source: AWS S3 Data Source
+ chunking_algorithm: The chunking algorithm to use for processing data sources.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+
+ chunking_options: Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+
body_knowledge_base_uuid: Knowledge base id
spaces_data_source: Spaces Bucket Data Source
@@ -100,6 +122,8 @@ def create(
body=maybe_transform(
{
"aws_data_source": aws_data_source,
+ "chunking_algorithm": chunking_algorithm,
+ "chunking_options": chunking_options,
"body_knowledge_base_uuid": body_knowledge_base_uuid,
"spaces_data_source": spaces_data_source,
"web_crawler_data_source": web_crawler_data_source,
@@ -112,6 +136,84 @@ def create(
cast_to=DataSourceCreateResponse,
)
+ def update(
+ self,
+ path_data_source_uuid: str,
+ *,
+ path_knowledge_base_uuid: str,
+ chunking_algorithm: Literal[
+ "CHUNKING_ALGORITHM_UNKNOWN",
+ "CHUNKING_ALGORITHM_SECTION_BASED",
+ "CHUNKING_ALGORITHM_HIERARCHICAL",
+ "CHUNKING_ALGORITHM_SEMANTIC",
+ "CHUNKING_ALGORITHM_FIXED_LENGTH",
+ ]
+ | Omit = omit,
+ chunking_options: data_source_update_params.ChunkingOptions | Omit = omit,
+ body_data_source_uuid: str | Omit = omit,
+ body_knowledge_base_uuid: str | Omit = omit,
+ # 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,
+ ) -> DataSourceUpdateResponse:
+ """To update a data source (e.g.
+
+ chunking options), send a PUT request to
+ `/v2/gen-ai/knowledge_bases/{knowledge_base_uuid}/data_sources/{data_source_uuid}`.
+
+ Args:
+ chunking_algorithm: The chunking algorithm to use for processing data sources.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+
+ chunking_options: Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+
+ body_data_source_uuid: Data Source ID (Path Parameter)
+
+ body_knowledge_base_uuid: Knowledge Base ID (Path Parameter)
+
+ 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 path_knowledge_base_uuid:
+ raise ValueError(
+ f"Expected a non-empty value for `path_knowledge_base_uuid` but received {path_knowledge_base_uuid!r}"
+ )
+ if not path_data_source_uuid:
+ raise ValueError(
+ f"Expected a non-empty value for `path_data_source_uuid` but received {path_data_source_uuid!r}"
+ )
+ return self._put(
+ f"/v2/gen-ai/knowledge_bases/{path_knowledge_base_uuid}/data_sources/{path_data_source_uuid}"
+ if self._client._base_url_overridden
+ else f"https://api.digitalocean.com/v2/gen-ai/knowledge_bases/{path_knowledge_base_uuid}/data_sources/{path_data_source_uuid}",
+ body=maybe_transform(
+ {
+ "chunking_algorithm": chunking_algorithm,
+ "chunking_options": chunking_options,
+ "body_data_source_uuid": body_data_source_uuid,
+ "body_knowledge_base_uuid": body_knowledge_base_uuid,
+ },
+ data_source_update_params.DataSourceUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=DataSourceUpdateResponse,
+ )
+
def list(
self,
knowledge_base_uuid: str,
@@ -272,6 +374,15 @@ async def create(
path_knowledge_base_uuid: str,
*,
aws_data_source: AwsDataSourceParam | Omit = omit,
+ chunking_algorithm: Literal[
+ "CHUNKING_ALGORITHM_UNKNOWN",
+ "CHUNKING_ALGORITHM_SECTION_BASED",
+ "CHUNKING_ALGORITHM_HIERARCHICAL",
+ "CHUNKING_ALGORITHM_SEMANTIC",
+ "CHUNKING_ALGORITHM_FIXED_LENGTH",
+ ]
+ | Omit = omit,
+ chunking_options: data_source_create_params.ChunkingOptions | Omit = omit,
body_knowledge_base_uuid: str | Omit = omit,
spaces_data_source: APISpacesDataSourceParam | Omit = omit,
web_crawler_data_source: APIWebCrawlerDataSourceParam | Omit = omit,
@@ -289,6 +400,16 @@ async def create(
Args:
aws_data_source: AWS S3 Data Source
+ chunking_algorithm: The chunking algorithm to use for processing data sources.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+
+ chunking_options: Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+
body_knowledge_base_uuid: Knowledge base id
spaces_data_source: Spaces Bucket Data Source
@@ -314,6 +435,8 @@ async def create(
body=await async_maybe_transform(
{
"aws_data_source": aws_data_source,
+ "chunking_algorithm": chunking_algorithm,
+ "chunking_options": chunking_options,
"body_knowledge_base_uuid": body_knowledge_base_uuid,
"spaces_data_source": spaces_data_source,
"web_crawler_data_source": web_crawler_data_source,
@@ -326,6 +449,84 @@ async def create(
cast_to=DataSourceCreateResponse,
)
+ async def update(
+ self,
+ path_data_source_uuid: str,
+ *,
+ path_knowledge_base_uuid: str,
+ chunking_algorithm: Literal[
+ "CHUNKING_ALGORITHM_UNKNOWN",
+ "CHUNKING_ALGORITHM_SECTION_BASED",
+ "CHUNKING_ALGORITHM_HIERARCHICAL",
+ "CHUNKING_ALGORITHM_SEMANTIC",
+ "CHUNKING_ALGORITHM_FIXED_LENGTH",
+ ]
+ | Omit = omit,
+ chunking_options: data_source_update_params.ChunkingOptions | Omit = omit,
+ body_data_source_uuid: str | Omit = omit,
+ body_knowledge_base_uuid: str | Omit = omit,
+ # 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,
+ ) -> DataSourceUpdateResponse:
+ """To update a data source (e.g.
+
+ chunking options), send a PUT request to
+ `/v2/gen-ai/knowledge_bases/{knowledge_base_uuid}/data_sources/{data_source_uuid}`.
+
+ Args:
+ chunking_algorithm: The chunking algorithm to use for processing data sources.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+
+ chunking_options: Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+
+ body_data_source_uuid: Data Source ID (Path Parameter)
+
+ body_knowledge_base_uuid: Knowledge Base ID (Path Parameter)
+
+ 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 path_knowledge_base_uuid:
+ raise ValueError(
+ f"Expected a non-empty value for `path_knowledge_base_uuid` but received {path_knowledge_base_uuid!r}"
+ )
+ if not path_data_source_uuid:
+ raise ValueError(
+ f"Expected a non-empty value for `path_data_source_uuid` but received {path_data_source_uuid!r}"
+ )
+ return await self._put(
+ f"/v2/gen-ai/knowledge_bases/{path_knowledge_base_uuid}/data_sources/{path_data_source_uuid}"
+ if self._client._base_url_overridden
+ else f"https://api.digitalocean.com/v2/gen-ai/knowledge_bases/{path_knowledge_base_uuid}/data_sources/{path_data_source_uuid}",
+ body=await async_maybe_transform(
+ {
+ "chunking_algorithm": chunking_algorithm,
+ "chunking_options": chunking_options,
+ "body_data_source_uuid": body_data_source_uuid,
+ "body_knowledge_base_uuid": body_knowledge_base_uuid,
+ },
+ data_source_update_params.DataSourceUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=DataSourceUpdateResponse,
+ )
+
async def list(
self,
knowledge_base_uuid: str,
@@ -468,6 +669,9 @@ def __init__(self, data_sources: DataSourcesResource) -> None:
self.create = to_raw_response_wrapper(
data_sources.create,
)
+ self.update = to_raw_response_wrapper(
+ data_sources.update,
+ )
self.list = to_raw_response_wrapper(
data_sources.list,
)
@@ -486,6 +690,9 @@ def __init__(self, data_sources: AsyncDataSourcesResource) -> None:
self.create = async_to_raw_response_wrapper(
data_sources.create,
)
+ self.update = async_to_raw_response_wrapper(
+ data_sources.update,
+ )
self.list = async_to_raw_response_wrapper(
data_sources.list,
)
@@ -504,6 +711,9 @@ def __init__(self, data_sources: DataSourcesResource) -> None:
self.create = to_streamed_response_wrapper(
data_sources.create,
)
+ self.update = to_streamed_response_wrapper(
+ data_sources.update,
+ )
self.list = to_streamed_response_wrapper(
data_sources.list,
)
@@ -522,6 +732,9 @@ def __init__(self, data_sources: AsyncDataSourcesResource) -> None:
self.create = async_to_streamed_response_wrapper(
data_sources.create,
)
+ self.update = async_to_streamed_response_wrapper(
+ data_sources.update,
+ )
self.list = async_to_streamed_response_wrapper(
data_sources.list,
)
diff --git a/src/gradient/resources/responses.py b/src/gradient/resources/responses.py
new file mode 100644
index 00000000..a342a78d
--- /dev/null
+++ b/src/gradient/resources/responses.py
@@ -0,0 +1,860 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, List, Union, Iterable, Optional
+from typing_extensions import Literal, overload
+
+import httpx
+
+from ..types import response_create_params
+from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from .._utils import required_args, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._streaming import Stream, AsyncStream
+from .._base_client import make_request_options
+from ..types.shared.create_response_response import CreateResponseResponse
+from ..types.shared.create_response_stream_response import CreateResponseStreamResponse
+
+__all__ = ["ResponsesResource", "AsyncResponsesResource"]
+
+
+class ResponsesResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ResponsesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#accessing-raw-response-data-eg-headers
+ """
+ return ResponsesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ResponsesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#with_streaming_response
+ """
+ return ResponsesResourceWithStreamingResponse(self)
+
+ @overload
+ def create(
+ self,
+ *,
+ input: Union[str, Iterable[response_create_params.InputUnionMember1]],
+ model: str,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tokens: Optional[int] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ modalities: Optional[List[Literal["text"]]] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
+ stream: Optional[Literal[False]] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[response_create_params.Tool] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ user: str | Omit = omit,
+ # 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,
+ ) -> CreateResponseResponse:
+ """Generate text responses from text prompts.
+
+ This endpoint supports both streaming
+ and non-streaming responses for VLLM models only.
+
+ Args:
+ input: The input text prompt or conversation history. Can be a string or an array of
+ message objects for conversation context.
+
+ model: Model ID used to generate the response. Must be a VLLM model.
+
+ instructions: System-level instructions for the model. This sets the behavior and context for
+ the response generation.
+
+ max_output_tokens: Maximum number of tokens to generate in the response. If not specified, the
+ model will use a default value.
+
+ max_tokens: The maximum number of tokens that can be generated in the completion. Alias for
+ max_output_tokens for compatibility.
+
+ metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
+ for storing additional information about the object in a structured format, and
+ querying for objects via API or the dashboard.
+
+ Keys are strings with a maximum length of 64 characters. Values are strings with
+ a maximum length of 512 characters.
+
+ modalities: Specifies the output types the model should generate. For text-to-text, this
+ should be ["text"].
+
+ parallel_tool_calls: Whether to enable parallel tool calls. When true, the model can make multiple
+ tool calls in parallel.
+
+ stop: Up to 4 sequences where the API will stop generating further tokens. The
+ returned text will not contain the stop sequence.
+
+ stream: If set to true, the model response data will be streamed to the client as it is
+ generated using server-sent events.
+
+ stream_options: Options for streaming response. Only set this when you set `stream: true`.
+
+ temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
+ make the output more random, while lower values like 0.2 will make it more
+ focused and deterministic. We generally recommend altering this or `top_p` but
+ not both.
+
+ tool_choice: Controls which (if any) tool is called by the model. `none` means the model will
+ not call any tool and instead generates a message. `auto` means the model can
+ pick between generating a message or calling one or more tools. `required` means
+ the model must call one or more tools. Specifying a particular tool via
+ `{"type": "function", "function": {"name": "my_function"}}` forces the model to
+ call that tool.
+
+ `none` is the default when no tools are present. `auto` is the default if tools
+ are present.
+
+ tools: A list of tools the model may call. Currently, only functions are supported as a
+ tool. Uses Responses API format (with `name`, `description`, `parameters` at top
+ level).
+
+ top_p: An alternative to sampling with temperature, called nucleus sampling, where the
+ model considers the results of the tokens with top_p probability mass. So 0.1
+ means only the tokens comprising the top 10% probability mass are considered.
+
+ We generally recommend altering this or `temperature` but not both.
+
+ user: A unique identifier representing your end-user, which can help DigitalOcean to
+ monitor and detect abuse.
+
+ 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
+ """
+ ...
+
+ @overload
+ def create(
+ self,
+ *,
+ input: Union[str, Iterable[response_create_params.InputUnionMember1]],
+ model: str,
+ stream: Literal[True],
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tokens: Optional[int] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ modalities: Optional[List[Literal["text"]]] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[response_create_params.Tool] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ user: str | Omit = omit,
+ # 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,
+ ) -> Stream[CreateResponseStreamResponse]:
+ """Generate text responses from text prompts.
+
+ This endpoint supports both streaming
+ and non-streaming responses for VLLM models only.
+
+ Args:
+ input: The input text prompt or conversation history. Can be a string or an array of
+ message objects for conversation context.
+
+ model: Model ID used to generate the response. Must be a VLLM model.
+
+ stream: If set to true, the model response data will be streamed to the client as it is
+ generated using server-sent events.
+
+ instructions: System-level instructions for the model. This sets the behavior and context for
+ the response generation.
+
+ max_output_tokens: Maximum number of tokens to generate in the response. If not specified, the
+ model will use a default value.
+
+ max_tokens: The maximum number of tokens that can be generated in the completion. Alias for
+ max_output_tokens for compatibility.
+
+ metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
+ for storing additional information about the object in a structured format, and
+ querying for objects via API or the dashboard.
+
+ Keys are strings with a maximum length of 64 characters. Values are strings with
+ a maximum length of 512 characters.
+
+ modalities: Specifies the output types the model should generate. For text-to-text, this
+ should be ["text"].
+
+ parallel_tool_calls: Whether to enable parallel tool calls. When true, the model can make multiple
+ tool calls in parallel.
+
+ stop: Up to 4 sequences where the API will stop generating further tokens. The
+ returned text will not contain the stop sequence.
+
+ stream_options: Options for streaming response. Only set this when you set `stream: true`.
+
+ temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
+ make the output more random, while lower values like 0.2 will make it more
+ focused and deterministic. We generally recommend altering this or `top_p` but
+ not both.
+
+ tool_choice: Controls which (if any) tool is called by the model. `none` means the model will
+ not call any tool and instead generates a message. `auto` means the model can
+ pick between generating a message or calling one or more tools. `required` means
+ the model must call one or more tools. Specifying a particular tool via
+ `{"type": "function", "function": {"name": "my_function"}}` forces the model to
+ call that tool.
+
+ `none` is the default when no tools are present. `auto` is the default if tools
+ are present.
+
+ tools: A list of tools the model may call. Currently, only functions are supported as a
+ tool. Uses Responses API format (with `name`, `description`, `parameters` at top
+ level).
+
+ top_p: An alternative to sampling with temperature, called nucleus sampling, where the
+ model considers the results of the tokens with top_p probability mass. So 0.1
+ means only the tokens comprising the top 10% probability mass are considered.
+
+ We generally recommend altering this or `temperature` but not both.
+
+ user: A unique identifier representing your end-user, which can help DigitalOcean to
+ monitor and detect abuse.
+
+ 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
+ """
+ ...
+
+ @overload
+ def create(
+ self,
+ *,
+ input: Union[str, Iterable[response_create_params.InputUnionMember1]],
+ model: str,
+ stream: bool,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tokens: Optional[int] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ modalities: Optional[List[Literal["text"]]] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[response_create_params.Tool] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ user: str | Omit = omit,
+ # 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,
+ ) -> CreateResponseResponse | Stream[CreateResponseStreamResponse]:
+ """Generate text responses from text prompts.
+
+ This endpoint supports both streaming
+ and non-streaming responses for VLLM models only.
+
+ Args:
+ input: The input text prompt or conversation history. Can be a string or an array of
+ message objects for conversation context.
+
+ model: Model ID used to generate the response. Must be a VLLM model.
+
+ stream: If set to true, the model response data will be streamed to the client as it is
+ generated using server-sent events.
+
+ instructions: System-level instructions for the model. This sets the behavior and context for
+ the response generation.
+
+ max_output_tokens: Maximum number of tokens to generate in the response. If not specified, the
+ model will use a default value.
+
+ max_tokens: The maximum number of tokens that can be generated in the completion. Alias for
+ max_output_tokens for compatibility.
+
+ metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
+ for storing additional information about the object in a structured format, and
+ querying for objects via API or the dashboard.
+
+ Keys are strings with a maximum length of 64 characters. Values are strings with
+ a maximum length of 512 characters.
+
+ modalities: Specifies the output types the model should generate. For text-to-text, this
+ should be ["text"].
+
+ parallel_tool_calls: Whether to enable parallel tool calls. When true, the model can make multiple
+ tool calls in parallel.
+
+ stop: Up to 4 sequences where the API will stop generating further tokens. The
+ returned text will not contain the stop sequence.
+
+ stream_options: Options for streaming response. Only set this when you set `stream: true`.
+
+ temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
+ make the output more random, while lower values like 0.2 will make it more
+ focused and deterministic. We generally recommend altering this or `top_p` but
+ not both.
+
+ tool_choice: Controls which (if any) tool is called by the model. `none` means the model will
+ not call any tool and instead generates a message. `auto` means the model can
+ pick between generating a message or calling one or more tools. `required` means
+ the model must call one or more tools. Specifying a particular tool via
+ `{"type": "function", "function": {"name": "my_function"}}` forces the model to
+ call that tool.
+
+ `none` is the default when no tools are present. `auto` is the default if tools
+ are present.
+
+ tools: A list of tools the model may call. Currently, only functions are supported as a
+ tool. Uses Responses API format (with `name`, `description`, `parameters` at top
+ level).
+
+ top_p: An alternative to sampling with temperature, called nucleus sampling, where the
+ model considers the results of the tokens with top_p probability mass. So 0.1
+ means only the tokens comprising the top 10% probability mass are considered.
+
+ We generally recommend altering this or `temperature` but not both.
+
+ user: A unique identifier representing your end-user, which can help DigitalOcean to
+ monitor and detect abuse.
+
+ 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
+ """
+ ...
+
+ @required_args(["input", "model"], ["input", "model", "stream"])
+ def create(
+ self,
+ *,
+ input: Union[str, Iterable[response_create_params.InputUnionMember1]],
+ model: str,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tokens: Optional[int] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ modalities: Optional[List[Literal["text"]]] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
+ stream: Optional[Literal[False]] | Literal[True] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[response_create_params.Tool] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ user: str | Omit = omit,
+ # 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,
+ ) -> CreateResponseResponse | Stream[CreateResponseStreamResponse]:
+ return self._post(
+ "/responses",
+ body=maybe_transform(
+ {
+ "input": input,
+ "model": model,
+ "instructions": instructions,
+ "max_output_tokens": max_output_tokens,
+ "max_tokens": max_tokens,
+ "metadata": metadata,
+ "modalities": modalities,
+ "parallel_tool_calls": parallel_tool_calls,
+ "stop": stop,
+ "stream": stream,
+ "stream_options": stream_options,
+ "temperature": temperature,
+ "tool_choice": tool_choice,
+ "tools": tools,
+ "top_p": top_p,
+ "user": user,
+ },
+ response_create_params.ResponseCreateParamsStreaming
+ if stream
+ else response_create_params.ResponseCreateParamsNonStreaming,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CreateResponseResponse,
+ stream=stream or False,
+ stream_cls=Stream[CreateResponseStreamResponse],
+ )
+
+
+class AsyncResponsesResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncResponsesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncResponsesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncResponsesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/digitalocean/gradient-python#with_streaming_response
+ """
+ return AsyncResponsesResourceWithStreamingResponse(self)
+
+ @overload
+ async def create(
+ self,
+ *,
+ input: Union[str, Iterable[response_create_params.InputUnionMember1]],
+ model: str,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tokens: Optional[int] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ modalities: Optional[List[Literal["text"]]] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
+ stream: Optional[Literal[False]] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[response_create_params.Tool] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ user: str | Omit = omit,
+ # 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,
+ ) -> CreateResponseResponse:
+ """Generate text responses from text prompts.
+
+ This endpoint supports both streaming
+ and non-streaming responses for VLLM models only.
+
+ Args:
+ input: The input text prompt or conversation history. Can be a string or an array of
+ message objects for conversation context.
+
+ model: Model ID used to generate the response. Must be a VLLM model.
+
+ instructions: System-level instructions for the model. This sets the behavior and context for
+ the response generation.
+
+ max_output_tokens: Maximum number of tokens to generate in the response. If not specified, the
+ model will use a default value.
+
+ max_tokens: The maximum number of tokens that can be generated in the completion. Alias for
+ max_output_tokens for compatibility.
+
+ metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
+ for storing additional information about the object in a structured format, and
+ querying for objects via API or the dashboard.
+
+ Keys are strings with a maximum length of 64 characters. Values are strings with
+ a maximum length of 512 characters.
+
+ modalities: Specifies the output types the model should generate. For text-to-text, this
+ should be ["text"].
+
+ parallel_tool_calls: Whether to enable parallel tool calls. When true, the model can make multiple
+ tool calls in parallel.
+
+ stop: Up to 4 sequences where the API will stop generating further tokens. The
+ returned text will not contain the stop sequence.
+
+ stream: If set to true, the model response data will be streamed to the client as it is
+ generated using server-sent events.
+
+ stream_options: Options for streaming response. Only set this when you set `stream: true`.
+
+ temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
+ make the output more random, while lower values like 0.2 will make it more
+ focused and deterministic. We generally recommend altering this or `top_p` but
+ not both.
+
+ tool_choice: Controls which (if any) tool is called by the model. `none` means the model will
+ not call any tool and instead generates a message. `auto` means the model can
+ pick between generating a message or calling one or more tools. `required` means
+ the model must call one or more tools. Specifying a particular tool via
+ `{"type": "function", "function": {"name": "my_function"}}` forces the model to
+ call that tool.
+
+ `none` is the default when no tools are present. `auto` is the default if tools
+ are present.
+
+ tools: A list of tools the model may call. Currently, only functions are supported as a
+ tool. Uses Responses API format (with `name`, `description`, `parameters` at top
+ level).
+
+ top_p: An alternative to sampling with temperature, called nucleus sampling, where the
+ model considers the results of the tokens with top_p probability mass. So 0.1
+ means only the tokens comprising the top 10% probability mass are considered.
+
+ We generally recommend altering this or `temperature` but not both.
+
+ user: A unique identifier representing your end-user, which can help DigitalOcean to
+ monitor and detect abuse.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def create(
+ self,
+ *,
+ input: Union[str, Iterable[response_create_params.InputUnionMember1]],
+ model: str,
+ stream: Literal[True],
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tokens: Optional[int] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ modalities: Optional[List[Literal["text"]]] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[response_create_params.Tool] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ user: str | Omit = omit,
+ # 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,
+ ) -> AsyncStream[CreateResponseStreamResponse]:
+ """Generate text responses from text prompts.
+
+ This endpoint supports both streaming
+ and non-streaming responses for VLLM models only.
+
+ Args:
+ input: The input text prompt or conversation history. Can be a string or an array of
+ message objects for conversation context.
+
+ model: Model ID used to generate the response. Must be a VLLM model.
+
+ stream: If set to true, the model response data will be streamed to the client as it is
+ generated using server-sent events.
+
+ instructions: System-level instructions for the model. This sets the behavior and context for
+ the response generation.
+
+ max_output_tokens: Maximum number of tokens to generate in the response. If not specified, the
+ model will use a default value.
+
+ max_tokens: The maximum number of tokens that can be generated in the completion. Alias for
+ max_output_tokens for compatibility.
+
+ metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
+ for storing additional information about the object in a structured format, and
+ querying for objects via API or the dashboard.
+
+ Keys are strings with a maximum length of 64 characters. Values are strings with
+ a maximum length of 512 characters.
+
+ modalities: Specifies the output types the model should generate. For text-to-text, this
+ should be ["text"].
+
+ parallel_tool_calls: Whether to enable parallel tool calls. When true, the model can make multiple
+ tool calls in parallel.
+
+ stop: Up to 4 sequences where the API will stop generating further tokens. The
+ returned text will not contain the stop sequence.
+
+ stream_options: Options for streaming response. Only set this when you set `stream: true`.
+
+ temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
+ make the output more random, while lower values like 0.2 will make it more
+ focused and deterministic. We generally recommend altering this or `top_p` but
+ not both.
+
+ tool_choice: Controls which (if any) tool is called by the model. `none` means the model will
+ not call any tool and instead generates a message. `auto` means the model can
+ pick between generating a message or calling one or more tools. `required` means
+ the model must call one or more tools. Specifying a particular tool via
+ `{"type": "function", "function": {"name": "my_function"}}` forces the model to
+ call that tool.
+
+ `none` is the default when no tools are present. `auto` is the default if tools
+ are present.
+
+ tools: A list of tools the model may call. Currently, only functions are supported as a
+ tool. Uses Responses API format (with `name`, `description`, `parameters` at top
+ level).
+
+ top_p: An alternative to sampling with temperature, called nucleus sampling, where the
+ model considers the results of the tokens with top_p probability mass. So 0.1
+ means only the tokens comprising the top 10% probability mass are considered.
+
+ We generally recommend altering this or `temperature` but not both.
+
+ user: A unique identifier representing your end-user, which can help DigitalOcean to
+ monitor and detect abuse.
+
+ 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
+ """
+ ...
+
+ @overload
+ async def create(
+ self,
+ *,
+ input: Union[str, Iterable[response_create_params.InputUnionMember1]],
+ model: str,
+ stream: bool,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tokens: Optional[int] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ modalities: Optional[List[Literal["text"]]] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[response_create_params.Tool] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ user: str | Omit = omit,
+ # 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,
+ ) -> CreateResponseResponse | AsyncStream[CreateResponseStreamResponse]:
+ """Generate text responses from text prompts.
+
+ This endpoint supports both streaming
+ and non-streaming responses for VLLM models only.
+
+ Args:
+ input: The input text prompt or conversation history. Can be a string or an array of
+ message objects for conversation context.
+
+ model: Model ID used to generate the response. Must be a VLLM model.
+
+ stream: If set to true, the model response data will be streamed to the client as it is
+ generated using server-sent events.
+
+ instructions: System-level instructions for the model. This sets the behavior and context for
+ the response generation.
+
+ max_output_tokens: Maximum number of tokens to generate in the response. If not specified, the
+ model will use a default value.
+
+ max_tokens: The maximum number of tokens that can be generated in the completion. Alias for
+ max_output_tokens for compatibility.
+
+ metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful
+ for storing additional information about the object in a structured format, and
+ querying for objects via API or the dashboard.
+
+ Keys are strings with a maximum length of 64 characters. Values are strings with
+ a maximum length of 512 characters.
+
+ modalities: Specifies the output types the model should generate. For text-to-text, this
+ should be ["text"].
+
+ parallel_tool_calls: Whether to enable parallel tool calls. When true, the model can make multiple
+ tool calls in parallel.
+
+ stop: Up to 4 sequences where the API will stop generating further tokens. The
+ returned text will not contain the stop sequence.
+
+ stream_options: Options for streaming response. Only set this when you set `stream: true`.
+
+ temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
+ make the output more random, while lower values like 0.2 will make it more
+ focused and deterministic. We generally recommend altering this or `top_p` but
+ not both.
+
+ tool_choice: Controls which (if any) tool is called by the model. `none` means the model will
+ not call any tool and instead generates a message. `auto` means the model can
+ pick between generating a message or calling one or more tools. `required` means
+ the model must call one or more tools. Specifying a particular tool via
+ `{"type": "function", "function": {"name": "my_function"}}` forces the model to
+ call that tool.
+
+ `none` is the default when no tools are present. `auto` is the default if tools
+ are present.
+
+ tools: A list of tools the model may call. Currently, only functions are supported as a
+ tool. Uses Responses API format (with `name`, `description`, `parameters` at top
+ level).
+
+ top_p: An alternative to sampling with temperature, called nucleus sampling, where the
+ model considers the results of the tokens with top_p probability mass. So 0.1
+ means only the tokens comprising the top 10% probability mass are considered.
+
+ We generally recommend altering this or `temperature` but not both.
+
+ user: A unique identifier representing your end-user, which can help DigitalOcean to
+ monitor and detect abuse.
+
+ 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
+ """
+ ...
+
+ @required_args(["input", "model"], ["input", "model", "stream"])
+ async def create(
+ self,
+ *,
+ input: Union[str, Iterable[response_create_params.InputUnionMember1]],
+ model: str,
+ instructions: Optional[str] | Omit = omit,
+ max_output_tokens: Optional[int] | Omit = omit,
+ max_tokens: Optional[int] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ modalities: Optional[List[Literal["text"]]] | Omit = omit,
+ parallel_tool_calls: Optional[bool] | Omit = omit,
+ stop: Union[Optional[str], SequenceNotStr[str], None] | Omit = omit,
+ stream: Optional[Literal[False]] | Literal[True] | Omit = omit,
+ stream_options: Optional[response_create_params.StreamOptions] | Omit = omit,
+ temperature: Optional[float] | Omit = omit,
+ tool_choice: response_create_params.ToolChoice | Omit = omit,
+ tools: Iterable[response_create_params.Tool] | Omit = omit,
+ top_p: Optional[float] | Omit = omit,
+ user: str | Omit = omit,
+ # 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,
+ ) -> CreateResponseResponse | AsyncStream[CreateResponseStreamResponse]:
+ return await self._post(
+ "/responses",
+ body=await async_maybe_transform(
+ {
+ "input": input,
+ "model": model,
+ "instructions": instructions,
+ "max_output_tokens": max_output_tokens,
+ "max_tokens": max_tokens,
+ "metadata": metadata,
+ "modalities": modalities,
+ "parallel_tool_calls": parallel_tool_calls,
+ "stop": stop,
+ "stream": stream,
+ "stream_options": stream_options,
+ "temperature": temperature,
+ "tool_choice": tool_choice,
+ "tools": tools,
+ "top_p": top_p,
+ "user": user,
+ },
+ response_create_params.ResponseCreateParamsStreaming
+ if stream
+ else response_create_params.ResponseCreateParamsNonStreaming,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CreateResponseResponse,
+ stream=stream or False,
+ stream_cls=AsyncStream[CreateResponseStreamResponse],
+ )
+
+
+class ResponsesResourceWithRawResponse:
+ def __init__(self, responses: ResponsesResource) -> None:
+ self._responses = responses
+
+ self.create = to_raw_response_wrapper(
+ responses.create,
+ )
+
+
+class AsyncResponsesResourceWithRawResponse:
+ def __init__(self, responses: AsyncResponsesResource) -> None:
+ self._responses = responses
+
+ self.create = async_to_raw_response_wrapper(
+ responses.create,
+ )
+
+
+class ResponsesResourceWithStreamingResponse:
+ def __init__(self, responses: ResponsesResource) -> None:
+ self._responses = responses
+
+ self.create = to_streamed_response_wrapper(
+ responses.create,
+ )
+
+
+class AsyncResponsesResourceWithStreamingResponse:
+ def __init__(self, responses: AsyncResponsesResource) -> None:
+ self._responses = responses
+
+ self.create = async_to_streamed_response_wrapper(
+ responses.create,
+ )
diff --git a/src/gradient/resources/retrieve.py b/src/gradient/resources/retrieve.py
index f8335ab3..992fa37f 100644
--- a/src/gradient/resources/retrieve.py
+++ b/src/gradient/resources/retrieve.py
@@ -67,6 +67,12 @@ def documents(
3. Performs vector similarity search in the knowledge base
4. Returns the most relevant document chunks
+ The search supports hybrid search combining:
+
+ - Vector similarity (semantic search)
+ - Keyword matching (BM25)
+ - Custom metadata filters
+
Args:
num_results: Number of results to return
@@ -162,6 +168,12 @@ async def documents(
3. Performs vector similarity search in the knowledge base
4. Returns the most relevant document chunks
+ The search supports hybrid search combining:
+
+ - Vector similarity (semantic search)
+ - Keyword matching (BM25)
+ - Custom metadata filters
+
Args:
num_results: Number of results to return
diff --git a/src/gradient/types/__init__.py b/src/gradient/types/__init__.py
index e927e099..fc2907b2 100644
--- a/src/gradient/types/__init__.py
+++ b/src/gradient/types/__init__.py
@@ -43,10 +43,12 @@
ChatCompletionChunk as ChatCompletionChunk,
ImageGenStreamEvent as ImageGenStreamEvent,
SubscriptionTierBase as SubscriptionTierBase,
+ CreateResponseResponse as CreateResponseResponse,
ImageGenCompletedEvent as ImageGenCompletedEvent,
DropletNextBackupWindow as DropletNextBackupWindow,
ImageGenPartialImageEvent as ImageGenPartialImageEvent,
ChatCompletionTokenLogprob as ChatCompletionTokenLogprob,
+ CreateResponseStreamResponse as CreateResponseStreamResponse,
)
from .api_agent import APIAgent as APIAgent
from .api_model import APIModel as APIModel
@@ -77,6 +79,7 @@
from .droplet_backup_policy import DropletBackupPolicy as DropletBackupPolicy
from .image_generate_params import ImageGenerateParams as ImageGenerateParams
from .api_agent_api_key_info import APIAgentAPIKeyInfo as APIAgentAPIKeyInfo
+from .response_create_params import ResponseCreateParams as ResponseCreateParams
from .agent_retrieve_response import AgentRetrieveResponse as AgentRetrieveResponse
from .api_openai_api_key_info import APIOpenAIAPIKeyInfo as APIOpenAIAPIKeyInfo
from .gpu_droplet_list_params import GPUDropletListParams as GPUDropletListParams
@@ -105,6 +108,7 @@
from .agent_update_status_response import (
AgentUpdateStatusResponse as AgentUpdateStatusResponse,
)
+from .billing_list_insights_params import BillingListInsightsParams as BillingListInsightsParams
from .knowledge_base_create_params import (
KnowledgeBaseCreateParams as KnowledgeBaseCreateParams,
)
@@ -118,6 +122,9 @@
from .gpu_droplet_retrieve_response import (
GPUDropletRetrieveResponse as GPUDropletRetrieveResponse,
)
+from .billing_list_insights_response import (
+ BillingListInsightsResponse as BillingListInsightsResponse,
+)
from .knowledge_base_create_response import (
KnowledgeBaseCreateResponse as KnowledgeBaseCreateResponse,
)
diff --git a/src/gradient/types/agents/api_evaluation_prompt.py b/src/gradient/types/agents/api_evaluation_prompt.py
index 7471e9ae..1bb08bf1 100644
--- a/src/gradient/types/agents/api_evaluation_prompt.py
+++ b/src/gradient/types/agents/api_evaluation_prompt.py
@@ -1,11 +1,60 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
from ..._models import BaseModel
from .api_evaluation_metric_result import APIEvaluationMetricResult
-__all__ = ["APIEvaluationPrompt", "PromptChunk"]
+__all__ = ["APIEvaluationPrompt", "EvaluationTraceSpan", "EvaluationTraceSpanRetrieverChunk", "PromptChunk"]
+
+
+class EvaluationTraceSpanRetrieverChunk(BaseModel):
+ chunk_usage_pct: Optional[float] = None
+ """The usage percentage of the chunk."""
+
+ chunk_used: Optional[bool] = None
+ """Indicates if the chunk was used in the prompt."""
+
+ index_uuid: Optional[str] = None
+ """The index uuid (Knowledge Base) of the chunk."""
+
+ source_name: Optional[str] = None
+ """The source name for the chunk, e.g., the file name or document title."""
+
+ text: Optional[str] = None
+ """Text content of the chunk."""
+
+
+class EvaluationTraceSpan(BaseModel):
+ """Represents a span within an evaluatioin trace (e.g., LLM call, tool call, etc.)"""
+
+ created_at: Optional[datetime] = None
+ """When the span was created"""
+
+ input: Optional[object] = None
+ """
+ Input data for the span (flexible structure - can be messages array, string,
+ etc.)
+ """
+
+ name: Optional[str] = None
+ """Name/identifier for the span"""
+
+ output: Optional[object] = None
+ """Output data from the span (flexible structure - can be message, string, etc.)"""
+
+ retriever_chunks: Optional[List[EvaluationTraceSpanRetrieverChunk]] = None
+ """Any retriever span chunks that were included as part of the span."""
+
+ span_level_metric_results: Optional[List[APIEvaluationMetricResult]] = None
+ """The span-level metric results."""
+
+ type: Optional[
+ Literal["TRACE_SPAN_TYPE_UNKNOWN", "TRACE_SPAN_TYPE_LLM", "TRACE_SPAN_TYPE_RETRIEVER", "TRACE_SPAN_TYPE_TOOL"]
+ ] = None
+ """Types of spans in a trace"""
class PromptChunk(BaseModel):
@@ -26,6 +75,9 @@ class PromptChunk(BaseModel):
class APIEvaluationPrompt(BaseModel):
+ evaluation_trace_spans: Optional[List[EvaluationTraceSpan]] = None
+ """The evaluated trace spans."""
+
ground_truth: Optional[str] = None
"""The ground truth for the prompt."""
@@ -47,3 +99,6 @@ class APIEvaluationPrompt(BaseModel):
prompt_level_metric_results: Optional[List[APIEvaluationMetricResult]] = None
"""The metric results for the prompt."""
+
+ trace_id: Optional[str] = None
+ """The trace id for the prompt."""
diff --git a/src/gradient/types/agents/api_evaluation_run.py b/src/gradient/types/agents/api_evaluation_run.py
index 5a758898..ed4ef0a1 100644
--- a/src/gradient/types/agents/api_evaluation_run.py
+++ b/src/gradient/types/agents/api_evaluation_run.py
@@ -14,6 +14,9 @@ class APIEvaluationRun(BaseModel):
agent_deleted: Optional[bool] = None
"""Whether agent is deleted"""
+ agent_deployment_name: Optional[str] = None
+ """The agent deployment name"""
+
agent_name: Optional[str] = None
"""Agent name"""
diff --git a/src/gradient/types/agents/chat/completion_create_params.py b/src/gradient/types/agents/chat/completion_create_params.py
index d238f8e1..797c6ea3 100644
--- a/src/gradient/types/agents/chat/completion_create_params.py
+++ b/src/gradient/types/agents/chat/completion_create_params.py
@@ -11,20 +11,42 @@
"CompletionCreateParamsBase",
"Message",
"MessageChatCompletionRequestSystemMessage",
+ "MessageChatCompletionRequestSystemMessageContent",
+ "MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestSystemMessageContentArrayOfContentPart",
- "MessageChatCompletionRequestSystemMessageContentArrayOfContentPartUnionMember1",
+ "MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestDeveloperMessage",
+ "MessageChatCompletionRequestDeveloperMessageContent",
+ "MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPart",
- "MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartUnionMember1",
+ "MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestUserMessage",
+ "MessageChatCompletionRequestUserMessageContent",
+ "MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestUserMessageContentArrayOfContentPart",
- "MessageChatCompletionRequestUserMessageContentArrayOfContentPartUnionMember1",
+ "MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestAssistantMessage",
+ "MessageChatCompletionRequestAssistantMessageContent",
+ "MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestAssistantMessageContentArrayOfContentPart",
- "MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartUnionMember1",
+ "MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestAssistantMessageToolCall",
"MessageChatCompletionRequestAssistantMessageToolCallFunction",
"MessageChatCompletionRequestToolMessage",
+ "MessageChatCompletionRequestToolMessageContent",
+ "MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartTextCacheControl",
+ "MessageChatCompletionRequestToolMessageContentArrayOfContentPart",
+ "MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"StreamOptions",
"ToolChoice",
"ToolChoiceChatCompletionNamedToolChoice",
@@ -165,7 +187,46 @@ class CompletionCreateParamsBase(TypedDict, total=False):
"""
-class MessageChatCompletionRequestSystemMessageContentArrayOfContentPartUnionMember1(TypedDict, total=False):
+class MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
"""Content part with type and text"""
text: Required[str]
@@ -174,9 +235,18 @@ class MessageChatCompletionRequestSystemMessageContentArrayOfContentPartUnionMem
type: Required[Literal["text"]]
"""The type of content part"""
+ cache_control: MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
MessageChatCompletionRequestSystemMessageContentArrayOfContentPart: TypeAlias = Union[
- str, MessageChatCompletionRequestSystemMessageContentArrayOfContentPartUnionMember1
+ str, MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestSystemMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestSystemMessageContentArrayOfContentPart],
]
@@ -186,14 +256,26 @@ class MessageChatCompletionRequestSystemMessage(TypedDict, total=False):
messages sent by the user.
"""
- content: Required[Union[str, SequenceNotStr[MessageChatCompletionRequestSystemMessageContentArrayOfContentPart]]]
+ content: Required[MessageChatCompletionRequestSystemMessageContent]
"""The contents of the system message."""
role: Required[Literal["system"]]
"""The role of the messages author, in this case `system`."""
-class MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartUnionMember1(TypedDict, total=False):
+class MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
"""Content part with type and text"""
text: Required[str]
@@ -202,9 +284,45 @@ class MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartUnion
type: Required[Literal["text"]]
"""The type of content part"""
+ cache_control: MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPart: TypeAlias = Union[
- str, MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartUnionMember1
+ str, MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestDeveloperMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPart],
]
@@ -214,14 +332,26 @@ class MessageChatCompletionRequestDeveloperMessage(TypedDict, total=False):
messages sent by the user.
"""
- content: Required[Union[str, SequenceNotStr[MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPart]]]
+ content: Required[MessageChatCompletionRequestDeveloperMessageContent]
"""The contents of the developer message."""
role: Required[Literal["developer"]]
"""The role of the messages author, in this case `developer`."""
-class MessageChatCompletionRequestUserMessageContentArrayOfContentPartUnionMember1(TypedDict, total=False):
+class MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
"""Content part with type and text"""
text: Required[str]
@@ -230,9 +360,47 @@ class MessageChatCompletionRequestUserMessageContentArrayOfContentPartUnionMembe
type: Required[Literal["text"]]
"""The type of content part"""
+ cache_control: MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: (
+ MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ )
+ """Cache control settings for the content part."""
+
MessageChatCompletionRequestUserMessageContentArrayOfContentPart: TypeAlias = Union[
- str, MessageChatCompletionRequestUserMessageContentArrayOfContentPartUnionMember1
+ str, MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestUserMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestUserMessageContentArrayOfContentPart],
]
@@ -242,14 +410,26 @@ class MessageChatCompletionRequestUserMessage(TypedDict, total=False):
information.
"""
- content: Required[Union[str, SequenceNotStr[MessageChatCompletionRequestUserMessageContentArrayOfContentPart]]]
+ content: Required[MessageChatCompletionRequestUserMessageContent]
"""The contents of the user message."""
role: Required[Literal["user"]]
"""The role of the messages author, in this case `user`."""
-class MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartUnionMember1(TypedDict, total=False):
+class MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
"""Content part with type and text"""
text: Required[str]
@@ -258,9 +438,45 @@ class MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartUnion
type: Required[Literal["text"]]
"""The type of content part"""
+ cache_control: MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
MessageChatCompletionRequestAssistantMessageContentArrayOfContentPart: TypeAlias = Union[
- str, MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartUnionMember1
+ str, MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestAssistantMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestAssistantMessageContentArrayOfContentPart],
]
@@ -296,15 +512,80 @@ class MessageChatCompletionRequestAssistantMessage(TypedDict, total=False):
role: Required[Literal["assistant"]]
"""The role of the messages author, in this case `assistant`."""
- content: Union[str, SequenceNotStr[MessageChatCompletionRequestAssistantMessageContentArrayOfContentPart], None]
+ content: Optional[MessageChatCompletionRequestAssistantMessageContent]
"""The contents of the assistant message."""
tool_calls: Iterable[MessageChatCompletionRequestAssistantMessageToolCall]
"""The tool calls generated by the model, such as function calls."""
+class MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: (
+ MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ )
+ """Cache control settings for the content part."""
+
+
+MessageChatCompletionRequestToolMessageContentArrayOfContentPart: TypeAlias = Union[
+ str, MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestToolMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestToolMessageContentArrayOfContentPart],
+]
+
+
class MessageChatCompletionRequestToolMessage(TypedDict, total=False):
- content: Required[str]
+ content: Required[MessageChatCompletionRequestToolMessageContent]
"""The contents of the tool message."""
role: Required[Literal["tool"]]
diff --git a/src/gradient/types/agents/evaluation_dataset_create_params.py b/src/gradient/types/agents/evaluation_dataset_create_params.py
index c8a84c23..661a42da 100644
--- a/src/gradient/types/agents/evaluation_dataset_create_params.py
+++ b/src/gradient/types/agents/evaluation_dataset_create_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing_extensions import TypedDict
+from typing_extensions import Literal, TypedDict
from ..knowledge_bases.api_file_upload_data_source_param import APIFileUploadDataSourceParam
@@ -10,6 +10,10 @@
class EvaluationDatasetCreateParams(TypedDict, total=False):
+ dataset_type: Literal[
+ "EVALUATION_DATASET_TYPE_UNKNOWN", "EVALUATION_DATASET_TYPE_ADK", "EVALUATION_DATASET_TYPE_NON_ADK"
+ ]
+
file_upload_dataset: APIFileUploadDataSourceParam
"""File to upload as data source for knowledge base."""
diff --git a/src/gradient/types/agents/evaluation_run_create_params.py b/src/gradient/types/agents/evaluation_run_create_params.py
index 52bbee85..b5e60803 100644
--- a/src/gradient/types/agents/evaluation_run_create_params.py
+++ b/src/gradient/types/agents/evaluation_run_create_params.py
@@ -10,8 +10,11 @@
class EvaluationRunCreateParams(TypedDict, total=False):
+ agent_deployment_names: SequenceNotStr[str]
+ """Agent deployment names to run the test case against (ADK agent workspaces)."""
+
agent_uuids: SequenceNotStr[str]
- """Agent UUIDs to run the test case against."""
+ """Agent UUIDs to run the test case against (legacy agents)."""
run_name: str
"""The name of the run."""
diff --git a/src/gradient/types/agents/evaluation_test_case_create_params.py b/src/gradient/types/agents/evaluation_test_case_create_params.py
index af49d024..ff0666b9 100644
--- a/src/gradient/types/agents/evaluation_test_case_create_params.py
+++ b/src/gradient/types/agents/evaluation_test_case_create_params.py
@@ -11,6 +11,8 @@
class EvaluationTestCaseCreateParams(TypedDict, total=False):
+ agent_workspace_name: str
+
dataset_uuid: str
"""Dataset against which the test‑case is executed."""
diff --git a/src/gradient/types/api_agent_model.py b/src/gradient/types/api_agent_model.py
index e42bb5d5..a6a36c6f 100644
--- a/src/gradient/types/api_agent_model.py
+++ b/src/gradient/types/api_agent_model.py
@@ -29,6 +29,15 @@ class APIAgentModel(BaseModel):
is_foundational: Optional[bool] = None
"""True if it is a foundational model provided by do"""
+ kb_default_chunk_size: Optional[int] = None
+ """Default chunking size limit to show in UI"""
+
+ kb_max_chunk_size: Optional[int] = None
+ """Maximum chunk size limit of model"""
+
+ kb_min_chunk_size: Optional[int] = None
+ """Minimum chunking size token limits if model supports KNOWLEDGEBASE usecase"""
+
metadata: Optional[object] = None
"""Additional meta data"""
diff --git a/src/gradient/types/api_model.py b/src/gradient/types/api_model.py
index 83b1b66a..1d9752e4 100644
--- a/src/gradient/types/api_model.py
+++ b/src/gradient/types/api_model.py
@@ -25,6 +25,15 @@ class APIModel(BaseModel):
is_foundational: Optional[bool] = None
"""True if it is a foundational model provided by do"""
+ kb_default_chunk_size: Optional[int] = None
+ """Default chunking size limit to show in UI"""
+
+ kb_max_chunk_size: Optional[int] = None
+ """Maximum chunk size limit of model"""
+
+ kb_min_chunk_size: Optional[int] = None
+ """Minimum chunking size token limits if model supports KNOWLEDGEBASE usecase"""
+
name: Optional[str] = None
"""Display name of the model"""
diff --git a/src/gradient/types/apps/__init__.py b/src/gradient/types/apps/__init__.py
new file mode 100644
index 00000000..b4a2e426
--- /dev/null
+++ b/src/gradient/types/apps/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .job_invocation_cancel_params import JobInvocationCancelParams as JobInvocationCancelParams
+from .job_invocation_cancel_response import JobInvocationCancelResponse as JobInvocationCancelResponse
diff --git a/src/gradient/types/apps/job_invocation_cancel_params.py b/src/gradient/types/apps/job_invocation_cancel_params.py
new file mode 100644
index 00000000..b026fcb9
--- /dev/null
+++ b/src/gradient/types/apps/job_invocation_cancel_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["JobInvocationCancelParams"]
+
+
+class JobInvocationCancelParams(TypedDict, total=False):
+ app_id: Required[str]
+
+ job_name: str
+ """The job name to list job invocations for."""
diff --git a/src/gradient/types/apps/job_invocation_cancel_response.py b/src/gradient/types/apps/job_invocation_cancel_response.py
new file mode 100644
index 00000000..96b2c642
--- /dev/null
+++ b/src/gradient/types/apps/job_invocation_cancel_response.py
@@ -0,0 +1,77 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = [
+ "JobInvocationCancelResponse",
+ "Trigger",
+ "TriggerManual",
+ "TriggerManualUser",
+ "TriggerScheduled",
+ "TriggerScheduledSchedule",
+]
+
+
+class TriggerManualUser(BaseModel):
+ """The user who triggered the job"""
+
+ email: Optional[str] = None
+
+ full_name: Optional[str] = None
+
+ uuid: Optional[str] = None
+
+
+class TriggerManual(BaseModel):
+ """Details about the manual trigger, if applicable"""
+
+ user: Optional[TriggerManualUser] = None
+ """The user who triggered the job"""
+
+
+class TriggerScheduledSchedule(BaseModel):
+ cron: Optional[str] = None
+ """The cron expression defining the schedule"""
+
+ time_zone: Optional[str] = None
+ """The time zone for the schedule"""
+
+
+class TriggerScheduled(BaseModel):
+ """The schedule for the job"""
+
+ schedule: Optional[TriggerScheduledSchedule] = None
+
+
+class Trigger(BaseModel):
+ manual: Optional[TriggerManual] = None
+ """Details about the manual trigger, if applicable"""
+
+ scheduled: Optional[TriggerScheduled] = None
+ """The schedule for the job"""
+
+ type: Optional[Literal["MANUAL", "SCHEDULE", "UNKNOWN"]] = None
+ """The type of trigger that initiated the job invocation."""
+
+
+class JobInvocationCancelResponse(BaseModel):
+ id: Optional[str] = None
+
+ completed_at: Optional[datetime] = None
+
+ created_at: Optional[datetime] = None
+
+ deployment_id: Optional[str] = None
+
+ job_name: Optional[str] = None
+
+ phase: Optional[Literal["UNKNOWN", "PENDING", "RUNNING", "SUCCEEDED", "FAILED", "CANCELED", "SKIPPED"]] = None
+ """The phase of the job invocation"""
+
+ started_at: Optional[datetime] = None
+
+ trigger: Optional[Trigger] = None
diff --git a/src/gradient/types/billing_list_insights_params.py b/src/gradient/types/billing_list_insights_params.py
new file mode 100644
index 00000000..6e89dd1e
--- /dev/null
+++ b/src/gradient/types/billing_list_insights_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from datetime import date
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["BillingListInsightsParams"]
+
+
+class BillingListInsightsParams(TypedDict, total=False):
+ account_urn: Required[str]
+
+ start_date: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]]
+
+ page: int
+ """Which 'page' of paginated results to return."""
+
+ per_page: int
+ """Number of items returned per page"""
diff --git a/src/gradient/types/billing_list_insights_response.py b/src/gradient/types/billing_list_insights_response.py
new file mode 100644
index 00000000..f7515a6b
--- /dev/null
+++ b/src/gradient/types/billing_list_insights_response.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import date
+
+from .._models import BaseModel
+
+__all__ = ["BillingListInsightsResponse", "DataPoint"]
+
+
+class DataPoint(BaseModel):
+ description: Optional[str] = None
+ """Description of the billed resource or service as shown on an invoice item"""
+
+ group_description: Optional[str] = None
+ """
+ Optional invoice item group name of the billed resource or service, blank when
+ not part an invoice item group
+ """
+
+ region: Optional[str] = None
+ """Region where the usage occurred"""
+
+ sku: Optional[str] = None
+ """Unique SKU identifier for the billed resource"""
+
+ start_date: Optional[date] = None
+ """Start date of the billing data point in YYYY-MM-DD format"""
+
+ total_amount: Optional[str] = None
+ """Total amount for this data point in USD"""
+
+ usage_team_urn: Optional[str] = None
+ """URN of the team that incurred the usage"""
+
+
+class BillingListInsightsResponse(BaseModel):
+ current_page: int
+ """Current page number"""
+
+ data_points: List[DataPoint]
+ """
+ Array of billing data points, which are day-over-day changes in billing resource
+ usage based on nightly invoice item estimates, for the requested period
+ """
+
+ total_items: int
+ """Total number of items available across all pages"""
+
+ total_pages: int
+ """Total number of pages available"""
diff --git a/src/gradient/types/chat/completion_create_params.py b/src/gradient/types/chat/completion_create_params.py
index e889c5e8..bf5bd49d 100644
--- a/src/gradient/types/chat/completion_create_params.py
+++ b/src/gradient/types/chat/completion_create_params.py
@@ -11,20 +11,42 @@
"CompletionCreateParamsBase",
"Message",
"MessageChatCompletionRequestSystemMessage",
+ "MessageChatCompletionRequestSystemMessageContent",
+ "MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestSystemMessageContentArrayOfContentPart",
- "MessageChatCompletionRequestSystemMessageContentArrayOfContentPartUnionMember1",
+ "MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestDeveloperMessage",
+ "MessageChatCompletionRequestDeveloperMessageContent",
+ "MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPart",
- "MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartUnionMember1",
+ "MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestUserMessage",
+ "MessageChatCompletionRequestUserMessageContent",
+ "MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestUserMessageContentArrayOfContentPart",
- "MessageChatCompletionRequestUserMessageContentArrayOfContentPartUnionMember1",
+ "MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestAssistantMessage",
+ "MessageChatCompletionRequestAssistantMessageContent",
+ "MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestAssistantMessageContentArrayOfContentPart",
- "MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartUnionMember1",
+ "MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"MessageChatCompletionRequestAssistantMessageToolCall",
"MessageChatCompletionRequestAssistantMessageToolCallFunction",
"MessageChatCompletionRequestToolMessage",
+ "MessageChatCompletionRequestToolMessageContent",
+ "MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartTextCacheControl",
+ "MessageChatCompletionRequestToolMessageContentArrayOfContentPart",
+ "MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartText",
+ "MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl",
"StreamOptions",
"ToolChoice",
"ToolChoiceChatCompletionNamedToolChoice",
@@ -165,7 +187,46 @@ class CompletionCreateParamsBase(TypedDict, total=False):
"""
-class MessageChatCompletionRequestSystemMessageContentArrayOfContentPartUnionMember1(TypedDict, total=False):
+class MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
"""Content part with type and text"""
text: Required[str]
@@ -174,9 +235,18 @@ class MessageChatCompletionRequestSystemMessageContentArrayOfContentPartUnionMem
type: Required[Literal["text"]]
"""The type of content part"""
+ cache_control: MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
MessageChatCompletionRequestSystemMessageContentArrayOfContentPart: TypeAlias = Union[
- str, MessageChatCompletionRequestSystemMessageContentArrayOfContentPartUnionMember1
+ str, MessageChatCompletionRequestSystemMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestSystemMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestSystemMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestSystemMessageContentArrayOfContentPart],
]
@@ -186,14 +256,26 @@ class MessageChatCompletionRequestSystemMessage(TypedDict, total=False):
messages sent by the user.
"""
- content: Required[Union[str, SequenceNotStr[MessageChatCompletionRequestSystemMessageContentArrayOfContentPart]]]
+ content: Required[MessageChatCompletionRequestSystemMessageContent]
"""The contents of the system message."""
role: Required[Literal["system"]]
"""The role of the messages author, in this case `system`."""
-class MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartUnionMember1(TypedDict, total=False):
+class MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
"""Content part with type and text"""
text: Required[str]
@@ -202,9 +284,45 @@ class MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartUnion
type: Required[Literal["text"]]
"""The type of content part"""
+ cache_control: MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPart: TypeAlias = Union[
- str, MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartUnionMember1
+ str, MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestDeveloperMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestDeveloperMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPart],
]
@@ -214,14 +332,26 @@ class MessageChatCompletionRequestDeveloperMessage(TypedDict, total=False):
messages sent by the user.
"""
- content: Required[Union[str, SequenceNotStr[MessageChatCompletionRequestDeveloperMessageContentArrayOfContentPart]]]
+ content: Required[MessageChatCompletionRequestDeveloperMessageContent]
"""The contents of the developer message."""
role: Required[Literal["developer"]]
"""The role of the messages author, in this case `developer`."""
-class MessageChatCompletionRequestUserMessageContentArrayOfContentPartUnionMember1(TypedDict, total=False):
+class MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
"""Content part with type and text"""
text: Required[str]
@@ -230,9 +360,47 @@ class MessageChatCompletionRequestUserMessageContentArrayOfContentPartUnionMembe
type: Required[Literal["text"]]
"""The type of content part"""
+ cache_control: MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: (
+ MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ )
+ """Cache control settings for the content part."""
+
MessageChatCompletionRequestUserMessageContentArrayOfContentPart: TypeAlias = Union[
- str, MessageChatCompletionRequestUserMessageContentArrayOfContentPartUnionMember1
+ str, MessageChatCompletionRequestUserMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestUserMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestUserMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestUserMessageContentArrayOfContentPart],
]
@@ -242,14 +410,26 @@ class MessageChatCompletionRequestUserMessage(TypedDict, total=False):
information.
"""
- content: Required[Union[str, SequenceNotStr[MessageChatCompletionRequestUserMessageContentArrayOfContentPart]]]
+ content: Required[MessageChatCompletionRequestUserMessageContent]
"""The contents of the user message."""
role: Required[Literal["user"]]
"""The role of the messages author, in this case `user`."""
-class MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartUnionMember1(TypedDict, total=False):
+class MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
"""Content part with type and text"""
text: Required[str]
@@ -258,9 +438,45 @@ class MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartUnion
type: Required[Literal["text"]]
"""The type of content part"""
+ cache_control: MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
MessageChatCompletionRequestAssistantMessageContentArrayOfContentPart: TypeAlias = Union[
- str, MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartUnionMember1
+ str, MessageChatCompletionRequestAssistantMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestAssistantMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestAssistantMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestAssistantMessageContentArrayOfContentPart],
]
@@ -296,15 +512,80 @@ class MessageChatCompletionRequestAssistantMessage(TypedDict, total=False):
role: Required[Literal["assistant"]]
"""The role of the messages author, in this case `assistant`."""
- content: Union[str, SequenceNotStr[MessageChatCompletionRequestAssistantMessageContentArrayOfContentPart], None]
+ content: Optional[MessageChatCompletionRequestAssistantMessageContent]
"""The contents of the assistant message."""
tool_calls: Iterable[MessageChatCompletionRequestAssistantMessageToolCall]
"""The tool calls generated by the model, such as function calls."""
+class MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartText(TypedDict, total=False):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartTextCacheControl
+ """Cache control settings for the content part."""
+
+
+class MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl(
+ TypedDict, total=False
+):
+ """Cache control settings for the content part."""
+
+ type: Required[Literal["ephemeral"]]
+ """The cache control type."""
+
+ ttl: Literal["5m", "1h"]
+ """The cache TTL."""
+
+
+class MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartText(
+ TypedDict, total=False
+):
+ """Content part with type and text"""
+
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["text"]]
+ """The type of content part"""
+
+ cache_control: (
+ MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartTextCacheControl
+ )
+ """Cache control settings for the content part."""
+
+
+MessageChatCompletionRequestToolMessageContentArrayOfContentPart: TypeAlias = Union[
+ str, MessageChatCompletionRequestToolMessageContentArrayOfContentPartChatCompletionRequestContentPartText
+]
+
+MessageChatCompletionRequestToolMessageContent: TypeAlias = Union[
+ str,
+ MessageChatCompletionRequestToolMessageContentChatCompletionRequestContentPartText,
+ SequenceNotStr[MessageChatCompletionRequestToolMessageContentArrayOfContentPart],
+]
+
+
class MessageChatCompletionRequestToolMessage(TypedDict, total=False):
- content: Required[str]
+ content: Required[MessageChatCompletionRequestToolMessageContent]
"""The contents of the tool message."""
role: Required[Literal["tool"]]
diff --git a/src/gradient/types/knowledge_base_create_params.py b/src/gradient/types/knowledge_base_create_params.py
index 24cfd98b..2c7bece1 100644
--- a/src/gradient/types/knowledge_base_create_params.py
+++ b/src/gradient/types/knowledge_base_create_params.py
@@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Iterable
-from typing_extensions import TypedDict
+from typing_extensions import Literal, TypedDict
from .._types import SequenceNotStr
from .knowledge_bases.aws_data_source_param import AwsDataSourceParam
@@ -11,7 +11,13 @@
from .knowledge_bases.api_file_upload_data_source_param import APIFileUploadDataSourceParam
from .knowledge_bases.api_web_crawler_data_source_param import APIWebCrawlerDataSourceParam
-__all__ = ["KnowledgeBaseCreateParams", "Datasource", "DatasourceDropboxDataSource", "DatasourceGoogleDriveDataSource"]
+__all__ = [
+ "KnowledgeBaseCreateParams",
+ "Datasource",
+ "DatasourceChunkingOptions",
+ "DatasourceDropboxDataSource",
+ "DatasourceGoogleDriveDataSource",
+]
class KnowledgeBaseCreateParams(TypedDict, total=False):
@@ -52,6 +58,25 @@ class KnowledgeBaseCreateParams(TypedDict, total=False):
"""The VPC to deploy the knowledge base database in"""
+class DatasourceChunkingOptions(TypedDict, total=False):
+ """Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature preview flag.**
+ """
+
+ child_chunk_size: int
+ """Hierarchical options"""
+
+ max_chunk_size: int
+ """Section_Based and Fixed_Length options"""
+
+ parent_chunk_size: int
+ """Hierarchical options"""
+
+ semantic_threshold: float
+ """Semantic options"""
+
+
class DatasourceDropboxDataSource(TypedDict, total=False):
"""Dropbox Data Source"""
@@ -88,6 +113,26 @@ class Datasource(TypedDict, total=False):
bucket_region: str
"""Deprecated, moved to data_source_details"""
+ chunking_algorithm: Literal[
+ "CHUNKING_ALGORITHM_UNKNOWN",
+ "CHUNKING_ALGORITHM_SECTION_BASED",
+ "CHUNKING_ALGORITHM_HIERARCHICAL",
+ "CHUNKING_ALGORITHM_SEMANTIC",
+ "CHUNKING_ALGORITHM_FIXED_LENGTH",
+ ]
+ """The chunking algorithm to use for processing data sources.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+ """
+
+ chunking_options: DatasourceChunkingOptions
+ """Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+ """
+
dropbox_data_source: DatasourceDropboxDataSource
"""Dropbox Data Source"""
diff --git a/src/gradient/types/knowledge_bases/__init__.py b/src/gradient/types/knowledge_bases/__init__.py
index a8ce2cc7..e7b88be8 100644
--- a/src/gradient/types/knowledge_bases/__init__.py
+++ b/src/gradient/types/knowledge_bases/__init__.py
@@ -10,12 +10,14 @@
from .indexing_job_list_params import IndexingJobListParams as IndexingJobListParams
from .data_source_create_params import DataSourceCreateParams as DataSourceCreateParams
from .data_source_list_response import DataSourceListResponse as DataSourceListResponse
+from .data_source_update_params import DataSourceUpdateParams as DataSourceUpdateParams
from .indexing_job_create_params import IndexingJobCreateParams as IndexingJobCreateParams
from .indexing_job_list_response import IndexingJobListResponse as IndexingJobListResponse
from .api_file_upload_data_source import APIFileUploadDataSource as APIFileUploadDataSource
from .api_web_crawler_data_source import APIWebCrawlerDataSource as APIWebCrawlerDataSource
from .data_source_create_response import DataSourceCreateResponse as DataSourceCreateResponse
from .data_source_delete_response import DataSourceDeleteResponse as DataSourceDeleteResponse
+from .data_source_update_response import DataSourceUpdateResponse as DataSourceUpdateResponse
from .api_spaces_data_source_param import APISpacesDataSourceParam as APISpacesDataSourceParam
from .indexing_job_create_response import IndexingJobCreateResponse as IndexingJobCreateResponse
from .api_knowledge_base_data_source import APIKnowledgeBaseDataSource as APIKnowledgeBaseDataSource
diff --git a/src/gradient/types/knowledge_bases/api_knowledge_base_data_source.py b/src/gradient/types/knowledge_bases/api_knowledge_base_data_source.py
index b73e325e..ef5db1a5 100644
--- a/src/gradient/types/knowledge_bases/api_knowledge_base_data_source.py
+++ b/src/gradient/types/knowledge_bases/api_knowledge_base_data_source.py
@@ -2,6 +2,7 @@
from typing import Optional
from datetime import datetime
+from typing_extensions import Literal
from ..._models import BaseModel
from .api_spaces_data_source import APISpacesDataSource
@@ -9,7 +10,13 @@
from .api_file_upload_data_source import APIFileUploadDataSource
from .api_web_crawler_data_source import APIWebCrawlerDataSource
-__all__ = ["APIKnowledgeBaseDataSource", "AwsDataSource", "DropboxDataSource", "GoogleDriveDataSource"]
+__all__ = [
+ "APIKnowledgeBaseDataSource",
+ "AwsDataSource",
+ "ChunkingOptions",
+ "DropboxDataSource",
+ "GoogleDriveDataSource",
+]
class AwsDataSource(BaseModel):
@@ -24,6 +31,25 @@ class AwsDataSource(BaseModel):
"""Region of bucket"""
+class ChunkingOptions(BaseModel):
+ """Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature preview flag.**
+ """
+
+ child_chunk_size: Optional[int] = None
+ """Hierarchical options"""
+
+ max_chunk_size: Optional[int] = None
+ """Section_Based and Fixed_Length options"""
+
+ parent_chunk_size: Optional[int] = None
+ """Hierarchical options"""
+
+ semantic_threshold: Optional[float] = None
+ """Semantic options"""
+
+
class DropboxDataSource(BaseModel):
"""Dropbox Data Source for Display"""
@@ -48,6 +74,28 @@ class APIKnowledgeBaseDataSource(BaseModel):
bucket_name: Optional[str] = None
"""Name of storage bucket - Deprecated, moved to data_source_details"""
+ chunking_algorithm: Optional[
+ Literal[
+ "CHUNKING_ALGORITHM_UNKNOWN",
+ "CHUNKING_ALGORITHM_SECTION_BASED",
+ "CHUNKING_ALGORITHM_HIERARCHICAL",
+ "CHUNKING_ALGORITHM_SEMANTIC",
+ "CHUNKING_ALGORITHM_FIXED_LENGTH",
+ ]
+ ] = None
+ """The chunking algorithm to use for processing data sources.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+ """
+
+ chunking_options: Optional[ChunkingOptions] = None
+ """Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+ """
+
created_at: Optional[datetime] = None
"""Creation date / time"""
diff --git a/src/gradient/types/knowledge_bases/data_source_create_params.py b/src/gradient/types/knowledge_bases/data_source_create_params.py
index ac3aa93c..bc65e42a 100644
--- a/src/gradient/types/knowledge_bases/data_source_create_params.py
+++ b/src/gradient/types/knowledge_bases/data_source_create_params.py
@@ -2,20 +2,40 @@
from __future__ import annotations
-from typing_extensions import Annotated, TypedDict
+from typing_extensions import Literal, Annotated, TypedDict
from ..._utils import PropertyInfo
from .aws_data_source_param import AwsDataSourceParam
from .api_spaces_data_source_param import APISpacesDataSourceParam
from .api_web_crawler_data_source_param import APIWebCrawlerDataSourceParam
-__all__ = ["DataSourceCreateParams"]
+__all__ = ["DataSourceCreateParams", "ChunkingOptions"]
class DataSourceCreateParams(TypedDict, total=False):
aws_data_source: AwsDataSourceParam
"""AWS S3 Data Source"""
+ chunking_algorithm: Literal[
+ "CHUNKING_ALGORITHM_UNKNOWN",
+ "CHUNKING_ALGORITHM_SECTION_BASED",
+ "CHUNKING_ALGORITHM_HIERARCHICAL",
+ "CHUNKING_ALGORITHM_SEMANTIC",
+ "CHUNKING_ALGORITHM_FIXED_LENGTH",
+ ]
+ """The chunking algorithm to use for processing data sources.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+ """
+
+ chunking_options: ChunkingOptions
+ """Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+ """
+
body_knowledge_base_uuid: Annotated[str, PropertyInfo(alias="knowledge_base_uuid")]
"""Knowledge base id"""
@@ -24,3 +44,22 @@ class DataSourceCreateParams(TypedDict, total=False):
web_crawler_data_source: APIWebCrawlerDataSourceParam
"""WebCrawlerDataSource"""
+
+
+class ChunkingOptions(TypedDict, total=False):
+ """Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature preview flag.**
+ """
+
+ child_chunk_size: int
+ """Hierarchical options"""
+
+ max_chunk_size: int
+ """Section_Based and Fixed_Length options"""
+
+ parent_chunk_size: int
+ """Hierarchical options"""
+
+ semantic_threshold: float
+ """Semantic options"""
diff --git a/src/gradient/types/knowledge_bases/data_source_update_params.py b/src/gradient/types/knowledge_bases/data_source_update_params.py
new file mode 100644
index 00000000..ffcdf95b
--- /dev/null
+++ b/src/gradient/types/knowledge_bases/data_source_update_params.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["DataSourceUpdateParams", "ChunkingOptions"]
+
+
+class DataSourceUpdateParams(TypedDict, total=False):
+ path_knowledge_base_uuid: Required[Annotated[str, PropertyInfo(alias="knowledge_base_uuid")]]
+
+ chunking_algorithm: Literal[
+ "CHUNKING_ALGORITHM_UNKNOWN",
+ "CHUNKING_ALGORITHM_SECTION_BASED",
+ "CHUNKING_ALGORITHM_HIERARCHICAL",
+ "CHUNKING_ALGORITHM_SEMANTIC",
+ "CHUNKING_ALGORITHM_FIXED_LENGTH",
+ ]
+ """The chunking algorithm to use for processing data sources.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+ """
+
+ chunking_options: ChunkingOptions
+ """Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature
+ preview flag.**
+ """
+
+ body_data_source_uuid: Annotated[str, PropertyInfo(alias="data_source_uuid")]
+ """Data Source ID (Path Parameter)"""
+
+ body_knowledge_base_uuid: Annotated[str, PropertyInfo(alias="knowledge_base_uuid")]
+ """Knowledge Base ID (Path Parameter)"""
+
+
+class ChunkingOptions(TypedDict, total=False):
+ """Configuration options for the chunking algorithm.
+
+ **Note: This feature requires enabling the knowledgebase enhancements feature preview flag.**
+ """
+
+ child_chunk_size: int
+ """Hierarchical options"""
+
+ max_chunk_size: int
+ """Section_Based and Fixed_Length options"""
+
+ parent_chunk_size: int
+ """Hierarchical options"""
+
+ semantic_threshold: float
+ """Semantic options"""
diff --git a/src/gradient/types/knowledge_bases/data_source_update_response.py b/src/gradient/types/knowledge_bases/data_source_update_response.py
new file mode 100644
index 00000000..31484137
--- /dev/null
+++ b/src/gradient/types/knowledge_bases/data_source_update_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .api_knowledge_base_data_source import APIKnowledgeBaseDataSource
+
+__all__ = ["DataSourceUpdateResponse"]
+
+
+class DataSourceUpdateResponse(BaseModel):
+ """
+ Update a data source of a knowledge base with change in chunking algorithm/options
+ """
+
+ knowledge_base_data_source: Optional[APIKnowledgeBaseDataSource] = None
+ """Data Source configuration for Knowledge Bases"""
diff --git a/src/gradient/types/response_create_params.py b/src/gradient/types/response_create_params.py
new file mode 100644
index 00000000..9b870f3c
--- /dev/null
+++ b/src/gradient/types/response_create_params.py
@@ -0,0 +1,331 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, List, Union, Iterable, Optional
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = [
+ "ResponseCreateParamsBase",
+ "InputUnionMember1",
+ "InputUnionMember1UnionMember0",
+ "InputUnionMember1UnionMember0Content",
+ "InputUnionMember1UnionMember1",
+ "InputUnionMember1UnionMember1ContentUnionMember1",
+ "InputUnionMember1UnionMember1ContentUnionMember1UnionMember0",
+ "InputUnionMember1UnionMember1ToolCall",
+ "InputUnionMember1UnionMember1ToolCallFunction",
+ "StreamOptions",
+ "ToolChoice",
+ "ToolChoiceChatCompletionNamedToolChoice",
+ "ToolChoiceChatCompletionNamedToolChoiceFunction",
+ "Tool",
+ "ResponseCreateParamsNonStreaming",
+ "ResponseCreateParamsStreaming",
+]
+
+
+class ResponseCreateParamsBase(TypedDict, total=False):
+ input: Required[Union[str, Iterable[InputUnionMember1]]]
+ """The input text prompt or conversation history.
+
+ Can be a string or an array of message objects for conversation context.
+ """
+
+ model: Required[str]
+ """Model ID used to generate the response. Must be a VLLM model."""
+
+ instructions: Optional[str]
+ """System-level instructions for the model.
+
+ This sets the behavior and context for the response generation.
+ """
+
+ max_output_tokens: Optional[int]
+ """Maximum number of tokens to generate in the response.
+
+ If not specified, the model will use a default value.
+ """
+
+ max_tokens: Optional[int]
+ """The maximum number of tokens that can be generated in the completion.
+
+ Alias for max_output_tokens for compatibility.
+ """
+
+ metadata: Optional[Dict[str, str]]
+ """Set of 16 key-value pairs that can be attached to an object.
+
+ This can be useful for storing additional information about the object in a
+ structured format, and querying for objects via API or the dashboard.
+
+ Keys are strings with a maximum length of 64 characters. Values are strings with
+ a maximum length of 512 characters.
+ """
+
+ modalities: Optional[List[Literal["text"]]]
+ """Specifies the output types the model should generate.
+
+ For text-to-text, this should be ["text"].
+ """
+
+ parallel_tool_calls: Optional[bool]
+ """Whether to enable parallel tool calls.
+
+ When true, the model can make multiple tool calls in parallel.
+ """
+
+ stop: Union[Optional[str], SequenceNotStr[str], None]
+ """Up to 4 sequences where the API will stop generating further tokens.
+
+ The returned text will not contain the stop sequence.
+ """
+
+ stream_options: Optional[StreamOptions]
+ """Options for streaming response. Only set this when you set `stream: true`."""
+
+ temperature: Optional[float]
+ """What sampling temperature to use, between 0 and 2.
+
+ Higher values like 0.8 will make the output more random, while lower values like
+ 0.2 will make it more focused and deterministic. We generally recommend altering
+ this or `top_p` but not both.
+ """
+
+ tool_choice: ToolChoice
+ """
+ Controls which (if any) tool is called by the model. `none` means the model will
+ not call any tool and instead generates a message. `auto` means the model can
+ pick between generating a message or calling one or more tools. `required` means
+ the model must call one or more tools. Specifying a particular tool via
+ `{"type": "function", "function": {"name": "my_function"}}` forces the model to
+ call that tool.
+
+ `none` is the default when no tools are present. `auto` is the default if tools
+ are present.
+ """
+
+ tools: Iterable[Tool]
+ """A list of tools the model may call.
+
+ Currently, only functions are supported as a tool. Uses Responses API format
+ (with `name`, `description`, `parameters` at top level).
+ """
+
+ top_p: Optional[float]
+ """
+ An alternative to sampling with temperature, called nucleus sampling, where the
+ model considers the results of the tokens with top_p probability mass. So 0.1
+ means only the tokens comprising the top 10% probability mass are considered.
+
+ We generally recommend altering this or `temperature` but not both.
+ """
+
+ user: str
+ """
+ A unique identifier representing your end-user, which can help DigitalOcean to
+ monitor and detect abuse.
+ """
+
+
+class InputUnionMember1UnionMember0ContentTyped(TypedDict, total=False):
+ text: str
+ """The reasoning text content"""
+
+ type: Literal["reasoning_text"]
+ """The type of content"""
+
+
+InputUnionMember1UnionMember0Content: TypeAlias = Union[InputUnionMember1UnionMember0ContentTyped, Dict[str, object]]
+
+
+class InputUnionMember1UnionMember0Typed(TypedDict, total=False):
+ type: Required[Literal["function_call", "function_call_output", "reasoning"]]
+ """
+ The type of input item (must be function_call, function_call_output, or
+ reasoning)
+ """
+
+ id: str
+ """The unique ID of the reasoning item (optional for reasoning)"""
+
+ arguments: str
+ """JSON string of function arguments (required for function_call)"""
+
+ call_id: str
+ """The call ID (required for function_call and function_call_output)"""
+
+ content: Optional[Iterable[InputUnionMember1UnionMember0Content]]
+ """Array of reasoning content parts (optional for reasoning, can be null)"""
+
+ encrypted_content: Optional[str]
+ """Encrypted content (optional)"""
+
+ name: str
+ """The function name (required for function_call)"""
+
+ output: str
+ """JSON string of function output (required for function_call_output)"""
+
+ status: Optional[str]
+ """Status of the item (optional, can be null)"""
+
+ summary: Iterable[object]
+ """Summary of the reasoning (optional for reasoning)"""
+
+
+InputUnionMember1UnionMember0: TypeAlias = Union[InputUnionMember1UnionMember0Typed, Dict[str, object]]
+
+
+class InputUnionMember1UnionMember1ContentUnionMember1UnionMember0(TypedDict, total=False):
+ text: Required[str]
+ """The text content"""
+
+ type: Required[Literal["input_text"]]
+ """The type of content part"""
+
+
+InputUnionMember1UnionMember1ContentUnionMember1: TypeAlias = Union[
+ InputUnionMember1UnionMember1ContentUnionMember1UnionMember0, Dict[str, object]
+]
+
+
+class InputUnionMember1UnionMember1ToolCallFunction(TypedDict, total=False):
+ """The function that the model called."""
+
+ arguments: Required[str]
+ """
+ The arguments to call the function with, as generated by the model in JSON
+ format. Note that the model does not always generate valid JSON, and may
+ hallucinate parameters not defined by your function schema. Validate the
+ arguments in your code before calling your function.
+ """
+
+ name: Required[str]
+ """The name of the function to call."""
+
+
+class InputUnionMember1UnionMember1ToolCall(TypedDict, total=False):
+ id: Required[str]
+ """The ID of the tool call."""
+
+ function: Required[InputUnionMember1UnionMember1ToolCallFunction]
+ """The function that the model called."""
+
+ type: Required[Literal["function"]]
+ """The type of the tool. Currently, only `function` is supported."""
+
+
+class InputUnionMember1UnionMember1Typed(TypedDict, total=False):
+ content: Required[Union[str, Iterable[InputUnionMember1UnionMember1ContentUnionMember1]]]
+ """The content of the message (string or content parts array)"""
+
+ role: Literal["user", "assistant", "system", "tool", "developer"]
+ """The role of the message author"""
+
+ tool_call_id: str
+ """Tool call ID that this message is responding to (required for tool role)"""
+
+ tool_calls: Iterable[InputUnionMember1UnionMember1ToolCall]
+ """Tool calls made by the assistant (for assistant role messages)"""
+
+ type: Literal["message"]
+ """Optional type identifier for message items (used by some clients like Codex)"""
+
+
+InputUnionMember1UnionMember1: TypeAlias = Union[InputUnionMember1UnionMember1Typed, Dict[str, object]]
+
+InputUnionMember1: TypeAlias = Union[InputUnionMember1UnionMember0, InputUnionMember1UnionMember1]
+
+
+class StreamOptions(TypedDict, total=False):
+ """Options for streaming response. Only set this when you set `stream: true`."""
+
+ include_usage: bool
+ """If set, an additional chunk will be streamed before the `data: [DONE]` message.
+
+ The `usage` field on this chunk shows the token usage statistics for the entire
+ request, and the `choices` field will always be an empty array.
+
+ All other chunks will also include a `usage` field, but with a null value.
+ **NOTE:** If the stream is interrupted, you may not receive the final usage
+ chunk which contains the total token usage for the request.
+ """
+
+
+class ToolChoiceChatCompletionNamedToolChoiceFunction(TypedDict, total=False):
+ name: Required[str]
+ """The name of the function to call."""
+
+
+class ToolChoiceChatCompletionNamedToolChoice(TypedDict, total=False):
+ """Specifies a tool the model should use.
+
+ Use to force the model to call a specific function.
+ """
+
+ function: Required[ToolChoiceChatCompletionNamedToolChoiceFunction]
+
+ type: Required[Literal["function"]]
+ """The type of the tool. Currently, only `function` is supported."""
+
+
+ToolChoice: TypeAlias = Union[Literal["none", "auto", "required"], ToolChoiceChatCompletionNamedToolChoice]
+
+
+class Tool(TypedDict, total=False):
+ """Tool definition for Responses API (flat format).
+
+ This format is used by VLLM's Responses API where name, description, and parameters are at the top level of the tool object.
+ """
+
+ type: Required[Literal["function", "web_search", "web_search_2025_08_26"]]
+ """The type of the tool.
+
+ Supported values are `function` (custom tools), `web_search`, and
+ `web_search_2025_08_26` (built-in web search).
+ """
+
+ description: str
+ """
+ A description of what the function does, used by the model to choose when and
+ how to call the function.
+ """
+
+ name: str
+ """The name of the function to be called.
+
+ Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length
+ of 64.
+ """
+
+ parameters: Dict[str, object]
+ """The parameters the functions accepts, described as a JSON Schema object.
+
+ See the [guide](/docs/guides/function-calling) for examples, and the
+ [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for
+ documentation about the format.
+
+ Omitting `parameters` defines a function with an empty parameter list.
+ """
+
+
+class ResponseCreateParamsNonStreaming(ResponseCreateParamsBase, total=False):
+ stream: Optional[Literal[False]]
+ """
+ If set to true, the model response data will be streamed to the client as it is
+ generated using server-sent events.
+ """
+
+
+class ResponseCreateParamsStreaming(ResponseCreateParamsBase):
+ stream: Required[Literal[True]]
+ """
+ If set to true, the model response data will be streamed to the client as it is
+ generated using server-sent events.
+ """
+
+
+ResponseCreateParams = Union[ResponseCreateParamsNonStreaming, ResponseCreateParamsStreaming]
diff --git a/src/gradient/types/shared/__init__.py b/src/gradient/types/shared/__init__.py
index 4fb2986a..272092b7 100644
--- a/src/gradient/types/shared/__init__.py
+++ b/src/gradient/types/shared/__init__.py
@@ -26,7 +26,9 @@
from .chat_completion_chunk import ChatCompletionChunk as ChatCompletionChunk
from .image_gen_stream_event import ImageGenStreamEvent as ImageGenStreamEvent
from .subscription_tier_base import SubscriptionTierBase as SubscriptionTierBase
+from .create_response_response import CreateResponseResponse as CreateResponseResponse
from .image_gen_completed_event import ImageGenCompletedEvent as ImageGenCompletedEvent
from .droplet_next_backup_window import DropletNextBackupWindow as DropletNextBackupWindow
from .chat_completion_token_logprob import ChatCompletionTokenLogprob as ChatCompletionTokenLogprob
from .image_gen_partial_image_event import ImageGenPartialImageEvent as ImageGenPartialImageEvent
+from .create_response_stream_response import CreateResponseStreamResponse as CreateResponseStreamResponse
diff --git a/src/gradient/types/shared/completion_usage.py b/src/gradient/types/shared/completion_usage.py
index 79ce64ee..596bd045 100644
--- a/src/gradient/types/shared/completion_usage.py
+++ b/src/gradient/types/shared/completion_usage.py
@@ -2,12 +2,31 @@
from ..._models import BaseModel
-__all__ = ["CompletionUsage"]
+__all__ = ["CompletionUsage", "CacheCreation"]
+
+
+class CacheCreation(BaseModel):
+ """Breakdown of prompt tokens written to cache."""
+
+ ephemeral_1h_input_tokens: int
+ """Number of prompt tokens written to 1h cache."""
+
+ ephemeral_5m_input_tokens: int
+ """Number of prompt tokens written to 5m cache."""
class CompletionUsage(BaseModel):
"""Usage statistics for the completion request."""
+ cache_created_input_tokens: int
+ """Number of prompt tokens written to cache."""
+
+ cache_creation: CacheCreation
+ """Breakdown of prompt tokens written to cache."""
+
+ cache_read_input_tokens: int
+ """Number of prompt tokens read from cache."""
+
completion_tokens: int
"""Number of tokens in the generated completion."""
diff --git a/src/gradient/types/shared/create_response_response.py b/src/gradient/types/shared/create_response_response.py
new file mode 100644
index 00000000..61dbb155
--- /dev/null
+++ b/src/gradient/types/shared/create_response_response.py
@@ -0,0 +1,332 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import builtins
+from typing import Dict, List, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from ..._models import BaseModel
+from .chat_completion_token_logprob import ChatCompletionTokenLogprob
+
+__all__ = [
+ "CreateResponseResponse",
+ "Usage",
+ "UsageInputTokensDetails",
+ "UsageOutputTokensDetails",
+ "Choice",
+ "ChoiceMessage",
+ "ChoiceMessageToolCall",
+ "ChoiceMessageToolCallFunction",
+ "ChoiceLogprobs",
+ "Output",
+ "OutputUnionMember0",
+ "OutputUnionMember1",
+ "OutputUnionMember2",
+ "OutputUnionMember2Content",
+ "Tool",
+]
+
+
+class UsageInputTokensDetails(BaseModel):
+ """A detailed breakdown of the input tokens."""
+
+ cached_tokens: int
+ """The number of tokens that were retrieved from the cache.
+
+ [More on prompt caching](https://platform.openai.com/docs/guides/prompt-caching).
+ """
+
+
+class UsageOutputTokensDetails(BaseModel):
+ """A detailed breakdown of the output tokens."""
+
+ reasoning_tokens: int
+ """The number of reasoning tokens."""
+
+ tool_output_tokens: int
+ """The number of tool output tokens."""
+
+
+class Usage(BaseModel):
+ """
+ Detailed token usage statistics for the request, including input/output token counts and detailed breakdowns.
+ """
+
+ input_tokens: int
+ """The number of input tokens."""
+
+ input_tokens_details: UsageInputTokensDetails
+ """A detailed breakdown of the input tokens."""
+
+ output_tokens: int
+ """The number of output tokens."""
+
+ output_tokens_details: UsageOutputTokensDetails
+ """A detailed breakdown of the output tokens."""
+
+ total_tokens: int
+ """The total number of tokens used."""
+
+
+class ChoiceMessageToolCallFunction(BaseModel):
+ """The function that the model called."""
+
+ arguments: str
+ """
+ The arguments to call the function with, as generated by the model in JSON
+ format. Note that the model does not always generate valid JSON, and may
+ hallucinate parameters not defined by your function schema. Validate the
+ arguments in your code before calling your function.
+ """
+
+ name: str
+ """The name of the function to call."""
+
+
+class ChoiceMessageToolCall(BaseModel):
+ id: str
+ """The ID of the tool call."""
+
+ function: ChoiceMessageToolCallFunction
+ """The function that the model called."""
+
+ type: Literal["function"]
+ """The type of the tool. Currently, only `function` is supported."""
+
+
+class ChoiceMessage(BaseModel):
+ """The generated message response."""
+
+ content: Optional[str] = None
+ """The generated text content."""
+
+ role: Optional[Literal["assistant"]] = None
+ """The role of the message author, which is always `assistant`."""
+
+ tool_calls: Optional[List[ChoiceMessageToolCall]] = None
+ """The tool calls generated by the model, such as function calls."""
+
+
+class ChoiceLogprobs(BaseModel):
+ """Log probability information for the choice.
+
+ Only present if logprobs was requested in the request.
+ """
+
+ content: Optional[List[ChatCompletionTokenLogprob]] = None
+ """A list of message content tokens with log probability information."""
+
+
+class Choice(BaseModel):
+ finish_reason: Literal["stop", "length", "tool_calls", "content_filter"]
+ """The reason the model stopped generating tokens.
+
+ This will be `stop` if the model hit a natural stop point or a provided stop
+ sequence, `length` if the maximum number of tokens specified in the request was
+ reached, or `tool_calls` if the model called a tool.
+ """
+
+ index: int
+ """The index of the choice in the list of choices."""
+
+ message: ChoiceMessage
+ """The generated message response."""
+
+ logprobs: Optional[ChoiceLogprobs] = None
+ """Log probability information for the choice.
+
+ Only present if logprobs was requested in the request.
+ """
+
+
+class OutputUnionMember0(BaseModel):
+ arguments: str
+ """JSON string of function arguments"""
+
+ call_id: str
+ """The unique ID of the function tool call"""
+
+ name: str
+ """The name of the function to call"""
+
+ type: Literal["function_call"]
+ """The type of output item"""
+
+ id: Optional[str] = None
+ """The unique ID of the function tool call (same as call_id)"""
+
+ encrypted_content: Optional[str] = None
+ """Encrypted content (optional)"""
+
+ status: Optional[str] = None
+ """Status of the item (optional, can be null)"""
+
+
+class OutputUnionMember1(BaseModel):
+ text: str
+ """The text content"""
+
+ type: Literal["text"]
+ """The type of output item"""
+
+
+class OutputUnionMember2Content(BaseModel):
+ text: str
+ """The reasoning text content"""
+
+ type: Literal["reasoning_text"]
+ """The type of content"""
+
+
+class OutputUnionMember2(BaseModel):
+ id: str
+ """The unique ID of the reasoning item"""
+
+ content: List[OutputUnionMember2Content]
+ """Array of reasoning content parts"""
+
+ summary: List[object]
+ """Summary of the reasoning (usually empty)"""
+
+ type: Literal["reasoning"]
+ """The type of output item"""
+
+ encrypted_content: Optional[str] = None
+ """Encrypted content (optional)"""
+
+ status: Optional[str] = None
+ """Status of the item (optional, can be null)"""
+
+
+Output: TypeAlias = Union[OutputUnionMember0, OutputUnionMember1, OutputUnionMember2]
+
+
+class Tool(BaseModel):
+ """Tool definition for Responses API (flat format).
+
+ This format is used by VLLM's Responses API where name, description, and parameters are at the top level of the tool object.
+ """
+
+ type: Literal["function", "web_search", "web_search_2025_08_26"]
+ """The type of the tool.
+
+ Supported values are `function` (custom tools), `web_search`, and
+ `web_search_2025_08_26` (built-in web search).
+ """
+
+ description: Optional[str] = None
+ """
+ A description of what the function does, used by the model to choose when and
+ how to call the function.
+ """
+
+ name: Optional[str] = None
+ """The name of the function to be called.
+
+ Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length
+ of 64.
+ """
+
+ parameters: Optional[Dict[str, object]] = None
+ """The parameters the functions accepts, described as a JSON Schema object.
+
+ See the [guide](/docs/guides/function-calling) for examples, and the
+ [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for
+ documentation about the format.
+
+ Omitting `parameters` defines a function with an empty parameter list.
+ """
+
+
+class CreateResponseResponse(BaseModel):
+ """
+ Represents a text-to-text response returned by the model, based on the provided input. VLLM models only.
+ """
+
+ id: str
+ """A unique identifier for the response."""
+
+ created: int
+ """The Unix timestamp (in seconds) of when the response was created."""
+
+ model: str
+ """The model used to generate the response."""
+
+ object: Literal["response"]
+ """The object type, which is always `response`."""
+
+ usage: Usage
+ """
+ Detailed token usage statistics for the request, including input/output token
+ counts and detailed breakdowns.
+ """
+
+ background: Optional[bool] = None
+ """Whether the request was processed in the background"""
+
+ choices: Optional[List[Choice]] = None
+ """A list of response choices.
+
+ Can be more than one if `n` is greater than 1. Optional - Responses API
+ primarily uses the output array.
+ """
+
+ input_messages: Optional[List[builtins.object]] = None
+ """Input messages (if applicable)"""
+
+ max_output_tokens: Optional[int] = None
+ """Maximum output tokens setting"""
+
+ max_tool_calls: Optional[int] = None
+ """Maximum tool calls setting"""
+
+ output: Optional[List[Output]] = None
+ """An array of content items generated by the model.
+
+ This includes text content, function calls, reasoning items, and other output
+ types. Use this field for Responses API compatibility.
+ """
+
+ output_messages: Optional[List[builtins.object]] = None
+ """Output messages (if applicable)"""
+
+ parallel_tool_calls: Optional[bool] = None
+ """Whether parallel tool calls are enabled"""
+
+ previous_response_id: Optional[str] = None
+ """Previous response ID (for multi-turn conversations)"""
+
+ prompt: Optional[str] = None
+ """Prompt used for the response"""
+
+ reasoning: Optional[str] = None
+ """Reasoning content"""
+
+ service_tier: Optional[str] = None
+ """Service tier used"""
+
+ status: Optional[str] = None
+ """Status of the response"""
+
+ temperature: Optional[float] = None
+ """Temperature setting used for the response"""
+
+ text: Optional[str] = None
+ """Text content"""
+
+ tool_choice: Optional[str] = None
+ """Tool choice setting used for the response"""
+
+ tools: Optional[List[Tool]] = None
+ """Tools available for the response"""
+
+ top_logprobs: Optional[int] = None
+ """Top logprobs setting"""
+
+ top_p: Optional[float] = None
+ """Top-p setting used for the response"""
+
+ truncation: Optional[str] = None
+ """Truncation setting"""
+
+ user: Optional[str] = None
+ """User identifier"""
diff --git a/src/gradient/types/shared/create_response_stream_response.py b/src/gradient/types/shared/create_response_stream_response.py
new file mode 100644
index 00000000..ef0230c8
--- /dev/null
+++ b/src/gradient/types/shared/create_response_stream_response.py
@@ -0,0 +1,139 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .completion_usage import CompletionUsage
+from .chat_completion_token_logprob import ChatCompletionTokenLogprob
+
+__all__ = [
+ "CreateResponseStreamResponse",
+ "Choice",
+ "ChoiceDelta",
+ "ChoiceDeltaToolCall",
+ "ChoiceDeltaToolCallFunction",
+ "ChoiceLogprobs",
+]
+
+
+class ChoiceDeltaToolCallFunction(BaseModel):
+ """The function that the model called."""
+
+ arguments: str
+ """
+ The arguments to call the function with, as generated by the model in JSON
+ format. Note that the model does not always generate valid JSON, and may
+ hallucinate parameters not defined by your function schema. Validate the
+ arguments in your code before calling your function.
+ """
+
+ name: str
+ """The name of the function to call."""
+
+
+class ChoiceDeltaToolCall(BaseModel):
+ id: str
+ """The ID of the tool call."""
+
+ function: ChoiceDeltaToolCallFunction
+ """The function that the model called."""
+
+ type: Literal["function"]
+ """The type of the tool. Currently, only `function` is supported."""
+
+
+class ChoiceDelta(BaseModel):
+ """A chunk of the response message generated by the model."""
+
+ content: Optional[str] = None
+ """The contents of the chunk message.
+
+ Can be null for chunks with tool calls or other non-text content.
+ """
+
+ reasoning_content: Optional[str] = None
+ """The reasoning content generated by the model.
+
+ Only present when the model generates reasoning text.
+ """
+
+ role: Optional[Literal["assistant"]] = None
+ """The role of the message author. Only present in the first chunk."""
+
+ tool_calls: Optional[List[ChoiceDeltaToolCall]] = None
+ """The tool calls generated by the model, such as function calls.
+
+ Only present when the model decides to call a tool.
+ """
+
+
+class ChoiceLogprobs(BaseModel):
+ """Log probability information for the choice.
+
+ Only present if logprobs was requested in the request.
+ """
+
+ content: Optional[List[ChatCompletionTokenLogprob]] = None
+ """A list of message content tokens with log probability information."""
+
+
+class Choice(BaseModel):
+ delta: ChoiceDelta
+ """A chunk of the response message generated by the model."""
+
+ index: int
+ """The index of the choice in the list of choices."""
+
+ finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter"]] = None
+ """The reason the model stopped generating tokens.
+
+ This will be `stop` if the model hit a natural stop point or a provided stop
+ sequence, `length` if the maximum number of tokens specified in the request was
+ reached, or `tool_calls` if the model called a tool. Only present in the final
+ chunk.
+ """
+
+ logprobs: Optional[ChoiceLogprobs] = None
+ """Log probability information for the choice.
+
+ Only present if logprobs was requested in the request.
+ """
+
+
+class CreateResponseStreamResponse(BaseModel):
+ """
+ Represents a streamed chunk of a text-to-text response returned by the model, based on the provided input. VLLM models only.
+ """
+
+ id: str
+ """A unique identifier for the response. Each chunk has the same ID."""
+
+ choices: List[Choice]
+ """A list of response choice chunks.
+
+ Can contain more than one element if `n` is greater than 1. Can also be empty
+ for the last chunk if you set `stream_options: {"include_usage": true}`.
+ """
+
+ created: int
+ """The Unix timestamp (in seconds) of when the response was created.
+
+ Each chunk has the same timestamp.
+ """
+
+ model: str
+ """The model used to generate the response."""
+
+ object: Literal["response.chunk"]
+ """The object type, which is always `response.chunk`."""
+
+ usage: Optional[CompletionUsage] = None
+ """
+ An optional field that will only be present when you set
+ `stream_options: {"include_usage": true}` in your request. When present, it
+ contains a null value **except for the last chunk** which contains the token
+ usage statistics for the entire request. **NOTE:** If the stream is interrupted
+ or cancelled, you may not receive the final usage chunk which contains the total
+ token usage for the request.
+ """
diff --git a/tests/api_resources/agents/test_evaluation_datasets.py b/tests/api_resources/agents/test_evaluation_datasets.py
index 64dceb03..5093660e 100644
--- a/tests/api_resources/agents/test_evaluation_datasets.py
+++ b/tests/api_resources/agents/test_evaluation_datasets.py
@@ -30,6 +30,7 @@ def test_method_create(self, client: Gradient) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Gradient) -> None:
evaluation_dataset = client.agents.evaluation_datasets.create(
+ dataset_type="EVALUATION_DATASET_TYPE_UNKNOWN",
file_upload_dataset={
"original_file_name": "example name",
"size_in_bytes": "12345",
@@ -126,6 +127,7 @@ async def test_method_create(self, async_client: AsyncGradient) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncGradient) -> None:
evaluation_dataset = await async_client.agents.evaluation_datasets.create(
+ dataset_type="EVALUATION_DATASET_TYPE_UNKNOWN",
file_upload_dataset={
"original_file_name": "example name",
"size_in_bytes": "12345",
diff --git a/tests/api_resources/agents/test_evaluation_runs.py b/tests/api_resources/agents/test_evaluation_runs.py
index c6acaf82..8fdfe2cd 100644
--- a/tests/api_resources/agents/test_evaluation_runs.py
+++ b/tests/api_resources/agents/test_evaluation_runs.py
@@ -32,6 +32,7 @@ def test_method_create(self, client: Gradient) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Gradient) -> None:
evaluation_run = client.agents.evaluation_runs.create(
+ agent_deployment_names=["example string"],
agent_uuids=["example string"],
run_name="Evaluation Run Name",
test_case_uuid='"12345678-1234-1234-1234-123456789012"',
@@ -216,6 +217,7 @@ async def test_method_create(self, async_client: AsyncGradient) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncGradient) -> None:
evaluation_run = await async_client.agents.evaluation_runs.create(
+ agent_deployment_names=["example string"],
agent_uuids=["example string"],
run_name="Evaluation Run Name",
test_case_uuid='"12345678-1234-1234-1234-123456789012"',
diff --git a/tests/api_resources/agents/test_evaluation_test_cases.py b/tests/api_resources/agents/test_evaluation_test_cases.py
index 7cd0a07e..a8942239 100644
--- a/tests/api_resources/agents/test_evaluation_test_cases.py
+++ b/tests/api_resources/agents/test_evaluation_test_cases.py
@@ -33,6 +33,7 @@ def test_method_create(self, client: Gradient) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Gradient) -> None:
evaluation_test_case = client.agents.evaluation_test_cases.create(
+ agent_workspace_name="example name",
dataset_uuid="123e4567-e89b-12d3-a456-426614174000",
description="example string",
metrics=["example string"],
@@ -278,6 +279,7 @@ async def test_method_create(self, async_client: AsyncGradient) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncGradient) -> None:
evaluation_test_case = await async_client.agents.evaluation_test_cases.create(
+ agent_workspace_name="example name",
dataset_uuid="123e4567-e89b-12d3-a456-426614174000",
description="example string",
metrics=["example string"],
diff --git a/tests/api_resources/apps/__init__.py b/tests/api_resources/apps/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/apps/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/apps/test_job_invocations.py b/tests/api_resources/apps/test_job_invocations.py
new file mode 100644
index 00000000..a7cb68df
--- /dev/null
+++ b/tests/api_resources/apps/test_job_invocations.py
@@ -0,0 +1,148 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gradient import Gradient, AsyncGradient
+from tests.utils import assert_matches_type
+from gradient.types.apps import JobInvocationCancelResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestJobInvocations:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_cancel(self, client: Gradient) -> None:
+ job_invocation = client.apps.job_invocations.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ )
+ assert_matches_type(JobInvocationCancelResponse, job_invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_cancel_with_all_params(self, client: Gradient) -> None:
+ job_invocation = client.apps.job_invocations.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ job_name="job_name",
+ )
+ assert_matches_type(JobInvocationCancelResponse, job_invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_cancel(self, client: Gradient) -> None:
+ response = client.apps.job_invocations.with_raw_response.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ job_invocation = response.parse()
+ assert_matches_type(JobInvocationCancelResponse, job_invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_cancel(self, client: Gradient) -> None:
+ with client.apps.job_invocations.with_streaming_response.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ job_invocation = response.parse()
+ assert_matches_type(JobInvocationCancelResponse, job_invocation, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_cancel(self, client: Gradient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `app_id` but received ''"):
+ client.apps.job_invocations.with_raw_response.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_invocation_id` but received ''"):
+ client.apps.job_invocations.with_raw_response.cancel(
+ job_invocation_id="",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ )
+
+
+class TestAsyncJobInvocations:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncGradient) -> None:
+ job_invocation = await async_client.apps.job_invocations.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ )
+ assert_matches_type(JobInvocationCancelResponse, job_invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_cancel_with_all_params(self, async_client: AsyncGradient) -> None:
+ job_invocation = await async_client.apps.job_invocations.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ job_name="job_name",
+ )
+ assert_matches_type(JobInvocationCancelResponse, job_invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncGradient) -> None:
+ response = await async_client.apps.job_invocations.with_raw_response.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ job_invocation = await response.parse()
+ assert_matches_type(JobInvocationCancelResponse, job_invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncGradient) -> None:
+ async with async_client.apps.job_invocations.with_streaming_response.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ job_invocation = await response.parse()
+ assert_matches_type(JobInvocationCancelResponse, job_invocation, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncGradient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `app_id` but received ''"):
+ await async_client.apps.job_invocations.with_raw_response.cancel(
+ job_invocation_id="123e4567-e89b-12d3-a456-426",
+ app_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_invocation_id` but received ''"):
+ await async_client.apps.job_invocations.with_raw_response.cancel(
+ job_invocation_id="",
+ app_id="4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf",
+ )
diff --git a/tests/api_resources/gpu_droplets/test_images.py b/tests/api_resources/gpu_droplets/test_images.py
index 4c4146e2..480f94a5 100644
--- a/tests/api_resources/gpu_droplets/test_images.py
+++ b/tests/api_resources/gpu_droplets/test_images.py
@@ -32,9 +32,9 @@ def test_method_create(self, client: Gradient) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Gradient) -> None:
image = client.gpu_droplets.images.create(
- description=" ",
+ description="Cloud-optimized image w/ small footprint",
distribution="Ubuntu",
- name="Nifty New Snapshot",
+ name="ubuntu-18.04-minimal",
region="nyc3",
tags=["base-image", "prod"],
url="http://cloud-images.ubuntu.com/minimal/releases/bionic/release/ubuntu-18.04-minimal-cloudimg-amd64.img",
@@ -232,9 +232,9 @@ async def test_method_create(self, async_client: AsyncGradient) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncGradient) -> None:
image = await async_client.gpu_droplets.images.create(
- description=" ",
+ description="Cloud-optimized image w/ small footprint",
distribution="Ubuntu",
- name="Nifty New Snapshot",
+ name="ubuntu-18.04-minimal",
region="nyc3",
tags=["base-image", "prod"],
url="http://cloud-images.ubuntu.com/minimal/releases/bionic/release/ubuntu-18.04-minimal-cloudimg-amd64.img",
diff --git a/tests/api_resources/knowledge_bases/test_data_sources.py b/tests/api_resources/knowledge_bases/test_data_sources.py
index ca721d93..d28fd409 100644
--- a/tests/api_resources/knowledge_bases/test_data_sources.py
+++ b/tests/api_resources/knowledge_bases/test_data_sources.py
@@ -13,6 +13,7 @@
DataSourceListResponse,
DataSourceCreateResponse,
DataSourceDeleteResponse,
+ DataSourceUpdateResponse,
DataSourceCreatePresignedURLsResponse,
)
@@ -42,6 +43,13 @@ def test_method_create_with_all_params(self, client: Gradient) -> None:
"region": "example string",
"secret_key": "example string",
},
+ chunking_algorithm="CHUNKING_ALGORITHM_SECTION_BASED",
+ chunking_options={
+ "child_chunk_size": 350,
+ "max_chunk_size": 750,
+ "parent_chunk_size": 1000,
+ "semantic_threshold": 0.5,
+ },
body_knowledge_base_uuid='"12345678-1234-1234-1234-123456789012"',
spaces_data_source={
"bucket_name": "example name",
@@ -93,6 +101,78 @@ def test_path_params_create(self, client: Gradient) -> None:
path_knowledge_base_uuid="",
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Gradient) -> None:
+ data_source = client.knowledge_bases.data_sources.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ )
+ assert_matches_type(DataSourceUpdateResponse, data_source, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Gradient) -> None:
+ data_source = client.knowledge_bases.data_sources.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ chunking_algorithm="CHUNKING_ALGORITHM_SECTION_BASED",
+ chunking_options={
+ "child_chunk_size": 350,
+ "max_chunk_size": 750,
+ "parent_chunk_size": 1000,
+ "semantic_threshold": 0.5,
+ },
+ body_data_source_uuid="98765432-1234-1234-1234-123456789012",
+ body_knowledge_base_uuid="12345678-1234-1234-1234-123456789012",
+ )
+ assert_matches_type(DataSourceUpdateResponse, data_source, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Gradient) -> None:
+ response = client.knowledge_bases.data_sources.with_raw_response.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ data_source = response.parse()
+ assert_matches_type(DataSourceUpdateResponse, data_source, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Gradient) -> None:
+ with client.knowledge_bases.data_sources.with_streaming_response.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ data_source = response.parse()
+ assert_matches_type(DataSourceUpdateResponse, data_source, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: Gradient) -> None:
+ with pytest.raises(
+ ValueError, match=r"Expected a non-empty value for `path_knowledge_base_uuid` but received ''"
+ ):
+ client.knowledge_bases.data_sources.with_raw_response.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_data_source_uuid` but received ''"):
+ client.knowledge_bases.data_sources.with_raw_response.update(
+ path_data_source_uuid="",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ )
+
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list(self, client: Gradient) -> None:
@@ -264,6 +344,13 @@ async def test_method_create_with_all_params(self, async_client: AsyncGradient)
"region": "example string",
"secret_key": "example string",
},
+ chunking_algorithm="CHUNKING_ALGORITHM_SECTION_BASED",
+ chunking_options={
+ "child_chunk_size": 350,
+ "max_chunk_size": 750,
+ "parent_chunk_size": 1000,
+ "semantic_threshold": 0.5,
+ },
body_knowledge_base_uuid='"12345678-1234-1234-1234-123456789012"',
spaces_data_source={
"bucket_name": "example name",
@@ -315,6 +402,78 @@ async def test_path_params_create(self, async_client: AsyncGradient) -> None:
path_knowledge_base_uuid="",
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncGradient) -> None:
+ data_source = await async_client.knowledge_bases.data_sources.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ )
+ assert_matches_type(DataSourceUpdateResponse, data_source, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncGradient) -> None:
+ data_source = await async_client.knowledge_bases.data_sources.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ chunking_algorithm="CHUNKING_ALGORITHM_SECTION_BASED",
+ chunking_options={
+ "child_chunk_size": 350,
+ "max_chunk_size": 750,
+ "parent_chunk_size": 1000,
+ "semantic_threshold": 0.5,
+ },
+ body_data_source_uuid="98765432-1234-1234-1234-123456789012",
+ body_knowledge_base_uuid="12345678-1234-1234-1234-123456789012",
+ )
+ assert_matches_type(DataSourceUpdateResponse, data_source, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncGradient) -> None:
+ response = await async_client.knowledge_bases.data_sources.with_raw_response.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ data_source = await response.parse()
+ assert_matches_type(DataSourceUpdateResponse, data_source, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncGradient) -> None:
+ async with async_client.knowledge_bases.data_sources.with_streaming_response.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ data_source = await response.parse()
+ assert_matches_type(DataSourceUpdateResponse, data_source, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncGradient) -> None:
+ with pytest.raises(
+ ValueError, match=r"Expected a non-empty value for `path_knowledge_base_uuid` but received ''"
+ ):
+ await async_client.knowledge_bases.data_sources.with_raw_response.update(
+ path_data_source_uuid="123e4567-e89b-12d3-a456-426614174000",
+ path_knowledge_base_uuid="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_data_source_uuid` but received ''"):
+ await async_client.knowledge_bases.data_sources.with_raw_response.update(
+ path_data_source_uuid="",
+ path_knowledge_base_uuid="123e4567-e89b-12d3-a456-426614174000",
+ )
+
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list(self, async_client: AsyncGradient) -> None:
diff --git a/tests/api_resources/test_billing.py b/tests/api_resources/test_billing.py
new file mode 100644
index 00000000..59181b15
--- /dev/null
+++ b/tests/api_resources/test_billing.py
@@ -0,0 +1,177 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gradient import Gradient, AsyncGradient
+from tests.utils import assert_matches_type
+from gradient.types import BillingListInsightsResponse
+from gradient._utils import parse_date
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestBilling:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_insights(self, client: Gradient) -> None:
+ billing = client.billing.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ )
+ assert_matches_type(BillingListInsightsResponse, billing, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_insights_with_all_params(self, client: Gradient) -> None:
+ billing = client.billing.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ page=1,
+ per_page=1,
+ )
+ assert_matches_type(BillingListInsightsResponse, billing, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list_insights(self, client: Gradient) -> None:
+ response = client.billing.with_raw_response.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ billing = response.parse()
+ assert_matches_type(BillingListInsightsResponse, billing, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list_insights(self, client: Gradient) -> None:
+ with client.billing.with_streaming_response.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ billing = response.parse()
+ assert_matches_type(BillingListInsightsResponse, billing, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_list_insights(self, client: Gradient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_urn` but received ''"):
+ client.billing.with_raw_response.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="",
+ start_date=parse_date("2025-01-01"),
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `start_date` but received ''"):
+ client.billing.with_raw_response.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `end_date` but received ''"):
+ client.billing.with_raw_response.list_insights(
+ end_date="",
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ )
+
+
+class TestAsyncBilling:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_insights(self, async_client: AsyncGradient) -> None:
+ billing = await async_client.billing.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ )
+ assert_matches_type(BillingListInsightsResponse, billing, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_insights_with_all_params(self, async_client: AsyncGradient) -> None:
+ billing = await async_client.billing.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ page=1,
+ per_page=1,
+ )
+ assert_matches_type(BillingListInsightsResponse, billing, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list_insights(self, async_client: AsyncGradient) -> None:
+ response = await async_client.billing.with_raw_response.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ billing = await response.parse()
+ assert_matches_type(BillingListInsightsResponse, billing, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_insights(self, async_client: AsyncGradient) -> None:
+ async with async_client.billing.with_streaming_response.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ billing = await response.parse()
+ assert_matches_type(BillingListInsightsResponse, billing, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_list_insights(self, async_client: AsyncGradient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_urn` but received ''"):
+ await async_client.billing.with_raw_response.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="",
+ start_date=parse_date("2025-01-01"),
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `start_date` but received ''"):
+ await async_client.billing.with_raw_response.list_insights(
+ end_date=parse_date("2025-01-31"),
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `end_date` but received ''"):
+ await async_client.billing.with_raw_response.list_insights(
+ end_date="",
+ account_urn="do:team:12345678-1234-1234-1234-123456789012",
+ start_date=parse_date("2025-01-01"),
+ )
diff --git a/tests/api_resources/test_knowledge_bases.py b/tests/api_resources/test_knowledge_bases.py
index 632951b4..9ce9785d 100644
--- a/tests/api_resources/test_knowledge_bases.py
+++ b/tests/api_resources/test_knowledge_bases.py
@@ -46,6 +46,13 @@ def test_method_create_with_all_params(self, client: Gradient) -> None:
},
"bucket_name": "example name",
"bucket_region": "example string",
+ "chunking_algorithm": "CHUNKING_ALGORITHM_SECTION_BASED",
+ "chunking_options": {
+ "child_chunk_size": 350,
+ "max_chunk_size": 750,
+ "parent_chunk_size": 1000,
+ "semantic_threshold": 0.5,
+ },
"dropbox_data_source": {
"folder": "example string",
"refresh_token": "example string",
@@ -447,6 +454,13 @@ async def test_method_create_with_all_params(self, async_client: AsyncGradient)
},
"bucket_name": "example name",
"bucket_region": "example string",
+ "chunking_algorithm": "CHUNKING_ALGORITHM_SECTION_BASED",
+ "chunking_options": {
+ "child_chunk_size": 350,
+ "max_chunk_size": 750,
+ "parent_chunk_size": 1000,
+ "semantic_threshold": 0.5,
+ },
"dropbox_data_source": {
"folder": "example string",
"refresh_token": "example string",
diff --git a/tests/api_resources/test_responses.py b/tests/api_resources/test_responses.py
new file mode 100644
index 00000000..0d8d7acf
--- /dev/null
+++ b/tests/api_resources/test_responses.py
@@ -0,0 +1,296 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gradient import Gradient, AsyncGradient
+from tests.utils import assert_matches_type
+from gradient.types.shared import CreateResponseResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestResponses:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_overload_1(self, client: Gradient) -> None:
+ response = client.responses.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ )
+ assert_matches_type(CreateResponseResponse, response, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params_overload_1(self, client: Gradient) -> None:
+ response = client.responses.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ instructions="You are a helpful assistant.",
+ max_output_tokens=1024,
+ max_tokens=1024,
+ metadata={"foo": "string"},
+ modalities=["text"],
+ parallel_tool_calls=True,
+ stop="\n",
+ stream=False,
+ stream_options={"include_usage": True},
+ temperature=1,
+ tool_choice="none",
+ tools=[
+ {
+ "type": "function",
+ "description": "description",
+ "name": "name",
+ "parameters": {"foo": "bar"},
+ }
+ ],
+ top_p=1,
+ user="user-1234",
+ )
+ assert_matches_type(CreateResponseResponse, response, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_create_overload_1(self, client: Gradient) -> None:
+ http_response = client.responses.with_raw_response.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ )
+
+ assert http_response.is_closed is True
+ assert http_response.http_request.headers.get("X-Stainless-Lang") == "python"
+ response = http_response.parse()
+ assert_matches_type(CreateResponseResponse, response, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_create_overload_1(self, client: Gradient) -> None:
+ with client.responses.with_streaming_response.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ ) as http_response:
+ assert not http_response.is_closed
+ assert http_response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ response = http_response.parse()
+ assert_matches_type(CreateResponseResponse, response, path=["response"])
+
+ assert cast(Any, http_response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_overload_2(self, client: Gradient) -> None:
+ response_stream = client.responses.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ stream=True,
+ )
+ response_stream.response.close()
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params_overload_2(self, client: Gradient) -> None:
+ response_stream = client.responses.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ stream=True,
+ instructions="You are a helpful assistant.",
+ max_output_tokens=1024,
+ max_tokens=1024,
+ metadata={"foo": "string"},
+ modalities=["text"],
+ parallel_tool_calls=True,
+ stop="\n",
+ stream_options={"include_usage": True},
+ temperature=1,
+ tool_choice="none",
+ tools=[
+ {
+ "type": "function",
+ "description": "description",
+ "name": "name",
+ "parameters": {"foo": "bar"},
+ }
+ ],
+ top_p=1,
+ user="user-1234",
+ )
+ response_stream.response.close()
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_create_overload_2(self, client: Gradient) -> None:
+ response = client.responses.with_raw_response.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ stream=True,
+ )
+
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ stream = response.parse()
+ stream.close()
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_create_overload_2(self, client: Gradient) -> None:
+ with client.responses.with_streaming_response.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ stream=True,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ stream = response.parse()
+ stream.close()
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncResponses:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_overload_1(self, async_client: AsyncGradient) -> None:
+ response = await async_client.responses.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ )
+ assert_matches_type(CreateResponseResponse, response, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params_overload_1(self, async_client: AsyncGradient) -> None:
+ response = await async_client.responses.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ instructions="You are a helpful assistant.",
+ max_output_tokens=1024,
+ max_tokens=1024,
+ metadata={"foo": "string"},
+ modalities=["text"],
+ parallel_tool_calls=True,
+ stop="\n",
+ stream=False,
+ stream_options={"include_usage": True},
+ temperature=1,
+ tool_choice="none",
+ tools=[
+ {
+ "type": "function",
+ "description": "description",
+ "name": "name",
+ "parameters": {"foo": "bar"},
+ }
+ ],
+ top_p=1,
+ user="user-1234",
+ )
+ assert_matches_type(CreateResponseResponse, response, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_create_overload_1(self, async_client: AsyncGradient) -> None:
+ http_response = await async_client.responses.with_raw_response.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ )
+
+ assert http_response.is_closed is True
+ assert http_response.http_request.headers.get("X-Stainless-Lang") == "python"
+ response = await http_response.parse()
+ assert_matches_type(CreateResponseResponse, response, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_create_overload_1(self, async_client: AsyncGradient) -> None:
+ async with async_client.responses.with_streaming_response.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ ) as http_response:
+ assert not http_response.is_closed
+ assert http_response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ response = await http_response.parse()
+ assert_matches_type(CreateResponseResponse, response, path=["response"])
+
+ assert cast(Any, http_response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_overload_2(self, async_client: AsyncGradient) -> None:
+ response_stream = await async_client.responses.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ stream=True,
+ )
+ await response_stream.response.aclose()
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params_overload_2(self, async_client: AsyncGradient) -> None:
+ response_stream = await async_client.responses.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ stream=True,
+ instructions="You are a helpful assistant.",
+ max_output_tokens=1024,
+ max_tokens=1024,
+ metadata={"foo": "string"},
+ modalities=["text"],
+ parallel_tool_calls=True,
+ stop="\n",
+ stream_options={"include_usage": True},
+ temperature=1,
+ tool_choice="none",
+ tools=[
+ {
+ "type": "function",
+ "description": "description",
+ "name": "name",
+ "parameters": {"foo": "bar"},
+ }
+ ],
+ top_p=1,
+ user="user-1234",
+ )
+ await response_stream.response.aclose()
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_create_overload_2(self, async_client: AsyncGradient) -> None:
+ response = await async_client.responses.with_raw_response.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ stream=True,
+ )
+
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ stream = await response.parse()
+ await stream.close()
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_create_overload_2(self, async_client: AsyncGradient) -> None:
+ async with async_client.responses.with_streaming_response.create(
+ input="Tell me a three-sentence bedtime story about a unicorn.",
+ model="llama3-8b-instruct",
+ stream=True,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ stream = await response.parse()
+ await stream.close()
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/test_client.py b/tests/test_client.py
index c78fa9f8..d5f1bbe6 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -8,10 +8,11 @@
import json
import asyncio
import inspect
+import dataclasses
import tracemalloc
-from typing import Any, Union, cast
+from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast
from unittest import mock
-from typing_extensions import Literal
+from typing_extensions import Literal, AsyncIterator, override
import httpx
import pytest
@@ -41,6 +42,7 @@
from .utils import update_env
+T = TypeVar("T")
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
access_token = "My Access Token"
model_access_key = "My Model Access Key"
@@ -57,6 +59,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
return 0.1
+def mirror_request_content(request: httpx.Request) -> httpx.Response:
+ return httpx.Response(200, content=request.content)
+
+
+# note: we can't use the httpx.MockTransport class as it consumes the request
+# body itself, which means we can't test that the body is read lazily
+class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport):
+ def __init__(
+ self,
+ handler: Callable[[httpx.Request], httpx.Response]
+ | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]],
+ ) -> None:
+ self.handler = handler
+
+ @override
+ def handle_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function"
+ assert inspect.isfunction(self.handler), "handler must be a function"
+ return self.handler(request)
+
+ @override
+ async def handle_async_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function"
+ return await self.handler(request)
+
+
+@dataclasses.dataclass
+class Counter:
+ value: int = 0
+
+
+def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
+
+
+async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
+
+
def _get_open_connections(client: Gradient | AsyncGradient) -> int:
transport = client._client._transport
assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
@@ -607,6 +660,70 @@ def test_multipart_repeating_array(self, client: Gradient) -> None:
b"",
]
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload(self, respx_mock: MockRouter, client: Gradient) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ def test_binary_content_upload_with_iterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_sync_iterator([file_content], counter=counter)
+
+ def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=request.read())
+
+ with Gradient(
+ base_url=base_url,
+ access_token=access_token,
+ _strict_response_validation=True,
+ http_client=httpx.Client(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Gradient) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
@pytest.mark.respx(base_url=base_url)
def test_basic_union_response(self, respx_mock: MockRouter, client: Gradient) -> None:
class Model1(BaseModel):
@@ -1648,6 +1765,72 @@ def test_multipart_repeating_array(self, async_client: AsyncGradient) -> None:
b"",
]
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncGradient) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = await async_client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ async def test_binary_content_upload_with_asynciterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_async_iterator([file_content], counter=counter)
+
+ async def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=await request.aread())
+
+ async with AsyncGradient(
+ base_url=base_url,
+ access_token=access_token,
+ _strict_response_validation=True,
+ http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = await client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload_with_body_is_deprecated(
+ self, respx_mock: MockRouter, async_client: AsyncGradient
+ ) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = await async_client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
@pytest.mark.respx(base_url=base_url)
async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncGradient) -> None:
class Model1(BaseModel):
diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py
new file mode 100644
index 00000000..4ba6d83c
--- /dev/null
+++ b/tests/test_utils/test_json.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+import datetime
+from typing import Union
+
+import pydantic
+
+from gradient import _compat
+from gradient._utils._json import openapi_dumps
+
+
+class TestOpenapiDumps:
+ def test_basic(self) -> None:
+ data = {"key": "value", "number": 42}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"key":"value","number":42}'
+
+ def test_datetime_serialization(self) -> None:
+ dt = datetime.datetime(2023, 1, 1, 12, 0, 0)
+ data = {"datetime": dt}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}'
+
+ def test_pydantic_model_serialization(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str
+ last_name: str
+ age: int
+
+ model_instance = User(first_name="John", last_name="Kramer", age=83)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}'
+
+ def test_pydantic_model_with_default_values(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+ score: int = 0
+
+ model_instance = User(name="Alice")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Alice"}}'
+
+ def test_pydantic_model_with_default_values_overridden(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+
+ model_instance = User(name="Bob", role="admin", active=False)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}'
+
+ def test_pydantic_model_with_alias(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str = pydantic.Field(alias="firstName")
+ last_name: str = pydantic.Field(alias="lastName")
+
+ model_instance = User(firstName="John", lastName="Doe")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}'
+
+ def test_pydantic_model_with_alias_and_default(self) -> None:
+ class User(pydantic.BaseModel):
+ user_name: str = pydantic.Field(alias="userName")
+ user_role: str = pydantic.Field(default="member", alias="userRole")
+ is_active: bool = pydantic.Field(default=True, alias="isActive")
+
+ model_instance = User(userName="charlie")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"charlie"}}'
+
+ model_with_overrides = User(userName="diana", userRole="admin", isActive=False)
+ data = {"model": model_with_overrides}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}'
+
+ def test_pydantic_model_with_nested_models_and_defaults(self) -> None:
+ class Address(pydantic.BaseModel):
+ street: str
+ city: str = "Unknown"
+
+ class User(pydantic.BaseModel):
+ name: str
+ address: Address
+ verified: bool = False
+
+ if _compat.PYDANTIC_V1:
+ # to handle forward references in Pydantic v1
+ User.update_forward_refs(**locals()) # type: ignore[reportDeprecated]
+
+ address = Address(street="123 Main St")
+ user = User(name="Diana", address=address)
+ data = {"user": user}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}'
+
+ address_with_city = Address(street="456 Oak Ave", city="Boston")
+ user_verified = User(name="Eve", address=address_with_city, verified=True)
+ data = {"user": user_verified}
+ json_bytes = openapi_dumps(data)
+ assert (
+ json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}'
+ )
+
+ def test_pydantic_model_with_optional_fields(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ email: Union[str, None]
+ phone: Union[str, None]
+
+ model_with_none = User(name="Eve", email=None, phone=None)
+ data = {"model": model_with_none}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}'
+
+ model_with_values = User(name="Frank", email="frank@example.com", phone=None)
+ data = {"model": model_with_values}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}'
From 26ac7960527278e89cce6694f2d880f7e26e9c94 Mon Sep 17 00:00:00 2001
From: rushitat
Date: Wed, 11 Feb 2026 15:46:31 -0800
Subject: [PATCH 4/8] Fix lint
---
src/gradient/_base_client.py | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/gradient/_base_client.py b/src/gradient/_base_client.py
index e2135952..ca3db359 100644
--- a/src/gradient/_base_client.py
+++ b/src/gradient/_base_client.py
@@ -62,7 +62,7 @@
not_given,
)
from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
-from ._compat import PYDANTIC_V1, model_copy, model_dump
+from ._compat import PYDANTIC_V1, model_copy
from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
from ._response import (
APIResponse,
@@ -486,17 +486,17 @@ def _build_request(
) -> httpx.Request:
if log.isEnabledFor(logging.DEBUG):
log.debug(
- "Request options: %s",
- model_dump(
- options,
- exclude_unset=True,
- # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
- exclude={
- "content",
- }
- if PYDANTIC_V1
- else {},
- ),
+ "Request options",
+ # model_dump(
+ # options,
+ # exclude_unset=True,
+ # # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
+ # exclude={
+ # "content",
+ # }
+ # if PYDANTIC_V1
+ # else {},
+ # ),
)
kwargs: dict[str, Any] = {}
From 21520fdac633e7bcc1be4ddbbd6f214e5a624484 Mon Sep 17 00:00:00 2001
From: rushitat
Date: Wed, 11 Feb 2026 22:09:46 -0800
Subject: [PATCH 5/8] responses
---
src/gradient/resources/responses.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/gradient/resources/responses.py b/src/gradient/resources/responses.py
index a342a78d..d0892fa9 100644
--- a/src/gradient/resources/responses.py
+++ b/src/gradient/resources/responses.py
@@ -392,7 +392,7 @@ def create(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CreateResponseResponse | Stream[CreateResponseStreamResponse]:
return self._post(
- "/responses",
+ "/responses" if self._client._base_url_overridden else f"{self._client.inference_endpoint}/v1/responses",
body=maybe_transform(
{
"input": input,
@@ -791,7 +791,7 @@ async def create(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CreateResponseResponse | AsyncStream[CreateResponseStreamResponse]:
return await self._post(
- "/responses",
+ "/responses" if self._client._base_url_overridden else f"{self._client.inference_endpoint}/v1/responses",
body=await async_maybe_transform(
{
"input": input,
From 9c18bdbbec7e7f8222099e9ada5780b61cde22fa Mon Sep 17 00:00:00 2001
From: rushitat
Date: Thu, 12 Feb 2026 08:47:37 -0800
Subject: [PATCH 6/8] Add TYPE_CHECKING imports for GPUDropletsResource in
_client
---
src/gradient/_client.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/gradient/_client.py b/src/gradient/_client.py
index 18ef0c7a..a0fc87b2 100644
--- a/src/gradient/_client.py
+++ b/src/gradient/_client.py
@@ -64,6 +64,7 @@
KnowledgeBasesResource,
AsyncKnowledgeBasesResource,
)
+ from .resources.gpu_droplets import GPUDropletsResource, AsyncGPUDropletsResource
__all__ = [
"Timeout",
From 7160d350f24837b987d807ea58dad7c0b63e2ec1 Mon Sep 17 00:00:00 2001
From: rushitat
Date: Thu, 12 Feb 2026 08:50:35 -0800
Subject: [PATCH 7/8] Fix Lint
---
src/gradient/_client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/gradient/_client.py b/src/gradient/_client.py
index a0fc87b2..42867c7d 100644
--- a/src/gradient/_client.py
+++ b/src/gradient/_client.py
@@ -56,6 +56,7 @@
from .resources.apps.apps import AppsResource, AsyncAppsResource
from .resources.chat.chat import ChatResource, AsyncChatResource
from .resources.responses import ResponsesResource, AsyncResponsesResource
+ from .resources.gpu_droplets import GPUDropletsResource, AsyncGPUDropletsResource
from .resources.agents.agents import AgentsResource, AsyncAgentsResource
from .resources.models.models import ModelsResource, AsyncModelsResource
from .resources.databases.databases import DatabasesResource, AsyncDatabasesResource
@@ -64,7 +65,6 @@
KnowledgeBasesResource,
AsyncKnowledgeBasesResource,
)
- from .resources.gpu_droplets import GPUDropletsResource, AsyncGPUDropletsResource
__all__ = [
"Timeout",
From 2674257bfacabef90178f53726c74185bba0acbe Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 17 Feb 2026 18:29:26 +0000
Subject: [PATCH 8/8] release: 3.11.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 18 ++++++++++++++++++
pyproject.toml | 2 +-
src/gradient/_version.py | 2 +-
4 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index cc4da81f..940f2ca3 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "3.10.1"
+ ".": "3.11.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a82ede75..e9caf7e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
+## 3.11.0 (2026-02-17)
+
+Full Changelog: [v3.10.1...v3.11.0](https://github.com/digitalocean/gradient-python/compare/v3.10.1...v3.11.0)
+
+### Features
+
+* **api:** api update ([f67228f](https://github.com/digitalocean/gradient-python/commit/f67228f0bd8de96342d15b1b7c3096e4b03c7aaa))
+
+
+### Bug Fixes
+
+* **client:** loosen auth header validation ([bf5fa9f](https://github.com/digitalocean/gradient-python/commit/bf5fa9f93b6443ac0b41d5fa134aab8ee12cfbc3))
+
+
+### Chores
+
+* **internal:** codegen related update ([07eeda8](https://github.com/digitalocean/gradient-python/commit/07eeda8ae3e1c7bdfe3fcc51a8786f35682f4c3f))
+
## 3.10.1 (2025-12-19)
Full Changelog: [v3.10.0...v3.10.1](https://github.com/digitalocean/gradient-python/compare/v3.10.0...v3.10.1)
diff --git a/pyproject.toml b/pyproject.toml
index 5d4e9ebe..3295b4c4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "gradient"
-version = "3.10.1"
+version = "3.11.0"
description = "The official Python library for the Gradient API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/gradient/_version.py b/src/gradient/_version.py
index f1eb0021..07c427f5 100644
--- a/src/gradient/_version.py
+++ b/src/gradient/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "gradient"
-__version__ = "3.10.1" # x-release-please-version
+__version__ = "3.11.0" # x-release-please-version