Skip to content

Commit 31e20b1

Browse files
committed
remove main
# What does this PR do? ## Test Plan
1 parent ce77c27 commit 31e20b1

File tree

8 files changed

+92
-149
lines changed

8 files changed

+92
-149
lines changed

.github/workflows/providers-build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ jobs:
112112
fi
113113
entrypoint=$(docker inspect --format '{{ .Config.Entrypoint }}' $IMAGE_ID)
114114
echo "Entrypoint: $entrypoint"
115-
if [ "$entrypoint" != "[python -m llama_stack.core.server.server /app/run.yaml]" ]; then
115+
if [ "$entrypoint" != "[llama stack run /app/run.yaml]" ]; then
116116
echo "Entrypoint is not correct"
117117
exit 1
118118
fi
@@ -150,7 +150,7 @@ jobs:
150150
fi
151151
entrypoint=$(docker inspect --format '{{ .Config.Entrypoint }}' $IMAGE_ID)
152152
echo "Entrypoint: $entrypoint"
153-
if [ "$entrypoint" != "[python -m llama_stack.core.server.server /app/run.yaml]" ]; then
153+
if [ "$entrypoint" != "[llama stack run /app/run.yaml]" ]; then
154154
echo "Entrypoint is not correct"
155155
exit 1
156156
fi

docs/docs/concepts/apis/external.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ server:
357357
8. Run the server:
358358

359359
```bash
360-
python -m llama_stack.core.server.server --yaml-config ~/.llama/run-byoa.yaml
360+
llama stack run ~/.llama/run-byoa.yaml
361361
```
362362

363363
9. Test the API:

docs/docs/deploying/kubernetes_deployment.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ spec:
170170
- name: llama-stack
171171
image: localhost/llama-stack-run-k8s:latest
172172
imagePullPolicy: IfNotPresent
173-
command: ["python", "-m", "llama_stack.core.server.server", "--config", "/app/config.yaml"]
173+
command: ["llama", "stack", "run", "/app/config.yaml"]
174174
ports:
175175
- containerPort: 5000
176176
volumeMounts:

docs/docs/distributions/k8s/stack-k8s.yaml.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ spec:
5252
value: "${SAFETY_MODEL}"
5353
- name: TAVILY_SEARCH_API_KEY
5454
value: "${TAVILY_SEARCH_API_KEY}"
55-
command: ["python", "-m", "llama_stack.core.server.server", "/etc/config/stack_run_config.yaml", "--port", "8321"]
55+
command: ["llama", "stack", "run", "/etc/config/stack_run_config.yaml", "--port", "8321"]
5656
ports:
5757
- containerPort: 8321
5858
volumeMounts:

llama_stack/cli/stack/run.py

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@
66

77
import argparse
88
import os
9+
import ssl
910
import subprocess
1011
from pathlib import Path
1112

13+
import uvicorn
14+
import yaml
15+
1216
from llama_stack.cli.stack.utils import ImageType
1317
from llama_stack.cli.subcommand import Subcommand
18+
from llama_stack.core.datatypes import LoggingConfig, StackRunConfig
19+
from llama_stack.core.stack import cast_image_name_to_string, replace_env_vars, validate_env_pair
20+
from llama_stack.core.utils.config_resolution import Mode, resolve_config_or_distro
1421
from llama_stack.log import get_logger
1522

1623
REPO_ROOT = Path(__file__).parent.parent.parent.parent
@@ -146,23 +153,7 @@ def _run_stack_run_cmd(self, args: argparse.Namespace) -> None:
146153
# using the current environment packages.
147154
if not image_type and not image_name:
148155
logger.info("No image type or image name provided. Assuming environment packages.")
149-
from llama_stack.core.server.server import main as server_main
150-
151-
# Build the server args from the current args passed to the CLI
152-
server_args = argparse.Namespace()
153-
for arg in vars(args):
154-
# If this is a function, avoid passing it
155-
# "args" contains:
156-
# func=<bound method StackRun._run_stack_run_cmd of <llama_stack.cli.stack.run.StackRun object at 0x10484b010>>
157-
if callable(getattr(args, arg)):
158-
continue
159-
if arg == "config":
160-
server_args.config = str(config_file)
161-
else:
162-
setattr(server_args, arg, getattr(args, arg))
163-
164-
# Run the server
165-
server_main(server_args)
156+
self._uvicorn_run(config_file, args)
166157
else:
167158
run_args = formulate_run_args(image_type, image_name)
168159

@@ -184,6 +175,76 @@ def _run_stack_run_cmd(self, args: argparse.Namespace) -> None:
184175

185176
run_command(run_args)
186177

178+
def _uvicorn_run(self, config_file: Path | None, args: argparse.Namespace) -> None:
179+
if not config_file:
180+
self.parser.error("Config file is required")
181+
182+
# Set environment variables if provided
183+
if args.env:
184+
for env_pair in args.env:
185+
try:
186+
key, value = validate_env_pair(env_pair)
187+
logger.info(f"Setting environment variable {key} => {value}")
188+
os.environ[key] = value
189+
except ValueError as e:
190+
logger.error(f"Error: {str(e)}")
191+
self.parser.error(f"Invalid environment variable format: {env_pair}")
192+
193+
config_file = resolve_config_or_distro(str(config_file), Mode.RUN)
194+
with open(config_file) as fp:
195+
config_contents = yaml.safe_load(fp)
196+
if isinstance(config_contents, dict) and (cfg := config_contents.get("logging_config")):
197+
logger_config = LoggingConfig(**cfg)
198+
else:
199+
logger_config = None
200+
config = StackRunConfig(**cast_image_name_to_string(replace_env_vars(config_contents)))
201+
202+
port = args.port or config.server.port
203+
host = config.server.host or "::"
204+
205+
# Set the config file in environment so create_app can find it
206+
os.environ["LLAMA_STACK_CONFIG"] = str(config_file)
207+
208+
uvicorn_config = {
209+
"factory": True,
210+
"host": host,
211+
"port": port,
212+
"lifespan": "on",
213+
"log_level": logger.getEffectiveLevel(),
214+
"log_config": logger_config,
215+
}
216+
217+
keyfile = config.server.tls_keyfile
218+
certfile = config.server.tls_certfile
219+
if keyfile and certfile:
220+
uvicorn_config["ssl_keyfile"] = config.server.tls_keyfile
221+
uvicorn_config["ssl_certfile"] = config.server.tls_certfile
222+
if config.server.tls_cafile:
223+
uvicorn_config["ssl_ca_certs"] = config.server.tls_cafile
224+
uvicorn_config["ssl_cert_reqs"] = ssl.CERT_REQUIRED
225+
226+
logger.info(
227+
f"HTTPS enabled with certificates:\n Key: {keyfile}\n Cert: {certfile}\n CA: {config.server.tls_cafile}"
228+
)
229+
else:
230+
logger.info(f"HTTPS enabled with certificates:\n Key: {keyfile}\n Cert: {certfile}")
231+
232+
logger.info(f"Listening on {host}:{port}")
233+
234+
# We need to catch KeyboardInterrupt because uvicorn's signal handling
235+
# re-raises SIGINT signals using signal.raise_signal(), which Python
236+
# converts to KeyboardInterrupt. Without this catch, we'd get a confusing
237+
# stack trace when using Ctrl+C or kill -2 (SIGINT).
238+
# SIGTERM (kill -15) works fine without this because Python doesn't
239+
# have a default handler for it.
240+
#
241+
# Another approach would be to ignore SIGINT entirely - let uvicorn handle it through its own
242+
# signal handling but this is quite intrusive and not worth the effort.
243+
try:
244+
uvicorn.run("llama_stack.core.server.server:create_app", **uvicorn_config)
245+
except (KeyboardInterrupt, SystemExit):
246+
logger.info("Received interrupt signal, shutting down gracefully...")
247+
187248
def _start_ui_development_server(self, stack_server_port: int):
188249
logger.info("Attempting to start UI development server...")
189250
# Check if npm is available

llama_stack/core/build_container.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,14 +324,14 @@ fi
324324
RUN pip uninstall -y uv
325325
EOF
326326

327-
# If a run config is provided, we use the --config flag
327+
# If a run config is provided, we use the llama stack CLI
328328
if [[ -n "$run_config" ]]; then
329329
add_to_container << EOF
330-
ENTRYPOINT ["python", "-m", "llama_stack.core.server.server", "$RUN_CONFIG_PATH"]
330+
ENTRYPOINT ["llama", "stack", "run", "$RUN_CONFIG_PATH"]
331331
EOF
332332
elif [[ "$distro_or_config" != *.yaml ]]; then
333333
add_to_container << EOF
334-
ENTRYPOINT ["python", "-m", "llama_stack.core.server.server", "$distro_or_config"]
334+
ENTRYPOINT ["llama", "stack", "run", "$distro_or_config"]
335335
EOF
336336
fi
337337

llama_stack/core/server/server.py

Lines changed: 5 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
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
87
import asyncio
98
import concurrent.futures
109
import functools
1110
import inspect
1211
import json
1312
import logging # allow-direct-logging
1413
import os
15-
import ssl
1614
import sys
1715
import traceback
1816
import warnings
@@ -35,7 +33,6 @@
3533

3634
from llama_stack.apis.common.errors import ConflictError, ResourceNotFoundError
3735
from llama_stack.apis.common.responses import PaginatedResponse
38-
from llama_stack.cli.utils import add_config_distro_args, get_config_from_args
3936
from llama_stack.core.access_control.access_control import AccessDeniedError
4037
from llama_stack.core.datatypes import (
4138
AuthenticationRequiredError,
@@ -55,7 +52,6 @@
5552
Stack,
5653
cast_image_name_to_string,
5754
replace_env_vars,
58-
validate_env_pair,
5955
)
6056
from llama_stack.core.utils.config import redact_sensitive_fields
6157
from 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-
592478
def _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()

llama_stack/core/start_stack.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ if [[ "$env_type" == "venv" ]]; then
116116
yaml_config_arg=""
117117
fi
118118

119-
$PYTHON_BINARY -m llama_stack.core.server.server \
119+
llama stack run \
120120
$yaml_config_arg \
121121
--port "$port" \
122122
$env_vars \

0 commit comments

Comments
 (0)