#!/usr/bin/env python3 """ Frox AI — Serve the Morph API Launches the FastAPI server every client (frox-chat, frox-code, a future CLI, a future Android app) connects to. Usage: python scripts/serve.py --family classic --model ./frox-morph-1-1-output/classic_sft_final python scripts/serve.py --family nano # untrained — proves the wiring works python scripts/serve.py --family classic --model ./ckpt --quantization 4bit --port 8080 Then point any client at http://:/v1 as an OpenAI-compatible endpoint. For frox-chat (Open WebUI): Settings → Connections → add an OpenAI API connection with that base URL. """ from __future__ import annotations import argparse import os import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from utils.common import print_banner, FAMILY_TIERS def main(): parser = argparse.ArgumentParser(description="Serve the Frox AI Morph API") parser.add_argument("--family", choices=list(FAMILY_TIERS), default="classic", help="Which Morph model-family tier to serve") parser.add_argument("--model", type=str, default=None, help="Path to a trained checkpoint. Omit to serve an " "untrained model (useful only for testing the wiring).") parser.add_argument("--quantization", choices=["4bit", "8bit"], default=None) parser.add_argument("--host", type=str, default="0.0.0.0") parser.add_argument("--port", type=int, default=8000) parser.add_argument("--cors-origins", type=str, default="*", help="Comma-separated allowed origins, e.g. " "'http://localhost:3000,tauri://localhost'. " "Defaults to '*' (fine for local dev, not for production).") parser.add_argument("--memory-path", type=str, default="./data/memories.json") parser.add_argument("--reload", action="store_true", help="Auto-reload on code changes (dev only)") conductor_group = parser.add_argument_group("Conductor (Fugu-inspired orchestration)") conductor_group.add_argument("--no-conductor", action="store_true", help="Disable the 'conductor' pseudo-model entirely") conductor_group.add_argument("--conductor-mode", choices=["auto", "fast", "deep"], default="auto", help="Default orchestration depth when a client requests model=conductor") conductor_group.add_argument("--worker", action="append", default=[], metavar="NAME=URL", help="Register a remote worker for true multi-tier orchestration, e.g. " "--worker code=http://localhost:8001/v1 (repeatable). Omit to use " "self-orchestration on this process's one loaded model instead.") args = parser.parse_args() os.environ["MORPH_FAMILY"] = args.family if args.model: os.environ["MORPH_CHECKPOINT"] = args.model if args.quantization: os.environ["MORPH_QUANTIZATION"] = args.quantization os.environ["MORPH_CORS_ORIGINS"] = args.cors_origins os.environ["MORPH_MEMORY_PATH"] = args.memory_path os.environ["MORPH_CONDUCTOR_ENABLED"] = "false" if args.no_conductor else "true" os.environ["MORPH_CONDUCTOR_MODE"] = args.conductor_mode for entry in args.worker: if "=" not in entry: parser.error(f"--worker expects NAME=URL, got: {entry!r}") name, url = entry.split("=", 1) os.environ[f"MORPH_WORKER_{name.strip().upper()}_URL"] = url.strip() print_banner() print(f"Serving Morph ({args.family}) at http://{args.host}:{args.port}") print(f" OpenAI-compatible base URL for clients: http://{args.host}:{args.port}/v1") if not args.model: print(" ⚠ No --model given: serving an UNTRAINED model. Responses will be gibberish.") print() import uvicorn uvicorn.run( "api.server:app", host=args.host, port=args.port, reload=args.reload, log_level="info", ) if __name__ == "__main__": main()