mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-10-04 04:04:14 +00:00
feat(files): fix expires_after API shape (#3604)
This was just quite incorrect. See source here: https://platform.openai.com/docs/api-reference/files/create
This commit is contained in:
parent
5e7fed8bbb
commit
3a09f00cdb
9 changed files with 705 additions and 448 deletions
|
@ -111,9 +111,7 @@ class Files(Protocol):
|
|||
self,
|
||||
file: Annotated[UploadFile, File()],
|
||||
purpose: Annotated[OpenAIFilePurpose, Form()],
|
||||
expires_after_anchor: Annotated[str | None, Form(alias="expires_after[anchor]")] = None,
|
||||
expires_after_seconds: Annotated[int | None, Form(alias="expires_after[seconds]")] = None,
|
||||
# TODO: expires_after is producing strange openapi spec, params are showing up as a required w/ oneOf being null
|
||||
expires_after: Annotated[ExpiresAfter | None, Form()] = None,
|
||||
) -> OpenAIFileObject:
|
||||
"""
|
||||
Upload a file that can be used across various endpoints.
|
||||
|
@ -121,10 +119,11 @@ class Files(Protocol):
|
|||
The file upload should be a multipart form request with:
|
||||
- file: The File object (not file name) to be uploaded.
|
||||
- purpose: The intended purpose of the uploaded file.
|
||||
- expires_after: Optional form values describing expiration for the file. Expected expires_after[anchor] = "created_at", expires_after[seconds] = {integer}. Seconds must be between 3600 and 2592000 (1 hour to 30 days).
|
||||
- expires_after: Optional form values describing expiration for the file.
|
||||
|
||||
:param file: The uploaded file object containing content and metadata (filename, content_type, etc.).
|
||||
:param purpose: The intended purpose of the uploaded file (e.g., "assistants", "fine-tune").
|
||||
:param expires_after: Optional form values describing expiration for the file.
|
||||
:returns: An OpenAIFileObject representing the uploaded file.
|
||||
"""
|
||||
...
|
||||
|
|
|
@ -14,6 +14,7 @@ from fastapi import File, Form, Response, UploadFile
|
|||
from llama_stack.apis.common.errors import ResourceNotFoundError
|
||||
from llama_stack.apis.common.responses import Order
|
||||
from llama_stack.apis.files import (
|
||||
ExpiresAfter,
|
||||
Files,
|
||||
ListOpenAIFileResponse,
|
||||
OpenAIFileDeleteResponse,
|
||||
|
@ -86,14 +87,13 @@ class LocalfsFilesImpl(Files):
|
|||
self,
|
||||
file: Annotated[UploadFile, File()],
|
||||
purpose: Annotated[OpenAIFilePurpose, Form()],
|
||||
expires_after_anchor: Annotated[str | None, Form(alias="expires_after[anchor]")] = None,
|
||||
expires_after_seconds: Annotated[int | None, Form(alias="expires_after[seconds]")] = None,
|
||||
expires_after: Annotated[ExpiresAfter | None, Form()] = None,
|
||||
) -> OpenAIFileObject:
|
||||
"""Upload a file that can be used across various endpoints."""
|
||||
if not self.sql_store:
|
||||
raise RuntimeError("Files provider not initialized")
|
||||
|
||||
if expires_after_anchor is not None or expires_after_seconds is not None:
|
||||
if expires_after is not None:
|
||||
raise NotImplementedError("File expiration is not supported by this provider")
|
||||
|
||||
file_id = self._generate_file_id()
|
||||
|
|
|
@ -195,8 +195,7 @@ class S3FilesImpl(Files):
|
|||
self,
|
||||
file: Annotated[UploadFile, File()],
|
||||
purpose: Annotated[OpenAIFilePurpose, Form()],
|
||||
expires_after_anchor: Annotated[str | None, Form(alias="expires_after[anchor]")] = None,
|
||||
expires_after_seconds: Annotated[int | None, Form(alias="expires_after[seconds]")] = None,
|
||||
expires_after: Annotated[ExpiresAfter | None, Form()] = None,
|
||||
) -> OpenAIFileObject:
|
||||
file_id = f"file-{uuid.uuid4().hex}"
|
||||
|
||||
|
@ -204,14 +203,6 @@ class S3FilesImpl(Files):
|
|||
|
||||
created_at = self._now()
|
||||
|
||||
expires_after = None
|
||||
if expires_after_anchor is not None or expires_after_seconds is not None:
|
||||
# we use ExpiresAfter to validate input
|
||||
expires_after = ExpiresAfter(
|
||||
anchor=expires_after_anchor, # type: ignore[arg-type]
|
||||
seconds=expires_after_seconds, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
# the default is no expiration.
|
||||
# to implement no expiration we set an expiration beyond the max.
|
||||
# we'll hide this fact from users when returning the file object.
|
||||
|
|
|
@ -567,6 +567,22 @@ def get_class_properties(typ: type) -> Iterable[Tuple[str, type | str]]:
|
|||
|
||||
if is_dataclass_type(typ):
|
||||
return ((field.name, field.type) for field in dataclasses.fields(typ))
|
||||
elif hasattr(typ, "model_fields"):
|
||||
# Pydantic BaseModel - use model_fields to exclude ClassVar and other non-field attributes
|
||||
# Reconstruct Annotated type if discriminator exists to preserve metadata
|
||||
from typing import Annotated, Any, cast
|
||||
from pydantic.fields import FieldInfo
|
||||
|
||||
def get_field_type(name: str, field: Any) -> type | str:
|
||||
# If field has discriminator, wrap in Annotated to preserve it for schema generation
|
||||
if field.discriminator:
|
||||
field_info = FieldInfo(annotation=None, discriminator=field.discriminator)
|
||||
# Annotated returns _AnnotatedAlias which isn't a type but is valid here
|
||||
return Annotated[field.annotation, field_info] # type: ignore[return-value]
|
||||
# field.annotation can be Union types, Annotated, etc. which aren't type but are valid
|
||||
return field.annotation # type: ignore[return-value,no-any-return]
|
||||
|
||||
return ((name, get_field_type(name, field)) for name, field in typ.model_fields.items())
|
||||
else:
|
||||
resolved_hints = get_resolved_hints(typ)
|
||||
return resolved_hints.items()
|
||||
|
|
|
@ -92,7 +92,12 @@ def get_class_property_docstrings(
|
|||
:returns: A dictionary mapping property names to descriptions.
|
||||
"""
|
||||
|
||||
result = {}
|
||||
result: Dict[str, str] = {}
|
||||
# Only try to get MRO if data_type is actually a class
|
||||
# Special types like Literal, Union, etc. don't have MRO
|
||||
if not inspect.isclass(data_type):
|
||||
return result
|
||||
|
||||
for base in inspect.getmro(data_type):
|
||||
docstr = docstring.parse_type(base)
|
||||
for param in docstr.params.values():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue