Spaces:
Running on Zero
Running on Zero
| #!/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://<this-host>:<port>/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() | |