joduor's picture
init: adaptive-model β€” handler, gateway, mcp-server, training
1de92bf verified
Raw
History Blame Contribute Delete
3.41 kB
"""
FastAPI gateway β€” Custom GPT Action surface (Path A).
Receives messages from the GPT Action, routes to the right adapter,
calls the HF Inference Endpoint, and returns both raw ui_spec and a
markdown fallback so the GPT can render something useful inline.
"""
import os
import sys
from pathlib import Path
import httpx
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
load_dotenv()
# Allow shared lib to be imported when running from repo root or this dir
sys.path.insert(0, str(Path(__file__).parent.parent))
from lib.router import route
from lib.markdown_renderer import render
HF_ENDPOINT_URL = os.getenv("HF_ENDPOINT_URL", "")
HF_TOKEN = os.getenv("HF_TOKEN", "")
app = FastAPI(
title="Adaptive Model Gateway",
version="1.0.0",
description="Routes chat turns to the right LoRA adapter and returns adaptive UI specs.",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["https://chat.openai.com", "https://chatgpt.com"],
allow_methods=["POST", "GET"],
allow_headers=["*"],
)
# ── request / response models ─────────────────────────────────────────────────
class Message(BaseModel):
role: str
content: str
class GenerateRequest(BaseModel):
messages: list[Message]
mode: str | None = None # explicit override; omit for heuristic routing
class GenerateResponse(BaseModel):
text: str
ui_spec: dict | None = None
ui_markdown: str | None = None # markdown fallback for Custom GPT rendering
adapter: str
# ── endpoints ─────────────────────────────────────────────────────────────────
@app.post("/generate", response_model=GenerateResponse)
async def generate(req: GenerateRequest) -> GenerateResponse:
if not HF_ENDPOINT_URL:
raise HTTPException(500, "HF_ENDPOINT_URL not set β€” check .env")
messages = [m.model_dump() for m in req.messages]
adapter = route(messages, req.mode)
headers = {"Content-Type": "application/json"}
if HF_TOKEN:
headers["Authorization"] = f"Bearer {HF_TOKEN}"
async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(
HF_ENDPOINT_URL,
json={"inputs": {"messages": messages, "mode": adapter}},
headers=headers,
)
if resp.status_code != 200:
raise HTTPException(resp.status_code, f"HF endpoint error: {resp.text[:400]}")
data = resp.json()
ui_spec = data.get("ui_spec")
return GenerateResponse(
text=data.get("text", ""),
ui_spec=ui_spec,
ui_markdown=render(ui_spec),
adapter=data.get("adapter", adapter),
)
@app.get("/health")
async def health() -> dict:
return {"status": "ok", "endpoint_configured": bool(HF_ENDPOINT_URL)}
# ── dev entry-point ───────────────────────────────────────────────────────────
if __name__ == "__main__":
import uvicorn
uvicorn.run("gateway.app:app", host="0.0.0.0", port=8000, reload=True)