File size: 2,936 Bytes
685cc60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import os
import sys
import asyncio
from contextlib import asynccontextmanager
from dotenv import load_dotenv

# Psycopg 3 async requires SelectorEventLoop on Windows
if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from psycopg_pool import AsyncConnectionPool
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from src.react_agent.agent import get_agent
from src.api.routes import router as chat_router

# Load environment variables (such as DATABASE_URL and GOOGLE_API_KEY)
load_dotenv()

@asynccontextmanager
async def lifespan(app: FastAPI):
    database_url = os.getenv("DATABASE_URL")
    if not database_url:
        raise ValueError("DATABASE_URL environment variable is not set")
    
    # Establish persistent connection pool to Supabase
    async with AsyncConnectionPool(
        conninfo=database_url,
        max_size=10,
        kwargs={"autocommit": True}
    ) as pool:
        app.state.pool = pool
        checkpointer = AsyncPostgresSaver(pool)
        # Create checkpoint tables if they don't exist (migrations)
        await checkpointer.setup()
        
        # Create custom chat_sessions table for thread names
        async with pool.connection() as conn:
            async with conn.cursor() as cur:
                await cur.execute("""
                    CREATE TABLE IF NOT EXISTS chat_sessions (
                        thread_id TEXT PRIMARY KEY,
                        user_id TEXT NOT NULL,
                        title TEXT NOT NULL,
                        updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
                    )
                """)
                await cur.execute("""
                    CREATE INDEX IF NOT EXISTS idx_chat_sessions_user_id_updated_at 
                    ON chat_sessions (user_id, updated_at DESC)
                """)
        
        # Compile agent with the persistent Postgres checkpointer
        app.state.agent = get_agent(checkpointer)
        yield

app = FastAPI(
    title="Vectorless-RAG API Backend",
    description="Local FastAPI backend serving the LangGraph ReAct Legal Assistant",
    version="1.0.0",
    lifespan=lifespan
)

# Enable CORS for Next.js frontend calls
allowed_origins_env = os.getenv("ALLOWED_ORIGINS", "*")
if allowed_origins_env.strip() == "*":
    allowed_origins = ["*"]
else:
    allowed_origins = [
        o.strip() for o in allowed_origins_env.split(",") if o.strip()
    ]

app.add_middleware(
    CORSMiddleware,
    allow_origins=allowed_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Include the routes
app.include_router(chat_router, prefix="/api")

@app.get("/")
def read_root():
    return {
        "status": "online",
        "message": "Vectorless-RAG Legal Assistant API is fully operational locally."
    }