File size: 3,220 Bytes
9398426
b245453
 
 
9398426
b245453
d1ceaf4
 
9398426
 
419498a
 
b245453
9398426
 
b245453
 
 
419498a
 
9398426
 
d1ceaf4
 
9398426
d1ceaf4
9398426
 
d1ceaf4
 
 
 
b245453
9398426
 
d1ceaf4
 
b245453
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419498a
 
b245453
 
 
 
 
 
 
 
 
 
9398426
 
d1ceaf4
 
 
9398426
 
d1ceaf4
b245453
d1ceaf4
 
 
 
 
 
b245453
 
d1ceaf4
 
 
 
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
"""
Beryl Chat API — FastAPI raw inference proxy (Docker Space)
Calls api-inference.huggingface.co directly (resolves inside HF network).
POST /run/predict  →  { data: [messages_json, model_key] }
"""
import os, json, requests
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

HF_TOKEN = os.environ.get("HF_TOKEN", "")
# Featherless AI provider via HF Router — resolves everywhere, supports Qwen/Mistral
HF_API   = "https://router.huggingface.co/featherless-ai/v1/chat/completions"
HEADERS  = {"Authorization": f"Bearer {HF_TOKEN}", "Content-Type": "application/json"}

MODELS = {
    "qwen":   "Qwen/Qwen2.5-72B-Instruct",
    "qwen7b": "Qwen/Qwen2.5-7B-Instruct",
    "glm":    "Qwen/Qwen2.5-7B-Instruct",
    "hermes": "NousResearch/Hermes-3-Llama-3.1-8B",
    "auto":   "Qwen/Qwen2.5-7B-Instruct",
}

EMOTION_KW = ["feel","lonely","sad","love","companion","miss",
              "emotional","relationship","heart","care","hurt"]

app = FastAPI(title="Beryl Chat API")


def route_model(messages: list, model_key: str) -> str:
    if model_key and model_key not in ("auto", ""):
        return MODELS.get(model_key, MODELS["auto"])
    last = (messages[-1].get("content","") if messages else "").lower()
    return MODELS["glm"] if any(k in last for k in EMOTION_KW) else MODELS["auto"]


def do_chat(messages: list, model_key: str) -> dict:
    model = route_model(messages, model_key)
    payload = {
        "model":       model,
        "messages":    messages,
        "max_tokens":  600,
        "temperature": 0.78,
        "stream":      False,
    }
    r = requests.post(HF_API, headers=HEADERS, json=payload, timeout=30)
    if r.status_code == 200:
        data = r.json()
        return {
            "response": data["choices"][0]["message"]["content"],
            "model":    model.split("/")[-1],
            "ok":       True,
        }
    # Fallback: try Qwen2.5-7B if primary model unavailable/quota
    if r.status_code in (429, 503, 400) and model != MODELS["qwen7b"]:
        payload["model"] = MODELS["qwen7b"]
        r2 = requests.post(HF_API, headers=HEADERS, json=payload, timeout=30)
        if r2.status_code == 200:
            data2 = r2.json()
            return {
                "response": data2["choices"][0]["message"]["content"],
                "model":    "Qwen2.5-7B-Instruct",
                "ok":       True,
            }
    return {"ok": False, "error": f"HTTP {r.status_code}: {r.text[:200]}", "response": ""}


@app.get("/health")
def health():
    return {"ok": True, "version": "1.0.0", "service": "beryl-chat-api"}


@app.post("/predict")
@app.post("/run/predict")
async def predict(request: Request):
    try:
        body = await request.json()
        data = body.get("data", [])
        messages_json = data[0] if len(data) > 0 else "[]"
        model_key     = data[1] if len(data) > 1 else "auto"
        messages      = json.loads(messages_json)
        result        = do_chat(messages, model_key)
        return JSONResponse({"data": [json.dumps(result)]})
    except Exception as e:
        err = json.dumps({"ok": False, "error": str(e), "response": ""})
        return JSONResponse({"data": [err]}, status_code=200)