-
Notifications
You must be signed in to change notification settings - Fork 0
First backend commit #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| backend/.env | ||
| backend/venv | ||
| backend/mem0_client.py | ||
| backend/dashboard.py |
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to dockerise the application. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,5 @@ | ||
| # AITutor | ||
| # AITutor backend | ||
|
|
||
| GET /api/survey/questions - get all questions | ||
| GET /api/survey/questions/{category} - get questions by category | ||
| POST /api/survey/submit - submit answers |
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add to .gitignore |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| from fastapi import APIRouter, HTTPException, Depends | ||
| from typing import List | ||
| from app.models.survey import SurveyQuestion, UserSurveyResponse | ||
| from app.services.survey import SurveyService | ||
| from app.services.mem0 import Mem0Service | ||
| from config import get_settings | ||
|
|
||
| router = APIRouter(prefix="/survey", tags=["survey"]) | ||
|
|
||
| @router.get("/questions", response_model=List[SurveyQuestion]) | ||
| async def get_survey_questions( | ||
| survey_service: SurveyService = Depends(lambda: SurveyService()) | ||
| ): | ||
| """ | ||
| Retrieves all available survey questions. | ||
| Returns a list of questions with their options. | ||
| """ | ||
| return survey_service.get_all_questions() | ||
|
|
||
| @router.get("/questions/{category}", response_model=List[SurveyQuestion]) | ||
| async def get_questions_by_category( | ||
| category: str, | ||
| survey_service: SurveyService = Depends(lambda: SurveyService()) | ||
| ): | ||
| """ | ||
| Retrieves questions for a specific category. | ||
| Args: | ||
| category: The category of questions to retrieve (e.g., 'learning_format', 'learning_pace') | ||
| Returns: | ||
| List of questions for the specified category | ||
| """ | ||
| questions = survey_service.get_questions_by_category(category) | ||
| if not questions: | ||
| raise HTTPException( | ||
| status_code=404, | ||
| detail=f"No questions found for category: {category}" | ||
| ) | ||
| return questions | ||
|
|
||
| @router.post("/submit") | ||
| async def submit_survey( | ||
| response: UserSurveyResponse, | ||
| survey_service: SurveyService = Depends(lambda: SurveyService()), | ||
| settings = Depends(get_settings) | ||
| ): | ||
| """ | ||
| Submits survey responses and stores them in Mem0.ai. | ||
| Args: | ||
| response: User's survey responses including user_id and answers | ||
| Returns: | ||
| Processed survey data and initial learning path | ||
| """ | ||
| # Process survey responses | ||
| processed_data = survey_service.process_answers(response.dict()) | ||
|
|
||
| # Store in Mem0.ai and get learning path | ||
| mem0_service = Mem0Service(api_key=settings.MEM0_API_KEY) | ||
| try: | ||
| result = await mem0_service.store_survey_response( | ||
| response.user_id, | ||
| processed_data | ||
| ) | ||
| return { | ||
| "status": "success", | ||
| "message": "Survey responses successfully processed", | ||
| "learning_path": result, | ||
| "user_id": response.user_id | ||
| } | ||
| except Exception as e: | ||
| raise HTTPException( | ||
| status_code=400, | ||
| detail=f"Failed to process survey: {str(e)}" | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| from fastapi import FastAPI | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's keep all endpoints under api folder |
||
| from app.api import survey | ||
| from fastapi.middleware.cors import CORSMiddleware | ||
| from config import get_settings | ||
|
|
||
| # Initialize FastAPI app | ||
| app = FastAPI( | ||
| title="AI Tutor API", | ||
| description="API for personalized JavaScript learning experience", | ||
| version="1.0.0" | ||
| ) | ||
|
|
||
| # Configure CORS | ||
| app.add_middleware( | ||
| CORSMiddleware, | ||
| allow_origins=["*"], # In production, replace with actual frontend domain | ||
| allow_credentials=True, | ||
| allow_methods=["*"], | ||
| allow_headers=["*"], | ||
| ) | ||
|
|
||
| # Include routers | ||
| app.include_router(survey.router, prefix="/api") | ||
|
|
||
| @app.get("/") | ||
| async def root(): | ||
| """ | ||
| Root endpoint returning API information | ||
| """ | ||
| return { | ||
| "message": "Welcome to AI Tutor API", | ||
| "version": "1.0.0", | ||
| "documentation": "/docs", | ||
| "health_check": "/health" | ||
| } | ||
|
|
||
| @app.get("/health") | ||
| async def health_check(): | ||
| """ | ||
| Health check endpoint for monitoring | ||
| """ | ||
| return { | ||
| "status": "healthy", | ||
| "api_version": "1.0.0" | ||
| } | ||
|
|
||
| if __name__ == "__main__": | ||
| import uvicorn | ||
| uvicorn.run(app, host="0.0.0.0", port=8000) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| from pydantic import BaseModel | ||
| from typing import List, Optional | ||
|
|
||
| class SurveyOption(BaseModel): | ||
| id: int | ||
| text: str | ||
|
|
||
| class SurveyQuestion(BaseModel): | ||
| id: int | ||
| question: str | ||
| options: List[SurveyOption] | ||
| category: str | ||
|
|
||
| class UserAnswer(BaseModel): | ||
| question_id: int | ||
| selected_option_id: int | ||
|
|
||
| class UserSurveyResponse(BaseModel): | ||
| user_id: str | ||
| answers: List[UserAnswer] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| from pydantic import BaseModel | ||
| from typing import Optional, Dict, List | ||
|
|
||
| class LearningPreferences(BaseModel): | ||
| learning_format: str | ||
| weekly_time: str | ||
| learning_pace: str | ||
| theory_vs_practice: str | ||
| independence_level: str | ||
|
|
||
| class LearningSession(BaseModel): | ||
| user_id: str | ||
| topic: str | ||
| session_type: str | ||
| completion_status: str | ||
| feedback: Optional[str] | ||
|
|
||
| class LearningPath(BaseModel): | ||
| user_id: str | ||
| current_topic: str | ||
| next_topics: List[str] | ||
| recommendations: Dict[str, str] |
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will need to be adapted to our usecase |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import aiohttp | ||
| from typing import Dict, Any, Optional | ||
| import json | ||
|
|
||
| class Mem0Service: | ||
| def __init__(self, api_key: str, base_url: str = "https://api.mem0.ai/v1"): | ||
| self.api_key = api_key | ||
| self.base_url = base_url | ||
| self.headers = { | ||
| "Authorization": f"Bearer {api_key}", | ||
| "Content-Type": "application/json" | ||
| } | ||
|
|
||
| async def store_survey_response(self, user_id: str, survey_data: Dict) -> Dict[str, Any]: | ||
| """Stores survey responses in Mem0.ai""" | ||
| prompt = f""" | ||
| Analyze the learning preferences for user {user_id}: | ||
| {json.dumps(survey_data, indent=2)} | ||
|
|
||
| Based on these preferences, create a personalized learning path and provide recommendations. | ||
| Include: | ||
| 1. Suggested learning format | ||
| 2. Recommended pace | ||
| 3. Theory/practice balance | ||
| 4. Initial topics to cover | ||
| """ | ||
|
|
||
| async with aiohttp.ClientSession() as session: | ||
| async with session.post( | ||
| f"{self.base_url}/store", | ||
| headers=self.headers, | ||
| json={ | ||
| "user_id": user_id, | ||
| "content": survey_data, | ||
| "metadata": {"type": "survey_response"} | ||
| } | ||
| ) as response: | ||
| return await response.json() | ||
|
|
||
| async def get_learning_path(self, user_id: str) -> Dict[str, Any]: | ||
| """Retrieves personalized learning path based on survey responses""" | ||
| async with aiohttp.ClientSession() as session: | ||
| async with session.get( | ||
| f"{self.base_url}/users/{user_id}/learning_path", | ||
| headers=self.headers | ||
| ) as response: | ||
| return await response.json() | ||
|
|
||
| async def update_progress(self, user_id: str, progress_data: Dict) -> Dict[str, Any]: | ||
| """Updates learning progress and gets recommendations""" | ||
| async with aiohttp.ClientSession() as session: | ||
| async with session.post( | ||
| f"{self.base_url}/users/{user_id}/progress", | ||
| headers=self.headers, | ||
| json=progress_data | ||
| ) as response: | ||
| return await response.json() | ||
|
|
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will need to be adapted to our usecase. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| from typing import List, Dict | ||
| from app.models.survey import SurveyQuestion, SurveyOption | ||
|
|
||
| class SurveyService: | ||
| def __init__(self): | ||
| self.questions = [ | ||
| SurveyQuestion( | ||
| id=1, | ||
| category="learning_format", | ||
| question="What's the best way for you to receive information during learning?", | ||
| options=[ | ||
| SurveyOption(id=1, text="Text-based guide with examples"), | ||
| SurveyOption(id=2, text="Video with step-by-step explanations"), | ||
| SurveyOption(id=3, text="Practical tasks to solve on your own"), | ||
| SurveyOption(id=4, text="Combined format (all options together)") | ||
| ] | ||
| ), | ||
| SurveyQuestion( | ||
| id=2, | ||
| category="learning_pace", | ||
| question="How much time per week can you dedicate to learning?", | ||
| options=[ | ||
| SurveyOption(id=1, text="Less than 3 hours"), | ||
| SurveyOption(id=2, text="3-5 hours"), | ||
| SurveyOption(id=3, text="5-10 hours"), | ||
| SurveyOption(id=4, text="More than 10 hours") | ||
| ] | ||
| ), | ||
| SurveyQuestion( | ||
| id=3, | ||
| category="learning_style", | ||
| question="What learning pace do you prefer?", | ||
| options=[ | ||
| SurveyOption(id=1, text="Fast-paced (intensive material coverage)"), | ||
| SurveyOption(id=2, text="Balanced (mix of learning and breaks)"), | ||
| SurveyOption(id=3, text="Slow-paced (step-by-step learning)") | ||
| ] | ||
| ), | ||
| SurveyQuestion( | ||
| id=4, | ||
| category="theory_practice", | ||
| question="How do you prefer to learn new concepts?", | ||
| options=[ | ||
| SurveyOption(id=1, text="Read theory first, then practice"), | ||
| SurveyOption(id=2, text="Learn by doing, reference theory as needed") | ||
| ] | ||
| ) | ||
| ] | ||
|
|
||
| def get_all_questions(self) -> List[SurveyQuestion]: | ||
| """Retrieves all survey questions""" | ||
| return self.questions | ||
|
|
||
| def get_questions_by_category(self, category: str) -> List[SurveyQuestion]: | ||
| """Retrieves questions filtered by category""" | ||
| return [q for q in self.questions if q.category == category] | ||
|
|
||
| def validate_answer(self, question_id: int, option_id: int) -> bool: | ||
| """Validates if the answer option exists for the given question""" | ||
| question = next((q for q in self.questions if q.id == question_id), None) | ||
| if not question: | ||
| return False | ||
| return any(opt.id == option_id for opt in question.options) | ||
|
|
||
| def process_answers(self, answers: Dict) -> Dict: | ||
| """Processes survey answers and formats data for Mem0.ai storage""" | ||
| processed_data = { | ||
| "learning_preferences": {}, | ||
| "metadata": { | ||
| "timestamp": "2024-01-01T00:00:00Z", | ||
| "survey_version": "1.0" | ||
| } | ||
| } | ||
|
|
||
| for answer in answers["answers"]: | ||
| question = next(q for q in self.questions if q.id == answer["question_id"]) | ||
| option = next(opt for opt in question.options if opt.id == answer["selected_option_id"]) | ||
| processed_data["learning_preferences"][question.category] = { | ||
| "selected_option": option.text, | ||
| "question_id": question.id | ||
| } | ||
|
|
||
| return processed_data |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # config.py | ||
| from pydantic_settings import BaseSettings | ||
| from functools import lru_cache | ||
|
|
||
| class Settings(BaseSettings): | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Separate classes for different configs will be good. |
||
| # Mem0.ai settings | ||
| MEM0_API_KEY: str | ||
| MEM0_API_URL: str = "https://api.mem0.ai/v1" | ||
|
|
||
| # Database settings (if needed later) | ||
| DATABASE_URL: str = "sqlite:///./test.db" | ||
|
|
||
| # API settings | ||
| API_V1_PREFIX: str = "/api/v1" | ||
| PROJECT_NAME: str = "AI Tutor" | ||
|
|
||
| # Security settings | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WHat is the use of this code |
||
| SECRET_KEY: str | ||
| ALGORITHM: str = "HS256" | ||
| ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 | ||
|
|
||
| class Config: | ||
| env_file = ".env" | ||
| case_sensitive = True | ||
|
|
||
| @lru_cache() | ||
| def get_settings(): | ||
| return Settings() | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verify this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Push sample.env and add .env to gitignore