Spaces:
Running
Running
| """Local FastAPI app for a pharmacovigilance demo with managed ChatKit sessions.""" | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import uuid | |
| from pathlib import Path | |
| from typing import Any, Mapping | |
| import httpx | |
| from dotenv import load_dotenv | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import FileResponse, JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| load_dotenv() | |
| APP_ROOT = Path(__file__).parent | |
| STATIC_DIR = APP_ROOT / "static" | |
| DEFAULT_OPENAI_BASE = "https://api.openai.com" | |
| SESSION_COOKIE_NAME = "chatkit_user_id" | |
| SESSION_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30 # 30 days | |
| OPENAI_BETA_HEADER = "chatkit_beta=v1" | |
| app = FastAPI(title="Pharmacovigilance MIT ChatKit Demo") | |
| app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") | |
| async def health() -> Mapping[str, str]: | |
| return {"status": "ok"} | |
| async def index() -> FileResponse: | |
| return FileResponse(STATIC_DIR / "index.html") | |
| async def create_chatkit_session(request: Request) -> JSONResponse: | |
| api_key = os.getenv("OPENAI_API_KEY", "").strip() | |
| workflow_id = os.getenv("WORKFLOW_ID", "").strip() | |
| missing_env = [ | |
| name | |
| for name, value in [ | |
| ("OPENAI_API_KEY", api_key), | |
| ("WORKFLOW_ID", workflow_id), | |
| ] | |
| if not value | |
| ] | |
| if missing_env: | |
| return response( | |
| {"error": f"Missing required environment variable(s): {', '.join(missing_env)}"}, | |
| 500, | |
| ) | |
| user_id, cookie_value = resolve_user_id(request.cookies) | |
| openai_base = os.getenv("OPENAI_BASE_URL", DEFAULT_OPENAI_BASE).strip() | |
| payload = { | |
| "workflow": {"id": workflow_id}, | |
| "user": user_id, | |
| } | |
| try: | |
| upstream = await create_openai_session(api_key, openai_base, payload) | |
| except httpx.RequestError as exc: | |
| return response({"error": f"Failed to reach OpenAI API: {exc}"}, 502, cookie_value) | |
| parsed = safe_json(upstream) | |
| workflow_version_used: str | None = None | |
| if not upstream.is_success: | |
| message = "Failed to create ChatKit session" | |
| if isinstance(parsed.get("error"), str): | |
| message = parsed["error"] | |
| elif isinstance(parsed.get("error"), Mapping): | |
| message = str(parsed["error"].get("message") or message) | |
| return response({"error": message}, upstream.status_code, cookie_value) | |
| client_secret = parsed.get("client_secret") | |
| expires_after = parsed.get("expires_after") | |
| if not isinstance(client_secret, str) or not client_secret.strip(): | |
| return response( | |
| {"error": "Missing client_secret in OpenAI response"}, | |
| 502, | |
| cookie_value, | |
| ) | |
| return response( | |
| { | |
| "client_secret": client_secret, | |
| "expires_after": expires_after, | |
| "workflow_version_used": workflow_version_used, | |
| }, | |
| 200, | |
| cookie_value, | |
| ) | |
| def resolve_user_id(cookies: Mapping[str, str]) -> tuple[str, str | None]: | |
| existing = cookies.get(SESSION_COOKIE_NAME) | |
| if existing and existing.strip(): | |
| return existing.strip(), None | |
| new_user_id = str(uuid.uuid4()) | |
| return new_user_id, new_user_id | |
| def safe_json(http_response: httpx.Response) -> dict[str, Any]: | |
| try: | |
| parsed = http_response.json() | |
| except (json.JSONDecodeError, httpx.DecodingError): | |
| return {} | |
| return parsed if isinstance(parsed, dict) else {} | |
| async def create_openai_session( | |
| api_key: str, openai_base: str, payload: Mapping[str, Any] | |
| ) -> httpx.Response: | |
| async with httpx.AsyncClient(base_url=openai_base, timeout=20.0) as client: | |
| return await client.post( | |
| "/v1/chatkit/sessions", | |
| headers={ | |
| "Authorization": f"Bearer {api_key}", | |
| "OpenAI-Beta": OPENAI_BETA_HEADER, | |
| "Content-Type": "application/json", | |
| }, | |
| json=payload, | |
| ) | |
| def is_production() -> bool: | |
| env = (os.getenv("ENVIRONMENT") or os.getenv("NODE_ENV") or "").strip().lower() | |
| return env == "production" | |
| def response( | |
| payload: Mapping[str, Any], | |
| status_code: int, | |
| cookie_value: str | None = None, | |
| ) -> JSONResponse: | |
| res = JSONResponse(payload, status_code=status_code) | |
| if cookie_value: | |
| res.set_cookie( | |
| key=SESSION_COOKIE_NAME, | |
| value=cookie_value, | |
| max_age=SESSION_COOKIE_MAX_AGE_SECONDS, | |
| httponly=True, | |
| samesite="lax", | |
| secure=is_production(), | |
| path="/", | |
| ) | |
| return res | |