Skip to content
Open
33 changes: 18 additions & 15 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,41 @@
# PORT=8000
# CORS_ORIGINS=http://localhost:3000

SUPABASE_URL="https://.supabase.co"
SUPABASE_KEY="ey...cnGQk"

DISCORD_BOT_TOKEN="MTM5...5Vr7fw0m9PGAllLfyztCs"
ENABLE_DISCORD_BOT="true"

GITHUB_TOKEN="ghp_...3IOhGP970vnjYK"

# EMBEDDING_MODEL=BAAI/bge-small-en-v1.5
# EMBEDDING_MAX_BATCH_SIZE=32
# EMBEDDING_DEVICE=cpu

#1. backend only
BACKEND_URL=http://localhost:8000

#2. Discord-only mode (no GitHub, no Supabase)
DISCORD_BOT_TOKEN="your_discord_bot_token"
GEMINI_API_KEY="your_gemini_api_key"
GEMINI_MODEL=gemini-2.0-flash

#3. Supabase setting (Discord + Github + Supabase)
SUPABASE_URL="https://...supabase.co"
SUPABASE_KEY="your_supabase_key"
GITHUB_TOKEN="your_github_token"

#4. Full mode (FalkorDB + CodeGraph + Agents)
# FalkorDB Configuration
FALKORDB_HOST=localhost
FALKORDB_PORT=6379
CODEGRAPH_BACKEND_URL=http://localhost:5000
CODEGRAPH_SECRET_TOKEN=DevRAI_CodeGraph_Secret
GEMINI_MODEL=gemini-2.0-flash
SECRET_TOKEN=DevRAI_CodeGraph_Secret
SECRET_TOKEN="your_codegraph_secret"
CODE_GRAPH_PUBLIC=1
FLASK_RUN_HOST=0.0.0.0
FLASK_RUN_PORT=5000

BACKEND_URL="http://localhost:8000"

GEMINI_API_KEY="AIz...ct527D5h-IJCRaXE"
TAVILY_API_KEY="tvly-dev-....1G2k3ivF56SJfWJ4"
RABBITMQ_URL=amqp://localhost:5672/
TAVILY_API_KEY="your_tavily_api_key"

# Langsmith
LANGSMITH_TRACING="true"
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
LANGSMITH_API_KEY="lsv2_pt...d9d989_953d073741"
LANGSMITH_API_KEY="your_langsmith_api_key"
LANGSMITH_PROJECT="devr"

ORG_NAME=AOSSIE
Expand Down
4 changes: 2 additions & 2 deletions backend/app/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
- v1: Version 1 API endpoints
"""

from .router import api_router
from .router import core_router, get_auth_router

__all__ = ["api_router"]
__all__ = ["core_router", "get_auth_router"]
39 changes: 22 additions & 17 deletions backend/app/api/router.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
from fastapi import APIRouter
from .v1.auth import router as auth_router
from .v1.health import router as health_router
from .v1.integrations import router as integrations_router

api_router = APIRouter()

api_router.include_router(
auth_router,
prefix="/v1/auth",
tags=["Authentication"]
)

api_router.include_router(
core_router = APIRouter()
# -------- Core Router ---------------
core_router.include_router(
health_router,
prefix="/v1",
tags=["Health"]
)

api_router.include_router(
integrations_router,
prefix="/v1/integrations",
tags=["Integrations"]
)
# -------- Auth router --------
def get_auth_router() -> APIRouter:
router= APIRouter()
from .v1.auth import router as auth_router
from .v1.integrations import router as integrations_router
router.include_router(
auth_router,
prefix="/v1/auth",
tags=["Authentication"]
)

router.include_router(
integrations_router,
prefix="/v1/integrations",
tags=["Integrations"]
)

return router

__all__ = ["api_router"]
__all__ = ["core_router", "get_auth_router"]
54 changes: 30 additions & 24 deletions backend/app/api/v1/health.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
import logging
from fastapi import APIRouter, HTTPException, Depends
from app.database.weaviate.client import get_weaviate_client
from app.core.dependencies import get_app_instance
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from main import DevRAIApplication
from fastapi import APIRouter, HTTPException, Request
from app.core.config import settings

router = APIRouter()
logger = logging.getLogger(__name__)

async def get_weaviate_status() -> str:
if not settings.code_intelligence_enabled:
return "disabled"

from app.database.weaviate.client import get_weaviate_client
async with get_weaviate_client() as client:
return "ready" if await client.is_ready() else "not_ready"


def get_discord_status(app_instance) -> str:
if not settings.discord_enabled:
return "disabled"

bot = app_instance.discord_bot
return "running" if bot and not bot.is_closed() else "stopped"

@router.get("/health")
async def health_check(app_instance: "DevRAIApplication" = Depends(get_app_instance)):
async def health_check(request: Request):
"""
General health check endpoint to verify services are running.

Returns:
dict: Status of the application and its services
"""
try:
async with get_weaviate_client() as client:
weaviate_ready = await client.is_ready()

services = {
"weaviate": await get_weaviate_status(),
"discord": get_discord_status(request.app.state.app_instance),
}

return {
"status": "healthy",
"services": {
"weaviate": "ready" if weaviate_ready else "not_ready",
"discord_bot": "running" if app_instance.discord_bot and not app_instance.discord_bot.is_closed() else "stopped"
"services": services,
}
}
except Exception as e:
logger.error(f"Health check failed: {e}")
raise HTTPException(
Expand All @@ -45,13 +54,11 @@ async def health_check(app_instance: "DevRAIApplication" = Depends(get_app_insta
async def weaviate_health():
"""Check specifically Weaviate service health."""
try:
async with get_weaviate_client() as client:
is_ready = await client.is_ready()

return {
"service": "weaviate",
"status": "ready" if is_ready else "not_ready"
}
"status": await get_weaviate_status(),
}

except Exception as e:
logger.error(f"Weaviate health check failed: {e}")
raise HTTPException(
Expand All @@ -65,15 +72,14 @@ async def weaviate_health():


@router.get("/health/discord")
async def discord_health(app_instance: "DevRAIApplication" = Depends(get_app_instance)):
async def discord_health(request: Request):
"""Check specifically Discord bot health."""
try:
bot_status = "running" if app_instance.discord_bot and not app_instance.discord_bot.is_closed() else "stopped"

return {
"service": "discord_bot",
"status": bot_status
"status": get_discord_status(request.app.state.app_instance),
}

except Exception as e:
logger.error(f"Discord bot health check failed: {e}")
raise HTTPException(
Expand Down
68 changes: 48 additions & 20 deletions backend/app/core/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@
from dotenv import load_dotenv
from pydantic import field_validator, ConfigDict
from typing import Optional
import os

load_dotenv()

class Settings(BaseSettings):
## CORE (minimal code)
backend_url: str

## OPTIONAL

# Gemini LLM API Key
gemini_api_key: str = ""
gemini_api_key: Optional[str] = None

# Tavily API Key
tavily_api_key: str = ""
tavily_api_key: Optional[str] = None

# Platforms
github_token: str = ""
discord_bot_token: str = ""
github_token: Optional[str] = None
discord_bot_token: Optional[str] = None

# DB configuration
supabase_url: str
supabase_key: str
supabase_url: Optional[str] = None
supabase_key: Optional[str] = None

# LangSmith Tracing
langsmith_tracing: bool = False
Expand All @@ -32,27 +38,49 @@ class Settings(BaseSettings):
classification_agent_model: str = "gemini-2.0-flash"
agent_timeout: int = 30
max_retries: int = 3

# FalkorDB / CodeGraph
falkordb_host: Optional[str] = None
falkordb_port: Optional[str] = None
codegraph_backend_url: Optional[str] = None
secret_token: Optional[str] = None

# RabbitMQ configuration
rabbitmq_url: Optional[str] = None

# Backend URL
backend_url: str = ""

# Onboarding UX toggles
onboarding_show_oauth_button: bool = True
# ------------------
# Derived feature gates
# ------------------

@property
def discord_enabled(self) -> bool:
return bool(self.discord_bot_token) and bool(self.gemini_api_key)

@property
def github_enabled(self) -> bool:
"""
GitHub verification + OAuth.
"""
return self.discord_enabled and bool(self.backend_url) and all([
self.github_token,
self.supabase_url,
self.supabase_key,
])

@field_validator("supabase_url", "supabase_key", mode="before")
@classmethod
def _not_empty(cls, v, field):
if not v:
raise ValueError(f"{field.name} must be set")
return v

model_config = ConfigDict(
env_file=".env",
extra="ignore"
) # to prevent errors from extra env variables
@property
def code_intelligence_enabled(self) -> bool:
"""
Runs DevrAI in full mode.
"""
return self.github_enabled and all([
self.rabbitmq_url,
self.falkordb_host,
self.falkordb_port,
self.codegraph_backend_url,
self.secret_token,
])


settings = Settings()
22 changes: 17 additions & 5 deletions backend/app/database/supabase/client.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
from app.core.config import settings
from supabase._async.client import AsyncClient

supabase_client: AsyncClient = AsyncClient(
settings.supabase_url,
settings.supabase_key
)
_client: AsyncClient | None = None

def get_supabase_client() -> AsyncClient:
global _client
"""
Returns a shared asynchronous Supabase client instance.
"""
return supabase_client
if _client is None:
if not settings.supabase_url or not settings.supabase_key:
missing = []
if not settings.supabase_url:
missing.append("SUPABASE_URL")
if not settings.supabase_key:
missing.append("SUPABASE_KEY")
raise RuntimeError(
f"Supabase misconfigured. Missing: {', '.join(missing)}"
)
_client = AsyncClient(
settings.supabase_url,
settings.supabase_key,
)
return _client
22 changes: 22 additions & 0 deletions backend/app/llm/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.messages import HumanMessage
from app.core.config import settings

async def chat_completion(prompt: str, context: dict | None = None) -> str:
"""
Stateless LLM chat used in discord-only mode.
No agents, no memory, no queue.
"""
# if not settings.llm_enabled:
# return "LLM responses are currently disabled."

llm = ChatGoogleGenerativeAI(
model=settings.classification_agent_model,
temperature=0.7,
google_api_key=settings.gemini_api_key,
)

response = await llm.ainvoke(
[HumanMessage(content=prompt)]
)
return response.content
Loading