"""GARÍBEL brain API (FastAPI) — wraps the grounded Orchestrator for C's Next UI. POST /garibel {message, intent?, level?, session_id?} -> {answer, sources, audio?}. Reuses the EXACT orchestrator/brain/RAG/TTS bundle the Gradio Space proved (foundry + LILA NISAMINA + full corpus + anti-fabrication + greetings/numbers + mms-tts-cab).""" import os, sys, base64 from fastapi import FastAPI, Request from fastapi.responses import JSONResponse _BR = os.path.join(os.path.dirname(__file__), "brain") # Match the proven Gradio bundle layout: modules live at brain/ root; bare imports (jw_quarantine_filter) # live in 30_lexicon/. Add every dir a bare/package import may resolve from. for _p in (_BR, os.path.join(_BR,"30_lexicon"), os.path.join(_BR,"99_publish"), os.path.join(_BR,"nisamina_mcp")): if os.path.isdir(_p) and _p not in sys.path: sys.path.insert(0, _p) app = FastAPI(title="GARIBEL brain") _ORCH = None; _SESS = {} def _orch(): global _ORCH if _ORCH is None: from orchestrator import Orchestrator from brain import load_brain rm = os.getenv("NISAMINA_REAL_BRAIN", "1") == "1" b = load_brain(real_mode=rm) tts = None if rm: try: from tts_garifuna import GarifunaTTS; tts = GarifunaTTS() except Exception as e: print("[tts] mock:", e) _ORCH = Orchestrator(brain=b, tts=tts, audio_enabled=True) return _ORCH def _sess(sid): from orchestrator import SessionState if sid not in _SESS: _SESS[sid] = SessionState() return _SESS[sid] @app.get("/health") def health(): return {"ok": True} @app.post("/garibel") async def garibel(req: Request): b = await req.json() msg = (b.get("message") or b.get("text") or "").strip() if not msg: return JSONResponse({"error": "empty"}, status_code=400) sid = str(b.get("session_id") or "default") intent = b.get("intent"); level = b.get("level") pre = "" if intent == "translate": pre = "[Just translate, concisely.] " elif intent == "simpler": pre = "[Explain more simply.] " elif intent == "sentence": pre = "[Use it in a sentence.] " if level == "beginner": pre += "[Beginner level.] " try: ss = _sess(sid) resp = _orch().orchestrate(pre + msg, ss) _SESS[sid] = resp.session_state srcs = [] if resp.citations: try: from nisamina_mcp.source_attribution import cite_record for rec in resp.citations[:6]: for c in cite_record(rec, limit=2): if c not in srcs: srcs.append(c) except Exception: pass out = {"answer": resp.text, "sources": srcs[:6], "blocked": resp.blocked_reason} if resp.audio_garifuna_wav: out["audio_b64"] = base64.b64encode(resp.audio_garifuna_wav).decode() out["audio_attribution"] = resp.audio_attribution out["audio_text"] = resp.audio_synthesized_text return out except Exception as e: import traceback; traceback.print_exc() return JSONResponse({"error": "brain_error", "detail": str(e)}, status_code=500)