187 lines
5.0 KiB
Python
187 lines
5.0 KiB
Python
"""Cloud backend configurations for E2E tests.
|
|
|
|
This module handles cloud API backends (OpenAI, xAI) that don't need local GPU workers.
|
|
For local backends (gRPC, HTTP), use ModelPool from infra/ to launch workers,
|
|
then launch the router separately pointing to those workers.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from infra import get_open_port, kill_process_tree, wait_for_health
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class RouterInstance:
|
|
"""A running router instance (for cloud backends)."""
|
|
|
|
base_url: str
|
|
router_process: subprocess.Popen
|
|
backend: str
|
|
|
|
def shutdown(self) -> None:
|
|
"""Shutdown the router."""
|
|
if self.router_process.poll() is None:
|
|
kill_process_tree(self.router_process.pid)
|
|
|
|
|
|
def launch_cloud_router(
|
|
backend: str, # "openai" or "xai"
|
|
*,
|
|
history_backend: str = "memory",
|
|
router_args: list[str] | None = None,
|
|
timeout: float = 60,
|
|
show_output: bool | None = None,
|
|
) -> RouterInstance:
|
|
"""Launch router with cloud API backend (OpenAI/xAI).
|
|
|
|
Args:
|
|
backend: "openai" or "xai"
|
|
history_backend: "memory" or "oracle"
|
|
router_args: Additional router arguments
|
|
timeout: Startup timeout in seconds
|
|
show_output: Show subprocess output
|
|
|
|
Returns:
|
|
RouterInstance with running router
|
|
"""
|
|
if show_output is None:
|
|
show_output = os.environ.get("SHOW_ROUTER_LOGS", "0") == "1"
|
|
|
|
router_port = get_open_port()
|
|
prometheus_port = get_open_port()
|
|
base_url = f"http://127.0.0.1:{router_port}"
|
|
|
|
# Get API key and worker URL
|
|
if backend == "openai":
|
|
worker_url = "https://api.openai.com"
|
|
api_key = os.environ.get("OPENAI_API_KEY")
|
|
if not api_key:
|
|
raise ValueError("OPENAI_API_KEY environment variable required")
|
|
elif backend == "xai":
|
|
worker_url = "https://api.x.ai"
|
|
api_key = os.environ.get("XAI_API_KEY")
|
|
if not api_key:
|
|
raise ValueError("XAI_API_KEY environment variable required")
|
|
else:
|
|
raise ValueError(f"Unsupported cloud backend: {backend}")
|
|
|
|
logger.info("Launching %s router on port %d", backend, router_port)
|
|
|
|
cmd = [
|
|
"python3",
|
|
"-m",
|
|
"sglang_router.launch_router",
|
|
"--host",
|
|
"127.0.0.1",
|
|
"--port",
|
|
str(router_port),
|
|
"--prometheus-port",
|
|
str(prometheus_port),
|
|
"--backend",
|
|
"openai",
|
|
"--worker-urls",
|
|
worker_url,
|
|
"--history-backend",
|
|
history_backend,
|
|
"--log-level",
|
|
"warn",
|
|
]
|
|
|
|
if router_args:
|
|
cmd.extend(router_args)
|
|
|
|
env = os.environ.copy()
|
|
if backend == "openai":
|
|
env["OPENAI_API_KEY"] = api_key
|
|
else:
|
|
env["XAI_API_KEY"] = api_key
|
|
|
|
router_proc = subprocess.Popen(
|
|
cmd,
|
|
env=env,
|
|
stdout=None if show_output else subprocess.PIPE,
|
|
stderr=None if show_output else subprocess.PIPE,
|
|
start_new_session=True,
|
|
)
|
|
|
|
try:
|
|
wait_for_health(base_url, timeout=timeout)
|
|
except TimeoutError:
|
|
kill_process_tree(router_proc.pid)
|
|
raise
|
|
|
|
logger.info("%s router ready at %s", backend, base_url)
|
|
|
|
return RouterInstance(
|
|
base_url=base_url,
|
|
router_process=router_proc,
|
|
backend=backend,
|
|
)
|
|
|
|
|
|
# Cloud backend configurations
|
|
CLOUD_BACKENDS: dict[str, dict[str, Any]] = {
|
|
"openai": {
|
|
"description": "OpenAI API backend",
|
|
"model": "gpt-4o-mini",
|
|
"api_key_env": "OPENAI_API_KEY",
|
|
"history_backend": "memory",
|
|
},
|
|
"xai": {
|
|
"description": "xAI API backend",
|
|
"model": "grok-2-latest",
|
|
"api_key_env": "XAI_API_KEY",
|
|
"history_backend": "memory",
|
|
},
|
|
"oracle_store": {
|
|
"description": "OpenAI API with Oracle history backend",
|
|
"model": "gpt-4o-mini",
|
|
"api_key_env": "OPENAI_API_KEY",
|
|
"history_backend": "oracle",
|
|
},
|
|
}
|
|
|
|
|
|
def get_cloud_backend_config(backend: str) -> dict[str, Any]:
|
|
"""Get configuration for a cloud backend."""
|
|
if backend not in CLOUD_BACKENDS:
|
|
raise KeyError(
|
|
f"Unknown cloud backend: {backend}. Available: {list(CLOUD_BACKENDS.keys())}"
|
|
)
|
|
return CLOUD_BACKENDS[backend]
|
|
|
|
|
|
def launch_cloud_backend(backend: str, **kwargs: Any) -> RouterInstance:
|
|
"""Launch a cloud backend router.
|
|
|
|
Args:
|
|
backend: Backend name from CLOUD_BACKENDS
|
|
**kwargs: Override launcher kwargs
|
|
|
|
Returns:
|
|
RouterInstance with running router
|
|
"""
|
|
cfg = get_cloud_backend_config(backend)
|
|
|
|
# Determine actual backend type (openai or xai)
|
|
if backend == "oracle_store":
|
|
actual_backend = "openai"
|
|
else:
|
|
actual_backend = backend
|
|
|
|
history_backend = kwargs.pop("history_backend", cfg["history_backend"])
|
|
|
|
return launch_cloud_router(
|
|
actual_backend,
|
|
history_backend=history_backend,
|
|
**kwargs,
|
|
)
|