mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-12-03 09:53:45 +00:00
fix: adopt FastAPI directly in llama-stack-api
This commit migrates the Batches API to use FastAPI routers directly in the API package, removing the need for custom decorator systems and manual router registration. The API package now defines FastAPI routers using standard FastAPI route decorators, making it self-sufficient and eliminating dependencies on the server package. The router implementation has been moved from llama_stack/core/server/routers/batches.py to llama_stack_api/batches/routes.py, where it belongs alongside the protocol and models. Standard error responses (standard_responses) have been moved from the server package to llama_stack_api/router_utils.py, ensuring the API package can define complete routers without server dependencies. FastAPI has been added as an explicit dependency to the llama-stack-api package, making it an intentional dependency rather than an implicit one. Router discovery is now fully automatic. The server discovers routers by checking for routes modules in each API package and looking for a create_router function. This eliminates the need for manual registration and makes the system scalable - new APIs with router modules are automatically discovered and used. The router registry has been simplified to use automatic discovery instead of maintaining a manual registry. The build_router function (renamed from create_router to better reflect its purpose) discovers and combines router factories with implementations to create the final router instances. Exposing Routers from the API is nice for the Bring Your Own API use case too. Signed-off-by: Sébastien Han <seb@redhat.com>
This commit is contained in:
parent
2fe24a6df8
commit
00e7ea6c3b
10 changed files with 54 additions and 70 deletions
|
|
@ -76,14 +76,8 @@ def create_llama_stack_app() -> FastAPI:
|
|||
],
|
||||
)
|
||||
|
||||
# Import batches router to trigger router registration
|
||||
try:
|
||||
from llama_stack.core.server.routers import batches # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Include routers for APIs that have them registered
|
||||
from llama_stack.core.server.router_registry import create_router, has_router
|
||||
# Include routers for APIs that have them (automatic discovery)
|
||||
from llama_stack.core.server.router_registry import build_router, has_router
|
||||
|
||||
def dummy_impl_getter(api: Api) -> Any:
|
||||
"""Dummy implementation getter for OpenAPI generation."""
|
||||
|
|
@ -95,7 +89,7 @@ def create_llama_stack_app() -> FastAPI:
|
|||
protocols = api_protocol_map()
|
||||
for api in protocols.keys():
|
||||
if has_router(api):
|
||||
router = create_router(api, dummy_impl_getter)
|
||||
router = build_router(api, dummy_impl_getter)
|
||||
if router:
|
||||
app.include_router(router)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from pydantic import BaseModel
|
|||
|
||||
from llama_stack.core.datatypes import StackRunConfig
|
||||
from llama_stack.core.external import load_external_apis
|
||||
from llama_stack.core.server.router_registry import create_router, has_router
|
||||
from llama_stack.core.server.router_registry import build_router, has_router
|
||||
from llama_stack.core.server.routes import get_all_api_routes
|
||||
from llama_stack_api import (
|
||||
Api,
|
||||
|
|
@ -120,7 +120,7 @@ class DistributionInspectImpl(Inspect):
|
|||
if not has_router(api):
|
||||
continue
|
||||
|
||||
router = create_router(api, dummy_impl_getter)
|
||||
router = build_router(api, dummy_impl_getter)
|
||||
if not router:
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
# This source code is licensed under the terms described in the LICENSE file in
|
||||
# the root directory of this source tree.
|
||||
|
||||
"""Router registry for FastAPI routers.
|
||||
"""Router utilities for FastAPI routers.
|
||||
|
||||
This module provides a way to register FastAPI routers for APIs that have been
|
||||
migrated to use explicit FastAPI routers instead of Protocol-based route discovery.
|
||||
This module provides utilities to discover and create FastAPI routers from API packages.
|
||||
Routers are automatically discovered by checking for routes modules in each API package.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
|
|
@ -18,47 +19,42 @@ from fastapi import APIRouter
|
|||
if TYPE_CHECKING:
|
||||
from llama_stack_api.datatypes import Api
|
||||
|
||||
# Registry of router factory functions
|
||||
# Each factory function takes a callable that returns the implementation for a given API
|
||||
# and returns an APIRouter
|
||||
# Use string keys to avoid circular imports
|
||||
_router_factories: dict[str, Callable[[Callable[["Api"], Any]], APIRouter]] = {}
|
||||
|
||||
|
||||
def register_router(api: "Api", router_factory: Callable[[Callable[["Api"], Any]], APIRouter]) -> None:
|
||||
"""Register a router factory for an API.
|
||||
|
||||
Args:
|
||||
api: The API enum value
|
||||
router_factory: A function that takes an impl_getter function and returns an APIRouter
|
||||
"""
|
||||
_router_factories[api.value] = router_factory # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def has_router(api: "Api") -> bool:
|
||||
"""Check if an API has a registered router.
|
||||
"""Check if an API has a router factory in its routes module.
|
||||
|
||||
Args:
|
||||
api: The API enum value
|
||||
|
||||
Returns:
|
||||
True if a router factory is registered for this API
|
||||
True if the API has a routes module with a create_router function
|
||||
"""
|
||||
return api.value in _router_factories # type: ignore[attr-defined]
|
||||
try:
|
||||
routes_module = importlib.import_module(f"llama_stack_api.{api.value}.routes")
|
||||
return hasattr(routes_module, "create_router")
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
|
||||
|
||||
def create_router(api: "Api", impl_getter: Callable[["Api"], Any]) -> APIRouter | None:
|
||||
"""Create a router for an API if one is registered.
|
||||
def build_router(api: "Api", impl_getter: Callable[["Api"], Any]) -> APIRouter | None:
|
||||
"""Build a router for an API by combining its router factory with the implementation.
|
||||
|
||||
This function discovers the router factory from the API package's routes module
|
||||
and calls it with the impl_getter to create the final router instance.
|
||||
|
||||
Args:
|
||||
api: The API enum value
|
||||
impl_getter: Function that returns the implementation for a given API
|
||||
|
||||
Returns:
|
||||
APIRouter if registered, None otherwise
|
||||
APIRouter if the API has a routes module with create_router, None otherwise
|
||||
"""
|
||||
api_value = api.value # type: ignore[attr-defined]
|
||||
if api_value not in _router_factories:
|
||||
return None
|
||||
try:
|
||||
routes_module = importlib.import_module(f"llama_stack_api.{api.value}.routes")
|
||||
if hasattr(routes_module, "create_router"):
|
||||
router_factory = routes_module.create_router
|
||||
return router_factory(impl_getter)
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
return _router_factories[api_value](impl_getter)
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def get_all_api_routes(
|
|||
|
||||
This function only returns routes from APIs that use the legacy @webmethod
|
||||
decorator system. For APIs that have been migrated to FastAPI routers,
|
||||
use the router registry (router_registry.has_router() and router_registry.create_router()).
|
||||
use the router registry (router_registry.has_router() and router_registry.build_router()).
|
||||
|
||||
Args:
|
||||
external_apis: Optional dictionary of external API specifications
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ from llama_stack.core.request_headers import (
|
|||
request_provider_data_context,
|
||||
user_from_scope,
|
||||
)
|
||||
from llama_stack.core.server.router_registry import create_router, has_router
|
||||
from llama_stack.core.server.router_registry import build_router
|
||||
from llama_stack.core.server.routes import get_all_api_routes
|
||||
from llama_stack.core.stack import (
|
||||
Stack,
|
||||
|
|
@ -449,14 +449,6 @@ def create_app() -> StackApp:
|
|||
external_apis = load_external_apis(config)
|
||||
all_routes = get_all_api_routes(external_apis)
|
||||
|
||||
# Import batches router to trigger router registration
|
||||
# This ensures the router is registered before we try to use it
|
||||
# We will make this code better once the migration is complete
|
||||
try:
|
||||
from llama_stack.core.server.routers import batches # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if config.apis:
|
||||
apis_to_serve = set(config.apis)
|
||||
else:
|
||||
|
|
@ -483,15 +475,11 @@ def create_app() -> StackApp:
|
|||
for api_str in apis_to_serve:
|
||||
api = Api(api_str)
|
||||
|
||||
if has_router(api):
|
||||
router = create_router(api, impl_getter)
|
||||
# Try to discover and use a router factory from the API package
|
||||
router = build_router(api, impl_getter)
|
||||
if router:
|
||||
app.include_router(router)
|
||||
logger.debug(f"Registered router for {api} API")
|
||||
else:
|
||||
logger.warning(
|
||||
f"API '{api.value}' has a registered router factory but it returned None. Skipping this API."
|
||||
)
|
||||
else:
|
||||
# Fall back to old webmethod-based route discovery until the migration is complete
|
||||
routes = all_routes[api]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
This module contains the Batches protocol definition.
|
||||
Pydantic models are defined in llama_stack_api.batches.models.
|
||||
The router implementation is in llama_stack.core.server.routers.batches.
|
||||
The FastAPI router is defined in llama_stack_api.batches.routes.
|
||||
"""
|
||||
|
||||
from typing import Literal, Protocol, runtime_checkable
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"""FastAPI router for the Batches API.
|
||||
|
||||
This module defines the FastAPI router for the Batches API using standard
|
||||
FastAPI route decorators instead of Protocol-based route discovery.
|
||||
FastAPI route decorators. The router is defined in the API package to keep
|
||||
all API-related code together.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
|
@ -15,15 +16,14 @@ from typing import Annotated
|
|||
|
||||
from fastapi import APIRouter, Body, Depends
|
||||
|
||||
from llama_stack.core.server.router_registry import register_router
|
||||
from llama_stack.core.server.router_utils import standard_responses
|
||||
from llama_stack_api.batches import Batches, BatchObject, ListBatchesResponse
|
||||
from llama_stack_api.batches.models import CreateBatchRequest
|
||||
from llama_stack_api.datatypes import Api
|
||||
from llama_stack_api.router_utils import standard_responses
|
||||
from llama_stack_api.version import LLAMA_STACK_API_V1
|
||||
|
||||
|
||||
def create_batches_router(impl_getter: Callable[[Api], Batches]) -> APIRouter:
|
||||
def create_router(impl_getter: Callable[[Api], Batches]) -> APIRouter:
|
||||
"""Create a FastAPI router for the Batches API.
|
||||
|
||||
Args:
|
||||
|
|
@ -111,7 +111,3 @@ def create_batches_router(impl_getter: Callable[[Api], Batches]) -> APIRouter:
|
|||
return await svc.list_batches(after=after, limit=limit)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
# Register the router factory
|
||||
register_router(Api.batches, create_batches_router)
|
||||
|
|
@ -24,6 +24,7 @@ classifiers = [
|
|||
"Topic :: Scientific/Engineering :: Information Analysis",
|
||||
]
|
||||
dependencies = [
|
||||
"fastapi>=0.115.0,<1.0",
|
||||
"pydantic>=2.11.9",
|
||||
"jsonschema",
|
||||
"opentelemetry-sdk>=1.30.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@
|
|||
# This source code is licensed under the terms described in the LICENSE file in
|
||||
# the root directory of this source tree.
|
||||
|
||||
"""Utilities for creating FastAPI routers with standard error responses."""
|
||||
"""Utilities for creating FastAPI routers with standard error responses.
|
||||
|
||||
This module provides standard error response definitions for FastAPI routers.
|
||||
These responses use OpenAPI $ref references to component responses defined
|
||||
in the OpenAPI specification.
|
||||
"""
|
||||
|
||||
standard_responses = {
|
||||
400: {"$ref": "#/components/responses/BadRequest400"},
|
||||
6
uv.lock
generated
6
uv.lock
generated
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')",
|
||||
|
|
@ -2294,6 +2294,7 @@ name = "llama-stack-api"
|
|||
version = "0.4.0.dev0"
|
||||
source = { editable = "src/llama_stack_api" }
|
||||
dependencies = [
|
||||
{ name = "fastapi" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-http" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
|
|
@ -2302,6 +2303,7 @@ dependencies = [
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "fastapi", specifier = ">=0.115.0,<1.0" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.30.0" },
|
||||
{ name = "opentelemetry-sdk", specifier = ">=1.30.0" },
|
||||
|
|
@ -4656,6 +4658,8 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/6b/fa/3234f913fe9a6525a7b97c6dad1f51e72b917e6872e051a5e2ffd8b16fbb/ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83", size = 137970, upload-time = "2025-09-22T19:51:09.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/ec/4edbf17ac2c87fa0845dd366ef8d5852b96eb58fcd65fc1ecf5fe27b4641/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27", size = 739639, upload-time = "2025-09-22T19:51:10.566Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/18/b0e1fafe59051de9e79cdd431863b03593ecfa8341c110affad7c8121efc/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640", size = 764456, upload-time = "2025-09-22T19:51:11.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/cd/150fdb96b8fab27fe08d8a59fe67554568727981806e6bc2677a16081ec7/ruamel_yaml_clib-0.2.14-cp314-cp314-win32.whl", hash = "sha256:9b4104bf43ca0cd4e6f738cb86326a3b2f6eef00f417bd1e7efb7bdffe74c539", size = 102394, upload-time = "2025-11-14T21:57:36.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/e6/a3fa40084558c7e1dc9546385f22a93949c890a8b2e445b2ba43935f51da/ruamel_yaml_clib-0.2.14-cp314-cp314-win_amd64.whl", hash = "sha256:13997d7d354a9890ea1ec5937a219817464e5cc344805b37671562a401ca3008", size = 122673, upload-time = "2025-11-14T21:57:38.177Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue