File size: 4,124 Bytes
296a506
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/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()