diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f81bf992..f04d0896 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.31.0" + ".": "0.32.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 193855ae..1b0430f8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 49 +configured_endpoints: 61 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/mixedbread%2Fmixedbread-34b8fc657696023cc3a74eee6febe0d2fc0decda668f874bfa42abe6222d8566.yml openapi_spec_hash: dcd015b362c3fcb4ef449606a8027873 -config_hash: a2549e1f1923904c8cee4ea4f5ff58e1 +config_hash: 2de40c343cf7b242d5925e3405ee8908 diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f35722..1a2bac45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.32.0 (2025-10-01) + +Full Changelog: [v0.31.0...v0.32.0](https://github.com/mixedbread-ai/mixedbread-python/compare/v0.31.0...v0.32.0) + +### Features + +* **api:** update via SDK Studio ([cdca00d](https://github.com/mixedbread-ai/mixedbread-python/commit/cdca00d9f31579e66e3992cd08e43aa70a9079d1)) + ## 0.31.0 (2025-10-01) Full Changelog: [v0.30.0...v0.31.0](https://github.com/mixedbread-ai/mixedbread-python/compare/v0.30.0...v0.31.0) diff --git a/README.md b/README.md index b9c96e63..ade28bb0 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ client = Mixedbread( environment="development", ) -vector_store = client.vector_stores.create() -print(vector_store.id) +store = client.stores.create() +print(store.id) ``` While you can provide an `api_key` keyword argument, @@ -60,8 +60,8 @@ client = AsyncMixedbread( async def main() -> None: - vector_store = await client.vector_stores.create() - print(vector_store.id) + store = await client.stores.create() + print(store.id) asyncio.run(main()) @@ -93,8 +93,8 @@ async def main() -> None: api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: - vector_store = await client.vector_stores.create() - print(vector_store.id) + store = await client.stores.create() + print(store.id) asyncio.run(main()) @@ -120,12 +120,12 @@ from mixedbread import Mixedbread client = Mixedbread() -all_vector_stores = [] +all_stores = [] # Automatically fetches more pages as needed. -for vector_store in client.vector_stores.list(): - # Do something with vector_store here - all_vector_stores.append(vector_store) -print(all_vector_stores) +for store in client.stores.list(): + # Do something with store here + all_stores.append(store) +print(all_stores) ``` Or, asynchronously: @@ -138,11 +138,11 @@ client = AsyncMixedbread() async def main() -> None: - all_vector_stores = [] + all_stores = [] # Iterate through items across all pages, issuing requests as needed. - async for vector_store in client.vector_stores.list(): - all_vector_stores.append(vector_store) - print(all_vector_stores) + async for store in client.stores.list(): + all_stores.append(store) + print(all_stores) asyncio.run(main()) @@ -151,7 +151,7 @@ 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.vector_stores.list() +first_page = await client.stores.list() 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() @@ -163,11 +163,11 @@ if first_page.has_next_page(): Or just work directly with the returned data: ```python -first_page = await client.vector_stores.list() +first_page = await client.stores.list() print(f"next page cursor: {first_page.pagination.last_cursor}") # => "next page cursor: ..." -for vector_store in first_page.data: - print(vector_store.id) +for store in first_page.data: + print(store.id) # Remove `await` for non-async usage. ``` @@ -181,10 +181,10 @@ from mixedbread import Mixedbread client = Mixedbread() -vector_store = client.vector_stores.create( +store = client.stores.create( expires_after={}, ) -print(vector_store.expires_after) +print(store.expires_after) ``` ## File uploads @@ -220,7 +220,7 @@ from mixedbread import Mixedbread client = Mixedbread() try: - client.vector_stores.create() + client.stores.create() except mixedbread.APIConnectionError as e: print("The server could not be reached") print(e.__cause__) # an underlying Exception, likely raised within httpx. @@ -263,7 +263,7 @@ client = Mixedbread( ) # Or, configure per-request: -client.with_options(max_retries=5).vector_stores.create() +client.with_options(max_retries=5).stores.create() ``` ### Timeouts @@ -286,7 +286,7 @@ client = Mixedbread( ) # Override per-request: -client.with_options(timeout=5.0).vector_stores.create() +client.with_options(timeout=5.0).stores.create() ``` On timeout, an `APITimeoutError` is thrown. @@ -327,11 +327,11 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from mixedbread import Mixedbread client = Mixedbread() -response = client.vector_stores.with_raw_response.create() +response = client.stores.with_raw_response.create() print(response.headers.get('X-My-Header')) -vector_store = response.parse() # get the object that `vector_stores.create()` would have returned -print(vector_store.id) +store = response.parse() # get the object that `stores.create()` would have returned +print(store.id) ``` These methods return an [`APIResponse`](https://github.com/mixedbread-ai/mixedbread-python/tree/main/src/mixedbread/_response.py) object. @@ -345,7 +345,7 @@ The above interface eagerly reads the full response body when you make the reque 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.vector_stores.with_streaming_response.create() as response: +with client.stores.with_streaming_response.create() as response: print(response.headers.get("X-My-Header")) for line in response.iter_lines(): diff --git a/api.md b/api.md index 2a054ea2..ba725e0b 100644 --- a/api.md +++ b/api.md @@ -77,6 +77,53 @@ Methods: - client.vector_stores.files.delete(file_id, \*, vector_store_identifier) -> FileDeleteResponse - client.vector_stores.files.search(\*\*params) -> FileSearchResponse +# Stores + +Types: + +```python +from mixedbread.types import ( + Store, + StoreChunkSearchOptions, + StoreDeleteResponse, + StoreQuestionAnsweringResponse, + StoreSearchResponse, +) +``` + +Methods: + +- client.stores.create(\*\*params) -> Store +- client.stores.retrieve(store_identifier) -> Store +- client.stores.update(store_identifier, \*\*params) -> Store +- client.stores.list(\*\*params) -> SyncCursor[Store] +- client.stores.delete(store_identifier) -> StoreDeleteResponse +- client.stores.question_answering(\*\*params) -> StoreQuestionAnsweringResponse +- client.stores.search(\*\*params) -> StoreSearchResponse + +## Files + +Types: + +```python +from mixedbread.types.stores import ( + ScoredStoreFile, + StoreFileStatus, + StoreFile, + FileListResponse, + FileDeleteResponse, + FileSearchResponse, +) +``` + +Methods: + +- client.stores.files.create(store_identifier, \*\*params) -> StoreFile +- client.stores.files.retrieve(file_id, \*, store_identifier, \*\*params) -> StoreFile +- client.stores.files.list(store_identifier, \*\*params) -> FileListResponse +- client.stores.files.delete(file_id, \*, store_identifier) -> FileDeleteResponse +- client.stores.files.search(\*\*params) -> FileSearchResponse + # Parsing ## Jobs diff --git a/pyproject.toml b/pyproject.toml index e3bc88a2..f9ec7480 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mixedbread" -version = "0.31.0" +version = "0.32.0" description = "The official Python library for the Mixedbread API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/mixedbread/_client.py b/src/mixedbread/_client.py index 31462225..0a01cdd2 100644 --- a/src/mixedbread/_client.py +++ b/src/mixedbread/_client.py @@ -47,6 +47,7 @@ AsyncAPIClient, make_request_options, ) +from .resources.stores import stores from .resources.parsing import parsing from .types.info_response import InfoResponse from .resources.extractions import extractions @@ -77,6 +78,7 @@ class Mixedbread(SyncAPIClient): vector_stores: vector_stores.VectorStoresResource + stores: stores.StoresResource parsing: parsing.ParsingResource files: files.FilesResource extractions: extractions.ExtractionsResource @@ -166,6 +168,7 @@ def __init__( ) self.vector_stores = vector_stores.VectorStoresResource(self) + self.stores = stores.StoresResource(self) self.parsing = parsing.ParsingResource(self) self.files = files.FilesResource(self) self.extractions = extractions.ExtractionsResource(self) @@ -440,6 +443,7 @@ def _make_status_error( class AsyncMixedbread(AsyncAPIClient): vector_stores: vector_stores.AsyncVectorStoresResource + stores: stores.AsyncStoresResource parsing: parsing.AsyncParsingResource files: files.AsyncFilesResource extractions: extractions.AsyncExtractionsResource @@ -529,6 +533,7 @@ def __init__( ) self.vector_stores = vector_stores.AsyncVectorStoresResource(self) + self.stores = stores.AsyncStoresResource(self) self.parsing = parsing.AsyncParsingResource(self) self.files = files.AsyncFilesResource(self) self.extractions = extractions.AsyncExtractionsResource(self) @@ -804,6 +809,7 @@ def _make_status_error( class MixedbreadWithRawResponse: def __init__(self, client: Mixedbread) -> None: self.vector_stores = vector_stores.VectorStoresResourceWithRawResponse(client.vector_stores) + self.stores = stores.StoresResourceWithRawResponse(client.stores) self.parsing = parsing.ParsingResourceWithRawResponse(client.parsing) self.files = files.FilesResourceWithRawResponse(client.files) self.extractions = extractions.ExtractionsResourceWithRawResponse(client.extractions) @@ -826,6 +832,7 @@ def __init__(self, client: Mixedbread) -> None: class AsyncMixedbreadWithRawResponse: def __init__(self, client: AsyncMixedbread) -> None: self.vector_stores = vector_stores.AsyncVectorStoresResourceWithRawResponse(client.vector_stores) + self.stores = stores.AsyncStoresResourceWithRawResponse(client.stores) self.parsing = parsing.AsyncParsingResourceWithRawResponse(client.parsing) self.files = files.AsyncFilesResourceWithRawResponse(client.files) self.extractions = extractions.AsyncExtractionsResourceWithRawResponse(client.extractions) @@ -848,6 +855,7 @@ def __init__(self, client: AsyncMixedbread) -> None: class MixedbreadWithStreamedResponse: def __init__(self, client: Mixedbread) -> None: self.vector_stores = vector_stores.VectorStoresResourceWithStreamingResponse(client.vector_stores) + self.stores = stores.StoresResourceWithStreamingResponse(client.stores) self.parsing = parsing.ParsingResourceWithStreamingResponse(client.parsing) self.files = files.FilesResourceWithStreamingResponse(client.files) self.extractions = extractions.ExtractionsResourceWithStreamingResponse(client.extractions) @@ -870,6 +878,7 @@ def __init__(self, client: Mixedbread) -> None: class AsyncMixedbreadWithStreamedResponse: def __init__(self, client: AsyncMixedbread) -> None: self.vector_stores = vector_stores.AsyncVectorStoresResourceWithStreamingResponse(client.vector_stores) + self.stores = stores.AsyncStoresResourceWithStreamingResponse(client.stores) self.parsing = parsing.AsyncParsingResourceWithStreamingResponse(client.parsing) self.files = files.AsyncFilesResourceWithStreamingResponse(client.files) self.extractions = extractions.AsyncExtractionsResourceWithStreamingResponse(client.extractions) diff --git a/src/mixedbread/_version.py b/src/mixedbread/_version.py index fae94c2c..6c91f797 100644 --- a/src/mixedbread/_version.py +++ b/src/mixedbread/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "mixedbread" -__version__ = "0.31.0" # x-release-please-version +__version__ = "0.32.0" # x-release-please-version diff --git a/src/mixedbread/resources/__init__.py b/src/mixedbread/resources/__init__.py index 0b3e0954..368addbe 100644 --- a/src/mixedbread/resources/__init__.py +++ b/src/mixedbread/resources/__init__.py @@ -16,6 +16,14 @@ FilesResourceWithStreamingResponse, AsyncFilesResourceWithStreamingResponse, ) +from .stores import ( + StoresResource, + AsyncStoresResource, + StoresResourceWithRawResponse, + AsyncStoresResourceWithRawResponse, + StoresResourceWithStreamingResponse, + AsyncStoresResourceWithStreamingResponse, +) from .parsing import ( ParsingResource, AsyncParsingResource, @@ -72,6 +80,12 @@ "AsyncVectorStoresResourceWithRawResponse", "VectorStoresResourceWithStreamingResponse", "AsyncVectorStoresResourceWithStreamingResponse", + "StoresResource", + "AsyncStoresResource", + "StoresResourceWithRawResponse", + "AsyncStoresResourceWithRawResponse", + "StoresResourceWithStreamingResponse", + "AsyncStoresResourceWithStreamingResponse", "ParsingResource", "AsyncParsingResource", "ParsingResourceWithRawResponse", diff --git a/src/mixedbread/resources/stores/__init__.py b/src/mixedbread/resources/stores/__init__.py new file mode 100644 index 00000000..8ec1ec90 --- /dev/null +++ b/src/mixedbread/resources/stores/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .files import ( + FilesResource, + AsyncFilesResource, + FilesResourceWithRawResponse, + AsyncFilesResourceWithRawResponse, + FilesResourceWithStreamingResponse, + AsyncFilesResourceWithStreamingResponse, +) +from .stores import ( + StoresResource, + AsyncStoresResource, + StoresResourceWithRawResponse, + AsyncStoresResourceWithRawResponse, + StoresResourceWithStreamingResponse, + AsyncStoresResourceWithStreamingResponse, +) + +__all__ = [ + "FilesResource", + "AsyncFilesResource", + "FilesResourceWithRawResponse", + "AsyncFilesResourceWithRawResponse", + "FilesResourceWithStreamingResponse", + "AsyncFilesResourceWithStreamingResponse", + "StoresResource", + "AsyncStoresResource", + "StoresResourceWithRawResponse", + "AsyncStoresResourceWithRawResponse", + "StoresResourceWithStreamingResponse", + "AsyncStoresResourceWithStreamingResponse", +] diff --git a/src/mixedbread/resources/stores/files.py b/src/mixedbread/resources/stores/files.py new file mode 100644 index 00000000..9fc14bb6 --- /dev/null +++ b/src/mixedbread/resources/stores/files.py @@ -0,0 +1,741 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable, Optional + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, 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.stores import file_list_params, file_create_params, file_search_params, file_retrieve_params +from ...types.stores.store_file import StoreFile +from ...types.stores.store_file_status import StoreFileStatus +from ...types.stores.file_list_response import FileListResponse +from ...types.stores.file_delete_response import FileDeleteResponse +from ...types.stores.file_search_response import FileSearchResponse + +__all__ = ["FilesResource", "AsyncFilesResource"] + + +class FilesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> FilesResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return FilesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FilesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return FilesResourceWithStreamingResponse(self) + + def create( + self, + store_identifier: str, + *, + metadata: object | Omit = omit, + experimental: file_create_params.Experimental | Omit = omit, + file_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StoreFile: + """Upload a file to a store. + + Args: store_identifier: The ID or name of the store. + + file_add_params: The file + to add to the store. + + Returns: VectorStoreFile: The uploaded file details. + + Args: + store_identifier: The ID or name of the store + + metadata: Optional metadata for the file + + experimental: Strategy for adding the file + + file_id: ID of the file to add + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._post( + f"/v1/stores/{store_identifier}/files", + body=maybe_transform( + { + "metadata": metadata, + "experimental": experimental, + "file_id": file_id, + }, + file_create_params.FileCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreFile, + ) + + def retrieve( + self, + file_id: str, + *, + store_identifier: str, + return_chunks: bool | 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, + ) -> StoreFile: + """Get a file from a store. + + Args: store_identifier: The ID or name of the store. + + file_id: The ID or name of + the file. options: Get file options. + + Returns: VectorStoreFile: The file details. + + Args: + store_identifier: The ID or name of the store + + file_id: The ID or name of the file + + return_chunks: Whether to return the chunks for the file + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + return self._get( + f"/v1/stores/{store_identifier}/files/{file_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"return_chunks": return_chunks}, file_retrieve_params.FileRetrieveParams), + ), + cast_to=StoreFile, + ) + + def list( + self, + store_identifier: str, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + statuses: Optional[List[StoreFileStatus]] | Omit = omit, + metadata_filter: Optional[file_list_params.MetadataFilter] | 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, + ) -> FileListResponse: + """ + List files indexed in a vector store with pagination and metadata filter. + + Args: vector_store_identifier: The ID or name of the vector store pagination: + Pagination parameters and metadata filter + + Returns: VectorStoreFileListResponse: Paginated list of vector store files + + Args: + store_identifier: The ID or name of the store + + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + statuses: Status to filter by + + metadata_filter: Metadata filter to apply to the query + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._post( + f"/v1/stores/{store_identifier}/files/list", + body=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "statuses": statuses, + "metadata_filter": metadata_filter, + }, + file_list_params.FileListParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileListResponse, + ) + + def delete( + self, + file_id: str, + *, + store_identifier: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileDeleteResponse: + """Delete a file from a store. + + Args: store_identifier: The ID or name of the store. + + file_id: The ID or name of + the file to delete. + + Returns: VectorStoreFileDeleted: The deleted file details. + + Args: + store_identifier: The ID or name of the store + + file_id: The ID or name of the file to delete + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + return self._delete( + f"/v1/stores/{store_identifier}/files/{file_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileDeleteResponse, + ) + + def search( + self, + *, + query: str, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[file_search_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: file_search_params.SearchOptions | 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, + ) -> FileSearchResponse: + """ + Search for files within a store based on semantic similarity. + + Args: store_identifier: The ID or name of the store to search within + search_params: Search configuration including query text, pagination, and + filters + + Returns: StoreFileSearchResponse: List of matching files with relevance scores + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + 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 + """ + return self._post( + "/v1/stores/files/search", + body=maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + }, + file_search_params.FileSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileSearchResponse, + ) + + +class AsyncFilesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncFilesResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return AsyncFilesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFilesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return AsyncFilesResourceWithStreamingResponse(self) + + async def create( + self, + store_identifier: str, + *, + metadata: object | Omit = omit, + experimental: file_create_params.Experimental | Omit = omit, + file_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StoreFile: + """Upload a file to a store. + + Args: store_identifier: The ID or name of the store. + + file_add_params: The file + to add to the store. + + Returns: VectorStoreFile: The uploaded file details. + + Args: + store_identifier: The ID or name of the store + + metadata: Optional metadata for the file + + experimental: Strategy for adding the file + + file_id: ID of the file to add + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._post( + f"/v1/stores/{store_identifier}/files", + body=await async_maybe_transform( + { + "metadata": metadata, + "experimental": experimental, + "file_id": file_id, + }, + file_create_params.FileCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreFile, + ) + + async def retrieve( + self, + file_id: str, + *, + store_identifier: str, + return_chunks: bool | 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, + ) -> StoreFile: + """Get a file from a store. + + Args: store_identifier: The ID or name of the store. + + file_id: The ID or name of + the file. options: Get file options. + + Returns: VectorStoreFile: The file details. + + Args: + store_identifier: The ID or name of the store + + file_id: The ID or name of the file + + return_chunks: Whether to return the chunks for the file + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + return await self._get( + f"/v1/stores/{store_identifier}/files/{file_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"return_chunks": return_chunks}, file_retrieve_params.FileRetrieveParams + ), + ), + cast_to=StoreFile, + ) + + async def list( + self, + store_identifier: str, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + statuses: Optional[List[StoreFileStatus]] | Omit = omit, + metadata_filter: Optional[file_list_params.MetadataFilter] | 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, + ) -> FileListResponse: + """ + List files indexed in a vector store with pagination and metadata filter. + + Args: vector_store_identifier: The ID or name of the vector store pagination: + Pagination parameters and metadata filter + + Returns: VectorStoreFileListResponse: Paginated list of vector store files + + Args: + store_identifier: The ID or name of the store + + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + statuses: Status to filter by + + metadata_filter: Metadata filter to apply to the query + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._post( + f"/v1/stores/{store_identifier}/files/list", + body=await async_maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "statuses": statuses, + "metadata_filter": metadata_filter, + }, + file_list_params.FileListParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileListResponse, + ) + + async def delete( + self, + file_id: str, + *, + store_identifier: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FileDeleteResponse: + """Delete a file from a store. + + Args: store_identifier: The ID or name of the store. + + file_id: The ID or name of + the file to delete. + + Returns: VectorStoreFileDeleted: The deleted file details. + + Args: + store_identifier: The ID or name of the store + + file_id: The ID or name of the file to delete + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + if not file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + return await self._delete( + f"/v1/stores/{store_identifier}/files/{file_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileDeleteResponse, + ) + + async def search( + self, + *, + query: str, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[file_search_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: file_search_params.SearchOptions | 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, + ) -> FileSearchResponse: + """ + Search for files within a store based on semantic similarity. + + Args: store_identifier: The ID or name of the store to search within + search_params: Search configuration including query text, pagination, and + filters + + Returns: StoreFileSearchResponse: List of matching files with relevance scores + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + 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 + """ + return await self._post( + "/v1/stores/files/search", + body=await async_maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + }, + file_search_params.FileSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileSearchResponse, + ) + + +class FilesResourceWithRawResponse: + def __init__(self, files: FilesResource) -> None: + self._files = files + + self.create = to_raw_response_wrapper( + files.create, + ) + self.retrieve = to_raw_response_wrapper( + files.retrieve, + ) + self.list = to_raw_response_wrapper( + files.list, + ) + self.delete = to_raw_response_wrapper( + files.delete, + ) + self.search = to_raw_response_wrapper( + files.search, + ) + + +class AsyncFilesResourceWithRawResponse: + def __init__(self, files: AsyncFilesResource) -> None: + self._files = files + + self.create = async_to_raw_response_wrapper( + files.create, + ) + self.retrieve = async_to_raw_response_wrapper( + files.retrieve, + ) + self.list = async_to_raw_response_wrapper( + files.list, + ) + self.delete = async_to_raw_response_wrapper( + files.delete, + ) + self.search = async_to_raw_response_wrapper( + files.search, + ) + + +class FilesResourceWithStreamingResponse: + def __init__(self, files: FilesResource) -> None: + self._files = files + + self.create = to_streamed_response_wrapper( + files.create, + ) + self.retrieve = to_streamed_response_wrapper( + files.retrieve, + ) + self.list = to_streamed_response_wrapper( + files.list, + ) + self.delete = to_streamed_response_wrapper( + files.delete, + ) + self.search = to_streamed_response_wrapper( + files.search, + ) + + +class AsyncFilesResourceWithStreamingResponse: + def __init__(self, files: AsyncFilesResource) -> None: + self._files = files + + self.create = async_to_streamed_response_wrapper( + files.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + files.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + files.list, + ) + self.delete = async_to_streamed_response_wrapper( + files.delete, + ) + self.search = async_to_streamed_response_wrapper( + files.search, + ) diff --git a/src/mixedbread/resources/stores/stores.py b/src/mixedbread/resources/stores/stores.py new file mode 100644 index 00000000..a2128963 --- /dev/null +++ b/src/mixedbread/resources/stores/stores.py @@ -0,0 +1,1053 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional + +import httpx + +from .files import ( + FilesResource, + AsyncFilesResource, + FilesResourceWithRawResponse, + AsyncFilesResourceWithRawResponse, + FilesResourceWithStreamingResponse, + AsyncFilesResourceWithStreamingResponse, +) +from ...types import ( + store_list_params, + store_create_params, + store_search_params, + store_update_params, + store_question_answering_params, +) +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, 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 ...pagination import SyncCursor, AsyncCursor +from ...types.store import Store +from ..._base_client import AsyncPaginator, make_request_options +from ...types.expires_after_param import ExpiresAfterParam +from ...types.store_delete_response import StoreDeleteResponse +from ...types.store_search_response import StoreSearchResponse +from ...types.store_chunk_search_options_param import StoreChunkSearchOptionsParam +from ...types.store_question_answering_response import StoreQuestionAnsweringResponse + +__all__ = ["StoresResource", "AsyncStoresResource"] + + +class StoresResource(SyncAPIResource): + @cached_property + def files(self) -> FilesResource: + return FilesResource(self._client) + + @cached_property + def with_raw_response(self) -> StoresResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return StoresResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> StoresResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return StoresResourceWithStreamingResponse(self) + + def create( + self, + *, + name: Optional[str] | Omit = omit, + description: Optional[str] | Omit = omit, + is_public: bool | Omit = omit, + expires_after: Optional[ExpiresAfterParam] | Omit = omit, + metadata: object | Omit = omit, + file_ids: Optional[SequenceNotStr[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, + ) -> Store: + """ + Create a new vector store. + + Args: vector_store_create: VectorStoreCreate object containing the name, + description, and metadata. + + Returns: VectorStore: The response containing the created vector store details. + + Args: + name: Name for the new store + + description: Description of the store + + is_public: Whether the store can be accessed by anyone with valid login credentials + + expires_after: Represents an expiration policy for a store. + + metadata: Optional metadata key-value pairs + + file_ids: Optional list of file IDs + + 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 + """ + return self._post( + "/v1/stores", + body=maybe_transform( + { + "name": name, + "description": description, + "is_public": is_public, + "expires_after": expires_after, + "metadata": metadata, + "file_ids": file_ids, + }, + store_create_params.StoreCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + def retrieve( + self, + store_identifier: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Store: + """ + Get a store by ID or name. + + Args: store_identifier: The ID or name of the store to retrieve. + + Returns: Store: The response containing the store details. + + Args: + store_identifier: The ID or name of the store + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._get( + f"/v1/stores/{store_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + def update( + self, + store_identifier: str, + *, + name: Optional[str] | Omit = omit, + description: Optional[str] | Omit = omit, + is_public: Optional[bool] | Omit = omit, + expires_after: Optional[ExpiresAfterParam] | Omit = omit, + metadata: object | 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, + ) -> Store: + """ + Update a store by ID or name. + + Args: store_identifier: The ID or name of the store to update. store_update: + StoreCreate object containing the name, description, and metadata. + + Returns: Store: The response containing the updated store details. + + Args: + store_identifier: The ID or name of the store + + name: New name for the store + + description: New description + + is_public: Whether the store can be accessed by anyone with valid login credentials + + expires_after: Represents an expiration policy for a store. + + metadata: Optional metadata key-value pairs + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._put( + f"/v1/stores/{store_identifier}", + body=maybe_transform( + { + "name": name, + "description": description, + "is_public": is_public, + "expires_after": expires_after, + "metadata": metadata, + }, + store_update_params.StoreUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + def list( + self, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + q: Optional[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, + ) -> SyncCursor[Store]: + """List all stores with optional search. + + Args: pagination: The pagination options. + + q: Optional search query to filter + vector stores. + + Returns: StoreListResponse: The list of stores. + + Args: + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + q: Search query for fuzzy matching over name and description fields + + 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 + """ + return self._get_api_list( + "/v1/stores", + page=SyncCursor[Store], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "q": q, + }, + store_list_params.StoreListParams, + ), + ), + model=Store, + ) + + def delete( + self, + store_identifier: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StoreDeleteResponse: + """ + Delete a store by ID or name. + + Args: store_identifier: The ID or name of the store to delete. + + Returns: Store: The response containing the deleted store details. + + Args: + store_identifier: The ID or name of the store to delete + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return self._delete( + f"/v1/stores/{store_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreDeleteResponse, + ) + + def question_answering( + self, + *, + query: str | Omit = omit, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_question_answering_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | Omit = omit, + stream: bool | Omit = omit, + qa_options: store_question_answering_params.QaOptions | 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, + ) -> StoreQuestionAnsweringResponse: + """Question answering + + Args: + query: Question to answer. + + If not provided, the question will be extracted from the + passed messages. + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + stream: Whether to stream the answer + + qa_options: Question answering configuration options + + 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 + """ + return self._post( + "/v1/stores/question-answering", + body=maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + "stream": stream, + "qa_options": qa_options, + }, + store_question_answering_params.StoreQuestionAnsweringParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreQuestionAnsweringResponse, + ) + + def search( + self, + *, + query: str, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_search_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | 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, + ) -> StoreSearchResponse: + """ + Perform semantic search across store chunks. + + This endpoint searches through store chunks using semantic similarity matching. + It supports complex search queries with filters and returns relevance-scored + results. + + Args: search_params: Search configuration including: - query text or + embeddings - store_identifiers: List of store identifiers to search - file_ids: + Optional list of file IDs to filter chunks by (or tuple of list and condition + operator) - metadata filters - pagination parameters - sorting preferences + \\__state: API state dependency \\__ctx: Service context dependency + + Returns: StoreSearchResponse containing: - List of matched chunks with relevance + scores - Pagination details including total result count + + Raises: HTTPException (400): If search parameters are invalid HTTPException + (404): If no vector stores are found to search + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + 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 + """ + return self._post( + "/v1/stores/search", + body=maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + }, + store_search_params.StoreSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreSearchResponse, + ) + + +class AsyncStoresResource(AsyncAPIResource): + @cached_property + def files(self) -> AsyncFilesResource: + return AsyncFilesResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncStoresResourceWithRawResponse: + """ + 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/mixedbread-ai/mixedbread-python#accessing-raw-response-data-eg-headers + """ + return AsyncStoresResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncStoresResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/mixedbread-ai/mixedbread-python#with_streaming_response + """ + return AsyncStoresResourceWithStreamingResponse(self) + + async def create( + self, + *, + name: Optional[str] | Omit = omit, + description: Optional[str] | Omit = omit, + is_public: bool | Omit = omit, + expires_after: Optional[ExpiresAfterParam] | Omit = omit, + metadata: object | Omit = omit, + file_ids: Optional[SequenceNotStr[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, + ) -> Store: + """ + Create a new vector store. + + Args: vector_store_create: VectorStoreCreate object containing the name, + description, and metadata. + + Returns: VectorStore: The response containing the created vector store details. + + Args: + name: Name for the new store + + description: Description of the store + + is_public: Whether the store can be accessed by anyone with valid login credentials + + expires_after: Represents an expiration policy for a store. + + metadata: Optional metadata key-value pairs + + file_ids: Optional list of file IDs + + 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 + """ + return await self._post( + "/v1/stores", + body=await async_maybe_transform( + { + "name": name, + "description": description, + "is_public": is_public, + "expires_after": expires_after, + "metadata": metadata, + "file_ids": file_ids, + }, + store_create_params.StoreCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + async def retrieve( + self, + store_identifier: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Store: + """ + Get a store by ID or name. + + Args: store_identifier: The ID or name of the store to retrieve. + + Returns: Store: The response containing the store details. + + Args: + store_identifier: The ID or name of the store + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._get( + f"/v1/stores/{store_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + async def update( + self, + store_identifier: str, + *, + name: Optional[str] | Omit = omit, + description: Optional[str] | Omit = omit, + is_public: Optional[bool] | Omit = omit, + expires_after: Optional[ExpiresAfterParam] | Omit = omit, + metadata: object | 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, + ) -> Store: + """ + Update a store by ID or name. + + Args: store_identifier: The ID or name of the store to update. store_update: + StoreCreate object containing the name, description, and metadata. + + Returns: Store: The response containing the updated store details. + + Args: + store_identifier: The ID or name of the store + + name: New name for the store + + description: New description + + is_public: Whether the store can be accessed by anyone with valid login credentials + + expires_after: Represents an expiration policy for a store. + + metadata: Optional metadata key-value pairs + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._put( + f"/v1/stores/{store_identifier}", + body=await async_maybe_transform( + { + "name": name, + "description": description, + "is_public": is_public, + "expires_after": expires_after, + "metadata": metadata, + }, + store_update_params.StoreUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Store, + ) + + def list( + self, + *, + limit: int | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + include_total: bool | Omit = omit, + q: Optional[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, + ) -> AsyncPaginator[Store, AsyncCursor[Store]]: + """List all stores with optional search. + + Args: pagination: The pagination options. + + q: Optional search query to filter + vector stores. + + Returns: StoreListResponse: The list of stores. + + Args: + limit: Maximum number of items to return per page (1-100) + + after: Cursor for forward pagination - get items after this position. Use last_cursor + from previous response. + + before: Cursor for backward pagination - get items before this position. Use + first_cursor from previous response. + + include_total: Whether to include total count in response (expensive operation) + + q: Search query for fuzzy matching over name and description fields + + 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 + """ + return self._get_api_list( + "/v1/stores", + page=AsyncCursor[Store], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "after": after, + "before": before, + "include_total": include_total, + "q": q, + }, + store_list_params.StoreListParams, + ), + ), + model=Store, + ) + + async def delete( + self, + store_identifier: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StoreDeleteResponse: + """ + Delete a store by ID or name. + + Args: store_identifier: The ID or name of the store to delete. + + Returns: Store: The response containing the deleted store details. + + Args: + store_identifier: The ID or name of the store to delete + + 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 store_identifier: + raise ValueError(f"Expected a non-empty value for `store_identifier` but received {store_identifier!r}") + return await self._delete( + f"/v1/stores/{store_identifier}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreDeleteResponse, + ) + + async def question_answering( + self, + *, + query: str | Omit = omit, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_question_answering_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | Omit = omit, + stream: bool | Omit = omit, + qa_options: store_question_answering_params.QaOptions | 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, + ) -> StoreQuestionAnsweringResponse: + """Question answering + + Args: + query: Question to answer. + + If not provided, the question will be extracted from the + passed messages. + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + stream: Whether to stream the answer + + qa_options: Question answering configuration options + + 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 + """ + return await self._post( + "/v1/stores/question-answering", + body=await async_maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + "stream": stream, + "qa_options": qa_options, + }, + store_question_answering_params.StoreQuestionAnsweringParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreQuestionAnsweringResponse, + ) + + async def search( + self, + *, + query: str, + store_identifiers: SequenceNotStr[str], + top_k: int | Omit = omit, + filters: Optional[store_search_params.Filters] | Omit = omit, + file_ids: Union[Iterable[object], SequenceNotStr[str], None] | Omit = omit, + search_options: StoreChunkSearchOptionsParam | 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, + ) -> StoreSearchResponse: + """ + Perform semantic search across store chunks. + + This endpoint searches through store chunks using semantic similarity matching. + It supports complex search queries with filters and returns relevance-scored + results. + + Args: search_params: Search configuration including: - query text or + embeddings - store_identifiers: List of store identifiers to search - file_ids: + Optional list of file IDs to filter chunks by (or tuple of list and condition + operator) - metadata filters - pagination parameters - sorting preferences + \\__state: API state dependency \\__ctx: Service context dependency + + Returns: StoreSearchResponse containing: - List of matched chunks with relevance + scores - Pagination details including total result count + + Raises: HTTPException (400): If search parameters are invalid HTTPException + (404): If no vector stores are found to search + + Args: + query: Search query text + + store_identifiers: IDs or names of stores to search + + top_k: Number of results to return + + filters: Optional filter conditions + + file_ids: Optional list of file IDs to filter chunks by (inclusion filter) + + search_options: Search configuration options + + 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 + """ + return await self._post( + "/v1/stores/search", + body=await async_maybe_transform( + { + "query": query, + "store_identifiers": store_identifiers, + "top_k": top_k, + "filters": filters, + "file_ids": file_ids, + "search_options": search_options, + }, + store_search_params.StoreSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StoreSearchResponse, + ) + + +class StoresResourceWithRawResponse: + def __init__(self, stores: StoresResource) -> None: + self._stores = stores + + self.create = to_raw_response_wrapper( + stores.create, + ) + self.retrieve = to_raw_response_wrapper( + stores.retrieve, + ) + self.update = to_raw_response_wrapper( + stores.update, + ) + self.list = to_raw_response_wrapper( + stores.list, + ) + self.delete = to_raw_response_wrapper( + stores.delete, + ) + self.question_answering = to_raw_response_wrapper( + stores.question_answering, + ) + self.search = to_raw_response_wrapper( + stores.search, + ) + + @cached_property + def files(self) -> FilesResourceWithRawResponse: + return FilesResourceWithRawResponse(self._stores.files) + + +class AsyncStoresResourceWithRawResponse: + def __init__(self, stores: AsyncStoresResource) -> None: + self._stores = stores + + self.create = async_to_raw_response_wrapper( + stores.create, + ) + self.retrieve = async_to_raw_response_wrapper( + stores.retrieve, + ) + self.update = async_to_raw_response_wrapper( + stores.update, + ) + self.list = async_to_raw_response_wrapper( + stores.list, + ) + self.delete = async_to_raw_response_wrapper( + stores.delete, + ) + self.question_answering = async_to_raw_response_wrapper( + stores.question_answering, + ) + self.search = async_to_raw_response_wrapper( + stores.search, + ) + + @cached_property + def files(self) -> AsyncFilesResourceWithRawResponse: + return AsyncFilesResourceWithRawResponse(self._stores.files) + + +class StoresResourceWithStreamingResponse: + def __init__(self, stores: StoresResource) -> None: + self._stores = stores + + self.create = to_streamed_response_wrapper( + stores.create, + ) + self.retrieve = to_streamed_response_wrapper( + stores.retrieve, + ) + self.update = to_streamed_response_wrapper( + stores.update, + ) + self.list = to_streamed_response_wrapper( + stores.list, + ) + self.delete = to_streamed_response_wrapper( + stores.delete, + ) + self.question_answering = to_streamed_response_wrapper( + stores.question_answering, + ) + self.search = to_streamed_response_wrapper( + stores.search, + ) + + @cached_property + def files(self) -> FilesResourceWithStreamingResponse: + return FilesResourceWithStreamingResponse(self._stores.files) + + +class AsyncStoresResourceWithStreamingResponse: + def __init__(self, stores: AsyncStoresResource) -> None: + self._stores = stores + + self.create = async_to_streamed_response_wrapper( + stores.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + stores.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + stores.update, + ) + self.list = async_to_streamed_response_wrapper( + stores.list, + ) + self.delete = async_to_streamed_response_wrapper( + stores.delete, + ) + self.question_answering = async_to_streamed_response_wrapper( + stores.question_answering, + ) + self.search = async_to_streamed_response_wrapper( + stores.search, + ) + + @cached_property + def files(self) -> AsyncFilesResourceWithStreamingResponse: + return AsyncFilesResourceWithStreamingResponse(self._stores.files) diff --git a/src/mixedbread/resources/vector_stores/files.py b/src/mixedbread/resources/vector_stores/files.py index e3d1776e..215f0a23 100644 --- a/src/mixedbread/resources/vector_stores/files.py +++ b/src/mixedbread/resources/vector_stores/files.py @@ -5,7 +5,6 @@ import functools import typing_extensions from typing import Any, List, Union, Iterable, Optional -from typing_extensions import Literal import httpx @@ -22,6 +21,7 @@ ) from ..._base_client import make_request_options from ...types.vector_stores import file_list_params, file_create_params, file_search_params, file_retrieve_params +from ...types.stores.store_file_status import StoreFileStatus from ...types.vector_stores.vector_store_file import VectorStoreFile from ...types.vector_stores.file_list_response import FileListResponse from ...types.vector_stores.file_delete_response import FileDeleteResponse @@ -50,7 +50,7 @@ def with_streaming_response(self) -> FilesResourceWithStreamingResponse: """ return FilesResourceWithStreamingResponse(self) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use post stores.files instead") def create( self, vector_store_identifier: str, @@ -105,7 +105,7 @@ def create( cast_to=VectorStoreFile, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.files instead") def retrieve( self, file_id: str, @@ -155,7 +155,7 @@ def retrieve( cast_to=VectorStoreFile, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use post stores.files.list instead") def list( self, vector_store_identifier: str, @@ -164,7 +164,7 @@ def list( after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, include_total: bool | Omit = omit, - statuses: Optional[List[Literal["pending", "in_progress", "cancelled", "completed", "failed"]]] | Omit = omit, + statuses: Optional[List[StoreFileStatus]] | Omit = omit, metadata_filter: Optional[file_list_params.MetadataFilter] | 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. @@ -224,7 +224,7 @@ def list( cast_to=FileListResponse, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.files instead") def delete( self, file_id: str, @@ -267,7 +267,7 @@ def delete( cast_to=FileDeleteResponse, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.files.search instead") def search( self, *, @@ -454,7 +454,7 @@ def with_streaming_response(self) -> AsyncFilesResourceWithStreamingResponse: """ return AsyncFilesResourceWithStreamingResponse(self) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use post stores.files instead") async def create( self, vector_store_identifier: str, @@ -509,7 +509,7 @@ async def create( cast_to=VectorStoreFile, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.files instead") async def retrieve( self, file_id: str, @@ -561,7 +561,7 @@ async def retrieve( cast_to=VectorStoreFile, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use post stores.files.list instead") async def list( self, vector_store_identifier: str, @@ -570,7 +570,7 @@ async def list( after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, include_total: bool | Omit = omit, - statuses: Optional[List[Literal["pending", "in_progress", "cancelled", "completed", "failed"]]] | Omit = omit, + statuses: Optional[List[StoreFileStatus]] | Omit = omit, metadata_filter: Optional[file_list_params.MetadataFilter] | 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. @@ -630,7 +630,7 @@ async def list( cast_to=FileListResponse, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.files instead") async def delete( self, file_id: str, @@ -673,7 +673,7 @@ async def delete( cast_to=FileDeleteResponse, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.files.search instead") async def search( self, *, diff --git a/src/mixedbread/resources/vector_stores/vector_stores.py b/src/mixedbread/resources/vector_stores/vector_stores.py index 548ffe13..b8e3dd13 100644 --- a/src/mixedbread/resources/vector_stores/vector_stores.py +++ b/src/mixedbread/resources/vector_stores/vector_stores.py @@ -68,7 +68,7 @@ def with_streaming_response(self) -> VectorStoresResourceWithStreamingResponse: """ return VectorStoresResourceWithStreamingResponse(self) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") def create( self, *, @@ -128,7 +128,7 @@ def create( cast_to=VectorStore, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") def retrieve( self, vector_store_identifier: str, @@ -166,7 +166,7 @@ def retrieve( cast_to=VectorStore, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") def update( self, vector_store_identifier: str, @@ -229,7 +229,7 @@ def update( cast_to=VectorStore, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") def list( self, *, @@ -291,7 +291,7 @@ def list( model=VectorStore, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") def delete( self, vector_store_identifier: str, @@ -329,7 +329,7 @@ def delete( cast_to=VectorStoreDeleteResponse, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.question_answering instead") def question_answering( self, *, @@ -398,7 +398,7 @@ def question_answering( cast_to=VectorStoreQuestionAnsweringResponse, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.search instead") def search( self, *, @@ -483,7 +483,7 @@ def with_streaming_response(self) -> AsyncVectorStoresResourceWithStreamingRespo """ return AsyncVectorStoresResourceWithStreamingResponse(self) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") async def create( self, *, @@ -543,7 +543,7 @@ async def create( cast_to=VectorStore, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") async def retrieve( self, vector_store_identifier: str, @@ -581,7 +581,7 @@ async def retrieve( cast_to=VectorStore, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") async def update( self, vector_store_identifier: str, @@ -644,7 +644,7 @@ async def update( cast_to=VectorStore, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") def list( self, *, @@ -706,7 +706,7 @@ def list( model=VectorStore, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores instead") async def delete( self, vector_store_identifier: str, @@ -744,7 +744,7 @@ async def delete( cast_to=VectorStoreDeleteResponse, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.question_answering instead") async def question_answering( self, *, @@ -813,7 +813,7 @@ async def question_answering( cast_to=VectorStoreQuestionAnsweringResponse, ) - @typing_extensions.deprecated("deprecated") + @typing_extensions.deprecated("Use stores.search instead") async def search( self, *, diff --git a/src/mixedbread/types/__init__.py b/src/mixedbread/types/__init__.py index 87fe66f4..3ec3253b 100644 --- a/src/mixedbread/types/__init__.py +++ b/src/mixedbread/types/__init__.py @@ -4,6 +4,7 @@ from . import shared from .. import _compat +from .store import Store as Store from .shared import Usage as Usage, SearchFilter as SearchFilter, SearchFilterCondition as SearchFilterCondition from .api_key import APIKey as APIKey from .embedding import Embedding as Embedding @@ -18,18 +19,25 @@ from .rerank_response import RerankResponse as RerankResponse from .data_source_type import DataSourceType as DataSourceType from .file_list_params import FileListParams as FileListParams +from .store_list_params import StoreListParams as StoreListParams from .file_create_params import FileCreateParams as FileCreateParams from .file_update_params import FileUpdateParams as FileUpdateParams from .api_key_list_params import APIKeyListParams as APIKeyListParams from .client_embed_params import ClientEmbedParams as ClientEmbedParams from .expires_after_param import ExpiresAfterParam as ExpiresAfterParam +from .store_create_params import StoreCreateParams as StoreCreateParams +from .store_search_params import StoreSearchParams as StoreSearchParams +from .store_update_params import StoreUpdateParams as StoreUpdateParams from .client_rerank_params import ClientRerankParams as ClientRerankParams from .file_delete_response import FileDeleteResponse as FileDeleteResponse from .api_key_create_params import APIKeyCreateParams as APIKeyCreateParams from .pagination_with_total import PaginationWithTotal as PaginationWithTotal +from .store_delete_response import StoreDeleteResponse as StoreDeleteResponse +from .store_search_response import StoreSearchResponse as StoreSearchResponse from .api_key_delete_response import APIKeyDeleteResponse as APIKeyDeleteResponse from .data_source_list_params import DataSourceListParams as DataSourceListParams from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams +from .scored_text_input_chunk import ScoredTextInputChunk as ScoredTextInputChunk from .linear_data_source_param import LinearDataSourceParam as LinearDataSourceParam from .multi_encoding_embedding import MultiEncodingEmbedding as MultiEncodingEmbedding from .notion_data_source_param import NotionDataSourceParam as NotionDataSourceParam @@ -42,8 +50,14 @@ from .vector_store_search_params import VectorStoreSearchParams as VectorStoreSearchParams from .vector_store_update_params import VectorStoreUpdateParams as VectorStoreUpdateParams from .data_source_delete_response import DataSourceDeleteResponse as DataSourceDeleteResponse +from .scored_audio_url_input_chunk import ScoredAudioURLInputChunk as ScoredAudioURLInputChunk +from .scored_image_url_input_chunk import ScoredImageURLInputChunk as ScoredImageURLInputChunk +from .scored_video_url_input_chunk import ScoredVideoURLInputChunk as ScoredVideoURLInputChunk from .vector_store_delete_response import VectorStoreDeleteResponse as VectorStoreDeleteResponse from .vector_store_search_response import VectorStoreSearchResponse as VectorStoreSearchResponse +from .store_question_answering_params import StoreQuestionAnsweringParams as StoreQuestionAnsweringParams +from .store_chunk_search_options_param import StoreChunkSearchOptionsParam as StoreChunkSearchOptionsParam +from .store_question_answering_response import StoreQuestionAnsweringResponse as StoreQuestionAnsweringResponse from .vector_store_question_answering_params import ( VectorStoreQuestionAnsweringParams as VectorStoreQuestionAnsweringParams, ) diff --git a/src/mixedbread/types/scored_audio_url_input_chunk.py b/src/mixedbread/types/scored_audio_url_input_chunk.py new file mode 100644 index 00000000..0e749c30 --- /dev/null +++ b/src/mixedbread/types/scored_audio_url_input_chunk.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ScoredAudioURLInputChunk", "AudioURL"] + + +class AudioURL(BaseModel): + url: str + """The audio URL. Can be either a URL or a Data URI.""" + + +class ScoredAudioURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[Dict[str, object]] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + score: float + """score of the chunk""" + + file_id: str + """file id""" + + filename: str + """filename""" + + store_id: str + """store id""" + + metadata: Optional[object] = None + """file metadata""" + + type: Optional[Literal["audio_url"]] = None + """Input type identifier""" + + transcription: Optional[str] = None + """speech recognition (sr) text of the audio""" + + summary: Optional[str] = None + """summary of the audio""" + + audio_url: AudioURL + """The audio input specification.""" + + sampling_rate: int + """The sampling rate of the audio.""" diff --git a/src/mixedbread/types/scored_image_url_input_chunk.py b/src/mixedbread/types/scored_image_url_input_chunk.py new file mode 100644 index 00000000..b7a925fd --- /dev/null +++ b/src/mixedbread/types/scored_image_url_input_chunk.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ScoredImageURLInputChunk", "ImageURL"] + + +class ImageURL(BaseModel): + url: str + """The image URL. Can be either a URL or a Data URI.""" + + format: Optional[str] = None + """The image format/mimetype""" + + +class ScoredImageURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[Dict[str, object]] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + score: float + """score of the chunk""" + + file_id: str + """file id""" + + filename: str + """filename""" + + store_id: str + """store id""" + + metadata: Optional[object] = None + """file metadata""" + + type: Optional[Literal["image_url"]] = None + """Input type identifier""" + + ocr_text: Optional[str] = None + """ocr text of the image""" + + summary: Optional[str] = None + """summary of the image""" + + image_url: ImageURL + """The image input specification.""" diff --git a/src/mixedbread/types/scored_text_input_chunk.py b/src/mixedbread/types/scored_text_input_chunk.py new file mode 100644 index 00000000..6a120e42 --- /dev/null +++ b/src/mixedbread/types/scored_text_input_chunk.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ScoredTextInputChunk"] + + +class ScoredTextInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[Dict[str, object]] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + score: float + """score of the chunk""" + + file_id: str + """file id""" + + filename: str + """filename""" + + store_id: str + """store id""" + + metadata: Optional[object] = None + """file metadata""" + + type: Optional[Literal["text"]] = None + """Input type identifier""" + + offset: Optional[int] = None + """The offset of the text in the file relative to the start of the file.""" + + text: str + """Text content to process""" diff --git a/src/mixedbread/types/scored_video_url_input_chunk.py b/src/mixedbread/types/scored_video_url_input_chunk.py new file mode 100644 index 00000000..c31cb23c --- /dev/null +++ b/src/mixedbread/types/scored_video_url_input_chunk.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ScoredVideoURLInputChunk", "VideoURL"] + + +class VideoURL(BaseModel): + url: str + """The video URL. Can be either a URL or a Data URI.""" + + +class ScoredVideoURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[Dict[str, object]] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + score: float + """score of the chunk""" + + file_id: str + """file id""" + + filename: str + """filename""" + + store_id: str + """store id""" + + metadata: Optional[object] = None + """file metadata""" + + type: Optional[Literal["video_url"]] = None + """Input type identifier""" + + transcription: Optional[str] = None + """speech recognition (sr) text of the video""" + + summary: Optional[str] = None + """summary of the video""" + + video_url: VideoURL + """The video input specification.""" diff --git a/src/mixedbread/types/store.py b/src/mixedbread/types/store.py new file mode 100644 index 00000000..5c7db5d1 --- /dev/null +++ b/src/mixedbread/types/store.py @@ -0,0 +1,74 @@ +# 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 +from .expires_after import ExpiresAfter + +__all__ = ["Store", "FileCounts"] + + +class FileCounts(BaseModel): + pending: Optional[int] = None + """Number of files waiting to be processed""" + + in_progress: Optional[int] = None + """Number of files currently being processed""" + + cancelled: Optional[int] = None + """Number of files whose processing was cancelled""" + + completed: Optional[int] = None + """Number of successfully processed files""" + + failed: Optional[int] = None + """Number of files that failed processing""" + + total: Optional[int] = None + """Total number of files""" + + +class Store(BaseModel): + id: str + """Unique identifier for the store""" + + name: str + """Name of the store""" + + description: Optional[str] = None + """Detailed description of the store's purpose and contents""" + + is_public: Optional[bool] = None + """Whether the store can be accessed by anyone with valid login credentials""" + + metadata: Optional[object] = None + """Additional metadata associated with the store""" + + file_counts: Optional[FileCounts] = None + """Counts of files in different states""" + + expires_after: Optional[ExpiresAfter] = None + """Represents an expiration policy for a store.""" + + status: Optional[Literal["expired", "in_progress", "completed"]] = None + """Processing status of the store""" + + created_at: datetime + """Timestamp when the store was created""" + + updated_at: datetime + """Timestamp when the store was last updated""" + + last_active_at: Optional[datetime] = None + """Timestamp when the store was last used""" + + usage_bytes: Optional[int] = None + """Total storage usage in bytes""" + + expires_at: Optional[datetime] = None + """Optional expiration timestamp for the store""" + + object: Optional[Literal["store"]] = None + """Type of the object""" diff --git a/src/mixedbread/types/store_chunk_search_options_param.py b/src/mixedbread/types/store_chunk_search_options_param.py new file mode 100644 index 00000000..89c64712 --- /dev/null +++ b/src/mixedbread/types/store_chunk_search_options_param.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import TypeAlias, TypedDict + +from .vector_stores.rerank_config_param import RerankConfigParam + +__all__ = ["StoreChunkSearchOptionsParam", "Rerank"] + +Rerank: TypeAlias = Union[bool, RerankConfigParam] + + +class StoreChunkSearchOptionsParam(TypedDict, total=False): + score_threshold: float + """Minimum similarity score threshold""" + + rewrite_query: bool + """Whether to rewrite the query""" + + rerank: Optional[Rerank] + """Whether to rerank results and optional reranking configuration""" + + return_metadata: bool + """Whether to return file metadata""" + + apply_search_rules: bool + """Whether to apply search rules""" diff --git a/src/mixedbread/types/store_create_params.py b/src/mixedbread/types/store_create_params.py new file mode 100644 index 00000000..1c307c18 --- /dev/null +++ b/src/mixedbread/types/store_create_params.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +from .._types import SequenceNotStr +from .expires_after_param import ExpiresAfterParam + +__all__ = ["StoreCreateParams"] + + +class StoreCreateParams(TypedDict, total=False): + name: Optional[str] + """Name for the new store""" + + description: Optional[str] + """Description of the store""" + + is_public: bool + """Whether the store can be accessed by anyone with valid login credentials""" + + expires_after: Optional[ExpiresAfterParam] + """Represents an expiration policy for a store.""" + + metadata: object + """Optional metadata key-value pairs""" + + file_ids: Optional[SequenceNotStr[str]] + """Optional list of file IDs""" diff --git a/src/mixedbread/types/store_delete_response.py b/src/mixedbread/types/store_delete_response.py new file mode 100644 index 00000000..c35970ab --- /dev/null +++ b/src/mixedbread/types/store_delete_response.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["StoreDeleteResponse"] + + +class StoreDeleteResponse(BaseModel): + id: str + """ID of the deleted store""" + + deleted: bool + """Whether the deletion was successful""" + + object: Optional[Literal["store"]] = None + """Type of the deleted object""" diff --git a/src/mixedbread/types/store_list_params.py b/src/mixedbread/types/store_list_params.py new file mode 100644 index 00000000..addec411 --- /dev/null +++ b/src/mixedbread/types/store_list_params.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["StoreListParams"] + + +class StoreListParams(TypedDict, total=False): + limit: int + """Maximum number of items to return per page (1-100)""" + + after: Optional[str] + """Cursor for forward pagination - get items after this position. + + Use last_cursor from previous response. + """ + + before: Optional[str] + """Cursor for backward pagination - get items before this position. + + Use first_cursor from previous response. + """ + + include_total: bool + """Whether to include total count in response (expensive operation)""" + + q: Optional[str] + """Search query for fuzzy matching over name and description fields""" diff --git a/src/mixedbread/types/store_question_answering_params.py b/src/mixedbread/types/store_question_answering_params.py new file mode 100644 index 00000000..37366a6f --- /dev/null +++ b/src/mixedbread/types/store_question_answering_params.py @@ -0,0 +1,57 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .store_chunk_search_options_param import StoreChunkSearchOptionsParam +from .shared_params.search_filter_condition import SearchFilterCondition + +__all__ = ["StoreQuestionAnsweringParams", "Filters", "FiltersUnionMember2", "QaOptions"] + + +class StoreQuestionAnsweringParams(TypedDict, total=False): + query: str + """Question to answer. + + If not provided, the question will be extracted from the passed messages. + """ + + store_identifiers: Required[SequenceNotStr[str]] + """IDs or names of stores to search""" + + top_k: int + """Number of results to return""" + + filters: Optional[Filters] + """Optional filter conditions""" + + file_ids: Union[Iterable[object], SequenceNotStr[str], None] + """Optional list of file IDs to filter chunks by (inclusion filter)""" + + search_options: StoreChunkSearchOptionsParam + """Search configuration options""" + + stream: bool + """Whether to stream the answer""" + + qa_options: QaOptions + """Question answering configuration options""" + + +FiltersUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +Filters: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[FiltersUnionMember2]] + + +class QaOptions(TypedDict, total=False): + cite: bool + """Whether to use citations""" + + multimodal: bool + """Whether to use multimodal context""" + + +from .shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/store_question_answering_response.py b/src/mixedbread/types/store_question_answering_response.py new file mode 100644 index 00000000..f47f71b3 --- /dev/null +++ b/src/mixedbread/types/store_question_answering_response.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .scored_text_input_chunk import ScoredTextInputChunk +from .scored_audio_url_input_chunk import ScoredAudioURLInputChunk +from .scored_image_url_input_chunk import ScoredImageURLInputChunk +from .scored_video_url_input_chunk import ScoredVideoURLInputChunk + +__all__ = ["StoreQuestionAnsweringResponse", "Source"] + +Source: TypeAlias = Annotated[ + Union[ScoredTextInputChunk, ScoredImageURLInputChunk, ScoredAudioURLInputChunk, ScoredVideoURLInputChunk], + PropertyInfo(discriminator="type"), +] + + +class StoreQuestionAnsweringResponse(BaseModel): + answer: str + """The answer generated by the LLM""" + + sources: Optional[List[Source]] = None + """Source documents used to generate the answer""" diff --git a/src/mixedbread/types/store_search_params.py b/src/mixedbread/types/store_search_params.py new file mode 100644 index 00000000..1bff13de --- /dev/null +++ b/src/mixedbread/types/store_search_params.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Required, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .store_chunk_search_options_param import StoreChunkSearchOptionsParam +from .shared_params.search_filter_condition import SearchFilterCondition + +__all__ = ["StoreSearchParams", "Filters", "FiltersUnionMember2"] + + +class StoreSearchParams(TypedDict, total=False): + query: Required[str] + """Search query text""" + + store_identifiers: Required[SequenceNotStr[str]] + """IDs or names of stores to search""" + + top_k: int + """Number of results to return""" + + filters: Optional[Filters] + """Optional filter conditions""" + + file_ids: Union[Iterable[object], SequenceNotStr[str], None] + """Optional list of file IDs to filter chunks by (inclusion filter)""" + + search_options: StoreChunkSearchOptionsParam + """Search configuration options""" + + +FiltersUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +Filters: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[FiltersUnionMember2]] + +from .shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/store_search_response.py b/src/mixedbread/types/store_search_response.py new file mode 100644 index 00000000..6ab757c5 --- /dev/null +++ b/src/mixedbread/types/store_search_response.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .scored_text_input_chunk import ScoredTextInputChunk +from .scored_audio_url_input_chunk import ScoredAudioURLInputChunk +from .scored_image_url_input_chunk import ScoredImageURLInputChunk +from .scored_video_url_input_chunk import ScoredVideoURLInputChunk + +__all__ = ["StoreSearchResponse", "Data"] + +Data: TypeAlias = Annotated[ + Union[ScoredTextInputChunk, ScoredImageURLInputChunk, ScoredAudioURLInputChunk, ScoredVideoURLInputChunk], + PropertyInfo(discriminator="type"), +] + + +class StoreSearchResponse(BaseModel): + object: Optional[Literal["list"]] = None + """The object type of the response""" + + data: List[Data] + """The list of scored store file chunks""" diff --git a/src/mixedbread/types/store_update_params.py b/src/mixedbread/types/store_update_params.py new file mode 100644 index 00000000..6133eb5c --- /dev/null +++ b/src/mixedbread/types/store_update_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +from .expires_after_param import ExpiresAfterParam + +__all__ = ["StoreUpdateParams"] + + +class StoreUpdateParams(TypedDict, total=False): + name: Optional[str] + """New name for the store""" + + description: Optional[str] + """New description""" + + is_public: Optional[bool] + """Whether the store can be accessed by anyone with valid login credentials""" + + expires_after: Optional[ExpiresAfterParam] + """Represents an expiration policy for a store.""" + + metadata: object + """Optional metadata key-value pairs""" diff --git a/src/mixedbread/types/stores/__init__.py b/src/mixedbread/types/stores/__init__.py new file mode 100644 index 00000000..3dee7dac --- /dev/null +++ b/src/mixedbread/types/stores/__init__.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .store_file import StoreFile as StoreFile +from .file_list_params import FileListParams as FileListParams +from .scored_store_file import ScoredStoreFile as ScoredStoreFile +from .store_file_status import StoreFileStatus as StoreFileStatus +from .file_create_params import FileCreateParams as FileCreateParams +from .file_list_response import FileListResponse as FileListResponse +from .file_search_params import FileSearchParams as FileSearchParams +from .file_delete_response import FileDeleteResponse as FileDeleteResponse +from .file_retrieve_params import FileRetrieveParams as FileRetrieveParams +from .file_search_response import FileSearchResponse as FileSearchResponse diff --git a/src/mixedbread/types/stores/file_create_params.py b/src/mixedbread/types/stores/file_create_params.py new file mode 100644 index 00000000..ce39cfd1 --- /dev/null +++ b/src/mixedbread/types/stores/file_create_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["FileCreateParams", "Experimental"] + + +class FileCreateParams(TypedDict, total=False): + metadata: object + """Optional metadata for the file""" + + experimental: Experimental + """Strategy for adding the file""" + + file_id: Required[str] + """ID of the file to add""" + + +class Experimental(TypedDict, total=False): + parsing_strategy: Literal["fast", "high_quality"] + """Strategy for adding the file""" + + contextualization: bool + """Whether to contextualize the file""" diff --git a/src/mixedbread/types/stores/file_delete_response.py b/src/mixedbread/types/stores/file_delete_response.py new file mode 100644 index 00000000..ada45e9c --- /dev/null +++ b/src/mixedbread/types/stores/file_delete_response.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FileDeleteResponse"] + + +class FileDeleteResponse(BaseModel): + id: str + """ID of the deleted file""" + + deleted: Optional[bool] = None + """Whether the deletion was successful""" + + object: Optional[Literal["store.file"]] = None + """Type of the deleted object""" diff --git a/src/mixedbread/types/stores/file_list_params.py b/src/mixedbread/types/stores/file_list_params.py new file mode 100644 index 00000000..27edd05e --- /dev/null +++ b/src/mixedbread/types/stores/file_list_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Iterable, Optional +from typing_extensions import TypeAlias, TypedDict + +from .store_file_status import StoreFileStatus +from ..shared_params.search_filter_condition import SearchFilterCondition + +__all__ = ["FileListParams", "MetadataFilter", "MetadataFilterUnionMember2"] + + +class FileListParams(TypedDict, total=False): + limit: int + """Maximum number of items to return per page (1-100)""" + + after: Optional[str] + """Cursor for forward pagination - get items after this position. + + Use last_cursor from previous response. + """ + + before: Optional[str] + """Cursor for backward pagination - get items before this position. + + Use first_cursor from previous response. + """ + + include_total: bool + """Whether to include total count in response (expensive operation)""" + + statuses: Optional[List[StoreFileStatus]] + """Status to filter by""" + + metadata_filter: Optional[MetadataFilter] + """Metadata filter to apply to the query""" + + +MetadataFilterUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +MetadataFilter: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[MetadataFilterUnionMember2]] + +from ..shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/stores/file_list_response.py b/src/mixedbread/types/stores/file_list_response.py new file mode 100644 index 00000000..2d47abcc --- /dev/null +++ b/src/mixedbread/types/stores/file_list_response.py @@ -0,0 +1,48 @@ +# 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 .store_file import StoreFile + +__all__ = ["FileListResponse", "Pagination"] + + +class Pagination(BaseModel): + has_more: bool + """ + Contextual direction-aware flag: True if more items exist in the requested + pagination direction. For 'after': more items after this page. For 'before': + more items before this page. + """ + + first_cursor: Optional[str] = None + """Cursor of the first item in this page. + + Use for backward pagination. None if page is empty. + """ + + last_cursor: Optional[str] = None + """Cursor of the last item in this page. + + Use for forward pagination. None if page is empty. + """ + + total: Optional[int] = None + """Total number of items available across all pages. + + Only included when include_total=true was requested. Expensive operation - use + sparingly. + """ + + +class FileListResponse(BaseModel): + pagination: Pagination + """Response model for cursor-based pagination.""" + + object: Optional[Literal["list"]] = None + """The object type of the response""" + + data: List[StoreFile] + """The list of store files""" diff --git a/src/mixedbread/types/stores/file_retrieve_params.py b/src/mixedbread/types/stores/file_retrieve_params.py new file mode 100644 index 00000000..ae924454 --- /dev/null +++ b/src/mixedbread/types/stores/file_retrieve_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["FileRetrieveParams"] + + +class FileRetrieveParams(TypedDict, total=False): + store_identifier: Required[str] + """The ID or name of the store""" + + return_chunks: bool + """Whether to return the chunks for the file""" diff --git a/src/mixedbread/types/stores/file_search_params.py b/src/mixedbread/types/stores/file_search_params.py new file mode 100644 index 00000000..8558e0c8 --- /dev/null +++ b/src/mixedbread/types/stores/file_search_params.py @@ -0,0 +1,65 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Required, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from ..vector_stores.rerank_config_param import RerankConfigParam +from ..shared_params.search_filter_condition import SearchFilterCondition + +__all__ = ["FileSearchParams", "Filters", "FiltersUnionMember2", "SearchOptions", "SearchOptionsRerank"] + + +class FileSearchParams(TypedDict, total=False): + query: Required[str] + """Search query text""" + + store_identifiers: Required[SequenceNotStr[str]] + """IDs or names of stores to search""" + + top_k: int + """Number of results to return""" + + filters: Optional[Filters] + """Optional filter conditions""" + + file_ids: Union[Iterable[object], SequenceNotStr[str], None] + """Optional list of file IDs to filter chunks by (inclusion filter)""" + + search_options: SearchOptions + """Search configuration options""" + + +FiltersUnionMember2: TypeAlias = Union["SearchFilter", SearchFilterCondition] + +Filters: TypeAlias = Union["SearchFilter", SearchFilterCondition, Iterable[FiltersUnionMember2]] + +SearchOptionsRerank: TypeAlias = Union[bool, RerankConfigParam] + + +class SearchOptions(TypedDict, total=False): + score_threshold: float + """Minimum similarity score threshold""" + + rewrite_query: bool + """Whether to rewrite the query""" + + rerank: Optional[SearchOptionsRerank] + """Whether to rerank results and optional reranking configuration""" + + return_metadata: bool + """Whether to return file metadata""" + + return_chunks: bool + """Whether to return matching text chunks""" + + chunks_per_file: int + """Number of chunks to return for each file""" + + apply_search_rules: bool + """Whether to apply search rules""" + + +from ..shared_params.search_filter import SearchFilter diff --git a/src/mixedbread/types/stores/file_search_response.py b/src/mixedbread/types/stores/file_search_response.py new file mode 100644 index 00000000..304512e4 --- /dev/null +++ b/src/mixedbread/types/stores/file_search_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .scored_store_file import ScoredStoreFile + +__all__ = ["FileSearchResponse"] + + +class FileSearchResponse(BaseModel): + object: Optional[Literal["list"]] = None + """The object type of the response""" + + data: List[ScoredStoreFile] + """The list of scored store files""" diff --git a/src/mixedbread/types/stores/scored_store_file.py b/src/mixedbread/types/stores/scored_store_file.py new file mode 100644 index 00000000..24aafbbe --- /dev/null +++ b/src/mixedbread/types/stores/scored_store_file.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .store_file_status import StoreFileStatus +from ..scored_text_input_chunk import ScoredTextInputChunk +from ..scored_audio_url_input_chunk import ScoredAudioURLInputChunk +from ..scored_image_url_input_chunk import ScoredImageURLInputChunk +from ..scored_video_url_input_chunk import ScoredVideoURLInputChunk + +__all__ = ["ScoredStoreFile", "Chunk"] + +Chunk: TypeAlias = Annotated[ + Union[ScoredTextInputChunk, ScoredImageURLInputChunk, ScoredAudioURLInputChunk, ScoredVideoURLInputChunk], + PropertyInfo(discriminator="type"), +] + + +class ScoredStoreFile(BaseModel): + id: str + """Unique identifier for the file""" + + filename: Optional[str] = None + """Name of the file""" + + metadata: Optional[object] = None + """Optional file metadata""" + + status: Optional[StoreFileStatus] = None + """Processing status of the file""" + + last_error: Optional[object] = None + """Last error message if processing failed""" + + store_id: str + """ID of the containing store""" + + created_at: datetime + """Timestamp of store file creation""" + + version: Optional[int] = None + """Version number of the file""" + + usage_bytes: Optional[int] = None + """Storage usage in bytes""" + + object: Optional[Literal["store.file"]] = None + """Type of the object""" + + chunks: Optional[List[Chunk]] = None + """Array of scored file chunks""" + + score: float + """score of the file""" diff --git a/src/mixedbread/types/stores/store_file.py b/src/mixedbread/types/stores/store_file.py new file mode 100644 index 00000000..75d385cb --- /dev/null +++ b/src/mixedbread/types/stores/store_file.py @@ -0,0 +1,184 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .store_file_status import StoreFileStatus + +__all__ = [ + "StoreFile", + "Chunk", + "ChunkTextInputChunk", + "ChunkImageURLInputChunk", + "ChunkImageURLInputChunkImageURL", + "ChunkAudioURLInputChunk", + "ChunkAudioURLInputChunkAudioURL", + "ChunkVideoURLInputChunk", + "ChunkVideoURLInputChunkVideoURL", +] + + +class ChunkTextInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[Dict[str, object]] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + type: Optional[Literal["text"]] = None + """Input type identifier""" + + offset: Optional[int] = None + """The offset of the text in the file relative to the start of the file.""" + + text: str + """Text content to process""" + + +class ChunkImageURLInputChunkImageURL(BaseModel): + url: str + """The image URL. Can be either a URL or a Data URI.""" + + format: Optional[str] = None + """The image format/mimetype""" + + +class ChunkImageURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[Dict[str, object]] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + type: Optional[Literal["image_url"]] = None + """Input type identifier""" + + ocr_text: Optional[str] = None + """ocr text of the image""" + + summary: Optional[str] = None + """summary of the image""" + + image_url: ChunkImageURLInputChunkImageURL + """The image input specification.""" + + +class ChunkAudioURLInputChunkAudioURL(BaseModel): + url: str + """The audio URL. Can be either a URL or a Data URI.""" + + +class ChunkAudioURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[Dict[str, object]] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + type: Optional[Literal["audio_url"]] = None + """Input type identifier""" + + transcription: Optional[str] = None + """speech recognition (sr) text of the audio""" + + summary: Optional[str] = None + """summary of the audio""" + + audio_url: ChunkAudioURLInputChunkAudioURL + """The audio input specification.""" + + sampling_rate: int + """The sampling rate of the audio.""" + + +class ChunkVideoURLInputChunkVideoURL(BaseModel): + url: str + """The video URL. Can be either a URL or a Data URI.""" + + +class ChunkVideoURLInputChunk(BaseModel): + chunk_index: int + """position of the chunk in a file""" + + mime_type: Optional[str] = None + """mime type of the chunk""" + + generated_metadata: Optional[Dict[str, object]] = None + """metadata of the chunk""" + + model: Optional[str] = None + """model used for this chunk""" + + type: Optional[Literal["video_url"]] = None + """Input type identifier""" + + transcription: Optional[str] = None + """speech recognition (sr) text of the video""" + + summary: Optional[str] = None + """summary of the video""" + + video_url: ChunkVideoURLInputChunkVideoURL + """The video input specification.""" + + +Chunk: TypeAlias = Annotated[ + Union[ChunkTextInputChunk, ChunkImageURLInputChunk, ChunkAudioURLInputChunk, ChunkVideoURLInputChunk], + PropertyInfo(discriminator="type"), +] + + +class StoreFile(BaseModel): + id: str + """Unique identifier for the file""" + + filename: Optional[str] = None + """Name of the file""" + + metadata: Optional[object] = None + """Optional file metadata""" + + status: Optional[StoreFileStatus] = None + """Processing status of the file""" + + last_error: Optional[object] = None + """Last error message if processing failed""" + + store_id: str + """ID of the containing store""" + + created_at: datetime + """Timestamp of store file creation""" + + version: Optional[int] = None + """Version number of the file""" + + usage_bytes: Optional[int] = None + """Storage usage in bytes""" + + object: Optional[Literal["store.file"]] = None + """Type of the object""" + + chunks: Optional[List[Chunk]] = None + """chunks""" diff --git a/src/mixedbread/types/stores/store_file_status.py b/src/mixedbread/types/stores/store_file_status.py new file mode 100644 index 00000000..a0061477 --- /dev/null +++ b/src/mixedbread/types/stores/store_file_status.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["StoreFileStatus"] + +StoreFileStatus: TypeAlias = Literal["pending", "in_progress", "cancelled", "completed", "failed"] diff --git a/src/mixedbread/types/vector_stores/file_list_params.py b/src/mixedbread/types/vector_stores/file_list_params.py index 9f89685c..ef2c2848 100644 --- a/src/mixedbread/types/vector_stores/file_list_params.py +++ b/src/mixedbread/types/vector_stores/file_list_params.py @@ -3,8 +3,9 @@ from __future__ import annotations from typing import List, Union, Iterable, Optional -from typing_extensions import Literal, TypeAlias, TypedDict +from typing_extensions import TypeAlias, TypedDict +from ..stores.store_file_status import StoreFileStatus from ..shared_params.search_filter_condition import SearchFilterCondition __all__ = ["FileListParams", "MetadataFilter", "MetadataFilterUnionMember2"] @@ -29,7 +30,7 @@ class FileListParams(TypedDict, total=False): include_total: bool """Whether to include total count in response (expensive operation)""" - statuses: Optional[List[Literal["pending", "in_progress", "cancelled", "completed", "failed"]]] + statuses: Optional[List[StoreFileStatus]] """Status to filter by""" metadata_filter: Optional[MetadataFilter] diff --git a/src/mixedbread/types/vector_stores/scored_vector_store_file.py b/src/mixedbread/types/vector_stores/scored_vector_store_file.py index 247d7d39..7300999c 100644 --- a/src/mixedbread/types/vector_stores/scored_vector_store_file.py +++ b/src/mixedbread/types/vector_stores/scored_vector_store_file.py @@ -6,6 +6,7 @@ from ..._utils import PropertyInfo from ..._models import BaseModel +from ..stores.store_file_status import StoreFileStatus __all__ = [ "ScoredVectorStoreFile", @@ -223,7 +224,7 @@ class ScoredVectorStoreFile(BaseModel): metadata: Optional[object] = None """Optional file metadata""" - status: Optional[Literal["pending", "in_progress", "cancelled", "completed", "failed"]] = None + status: Optional[StoreFileStatus] = None """Processing status of the file""" last_error: Optional[object] = None diff --git a/src/mixedbread/types/vector_stores/vector_store_file.py b/src/mixedbread/types/vector_stores/vector_store_file.py index 89f64d8a..9b0713fc 100644 --- a/src/mixedbread/types/vector_stores/vector_store_file.py +++ b/src/mixedbread/types/vector_stores/vector_store_file.py @@ -6,6 +6,7 @@ from ..._utils import PropertyInfo from ..._models import BaseModel +from ..stores.store_file_status import StoreFileStatus __all__ = [ "VectorStoreFile", @@ -158,7 +159,7 @@ class VectorStoreFile(BaseModel): metadata: Optional[object] = None """Optional file metadata""" - status: Optional[Literal["pending", "in_progress", "cancelled", "completed", "failed"]] = None + status: Optional[StoreFileStatus] = None """Processing status of the file""" last_error: Optional[object] = None diff --git a/tests/api_resources/stores/__init__.py b/tests/api_resources/stores/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/stores/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/stores/test_files.py b/tests/api_resources/stores/test_files.py new file mode 100644 index 00000000..66797c82 --- /dev/null +++ b/tests/api_resources/stores/test_files.py @@ -0,0 +1,707 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.types.stores import ( + StoreFile, + FileListResponse, + FileDeleteResponse, + FileSearchResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestFiles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Mixedbread) -> None: + file = client.stores.files.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.create( + store_identifier="store_identifier", + metadata={}, + experimental={ + "parsing_strategy": "fast", + "contextualization": True, + }, + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.create( + store_identifier="", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + def test_method_retrieve(self, client: Mixedbread) -> None: + file = client.stores.files.retrieve( + file_id="file_id", + store_identifier="store_identifier", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.retrieve( + file_id="file_id", + store_identifier="store_identifier", + return_chunks=True, + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.retrieve( + file_id="file_id", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.retrieve( + file_id="file_id", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.retrieve( + file_id="file_id", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.stores.files.with_raw_response.retrieve( + file_id="", + store_identifier="store_identifier", + ) + + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + file = client.stores.files.list( + store_identifier="store_identifier", + ) + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.list( + store_identifier="store_identifier", + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + statuses=["pending"], + metadata_filter={ + "all": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "any": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "none": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + }, + ) + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.list( + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.list( + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileListResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.list( + store_identifier="", + ) + + @parametrize + def test_method_delete(self, client: Mixedbread) -> None: + file = client.stores.files.delete( + file_id="file_id", + store_identifier="store_identifier", + ) + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.delete( + file_id="file_id", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.delete( + file_id="file_id", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.files.with_raw_response.delete( + file_id="file_id", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.stores.files.with_raw_response.delete( + file_id="", + store_identifier="store_identifier", + ) + + @parametrize + def test_method_search(self, client: Mixedbread) -> None: + file = client.stores.files.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + def test_method_search_with_all_params(self, client: Mixedbread) -> None: + file = client.stores.files.search( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "any": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "none": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "return_metadata": True, + "return_chunks": True, + "chunks_per_file": 0, + "apply_search_rules": True, + }, + ) + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + def test_raw_response_search(self, client: Mixedbread) -> None: + response = client.stores.files.with_raw_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + def test_streaming_response_search(self, client: Mixedbread) -> None: + with client.stores.files.with_streaming_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileSearchResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncFiles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.create( + store_identifier="store_identifier", + metadata={}, + experimental={ + "parsing_strategy": "fast", + "contextualization": True, + }, + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.create( + store_identifier="store_identifier", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.create( + store_identifier="", + file_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.retrieve( + file_id="file_id", + store_identifier="store_identifier", + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.retrieve( + file_id="file_id", + store_identifier="store_identifier", + return_chunks=True, + ) + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.retrieve( + file_id="file_id", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.retrieve( + file_id="file_id", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(StoreFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.retrieve( + file_id="file_id", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.stores.files.with_raw_response.retrieve( + file_id="", + store_identifier="store_identifier", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.list( + store_identifier="store_identifier", + ) + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.list( + store_identifier="store_identifier", + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + statuses=["pending"], + metadata_filter={ + "all": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "any": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "none": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + }, + ) + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.list( + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(FileListResponse, file, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.list( + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileListResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.list( + store_identifier="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.delete( + file_id="file_id", + store_identifier="store_identifier", + ) + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.delete( + file_id="file_id", + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.delete( + file_id="file_id", + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.files.with_raw_response.delete( + file_id="file_id", + store_identifier="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.stores.files.with_raw_response.delete( + file_id="", + store_identifier="store_identifier", + ) + + @parametrize + async def test_method_search(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + async def test_method_search_with_all_params(self, async_client: AsyncMixedbread) -> None: + file = await async_client.stores.files.search( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "any": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "none": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "return_metadata": True, + "return_chunks": True, + "chunks_per_file": 0, + "apply_search_rules": True, + }, + ) + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + async def test_raw_response_search(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.files.with_raw_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(FileSearchResponse, file, path=["response"]) + + @parametrize + async def test_streaming_response_search(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.files.with_streaming_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileSearchResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_stores.py b/tests/api_resources/test_stores.py new file mode 100644 index 00000000..180773e7 --- /dev/null +++ b/tests/api_resources/test_stores.py @@ -0,0 +1,800 @@ +# 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 mixedbread import Mixedbread, AsyncMixedbread +from tests.utils import assert_matches_type +from mixedbread.types import ( + Store, + StoreDeleteResponse, + StoreSearchResponse, + StoreQuestionAnsweringResponse, +) +from mixedbread.pagination import SyncCursor, AsyncCursor + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestStores: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Mixedbread) -> None: + store = client.stores.create() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.create( + name="Technical Documentation", + description="Contains technical specifications and guides", + is_public=False, + expires_after={ + "anchor": "last_active_at", + "days": 0, + }, + metadata={}, + file_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Mixedbread) -> None: + store = client.stores.retrieve( + "store_identifier", + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.retrieve( + "store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.retrieve( + "store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Mixedbread) -> None: + store = client.stores.update( + store_identifier="store_identifier", + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.update( + store_identifier="store_identifier", + name="x", + description="description", + is_public=True, + expires_after={ + "anchor": "last_active_at", + "days": 0, + }, + metadata={}, + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.update( + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.update( + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.with_raw_response.update( + store_identifier="", + ) + + @parametrize + def test_method_list(self, client: Mixedbread) -> None: + store = client.stores.list() + assert_matches_type(SyncCursor[Store], store, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.list( + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + q="x", + ) + assert_matches_type(SyncCursor[Store], store, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(SyncCursor[Store], store, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(SyncCursor[Store], store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Mixedbread) -> None: + store = client.stores.delete( + "store_identifier", + ) + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.delete( + "store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.delete( + "store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Mixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + client.stores.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_question_answering(self, client: Mixedbread) -> None: + store = client.stores.question_answering( + store_identifiers=["string"], + ) + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + def test_method_question_answering_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.question_answering( + query="x", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "any": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "none": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "return_metadata": True, + "apply_search_rules": True, + }, + stream=True, + qa_options={ + "cite": True, + "multimodal": True, + }, + ) + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + def test_raw_response_question_answering(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.question_answering( + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + def test_streaming_response_question_answering(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.question_answering( + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_search(self, client: Mixedbread) -> None: + store = client.stores.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + def test_method_search_with_all_params(self, client: Mixedbread) -> None: + store = client.stores.search( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "any": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "none": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "return_metadata": True, + "apply_search_rules": True, + }, + ) + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + def test_raw_response_search(self, client: Mixedbread) -> None: + response = client.stores.with_raw_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = response.parse() + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + def test_streaming_response_search(self, client: Mixedbread) -> None: + with client.stores.with_streaming_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = response.parse() + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncStores: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.create() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.create( + name="Technical Documentation", + description="Contains technical specifications and guides", + is_public=False, + expires_after={ + "anchor": "last_active_at", + "days": 0, + }, + metadata={}, + file_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.retrieve( + "store_identifier", + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.retrieve( + "store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.retrieve( + "store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.update( + store_identifier="store_identifier", + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.update( + store_identifier="store_identifier", + name="x", + description="description", + is_public=True, + expires_after={ + "anchor": "last_active_at", + "days": 0, + }, + metadata={}, + ) + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.update( + store_identifier="store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.update( + store_identifier="store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(Store, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.with_raw_response.update( + store_identifier="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.list() + assert_matches_type(AsyncCursor[Store], store, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.list( + limit=10, + after="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + before="eyJjcmVhdGVkX2F0IjoiMjAyNC0xMi0zMVQyMzo1OTo1OS4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", + include_total=False, + q="x", + ) + assert_matches_type(AsyncCursor[Store], store, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(AsyncCursor[Store], store, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(AsyncCursor[Store], store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.delete( + "store_identifier", + ) + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.delete( + "store_identifier", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.delete( + "store_identifier", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(StoreDeleteResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncMixedbread) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `store_identifier` but received ''"): + await async_client.stores.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_question_answering(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.question_answering( + store_identifiers=["string"], + ) + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + async def test_method_question_answering_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.question_answering( + query="x", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "any": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "none": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "return_metadata": True, + "apply_search_rules": True, + }, + stream=True, + qa_options={ + "cite": True, + "multimodal": True, + }, + ) + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + async def test_raw_response_question_answering(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.question_answering( + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + @parametrize + async def test_streaming_response_question_answering(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.question_answering( + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(StoreQuestionAnsweringResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_search(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + async def test_method_search_with_all_params(self, async_client: AsyncMixedbread) -> None: + store = await async_client.stores.search( + query="how to configure SSL", + store_identifiers=["string"], + top_k=1, + filters={ + "all": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "any": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + "none": [ + { + "key": "price", + "value": "100", + "operator": "gt", + }, + { + "key": "color", + "value": "red", + "operator": "eq", + }, + ], + }, + file_ids=["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + search_options={ + "score_threshold": 0, + "rewrite_query": True, + "rerank": True, + "return_metadata": True, + "apply_search_rules": True, + }, + ) + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + async def test_raw_response_search(self, async_client: AsyncMixedbread) -> None: + response = await async_client.stores.with_raw_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + store = await response.parse() + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + @parametrize + async def test_streaming_response_search(self, async_client: AsyncMixedbread) -> None: + async with async_client.stores.with_streaming_response.search( + query="how to configure SSL", + store_identifiers=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + store = await response.parse() + assert_matches_type(StoreSearchResponse, store, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/test_client.py b/tests/test_client.py index 177f226f..ef2e38b3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -732,20 +732,20 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("mixedbread._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: Mixedbread) -> None: - respx_mock.post("/v1/vector_stores").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/v1/stores").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.vector_stores.with_streaming_response.create().__enter__() + client.stores.with_streaming_response.create().__enter__() assert _get_open_connections(self.client) == 0 @mock.patch("mixedbread._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: Mixedbread) -> None: - respx_mock.post("/v1/vector_stores").mock(return_value=httpx.Response(500)) + respx_mock.post("/v1/stores").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.vector_stores.with_streaming_response.create().__enter__() + client.stores.with_streaming_response.create().__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -772,9 +772,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/vector_stores").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = client.vector_stores.with_raw_response.create() + response = client.stores.with_raw_response.create() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -796,9 +796,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/vector_stores").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = client.vector_stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) + response = client.stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -819,9 +819,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/vector_stores").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = client.vector_stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) + response = client.stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1559,10 +1559,10 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte async def test_retrying_timeout_errors_doesnt_leak( self, respx_mock: MockRouter, async_client: AsyncMixedbread ) -> None: - respx_mock.post("/v1/vector_stores").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/v1/stores").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.vector_stores.with_streaming_response.create().__aenter__() + await async_client.stores.with_streaming_response.create().__aenter__() assert _get_open_connections(self.client) == 0 @@ -1571,10 +1571,10 @@ async def test_retrying_timeout_errors_doesnt_leak( async def test_retrying_status_errors_doesnt_leak( self, respx_mock: MockRouter, async_client: AsyncMixedbread ) -> None: - respx_mock.post("/v1/vector_stores").mock(return_value=httpx.Response(500)) + respx_mock.post("/v1/stores").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.vector_stores.with_streaming_response.create().__aenter__() + await async_client.stores.with_streaming_response.create().__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1602,9 +1602,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/vector_stores").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = await client.vector_stores.with_raw_response.create() + response = await client.stores.with_raw_response.create() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1627,11 +1627,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/vector_stores").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = await client.vector_stores.with_raw_response.create( - extra_headers={"x-stainless-retry-count": Omit()} - ) + response = await client.stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1653,9 +1651,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/vector_stores").mock(side_effect=retry_handler) + respx_mock.post("/v1/stores").mock(side_effect=retry_handler) - response = await client.vector_stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) + response = await client.stores.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42"