mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-10-04 20:14:13 +00:00
Merge branch 'main' into remove-batch-inference
This commit is contained in:
commit
32b87bf88a
748 changed files with 127607 additions and 50032 deletions
|
@ -16,7 +16,7 @@ import sys
|
|||
import fire
|
||||
import ruamel.yaml as yaml
|
||||
|
||||
from llama_stack.apis.version import LLAMA_STACK_API_VERSION # noqa: E402
|
||||
from llama_stack.apis.version import LLAMA_STACK_API_V1 # noqa: E402
|
||||
from llama_stack.core.stack import LlamaStack # noqa: E402
|
||||
|
||||
from .pyopenapi.options import Options # noqa: E402
|
||||
|
@ -25,7 +25,7 @@ from .pyopenapi.utility import Specification, validate_api # noqa: E402
|
|||
|
||||
|
||||
def str_presenter(dumper, data):
|
||||
if data.startswith(f"/{LLAMA_STACK_API_VERSION}") or data.startswith(
|
||||
if data.startswith(f"/{LLAMA_STACK_API_V1}") or data.startswith(
|
||||
"#/components/schemas/"
|
||||
):
|
||||
style = None
|
||||
|
@ -58,7 +58,7 @@ def main(output_dir: str):
|
|||
server=Server(url="http://any-hosted-llama-stack.com"),
|
||||
info=Info(
|
||||
title="Llama Stack Specification",
|
||||
version=LLAMA_STACK_API_VERSION,
|
||||
version=LLAMA_STACK_API_V1,
|
||||
description="""This is the specification of the Llama Stack that provides
|
||||
a set of endpoints and their corresponding interfaces that are tailored to
|
||||
best leverage Llama Models.""",
|
||||
|
|
|
@ -9,7 +9,9 @@ import ipaddress
|
|||
import types
|
||||
import typing
|
||||
from dataclasses import make_dataclass
|
||||
from typing import Any, Dict, Set, Union
|
||||
from typing import Annotated, Any, Dict, get_args, get_origin, Set, Union
|
||||
|
||||
from fastapi import UploadFile
|
||||
|
||||
from llama_stack.apis.datatypes import Error
|
||||
from llama_stack.strong_typing.core import JsonType
|
||||
|
@ -30,9 +32,6 @@ from llama_stack.strong_typing.schema import (
|
|||
Schema,
|
||||
SchemaOptions,
|
||||
)
|
||||
from typing import get_origin, get_args
|
||||
from typing import Annotated
|
||||
from fastapi import UploadFile
|
||||
from llama_stack.strong_typing.serialization import json_dump_string, object_to_json
|
||||
|
||||
from .operations import (
|
||||
|
@ -623,11 +622,11 @@ class Generator:
|
|||
# data passed in request body as multipart/form-data
|
||||
elif op.multipart_params:
|
||||
builder = ContentBuilder(self.schema_builder)
|
||||
|
||||
|
||||
# Create schema properties for multipart form fields
|
||||
properties = {}
|
||||
required_fields = []
|
||||
|
||||
|
||||
for name, param_type in op.multipart_params:
|
||||
if get_origin(param_type) is Annotated:
|
||||
base_type = get_args(param_type)[0]
|
||||
|
@ -635,28 +634,21 @@ class Generator:
|
|||
base_type = param_type
|
||||
if base_type is UploadFile:
|
||||
# File upload
|
||||
properties[name] = {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
properties[name] = {"type": "string", "format": "binary"}
|
||||
else:
|
||||
# Form field
|
||||
properties[name] = self.schema_builder.classdef_to_ref(base_type)
|
||||
|
||||
|
||||
required_fields.append(name)
|
||||
|
||||
|
||||
multipart_schema = {
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required_fields
|
||||
"required": required_fields,
|
||||
}
|
||||
|
||||
|
||||
requestBody = RequestBody(
|
||||
content={
|
||||
"multipart/form-data": {
|
||||
"schema": multipart_schema
|
||||
}
|
||||
},
|
||||
content={"multipart/form-data": {"schema": multipart_schema}},
|
||||
required=True,
|
||||
)
|
||||
# data passed in payload as JSON and mapped to request parameters
|
||||
|
@ -800,9 +792,10 @@ class Generator:
|
|||
)
|
||||
|
||||
return Operation(
|
||||
tags=[getattr(op.defining_class, "API_NAMESPACE", op.defining_class.__name__)],
|
||||
summary=None,
|
||||
# summary=doc_string.short_description,
|
||||
tags=[
|
||||
getattr(op.defining_class, "API_NAMESPACE", op.defining_class.__name__)
|
||||
],
|
||||
summary=doc_string.short_description,
|
||||
description=description,
|
||||
parameters=parameters,
|
||||
requestBody=requestBody,
|
||||
|
@ -835,7 +828,7 @@ class Generator:
|
|||
else:
|
||||
raise NotImplementedError(f"unknown HTTP method: {op.http_method}")
|
||||
|
||||
route = op.get_route()
|
||||
route = op.get_route(op.webmethod)
|
||||
route = route.replace(":path", "")
|
||||
print(f"route: {route}")
|
||||
if route in paths:
|
||||
|
|
|
@ -11,7 +11,7 @@ import typing
|
|||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
||||
|
||||
from llama_stack.apis.version import LLAMA_STACK_API_VERSION
|
||||
from llama_stack.apis.version import LLAMA_STACK_API_V1, LLAMA_STACK_API_V1BETA, LLAMA_STACK_API_V1ALPHA
|
||||
|
||||
from termcolor import colored
|
||||
|
||||
|
@ -113,11 +113,13 @@ class EndpointOperation:
|
|||
request_examples: Optional[List[Any]] = None
|
||||
response_examples: Optional[List[Any]] = None
|
||||
|
||||
def get_route(self) -> str:
|
||||
if self.route is not None:
|
||||
return "/".join(["", LLAMA_STACK_API_VERSION, self.route.lstrip("/")])
|
||||
def get_route(self, webmethod) -> str:
|
||||
api_level = webmethod.level
|
||||
|
||||
route_parts = ["", LLAMA_STACK_API_VERSION, self.name]
|
||||
if self.route is not None:
|
||||
return "/".join(["", api_level, self.route.lstrip("/")])
|
||||
|
||||
route_parts = ["", api_level, self.name]
|
||||
for param_name, _ in self.path_params:
|
||||
route_parts.append("{" + param_name + "}")
|
||||
return "/".join(route_parts)
|
||||
|
@ -152,33 +154,39 @@ def _get_endpoint_functions(
|
|||
|
||||
functions = inspect.getmembers(endpoint, inspect.isfunction)
|
||||
for func_name, func_ref in functions:
|
||||
webmethod = getattr(func_ref, "__webmethod__", None)
|
||||
if not webmethod:
|
||||
webmethods = []
|
||||
|
||||
# Check for multiple webmethods (stacked decorators)
|
||||
if hasattr(func_ref, "__webmethods__"):
|
||||
webmethods = func_ref.__webmethods__
|
||||
|
||||
if not webmethods:
|
||||
continue
|
||||
|
||||
print(f"Processing {colored(func_name, 'white')}...")
|
||||
operation_name = func_name
|
||||
|
||||
if webmethod.method == "GET":
|
||||
prefix = "get"
|
||||
elif webmethod.method == "DELETE":
|
||||
prefix = "delete"
|
||||
elif webmethod.method == "POST":
|
||||
prefix = "post"
|
||||
elif operation_name.startswith("get_") or operation_name.endswith("/get"):
|
||||
prefix = "get"
|
||||
elif (
|
||||
operation_name.startswith("delete_")
|
||||
or operation_name.startswith("remove_")
|
||||
or operation_name.endswith("/delete")
|
||||
or operation_name.endswith("/remove")
|
||||
):
|
||||
prefix = "delete"
|
||||
else:
|
||||
# by default everything else is a POST
|
||||
prefix = "post"
|
||||
for webmethod in webmethods:
|
||||
print(f"Processing {colored(func_name, 'white')}...")
|
||||
operation_name = func_name
|
||||
|
||||
if webmethod.method == "GET":
|
||||
prefix = "get"
|
||||
elif webmethod.method == "DELETE":
|
||||
prefix = "delete"
|
||||
elif webmethod.method == "POST":
|
||||
prefix = "post"
|
||||
elif operation_name.startswith("get_") or operation_name.endswith("/get"):
|
||||
prefix = "get"
|
||||
elif (
|
||||
operation_name.startswith("delete_")
|
||||
or operation_name.startswith("remove_")
|
||||
or operation_name.endswith("/delete")
|
||||
or operation_name.endswith("/remove")
|
||||
):
|
||||
prefix = "delete"
|
||||
else:
|
||||
# by default everything else is a POST
|
||||
prefix = "post"
|
||||
|
||||
yield prefix, operation_name, func_name, func_ref
|
||||
yield prefix, operation_name, func_name, func_ref
|
||||
|
||||
|
||||
def _get_defining_class(member_fn: str, derived_cls: type) -> type:
|
||||
|
@ -239,105 +247,101 @@ def get_endpoint_operations(
|
|||
"update",
|
||||
],
|
||||
):
|
||||
# extract routing information from function metadata
|
||||
webmethod = getattr(func_ref, "__webmethod__", None)
|
||||
if webmethod is not None:
|
||||
# Get all webmethods for this function
|
||||
webmethods = getattr(func_ref, "__webmethods__", [])
|
||||
|
||||
# Create one EndpointOperation for each webmethod
|
||||
for webmethod in webmethods:
|
||||
route = webmethod.route
|
||||
route_params = _get_route_parameters(route) if route is not None else None
|
||||
public = webmethod.public
|
||||
request_examples = webmethod.request_examples
|
||||
response_examples = webmethod.response_examples
|
||||
else:
|
||||
route = None
|
||||
route_params = None
|
||||
public = False
|
||||
request_examples = None
|
||||
response_examples = None
|
||||
|
||||
# inspect function signature for path and query parameters, and request/response payload type
|
||||
signature = get_signature(func_ref)
|
||||
# inspect function signature for path and query parameters, and request/response payload type
|
||||
signature = get_signature(func_ref)
|
||||
|
||||
path_params = []
|
||||
query_params = []
|
||||
request_params = []
|
||||
multipart_params = []
|
||||
path_params = []
|
||||
query_params = []
|
||||
request_params = []
|
||||
multipart_params = []
|
||||
|
||||
for param_name, parameter in signature.parameters.items():
|
||||
param_type = _get_annotation_type(parameter.annotation, func_ref)
|
||||
for param_name, parameter in signature.parameters.items():
|
||||
param_type = _get_annotation_type(parameter.annotation, func_ref)
|
||||
|
||||
# omit "self" for instance methods
|
||||
if param_name == "self" and param_type is inspect.Parameter.empty:
|
||||
continue
|
||||
# omit "self" for instance methods
|
||||
if param_name == "self" and param_type is inspect.Parameter.empty:
|
||||
continue
|
||||
|
||||
# check if all parameters have explicit type
|
||||
if parameter.annotation is inspect.Parameter.empty:
|
||||
# check if all parameters have explicit type
|
||||
if parameter.annotation is inspect.Parameter.empty:
|
||||
raise ValidationError(
|
||||
f"parameter '{param_name}' in function '{func_name}' has no type annotation"
|
||||
)
|
||||
|
||||
is_multipart = _is_multipart_param(param_type)
|
||||
|
||||
if prefix in ["get", "delete"]:
|
||||
if route_params is not None and param_name in route_params:
|
||||
path_params.append((param_name, param_type))
|
||||
else:
|
||||
query_params.append((param_name, param_type))
|
||||
else:
|
||||
if route_params is not None and param_name in route_params:
|
||||
path_params.append((param_name, param_type))
|
||||
elif is_multipart:
|
||||
multipart_params.append((param_name, param_type))
|
||||
else:
|
||||
request_params.append((param_name, param_type))
|
||||
|
||||
# check if function has explicit return type
|
||||
if signature.return_annotation is inspect.Signature.empty:
|
||||
raise ValidationError(
|
||||
f"parameter '{param_name}' in function '{func_name}' has no type annotation"
|
||||
f"function '{func_name}' has no return type annotation"
|
||||
)
|
||||
|
||||
is_multipart = _is_multipart_param(param_type)
|
||||
|
||||
if prefix in ["get", "delete"]:
|
||||
if route_params is not None and param_name in route_params:
|
||||
path_params.append((param_name, param_type))
|
||||
else:
|
||||
query_params.append((param_name, param_type))
|
||||
return_type = _get_annotation_type(signature.return_annotation, func_ref)
|
||||
|
||||
# operations that produce events are labeled as Generator[YieldType, SendType, ReturnType]
|
||||
# where YieldType is the event type, SendType is None, and ReturnType is the immediate response type to the request
|
||||
if typing.get_origin(return_type) is collections.abc.Generator:
|
||||
event_type, send_type, response_type = typing.get_args(return_type)
|
||||
if send_type is not type(None):
|
||||
raise ValidationError(
|
||||
f"function '{func_name}' has a return type Generator[Y,S,R] and therefore looks like an event but has an explicit send type"
|
||||
)
|
||||
else:
|
||||
if route_params is not None and param_name in route_params:
|
||||
path_params.append((param_name, param_type))
|
||||
elif is_multipart:
|
||||
multipart_params.append((param_name, param_type))
|
||||
event_type = None
|
||||
|
||||
def process_type(t):
|
||||
if typing.get_origin(t) is collections.abc.AsyncIterator:
|
||||
# NOTE(ashwin): this is SSE and there is no way to represent it. either we make it a List
|
||||
# or the item type. I am choosing it to be the latter
|
||||
args = typing.get_args(t)
|
||||
return args[0]
|
||||
elif typing.get_origin(t) is typing.Union:
|
||||
types = [process_type(a) for a in typing.get_args(t)]
|
||||
return typing._UnionGenericAlias(typing.Union, tuple(types))
|
||||
else:
|
||||
return t
|
||||
|
||||
response_type = process_type(return_type)
|
||||
|
||||
if prefix in ["delete", "remove"]:
|
||||
http_method = HTTPMethod.DELETE
|
||||
elif prefix == "post":
|
||||
http_method = HTTPMethod.POST
|
||||
elif prefix == "get":
|
||||
http_method = HTTPMethod.GET
|
||||
elif prefix == "set":
|
||||
http_method = HTTPMethod.PUT
|
||||
elif prefix == "update":
|
||||
http_method = HTTPMethod.PATCH
|
||||
else:
|
||||
request_params.append((param_name, param_type))
|
||||
raise ValidationError(f"unknown prefix {prefix}")
|
||||
|
||||
# check if function has explicit return type
|
||||
if signature.return_annotation is inspect.Signature.empty:
|
||||
raise ValidationError(
|
||||
f"function '{func_name}' has no return type annotation"
|
||||
)
|
||||
|
||||
return_type = _get_annotation_type(signature.return_annotation, func_ref)
|
||||
|
||||
# operations that produce events are labeled as Generator[YieldType, SendType, ReturnType]
|
||||
# where YieldType is the event type, SendType is None, and ReturnType is the immediate response type to the request
|
||||
if typing.get_origin(return_type) is collections.abc.Generator:
|
||||
event_type, send_type, response_type = typing.get_args(return_type)
|
||||
if send_type is not type(None):
|
||||
raise ValidationError(
|
||||
f"function '{func_name}' has a return type Generator[Y,S,R] and therefore looks like an event but has an explicit send type"
|
||||
)
|
||||
else:
|
||||
event_type = None
|
||||
|
||||
def process_type(t):
|
||||
if typing.get_origin(t) is collections.abc.AsyncIterator:
|
||||
# NOTE(ashwin): this is SSE and there is no way to represent it. either we make it a List
|
||||
# or the item type. I am choosing it to be the latter
|
||||
args = typing.get_args(t)
|
||||
return args[0]
|
||||
elif typing.get_origin(t) is typing.Union:
|
||||
types = [process_type(a) for a in typing.get_args(t)]
|
||||
return typing._UnionGenericAlias(typing.Union, tuple(types))
|
||||
else:
|
||||
return t
|
||||
|
||||
response_type = process_type(return_type)
|
||||
|
||||
if prefix in ["delete", "remove"]:
|
||||
http_method = HTTPMethod.DELETE
|
||||
elif prefix == "post":
|
||||
http_method = HTTPMethod.POST
|
||||
elif prefix == "get":
|
||||
http_method = HTTPMethod.GET
|
||||
elif prefix == "set":
|
||||
http_method = HTTPMethod.PUT
|
||||
elif prefix == "update":
|
||||
http_method = HTTPMethod.PATCH
|
||||
else:
|
||||
raise ValidationError(f"unknown prefix {prefix}")
|
||||
|
||||
result.append(
|
||||
EndpointOperation(
|
||||
# Create an EndpointOperation for this specific webmethod
|
||||
operation = EndpointOperation(
|
||||
defining_class=_get_defining_class(func_name, endpoint),
|
||||
name=operation_name,
|
||||
func_name=func_name,
|
||||
|
@ -354,7 +358,10 @@ def get_endpoint_operations(
|
|||
request_examples=request_examples if use_examples else None,
|
||||
response_examples=response_examples if use_examples else None,
|
||||
)
|
||||
)
|
||||
|
||||
# Store the specific webmethod with this operation
|
||||
operation.webmethod = webmethod
|
||||
result.append(operation)
|
||||
|
||||
if not result:
|
||||
raise ValidationError(f"no eligible endpoint operations in type {endpoint}")
|
||||
|
|
|
@ -29,4 +29,4 @@ fi
|
|||
|
||||
stack_dir=$(dirname $(dirname $THIS_DIR))
|
||||
PYTHONPATH=$PYTHONPATH:$stack_dir \
|
||||
python -m docs.openapi_generator.generate $(dirname $THIS_DIR)/_static
|
||||
python -m docs.openapi_generator.generate $(dirname $THIS_DIR)/static
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue