| | from datetime import datetime, timedelta |
| | import os |
| | from typing import Dict, List, Any |
| | from pymongo import MongoClient |
| | import requests |
| | import uuid |
| | import openai |
| | from openai import OpenAI |
| | import streamlit as st |
| | from bson import ObjectId |
| | from dotenv import load_dotenv |
| | import json |
| |
|
| | load_dotenv() |
| | MONGODB_URI = os.getenv("MONGO_URI") |
| | PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_KEY") |
| | OPENAI_API_KEY = os.getenv("OPENAI_KEY") |
| |
|
| | client = MongoClient(MONGODB_URI) |
| | db = client['novascholar_db'] |
| | courses_collection = db['courses'] |
| |
|
| | def generate_perplexity_response(api_key, course_name, duration_weeks, sessions_per_week): |
| | headers = { |
| | "accept": "application/json", |
| | "content-type": "application/json", |
| | "authorization": f"Bearer {api_key}" |
| | } |
| | |
| | |
| | total_sessions = duration_weeks * sessions_per_week |
| | |
| | prompt = f""" |
| | You are an expert educational AI assistant specializing in curriculum design and instructional planning. Your task is to generate a comprehensive, academically rigorous course structure for the course {course_name} that fits exactly within {duration_weeks} weeks with {total_sessions} total sessions ({sessions_per_week} sessions per week). |
| | |
| | Please generate a detailed course structure in JSON format following these specifications: |
| | |
| | 1. The course structure must be designed for exactly {duration_weeks} weeks with {total_sessions} total sessions |
| | 2. Each module should contain an appropriate number of sessions that sum up to exactly {total_sessions} |
| | 3. Each session should be designed for a 1-1.5-hour class duration |
| | 4. Follow standard academic practices and nomenclature |
| | 5. Ensure progressive complexity from foundational to advanced concepts |
| | 6. The course_title should exactly match the course name provided |
| | 7. Ensure that the property names are enclosed in double quotes (") and followed by a colon (:), and the values are enclosed in double quotes ("). |
| | 8. **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}.** |
| | |
| | The JSON response should follow this structure: |
| | {{ |
| | "course_title": "string", |
| | "course_description": "string", |
| | "total_duration_weeks": {duration_weeks}, |
| | "sessions_per_week": {sessions_per_week}, |
| | "total_sessions": {total_sessions}, |
| | "modules": [ |
| | {{ |
| | "module_title": "string", |
| | "module_duration_sessions": number, |
| | "sub_modules": [ |
| | {{ |
| | "title": "string", |
| | "topics": [ |
| | {{ |
| | "title": "string", |
| | "short_description": "string", |
| | "concise_learning_objectives": ["string"] |
| | }} |
| | ] |
| | }} |
| | ] |
| | }} |
| | ] |
| | }} |
| | |
| | Ensure that: |
| | 1. The sum of all module_duration_sessions equals exactly {total_sessions} |
| | 2. Each topic has clear learning objectives |
| | 3. Topics build upon each other logically |
| | 4. Content is distributed evenly across the available sessions |
| | 5. **This Instruction is Strictly followed: **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}.**** |
| | |
| | """ |
| |
|
| | messages = [ |
| | { |
| | "role": "system", |
| | "content": ( |
| | "You are an expert educational AI assistant specializing in course design and curriculum planning. " |
| | "Your task is to generate accurate, detailed, and structured educational content that precisely fits " |
| | "the specified duration." |
| | ), |
| | }, |
| | { |
| | "role": "user", |
| | "content": prompt |
| | }, |
| | ] |
| | |
| | try: |
| | client = OpenAI(api_key=api_key, base_url="https://api.perplexity.ai") |
| | response = client.chat.completions.create( |
| | model="llama-3.1-sonar-small-128k-online", |
| | messages=messages |
| | ) |
| | content = response.choices[0].message.content |
| | |
| | |
| | course_plan = json.loads(content) |
| | total_planned_sessions = sum( |
| | module.get('module_duration_sessions', 0) |
| | for module in course_plan.get('modules', []) |
| | ) |
| | |
| | if abs(total_planned_sessions - total_sessions) > 5: |
| | raise ValueError(f"Generated plan has {total_planned_sessions} sessions, but {total_sessions} were requested") |
| | |
| | return content |
| | except Exception as e: |
| | st.error(f"Failed to fetch data from Perplexity API: {e}") |
| | return "" |
| |
|
| | def generate_session_resources(api_key, session_titles: List[str]): |
| | """ |
| | Generate relevant resources for each session title separately |
| | """ |
| | resources_prompt = f""" |
| | You are an expert educational content curator. For each session title provided, suggest highly relevant and accurate learning resources. |
| | Please provide resources for these sessions: {session_titles} |
| | |
| | For each session, provide resources in this JSON format: |
| | {{ |
| | "session_resources": [ |
| | {{ |
| | "session_title": "string", |
| | "resources": {{ |
| | "readings": [ |
| | {{ |
| | "title": "string", |
| | "url": "string", |
| | "type": "string", |
| | "estimated_read_time": "string" |
| | }} |
| | ], |
| | "books": [ |
| | {{ |
| | "title": "string", |
| | "author": "string", |
| | "isbn": "string", |
| | "chapters": "string" |
| | }} |
| | ], |
| | "additional_resources": [ |
| | {{ |
| | "title": "string", |
| | "url": "string", |
| | "type": "string", |
| | "description": "string" |
| | }} |
| | ] |
| | }} |
| | }} |
| | ] |
| | }} |
| | |
| | Guidelines: |
| | 1. Ensure all URLs are real and currently active |
| | 2. Prioritize high-quality, authoritative sources |
| | 3. Include 1-2 resources of each type |
| | 5. For readings, include a mix of academic and practical resources. It can exceed to 3-4 readings |
| | 6. Book references should be real, recently published works |
| | 7. Additional resources can include tools, documentation, or practice platforms |
| | 8. Ensure that the property names are enclosed in double quotes (") and followed by a colon (:), and the values are enclosed in double quotes ("). |
| | 9. ***NOTE: **DO NOT INCLUDE THE WORD JSON IN THE OUTPUT STRING, DO NOT INCLUDE BACKTICKS (```) IN THE OUTPUT, AND DO NOT INCLUDE ANY OTHER TEXT, OTHER THAN THE ACTUAL JSON RESPONSE. START THE RESPONSE STRING WITH AN OPEN CURLY BRACE {{ AND END WITH A CLOSING CURLY BRACE }}.** |
| | """ |
| |
|
| | messages = [ |
| | { |
| | "role": "system", |
| | "content": "You are an expert educational content curator, focused on providing accurate and relevant learning resources.", |
| | }, |
| | { |
| | "role": "user", |
| | "content": resources_prompt |
| | }, |
| | ] |
| |
|
| | try: |
| | client = OpenAI(api_key=api_key, base_url="https://api.perplexity.ai") |
| | response = client.chat.completions.create( |
| | model="llama-3.1-sonar-small-128k-online", |
| | messages=messages |
| | ) |
| | print("Response is: \n", response.choices[0].message.content) |
| | |
| | |
| | |
| | |
| | |
| | return response.choices[0].message.content |
| | except Exception as e: |
| | st.error(f"Failed to generate resources: {e}") |
| | return None |
| |
|
| | def validate_course_plan(course_plan): |
| | required_fields = ['course_title', 'course_description', 'modules'] |
| | if not all(field in course_plan for field in required_fields): |
| | raise ValueError("Invalid course plan structure") |
| | |
| | for module in course_plan['modules']: |
| | if 'module_title' not in module or 'sub_modules' not in module: |
| | raise ValueError("Invalid module structure") |
| |
|
| | def create_session(title: str, date: datetime, module_name: str, resources: dict): |
| | """Create a session document with pre-class, in-class, and post-class components.""" |
| | return { |
| | "session_id": ObjectId(), |
| | "title": title, |
| | "date": date, |
| | "status": "upcoming", |
| | "created_at": datetime.utcnow(), |
| | "module_name": module_name, |
| | "pre_class": { |
| | "resources": [], |
| | "completion_required": True |
| | }, |
| | "in_class": { |
| | "quiz": [], |
| | "polls": [] |
| | }, |
| | "post_class": { |
| | "assignments": [] |
| | }, |
| | "external_resources": { |
| | "readings": resources.get("readings", []), |
| | "books": resources.get("books", []), |
| | "additional_resources": resources.get("additional_resources", []) |
| | } |
| | } |
| |
|
| | def create_course(course_name: str, start_date: datetime, duration_weeks: int, sessions_per_week: int): |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | session_titles = [] |
| | |
| | course_plan_json = {} |
| | with open('sample_files/sample_course.json', 'r') as file: |
| | course_plan_json = json.load(file) |
| |
|
| | for module in course_plan_json['modules']: |
| | for sub_module in module['sub_modules']: |
| | for topic in sub_module['topics']: |
| | session_titles.append(topic['title']) |
| | |
| | |
| | session_resources = generate_session_resources(PERPLEXITY_API_KEY, session_titles) |
| | |
| | resources = json.loads(session_resources) |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | resources_map = { |
| | resource['session_title']: resource['resources'] |
| | for resource in resources['session_resources'] |
| | } |
| | print("Resources Map is: \n", resources_map) |
| | |
| | |
| | all_sessions = [] |
| | current_date = start_date |
| | |
| | for module in course_plan_json['modules']: |
| | for sub_module in module['sub_modules']: |
| | for topic in sub_module['topics']: |
| | session = create_session( |
| | title=topic['title'], |
| | date=current_date, |
| | module_name=module['module_title'], |
| | resources=resources_map.get(topic['title'], {}) |
| | ) |
| | all_sessions.append(session) |
| | current_date += timedelta(days=3.5) |
| | |
| | print("All Sessions are: \n", all_sessions) |
| |
|
| | def get_new_course_id(): |
| | """Generate a new course ID by incrementing the last course ID""" |
| | last_course = courses_collection.find_one(sort=[("course_id", -1)]) |
| | if last_course: |
| | last_course_id = int(last_course["course_id"][2:]) |
| | new_course_id = f"CS{last_course_id + 1}" |
| | else: |
| | new_course_id = "CS101" |
| | return new_course_id |
| |
|
| | |
| | |
| | |
| | |
| | |