2026-06-15 15:53:57 +08:00
|
|
|
"""CLI entry point that launches the evaluation console web server.
|
|
|
|
|
|
|
|
|
|
Run alongside the existing main.py CLI; both share the same rag_eval library
|
|
|
|
|
and the same runs/ artifacts. Example:
|
|
|
|
|
|
|
|
|
|
python webmain.py
|
|
|
|
|
python webmain.py --host 0.0.0.0 --port 8800
|
2026-06-23 11:46:34 +08:00
|
|
|
python webmain.py --host 0.0.0.0 --port 8800 --log-level debug
|
2026-06-15 15:53:57 +08:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import argparse
|
2026-06-23 11:46:34 +08:00
|
|
|
import logging
|
|
|
|
|
import logging.config
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from pathlib import Path
|
2026-06-15 15:53:57 +08:00
|
|
|
|
2026-06-23 11:46:34 +08:00
|
|
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_log_config(log_file: Path, level: str) -> dict:
|
|
|
|
|
"""Build a uvicorn-compatible logging config dict.
|
|
|
|
|
|
|
|
|
|
Writes to both stderr (console) and a rotating daily log file.
|
|
|
|
|
All webapp.* and rag_eval.* loggers inherit from root so every
|
|
|
|
|
logger.info() call in the API routes is captured.
|
|
|
|
|
"""
|
|
|
|
|
level_upper = level.upper()
|
|
|
|
|
return {
|
|
|
|
|
"version": 1,
|
|
|
|
|
"disable_existing_loggers": False,
|
|
|
|
|
"formatters": {
|
|
|
|
|
"detailed": {
|
|
|
|
|
"format": "%(asctime)s %(levelname)-8s %(name)s %(message)s",
|
|
|
|
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
|
|
|
|
},
|
|
|
|
|
"console": {
|
|
|
|
|
"format": "%(asctime)s %(levelname)-8s %(name)-30s %(message)s",
|
|
|
|
|
"datefmt": "%H:%M:%S",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"handlers": {
|
|
|
|
|
"console": {
|
|
|
|
|
"class": "logging.StreamHandler",
|
|
|
|
|
"stream": "ext://sys.stderr",
|
|
|
|
|
"formatter": "console",
|
|
|
|
|
"level": level_upper,
|
|
|
|
|
},
|
|
|
|
|
"file": {
|
|
|
|
|
"class": "logging.handlers.RotatingFileHandler",
|
|
|
|
|
"filename": str(log_file),
|
|
|
|
|
"maxBytes": 50 * 1024 * 1024, # 50 MB per file
|
|
|
|
|
"backupCount": 7, # keep 7 rotated files
|
|
|
|
|
"encoding": "utf-8",
|
|
|
|
|
"formatter": "detailed",
|
|
|
|
|
"level": "DEBUG", # file always captures everything
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"loggers": {
|
|
|
|
|
# Our application loggers — detailed level
|
|
|
|
|
"webapp": {"handlers": ["console", "file"], "level": level_upper, "propagate": False},
|
|
|
|
|
"rag_eval": {"handlers": ["console", "file"], "level": level_upper, "propagate": False},
|
|
|
|
|
# uvicorn access log — captured to file, shown on console
|
|
|
|
|
"uvicorn.access": {"handlers": ["console", "file"], "level": "INFO", "propagate": False},
|
|
|
|
|
"uvicorn.error": {"handlers": ["console", "file"], "level": "INFO", "propagate": False},
|
|
|
|
|
"uvicorn": {"handlers": ["console", "file"], "level": "INFO", "propagate": False},
|
|
|
|
|
},
|
|
|
|
|
"root": {
|
|
|
|
|
"handlers": ["console", "file"],
|
|
|
|
|
"level": "WARNING", # suppress noisy third-party libs at WARNING
|
|
|
|
|
},
|
|
|
|
|
}
|
2026-06-15 15:53:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
|
|
|
"""Parse host/port/reload options for the console server."""
|
|
|
|
|
parser = argparse.ArgumentParser(description="Launch the RAGAS evaluation console.")
|
|
|
|
|
parser.add_argument("--host", default="127.0.0.1", help="Bind host (default 127.0.0.1).")
|
|
|
|
|
parser.add_argument("--port", type=int, default=8800, help="Bind port (default 8800).")
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--reload",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Enable auto-reload for local development.",
|
|
|
|
|
)
|
2026-06-23 11:46:34 +08:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--log-level",
|
|
|
|
|
default="info",
|
|
|
|
|
choices=["debug", "info", "warning", "error"],
|
|
|
|
|
help="Console log level (default: info). File always captures DEBUG.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--log-file",
|
|
|
|
|
default=None,
|
|
|
|
|
help="Log file path (default: logs/server_YYYY-MM-DD.log).",
|
|
|
|
|
)
|
2026-06-15 15:53:57 +08:00
|
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
2026-06-23 11:46:34 +08:00
|
|
|
"""Start uvicorn with the configured application and logging."""
|
|
|
|
|
import uvicorn
|
|
|
|
|
|
2026-06-15 15:53:57 +08:00
|
|
|
args = parse_args()
|
2026-06-23 11:46:34 +08:00
|
|
|
|
|
|
|
|
# Resolve log file path
|
|
|
|
|
logs_dir = REPO_ROOT / "logs"
|
|
|
|
|
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
if args.log_file:
|
|
|
|
|
log_file = Path(args.log_file)
|
|
|
|
|
else:
|
|
|
|
|
date_str = datetime.now().strftime("%Y-%m-%d")
|
|
|
|
|
log_file = logs_dir / f"server_{date_str}.log"
|
|
|
|
|
|
|
|
|
|
log_config = _build_log_config(log_file, args.log_level)
|
|
|
|
|
|
|
|
|
|
# Apply config before uvicorn starts so our loggers are ready immediately
|
|
|
|
|
logging.config.dictConfig(log_config)
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger("webapp.server")
|
|
|
|
|
logger.info(
|
|
|
|
|
"Starting RAGAS Console host=%s port=%d log_level=%s log_file=%s",
|
|
|
|
|
args.host, args.port, args.log_level, log_file,
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-15 15:53:57 +08:00
|
|
|
uvicorn.run(
|
|
|
|
|
"webapp.server:app",
|
|
|
|
|
host=args.host,
|
|
|
|
|
port=args.port,
|
|
|
|
|
reload=args.reload,
|
2026-06-23 11:46:34 +08:00
|
|
|
log_config=log_config, # hand our config to uvicorn so it uses same handlers
|
2026-06-15 15:53:57 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|