diff --git a/docs/_static/llama-stack-spec.html b/docs/_static/llama-stack-spec.html index 2b6e1d11c..02d05776d 100644 --- a/docs/_static/llama-stack-spec.html +++ b/docs/_static/llama-stack-spec.html @@ -678,6 +678,65 @@ } } }, + "/v1/files": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListBucketResponse" + } + } + } + } + }, + "tags": [ + "Files (Coming Soon)" + ], + "description": "List all buckets.", + "parameters": [ + { + "name": "bucket", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileUploadResponse" + } + } + } + } + }, + "tags": [ + "Files (Coming Soon)" + ], + "description": "Create a new upload session for a file identified by a bucket and key.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUploadSessionRequest" + } + } + }, + "required": true + } + } + }, "/v1/agents/{agent_id}": { "delete": { "responses": { @@ -779,6 +838,84 @@ ] } }, + "/v1/files/{bucket}/{key}": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileResponse" + } + } + } + } + }, + "tags": [ + "Files (Coming Soon)" + ], + "description": "Get a file info identified by a bucket and key.", + "parameters": [ + { + "name": "bucket", + "in": "path", + "description": "Bucket name (valid chars: a-zA-Z0-9_-)", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "Key under which the file is stored (valid chars: a-zA-Z0-9_-/.)", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "delete": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileResponse" + } + } + } + } + }, + "tags": [ + "Files (Coming Soon)" + ], + "description": "Delete a file identified by a bucket and key.", + "parameters": [ + { + "name": "bucket", + "in": "path", + "description": "Bucket name (valid chars: a-zA-Z0-9_-)", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "Key under which the file is stored (valid chars: a-zA-Z0-9_-/.)", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, "/v1/inference/embeddings": { "post": { "responses": { @@ -1470,6 +1607,91 @@ "parameters": [] } }, + "/v1/files/session:{upload_id}": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/FileUploadResponse" + }, + { + "type": "null" + } + ] + } + } + } + } + }, + "tags": [ + "Files (Coming Soon)" + ], + "description": "Returns information about an existsing upload session", + "parameters": [ + { + "name": "upload_id", + "in": "path", + "description": "ID of the upload session", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/FileResponse" + }, + { + "type": "null" + } + ] + } + } + } + } + }, + "tags": [ + "Files (Coming Soon)" + ], + "description": "Upload file content to an existing upload session. On the server, request body will have the raw bytes that are uploaded.", + "parameters": [ + { + "name": "upload_id", + "in": "path", + "description": "ID of the upload session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + } + } + }, "/v1/vector-dbs/{vector_db_id}": { "get": { "responses": { @@ -1826,6 +2048,37 @@ } } }, + "/v1/files/{bucket}": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFileResponse" + } + } + } + } + }, + "tags": [ + "Files (Coming Soon)" + ], + "description": "List all files in a bucket.", + "parameters": [ + { + "name": "bucket", + "in": "path", + "description": "Bucket name (valid chars: a-zA-Z0-9_-)", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, "/v1/models": { "get": { "responses": { @@ -5441,6 +5694,105 @@ ], "title": "AgentTurnResponseTurnStartPayload" }, + "CreateUploadSessionRequest": { + "type": "object", + "properties": { + "bucket": { + "type": "string", + "description": "Bucket under which the file is stored (valid chars: a-zA-Z0-9_-)" + }, + "key": { + "type": "string", + "description": "Key under which the file is stored (valid chars: a-zA-Z0-9_-/.)" + }, + "mime_type": { + "type": "string", + "description": "MIME type of the file" + }, + "size": { + "type": "integer", + "description": "File size in bytes" + } + }, + "additionalProperties": false, + "required": [ + "bucket", + "key", + "mime_type", + "size" + ], + "title": "CreateUploadSessionRequest" + }, + "FileUploadResponse": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the upload session" + }, + "url": { + "type": "string", + "description": "Upload URL for the file or file parts" + }, + "offset": { + "type": "integer", + "description": "Upload content offset" + }, + "size": { + "type": "integer", + "description": "Upload content size" + } + }, + "additionalProperties": false, + "required": [ + "id", + "url", + "offset", + "size" + ], + "title": "FileUploadResponse", + "description": "Response after initiating a file upload session." + }, + "FileResponse": { + "type": "object", + "properties": { + "bucket": { + "type": "string", + "description": "Bucket under which the file is stored (valid chars: a-zA-Z0-9_-)" + }, + "key": { + "type": "string", + "description": "Key under which the file is stored (valid chars: a-zA-Z0-9_-/.)" + }, + "mime_type": { + "type": "string", + "description": "MIME type of the file" + }, + "url": { + "type": "string", + "description": "Upload URL for the file contents" + }, + "bytes": { + "type": "integer", + "description": "Size of the file in bytes" + }, + "created_at": { + "type": "integer", + "description": "Timestamp of when the file was created" + } + }, + "additionalProperties": false, + "required": [ + "bucket", + "key", + "mime_type", + "url", + "bytes", + "created_at" + ], + "title": "FileResponse", + "description": "Response representing a file entry." + }, "EmbeddingsRequest": { "type": "object", "properties": { @@ -6756,6 +7108,37 @@ ], "title": "ToolInvocationResult" }, + "BucketResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name" + ], + "title": "BucketResponse" + }, + "ListBucketResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketResponse" + }, + "description": "List of FileResponse entries" + } + }, + "additionalProperties": false, + "required": [ + "data" + ], + "title": "ListBucketResponse", + "description": "Response representing a list of file entries." + }, "ListDatasetsResponse": { "type": "object", "properties": { @@ -6772,6 +7155,24 @@ ], "title": "ListDatasetsResponse" }, + "ListFileResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileResponse" + }, + "description": "List of FileResponse entries" + } + }, + "additionalProperties": false, + "required": [ + "data" + ], + "title": "ListFileResponse", + "description": "Response representing a list of file entries." + }, "ListModelsResponse": { "type": "object", "properties": { @@ -8543,6 +8944,9 @@ { "name": "Eval" }, + { + "name": "Files (Coming Soon)" + }, { "name": "Inference", "description": "This API provides the raw interface to the underlying models. Two kinds of models are supported:\n- LLM models: these models generate \"raw\" and \"chat\" (conversational) completions.\n- Embedding models: these models generate embeddings to be used for semantic search.", @@ -8598,6 +9002,7 @@ "DatasetIO", "Datasets", "Eval", + "Files (Coming Soon)", "Inference", "Inspect", "Models", diff --git a/docs/_static/llama-stack-spec.yaml b/docs/_static/llama-stack-spec.yaml index 99300fedf..f79120f1d 100644 --- a/docs/_static/llama-stack-spec.yaml +++ b/docs/_static/llama-stack-spec.yaml @@ -406,6 +406,43 @@ paths: schema: $ref: '#/components/schemas/CreateAgentTurnRequest' required: true + /v1/files: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListBucketResponse' + tags: + - Files (Coming Soon) + description: List all buckets. + parameters: + - name: bucket + in: query + required: true + schema: + type: string + post: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/FileUploadResponse' + tags: + - Files (Coming Soon) + description: >- + Create a new upload session for a file identified by a bucket and key. + parameters: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUploadSessionRequest' + required: true /v1/agents/{agent_id}: delete: responses: @@ -468,6 +505,59 @@ paths: required: true schema: type: string + /v1/files/{bucket}/{key}: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/FileResponse' + tags: + - Files (Coming Soon) + description: >- + Get a file info identified by a bucket and key. + parameters: + - name: bucket + in: path + description: 'Bucket name (valid chars: a-zA-Z0-9_-)' + required: true + schema: + type: string + - name: key + in: path + description: >- + Key under which the file is stored (valid chars: a-zA-Z0-9_-/.) + required: true + schema: + type: string + delete: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/FileResponse' + tags: + - Files (Coming Soon) + description: >- + Delete a file identified by a bucket and key. + parameters: + - name: bucket + in: path + description: 'Bucket name (valid chars: a-zA-Z0-9_-)' + required: true + schema: + type: string + - name: key + in: path + description: >- + Key under which the file is stored (valid chars: a-zA-Z0-9_-/.) + required: true + schema: + type: string /v1/inference/embeddings: post: responses: @@ -875,6 +965,57 @@ paths: - PostTraining (Coming Soon) description: '' parameters: [] + /v1/files/session:{upload_id}: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/FileUploadResponse' + - type: 'null' + tags: + - Files (Coming Soon) + description: >- + Returns information about an existsing upload session + parameters: + - name: upload_id + in: path + description: ID of the upload session + required: true + schema: + type: string + post: + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/FileResponse' + - type: 'null' + tags: + - Files (Coming Soon) + description: >- + Upload file content to an existing upload session. On the server, request + body will have the raw bytes that are uploaded. + parameters: + - name: upload_id + in: path + description: ID of the upload session + required: true + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + required: true /v1/vector-dbs/{vector_db_id}: get: responses: @@ -1091,6 +1232,25 @@ paths: schema: $ref: '#/components/schemas/RegisterDatasetRequest' required: true + /v1/files/{bucket}: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListFileResponse' + tags: + - Files (Coming Soon) + description: List all files in a bucket. + parameters: + - name: bucket + in: path + description: 'Bucket name (valid chars: a-zA-Z0-9_-)' + required: true + schema: + type: string /v1/models: get: responses: @@ -3508,6 +3668,87 @@ components: - event_type - turn_id title: AgentTurnResponseTurnStartPayload + CreateUploadSessionRequest: + type: object + properties: + bucket: + type: string + description: >- + Bucket under which the file is stored (valid chars: a-zA-Z0-9_-) + key: + type: string + description: >- + Key under which the file is stored (valid chars: a-zA-Z0-9_-/.) + mime_type: + type: string + description: MIME type of the file + size: + type: integer + description: File size in bytes + additionalProperties: false + required: + - bucket + - key + - mime_type + - size + title: CreateUploadSessionRequest + FileUploadResponse: + type: object + properties: + id: + type: string + description: ID of the upload session + url: + type: string + description: Upload URL for the file or file parts + offset: + type: integer + description: Upload content offset + size: + type: integer + description: Upload content size + additionalProperties: false + required: + - id + - url + - offset + - size + title: FileUploadResponse + description: >- + Response after initiating a file upload session. + FileResponse: + type: object + properties: + bucket: + type: string + description: >- + Bucket under which the file is stored (valid chars: a-zA-Z0-9_-) + key: + type: string + description: >- + Key under which the file is stored (valid chars: a-zA-Z0-9_-/.) + mime_type: + type: string + description: MIME type of the file + url: + type: string + description: Upload URL for the file contents + bytes: + type: integer + description: Size of the file in bytes + created_at: + type: integer + description: Timestamp of when the file was created + additionalProperties: false + required: + - bucket + - key + - mime_type + - url + - bytes + - created_at + title: FileResponse + description: Response representing a file entry. EmbeddingsRequest: type: object properties: @@ -4339,6 +4580,29 @@ components: required: - content title: ToolInvocationResult + BucketResponse: + type: object + properties: + name: + type: string + additionalProperties: false + required: + - name + title: BucketResponse + ListBucketResponse: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/BucketResponse' + description: List of FileResponse entries + additionalProperties: false + required: + - data + title: ListBucketResponse + description: >- + Response representing a list of file entries. ListDatasetsResponse: type: object properties: @@ -4350,6 +4614,20 @@ components: required: - data title: ListDatasetsResponse + ListFileResponse: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/FileResponse' + description: List of FileResponse entries + additionalProperties: false + required: + - data + title: ListFileResponse + description: >- + Response representing a list of file entries. ListModelsResponse: type: object properties: @@ -5467,6 +5745,7 @@ tags: - name: DatasetIO - name: Datasets - name: Eval + - name: Files (Coming Soon) - name: Inference description: >- This API provides the raw interface to the underlying models. Two kinds of models @@ -5501,6 +5780,7 @@ x-tagGroups: - DatasetIO - Datasets - Eval + - Files (Coming Soon) - Inference - Inspect - Models diff --git a/docs/openapi_generator/pyopenapi/generator.py b/docs/openapi_generator/pyopenapi/generator.py index 60cd7a242..4220cfc05 100644 --- a/docs/openapi_generator/pyopenapi/generator.py +++ b/docs/openapi_generator/pyopenapi/generator.py @@ -477,6 +477,7 @@ class Generator: "SyntheticDataGeneration", "PostTraining", "BatchInference", + "Files", ]: op.defining_class.__name__ = f"{op.defining_class.__name__} (Coming Soon)" print(op.defining_class.__name__) @@ -520,8 +521,30 @@ class Generator: # parameters passed anywhere parameters = path_parameters + query_parameters - # data passed in payload - if op.request_params: + webmethod = getattr(op.func_ref, "__webmethod__", None) + raw_bytes_request_body = False + if webmethod: + raw_bytes_request_body = getattr(webmethod, "raw_bytes_request_body", False) + + # data passed in request body as raw bytes cannot have request parameters + if raw_bytes_request_body and op.request_params: + raise ValueError("Cannot have both raw bytes request body and request parameters") + + # data passed in request body as raw bytes + if raw_bytes_request_body: + requestBody = RequestBody( + content={ + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary", + } + } + }, + required=True, + ) + # data passed in payload as JSON and mapped to request parameters + elif op.request_params: builder = ContentBuilder(self.schema_builder) first = next(iter(op.request_params)) request_name, request_type = first diff --git a/docs/openapi_generator/pyopenapi/specification.py b/docs/openapi_generator/pyopenapi/specification.py index 9e5363b4a..d3e5a1f19 100644 --- a/docs/openapi_generator/pyopenapi/specification.py +++ b/docs/openapi_generator/pyopenapi/specification.py @@ -78,7 +78,7 @@ class MediaType: @dataclass class RequestBody: - content: Dict[str, MediaType] + content: Dict[str, MediaType | Dict[str, Any]] description: Optional[str] = None required: Optional[bool] = None diff --git a/llama_stack/apis/files/__init__.py b/llama_stack/apis/files/__init__.py new file mode 100644 index 000000000..269baf177 --- /dev/null +++ b/llama_stack/apis/files/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from .files import * # noqa: F401 F403 diff --git a/llama_stack/apis/files/files.py b/llama_stack/apis/files/files.py new file mode 100644 index 000000000..f17fadc8c --- /dev/null +++ b/llama_stack/apis/files/files.py @@ -0,0 +1,174 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import List, Optional, Protocol, runtime_checkable + +from pydantic import BaseModel + +from llama_stack.providers.utils.telemetry.trace_protocol import trace_protocol +from llama_stack.schema_utils import json_schema_type, webmethod + + +@json_schema_type +class FileUploadResponse(BaseModel): + """ + Response after initiating a file upload session. + + :param id: ID of the upload session + :param url: Upload URL for the file or file parts + :param offset: Upload content offset + :param size: Upload content size + """ + + id: str + url: str + offset: int + size: int + + +@json_schema_type +class BucketResponse(BaseModel): + name: str + + +@json_schema_type +class ListBucketResponse(BaseModel): + """ + Response representing a list of file entries. + + :param data: List of FileResponse entries + """ + + data: List[BucketResponse] + + +@json_schema_type +class FileResponse(BaseModel): + """ + Response representing a file entry. + + :param bucket: Bucket under which the file is stored (valid chars: a-zA-Z0-9_-) + :param key: Key under which the file is stored (valid chars: a-zA-Z0-9_-/.) + :param mime_type: MIME type of the file + :param url: Upload URL for the file contents + :param bytes: Size of the file in bytes + :param created_at: Timestamp of when the file was created + """ + + bucket: str + key: str + mime_type: str + url: str + bytes: int + created_at: int + + +@json_schema_type +class ListFileResponse(BaseModel): + """ + Response representing a list of file entries. + + :param data: List of FileResponse entries + """ + + data: List[FileResponse] + + +@runtime_checkable +@trace_protocol +class Files(Protocol): + @webmethod(route="/files", method="POST") + async def create_upload_session( + self, + bucket: str, + key: str, + mime_type: str, + size: int, + ) -> FileUploadResponse: + """ + Create a new upload session for a file identified by a bucket and key. + + :param bucket: Bucket under which the file is stored (valid chars: a-zA-Z0-9_-) + :param key: Key under which the file is stored (valid chars: a-zA-Z0-9_-/.) + :param mime_type: MIME type of the file + :param size: File size in bytes + """ + ... + + @webmethod(route="/files/session:{upload_id}", method="POST", raw_bytes_request_body=True) + async def upload_content_to_session( + self, + upload_id: str, + ) -> Optional[FileResponse]: + """ + Upload file content to an existing upload session. + On the server, request body will have the raw bytes that are uploaded. + + :param upload_id: ID of the upload session + """ + ... + + @webmethod(route="/files/session:{upload_id}", method="GET") + async def get_upload_session_info( + self, + upload_id: str, + ) -> Optional[FileUploadResponse]: + """ + Returns information about an existsing upload session + + :param upload_id: ID of the upload session + """ + ... + + @webmethod(route="/files", method="GET") + async def list_all_buckets( + self, + bucket: str, + ) -> ListBucketResponse: + """ + List all buckets. + """ + ... + + @webmethod(route="/files/{bucket}", method="GET") + async def list_files_in_bucket( + self, + bucket: str, + ) -> ListFileResponse: + """ + List all files in a bucket. + + :param bucket: Bucket name (valid chars: a-zA-Z0-9_-) + """ + ... + + @webmethod(route="/files/{bucket}/{key:path}", method="GET") + async def get_file( + self, + bucket: str, + key: str, + ) -> FileResponse: + """ + Get a file info identified by a bucket and key. + + :param bucket: Bucket name (valid chars: a-zA-Z0-9_-) + :param key: Key under which the file is stored (valid chars: a-zA-Z0-9_-/.) + """ + ... + + @webmethod(route="/files/{bucket}/{key:path}", method="DELETE") + async def delete_file( + self, + bucket: str, + key: str, + ) -> FileResponse: + """ + Delete a file identified by a bucket and key. + + :param bucket: Bucket name (valid chars: a-zA-Z0-9_-) + :param key: Key under which the file is stored (valid chars: a-zA-Z0-9_-/.) + """ + ... diff --git a/llama_stack/distribution/stack.py b/llama_stack/distribution/stack.py index 9335dc3a9..1328c88ef 100644 --- a/llama_stack/distribution/stack.py +++ b/llama_stack/distribution/stack.py @@ -19,6 +19,7 @@ from llama_stack.apis.benchmarks import Benchmarks from llama_stack.apis.datasetio import DatasetIO from llama_stack.apis.datasets import Datasets from llama_stack.apis.eval import Eval +from llama_stack.apis.files import Files from llama_stack.apis.inference import Inference from llama_stack.apis.inspect import Inspect from llama_stack.apis.models import Models @@ -63,6 +64,7 @@ class LlamaStack( ToolGroups, ToolRuntime, RAGToolRuntime, + Files, ): pass diff --git a/llama_stack/schema_utils.py b/llama_stack/schema_utils.py index 56b9e5e4c..581404844 100644 --- a/llama_stack/schema_utils.py +++ b/llama_stack/schema_utils.py @@ -19,6 +19,7 @@ class WebMethod: request_examples: Optional[List[Any]] = None response_examples: Optional[List[Any]] = None method: Optional[str] = None + raw_bytes_request_body: Optional[bool] = False def webmethod( @@ -27,6 +28,7 @@ def webmethod( public: Optional[bool] = False, request_examples: Optional[List[Any]] = None, response_examples: Optional[List[Any]] = None, + raw_bytes_request_body: Optional[bool] = False, ) -> Callable[[T], T]: """ Decorator that supplies additional metadata to an endpoint operation function. @@ -44,6 +46,7 @@ def webmethod( public=public or False, request_examples=request_examples, response_examples=response_examples, + raw_bytes_request_body=raw_bytes_request_body, ) return cls