|
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 |
|
|
|
|
|
|
|
|
|
|
|
|