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
455 changes: 455 additions & 0 deletions .vscode/PythonImportHelper-v2-Completion.json

Large diffs are not rendered by default.

Empty file added backend/app/__init__.py
Empty file.
Empty file added backend/app/core/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions backend/app/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from fastapi.middleware.cors import CORSMiddleware

class Settings:
APP_NAME = "Coding Test"
VERSION = "1.0.0"
DESCRIPTION = "Clean Architecture API with FastAPI"
CORS_ORIGINS = ["http://localhost:3000"]

def setup_cors(app):
app.add_middleware(
CORSMiddleware,
allow_origins=Settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


class LlamaConfig:
MODEL_NAME = "meta-llama/Llama-2-7b-chat-hf"
USE_QUANTIZATION = True
MAX_NEW_TOKENS = 1000
TEMPERATURE = 0.7
TOP_P = 0.95
REPETITION_PENALTY = 1.15

config = LlamaConfig()
36 changes: 36 additions & 0 deletions backend/app/core/exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from fastapi import HTTPException
from fastapi.responses import JSONResponse
from responseConfig import StandardResponse
from ..core.logger import logger
class ReponseError(HTTPException):
def __init__(self, status_code: int, detail: str):
super().__init__(
status_code=status_code,
detail={
"status": "error",
"message": detail
}
)


class AIProcessingError(ReponseError):
def __init__(self, message: str = "AI processing failed"):
super().__init__(status_code=500, message=message)

async def custom_exception_handler(request, exc: ReponseError):
return JSONResponse(
status_code=exc.status_code,
content=exc.detail
)

async def general_exception_handler(request, exc: Exception):
logger.error(f"Unexpected error: {str(exc)}", exc_info=True)

return JSONResponse(
status_code=500,
content=StandardResponse(
status="error",
message="Internal server error",
data=None
).dict()
)
37 changes: 37 additions & 0 deletions backend/app/core/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import logging
import sys
from pathlib import Path
from typing import Optional

def setup_logger(
name: str = "API",
log_file: Optional[str] = "app.log",
level: int = logging.INFO
):
"""
Configure application logger
"""
logger = logging.getLogger(name)
logger.setLevel(level)

# Format
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

# Console handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

# File handler (optional)
if log_file:
Path(log_file).parent.mkdir(parents=True, exist_ok=True)
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

return logger

# Initialize logger
logger = setup_logger()
27 changes: 27 additions & 0 deletions backend/app/core/modelLoader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
BitsAndBytesConfig
)
from .config import LlamaConfig

def load_llama():
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype="float16"
) if LlamaConfig.USE_QUANTIZATION else None

tokenizer = AutoTokenizer.from_pretrained(
LlamaConfig.MODEL_NAME,
use_auth_token=True
)

model = AutoModelForCausalLM.from_pretrained(
LlamaConfig.MODEL_NAME,
quantization_config=quantization_config,
device_map="auto",
use_auth_token=True
)

return model, tokenizer
8 changes: 8 additions & 0 deletions backend/app/core/responseConfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel
from typing import Optional, List
from ..schemas.userDataModel import UserData
class StandardResponse(BaseModel):
status: str
message: str
data: Optional[List[UserData]] = None
answer: Optional[str] = None
46 changes: 46 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from fastapi import FastAPI
from .core.config import Settings, setup_cors
from .core.logger import logger
from .core import exception
from .routes import data, ai

app = FastAPI(
title=Settings.APP_NAME,
description=Settings.DESCRIPTION,
version=Settings.VERSION
)

# Setup exception handlers
app.add_exception_handler(exception.RepsonseError, exception.custom_exception_handler)
app.add_exception_handler(Exception, exception.general_exception_handler)

# Setup CORS
setup_cors(app)

# Include routes
app.include_router(data.router)
app.include_router(ai.router)


@app.on_event("startup")
async def startup_event():
logger.info("Application starting up...")

@app.on_event("shutdown")
async def shutdown_event():
logger.info("Application shutting down...")

@app.get("/health")
def health_check():
logger.debug("Health check requested")
return {"status": "ok"}


if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8000,
reload=True
)
11 changes: 11 additions & 0 deletions backend/app/routes/ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from fastapi import APIRouter
from ..schemas.aiRequestModel import AIRequest, AIResponse
from ..core.responseConfig import StandardResponse
from ..services.aiServices import AIService

router = APIRouter(prefix="/api/ai", tags=["AI"])

@router.post("", response_model= AIResponse)
async def ai_endpoint(request: AIRequest):
"""Process user question with AI"""
return await AIService.process_question(request.question)
22 changes: 22 additions & 0 deletions backend/app/routes/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from fastapi import APIRouter
from ..schemas import StandardResponse
from ..utils.data_loader import load_dummy_data

router = APIRouter(prefix="/api/data", tags=["Data"])

@router.get("", response_model=StandardResponse)
async def get_data():
"""Get all dummy users"""
try:
data = load_dummy_data("dummyData.json")
return StandardResponse(
status="success",
message="Data retrieved successfully",
data=data
)
except Exception as e:
return StandardResponse(
status="error",
message=str(e),
data=None
)
Empty file added backend/app/schemas/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions backend/app/schemas/aiRequestModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pydantic import BaseModel

class AIRequest(BaseModel):
question: str
max_tokens: int = 1000

class AIResponse(BaseModel):
status: str
answer: str
model: str
tokens_generated: int
processing_time_ms: float
error: str = None
8 changes: 8 additions & 0 deletions backend/app/schemas/userDataModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pydantic import BaseModel
from typing import Optional, List

class UserData(BaseModel):
id: int
name: str
email: str
role: Optional[str] = None
Empty file.
21 changes: 21 additions & 0 deletions backend/app/services/aiServices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from backend.app.services.lamaService import LlamaService
from ..core.exception import ReponseError
from ..core.responseConfig import StandardResponse
class AIService:
@staticmethod
async def process_question(question: str) -> StandardResponse:
response, statusCode = LlamaService.generate_response(question)
if statusCode != 200:
return ReponseError(400, f"Error Response From Llama: \n {response}")
if not question:
return StandardResponse(
status="error",
message="Question cannot be empty",
answer=None
)

return StandardResponse(
status="success",
message="Question processed",
answer=response
)
61 changes: 61 additions & 0 deletions backend/app/services/lamaService.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import torch
import time
from typing import Tuple
from transformers import pipeline
from ..core.modelLoader import load_llama
from core.config import LlamaConfig
from ..schemas.aiRequestModel import AIResponse, AIRequest

class LlamaService:
def __init__(self):
self.model, self.tokenizer = load_llama()
self.pipe = pipeline(
"text-generation",
model=self.model,
tokenizer=self.tokenizer,
device_map="auto"
)

async def generate_response(
self,
prompt: str
) -> Tuple[AIResponse, int]:
start_time = time.perf_counter()

try:
with torch.no_grad():
sequences = await self.pipe(
prompt,
max_new_tokens=LlamaConfig.MAX_NEW_TOKENS,
temperature=LlamaConfig.TEMPERATURE,
top_p=LlamaConfig.TOP_P,
repetition_penalty=LlamaConfig.REPETITION_PENALTY,
do_sample=True,
num_return_sequences=1
)

response_text = sequences[0]['generated_text']

return AIResponse(
status="success",
answer=response_text,
model=LlamaConfig.MODEL_NAME,
tokens_generated=len(self.tokenizer.encode(response_text)),
processing_time_ms=round(
(time.perf_counter() - start_time) * 1000,
2
)
), 200

except Exception as e:
return AIResponse(
status="error",
answer="Could not process request",
model=LlamaConfig.MODEL_NAME,
tokens_generated=0,
processing_time_ms=round(
(time.perf_counter() - start_time) * 1000,
2
),
error=str(e)
), 500
Empty file added backend/app/utils/__init__.py
Empty file.
11 changes: 11 additions & 0 deletions backend/app/utils/data_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import json
from typing import List
from ..schemas.userDataModel import UserData

def load_dummy_data(file_path: str) -> List[UserData]:
try:
with open(file_path, "r") as f:
raw_data = json.load(f)
return [UserData(**item) for item in raw_data]
except Exception as e:
raise RuntimeError(f"Error loading data: {str(e)}")
33 changes: 0 additions & 33 deletions backend/main.py

This file was deleted.

5 changes: 5 additions & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
fastapi
uvicorn
transformers
accelerate
bitsandbytes
torch
sentencepiece
Loading