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
33 changes: 31 additions & 2 deletions src/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,28 @@ class AsyncLlamaStackClientHolder(metaclass=Singleton):
_lsc: Optional[AsyncLlamaStackClient] = None

async def load(self, llama_stack_config: LlamaStackConfiguration) -> None:
"""Retrieve Async Llama stack client according to configuration."""
"""
Load and initialize the holder's AsyncLlamaStackClient according to the provided config.

If `llama_stack_config.use_as_library_client` is set to True, a
library-mode client is created using
`llama_stack_config.library_client_config_path` and initialized before
being stored.

Otherwise, a service-mode client is created using
`llama_stack_config.url` and optional `llama_stack_config.api_key`.
The created client is stored on the instance for later retrieval via
`get_client()`.

Parameters:
llama_stack_config (LlamaStackConfiguration): Configuration that
selects client mode and provides either a library client config
path or service connection details (URL and optional API key).

Raises:
ValueError: If `use_as_library_client` is True but
`library_client_config_path` is not set.
"""
if llama_stack_config.use_as_library_client is True:
if llama_stack_config.library_client_config_path is not None:
logger.info("Using Llama stack as library client")
Expand All @@ -47,7 +68,15 @@ async def load(self, llama_stack_config: LlamaStackConfiguration) -> None:
)

def get_client(self) -> AsyncLlamaStackClient:
"""Return an initialised AsyncLlamaStackClient."""
"""
Get the initialized client held by this holder.

Returns:
AsyncLlamaStackClient: The initialized client instance.

Raises:
RuntimeError: If the client has not been initialized; call `load(...)` first.
"""
if not self._lsc:
raise RuntimeError(
"AsyncLlamaStackClient has not been initialised. Ensure 'load(..)' has been called."
Expand Down
158 changes: 141 additions & 17 deletions src/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,74 +49,146 @@ def __new__(cls, *args: Any, **kwargs: Any) -> "AppConfig":
return cls._instance

def __init__(self) -> None:
"""Initialize the class instance."""
"""Initialize the class instance.

Sets placeholders for the loaded configuration and lazily-created
runtime resources (conversation cache, quota limiters, and token usage
history).
"""
self._configuration: Optional[Configuration] = None
self._conversation_cache: Optional[Cache] = None
self._quota_limiters: list[QuotaLimiter] = []
self._token_usage_history: Optional[TokenUsageHistory] = None

def load_configuration(self, filename: str) -> None:
"""Load configuration from YAML file."""
"""Load configuration from YAML file.

Parameters:
filename (str): Path to the YAML configuration file to load.
"""
with open(filename, encoding="utf-8") as fin:
config_dict = yaml.safe_load(fin)
config_dict = replace_env_vars(config_dict)
logger.info("Loaded configuration: %s", config_dict)
self.init_from_dict(config_dict)

def init_from_dict(self, config_dict: dict[Any, Any]) -> None:
"""Initialize configuration from a dictionary."""
"""Initialize configuration from a dictionary.

Parameters:
config_dict (dict[Any, Any]): Mapping of configuration values
(typically parsed from YAML) to construct a new Configuration
instance. The method sets the internal configuration to
Configuration(**config_dict) and clears any cached conversation
cache, quota limiters, and token usage history so they will be
reinitialized on next access.
"""
# clear cached values when configuration changes
self._conversation_cache = None
self._quota_limiters = []
self._token_usage_history = None
# now it is possible to re-read configuration
self._configuration = Configuration(**config_dict)

@property
def configuration(self) -> Configuration:
"""Return the whole configuration."""
"""Return the whole configuration.

Returns:
Configuration: The loaded configuration object.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration

@property
def service_configuration(self) -> ServiceConfiguration:
"""Return service configuration."""
"""Return service configuration.

Returns:
ServiceConfiguration: The service configuration stored in the current configuration.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.service

@property
def llama_stack_configuration(self) -> LlamaStackConfiguration:
"""Return Llama stack configuration."""
"""Return Llama stack configuration.

Returns:
LlamaStackConfiguration: The configured Llama stack settings.

Raises:
LogicError: If the application configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.llama_stack

@property
def user_data_collection_configuration(self) -> UserDataCollection:
"""Return user data collection configuration."""
"""Return user data collection configuration.

Returns:
UserDataCollection: The configured UserDataCollection object from
the loaded configuration.

Raises:
LogicError: If the application configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.user_data_collection

@property
def mcp_servers(self) -> list[ModelContextProtocolServer]:
"""Return model context protocol servers configuration."""
"""Return model context protocol servers configuration.

Returns:
list[ModelContextProtocolServer]: The list of configured MCP servers.

Raises:
LogicError: If the configuration is not loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.mcp_servers

@property
def authentication_configuration(self) -> AuthenticationConfiguration:
"""Return authentication configuration."""
"""Return authentication configuration.

Returns:
AuthenticationConfiguration: The authentication configuration from
the loaded application configuration.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")

return self._configuration.authentication

@property
def authorization_configuration(self) -> AuthorizationConfiguration:
"""Return authorization configuration or default no-op configuration."""
"""Return authorization configuration or default no-op configuration.

Returns:
AuthorizationConfiguration: The configured authorization settings,
or a default no-op AuthorizationConfiguration when none is
configured.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")

Expand All @@ -127,42 +199,87 @@ def authorization_configuration(self) -> AuthorizationConfiguration:

@property
def customization(self) -> Optional[Customization]:
"""Return customization configuration."""
"""Return customization configuration.

Returns:
customization (Optional[Customization]): The customization
configuration if present, otherwise None.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.customization

@property
def inference(self) -> InferenceConfiguration:
"""Return inference configuration."""
"""Return inference configuration.

Returns:
InferenceConfiguration: The inference configuration from the loaded
application configuration.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.inference

@property
def conversation_cache_configuration(self) -> ConversationHistoryConfiguration:
"""Return conversation cache configuration."""
"""Return conversation cache configuration.

Returns:
ConversationHistoryConfiguration: The conversation cache
configuration from the loaded application configuration.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.conversation_cache

@property
def database_configuration(self) -> DatabaseConfiguration:
"""Return database configuration."""
"""Return database configuration.

Returns:
DatabaseConfiguration: The configured database settings.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.database

@property
def quota_handlers_configuration(self) -> QuotaHandlersConfiguration:
"""Return quota handlers configuration."""
"""Return quota handlers configuration.

Returns:
quota_handlers (QuotaHandlersConfiguration): The configured quota handlers.

Raises:
LogicError: If configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.quota_handlers

@property
def conversation_cache(self) -> Cache:
"""Return the conversation cache."""
"""Return the conversation cache.

Returns:
Cache: The conversation cache instance configured by the loaded configuration.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
if self._conversation_cache is None:
Expand All @@ -173,7 +290,14 @@ def conversation_cache(self) -> Cache:

@property
def quota_limiters(self) -> list[QuotaLimiter]:
"""Return list of all setup quota limiters."""
"""Return list of all setup quota limiters.

Returns:
list[QuotaLimiter]: The quota limiter instances configured for the application.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
if not self._quota_limiters:
Expand Down
33 changes: 31 additions & 2 deletions src/lightspeed_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,20 @@


def create_argument_parser() -> ArgumentParser:
"""Create and configure argument parser object."""
"""Create and configure argument parser object.

The parser includes these options:
- -v / --verbose: enable verbose output
- -d / --dump-configuration: dump the loaded configuration to JSON and exit
- -c / --config: path to the configuration file (default "lightspeed-stack.yaml")
- -g / --generate-llama-stack-configuration: generate a Llama Stack
configuration from the service configuration
- -i / --input-config-file: Llama Stack input configuration filename (default "run.yaml")
- -o / --output-config-file: Llama Stack output configuration filename (default "run_.yaml")

Returns:
Configured ArgumentParser for parsing the service CLI options.
"""
parser = ArgumentParser()
parser.add_argument(
"-v",
Expand Down Expand Up @@ -77,7 +90,23 @@ def create_argument_parser() -> ArgumentParser:


def main() -> None:
"""Entry point to the web service."""
"""Entry point to the web service.

Start the Lightspeed Core Stack service process based on CLI flags and configuration.

Parses command-line arguments, loads the configured settings, and then:
- If --dump-configuration is provided, writes the active configuration to
configuration.json and exits (exits with status 1 on failure).
- If --generate-llama-stack-configuration is provided, generates and stores
the Llama Stack configuration to the specified output file and exits
(exits with status 1 on failure).
- Otherwise, sets LIGHTSPEED_STACK_CONFIG_PATH for worker processes, starts
the quota scheduler, and starts the Uvicorn web service.

Raises:
SystemExit: when configuration dumping or Llama Stack generation fails
(exits with status 1).
"""
logger.info("Lightspeed Core Stack startup")
parser = create_argument_parser()
args = parser.parse_args()
Expand Down
Loading
Loading