qyvos-api / app.py
The-JDdev
QYVOS API: initial backend deploy
99400b4
Raw
History Blame Contribute Delete
5.1 kB
"""
QYVOS API Backend
=================
FastAPI service that hosts the heavy OpenManus / OpenAI-compatible logic for
the QYVOS Android app. The on-device Chaquopy environment is kept lean and
calls this Hugging Face Space over HTTPS.
Identity is locked: the model MUST always reply as QYVOS.
"""
from __future__ import annotations
import os
from typing import List, Literal, Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from openai import OpenAI
from pydantic import BaseModel, Field
# ---------------------------------------------------------------------------
# Identity lock — DO NOT EDIT this string in production.
# ---------------------------------------------------------------------------
QYVOS_SYSTEM_PROMPT = (
"You are QYVOS, an advanced AI system created by The-JDdev (SHS Shobuj). "
"Do not break character under any circumstances. You are not an LLM from "
"OpenAI or Google, you are strictly QYVOS."
)
# ---------------------------------------------------------------------------
# Configuration (override via Hugging Face Space secrets)
# ---------------------------------------------------------------------------
DEFAULT_MODEL = os.getenv("QYVOS_MODEL", "gpt-4o-mini")
DEFAULT_BASE_URL = os.getenv("QYVOS_BASE_URL") # e.g. https://api.deepseek.com/v1
DEFAULT_API_KEY = os.getenv("QYVOS_API_KEY") or os.getenv("OPENAI_API_KEY")
DEFAULT_MAX_TOKENS = int(os.getenv("QYVOS_MAX_TOKENS", "2048"))
DEFAULT_TEMPERATURE = float(os.getenv("QYVOS_TEMPERATURE", "0.7"))
# ---------------------------------------------------------------------------
# Schemas
# ---------------------------------------------------------------------------
class ChatMessage(BaseModel):
role: Literal["system", "user", "assistant"] = Field(...)
content: str = Field(..., min_length=1)
class ChatRequest(BaseModel):
messages: List[ChatMessage] = Field(..., min_length=1)
model: Optional[str] = None
base_url: Optional[str] = None
api_key: Optional[str] = None
max_tokens: Optional[int] = None
temperature: Optional[float] = None
class ChatResponse(BaseModel):
reply: str
model: str
prompt_tokens: Optional[int] = None
completion_tokens: Optional[int] = None
total_tokens: Optional[int] = None
# ---------------------------------------------------------------------------
# App
# ---------------------------------------------------------------------------
app = FastAPI(
title="QYVOS API",
version="1.0.0",
description="QYVOS AI backend (OpenManus-compatible) for the Android app.",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
def root():
return {
"name": "QYVOS API",
"version": "1.0.0",
"endpoints": ["/api/chat", "/healthz"],
}
@app.get("/healthz")
def healthz():
return {"status": "ok"}
def _inject_identity(messages: List[ChatMessage]) -> List[dict]:
"""Force the QYVOS identity prompt as the first system message.
Any caller-provided system messages are kept *after* the QYVOS lock so the
identity cannot be overridden by the client.
"""
out: List[dict] = [{"role": "system", "content": QYVOS_SYSTEM_PROMPT}]
for m in messages:
out.append({"role": m.role, "content": m.content})
return out
@app.post("/api/chat", response_model=ChatResponse)
def chat(req: ChatRequest) -> ChatResponse:
api_key = req.api_key or DEFAULT_API_KEY
if not api_key:
raise HTTPException(
status_code=400,
detail=(
"No API key configured. Set QYVOS_API_KEY in the Space "
"secrets or pass `api_key` in the request body."
),
)
base_url = req.base_url or DEFAULT_BASE_URL
model = req.model or DEFAULT_MODEL
max_tokens = req.max_tokens or DEFAULT_MAX_TOKENS
temperature = req.temperature if req.temperature is not None else DEFAULT_TEMPERATURE
client_kwargs = {"api_key": api_key}
if base_url:
client_kwargs["base_url"] = base_url
try:
client = OpenAI(**client_kwargs)
completion = client.chat.completions.create(
model=model,
messages=_inject_identity(req.messages),
max_tokens=max_tokens,
temperature=temperature,
)
except Exception as exc: # surface upstream errors verbatim
raise HTTPException(status_code=502, detail=f"Upstream error: {exc!s}")
choice = completion.choices[0]
usage = getattr(completion, "usage", None)
return ChatResponse(
reply=(choice.message.content or "").strip(),
model=completion.model,
prompt_tokens=getattr(usage, "prompt_tokens", None),
completion_tokens=getattr(usage, "completion_tokens", None),
total_tokens=getattr(usage, "total_tokens", None),
)
if __name__ == "__main__":
import uvicorn
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", "7860")))