File size: 14,405 Bytes
702ea87 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 | #!/usr/bin/env python3
"""
Server startup script with pre-flight checks.
Usage:
python scripts/run_server.py
python scripts/run_server.py --port 8080 --reload
python scripts/run_server.py --host 0.0.0.0 --port 8000
"""
import argparse
import sys
import time
from pathlib import Path
import requests
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
# Add project root to path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from config.settings import LLMProvider, Settings
console = Console()
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Start EyeWiki RAG API server with pre-flight checks"
)
parser.add_argument(
"--host",
type=str,
default="0.0.0.0",
help="Host to bind (default: 0.0.0.0)",
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="Port number (default: 8000)",
)
parser.add_argument(
"--reload",
action="store_true",
help="Enable hot reload for development",
)
parser.add_argument(
"--skip-checks",
action="store_true",
help="Skip pre-flight checks (not recommended)",
)
return parser.parse_args()
def print_header():
"""Print welcome header."""
console.print()
console.print(
Panel.fit(
"[bold blue]EyeWiki RAG API Server[/bold blue]\n"
"[dim]Retrieval-Augmented Generation for Medical Knowledge[/dim]",
border_style="blue",
)
)
console.print()
def check_ollama(settings: Settings) -> bool:
"""
Check if Ollama is running and has required models.
Args:
settings: Application settings
Returns:
True if check passed, False otherwise
"""
console.print("[bold cyan]1. Checking Ollama service...[/bold cyan]")
try:
# Check if Ollama is running
response = requests.get(f"{settings.ollama_base_url}/api/tags", timeout=5)
response.raise_for_status()
models_data = response.json()
available_models = [model["name"] for model in models_data.get("models", [])]
# Check for required LLM model (embedding model is sentence-transformers, not Ollama)
required_models = {
"LLM": settings.llm_model,
}
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Model Type", style="cyan")
table.add_column("Required Model", style="yellow")
table.add_column("Status", style="green")
all_found = True
for model_type, model_name in required_models.items():
# Check if model name (with or without tag) is in available models
found = any(
model_name in model or model in model_name for model in available_models
)
status = "[green]β Found[/green]" if found else "[red]β Missing[/red]"
table.add_row(model_type, model_name, status)
if not found:
all_found = False
console.print(table)
if not all_found:
console.print(
"\n[red]Error:[/red] Some required models are missing. "
"Pull them with:"
)
for model_type, model_name in required_models.items():
if not any(
model_name in model or model in model_name
for model in available_models
):
console.print(f" [yellow]ollama pull {model_name}[/yellow]")
console.print()
return False
console.print("[green]β Ollama is running with all required models[/green]\n")
return True
except requests.RequestException as e:
console.print(f"[red]β Failed to connect to Ollama:[/red] {e}")
console.print(
f"\nMake sure Ollama is running at [yellow]{settings.ollama_base_url}[/yellow]"
)
console.print("Start it with: [yellow]ollama serve[/yellow]\n")
return False
def check_openai_config(settings: Settings) -> bool:
"""
Check if OpenAI-compatible API is configured with required API key.
Args:
settings: Application settings
Returns:
True if check passed, False otherwise
"""
console.print("[bold cyan]1. Checking OpenAI-compatible API configuration...[/bold cyan]")
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Property", style="cyan")
table.add_column("Value", style="yellow")
table.add_column("Status", style="green")
# Check API key
has_key = bool(settings.openai_api_key)
key_display = f"{settings.openai_api_key[:8]}..." if has_key else "(not set)"
key_status = "[green]β Set[/green]" if has_key else "[red]β Missing[/red]"
table.add_row("API Key", key_display, key_status)
# Show base URL
base_url = settings.openai_base_url or "(OpenAI default)"
table.add_row("Base URL", base_url, "[green]β[/green]")
# Show model
table.add_row("Model", settings.openai_model, "[green]β[/green]")
console.print(table)
if not has_key:
console.print(
"\n[red]Error:[/red] API key is required for OpenAI-compatible provider."
)
console.print(
"Set the [yellow]OPENAI_API_KEY[/yellow] environment variable or add it to your [yellow].env[/yellow] file.\n"
)
return False
console.print("[green]β OpenAI-compatible API configuration looks good[/green]\n")
return True
def check_vector_store(settings: Settings) -> bool:
"""
Check if vector store exists and has documents.
Args:
settings: Application settings
Returns:
True if check passed, False otherwise
"""
console.print("[bold cyan]2. Checking vector store...[/bold cyan]")
qdrant_path = Path(settings.qdrant_path)
collection_name = settings.qdrant_collection_name
# Check if Qdrant directory exists
if not qdrant_path.exists():
console.print(f"[red]β Qdrant directory not found:[/red] {qdrant_path}")
console.print(
"\nRun the indexing pipeline first:\n"
" [yellow]python scripts/build_index.py --index-vectors[/yellow]\n"
)
return False
# Try to connect to Qdrant and check collection
try:
from qdrant_client import QdrantClient
client = QdrantClient(path=str(qdrant_path))
# Check if collection exists
collections = client.get_collections().collections
collection_names = [col.name for col in collections]
if collection_name not in collection_names:
console.print(
f"[red]β Collection '{collection_name}' not found[/red]\n"
f"Available collections: {collection_names}"
)
console.print(
"\nRun the indexing pipeline first:\n"
" [yellow]python scripts/build_index.py --index-vectors[/yellow]\n"
)
return False
# Get collection info
collection_info = client.get_collection(collection_name)
points_count = collection_info.points_count
if points_count == 0:
console.print(
f"[yellow]β Collection '{collection_name}' exists but is empty[/yellow]"
)
console.print(
"\nRun the indexing pipeline:\n"
" [yellow]python scripts/build_index.py --index-vectors[/yellow]\n"
)
return False
# Print stats
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Property", style="cyan")
table.add_column("Value", style="yellow")
table.add_row("Collection", collection_name)
table.add_row("Location", str(qdrant_path))
table.add_row("Documents", f"{points_count:,}")
console.print(table)
console.print("[green]β Vector store is ready[/green]\n")
return True
except Exception as e:
console.print(f"[red]β Failed to access vector store:[/red] {e}\n")
return False
def check_required_files() -> bool:
"""
Check if all required files exist.
Returns:
True if all files exist, False otherwise
"""
console.print("[bold cyan]3. Checking required files...[/bold cyan]")
required_files = {
"System Prompt": project_root / "prompts" / "system_prompt.txt",
"Query Prompt": project_root / "prompts" / "query_prompt.txt",
"Medical Disclaimer": project_root / "prompts" / "medical_disclaimer.txt",
}
table = Table(show_header=True, header_style="bold magenta")
table.add_column("File", style="cyan")
table.add_column("Path", style="yellow")
table.add_column("Status", style="green")
all_exist = True
for name, path in required_files.items():
exists = path.exists()
status = "[green]β Found[/green]" if exists else "[red]β Missing[/red]"
table.add_row(name, str(path.relative_to(project_root)), status)
if not exists:
all_exist = False
console.print(table)
if not all_exist:
console.print(
"\n[red]Error:[/red] Some required files are missing.\n"
"Make sure all prompt files are in the [yellow]prompts/[/yellow] directory.\n"
)
return False
console.print("[green]β All required files found[/green]\n")
return True
def run_preflight_checks(skip_checks: bool = False) -> bool:
"""
Run all pre-flight checks.
Args:
skip_checks: Skip all checks if True
Returns:
True if all checks passed, False otherwise
"""
if skip_checks:
console.print("[yellow]β Skipping pre-flight checks[/yellow]\n")
return True
console.print("[bold yellow]Running Pre-flight Checks...[/bold yellow]\n")
# Load settings
try:
settings = Settings()
except Exception as e:
console.print(f"[red]β Failed to load settings:[/red] {e}\n")
return False
console.print(f"[dim]LLM Provider: {settings.llm_provider.value}[/dim]\n")
# Check LLM provider (Ollama or OpenAI-compatible)
if settings.llm_provider == LLMProvider.OLLAMA:
llm_check = check_ollama(settings)
else:
llm_check = check_openai_config(settings)
# Run checks
checks = [
llm_check,
check_vector_store(settings),
check_required_files(),
]
if not all(checks):
console.print("[bold red]β Pre-flight checks failed[/bold red]")
console.print("Fix the issues above and try again.\n")
return False
console.print("[bold green]β All pre-flight checks passed![/bold green]\n")
return True
def print_access_urls(host: str, port: int):
"""
Print access URLs for the server.
Args:
host: Server host
port: Server port
"""
# Determine display host
display_host = "localhost" if host in ["0.0.0.0", "127.0.0.1"] else host
table = Table(
show_header=True,
header_style="bold magenta",
title="[bold green]Server Access URLs[/bold green]",
title_style="bold green",
)
table.add_column("Service", style="cyan", width=20)
table.add_column("URL", style="yellow")
table.add_column("Description", style="dim")
urls = [
("API Root", f"http://{display_host}:{port}", "API information"),
("Health Check", f"http://{display_host}:{port}/health", "Service health status"),
(
"Interactive Docs",
f"http://{display_host}:{port}/docs",
"Swagger UI documentation",
),
("ReDoc", f"http://{display_host}:{port}/redoc", "Alternative API docs"),
(
"Gradio UI",
f"http://{display_host}:{port}/ui",
"Web chat interface",
),
]
for service, url, description in urls:
table.add_row(service, url, description)
console.print()
console.print(table)
console.print()
# Print quick start commands
console.print("[bold cyan]Quick Test Commands:[/bold cyan]")
console.print(
f" [dim]# Test health endpoint[/dim]\n"
f" [yellow]curl http://{display_host}:{port}/health[/yellow]\n"
)
console.print(
f" [dim]# Query the API[/dim]\n"
f" [yellow]curl -X POST http://{display_host}:{port}/query \\[/yellow]\n"
f' [yellow] -H "Content-Type: application/json" \\[/yellow]\n'
f' [yellow] -d \'{{"question": "What is glaucoma?"}}\' [/yellow]\n'
)
def start_server(host: str, port: int, reload: bool):
"""
Start the uvicorn server.
Args:
host: Server host
port: Server port
reload: Enable hot reload
"""
console.print("[bold green]Starting server...[/bold green]\n")
# Print URLs before starting
print_access_urls(host, port)
# Import uvicorn here to avoid import errors if not installed
try:
import uvicorn
except ImportError:
console.print("[red]Error:[/red] uvicorn is not installed")
console.print("Install it with: [yellow]pip install uvicorn[/yellow]\n")
sys.exit(1)
# Start server
try:
console.print(
f"[dim]Server listening on {host}:{port}[/dim]",
f"[dim](Press CTRL+C to stop)[/dim]\n",
)
uvicorn.run(
"src.api.main:app",
host=host,
port=port,
reload=reload,
log_level="info",
)
except KeyboardInterrupt:
console.print("\n\n[yellow]Server stopped by user[/yellow]")
except Exception as e:
console.print(f"\n[red]Error starting server:[/red] {e}")
sys.exit(1)
def main():
"""Main entry point."""
args = parse_args()
print_header()
# Run pre-flight checks
if not run_preflight_checks(skip_checks=args.skip_checks):
console.print("[red]Startup aborted due to failed checks[/red]\n")
sys.exit(1)
# Start server
start_server(host=args.host, port=args.port, reload=args.reload)
if __name__ == "__main__":
main()
|