44# This source code is licensed under the terms described in the LICENSE file in
55# the root directory of this source tree.
66
7- import argparse
87import asyncio
98import concurrent .futures
109import functools
1110import inspect
1211import json
1312import logging # allow-direct-logging
1413import os
15- import ssl
1614import sys
1715import traceback
1816import warnings
3533
3634from llama_stack .apis .common .errors import ConflictError , ResourceNotFoundError
3735from llama_stack .apis .common .responses import PaginatedResponse
38- from llama_stack .cli .utils import add_config_distro_args , get_config_from_args
3936from llama_stack .core .access_control .access_control import AccessDeniedError
4037from llama_stack .core .datatypes import (
4138 AuthenticationRequiredError ,
5552 Stack ,
5653 cast_image_name_to_string ,
5754 replace_env_vars ,
58- validate_env_pair ,
5955)
6056from llama_stack .core .utils .config import redact_sensitive_fields
6157from llama_stack .core .utils .config_resolution import Mode , resolve_config_or_distro
@@ -333,23 +329,18 @@ async def send_version_error(send):
333329 return await self .app (scope , receive , send )
334330
335331
336- def create_app (
337- config_file : str | None = None ,
338- env_vars : list [str ] | None = None ,
339- ) -> StackApp :
332+ def create_app () -> StackApp :
340333 """Create and configure the FastAPI application.
341334
342- Args:
343- config_file: Path to config file. If None, uses LLAMA_STACK_CONFIG env var or default resolution.
344- env_vars: List of environment variables in KEY=value format.
345- disable_version_check: Whether to disable version checking. If None, uses LLAMA_STACK_DISABLE_VERSION_CHECK env var.
335+ This factory function reads configuration from environment variables:
336+ - LLAMA_STACK_CONFIG: Path to config file (required)
346337
347338 Returns:
348339 Configured StackApp instance.
349340 """
350- config_file = config_file or os .getenv ("LLAMA_STACK_CONFIG" )
341+ config_file = os .getenv ("LLAMA_STACK_CONFIG" )
351342 if config_file is None :
352- raise ValueError ("No config file provided and LLAMA_STACK_CONFIG env var is not set " )
343+ raise ValueError ("LLAMA_STACK_CONFIG environment variable is required " )
353344
354345 config_file = resolve_config_or_distro (config_file , Mode .RUN )
355346
@@ -361,16 +352,6 @@ def create_app(
361352 logger_config = LoggingConfig (** cfg )
362353 logger = get_logger (name = __name__ , category = "core::server" , config = logger_config )
363354
364- if env_vars :
365- for env_pair in env_vars :
366- try :
367- key , value = validate_env_pair (env_pair )
368- logger .info (f"Setting environment variable { key } => { value } " )
369- os .environ [key ] = value
370- except ValueError as e :
371- logger .error (f"Error: { str (e )} " )
372- raise ValueError (f"Invalid environment variable format: { env_pair } " ) from e
373-
374355 config = replace_env_vars (config_contents )
375356 config = StackRunConfig (** cast_image_name_to_string (config ))
376357
@@ -494,101 +475,6 @@ def create_app(
494475 return app
495476
496477
497- def main (args : argparse .Namespace | None = None ):
498- """Start the LlamaStack server."""
499- parser = argparse .ArgumentParser (description = "Start the LlamaStack server." )
500-
501- add_config_distro_args (parser )
502- parser .add_argument (
503- "--port" ,
504- type = int ,
505- default = int (os .getenv ("LLAMA_STACK_PORT" , 8321 )),
506- help = "Port to listen on" ,
507- )
508- parser .add_argument (
509- "--env" ,
510- action = "append" ,
511- help = "Environment variables in KEY=value format. Can be specified multiple times." ,
512- )
513-
514- # Determine whether the server args are being passed by the "run" command, if this is the case
515- # the args will be passed as a Namespace object to the main function, otherwise they will be
516- # parsed from the command line
517- if args is None :
518- args = parser .parse_args ()
519-
520- config_or_distro = get_config_from_args (args )
521-
522- try :
523- app = create_app (
524- config_file = config_or_distro ,
525- env_vars = args .env ,
526- )
527- except Exception as e :
528- logger .error (f"Error creating app: { str (e )} " )
529- sys .exit (1 )
530-
531- config_file = resolve_config_or_distro (config_or_distro , Mode .RUN )
532- with open (config_file ) as fp :
533- config_contents = yaml .safe_load (fp )
534- if isinstance (config_contents , dict ) and (cfg := config_contents .get ("logging_config" )):
535- logger_config = LoggingConfig (** cfg )
536- else :
537- logger_config = None
538- config = StackRunConfig (** cast_image_name_to_string (replace_env_vars (config_contents )))
539-
540- import uvicorn
541-
542- # Configure SSL if certificates are provided
543- port = args .port or config .server .port
544-
545- ssl_config = None
546- keyfile = config .server .tls_keyfile
547- certfile = config .server .tls_certfile
548-
549- if keyfile and certfile :
550- ssl_config = {
551- "ssl_keyfile" : keyfile ,
552- "ssl_certfile" : certfile ,
553- }
554- if config .server .tls_cafile :
555- ssl_config ["ssl_ca_certs" ] = config .server .tls_cafile
556- ssl_config ["ssl_cert_reqs" ] = ssl .CERT_REQUIRED
557- logger .info (
558- f"HTTPS enabled with certificates:\n Key: { keyfile } \n Cert: { certfile } \n CA: { config .server .tls_cafile } "
559- )
560- else :
561- logger .info (f"HTTPS enabled with certificates:\n Key: { keyfile } \n Cert: { certfile } " )
562-
563- listen_host = config .server .host or ["::" , "0.0.0.0" ]
564- logger .info (f"Listening on { listen_host } :{ port } " )
565-
566- uvicorn_config = {
567- "app" : app ,
568- "host" : listen_host ,
569- "port" : port ,
570- "lifespan" : "on" ,
571- "log_level" : logger .getEffectiveLevel (),
572- "log_config" : logger_config ,
573- }
574- if ssl_config :
575- uvicorn_config .update (ssl_config )
576-
577- # We need to catch KeyboardInterrupt because uvicorn's signal handling
578- # re-raises SIGINT signals using signal.raise_signal(), which Python
579- # converts to KeyboardInterrupt. Without this catch, we'd get a confusing
580- # stack trace when using Ctrl+C or kill -2 (SIGINT).
581- # SIGTERM (kill -15) works fine without this because Python doesn't
582- # have a default handler for it.
583- #
584- # Another approach would be to ignore SIGINT entirely - let uvicorn handle it through its own
585- # signal handling but this is quite intrusive and not worth the effort.
586- try :
587- asyncio .run (uvicorn .Server (uvicorn .Config (** uvicorn_config )).serve ())
588- except (KeyboardInterrupt , SystemExit ):
589- logger .info ("Received interrupt signal, shutting down gracefully..." )
590-
591-
592478def _log_run_config (run_config : StackRunConfig ):
593479 """Logs the run config with redacted fields and disabled providers removed."""
594480 logger .info ("Run configuration:" )
@@ -615,7 +501,3 @@ def remove_disabled_providers(obj):
615501 return [item for item in (remove_disabled_providers (i ) for i in obj ) if item is not None ]
616502 else :
617503 return obj
618-
619-
620- if __name__ == "__main__" :
621- main ()
0 commit comments