Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| import requests | |
| import openai | |
| import pinecone | |
| import json | |
| import re | |
| from tenacity import retry, stop_after_attempt, wait_exponential | |
| PINECONE_API_KEY = st.secrets["PINECONE_API_KEY"] | |
| # Set OpenAI API key from Streamlit Secrets | |
| OPENAI_API_KEY = st.secrets["OPENAI_API_KEY"] | |
| # Set maximum token length | |
| MAX_TOKENS = 1024 | |
| # Set OpenAI model | |
| MODEL = "gpt-3.5-turbo" | |
| # Initialize OpenAI | |
| openai.api_key = OPENAI_API_KEY | |
| conversation = [] | |
| def load_pinecone_index(): | |
| pinecone.init(api_key=PINECONE_API_KEY, environment="us-central1-gcp") | |
| index_name = "prequelworkshops" | |
| return pinecone.Index(index_name) | |
| def get_embeddings(texts): | |
| """ | |
| Embed texts using OpenAI's ada model. | |
| Args: | |
| texts: The list of texts to embed. | |
| Returns: | |
| A list of embeddings, each of which is a list of floats. | |
| Raises: | |
| Exception: If the OpenAI API call fails. | |
| """ | |
| # Call the OpenAI API to get the embeddings | |
| response = openai.Embedding.create(input=texts, model="text-embedding-ada-002") | |
| # Extract the embedding data from the response | |
| data = response["data"] # type: ignore | |
| # Return the embeddings as a list of lists of floats | |
| return [result["embedding"] for result in data] | |
| # Pinecone fetch function | |
| def fetch_lesson(index, query): | |
| vector = get_embeddings([query])[0] | |
| return index.query( | |
| vector=vector, | |
| # filter={ | |
| # "genre": {"$eq": "documentary"}, | |
| # "year": 2019 | |
| # }, | |
| top_k=1, | |
| include_metadata=True | |
| ) | |
| # OpenAI prompt generation function | |
| def query_openai(prompt): | |
| conversation.append({"role": "user", "content": prompt}) | |
| response = openai.ChatCompletion.create( | |
| model=MODEL, | |
| messages=conversation, | |
| max_tokens=MAX_TOKENS, | |
| n=1, | |
| stop=None, | |
| temperature=0.7 | |
| ) | |
| conversation.append(response.choices[0].message) | |
| return response.choices[0].message.content.strip() | |
| def extract_arrays(s): | |
| # Find the starting and ending indices of the array | |
| start = s.find('[') | |
| end = s.find(']') | |
| # Fix any formatting issues from GPT | |
| s = s[start:end+1] | |
| s = s.replace('"', "'") | |
| s = re.sub(r"\[\s*'", '["', s) | |
| s = re.sub(r"'\s*\]", '"]', s) | |
| s = re.sub(r",\s*'", ',"', s, 0) | |
| array_str = s.replace("',", '",') | |
| print(array_str) | |
| try: | |
| arrays = json.loads(array_str) # try to parse the string as JSON | |
| except json.JSONDecodeError as e: | |
| print(e) | |
| return None # if parsing fails, return None | |
| if isinstance(arrays, list): # if parsing succeeds and the result is a list | |
| return arrays # return the list | |
| else: | |
| return None | |
| def generate_curriculum(skills): | |
| prompt = f""" | |
| You are a world-class middle and high school educator who develops project-based entrepreneurship curriculum catered to student interests. | |
| Create a curriculum of up to 5 lessons for a course based on the student's target skills to learn. | |
| Output the curriculum as a javascript array of strings, where each string is a description of the lesson. | |
| The output should just be the array and nothing else. | |
| Student's target skills: {skills} | |
| """ | |
| response = query_openai(prompt) | |
| return extract_arrays(response) | |
| def generate_ideas(metadatas, interests): | |
| summary = "\n".join([f"- Lesson {i + 1}: {metadata['title']}. The description of the lesson is \"{metadata['description']}\". The learning outcomes are \"{metadata['outcome']}\"" for i, metadata in enumerate(metadatas)]) | |
| prompt = f""" | |
| We've created a curriculum for the student: | |
| {summary} | |
| The student has interests in \"{interests}\". | |
| What are 5 ambitious projects related to the interests that the student could do after they've learned the skills from this curriculum? | |
| The output should be a markdown list of the projects and nothing else. | |
| """ | |
| response = query_openai(prompt) | |
| return response | |
| def generate_application(metadata, interests): | |
| prompt = f""" | |
| You are now writing a description for the lesson titled \"{metadata['title']}\". | |
| This description comes after the title so don't mention the title explicitly. | |
| Sound like you're naturally explaining the lesson in person. | |
| Describe the lesson and its outcome in one sentence objectively. | |
| Next, in one separate and concise sentence, explain what the student can now do after learning this lesson. | |
| Use one of the previously described ambitious projects that's relevant to this lesson. | |
| Explain the project in full, this is the first time you're talking to the student about it. | |
| Don't put the project in quotes, don't explicitly use the words "ambitious project", don't say "for example". | |
| Don't repeat an example of a project if you've used it for a previous lesson. | |
| The sentence should implicitly help the student to feel inspired to connect with the lesson. | |
| Don't start your sentences the same way compared to your previous responses. | |
| """ | |
| response = query_openai(prompt) | |
| return response | |
| def format_metadata(metadata, interests, i): | |
| print(metadata) | |
| application = generate_application(metadata, interests) | |
| return f"Lesson {i + 1}: [{metadata['title']}]({metadata['slides']})\n\n{application}" | |
| def filtered_metadatas(lessons): | |
| metadatas = [lesson.matches[0].metadata for lesson in lessons] | |
| deduped_list = [i for n, i in enumerate(metadatas) if i not in metadatas[n + 1:]] | |
| return deduped_list | |
| # Streamlit UI | |
| st.set_page_config(layout="centered") | |
| st.title("Discover Prequel") | |
| st.markdown("Connecting learning to real-world experiences and personal passions makes education more engaging, meaningful, and applicable.\n\nUse this tool to generate a list of [Prequel](https://joinprequel.com/)'s life skill workshops relevant to each student's unique interests and goals.") | |
| skills = st.text_input("""What life skills would the student like to learn? List up to 3. | |
| _Examples: public speaking, business, time management, technology_""") | |
| interests = st.text_input("""What are some interests or goals the student has? List up to 3. | |
| _Examples: animals, video games, grow on social media_""") | |
| submit_button = st.button("Generate curriculum") | |
| status = st.empty() | |
| if submit_button: | |
| status.text("Whipping up a lesson plan as unique as you... this might take a minute") | |
| curriculum = generate_curriculum(skills) | |
| if curriculum is not None: | |
| status.text("Feeding the hamsters powering our servers...") | |
| index = load_pinecone_index() | |
| lessons = [fetch_lesson(index, lesson) for lesson in curriculum] | |
| lessons_metadata = filtered_metadatas(lessons) | |
| status.text("Harvesting the seeds of wisdom...") | |
| ideas = generate_ideas(lessons_metadata, interests) | |
| status.text("Building a bridge from your dreams to reality... just one more moment!") | |
| lesson_text = "\n\n".join([format_metadata(metadata, interests, i) for i, metadata in enumerate(lessons_metadata)]) | |
| status.empty() | |
| st.markdown(f""" | |
| \n\n## Your personalized learning playlist\n\n{lesson_text} | |
| """) | |
| else: | |
| status.text("The Wheel of Education spun out of control! Care to give it another whirl? Click 'Generate curriculum' again") | |