Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions src/openai/lib/_vector_stores.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

import math
import time
from typing import TYPE_CHECKING, Mapping
from typing_extensions import assert_never

from .._types import Omit
from .._types import Omit, omit
from .._utils import is_given
from ..types.vector_stores.vector_store_file import VectorStoreFile
from ..types.vector_stores.vector_store_file_batch import VectorStoreFileBatch
Expand All @@ -21,19 +23,37 @@ def _get_poll_interval_ms(headers: Mapping[str, str]) -> int:
return 1000


def validate_max_wait_seconds(max_wait_seconds: float | Omit) -> None:
if is_given(max_wait_seconds) and (not math.isfinite(max_wait_seconds) or max_wait_seconds < 0):
raise ValueError("Expected a finite, non-negative value for `max_wait_seconds`")


def _remaining_poll_time(deadline: float, file_id: str) -> float:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError(f"Timed out waiting for vector store file {file_id!r} to finish processing")
return remaining


def poll_vector_store_file(
resource: Files,
file_id: str,
*,
vector_store_id: str,
poll_interval_ms: int | Omit,
max_wait_seconds: float | Omit = omit,
) -> VectorStoreFile:
"""Poll a vector-store file using the caller's resource hooks."""
validate_max_wait_seconds(max_wait_seconds)
deadline = time.monotonic() + max_wait_seconds if is_given(max_wait_seconds) else None
headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"}
if is_given(poll_interval_ms):
headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms)

while True:
if deadline is not None:
_remaining_poll_time(deadline, file_id)

response = resource.with_raw_response.retrieve(
file_id,
vector_store_id=vector_store_id,
Expand All @@ -45,7 +65,10 @@ def poll_vector_store_file(
if not is_given(poll_interval_ms):
poll_interval_ms = _get_poll_interval_ms(response.headers)

resource._sleep(poll_interval_ms / 1000)
sleep_seconds = poll_interval_ms / 1000
if deadline is not None:
sleep_seconds = min(sleep_seconds, _remaining_poll_time(deadline, file_id))
resource._sleep(sleep_seconds)
elif file.status == "cancelled" or file.status == "completed" or file.status == "failed":
return file
else:
Expand All @@ -61,13 +84,19 @@ async def async_poll_vector_store_file(
*,
vector_store_id: str,
poll_interval_ms: int | Omit,
max_wait_seconds: float | Omit = omit,
) -> VectorStoreFile:
"""Poll a vector-store file using the caller's async resource hooks."""
validate_max_wait_seconds(max_wait_seconds)
deadline = time.monotonic() + max_wait_seconds if is_given(max_wait_seconds) else None
headers: dict[str, str] = {"X-Stainless-Poll-Helper": "true"}
if is_given(poll_interval_ms):
headers["X-Stainless-Custom-Poll-Interval"] = str(poll_interval_ms)

while True:
if deadline is not None:
_remaining_poll_time(deadline, file_id)

response = await resource.with_raw_response.retrieve(
file_id,
vector_store_id=vector_store_id,
Expand All @@ -79,7 +108,10 @@ async def async_poll_vector_store_file(
if not is_given(poll_interval_ms):
poll_interval_ms = _get_poll_interval_ms(response.headers)

await resource._sleep(poll_interval_ms / 1000)
sleep_seconds = poll_interval_ms / 1000
if deadline is not None:
sleep_seconds = min(sleep_seconds, _remaining_poll_time(deadline, file_id))
await resource._sleep(sleep_seconds)
elif file.status == "cancelled" or file.status == "completed" or file.status == "failed":
return file
else:
Expand Down
49 changes: 45 additions & 4 deletions src/openai/resources/vector_stores/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ..._base_client import AsyncPaginator, make_request_options
from ...lib._vector_stores import (
poll_vector_store_file as _poll_vector_store_file,
validate_max_wait_seconds as _validate_max_wait_seconds,
async_poll_vector_store_file as _async_poll_vector_store_file,
)
from ...types.vector_stores import file_list_params, file_create_params, file_update_params
Expand Down Expand Up @@ -335,6 +336,7 @@ def create_and_poll(
vector_store_id: str,
attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit,
poll_interval_ms: int | Omit = omit,
max_wait_seconds: float | Omit = omit,
chunking_strategy: FileChunkingStrategyParam | 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.
Expand All @@ -343,7 +345,11 @@ def create_and_poll(
extra_body: Body | None = None,
timeout: float | httpx2.Timeout | None | NotGiven = not_given,
) -> VectorStoreFile:
"""Attach a file to the given vector store and wait for it to be processed."""
"""Attach a file to the given vector store and wait for it to be processed.
`max_wait_seconds` limits polling after attachment; see `poll` for details.
"""
_validate_max_wait_seconds(max_wait_seconds)
self.create(
vector_store_id=vector_store_id,
file_id=file_id,
Expand All @@ -359,6 +365,7 @@ def create_and_poll(
file_id,
vector_store_id=vector_store_id,
poll_interval_ms=poll_interval_ms,
max_wait_seconds=max_wait_seconds,
)

def poll(
Expand All @@ -367,9 +374,16 @@ def poll(
*,
vector_store_id: str,
poll_interval_ms: int | Omit = omit,
max_wait_seconds: float | Omit = omit,
) -> VectorStoreFile:
"""Wait for the vector store file to finish processing.
`max_wait_seconds` must be finite and non-negative. When provided, it limits
polling and raises `TimeoutError` on expiry; zero prevents any polling requests.
Omit it to wait indefinitely. In-flight requests and retries use the client's
HTTP timeout settings and may finish after the polling deadline. Timing out
does not cancel processing on the server.
Note: this will return even if the file failed to process, you need to check
file.last_error and file.status to handle these cases
"""
Expand All @@ -378,6 +392,7 @@ def poll(
file_id,
vector_store_id=vector_store_id,
poll_interval_ms=poll_interval_ms,
max_wait_seconds=max_wait_seconds,
)

def upload(
Expand All @@ -402,15 +417,21 @@ def upload_and_poll(
file: FileTypes,
attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit,
poll_interval_ms: int | Omit = omit,
max_wait_seconds: float | Omit = omit,
chunking_strategy: FileChunkingStrategyParam | Omit = omit,
) -> VectorStoreFile:
"""Add a file to a vector store and poll until processing is complete."""
"""Add a file to a vector store and poll until processing is complete.
`max_wait_seconds` limits polling after upload and attachment; see `poll` for details.
"""
_validate_max_wait_seconds(max_wait_seconds)
file_obj = self._client.files.create(file=file, purpose="assistants")
return self.create_and_poll(
vector_store_id=vector_store_id,
file_id=file_obj.id,
chunking_strategy=chunking_strategy,
poll_interval_ms=poll_interval_ms,
max_wait_seconds=max_wait_seconds,
attributes=attributes,
)

Expand Down Expand Up @@ -767,6 +788,7 @@ async def create_and_poll(
vector_store_id: str,
attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit,
poll_interval_ms: int | Omit = omit,
max_wait_seconds: float | Omit = omit,
chunking_strategy: FileChunkingStrategyParam | 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.
Expand All @@ -775,7 +797,11 @@ async def create_and_poll(
extra_body: Body | None = None,
timeout: float | httpx2.Timeout | None | NotGiven = not_given,
) -> VectorStoreFile:
"""Attach a file to the given vector store and wait for it to be processed."""
"""Attach a file to the given vector store and wait for it to be processed.
`max_wait_seconds` limits polling after attachment; see `poll` for details.
"""
_validate_max_wait_seconds(max_wait_seconds)
await self.create(
vector_store_id=vector_store_id,
file_id=file_id,
Expand All @@ -791,6 +817,7 @@ async def create_and_poll(
file_id,
vector_store_id=vector_store_id,
poll_interval_ms=poll_interval_ms,
max_wait_seconds=max_wait_seconds,
)

async def poll(
Expand All @@ -799,9 +826,16 @@ async def poll(
*,
vector_store_id: str,
poll_interval_ms: int | Omit = omit,
max_wait_seconds: float | Omit = omit,
) -> VectorStoreFile:
"""Wait for the vector store file to finish processing.
`max_wait_seconds` must be finite and non-negative. When provided, it limits
polling and raises `TimeoutError` on expiry; zero prevents any polling requests.
Omit it to wait indefinitely. In-flight requests and retries use the client's
HTTP timeout settings and may finish after the polling deadline. Timing out
does not cancel processing on the server.
Note: this will return even if the file failed to process, you need to check
file.last_error and file.status to handle these cases
"""
Expand All @@ -810,6 +844,7 @@ async def poll(
file_id,
vector_store_id=vector_store_id,
poll_interval_ms=poll_interval_ms,
max_wait_seconds=max_wait_seconds,
)

async def upload(
Expand All @@ -836,14 +871,20 @@ async def upload_and_poll(
file: FileTypes,
attributes: Optional[Dict[str, Union[str, float, bool]]] | Omit = omit,
poll_interval_ms: int | Omit = omit,
max_wait_seconds: float | Omit = omit,
chunking_strategy: FileChunkingStrategyParam | Omit = omit,
) -> VectorStoreFile:
"""Add a file to a vector store and poll until processing is complete."""
"""Add a file to a vector store and poll until processing is complete.
`max_wait_seconds` limits polling after upload and attachment; see `poll` for details.
"""
_validate_max_wait_seconds(max_wait_seconds)
file_obj = await self._client.files.create(file=file, purpose="assistants")
return await self.create_and_poll(
vector_store_id=vector_store_id,
file_id=file_obj.id,
poll_interval_ms=poll_interval_ms,
max_wait_seconds=max_wait_seconds,
chunking_strategy=chunking_strategy,
attributes=attributes,
)
Expand Down
Loading
Loading