[smg][ci] rename 3rd models from cloud backend and delete dead code (#16692)
This commit is contained in:
@@ -1,87 +0,0 @@
|
|||||||
"""Cloud runtime configurations for E2E tests.
|
|
||||||
|
|
||||||
This module handles cloud API runtimes (OpenAI, xAI) that don't need local GPU workers.
|
|
||||||
For local runtimes (gRPC, HTTP), use ModelPool from infra/ to launch workers,
|
|
||||||
then launch the gateway separately pointing to those workers.
|
|
||||||
|
|
||||||
Cloud runtimes vs History backends:
|
|
||||||
- Cloud runtimes: Where models run (openai, xai)
|
|
||||||
- History backends: Gateway plugin for conversation storage (memory, oracle)
|
|
||||||
These are orthogonal - any cloud runtime can use any history backend.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from infra import Gateway
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Cloud runtime configurations (where models run)
|
|
||||||
CLOUD_RUNTIMES: dict[str, dict[str, Any]] = {
|
|
||||||
"openai": {
|
|
||||||
"description": "OpenAI API",
|
|
||||||
"model": "gpt-5-nano",
|
|
||||||
"api_key_env": "OPENAI_API_KEY",
|
|
||||||
},
|
|
||||||
"xai": {
|
|
||||||
"description": "xAI API",
|
|
||||||
"model": "grok-2-latest",
|
|
||||||
"api_key_env": "XAI_API_KEY",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Backward compatibility alias
|
|
||||||
CLOUD_BACKENDS = CLOUD_RUNTIMES
|
|
||||||
|
|
||||||
|
|
||||||
def get_cloud_runtime_config(runtime: str) -> dict[str, Any]:
|
|
||||||
"""Get configuration for a cloud runtime."""
|
|
||||||
if runtime not in CLOUD_RUNTIMES:
|
|
||||||
raise KeyError(
|
|
||||||
f"Unknown cloud runtime: {runtime}. Available: {list(CLOUD_RUNTIMES.keys())}"
|
|
||||||
)
|
|
||||||
return CLOUD_RUNTIMES[runtime]
|
|
||||||
|
|
||||||
|
|
||||||
def launch_cloud_gateway(
|
|
||||||
runtime: str, # "openai" or "xai"
|
|
||||||
*,
|
|
||||||
history_backend: str = "memory",
|
|
||||||
extra_args: list[str] | None = None,
|
|
||||||
timeout: float = 60,
|
|
||||||
show_output: bool | None = None,
|
|
||||||
) -> Gateway:
|
|
||||||
"""Launch gateway with cloud API runtime.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
runtime: Cloud runtime ("openai" or "xai")
|
|
||||||
history_backend: History storage backend ("memory" or "oracle")
|
|
||||||
extra_args: Additional router arguments
|
|
||||||
timeout: Startup timeout in seconds
|
|
||||||
show_output: Show subprocess output
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Gateway instance with running router
|
|
||||||
"""
|
|
||||||
if runtime not in CLOUD_RUNTIMES:
|
|
||||||
raise ValueError(f"Unknown cloud runtime: {runtime}")
|
|
||||||
|
|
||||||
gateway = Gateway()
|
|
||||||
gateway.start(
|
|
||||||
cloud_backend=runtime,
|
|
||||||
history_backend=history_backend,
|
|
||||||
timeout=timeout,
|
|
||||||
show_output=show_output,
|
|
||||||
extra_args=extra_args,
|
|
||||||
)
|
|
||||||
return gateway
|
|
||||||
|
|
||||||
|
|
||||||
# Backward compatibility aliases
|
|
||||||
get_cloud_backend_config = get_cloud_runtime_config
|
|
||||||
launch_cloud_backend = launch_cloud_gateway
|
|
||||||
launch_cloud_router = launch_cloud_gateway
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
"""Legacy router manager for integration tests.
|
|
||||||
|
|
||||||
DEPRECATED: This module will be removed during e2e_response_api migration.
|
|
||||||
Use infra.Gateway instead for all router management.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from infra import wait_for_health
|
|
||||||
|
|
||||||
from .ports import find_free_port
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ProcHandle:
|
|
||||||
process: subprocess.Popen
|
|
||||||
url: str
|
|
||||||
|
|
||||||
|
|
||||||
class RouterManager:
|
|
||||||
"""Helper to spawn a router process and interact with admin endpoints."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._children: List[subprocess.Popen] = []
|
|
||||||
|
|
||||||
def start_router(
|
|
||||||
self,
|
|
||||||
worker_urls: Optional[List[str]] = None,
|
|
||||||
policy: str = "round_robin",
|
|
||||||
port: Optional[int] = None,
|
|
||||||
extra: Optional[Dict] = None,
|
|
||||||
# PD options
|
|
||||||
pd_disaggregation: bool = False,
|
|
||||||
prefill_urls: Optional[List[tuple]] = None,
|
|
||||||
decode_urls: Optional[List[str]] = None,
|
|
||||||
prefill_policy: Optional[str] = None,
|
|
||||||
decode_policy: Optional[str] = None,
|
|
||||||
) -> ProcHandle:
|
|
||||||
worker_urls = worker_urls or []
|
|
||||||
port = port or find_free_port()
|
|
||||||
cmd = [
|
|
||||||
"python3",
|
|
||||||
"-m",
|
|
||||||
"sglang_router.launch_router",
|
|
||||||
"--host",
|
|
||||||
"127.0.0.1",
|
|
||||||
"--port",
|
|
||||||
str(port),
|
|
||||||
"--policy",
|
|
||||||
policy,
|
|
||||||
]
|
|
||||||
# Avoid Prometheus port collisions by assigning a free port per router
|
|
||||||
prom_port = find_free_port()
|
|
||||||
cmd.extend(
|
|
||||||
["--prometheus-port", str(prom_port), "--prometheus-host", "127.0.0.1"]
|
|
||||||
)
|
|
||||||
if worker_urls:
|
|
||||||
cmd.extend(["--worker-urls", *worker_urls])
|
|
||||||
|
|
||||||
# PD routing configuration
|
|
||||||
if pd_disaggregation:
|
|
||||||
cmd.append("--pd-disaggregation")
|
|
||||||
if prefill_urls:
|
|
||||||
for url, bport in prefill_urls:
|
|
||||||
if bport is None:
|
|
||||||
cmd.extend(["--prefill", url, "none"])
|
|
||||||
else:
|
|
||||||
cmd.extend(["--prefill", url, str(bport)])
|
|
||||||
if decode_urls:
|
|
||||||
for url in decode_urls:
|
|
||||||
cmd.extend(["--decode", url])
|
|
||||||
if prefill_policy:
|
|
||||||
cmd.extend(["--prefill-policy", prefill_policy])
|
|
||||||
if decode_policy:
|
|
||||||
cmd.extend(["--decode-policy", decode_policy])
|
|
||||||
|
|
||||||
# Map supported extras to CLI flags (subset for integration)
|
|
||||||
if extra:
|
|
||||||
flag_map = {
|
|
||||||
"max_payload_size": "--max-payload-size",
|
|
||||||
"dp_aware": "--dp-aware",
|
|
||||||
"api_key": "--api-key",
|
|
||||||
# Health/monitoring
|
|
||||||
"worker_startup_check_interval": "--worker-startup-check-interval",
|
|
||||||
# Cache-aware tuning
|
|
||||||
"cache_threshold": "--cache-threshold",
|
|
||||||
"balance_abs_threshold": "--balance-abs-threshold",
|
|
||||||
"balance_rel_threshold": "--balance-rel-threshold",
|
|
||||||
# Retry
|
|
||||||
"retry_max_retries": "--retry-max-retries",
|
|
||||||
"retry_initial_backoff_ms": "--retry-initial-backoff-ms",
|
|
||||||
"retry_max_backoff_ms": "--retry-max-backoff-ms",
|
|
||||||
"retry_backoff_multiplier": "--retry-backoff-multiplier",
|
|
||||||
"retry_jitter_factor": "--retry-jitter-factor",
|
|
||||||
"disable_retries": "--disable-retries",
|
|
||||||
# Circuit breaker
|
|
||||||
"cb_failure_threshold": "--cb-failure-threshold",
|
|
||||||
"cb_success_threshold": "--cb-success-threshold",
|
|
||||||
"cb_timeout_duration_secs": "--cb-timeout-duration-secs",
|
|
||||||
"cb_window_duration_secs": "--cb-window-duration-secs",
|
|
||||||
"disable_circuit_breaker": "--disable-circuit-breaker",
|
|
||||||
# Rate limiting
|
|
||||||
"max_concurrent_requests": "--max-concurrent-requests",
|
|
||||||
"queue_size": "--queue-size",
|
|
||||||
"queue_timeout_secs": "--queue-timeout-secs",
|
|
||||||
"rate_limit_tokens_per_second": "--rate-limit-tokens-per-second",
|
|
||||||
# mTLS configuration
|
|
||||||
"client_cert_path": "--client-cert-path",
|
|
||||||
"client_key_path": "--client-key-path",
|
|
||||||
"ca_cert_paths": "--ca-cert-paths",
|
|
||||||
}
|
|
||||||
for k, v in extra.items():
|
|
||||||
if v is None:
|
|
||||||
continue
|
|
||||||
flag = flag_map.get(k)
|
|
||||||
if not flag:
|
|
||||||
continue
|
|
||||||
if isinstance(v, bool):
|
|
||||||
if v:
|
|
||||||
cmd.append(flag)
|
|
||||||
elif isinstance(v, list):
|
|
||||||
# Handle list arguments (e.g., ca_cert_paths)
|
|
||||||
if v: # Only add if list is not empty
|
|
||||||
cmd.append(flag)
|
|
||||||
cmd.extend([str(item) for item in v])
|
|
||||||
else:
|
|
||||||
cmd.extend([flag, str(v)])
|
|
||||||
|
|
||||||
proc = subprocess.Popen(cmd)
|
|
||||||
self._children.append(proc)
|
|
||||||
url = f"http://127.0.0.1:{port}"
|
|
||||||
wait_for_health(url, timeout=30.0, check_interval=0.2)
|
|
||||||
return ProcHandle(process=proc, url=url)
|
|
||||||
|
|
||||||
def add_worker(self, base_url: str, worker_url: str, timeout: float = 30.0) -> None:
|
|
||||||
r = requests.post(f"{base_url}/workers", json={"url": worker_url})
|
|
||||||
assert (
|
|
||||||
r.status_code == 202
|
|
||||||
), f"add_worker failed: {r.status_code} {r.text}" # ACCEPTED status
|
|
||||||
|
|
||||||
payload = r.json()
|
|
||||||
worker_id = payload.get("worker_id")
|
|
||||||
assert worker_id, f"add_worker did not return worker_id: {payload}"
|
|
||||||
|
|
||||||
# Poll until worker is actually added and healthy
|
|
||||||
start = time.time()
|
|
||||||
with requests.Session() as s:
|
|
||||||
while time.time() - start < timeout:
|
|
||||||
try:
|
|
||||||
r = s.get(f"{base_url}/workers/{worker_id}", timeout=2)
|
|
||||||
if r.status_code == 200:
|
|
||||||
data = r.json()
|
|
||||||
# Check if registration job failed
|
|
||||||
job_status = data.get("job_status")
|
|
||||||
if job_status and job_status.get("state") == "failed":
|
|
||||||
raise RuntimeError(
|
|
||||||
f"Worker registration failed: {job_status.get('message', 'Unknown error')}"
|
|
||||||
)
|
|
||||||
# Check if worker is healthy and registered (not just in job queue)
|
|
||||||
if data.get("is_healthy", False):
|
|
||||||
return
|
|
||||||
# Worker not ready yet, continue polling
|
|
||||||
except requests.RequestException:
|
|
||||||
pass
|
|
||||||
time.sleep(0.1)
|
|
||||||
raise TimeoutError(
|
|
||||||
f"Worker {worker_url} was not added and healthy after {timeout}s"
|
|
||||||
)
|
|
||||||
|
|
||||||
def remove_worker(
|
|
||||||
self, base_url: str, worker_url: str, timeout: float = 30.0
|
|
||||||
) -> None:
|
|
||||||
# Resolve worker_id from the current registry snapshot
|
|
||||||
r_list = requests.get(f"{base_url}/workers")
|
|
||||||
assert (
|
|
||||||
r_list.status_code == 200
|
|
||||||
), f"list_workers failed: {r_list.status_code} {r_list.text}"
|
|
||||||
workers = r_list.json().get("workers", [])
|
|
||||||
worker_id = next(
|
|
||||||
(w.get("id") for w in workers if w.get("url") == worker_url), None
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
worker_id
|
|
||||||
), f"could not find worker_id for url={worker_url}. workers={workers}"
|
|
||||||
|
|
||||||
r = requests.delete(f"{base_url}/workers/{worker_id}")
|
|
||||||
assert (
|
|
||||||
r.status_code == 202
|
|
||||||
), f"remove_worker failed: {r.status_code} {r.text}" # ACCEPTED status
|
|
||||||
|
|
||||||
# Poll until worker is actually removed (GET returns 404) or timeout
|
|
||||||
start = time.time()
|
|
||||||
last_status = None
|
|
||||||
with requests.Session() as s:
|
|
||||||
while time.time() - start < timeout:
|
|
||||||
try:
|
|
||||||
r = s.get(f"{base_url}/workers/{worker_id}", timeout=2)
|
|
||||||
if r.status_code == 404:
|
|
||||||
# Worker successfully removed
|
|
||||||
return
|
|
||||||
elif r.status_code == 200:
|
|
||||||
# Check if removal job failed
|
|
||||||
data = r.json()
|
|
||||||
job_status = data.get("job_status")
|
|
||||||
if job_status:
|
|
||||||
last_status = job_status
|
|
||||||
if job_status.get("state") == "failed":
|
|
||||||
raise RuntimeError(
|
|
||||||
f"Worker removal failed: {job_status.get('message', 'Unknown error')}"
|
|
||||||
)
|
|
||||||
# Worker still being processed, continue polling
|
|
||||||
except requests.RequestException:
|
|
||||||
pass
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
# Provide detailed timeout error with last known status
|
|
||||||
error_msg = f"Worker {worker_url} was not removed after {timeout}s"
|
|
||||||
if last_status:
|
|
||||||
error_msg += f". Last job status: {last_status}"
|
|
||||||
raise TimeoutError(error_msg)
|
|
||||||
|
|
||||||
def list_workers(self, base_url: str) -> list[str]:
|
|
||||||
r = requests.get(f"{base_url}/workers")
|
|
||||||
assert r.status_code == 200, f"list_workers failed: {r.status_code} {r.text}"
|
|
||||||
data = r.json()
|
|
||||||
# Extract URLs from WorkerInfo objects
|
|
||||||
workers = data.get("workers", [])
|
|
||||||
return [w["url"] for w in workers]
|
|
||||||
|
|
||||||
def stop_all(self):
|
|
||||||
for p in self._children:
|
|
||||||
if p.poll() is None:
|
|
||||||
p.terminate()
|
|
||||||
try:
|
|
||||||
p.wait(timeout=5)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
p.kill()
|
|
||||||
self._children.clear()
|
|
||||||
@@ -373,12 +373,12 @@ def _setup_cloud_backend(
|
|||||||
gateway_config: Gateway configuration from marker.
|
gateway_config: Gateway configuration from marker.
|
||||||
"""
|
"""
|
||||||
import openai
|
import openai
|
||||||
from backends import CLOUD_RUNTIMES, launch_cloud_gateway
|
from infra import THIRD_PARTY_MODELS, launch_cloud_gateway
|
||||||
|
|
||||||
if backend_name not in CLOUD_RUNTIMES:
|
if backend_name not in THIRD_PARTY_MODELS:
|
||||||
pytest.fail(f"Unknown cloud runtime: {backend_name}")
|
pytest.fail(f"Unknown cloud runtime: {backend_name}")
|
||||||
|
|
||||||
cfg = CLOUD_RUNTIMES[backend_name]
|
cfg = THIRD_PARTY_MODELS[backend_name]
|
||||||
api_key_env = cfg.get("api_key_env")
|
api_key_env = cfg.get("api_key_env")
|
||||||
|
|
||||||
if api_key_env and not os.environ.get(api_key_env):
|
if api_key_env and not os.environ.get(api_key_env):
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from .constants import ( # Enums; Convenience sets; Fixture parameters; Default
|
|||||||
Runtime,
|
Runtime,
|
||||||
WorkerType,
|
WorkerType,
|
||||||
)
|
)
|
||||||
from .gateway import Gateway, WorkerInfo
|
from .gateway import Gateway, WorkerInfo, launch_cloud_gateway
|
||||||
from .gpu_allocator import (
|
from .gpu_allocator import (
|
||||||
GPUAllocator,
|
GPUAllocator,
|
||||||
GPUInfo,
|
GPUInfo,
|
||||||
@@ -54,6 +54,7 @@ from .model_specs import ( # Default model paths; Model groups
|
|||||||
FUNCTION_CALLING_MODELS,
|
FUNCTION_CALLING_MODELS,
|
||||||
MODEL_SPECS,
|
MODEL_SPECS,
|
||||||
REASONING_MODELS,
|
REASONING_MODELS,
|
||||||
|
THIRD_PARTY_MODELS,
|
||||||
)
|
)
|
||||||
from .process_utils import (
|
from .process_utils import (
|
||||||
detect_ib_device,
|
detect_ib_device,
|
||||||
@@ -121,6 +122,7 @@ __all__ = [
|
|||||||
# Gateway
|
# Gateway
|
||||||
"Gateway",
|
"Gateway",
|
||||||
"WorkerInfo",
|
"WorkerInfo",
|
||||||
|
"launch_cloud_gateway",
|
||||||
# Default model paths
|
# Default model paths
|
||||||
"DEFAULT_MODEL_PATH",
|
"DEFAULT_MODEL_PATH",
|
||||||
"DEFAULT_SMALL_MODEL_PATH",
|
"DEFAULT_SMALL_MODEL_PATH",
|
||||||
@@ -135,6 +137,8 @@ __all__ = [
|
|||||||
"EMBEDDING_MODELS",
|
"EMBEDDING_MODELS",
|
||||||
"REASONING_MODELS",
|
"REASONING_MODELS",
|
||||||
"FUNCTION_CALLING_MODELS",
|
"FUNCTION_CALLING_MODELS",
|
||||||
|
# Third-party models
|
||||||
|
"THIRD_PARTY_MODELS",
|
||||||
# Evaluation
|
# Evaluation
|
||||||
"run_eval",
|
"run_eval",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -554,3 +554,42 @@ class Gateway:
|
|||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def launch_cloud_gateway(
|
||||||
|
runtime: str, # "openai" or "xai"
|
||||||
|
*,
|
||||||
|
history_backend: str = "memory",
|
||||||
|
extra_args: list[str] | None = None,
|
||||||
|
timeout: float = 60,
|
||||||
|
show_output: bool | None = None,
|
||||||
|
) -> Gateway:
|
||||||
|
"""Launch gateway with cloud API runtime.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
runtime: Cloud runtime ("openai" or "xai")
|
||||||
|
history_backend: History storage backend ("memory" or "oracle")
|
||||||
|
extra_args: Additional router arguments
|
||||||
|
timeout: Startup timeout in seconds
|
||||||
|
show_output: Show subprocess output
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Gateway instance with running router
|
||||||
|
"""
|
||||||
|
from .model_specs import THIRD_PARTY_MODELS
|
||||||
|
|
||||||
|
if runtime not in THIRD_PARTY_MODELS:
|
||||||
|
raise ValueError(
|
||||||
|
f"Unknown cloud runtime: {runtime}. "
|
||||||
|
f"Available: {list(THIRD_PARTY_MODELS.keys())}"
|
||||||
|
)
|
||||||
|
|
||||||
|
gateway = Gateway()
|
||||||
|
gateway.start(
|
||||||
|
cloud_backend=runtime,
|
||||||
|
history_backend=history_backend,
|
||||||
|
timeout=timeout,
|
||||||
|
show_output=show_output,
|
||||||
|
extra_args=extra_args,
|
||||||
|
)
|
||||||
|
return gateway
|
||||||
|
|||||||
@@ -131,3 +131,21 @@ DEFAULT_QWEN_FUNCTION_CALLING_MODEL_PATH = MODEL_SPECS["qwen-7b"]["model"]
|
|||||||
DEFAULT_MISTRAL_FUNCTION_CALLING_MODEL_PATH = MODEL_SPECS["mistral-7b"]["model"]
|
DEFAULT_MISTRAL_FUNCTION_CALLING_MODEL_PATH = MODEL_SPECS["mistral-7b"]["model"]
|
||||||
DEFAULT_GPT_OSS_MODEL_PATH = MODEL_SPECS["gpt-oss"]["model"]
|
DEFAULT_GPT_OSS_MODEL_PATH = MODEL_SPECS["gpt-oss"]["model"]
|
||||||
DEFAULT_EMBEDDING_MODEL_PATH = MODEL_SPECS["embedding"]["model"]
|
DEFAULT_EMBEDDING_MODEL_PATH = MODEL_SPECS["embedding"]["model"]
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Third-party model configurations (cloud APIs)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
THIRD_PARTY_MODELS: dict[str, dict] = {
|
||||||
|
"openai": {
|
||||||
|
"description": "OpenAI API",
|
||||||
|
"model": "gpt-5-nano",
|
||||||
|
"api_key_env": "OPENAI_API_KEY",
|
||||||
|
},
|
||||||
|
"xai": {
|
||||||
|
"description": "xAI API",
|
||||||
|
"model": "grok-4-fast",
|
||||||
|
"api_key_env": "XAI_API_KEY",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user