Spaces:
Build error
Build error
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}" | |
} | |
# Calculate sessions based on duration | |
total_sessions = duration_weeks * sessions_per_week # Assuming 2 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 | |
# Validate session count | |
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) | |
# try: | |
# return json.loads(response.choices[0].message.content) | |
# except json.JSONDecodeError as e: | |
# st.error(f"Failed to decode JSON response: {e}") | |
# return None | |
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): | |
# First generate a course plan using Perplexity API | |
# course_plan = generate_perplexity_response(PERPLEXITY_API_KEY, course_name, duration_weeks, sessions_per_week) | |
# course_plan_json = json.loads(course_plan) | |
# print("Course Structure is: \n", course_plan_json); | |
# Earlier Code: | |
# Generate sessions for each module with resources | |
# 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=topic['resources'] | |
# ) | |
# all_sessions.append(session) | |
# current_date += timedelta(days=3.5) # Spacing sessions evenly across the week | |
# return course_plan_json, all_sessions | |
# New Code: | |
# Extract all session titles | |
session_titles = [] | |
# Load the course plan JSON | |
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']) | |
# Generate resources for all sessions | |
session_resources = generate_session_resources(PERPLEXITY_API_KEY, session_titles) | |
# print("Session Resources are: \n", session_resources) | |
resources = json.loads(session_resources) | |
# print("Resources JSON is: \n", resources_json) | |
# print("Session Resources are: \n", session_resources) | |
# Create a mapping of session titles to their resources | |
# Import Resources JSON | |
# resources = {} | |
# with open('sample_files/sample_course_resources.json', 'r') as file: | |
# resources = json.load(file) | |
resources_map = { | |
resource['session_title']: resource['resources'] | |
for resource in resources['session_resources'] | |
} | |
print("Resources Map is: \n", resources_map) | |
# print("Sample is: ", resources_map.get('Overview of ML Concepts, History, and Applications')); | |
# Generate sessions with their corresponding resources | |
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 | |
# if __name__ == "__main__": | |
# course_name = "Introduction to Machine Learning" | |
# start_date = datetime(2022, 9, 1) | |
# duration_weeks = 4 | |
# create_course(course_name, start_date, duration_weeks, 3) |