Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 55 additions & 28 deletions app/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,30 @@ def set_model(self, model: str) -> None:

def setup_vectorstore(self, file_paths: List[str]) -> Optional[FAISS]:
"""Load documents and create a vector store for retrieval"""
if not file_paths:
logger.warning("DOC_PATHS is empty. RAG retriever will remain unconfigured.")
self.retriever = None
return None

all_documents = []
for file_path in file_paths:
if os.path.exists(file_path):
if file_path.endswith(".pdf"):
loader = PyMuPDFLoader(file_path)
else:
loader = TextLoader(file_path)
documents = loader.load()
all_documents.extend(documents)
try:
if file_path.endswith(".pdf"):
loader = PyMuPDFLoader(file_path)
else:
loader = TextLoader(file_path)
documents = loader.load()
all_documents.extend(documents)
except Exception as e:
logger.warning(f"Failed to load document '{file_path}': {e}")
else:
logger.warning(f"Document path does not exist: {file_path}")

if not all_documents:
logger.warning("No documents were loaded from DOC_PATHS. RAG retriever will remain unconfigured.")
self.retriever = None
return None

embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
Expand All @@ -155,6 +170,9 @@ def setup_vectorstore(self, file_paths: List[str]) -> Optional[FAISS]:

def get_relevant_document(self, query: str, threshold: float = 0.5):
"""Get the most relevant document for a query"""
if not self.retriever:
return None, 0.0

results = self.retriever.invoke(query)
if results:
top_result = results[0]
Expand Down Expand Up @@ -205,29 +223,38 @@ def debug(self, code: str, context: bool) -> str:

def run(self, question: str) -> str:
"""Process a question through the RAG pipeline"""
# build chain components
chain_input = {
"context": self.retriever | format_docs,
"question": RunnablePassthrough()
}

# first chain: prompt -> combine messages -> model -> extract answer
first_chain = (
chain_input
| self.prompt
| combine_messages
| self.model
| extract_answer_from_output
)

doc_result, _ = self.get_relevant_document(question)
if doc_result:
first_response = first_chain.invoke({
"query": question,
"context": doc_result.page_content
})
if not self.retriever:
first_chain = (
self.prompt
| combine_messages
| self.model
| extract_answer_from_output
)
first_response = first_chain.invoke({"context": "", "question": question})
else:
first_response = first_chain.invoke(question)
# build chain components
chain_input = {
"context": self.retriever | format_docs,
"question": RunnablePassthrough()
}

# first chain: prompt -> combine messages -> model -> extract answer
first_chain = (
chain_input
| self.prompt
| combine_messages
| self.model
| extract_answer_from_output
)

doc_result, _ = self.get_relevant_document(question)
if doc_result:
first_response = first_chain.invoke({
"query": question,
"context": doc_result.page_content
})
else:
first_response = first_chain.invoke(question)

# second chain for making answer child-friendly
second_chain = (
Expand Down
1 change: 1 addition & 0 deletions app/routes/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async def admin_panel(
denied_keys = db.query(APIKey).filter(APIKey.approved == False, APIKey.is_active == True).all()

return templates.TemplateResponse(
request,
"admin_panel.html",
{
"request": request,
Expand Down
8 changes: 5 additions & 3 deletions app/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async def logout(request: Request):
@router.get("/oauth-login", response_class=HTMLResponse)
async def login(request: Request):
"""Render OAuth login page"""
return templates.TemplateResponse("oauth_login.html", {"request": request})
return templates.TemplateResponse(request, "oauth_login.html", {"request": request})

@router.get("/auth/github")
async def login_github(request: Request):
Expand Down Expand Up @@ -137,7 +137,7 @@ async def auth_callback(provider: str, request: Request, db: Session = Depends(g
@router.get("/admin-login", response_class=HTMLResponse)
async def admin_login(request: Request):
"""Render admin login page"""
return templates.TemplateResponse("admin_login.html", {"request": request})
return templates.TemplateResponse(request, "admin_login.html", {"request": request})

@router.post("/admin-login")
async def admin_login_submit(
Expand All @@ -155,6 +155,7 @@ async def admin_login_submit(

if not key:
return templates.TemplateResponse(
request,
"admin_login.html",
{"request": request, "error": "Invalid API key"}
)
Expand All @@ -167,7 +168,7 @@ async def admin_login_submit(
@router.get("/request-key", response_class=HTMLResponse)
async def request_key_form(request: Request):
"""Render API key request page"""
return templates.TemplateResponse("request_key.html", {"request": request})
return templates.TemplateResponse(request, "request_key.html", {"request": request})

@router.post("/request-key", response_class=HTMLResponse)
async def submit_key_request(
Expand All @@ -192,6 +193,7 @@ async def submit_key_request(
db.commit()

return templates.TemplateResponse(
request,
"request_key.html",
{
"request": request,
Expand Down
2 changes: 1 addition & 1 deletion app/routes/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
@router.get("/", response_class=HTMLResponse)
async def root(request: Request):
"""Serve the root welcome page with links to documentation"""
return templates.TemplateResponse("welcome.html", {"request": request})
return templates.TemplateResponse(request, "welcome.html", {"request": request})
24 changes: 21 additions & 3 deletions app/routes/web.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""
Web routes handling HTML responses for Sugar-AI.
"""
from fastapi import APIRouter, Depends, Request
import os

from fastapi import APIRouter, Depends, Request, status
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
Expand All @@ -15,10 +17,26 @@
# set up templates
templates = Jinja2Templates(directory=settings.TEMPLATES_DIR)


@router.get("/health", status_code=status.HTTP_200_OK)
async def health_check():
"""Return service health and essential LLM configuration status."""
openai_api_key = os.getenv("OPENAI_API_KEY")
local_llm_url = os.getenv("LOCAL_LLM_URL")

return {
"service": "online",
"components": {
"llm": "configured" if openai_api_key or local_llm_url else "unconfigured",
"openai_api_key": "configured" if openai_api_key else "unconfigured",
"local_llm_url": "configured" if local_llm_url else "unconfigured",
},
}

@router.get("/", response_class=HTMLResponse)
async def root(request: Request):
"""Render welcome page"""
return templates.TemplateResponse("welcome.html", {"request": request})
return templates.TemplateResponse(request, "welcome.html", {"request": request})

@router.get("/dashboard", response_class=HTMLResponse)
async def dashboard(request: Request, db: Session = Depends(get_db)):
Expand Down Expand Up @@ -64,7 +82,7 @@ async def dashboard(request: Request, db: Session = Depends(get_db)):
if user and user.get("can_change_model", False):
admin_url = "/admin"

return templates.TemplateResponse("dashboard.html", {
return templates.TemplateResponse(request, "dashboard.html", {
"request": request,
"user": user,
"api_key": api_key,
Expand Down
2 changes: 2 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ async def startup_event():

initialized_agent = RAGAgent(model=active_model)
initialized_agent.retriever = initialized_agent.setup_vectorstore(settings.DOC_PATHS)
if not initialized_agent.retriever:
logger.warning("RAG retriever is unconfigured. /ask will run without document retrieval.")

# Inject this instance into the API module
# This updates the 'agent = None' in api.py to be the real loaded model
Expand Down