From 67d0c0adf05f74b99afe7e311151577e9d55c1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Tue, 25 Feb 2025 15:08:58 +0100 Subject: [PATCH] feat(server): Use system packages for execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users prefer to rely on the main CLI rather than invoking the server through a Python module. Users interact with a high-level CLI rather than needing to know internal module structures. Now, when running llama stack run , the server will attempt to use the system package or a virtual environment if one is active. This also eliminates the current process dependency chain when running from a virtual environment: -> llama stack run -> start_env.sh -> python -m server... Signed-off-by: Sébastien Han --- llama_stack/cli/stack/run.py | 58 +++++++++++++++-------- llama_stack/distribution/server/server.py | 29 +++++++++--- 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/llama_stack/cli/stack/run.py b/llama_stack/cli/stack/run.py index 7d6af8120b..b6e42fb217 100644 --- a/llama_stack/cli/stack/run.py +++ b/llama_stack/cli/stack/run.py @@ -55,7 +55,6 @@ def _add_arguments(self): "--env", action="append", help="Environment variables to pass to the server in KEY=VALUE format. Can be specified multiple times.", - default=[], metavar="KEY=VALUE", ) self.parser.add_argument( @@ -73,7 +72,6 @@ def _add_arguments(self): type=str, help="Image Type used during the build. This can be either conda or container or venv.", choices=["conda", "container", "venv"], - default="conda", ) def _run_stack_run_cmd(self, args: argparse.Namespace) -> None: @@ -131,20 +129,42 @@ def _run_stack_run_cmd(self, args: argparse.Namespace) -> None: except AttributeError as e: self.parser.error(f"failed to parse config file '{config_file}':\n {e}") - run_args = formulate_run_args(args.image_type, args.image_name, config, template_name) - - run_args.extend([str(config_file), str(args.port)]) - if args.disable_ipv6: - run_args.append("--disable-ipv6") - - for env_var in args.env: - if "=" not in env_var: - self.parser.error(f"Environment variable '{env_var}' must be in KEY=VALUE format") - key, value = env_var.split("=", 1) # split on first = only - if not key: - self.parser.error(f"Environment variable '{env_var}' has empty key") - run_args.extend(["--env", f"{key}={value}"]) - - if args.tls_keyfile and args.tls_certfile: - run_args.extend(["--tls-keyfile", args.tls_keyfile, "--tls-certfile", args.tls_certfile]) - run_with_pty(run_args) + # If neither image type nor image name is provided, assume the server should be run directly + # using the current environment packages. + if not args.image_type and not args.image_name: + logger.info("No image type or image name provided. Assuming environment packages.") + from llama_stack.distribution.server.server import main as server_main + + # Build the server args from the current args passed to the CLI + server_args = argparse.Namespace() + for arg in vars(args): + # If this is a function, avoid passing it + # "args" contains: + # func=> + if callable(getattr(args, arg)): + continue + setattr(server_args, arg, getattr(args, arg)) + + # Run the server + server_main(server_args) + else: + run_args = formulate_run_args(args.image_type, args.image_name, config, template_name) + + run_args.extend([str(config_file), str(args.port)]) + if args.disable_ipv6: + run_args.append("--disable-ipv6") + + if args.env: + for env_var in args.env: + if "=" not in env_var: + self.parser.error(f"Environment variable '{env_var}' must be in KEY=VALUE format") + return + key, value = env_var.split("=", 1) # split on first = only + if not key: + self.parser.error(f"Environment variable '{env_var}' has empty key") + return + run_args.extend(["--env", f"{key}={value}"]) + + if args.tls_keyfile and args.tls_certfile: + run_args.extend(["--tls-keyfile", args.tls_keyfile, "--tls-certfile", args.tls_certfile]) + run_with_pty(run_args) diff --git a/llama_stack/distribution/server/server.py b/llama_stack/distribution/server/server.py index 4b70e00873..9ad71f83e5 100644 --- a/llama_stack/distribution/server/server.py +++ b/llama_stack/distribution/server/server.py @@ -312,13 +312,18 @@ async def send_version_error(send): return await self.app(scope, receive, send) -def main(): - logcat.init() - +def main(args: argparse.Namespace = None): """Start the LlamaStack server.""" + logcat.init() parser = argparse.ArgumentParser(description="Start the LlamaStack server.") parser.add_argument( "--yaml-config", + dest="config", + help="(Deprecated) Path to YAML configuration file - use --config instead", + ) + parser.add_argument( + "--config", + dest="config", help="Path to YAML configuration file", ) parser.add_argument( @@ -348,7 +353,19 @@ def main(): required="--tls-keyfile" in sys.argv, ) - args = parser.parse_args() + # Determine whether the server args are being passed by the "run" command, if this is the case + # the args will be passed as a Namespace object to the main function, otherwise they will be + # parsed from the command line + if args is None: + args = parser.parse_args() + + # Check for deprecated argument usage + if "--yaml-config" in sys.argv: + warnings.warn( + "The '--yaml-config' argument is deprecated and will be removed in a future version. Use '--config' instead.", + DeprecationWarning, + stacklevel=2, + ) if args.env: for env_pair in args.env: @@ -360,9 +377,9 @@ def main(): logcat.error("server", f"Error: {str(e)}") sys.exit(1) - if args.yaml_config: + if args.config: # if the user provided a config file, use it, even if template was specified - config_file = Path(args.yaml_config) + config_file = Path(args.config) if not config_file.exists(): raise ValueError(f"Config file {config_file} does not exist") logcat.info("server", f"Using config file: {config_file}")