vanna-clubpetro/server.py
leonardosalazar-cp 1d152c0dce Initial commit: Vanna 2.0 deployment for ClubPetro
Wrapper application around upstream Vanna with:
- Tenant-aware ChromaDB memory (per program/store)
- ClickHouse RLS runner with introspection guards
- PT-BR system prompt and chat translations
- Custom Plotly chart generator (ranked bar, datetime coercion)
- Embed bootstrap (theme pierce + i18n + markdown) shared by demo and React app
- Event sink for chat turn observability
2026-04-29 17:22:05 -03:00

96 lines
3.1 KiB
Python

"""FastAPI server: oficial VannaFastAPIServer + estáticos do web component."""
from __future__ import annotations
import os
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from vanna.servers.fastapi import VannaFastAPIServer
import csv_cleanup
from agent import RequestContextUserResolver, build_agent
from chat_filter import FilteringChatHandler
from events_sink import EventSink
def _build_app():
agent = build_agent(user_resolver=RequestContextUserResolver())
cors_origins = [
o.strip()
for o in os.environ.get("VANNA_CORS_ORIGINS", "*").split(",")
if o.strip()
] or ["*"]
server = VannaFastAPIServer(
agent,
config={
"cors": {
"allow_origins": cors_origins,
"allow_methods": ["*"],
"allow_headers": ["*"],
"allow_credentials": True,
},
"api_base_url": "",
},
)
server.chat_handler = FilteringChatHandler(agent, event_sink=EventSink())
fastapi_app = server.create_app()
here = os.path.dirname(os.path.abspath(__file__))
dist = os.path.join(here, "vanna", "frontends", "webcomponent", "dist")
if os.path.isdir(dist):
fastapi_app.mount("/static", StaticFiles(directory=dist), name="static")
@fastapi_app.get("/vanna-theme.css")
async def vanna_theme():
path = os.path.join(here, "static", "vanna-theme.css")
return FileResponse(path, media_type="text/css")
@fastapi_app.get("/vanna-embed-bootstrap.js")
async def vanna_embed_bootstrap():
path = os.path.join(here, "static", "vanna-embed-bootstrap.js")
return FileResponse(path, media_type="application/javascript")
@fastapi_app.get("/clubpetro-logo.png")
async def clubpetro_logo():
path = os.path.join(here, "static", "clubpetro-logo.png")
return FileResponse(path, media_type="image/png")
@fastapi_app.get("/clubpetro-logo.svg")
async def clubpetro_logo_svg():
path = os.path.join(here, "static", "clubpetro-logo.svg")
return FileResponse(path, media_type="image/svg+xml")
@fastapi_app.get("/dashboard-bg.png")
async def dashboard_bg():
path = os.path.join(here, "static", "dashboard-bg.png")
return FileResponse(path, media_type="image/png")
@fastapi_app.get("/embed-demo.html", response_class=HTMLResponse)
async def embed_demo():
path = os.path.join(here, "static", "embed-demo.html")
with open(path, "r", encoding="utf-8") as f:
html = f.read()
return (
html
.replace("__PROGRAM_ID__", os.environ.get("RLS_PROGRAM_ID", ""))
.replace("__STORE_ID__", os.environ.get("RLS_STORE_ID", ""))
.replace("__USER_ID__", os.environ.get("RLS_USER_ID", ""))
)
return fastapi_app
app = _build_app()
@app.on_event("startup")
async def _csv_cleanup_startup() -> None:
await csv_cleanup.startup()
@app.on_event("shutdown")
async def _csv_cleanup_shutdown() -> None:
await csv_cleanup.shutdown()