Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, HTTPException
|
2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
3 |
+
from fastapi.responses import StreamingResponse
|
4 |
+
from pydantic import BaseModel, Field
|
5 |
+
from typing import List, Optional
|
6 |
+
from llama_index.llms.ollama import Ollama
|
7 |
+
from llama_index.core import PromptTemplate
|
8 |
+
from llama_index.llms.groq import Groq
|
9 |
+
import os
|
10 |
+
import dotenv
|
11 |
+
from enum import Enum
|
12 |
+
|
13 |
+
dotenv.load_dotenv()
|
14 |
+
|
15 |
+
app = FastAPI()
|
16 |
+
|
17 |
+
# Enable CORS for all origins (for development purposes)
|
18 |
+
app.add_middleware(
|
19 |
+
CORSMiddleware,
|
20 |
+
allow_origins=["*"],
|
21 |
+
allow_credentials=True,
|
22 |
+
allow_methods=["*"],
|
23 |
+
allow_headers=["*"],
|
24 |
+
)
|
25 |
+
|
26 |
+
# Load LLM model
|
27 |
+
# llm = Ollama(model="llama3", request_timeout=120.0)
|
28 |
+
# llama3-70b-8192
|
29 |
+
llm = Groq(model="llama-3.3-70b-versatile",
|
30 |
+
api_key=os.getenv("GROQ_API_KEY"),
|
31 |
+
)
|
32 |
+
|
33 |
+
|
34 |
+
|
35 |
+
class Options(BaseModel):
|
36 |
+
"""A quiz with a title and a list of questions."""
|
37 |
+
questions: List[str] = Field(..., description="List of questions in the quiz")
|
38 |
+
|
39 |
+
class Quiz(BaseModel):
|
40 |
+
"""An album inspired by a movie, containing an artist and a list of songs."""
|
41 |
+
question: str = Field(..., description="The question to be answered")
|
42 |
+
correct_answer: str = Field(..., description="The correct answer to the question")
|
43 |
+
options: List[Options] = Field(..., description="List of options for the question")
|
44 |
+
|
45 |
+
class Difficulty(str, Enum):
|
46 |
+
easy = "Easy"
|
47 |
+
medium = "Medium"
|
48 |
+
hard = "Hard"
|
49 |
+
|
50 |
+
class QuizFormat(str, Enum):
|
51 |
+
multiple_choice = "Multiple Choice"
|
52 |
+
open_ended = "Open ended"
|
53 |
+
true_false = "True/False"
|
54 |
+
|
55 |
+
class QuizRequest(BaseModel):
|
56 |
+
topic: str = Field(..., description="Topic of the quiz")
|
57 |
+
subject: str = Field(..., description="Subject of the quiz")
|
58 |
+
grade_level: str = Field(..., description="Grade level (e.g., Primary 3, JSS 1)")
|
59 |
+
description: Optional[str] = Field(None, description="Brief description of the quiz (optional)")
|
60 |
+
difficulty: Difficulty = Field(..., description="Difficulty level")
|
61 |
+
format: QuizFormat = Field(..., description="Format of the quiz")
|
62 |
+
num_questions: int = Field(..., gt=0, le=50, description="Number of questions (1-50)")
|
63 |
+
language: str = Field(..., description="Language to translate the quiz into")
|
64 |
+
|
65 |
+
# Define the prompt template for quiz generation
|
66 |
+
PROMPT_TEMPLATE = """
|
67 |
+
Generate a set of quizzes that align with the given requirements:
|
68 |
+
Topic: {topic}
|
69 |
+
Subject: {subject}
|
70 |
+
Grade Level: {grade_level}
|
71 |
+
{description_part}
|
72 |
+
Difficulty: {difficulty}
|
73 |
+
Format: {format}
|
74 |
+
Number of Questions: {num_questions}
|
75 |
+
Ensure the format follows and ensure its in json format. No extra information just your response please and make it well formatted:
|
76 |
+
[
|
77 |
+
{{
|
78 |
+
"question": "Question text here?",
|
79 |
+
"correct_answer": "Correct answer",
|
80 |
+
"options": ["Option 1", "Option 2", "Option 3", "Option 4"]
|
81 |
+
}},
|
82 |
+
]
|
83 |
+
"""
|
84 |
+
|
85 |
+
FORMAT_PROMPT_TEMPLATE = """
|
86 |
+
You are a formatting assistant. Your ONLY job is to format quizzes into a structured, teacher-friendly format.
|
87 |
+
|
88 |
+
STRICT INSTRUCTIONS:
|
89 |
+
1. Do NOT include any explanations, introductions, or thought processes. Output ONLY the formatted quiz.
|
90 |
+
2. Do NOT add or remove content from questions, answers, or options.
|
91 |
+
3. Format each quiz as follows:
|
92 |
+
- Start with the question number (e.g., "1. Question text here?").
|
93 |
+
- On the next line, write "Answer: " followed by the correct answer.
|
94 |
+
- On the next line, write "Options: " followed by the list of options, each on a new line with a bullet point.
|
95 |
+
4. Separate each quiz with two blank lines for clarity.
|
96 |
+
5. Use STRICTLY these HTML tags for proper formatting:
|
97 |
+
- `<br>` for line breaks.
|
98 |
+
- `<p>` for paragraphs.
|
99 |
+
- `<ul>` and `<li>` for lists.
|
100 |
+
|
101 |
+
Output ONLY the formatted quiz. No extra text.
|
102 |
+
|
103 |
+
Here is the input JSON:
|
104 |
+
{quiz_json}
|
105 |
+
"""
|
106 |
+
|
107 |
+
# Define the prompt template for translation
|
108 |
+
TRANSLATION_PROMPT_TEMPLATE = """
|
109 |
+
Translate the following text into {target_language}. Ensure the translation is accurate and maintains the original meaning:
|
110 |
+
{text}
|
111 |
+
"""
|
112 |
+
|
113 |
+
def format_quiz_stream(prompt: str):
|
114 |
+
"""Stream responses from the LLM for formatting"""
|
115 |
+
response = llm.complete(prompt)
|
116 |
+
if hasattr(response, "text"):
|
117 |
+
for chunk in response.text.split("\n"):
|
118 |
+
yield chunk
|
119 |
+
elif hasattr(response, "content"):
|
120 |
+
for chunk in response.content.split("\n"):
|
121 |
+
yield chunk
|
122 |
+
else:
|
123 |
+
raise AttributeError("Response does not contain text content.")
|
124 |
+
|
125 |
+
def translate_text(text: str, target_language: str) -> str:
|
126 |
+
"""Translate text to the desired language using the LLM"""
|
127 |
+
translation_prompt = TRANSLATION_PROMPT_TEMPLATE.format(
|
128 |
+
target_language=target_language,
|
129 |
+
text=text
|
130 |
+
)
|
131 |
+
translation_response = llm.complete(translation_prompt)
|
132 |
+
return translation_response.text
|
133 |
+
|
134 |
+
@app.post("/generate-quizzes/")
|
135 |
+
def generate_quiz(quiz_request: QuizRequest):
|
136 |
+
print(quiz_request)
|
137 |
+
"""Generates quizzes dynamically based on user input"""
|
138 |
+
try:
|
139 |
+
# Handle optional description
|
140 |
+
description_part = f"Description: {quiz_request.description}" if quiz_request.description else ""
|
141 |
+
|
142 |
+
# Step 1: Generate the raw quiz JSON using the first LLM
|
143 |
+
prompt = PROMPT_TEMPLATE.format(
|
144 |
+
topic=quiz_request.topic,
|
145 |
+
subject=quiz_request.subject,
|
146 |
+
grade_level=quiz_request.grade_level,
|
147 |
+
format=quiz_request.format,
|
148 |
+
description_part=description_part, # This avoids adding "Description: None" if it's not provided
|
149 |
+
difficulty=quiz_request.difficulty,
|
150 |
+
num_questions=quiz_request.num_questions
|
151 |
+
)
|
152 |
+
|
153 |
+
# Generate the raw quiz JSON
|
154 |
+
raw_quiz_response = llm.complete(prompt).text
|
155 |
+
|
156 |
+
# Step 2: Translate the raw quiz JSON into the desired language
|
157 |
+
translated_quiz_response = translate_text(raw_quiz_response, quiz_request.language)
|
158 |
+
|
159 |
+
# Step 3: Format the translated quizzes into a teacher-friendly format
|
160 |
+
format_prompt = FORMAT_PROMPT_TEMPLATE.format(
|
161 |
+
quiz_json=translated_quiz_response,
|
162 |
+
|
163 |
+
)
|
164 |
+
|
165 |
+
# Return the formatted quizzes as a streaming response
|
166 |
+
return StreamingResponse(format_quiz_stream(format_prompt), media_type="text/plain")
|
167 |
+
except Exception as e:
|
168 |
+
print(e)
|
169 |
+
raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
|