From 4bc12e4d6bd408e6256f2e6e5539fd8feb29fab2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 3 Apr 2025 18:40:58 +0000
Subject: [PATCH 01/15] chore(internal): version bump (#99)
From 5675c9efbda688fa1799f1e48f64c20143fcf410 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 17:29:48 +0000
Subject: [PATCH 02/15] feat(api): api update (#102)
---
.stats.yml | 2 +-
src/codex/resources/projects/clusters.py | 6 +++---
src/codex/types/projects/cluster_list_params.py | 4 ++--
src/codex/types/projects/entry.py | 3 +++
4 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index c22765c..be1ee82 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
configured_endpoints: 36
-openapi_spec_hash: 6a5cfa54c19b354978b1654c152b0431
+openapi_spec_hash: 1850a850b7e27992c6e47190db9add88
config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index 54b6c4c..2faed31 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import List
+from typing import List, Optional
from typing_extensions import Literal
import httpx
@@ -53,7 +53,7 @@ def list(
limit: int | NotGiven = NOT_GIVEN,
offset: int | NotGiven = NOT_GIVEN,
order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
- sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN,
+ sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN,
states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -164,7 +164,7 @@ def list(
limit: int | NotGiven = NOT_GIVEN,
offset: int | NotGiven = NOT_GIVEN,
order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
- sort: Literal["created_at", "answered_at", "cluster_frequency_count"] | NotGiven = NOT_GIVEN,
+ sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN,
states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
index 438b481..4889324 100644
--- a/src/codex/types/projects/cluster_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import List
+from typing import List, Optional
from typing_extensions import Literal, TypedDict
__all__ = ["ClusterListParams"]
@@ -15,6 +15,6 @@ class ClusterListParams(TypedDict, total=False):
order: Literal["asc", "desc"]
- sort: Literal["created_at", "answered_at", "cluster_frequency_count"]
+ sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]]
states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index 77e2ca3..b8eeb0e 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -29,3 +29,6 @@ class Entry(BaseModel):
draft_answer: Optional[str] = None
draft_answer_last_edited: Optional[datetime] = None
+
+ frequency_count: Optional[int] = None
+ """number of times the entry matched for a /query request"""
From 45f7fde22223d99bec5ab8142b59f5124b275c28 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 19:58:07 +0000
Subject: [PATCH 03/15] chore(internal): remove trailing character (#103)
---
tests/test_client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_client.py b/tests/test_client.py
index 0b0b783..2d356fa 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1577,7 +1577,7 @@ def test_get_platform(self) -> None:
import threading
from codex._utils import asyncify
- from codex._base_client import get_platform
+ from codex._base_client import get_platform
async def test_main() -> None:
result = await asyncify(get_platform)()
From 404e013054df741a1d3c77597f35030cba080b82 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 22:56:16 +0000
Subject: [PATCH 04/15] feat(api): api update (#104)
---
.stats.yml | 2 +-
README.md | 445 +++++++++++++++++-
src/codex/resources/projects/entries.py | 16 +-
src/codex/types/project_create_params.py | 12 +
src/codex/types/project_list_response.py | 12 +
src/codex/types/project_return_schema.py | 12 +
src/codex/types/project_update_params.py | 12 +
.../types/projects/entry_query_params.py | 2 +
tests/api_resources/projects/test_entries.py | 2 +
tests/api_resources/test_projects.py | 40 +-
10 files changed, 547 insertions(+), 8 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index be1ee82..69bb6fc 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
configured_endpoints: 36
-openapi_spec_hash: 1850a850b7e27992c6e47190db9add88
+openapi_spec_hash: 8ef89533cd58e3b2ceb53a877832f48b
config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/README.md b/README.md
index 434ebb1..1042b46 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,447 @@
[](https://pypi.org/project/codex-sdk/)
-This library is not meant to be used directly. Refer to https://pypi.org/project/cleanlab-codex/ instead.
+The Codex SDK library provides convenient access to the Codex REST API from any Python 3.8+
+application. The library includes type definitions for all request params and response fields,
+and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
+
+It is generated with [Stainless](https://www.stainless.com/).
+
+## Documentation
+
+The REST API documentation can be found on [help.cleanlab.ai](https://help.cleanlab.ai). The full API of this library can be found in [api.md](api.md).
+
+## Installation
+
+```sh
+# install from PyPI
+pip install --pre codex-sdk
+```
+
+## Usage
+
+The full API of this library can be found in [api.md](api.md).
+
+```python
+from codex import Codex
+
+client = Codex(
+ # or 'production' | 'local'; defaults to "production".
+ environment="staging",
+)
+
+project_return_schema = client.projects.create(
+ config={},
+ name="name",
+ organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+print(project_return_schema.id)
+```
+
+## Async usage
+
+Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call:
+
+```python
+import asyncio
+from codex import AsyncCodex
+
+client = AsyncCodex(
+ # or 'production' | 'local'; defaults to "production".
+ environment="staging",
+)
+
+
+async def main() -> None:
+ project_return_schema = await client.projects.create(
+ config={},
+ name="name",
+ organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ print(project_return_schema.id)
+
+
+asyncio.run(main())
+```
+
+Functionality between the synchronous and asynchronous clients is otherwise identical.
+
+## Using types
+
+Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
+
+- Serializing back into JSON, `model.to_json()`
+- Converting to a dictionary, `model.to_dict()`
+
+Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
+
+## Pagination
+
+List methods in the Codex API are paginated.
+
+This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
+
+```python
+from codex import Codex
+
+client = Codex()
+
+all_clusters = []
+# Automatically fetches more pages as needed.
+for cluster in client.projects.clusters.list(
+ project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+):
+ # Do something with cluster here
+ all_clusters.append(cluster)
+print(all_clusters)
+```
+
+Or, asynchronously:
+
+```python
+import asyncio
+from codex import AsyncCodex
+
+client = AsyncCodex()
+
+
+async def main() -> None:
+ all_clusters = []
+ # Iterate through items across all pages, issuing requests as needed.
+ async for cluster in client.projects.clusters.list(
+ project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ):
+ all_clusters.append(cluster)
+ print(all_clusters)
+
+
+asyncio.run(main())
+```
+
+Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
+
+```python
+first_page = await client.projects.clusters.list(
+ project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+if first_page.has_next_page():
+ print(f"will fetch next page using these details: {first_page.next_page_info()}")
+ next_page = await first_page.get_next_page()
+ print(f"number of items we just fetched: {len(next_page.clusters)}")
+
+# Remove `await` for non-async usage.
+```
+
+Or just work directly with the returned data:
+
+```python
+first_page = await client.projects.clusters.list(
+ project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+for cluster in first_page.clusters:
+ print(cluster.id)
+
+# Remove `await` for non-async usage.
+```
+
+## Nested params
+
+Nested parameters are dictionaries, typed using `TypedDict`, for example:
+
+```python
+from codex import Codex
+
+client = Codex()
+
+project_return_schema = client.projects.create(
+ config={
+ "clustering_use_llm_matching": True,
+ "llm_matching_model": "llm_matching_model",
+ "llm_matching_quality_preset": "llm_matching_quality_preset",
+ "lower_llm_match_distance_threshold": 0,
+ "max_distance": 0,
+ "query_use_llm_matching": True,
+ "upper_llm_match_distance_threshold": 0,
+ },
+ name="name",
+ organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+print(project_return_schema.config)
+```
+
+## Handling errors
+
+When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `codex.APIConnectionError` is raised.
+
+When the API returns a non-success status code (that is, 4xx or 5xx
+response), a subclass of `codex.APIStatusError` is raised, containing `status_code` and `response` properties.
+
+All errors inherit from `codex.APIError`.
+
+```python
+import codex
+from codex import Codex
+
+client = Codex()
+
+try:
+ client.projects.create(
+ config={},
+ name="name",
+ organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+except codex.APIConnectionError as e:
+ print("The server could not be reached")
+ print(e.__cause__) # an underlying Exception, likely raised within httpx.
+except codex.RateLimitError as e:
+ print("A 429 status code was received; we should back off a bit.")
+except codex.APIStatusError as e:
+ print("Another non-200-range status code was received")
+ print(e.status_code)
+ print(e.response)
+```
+
+Error codes are as follows:
+
+| Status Code | Error Type |
+| ----------- | -------------------------- |
+| 400 | `BadRequestError` |
+| 401 | `AuthenticationError` |
+| 403 | `PermissionDeniedError` |
+| 404 | `NotFoundError` |
+| 422 | `UnprocessableEntityError` |
+| 429 | `RateLimitError` |
+| >=500 | `InternalServerError` |
+| N/A | `APIConnectionError` |
+
+### Retries
+
+Certain errors are automatically retried 2 times by default, with a short exponential backoff.
+Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
+429 Rate Limit, and >=500 Internal errors are all retried by default.
+
+You can use the `max_retries` option to configure or disable retry settings:
+
+```python
+from codex import Codex
+
+# Configure the default for all requests:
+client = Codex(
+ # default is 2
+ max_retries=0,
+)
+
+# Or, configure per-request:
+client.with_options(max_retries=5).projects.create(
+ config={},
+ name="name",
+ organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+```
+
+### Timeouts
+
+By default requests time out after 1 minute. You can configure this with a `timeout` option,
+which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
+
+```python
+from codex import Codex
+
+# Configure the default for all requests:
+client = Codex(
+ # 20 seconds (default is 1 minute)
+ timeout=20.0,
+)
+
+# More granular control:
+client = Codex(
+ timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
+)
+
+# Override per-request:
+client.with_options(timeout=5.0).projects.create(
+ config={},
+ name="name",
+ organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+```
+
+On timeout, an `APITimeoutError` is thrown.
+
+Note that requests that time out are [retried twice by default](#retries).
+
+## Advanced
+
+### Logging
+
+We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.
+
+You can enable logging by setting the environment variable `CODEX_LOG` to `info`.
+
+```shell
+$ export CODEX_LOG=info
+```
+
+Or to `debug` for more verbose logging.
+
+### How to tell whether `None` means `null` or missing
+
+In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`:
+
+```py
+if response.my_field is None:
+ if 'my_field' not in response.model_fields_set:
+ print('Got json like {}, without a "my_field" key present at all.')
+ else:
+ print('Got json like {"my_field": null}.')
+```
+
+### Accessing raw response data (e.g. headers)
+
+The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,
+
+```py
+from codex import Codex
+
+client = Codex()
+response = client.projects.with_raw_response.create(
+ config={},
+ name="name",
+ organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+)
+print(response.headers.get('X-My-Header'))
+
+project = response.parse() # get the object that `projects.create()` would have returned
+print(project.id)
+```
+
+These methods return an [`APIResponse`](https://github.com/cleanlab/codex-python/tree/main/src/codex/_response.py) object.
+
+The async client returns an [`AsyncAPIResponse`](https://github.com/cleanlab/codex-python/tree/main/src/codex/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
+
+#### `.with_streaming_response`
+
+The above interface eagerly reads the full response body when you make the request, which may not always be what you want.
+
+To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods.
+
+```python
+with client.projects.with_streaming_response.create(
+ config={},
+ name="name",
+ organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+) as response:
+ print(response.headers.get("X-My-Header"))
+
+ for line in response.iter_lines():
+ print(line)
+```
+
+The context manager is required so that the response will reliably be closed.
+
+### Making custom/undocumented requests
+
+This library is typed for convenient access to the documented API.
+
+If you need to access undocumented endpoints, params, or response properties, the library can still be used.
+
+#### Undocumented endpoints
+
+To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other
+http verbs. Options on the client will be respected (such as retries) when making this request.
+
+```py
+import httpx
+
+response = client.post(
+ "/foo",
+ cast_to=httpx.Response,
+ body={"my_param": True},
+)
+
+print(response.headers.get("x-foo"))
+```
+
+#### Undocumented request params
+
+If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request
+options.
+
+#### Undocumented response properties
+
+To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You
+can also get all the extra fields on the Pydantic model as a dict with
+[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra).
+
+### Configuring the HTTP client
+
+You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including:
+
+- Support for [proxies](https://www.python-httpx.org/advanced/proxies/)
+- Custom [transports](https://www.python-httpx.org/advanced/transports/)
+- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality
+
+```python
+import httpx
+from codex import Codex, DefaultHttpxClient
+
+client = Codex(
+ # Or use the `CODEX_BASE_URL` env var
+ base_url="http://my.test.server.example.com:8083",
+ http_client=DefaultHttpxClient(
+ proxy="http://my.test.proxy.example.com",
+ transport=httpx.HTTPTransport(local_address="0.0.0.0"),
+ ),
+)
+```
+
+You can also customize the client on a per-request basis by using `with_options()`:
+
+```python
+client.with_options(http_client=DefaultHttpxClient(...))
+```
+
+### Managing HTTP resources
+
+By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting.
+
+```py
+from codex import Codex
+
+with Codex() as client:
+ # make requests here
+ ...
+
+# HTTP client is now closed
+```
+
+## Versioning
+
+This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
+
+1. Changes that only affect static types, without breaking runtime behavior.
+2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
+3. Changes that we do not expect to impact the vast majority of users in practice.
+
+We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
+
+We are keen for your feedback; please open an [issue](https://www.github.com/cleanlab/codex-python/issues) with questions, bugs, or suggestions.
+
+### Determining the installed version
+
+If you've upgraded to the latest version but aren't seeing any new features you were expecting then your python environment is likely still using an older version.
+
+You can determine the version that is being used at runtime with:
+
+```py
+import codex
+print(codex.__version__)
+```
+
+## Requirements
+
+Python 3.8 or higher.
+
+## Contributing
+
+See [the contributing documentation](./CONTRIBUTING.md).
diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py
index 7df9f1f..f0fa39c 100644
--- a/src/codex/resources/projects/entries.py
+++ b/src/codex/resources/projects/entries.py
@@ -234,6 +234,7 @@ def query(
project_id: str,
*,
question: str,
+ use_llm_matching: bool | NotGiven = NOT_GIVEN,
client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
x_client_library_version: str | NotGiven = NOT_GIVEN,
x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -281,7 +282,11 @@ def query(
entry_query_params.EntryQueryParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams),
),
cast_to=EntryQueryResponse,
)
@@ -493,6 +498,7 @@ async def query(
project_id: str,
*,
question: str,
+ use_llm_matching: bool | NotGiven = NOT_GIVEN,
client_metadata: Optional[object] | NotGiven = NOT_GIVEN,
x_client_library_version: str | NotGiven = NOT_GIVEN,
x_integration_type: str | NotGiven = NOT_GIVEN,
@@ -540,7 +546,13 @@ async def query(
entry_query_params.EntryQueryParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"use_llm_matching": use_llm_matching}, entry_query_params.EntryQueryParams
+ ),
),
cast_to=EntryQueryResponse,
)
diff --git a/src/codex/types/project_create_params.py b/src/codex/types/project_create_params.py
index 80882b2..ecdd194 100644
--- a/src/codex/types/project_create_params.py
+++ b/src/codex/types/project_create_params.py
@@ -19,4 +19,16 @@ class ProjectCreateParams(TypedDict, total=False):
class Config(TypedDict, total=False):
+ clustering_use_llm_matching: bool
+
+ llm_matching_model: str
+
+ llm_matching_quality_preset: str
+
+ lower_llm_match_distance_threshold: float
+
max_distance: float
+
+ query_use_llm_matching: bool
+
+ upper_llm_match_distance_threshold: float
diff --git a/src/codex/types/project_list_response.py b/src/codex/types/project_list_response.py
index 84aafa9..2b4fec4 100644
--- a/src/codex/types/project_list_response.py
+++ b/src/codex/types/project_list_response.py
@@ -9,8 +9,20 @@
class ProjectConfig(BaseModel):
+ clustering_use_llm_matching: Optional[bool] = None
+
+ llm_matching_model: Optional[str] = None
+
+ llm_matching_quality_preset: Optional[str] = None
+
+ lower_llm_match_distance_threshold: Optional[float] = None
+
max_distance: Optional[float] = None
+ query_use_llm_matching: Optional[bool] = None
+
+ upper_llm_match_distance_threshold: Optional[float] = None
+
class Project(BaseModel):
id: str
diff --git a/src/codex/types/project_return_schema.py b/src/codex/types/project_return_schema.py
index 8531272..51a6c1a 100644
--- a/src/codex/types/project_return_schema.py
+++ b/src/codex/types/project_return_schema.py
@@ -9,8 +9,20 @@
class Config(BaseModel):
+ clustering_use_llm_matching: Optional[bool] = None
+
+ llm_matching_model: Optional[str] = None
+
+ llm_matching_quality_preset: Optional[str] = None
+
+ lower_llm_match_distance_threshold: Optional[float] = None
+
max_distance: Optional[float] = None
+ query_use_llm_matching: Optional[bool] = None
+
+ upper_llm_match_distance_threshold: Optional[float] = None
+
class ProjectReturnSchema(BaseModel):
id: str
diff --git a/src/codex/types/project_update_params.py b/src/codex/types/project_update_params.py
index 46d747e..0a5aa54 100644
--- a/src/codex/types/project_update_params.py
+++ b/src/codex/types/project_update_params.py
@@ -17,4 +17,16 @@ class ProjectUpdateParams(TypedDict, total=False):
class Config(TypedDict, total=False):
+ clustering_use_llm_matching: bool
+
+ llm_matching_model: str
+
+ llm_matching_quality_preset: str
+
+ lower_llm_match_distance_threshold: float
+
max_distance: float
+
+ query_use_llm_matching: bool
+
+ upper_llm_match_distance_threshold: float
diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py
index 50b5f26..d58b7bf 100644
--- a/src/codex/types/projects/entry_query_params.py
+++ b/src/codex/types/projects/entry_query_params.py
@@ -13,6 +13,8 @@
class EntryQueryParams(TypedDict, total=False):
question: Required[str]
+ use_llm_matching: bool
+
client_metadata: Optional[object]
x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")]
diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py
index 5fa5ed9..ca7eecb 100644
--- a/tests/api_resources/projects/test_entries.py
+++ b/tests/api_resources/projects/test_entries.py
@@ -262,6 +262,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None:
entry = client.projects.entries.query(
project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
question="question",
+ use_llm_matching=True,
client_metadata={},
x_client_library_version="x-client-library-version",
x_integration_type="x-integration-type",
@@ -556,6 +557,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N
entry = await async_client.projects.entries.query(
project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
question="question",
+ use_llm_matching=True,
client_metadata={},
x_client_library_version="x-client-library-version",
x_integration_type="x-integration-type",
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 10d2346..210f4e1 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -34,7 +34,15 @@ def test_method_create(self, client: Codex) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Codex) -> None:
project = client.projects.create(
- config={"max_distance": 0},
+ config={
+ "clustering_use_llm_matching": True,
+ "llm_matching_model": "llm_matching_model",
+ "llm_matching_quality_preset": "llm_matching_quality_preset",
+ "lower_llm_match_distance_threshold": 0,
+ "max_distance": 0,
+ "query_use_llm_matching": True,
+ "upper_llm_match_distance_threshold": 0,
+ },
name="name",
organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
description="description",
@@ -128,7 +136,15 @@ def test_method_update(self, client: Codex) -> None:
def test_method_update_with_all_params(self, client: Codex) -> None:
project = client.projects.update(
project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- config={"max_distance": 0},
+ config={
+ "clustering_use_llm_matching": True,
+ "llm_matching_model": "llm_matching_model",
+ "llm_matching_quality_preset": "llm_matching_quality_preset",
+ "lower_llm_match_distance_threshold": 0,
+ "max_distance": 0,
+ "query_use_llm_matching": True,
+ "upper_llm_match_distance_threshold": 0,
+ },
name="name",
description="description",
)
@@ -324,7 +340,15 @@ async def test_method_create(self, async_client: AsyncCodex) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> None:
project = await async_client.projects.create(
- config={"max_distance": 0},
+ config={
+ "clustering_use_llm_matching": True,
+ "llm_matching_model": "llm_matching_model",
+ "llm_matching_quality_preset": "llm_matching_quality_preset",
+ "lower_llm_match_distance_threshold": 0,
+ "max_distance": 0,
+ "query_use_llm_matching": True,
+ "upper_llm_match_distance_threshold": 0,
+ },
name="name",
organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
description="description",
@@ -418,7 +442,15 @@ async def test_method_update(self, async_client: AsyncCodex) -> None:
async def test_method_update_with_all_params(self, async_client: AsyncCodex) -> None:
project = await async_client.projects.update(
project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
- config={"max_distance": 0},
+ config={
+ "clustering_use_llm_matching": True,
+ "llm_matching_model": "llm_matching_model",
+ "llm_matching_quality_preset": "llm_matching_quality_preset",
+ "lower_llm_match_distance_threshold": 0,
+ "max_distance": 0,
+ "query_use_llm_matching": True,
+ "upper_llm_match_distance_threshold": 0,
+ },
name="name",
description="description",
)
From 772695b60b05439999d9f8bd81ec772908c664d3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 8 Apr 2025 04:40:49 +0000
Subject: [PATCH 05/15] fix(client): send all configured auth headers (#106)
---
src/codex/_client.py | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/src/codex/_client.py b/src/codex/_client.py
index 9bdd5f0..2641513 100644
--- a/src/codex/_client.py
+++ b/src/codex/_client.py
@@ -154,11 +154,7 @@ def qs(self) -> Querystring:
@property
@override
def auth_headers(self) -> dict[str, str]:
- if self._authenticated_api_key:
- return self._authenticated_api_key
- if self._public_access_key:
- return self._public_access_key
- return {}
+ return {**self._authenticated_api_key, **self._public_access_key}
@property
def _authenticated_api_key(self) -> dict[str, str]:
@@ -386,11 +382,7 @@ def qs(self) -> Querystring:
@property
@override
def auth_headers(self) -> dict[str, str]:
- if self._authenticated_api_key:
- return self._authenticated_api_key
- if self._public_access_key:
- return self._public_access_key
- return {}
+ return {**self._authenticated_api_key, **self._public_access_key}
@property
def _authenticated_api_key(self) -> dict[str, str]:
From 9a3564e4e18decc1a7238a986f6a5b23db845f35 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 8 Apr 2025 19:28:30 +0000
Subject: [PATCH 06/15] feat(api): api update (#107)
---
.stats.yml | 2 +-
api.md | 9 +++-
src/codex/resources/projects/clusters.py | 6 ++-
src/codex/resources/projects/projects.py | 9 ++--
src/codex/types/__init__.py | 1 +
src/codex/types/project_retrieve_response.py | 44 +++++++++++++++++++
.../types/projects/cluster_list_params.py | 2 +-
tests/api_resources/test_projects.py | 13 +++---
8 files changed, 70 insertions(+), 16 deletions(-)
create mode 100644 src/codex/types/project_retrieve_response.py
diff --git a/.stats.yml b/.stats.yml
index 69bb6fc..4500dc4 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
configured_endpoints: 36
-openapi_spec_hash: 8ef89533cd58e3b2ceb53a877832f48b
+openapi_spec_hash: ee7ad81c8308305b6a609a18615ae394
config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/api.md b/api.md
index b241c15..e9dd565 100644
--- a/api.md
+++ b/api.md
@@ -135,13 +135,18 @@ Methods:
Types:
```python
-from codex.types import ProjectReturnSchema, ProjectListResponse, ProjectExportResponse
+from codex.types import (
+ ProjectReturnSchema,
+ ProjectRetrieveResponse,
+ ProjectListResponse,
+ ProjectExportResponse,
+)
```
Methods:
- client.projects.create(\*\*params) -> ProjectReturnSchema
-- client.projects.retrieve(project_id) -> ProjectReturnSchema
+- client.projects.retrieve(project_id) -> ProjectRetrieveResponse
- client.projects.update(project_id, \*\*params) -> ProjectReturnSchema
- client.projects.list(\*\*params) -> ProjectListResponse
- client.projects.delete(project_id) -> None
diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py
index 2faed31..f376c0b 100644
--- a/src/codex/resources/projects/clusters.py
+++ b/src/codex/resources/projects/clusters.py
@@ -53,7 +53,8 @@ def list(
limit: int | NotGiven = NOT_GIVEN,
offset: int | NotGiven = NOT_GIVEN,
order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
- sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN,
+ sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
+ | NotGiven = NOT_GIVEN,
states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -164,7 +165,8 @@ def list(
limit: int | NotGiven = NOT_GIVEN,
offset: int | NotGiven = NOT_GIVEN,
order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
- sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]] | NotGiven = NOT_GIVEN,
+ sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
+ | NotGiven = NOT_GIVEN,
states: List[Literal["unanswered", "draft", "published", "published_with_draft"]] | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py
index 8a7ff1b..ccc7726 100644
--- a/src/codex/resources/projects/projects.py
+++ b/src/codex/resources/projects/projects.py
@@ -48,6 +48,7 @@
from ..._base_client import make_request_options
from ...types.project_list_response import ProjectListResponse
from ...types.project_return_schema import ProjectReturnSchema
+from ...types.project_retrieve_response import ProjectRetrieveResponse
__all__ = ["ProjectsResource", "AsyncProjectsResource"]
@@ -137,7 +138,7 @@ def retrieve(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> ProjectReturnSchema:
+ ) -> ProjectRetrieveResponse:
"""
Get a single project.
@@ -157,7 +158,7 @@ def retrieve(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ProjectReturnSchema,
+ cast_to=ProjectRetrieveResponse,
)
def update(
@@ -409,7 +410,7 @@ async def retrieve(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
- ) -> ProjectReturnSchema:
+ ) -> ProjectRetrieveResponse:
"""
Get a single project.
@@ -429,7 +430,7 @@ async def retrieve(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ProjectReturnSchema,
+ cast_to=ProjectRetrieveResponse,
)
async def update(
diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py
index 8f241bc..6c18437 100644
--- a/src/codex/types/__init__.py
+++ b/src/codex/types/__init__.py
@@ -13,5 +13,6 @@
from .project_list_response import ProjectListResponse as ProjectListResponse
from .project_return_schema import ProjectReturnSchema as ProjectReturnSchema
from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
+from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic
from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams
diff --git a/src/codex/types/project_retrieve_response.py b/src/codex/types/project_retrieve_response.py
new file mode 100644
index 0000000..62209d3
--- /dev/null
+++ b/src/codex/types/project_retrieve_response.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["ProjectRetrieveResponse", "Config"]
+
+
+class Config(BaseModel):
+ clustering_use_llm_matching: Optional[bool] = None
+
+ llm_matching_model: Optional[str] = None
+
+ llm_matching_quality_preset: Optional[str] = None
+
+ lower_llm_match_distance_threshold: Optional[float] = None
+
+ max_distance: Optional[float] = None
+
+ query_use_llm_matching: Optional[bool] = None
+
+ upper_llm_match_distance_threshold: Optional[float] = None
+
+
+class ProjectRetrieveResponse(BaseModel):
+ id: str
+
+ config: Config
+
+ created_at: datetime
+
+ created_by_user_id: str
+
+ name: str
+
+ organization_id: str
+
+ updated_at: datetime
+
+ custom_rank_enabled: Optional[bool] = None
+
+ description: Optional[str] = None
diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py
index 4889324..b272d80 100644
--- a/src/codex/types/projects/cluster_list_params.py
+++ b/src/codex/types/projects/cluster_list_params.py
@@ -15,6 +15,6 @@ class ClusterListParams(TypedDict, total=False):
order: Literal["asc", "desc"]
- sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count"]]
+ sort: Optional[Literal["created_at", "answered_at", "cluster_frequency_count", "custom_rank"]]
states: List[Literal["unanswered", "draft", "published", "published_with_draft"]]
diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py
index 210f4e1..1e2fccb 100644
--- a/tests/api_resources/test_projects.py
+++ b/tests/api_resources/test_projects.py
@@ -11,6 +11,7 @@
from codex.types import (
ProjectListResponse,
ProjectReturnSchema,
+ ProjectRetrieveResponse,
)
from tests.utils import assert_matches_type
@@ -85,7 +86,7 @@ def test_method_retrieve(self, client: Codex) -> None:
project = client.projects.retrieve(
"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
)
- assert_matches_type(ProjectReturnSchema, project, path=["response"])
+ assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
@pytest.mark.skip()
@parametrize
@@ -97,7 +98,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
project = response.parse()
- assert_matches_type(ProjectReturnSchema, project, path=["response"])
+ assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
@pytest.mark.skip()
@parametrize
@@ -109,7 +110,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
project = response.parse()
- assert_matches_type(ProjectReturnSchema, project, path=["response"])
+ assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -391,7 +392,7 @@ async def test_method_retrieve(self, async_client: AsyncCodex) -> None:
project = await async_client.projects.retrieve(
"182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
)
- assert_matches_type(ProjectReturnSchema, project, path=["response"])
+ assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
@pytest.mark.skip()
@parametrize
@@ -403,7 +404,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
project = await response.parse()
- assert_matches_type(ProjectReturnSchema, project, path=["response"])
+ assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
@pytest.mark.skip()
@parametrize
@@ -415,7 +416,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
project = await response.parse()
- assert_matches_type(ProjectReturnSchema, project, path=["response"])
+ assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
assert cast(Any, response.is_closed) is True
From 552640f0d27993032b3b6b0a9861c3dbaf09d852 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 9 Apr 2025 03:40:16 +0000
Subject: [PATCH 07/15] chore(internal): slight transform perf improvement
(#108)
---
src/codex/_utils/_transform.py | 22 ++++++++++++++++++++++
tests/test_transform.py | 12 ++++++++++++
2 files changed, 34 insertions(+)
diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
index 7ac2e17..3ec6208 100644
--- a/src/codex/_utils/_transform.py
+++ b/src/codex/_utils/_transform.py
@@ -142,6 +142,10 @@ def _maybe_transform_key(key: str, type_: type) -> str:
return key
+def _no_transform_needed(annotation: type) -> bool:
+ return annotation == float or annotation == int
+
+
def _transform_recursive(
data: object,
*,
@@ -184,6 +188,15 @@ def _transform_recursive(
return cast(object, data)
inner_type = extract_type_arg(stripped_type, 0)
+ if _no_transform_needed(inner_type):
+ # for some types there is no need to transform anything, so we can get a small
+ # perf boost from skipping that work.
+ #
+ # but we still need to convert to a list to ensure the data is json-serializable
+ if is_list(data):
+ return data
+ return list(data)
+
return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
if is_union_type(stripped_type):
@@ -332,6 +345,15 @@ async def _async_transform_recursive(
return cast(object, data)
inner_type = extract_type_arg(stripped_type, 0)
+ if _no_transform_needed(inner_type):
+ # for some types there is no need to transform anything, so we can get a small
+ # perf boost from skipping that work.
+ #
+ # but we still need to convert to a list to ensure the data is json-serializable
+ if is_list(data):
+ return data
+ return list(data)
+
return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
if is_union_type(stripped_type):
diff --git a/tests/test_transform.py b/tests/test_transform.py
index 324f31a..b1ea479 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -432,3 +432,15 @@ async def test_base64_file_input(use_async: bool) -> None:
assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
"foo": "SGVsbG8sIHdvcmxkIQ=="
} # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_transform_skipping(use_async: bool) -> None:
+ # lists of ints are left as-is
+ data = [1, 2, 3]
+ assert await transform(data, List[int], use_async) is data
+
+ # iterables of ints are converted to a list
+ data = iter([1, 2, 3])
+ assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
From a25d0033bafc06b6c6ad35597278067c02794071 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Apr 2025 02:29:52 +0000
Subject: [PATCH 08/15] feat(api): api update
---
.stats.yml | 2 +-
.../types/projects/cluster_list_response.py | 110 ++++++++++++++++-
src/codex/types/projects/entry.py | 110 ++++++++++++++++-
.../types/projects/entry_query_response.py | 111 +++++++++++++++++-
4 files changed, 329 insertions(+), 4 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 4500dc4..ab9fbe2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,3 +1,3 @@
configured_endpoints: 36
-openapi_spec_hash: ee7ad81c8308305b6a609a18615ae394
+openapi_spec_hash: 2e10455457751c9efb36adc4399c684d
config_hash: adbedb6317fca6f566f54564cc341846
diff --git a/src/codex/types/projects/cluster_list_response.py b/src/codex/types/projects/cluster_list_response.py
index d13a5f9..33bc18a 100644
--- a/src/codex/types/projects/cluster_list_response.py
+++ b/src/codex/types/projects/cluster_list_response.py
@@ -6,7 +6,112 @@
from ..._models import BaseModel
-__all__ = ["ClusterListResponse"]
+__all__ = [
+ "ClusterListResponse",
+ "ManagedMetadata",
+ "ManagedMetadataContextSufficiency",
+ "ManagedMetadataQueryEaseCustomized",
+ "ManagedMetadataResponseHelpfulness",
+ "ManagedMetadataTrustworthiness",
+]
+
+
+class ManagedMetadataContextSufficiency(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class ManagedMetadataQueryEaseCustomized(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class ManagedMetadataResponseHelpfulness(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class ManagedMetadataTrustworthiness(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class ManagedMetadata(BaseModel):
+ latest_context: Optional[str] = None
+ """The most recent context string."""
+
+ latest_entry_point: Optional[str] = None
+ """The most recent entry point string."""
+
+ latest_llm_response: Optional[str] = None
+ """The most recent LLM response string."""
+
+ latest_location: Optional[str] = None
+ """The most recent location string."""
+
+ context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ contexts: Optional[List[str]] = None
+
+ entry_points: Optional[List[str]] = None
+
+ llm_responses: Optional[List[str]] = None
+
+ locations: Optional[List[str]] = None
+
+ query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ trustworthiness: Optional[ManagedMetadataTrustworthiness] = None
+ """Holds a list of scores and computes aggregate statistics."""
class ClusterListResponse(BaseModel):
@@ -16,6 +121,9 @@ class ClusterListResponse(BaseModel):
created_at: datetime
+ managed_metadata: ManagedMetadata
+ """Extract system-defined, managed metadata from client_query_metadata."""
+
project_id: str
question: str
diff --git a/src/codex/types/projects/entry.py b/src/codex/types/projects/entry.py
index b8eeb0e..24d57c8 100644
--- a/src/codex/types/projects/entry.py
+++ b/src/codex/types/projects/entry.py
@@ -6,7 +6,112 @@
from ..._models import BaseModel
-__all__ = ["Entry"]
+__all__ = [
+ "Entry",
+ "ManagedMetadata",
+ "ManagedMetadataContextSufficiency",
+ "ManagedMetadataQueryEaseCustomized",
+ "ManagedMetadataResponseHelpfulness",
+ "ManagedMetadataTrustworthiness",
+]
+
+
+class ManagedMetadataContextSufficiency(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class ManagedMetadataQueryEaseCustomized(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class ManagedMetadataResponseHelpfulness(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class ManagedMetadataTrustworthiness(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class ManagedMetadata(BaseModel):
+ latest_context: Optional[str] = None
+ """The most recent context string."""
+
+ latest_entry_point: Optional[str] = None
+ """The most recent entry point string."""
+
+ latest_llm_response: Optional[str] = None
+ """The most recent LLM response string."""
+
+ latest_location: Optional[str] = None
+ """The most recent location string."""
+
+ context_sufficiency: Optional[ManagedMetadataContextSufficiency] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ contexts: Optional[List[str]] = None
+
+ entry_points: Optional[List[str]] = None
+
+ llm_responses: Optional[List[str]] = None
+
+ locations: Optional[List[str]] = None
+
+ query_ease_customized: Optional[ManagedMetadataQueryEaseCustomized] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ response_helpfulness: Optional[ManagedMetadataResponseHelpfulness] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ trustworthiness: Optional[ManagedMetadataTrustworthiness] = None
+ """Holds a list of scores and computes aggregate statistics."""
class Entry(BaseModel):
@@ -14,6 +119,9 @@ class Entry(BaseModel):
created_at: datetime
+ managed_metadata: ManagedMetadata
+ """Extract system-defined, managed metadata from client_query_metadata."""
+
project_id: str
question: str
diff --git a/src/codex/types/projects/entry_query_response.py b/src/codex/types/projects/entry_query_response.py
index 766e1e2..26db1d8 100644
--- a/src/codex/types/projects/entry_query_response.py
+++ b/src/codex/types/projects/entry_query_response.py
@@ -4,12 +4,121 @@
from ..._models import BaseModel
-__all__ = ["EntryQueryResponse", "Entry"]
+__all__ = [
+ "EntryQueryResponse",
+ "Entry",
+ "EntryManagedMetadata",
+ "EntryManagedMetadataContextSufficiency",
+ "EntryManagedMetadataQueryEaseCustomized",
+ "EntryManagedMetadataResponseHelpfulness",
+ "EntryManagedMetadataTrustworthiness",
+]
+
+
+class EntryManagedMetadataContextSufficiency(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class EntryManagedMetadataQueryEaseCustomized(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class EntryManagedMetadataResponseHelpfulness(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class EntryManagedMetadataTrustworthiness(BaseModel):
+ average: Optional[float] = None
+ """The average of all scores."""
+
+ latest: Optional[float] = None
+ """The most recent score."""
+
+ max: Optional[float] = None
+ """The maximum score."""
+
+ min: Optional[float] = None
+ """The minimum score."""
+
+ scores: Optional[List[float]] = None
+
+
+class EntryManagedMetadata(BaseModel):
+ latest_context: Optional[str] = None
+ """The most recent context string."""
+
+ latest_entry_point: Optional[str] = None
+ """The most recent entry point string."""
+
+ latest_llm_response: Optional[str] = None
+ """The most recent LLM response string."""
+
+ latest_location: Optional[str] = None
+ """The most recent location string."""
+
+ context_sufficiency: Optional[EntryManagedMetadataContextSufficiency] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ contexts: Optional[List[str]] = None
+
+ entry_points: Optional[List[str]] = None
+
+ llm_responses: Optional[List[str]] = None
+
+ locations: Optional[List[str]] = None
+
+ query_ease_customized: Optional[EntryManagedMetadataQueryEaseCustomized] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ response_helpfulness: Optional[EntryManagedMetadataResponseHelpfulness] = None
+ """Holds a list of scores and computes aggregate statistics."""
+
+ trustworthiness: Optional[EntryManagedMetadataTrustworthiness] = None
+ """Holds a list of scores and computes aggregate statistics."""
class Entry(BaseModel):
id: str
+ managed_metadata: EntryManagedMetadata
+ """Extract system-defined, managed metadata from client_query_metadata."""
+
question: str
answer: Optional[str] = None
From 8ce4af445131dae911282d7330cd31d3124212ca Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Apr 2025 04:04:24 +0000
Subject: [PATCH 09/15] chore(internal): expand CI branch coverage
---
.github/workflows/ci.yml | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5ac5f63..b892020 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,18 +1,18 @@
name: CI
on:
push:
- branches:
- - main
- pull_request:
- branches:
- - main
- - next
+ branches-ignore:
+ - 'generated'
+ - 'codegen/**'
+ - 'integrated/**'
+ - 'preview-head/**'
+ - 'preview-base/**'
+ - 'preview/**'
jobs:
lint:
name: lint
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v4
From 0aee51f42c7400fea6538a505ab0368f9397681a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Apr 2025 04:08:38 +0000
Subject: [PATCH 10/15] chore(internal): reduce CI branch coverage
---
.github/workflows/ci.yml | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b892020..e8b7236 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,13 +1,12 @@
name: CI
on:
push:
- branches-ignore:
- - 'generated'
- - 'codegen/**'
- - 'integrated/**'
- - 'preview-head/**'
- - 'preview-base/**'
- - 'preview/**'
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ - next
jobs:
lint:
From 38418ad25f1ce660c9ee1cfabf0fc48e965c02f9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 12 Apr 2025 02:09:29 +0000
Subject: [PATCH 11/15] fix(perf): skip traversing types for NotGiven values
---
src/codex/_utils/_transform.py | 11 +++++++++++
tests/test_transform.py | 9 ++++++++-
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
index 3ec6208..3b2b8e0 100644
--- a/src/codex/_utils/_transform.py
+++ b/src/codex/_utils/_transform.py
@@ -12,6 +12,7 @@
from ._utils import (
is_list,
+ is_given,
is_mapping,
is_iterable,
)
@@ -258,6 +259,11 @@ def _transform_typeddict(
result: dict[str, object] = {}
annotations = get_type_hints(expected_type, include_extras=True)
for key, value in data.items():
+ if not is_given(value):
+ # we don't need to include `NotGiven` values here as they'll
+ # be stripped out before the request is sent anyway
+ continue
+
type_ = annotations.get(key)
if type_ is None:
# we do not have a type annotation for this field, leave it as is
@@ -415,6 +421,11 @@ async def _async_transform_typeddict(
result: dict[str, object] = {}
annotations = get_type_hints(expected_type, include_extras=True)
for key, value in data.items():
+ if not is_given(value):
+ # we don't need to include `NotGiven` values here as they'll
+ # be stripped out before the request is sent anyway
+ continue
+
type_ = annotations.get(key)
if type_ is None:
# we do not have a type annotation for this field, leave it as is
diff --git a/tests/test_transform.py b/tests/test_transform.py
index b1ea479..527845a 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -8,7 +8,7 @@
import pytest
-from codex._types import Base64FileInput
+from codex._types import NOT_GIVEN, Base64FileInput
from codex._utils import (
PropertyInfo,
transform as _transform,
@@ -444,3 +444,10 @@ async def test_transform_skipping(use_async: bool) -> None:
# iterables of ints are converted to a list
data = iter([1, 2, 3])
assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_strips_notgiven(use_async: bool) -> None:
+ assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"}
+ assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {}
From fb1f7c1783af5dd17ef959733b670604ac6b877b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 12 Apr 2025 02:11:14 +0000
Subject: [PATCH 12/15] fix(perf): optimize some hot paths
---
src/codex/_utils/_transform.py | 14 +++++++++++++-
src/codex/_utils/_typing.py | 2 ++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/codex/_utils/_transform.py b/src/codex/_utils/_transform.py
index 3b2b8e0..b0cc20a 100644
--- a/src/codex/_utils/_transform.py
+++ b/src/codex/_utils/_transform.py
@@ -5,7 +5,7 @@
import pathlib
from typing import Any, Mapping, TypeVar, cast
from datetime import date, datetime
-from typing_extensions import Literal, get_args, override, get_type_hints
+from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints
import anyio
import pydantic
@@ -13,6 +13,7 @@
from ._utils import (
is_list,
is_given,
+ lru_cache,
is_mapping,
is_iterable,
)
@@ -109,6 +110,7 @@ class Params(TypedDict, total=False):
return cast(_T, transformed)
+@lru_cache(maxsize=8096)
def _get_annotated_type(type_: type) -> type | None:
"""If the given type is an `Annotated` type then it is returned, if not `None` is returned.
@@ -433,3 +435,13 @@ async def _async_transform_typeddict(
else:
result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
return result
+
+
+@lru_cache(maxsize=8096)
+def get_type_hints(
+ obj: Any,
+ globalns: dict[str, Any] | None = None,
+ localns: Mapping[str, Any] | None = None,
+ include_extras: bool = False,
+) -> dict[str, Any]:
+ return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
diff --git a/src/codex/_utils/_typing.py b/src/codex/_utils/_typing.py
index 278749b..1958820 100644
--- a/src/codex/_utils/_typing.py
+++ b/src/codex/_utils/_typing.py
@@ -13,6 +13,7 @@
get_origin,
)
+from ._utils import lru_cache
from .._types import InheritsGeneric
from .._compat import is_union as _is_union
@@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
+@lru_cache(maxsize=8096)
def strip_annotated_type(typ: type) -> type:
if is_required_type(typ) or is_annotated_type(typ):
return strip_annotated_type(cast(type, get_args(typ)[0]))
From fc0f5bcf5e4f92ed59aa3498a4fb52713fe0c7c7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 02:11:58 +0000
Subject: [PATCH 13/15] chore(internal): update pyright settings
---
pyproject.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyproject.toml b/pyproject.toml
index 6ca8da4..ff6cde5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -147,6 +147,7 @@ exclude = [
]
reportImplicitOverride = true
+reportOverlappingOverload = false
reportImportCycles = false
reportPrivateUsage = false
From 228700d4e54104c50f0c8eac09bb490a2e0b4fe8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 02:12:59 +0000
Subject: [PATCH 14/15] chore(client): minor internal fixes
---
src/codex/_base_client.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py
index 273341b..564ab8a 100644
--- a/src/codex/_base_client.py
+++ b/src/codex/_base_client.py
@@ -409,7 +409,8 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
idempotency_header = self._idempotency_header
if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
- headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
+ options.idempotency_key = options.idempotency_key or self._idempotency_key()
+ headers[idempotency_header] = options.idempotency_key
# Don't set these headers if they were already set or removed by the caller. We check
# `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
@@ -943,6 +944,10 @@ def _request(
request = self._build_request(options, retries_taken=retries_taken)
self._prepare_request(request)
+ if options.idempotency_key:
+ # ensure the idempotency key is reused between requests
+ input_options.idempotency_key = options.idempotency_key
+
kwargs: HttpxSendArgs = {}
if self.custom_auth is not None:
kwargs["auth"] = self.custom_auth
@@ -1475,6 +1480,10 @@ async def _request(
request = self._build_request(options, retries_taken=retries_taken)
await self._prepare_request(request)
+ if options.idempotency_key:
+ # ensure the idempotency key is reused between requests
+ input_options.idempotency_key = options.idempotency_key
+
kwargs: HttpxSendArgs = {}
if self.custom_auth is not None:
kwargs["auth"] = self.custom_auth
From 23e92dbce4294d777e0b56cbcd00ccf43114ca7d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 22:59:09 +0000
Subject: [PATCH 15/15] release: 0.1.0-alpha.15
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 29 +++++++++++++++++++++++++++++
pyproject.toml | 2 +-
src/codex/_version.py | 2 +-
4 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b069996..08e82c4 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.1.0-alpha.14"
+ ".": "0.1.0-alpha.15"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9b6747..cff410b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,34 @@
# Changelog
+## 0.1.0-alpha.15 (2025-04-15)
+
+Full Changelog: [v0.1.0-alpha.14...v0.1.0-alpha.15](https://github.com/cleanlab/codex-python/compare/v0.1.0-alpha.14...v0.1.0-alpha.15)
+
+### Features
+
+* **api:** api update ([a25d003](https://github.com/cleanlab/codex-python/commit/a25d0033bafc06b6c6ad35597278067c02794071))
+* **api:** api update ([#102](https://github.com/cleanlab/codex-python/issues/102)) ([5675c9e](https://github.com/cleanlab/codex-python/commit/5675c9efbda688fa1799f1e48f64c20143fcf410))
+* **api:** api update ([#104](https://github.com/cleanlab/codex-python/issues/104)) ([404e013](https://github.com/cleanlab/codex-python/commit/404e013054df741a1d3c77597f35030cba080b82))
+* **api:** api update ([#107](https://github.com/cleanlab/codex-python/issues/107)) ([9a3564e](https://github.com/cleanlab/codex-python/commit/9a3564e4e18decc1a7238a986f6a5b23db845f35))
+
+
+### Bug Fixes
+
+* **client:** send all configured auth headers ([#106](https://github.com/cleanlab/codex-python/issues/106)) ([772695b](https://github.com/cleanlab/codex-python/commit/772695b60b05439999d9f8bd81ec772908c664d3))
+* **perf:** optimize some hot paths ([fb1f7c1](https://github.com/cleanlab/codex-python/commit/fb1f7c1783af5dd17ef959733b670604ac6b877b))
+* **perf:** skip traversing types for NotGiven values ([38418ad](https://github.com/cleanlab/codex-python/commit/38418ad25f1ce660c9ee1cfabf0fc48e965c02f9))
+
+
+### Chores
+
+* **client:** minor internal fixes ([228700d](https://github.com/cleanlab/codex-python/commit/228700d4e54104c50f0c8eac09bb490a2e0b4fe8))
+* **internal:** expand CI branch coverage ([8ce4af4](https://github.com/cleanlab/codex-python/commit/8ce4af445131dae911282d7330cd31d3124212ca))
+* **internal:** reduce CI branch coverage ([0aee51f](https://github.com/cleanlab/codex-python/commit/0aee51f42c7400fea6538a505ab0368f9397681a))
+* **internal:** remove trailing character ([#103](https://github.com/cleanlab/codex-python/issues/103)) ([45f7fde](https://github.com/cleanlab/codex-python/commit/45f7fde22223d99bec5ab8142b59f5124b275c28))
+* **internal:** slight transform perf improvement ([#108](https://github.com/cleanlab/codex-python/issues/108)) ([552640f](https://github.com/cleanlab/codex-python/commit/552640f0d27993032b3b6b0a9861c3dbaf09d852))
+* **internal:** update pyright settings ([fc0f5bc](https://github.com/cleanlab/codex-python/commit/fc0f5bcf5e4f92ed59aa3498a4fb52713fe0c7c7))
+* **internal:** version bump ([#99](https://github.com/cleanlab/codex-python/issues/99)) ([4bc12e4](https://github.com/cleanlab/codex-python/commit/4bc12e4d6bd408e6256f2e6e5539fd8feb29fab2))
+
## 0.1.0-alpha.14 (2025-04-03)
Full Changelog: [v0.1.0-alpha.13...v0.1.0-alpha.14](https://github.com/cleanlab/codex-python/compare/v0.1.0-alpha.13...v0.1.0-alpha.14)
diff --git a/pyproject.toml b/pyproject.toml
index ff6cde5..686a2e1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "codex-sdk"
-version = "0.1.0-alpha.14"
+version = "0.1.0-alpha.15"
description = "Internal SDK used within cleanlab-codex package. Refer to https://pypi.org/project/cleanlab-codex/ instead."
dynamic = ["readme"]
license = "MIT"
diff --git a/src/codex/_version.py b/src/codex/_version.py
index 1509753..d4919cf 100644
--- a/src/codex/_version.py
+++ b/src/codex/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "codex"
-__version__ = "0.1.0-alpha.14" # x-release-please-version
+__version__ = "0.1.0-alpha.15" # x-release-please-version