Jeff Myers II commited on
Commit
64fde65
·
1 Parent(s): 13a2860

Completed Prototype

Browse files
Files changed (3) hide show
  1. Gemma_Model.py → Gemma.py +20 -57
  2. News.py +29 -24
  3. app.py +259 -52
Gemma_Model.py → Gemma.py RENAMED
@@ -1,9 +1,4 @@
1
- from transformers import (
2
- # AutoTokenizer,
3
- # BitsAndBytesConfig,
4
- Gemma3ForCausalLM,
5
- GemmaTokenizerFast
6
- )
7
  import torch
8
  import json
9
  import os
@@ -14,21 +9,16 @@ class GemmaLLM:
14
 
15
  def __init__(self):
16
  model_id = "google/gemma-3-1b-it"
17
- # quantization_config = BitsAndBytesConfig(load_in_8bit=True)
18
 
 
19
  self.model = Gemma3ForCausalLM.from_pretrained(
20
  model_id,
21
- device_map="cpu",
22
- # quantization_config=quantization_config,
23
- # low_cpu_mem_usage=True,
24
  torch_dtype=torch.float16,
25
  token=os.environ.get("GEMMA_TOKEN"),
26
  ).eval()
27
 
28
- self.tokenizer = GemmaTokenizerFast.from_pretrained(model_id, token=os.environ.get("GEMMA_TOKEN"))
29
-
30
  def generate(self, message) -> str:
31
- print("Generating...")
32
  inputs = self.tokenizer.apply_chat_template(
33
  message,
34
  add_generation_prompt=True,
@@ -40,72 +30,45 @@ class GemmaLLM:
40
  input_length = inputs["input_ids"].shape[1]
41
 
42
  with torch.inference_mode():
43
- outputs = self.model.generate(
44
- **inputs, max_new_tokens=1024,
45
- )[0][input_length:]
46
-
47
- outputs = self.tokenizer.decode(outputs, skip_special_tokens=True)
48
-
49
- print("Completed generating!")
50
 
51
  return outputs
52
 
53
- def get_summary_message(self, article, num_paragraphs) -> dict:
54
 
55
  summarize = "You are a helpful assistant. Your main task is to summarize articles. You will be given an article that you will generate a summary for. The summary should include all the key points of the article. ONLY RESPOND WITH THE SUMMARY!!!"
56
 
57
  summary = f"Summarize the data in the following JSON into {num_paragraphs} paragraph(s) so that it is easy to read and understand:\n"
58
 
59
- message = [
60
- {
61
- "role": "system",
62
- "content": [
63
- {"type": "text", "text": summarize},
64
- ],
65
- },
66
- {
67
- "role": "user",
68
- "content": [
69
- {"type": "text", "text": summary + json.dumps(article, indent=4)},
70
- ],
71
- },
72
- ]
73
 
74
  return message
75
 
76
- def get_summary(self, message) -> str:
 
77
  summary = self.generate(message)
78
 
79
  return summary
80
 
81
- def get_questions_message(self, summary, num_questions, difficulty) -> dict:
82
- schema = json.dumps([
 
83
  dict(question=str.__name__, correct_answer=str.__name__, false_answers=[str.__name__, str.__name__, str.__name__]),
84
  dict(question=str.__name__, correct_answer=str.__name__, false_answers=[str.__name__, str.__name__, str.__name__]),
85
- dict(question=str.__name__, correct_answer=str.__name__, false_answers=[str.__name__, str.__name__, str.__name__])], indent=4)
86
-
87
- question = "You are a helpful assistant. Your main task is to generate " + str(num_questions) + " multiple choice questions from an article. Respond in the following JSON structure and schema:\n\njson\n```\n" + schema + "\n```\n\nThere should only be " + str(num_questions) + " questions generated. Each question should only have 3 false answers and 1 correct answer. The correct answer should be the most relevant answer based on the context derived from the article. False answers should not contain the correct answer. False answers should contain false information but also be reasonably plausible for answering the question. ONLY RESPOND WITH RAW JSON!!!"
88
 
89
- questions = f"Generate {difficulty} questions and answers from the following article:\n"
90
 
91
- message = [
92
- {
93
- "role": "system",
94
- "content": [
95
- {"type": "text", "text": question},
96
- ],
97
- },
98
- {
99
- "role": "user",
100
- "content": [
101
- {"type": "text", "text": questions + summary},
102
- ],
103
- },
104
- ]
105
 
106
  return message
107
 
108
- def get_questions(self, message) -> dict:
 
109
  questions = self.generate(message)
110
 
111
  return json.loads(questions.strip("```").replace("json\n", ""))
 
1
+ from transformers import AutoTokenizer, Gemma3ForCausalLM
 
 
 
 
 
2
  import torch
3
  import json
4
  import os
 
9
 
10
  def __init__(self):
11
  model_id = "google/gemma-3-1b-it"
 
12
 
13
+ self.tokenizer = AutoTokenizer.from_pretrained(model_id)
14
  self.model = Gemma3ForCausalLM.from_pretrained(
15
  model_id,
16
+ device_map="cuda" if torch.cuda.is_available() else "cpu",
 
 
17
  torch_dtype=torch.float16,
18
  token=os.environ.get("GEMMA_TOKEN"),
19
  ).eval()
20
 
 
 
21
  def generate(self, message) -> str:
 
22
  inputs = self.tokenizer.apply_chat_template(
23
  message,
24
  add_generation_prompt=True,
 
30
  input_length = inputs["input_ids"].shape[1]
31
 
32
  with torch.inference_mode():
33
+ outputs = self.model.generate(**inputs, max_new_tokens=1024)[0][input_length:]
34
+ outputs = self.tokenizer.decode(outputs, skip_special_tokens=True)
 
 
 
 
 
35
 
36
  return outputs
37
 
38
+ def _get_summary_message(self, article, num_paragraphs) -> dict:
39
 
40
  summarize = "You are a helpful assistant. Your main task is to summarize articles. You will be given an article that you will generate a summary for. The summary should include all the key points of the article. ONLY RESPOND WITH THE SUMMARY!!!"
41
 
42
  summary = f"Summarize the data in the following JSON into {num_paragraphs} paragraph(s) so that it is easy to read and understand:\n"
43
 
44
+ message = [{"role": "system", "content": [{"type": "text", "text": summarize}]},
45
+ {"role": "user", "content": [{"type": "text", "text": summary + json.dumps(article, indent=4)}]}]
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  return message
48
 
49
+ def get_summary(self, article, num_paragraphs) -> str:
50
+ message = self._get_summary_message(article, num_paragraphs)
51
  summary = self.generate(message)
52
 
53
  return summary
54
 
55
+ def _get_questions_message(self, summary, num_questions, difficulty) -> dict:
56
+ question = f"""
57
+ You are a helpful assistant. Your main task is to generate {num_questions} multiple choice questions from an article. Respond in the following JSON structure and schema:\n\njson\n```{json.dumps(list((
58
  dict(question=str.__name__, correct_answer=str.__name__, false_answers=[str.__name__, str.__name__, str.__name__]),
59
  dict(question=str.__name__, correct_answer=str.__name__, false_answers=[str.__name__, str.__name__, str.__name__]),
60
+ dict(question=str.__name__, correct_answer=str.__name__, false_answers=[str.__name__, str.__name__, str.__name__]))), indent=4)}```\n\nThere should only be {num_questions} questions generated. Each question should only have 3 false answers and 1 correct answer. The correct answer should be the most relevant answer based on the context derived from the article. False answers should not contain the correct answer. False answers should contain false information but also be reasonably plausible for answering the question. ONLY RESPOND WITH RAW JSON!!!
61
+ """
 
62
 
63
+ questions = f"Generate {difficulty.lower()} questions and answers from the following article:\n"
64
 
65
+ message = [{"role": "system", "content": [{"type": "text", "text": question}]},
66
+ {"role": "user", "content": [{"type": "text", "text": questions + summary}]}]
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  return message
69
 
70
+ def get_questions(self, summary, num_questions, difficulty) -> dict:
71
+ message = self._get_questions_message(summary, num_questions, difficulty)
72
  questions = self.generate(message)
73
 
74
  return json.loads(questions.strip("```").replace("json\n", ""))
News.py CHANGED
@@ -5,58 +5,63 @@ import os
5
  __export__ = ["News"]
6
 
7
  class News:
8
- __EX_SOURCES__ = {"ABC News", "Bloomberg", "The Hill", "Fox Sports", "Google News"}
9
- __CATEGORIES__ = {
10
- "business",
11
- "entertainment",
12
- "general",
13
- "health",
14
- "science",
15
- "sports",
16
- "technology"
17
- }
18
 
19
  def __init__(self):
20
  newsapi_key = os.environ.get("NEWS_API_KEY")
21
  self.newsapi = NewsApiClient(api_key=newsapi_key)
22
 
23
  def get_sources(self, category=None):
24
- sources = self.newsapi.get_sources(language="en", country="us", category=category)["sources"]
25
  sources = {source["name"] for source in sources if source["name"] not in self.__EX_SOURCES__}
26
- print(sources)
27
  return sources
28
-
29
 
30
- def get_top_headlines(self, num_headlines=None, category=None):
31
- sources = self.get_sources(category=category)
32
 
33
  headlines = self.newsapi.get_top_headlines(
34
  sources=", ".join(sources),
35
- page_size=num_headlines
 
36
  )["articles"]
37
 
 
 
38
  return headlines
39
 
40
- def get_headlines(self, num_headlines=None, query=None):
41
  sources = self.get_sources()
42
 
43
  headlines = self.newsapi.get_everything(
44
  q=query,
45
  sources=", ".join(sources),
46
- page_size=num_headlines
 
47
  )["articles"]
48
 
 
 
49
  return headlines
50
 
51
- def get_articles_from_headlines(self, headlines):
52
  for headline in headlines:
 
 
 
 
53
  article = Article(headline["url"])
54
  article.download()
55
  article.parse()
 
56
  headline["content"] = article.text
57
- # headline["authors"] = article.authors
58
- headline["source"] = headline["source"]["name"]
59
- del headline["author"]
60
- # headline.pop("author", None)
61
-
62
  return headlines
 
 
5
  __export__ = ["News"]
6
 
7
  class News:
8
+ __EX_SOURCES__ = ["ABC News", "Bloomberg", "The Hill", "Fox Sports", "Google News", "Newsweek"]
9
+ __CATEGORIES__ = [
10
+ "General",
11
+ "Business",
12
+ "Entertainment",
13
+ "Health",
14
+ "Science",
15
+ "Technology"
16
+ ]
 
17
 
18
  def __init__(self):
19
  newsapi_key = os.environ.get("NEWS_API_KEY")
20
  self.newsapi = NewsApiClient(api_key=newsapi_key)
21
 
22
  def get_sources(self, category=None):
23
+ sources = self.newsapi.get_sources(language="en", country="us", category=category.lower() if category else category)["sources"]
24
  sources = {source["name"] for source in sources if source["name"] not in self.__EX_SOURCES__}
 
25
  return sources
 
26
 
27
+ def get_top_headlines(self, num_headlines=5, category=None):
28
+ sources = self.get_sources(category.lower() if category else category)
29
 
30
  headlines = self.newsapi.get_top_headlines(
31
  sources=", ".join(sources),
32
+ page_size=num_headlines,
33
+ language="en",
34
  )["articles"]
35
 
36
+ headlines = self._get_articles_from_headlines(headlines)
37
+
38
  return headlines
39
 
40
+ def get_headlines(self, num_headlines=5, query=None):
41
  sources = self.get_sources()
42
 
43
  headlines = self.newsapi.get_everything(
44
  q=query,
45
  sources=", ".join(sources),
46
+ page_size=num_headlines,
47
+ lanuguage="en",
48
  )["articles"]
49
 
50
+ headlines = self._get_articles_from_headlines(headlines)
51
+
52
  return headlines
53
 
54
+ def _get_articles_from_headlines(self, headlines):
55
  for headline in headlines:
56
+ del headline["author"]
57
+
58
+ headline["source"] = headline["source"]["name"]
59
+
60
  article = Article(headline["url"])
61
  article.download()
62
  article.parse()
63
+
64
  headline["content"] = article.text
65
+
 
 
 
 
66
  return headlines
67
+
app.py CHANGED
@@ -1,64 +1,271 @@
1
- from flask import Flask, request, jsonify
2
- from flask_cors import CORS
 
3
  from News import News
4
- from Gemma_Model import GemmaLLM
5
 
6
- print("Starting server...")
 
 
7
 
8
- app = Flask(__name__)
9
- CORS(app)
 
10
 
11
- news = News()
12
- gemma = GemmaLLM()
 
 
 
 
 
 
 
13
 
14
- # business entertainment general health science sports technology
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- @app.get("/get_top_articles/")
17
- @app.get("/get_top_articles/<string:category>/")
18
- @app.get("/get_top_articles/<int:num_articles>/")
19
- @app.get("/get_top_articles/<int:num_articles>/<string:category>/")
20
- def get_top_articles(num_articles=5, category=None):
21
- if category is not None: category = category.lower()
22
- articles = news.get_top_headlines(num_headlines=num_articles, category=category)
23
- articles = news.get_articles_from_headlines(articles)
24
-
25
- return jsonify(articles)
26
 
27
- @app.get("/get_articles/")
28
- @app.get("/get_articles/<string:query>/")
29
- @app.get("/get_articles/<int:num_articles>/")
30
- @app.get("/get_articles/<int:num_articles>/<string:query>/")
31
- def get_articles(num_articles=5, query=None):
32
- if query is not None: query = query.lower()
33
- articles = news.get_headlines(num_headlines=num_articles, query=query)
34
- articles = news.get_articles_from_headlines(articles)
35
-
36
- return jsonify(articles)
37
 
38
- @app.post("/get_summary/")
39
- @app.post("/get_summary/<int:num_paragraphs>/")
40
- def get_summary(num_paragraphs=1):
41
- article = request.json
42
- message = gemma.get_summary_message(article, num_paragraphs)
43
- summary = gemma.get_summary(message)
44
- article["summary"] = summary
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- return jsonify(article)
47
 
48
- @app.post("/get_questions/")
49
- @app.post("/get_questions/<string:difficulty>/")
50
- @app.post("/get_questions/<int:num_questions>/")
51
- @app.post("/get_questions/<int:num_questions>/<string:difficulty>/")
52
- def get_questions(num_questions=3, difficulty="average"):
53
- if "summary" in request.json:
54
- summary = request.json["summary"]
55
- questions = gemma.get_questions(gemma.get_questions_message(summary, num_questions, difficulty))
56
- elif "summaries" in request.json:
57
- summaries = request.json["summaries"]
58
- messages = [gemma.get_questions_message(summary) for summary in summaries]
59
- questions = gemma.get_questions(messages)
60
- else: return jsonify({})
61
 
62
- return jsonify(questions)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- if __name__ == "__main__": app.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %%
2
+ import random
3
+ import gradio as gr
4
  from News import News
5
+ from Gemma import GemmaLLM
6
 
7
+ # %%
8
+ class Cooldown:
9
+ ...
10
 
11
+ cooldown = Cooldown() ################################ News fetching cooldown in seconds
12
+ news = News() ######################################## Initialize the News object
13
+ model = GemmaLLM() ################################### Initialize the Gemma model
14
 
15
+ # %%
16
+ with gr.Blocks() as demo:
17
+ gr.Markdown("# News Quiz Application")
18
+
19
+ ######
20
+ ###### State Variables and Components Initialization
21
+ ######
22
+
23
+ init = ( ######################################### Initialize the Gradio interface with components and state variables
24
 
25
+ gr.Markdown("## News Articles"),
26
+
27
+ ## State variables for news articles and quiz,
28
+
29
+ gr.State({}), gr.State([]), gr.State({}),
30
+
31
+ gr.Slider(label="Number of Articles", minimum=1, maximum=10, value=3, step=1),
32
+ gr.Radio(label="Category (optional)", choices=news.__CATEGORIES__),
33
+ gr.Button("Get Articles"),
34
+
35
+ gr.Radio(visible=False),
36
+ gr.Textbox(visible=False),
37
+ gr.Button(visible=False),
38
+
39
+ gr.Checkbox(visible=False),
40
+ gr.Textbox(visible=False),
41
+ gr.Button(visible=False),
42
 
43
+ ## State variables for quiz
44
+
45
+ gr.State([]), gr.State([]), gr.State(),
46
+
47
+ [gr.Radio(visible=False) for _ in range(10)],
48
+ gr.Button(visible=False),
49
+
50
+ gr.Textbox(visible=False),
51
+ gr.Button(visible=False),
 
52
 
53
+ )
 
 
 
 
 
 
 
 
 
54
 
55
+ ( ################################################ State variables and components for news articles and quiz
56
+
57
+ heading,
58
+
59
+ ## Components for news articles
60
+
61
+ article, articles, descriptions,
62
+ num_headlines, category, get,
63
+ headline, description, show,
64
+ summarize, content, ready,
65
+
66
+ ## Components for quiz
67
+
68
+ answers, response, results,
69
+ quiz, submit,
70
+ evaluation, read,
71
+
72
+ ) = init
73
+
74
+ def hide_news(): ################################# Hide news-related components
75
+ num_headlines = gr.Slider(visible=False)
76
+ category = gr.Radio(visible=False)
77
+ get = gr.Button(visible=False)
78
+
79
+ headline = gr.Radio(visible=False)
80
+ description = gr.Textbox(visible=False)
81
+ show = gr.Button(visible=False)
82
+
83
+ summarize = gr.Checkbox(visible=False)
84
+ content = gr.Textbox(visible=False)
85
+ ready = gr.Button(visible=False)
86
 
87
+ return num_headlines, category, get, headline, description, show, summarize, content, ready
88
 
89
+ def show_news(): ################################# Show news-related components
90
+ num_headlines = gr.Slider(label="Number of Articles", minimum=1, maximum=10, value=3, step=1, visible=True)
91
+ category = gr.Radio(label="Category (optional)", choices=news.__CATEGORIES__, visible=True)
92
+ get = gr.Button("Get Articles", visible=True)
 
 
 
 
 
 
 
 
 
93
 
94
+ return num_headlines, category, get
95
+
96
+ def show_headline(headlines, descriptions): ###### Show news headlines and descriptions
97
+ headline = gr.Radio(label="News Headlines", choices=headlines, value=headlines[0], interactive=True, visible=True)
98
+ description = gr.Textbox(label="Headline Description", value=descriptions[0], visible=True)
99
+ show = gr.Button("Show Content", visible=True)
100
+
101
+ return headline, description, show
102
+
103
+ def show_content(summary): ####################### Show article content and summary
104
+ summarize = gr.Checkbox(label="Show Summary?", value=True, interactive=True, visible=True)
105
+ content = gr.Textbox(label="Summary", value=summary, visible=True)
106
+ ready = gr.Button("Begin Quiz", visible=True)
107
+
108
+ return summarize, content, ready
109
+
110
+ def format_mcq(mcq): ############################# Format multiple choice question for quiz
111
+ if not mcq or not isinstance(mcq, dict):
112
+ print(f"Multiple choice question object is a {type(mcq)} but should be {type(dict())}.")
113
+ return "Invalid multiple choice question.", None
114
+
115
+ question = mcq.get('question', 'No question provided.')
116
+ answer = mcq.get('correct_answer', 'No correct answer provided.')
117
+ false_answers = mcq.get('false_answers', [])
118
+
119
+ if not isinstance(false_answers, list):
120
+ print(f"False answers is a {type(false_answers)} but should be {type(list())}.")
121
+ return "Invalid false answers format.", None
122
+
123
+ options = random.shuffle([answer] + false_answers)
124
+
125
+ print("Question:", question)
126
+ print(f"Formatted options: {options}")
127
+
128
+ return question, options, answer
129
+
130
+ def hide_quiz(): ################################# Hide quiz-related components
131
+ quiz = [gr.Radio(visible=False) for _ in range(10)]
132
+ submit = gr.Button(visible=False)
133
+
134
+ evaluation = gr.Textbox(visible=False)
135
+ read = gr.Button(visible=False)
136
 
137
+ return read, evaluation, submit, *quiz
138
+
139
+ def show_quiz(mcqs): ############################# Show quiz-related components
140
+ quiz = [(mcq["question"], mcq["false_answers"], mcq["correct_answer"]) for mcq in mcqs]
141
+ quiz = [(question, random.sample(distractors + [answer], 4), answer) for question, distractors, answer in quiz]
142
+ questions, options, answers = zip(*quiz) if quiz else ([], [], [])
143
+
144
+ print("options", len(options))
145
+
146
+ quiz = [gr.Radio(label=f"{i + 1}: {questions[i]}", choices=options[i], visible=True) for i in range(len(mcqs))]\
147
+ + [gr.Radio(visible=False) for _ in range(10 - len(mcqs))]
148
+
149
+ print("quiz", len(quiz))
150
+
151
+ submit = gr.Button("Submit Answers", interactive=bool(answers), visible=True)
152
+
153
+ return submit, list(answers), *quiz
154
+
155
+ def show_eval(eva): ############################## Show evaluation of user's response to the quiz
156
+ evaluation = gr.Textbox(label="Evaluation", value=eva, visible=True)
157
+ read = gr.Button("Read Articles", visible=True)
158
+
159
+ return evaluation, read
160
+
161
+ ######
162
+ ###### Get and Display News Articles
163
+ ######
164
+
165
+ def get_headline(category, num_headlines): ####### Get news headlines based on selected category and number
166
+ articles = news.get_top_headlines(category=category, num_headlines=num_headlines)
167
+ headlines, descriptions = zip(*[(article['title'], article.get('description', 'No description available.')) for article in articles])
168
+ show = show_headline(headlines, descriptions)
169
+ descriptions = {h: d for h, d in zip(headlines, descriptions)}
170
+ return articles, descriptions, *show
171
+
172
+ get.click(get_headline, inputs=[category, num_headlines], outputs=[articles, descriptions, headline, description, show])
173
+
174
+ def get_description(descriptions, headline): ##### Get description for the selected headline
175
+ description = "No description available."
176
+
177
+ if not descriptions: print("Descriptions are empty.")
178
+ elif not headline: print("Headline is empty.")
179
+ elif not isinstance(descriptions, dict): print(f"Descriptions is a {type(descriptions)} but should be {type(dict())}.")
180
+ else: description = descriptions.get(headline, description)
181
+
182
+ return description
183
+
184
+ headline.change(get_description, inputs=[descriptions, headline], outputs=[description])
185
+
186
+ def get_article(articles, headline): ############# Get article for the selected headline
187
+ headlines = [a['title'] for a in articles]
188
+
189
+ if headline not in headlines: return {}
190
+ return articles[headlines.index(headline)]
191
+
192
+ show.click(get_article, inputs=[articles, headline], outputs=[article])
193
+
194
+ def get_content(articles, article): ############## Get content for the selected article
195
+ if "summary" not in article:
196
+ idx = articles.index(article)
197
+ articles[idx]["summary"] = model.get_summary(article, 1)
198
+ article = articles[idx]
199
+
200
+ return articles, article, *show_content(article.get("summary", "No summary available."))
201
+
202
+ article.change(get_content, inputs=[articles, article], outputs=[articles, article, summarize, content, ready])
203
+
204
+ def toggle_summary(article, summarize): ########## Toggle between showing summary and full content
205
+ content = "No article available."
206
+
207
+ if not article: print("Selected article is empty.")
208
+ elif not isinstance(article, dict): print(f"Selected article is a {type(article)} but should be {type(dict())}.")
209
+ elif summarize: content = article.get("summary", "Summary not available.")
210
+ else: content = article.get("content", "Content not available.")
211
+
212
+ return content
213
+
214
+ summarize.change(toggle_summary, inputs=[article, summarize], outputs=[content])
215
+
216
+ ######
217
+ ###### Quiz Generation and Evaluation
218
+ ######
219
+
220
+ def get_quiz(content): ########################### Generate quiz questions from the article content
221
+ multichoicequests = []
222
+
223
+ if not content: mcqs = multichoicequests
224
+ else: mcqs = model.get_questions(content, 3, "Moderate")
225
+
226
+ if not isinstance(mcqs, list): print(f"Multiple choice questions object is a {type(mcqs)} but should be {type(list())}.")
227
+ elif len(mcqs) == 0: print("Content is empty or no multiple choice questions generated.")
228
+
229
+ for mcq in mcqs:
230
+ missing = set()
231
+
232
+ if not isinstance(mcq, dict): print(f"Multiple choice question object is {type(mcq)} but should be {type(dict())}.")
233
+ else: missing = set(['question', 'correct_answer', 'false_answers']) - set(mcq.keys())
234
+
235
+ if missing: print(f"Multiple choice question object is missing keys: {missing}.")
236
+ else: multichoicequests.append(mcq)
237
+
238
+ return gr.Markdown("## News Quiz"), *hide_news(), *show_quiz(multichoicequests)
239
+
240
+ ready.click(get_quiz, inputs=[content], outputs=[
241
+ heading, num_headlines, category, get, headline, description, show, summarize, content, ready, submit, answers, *quiz])
242
+
243
+ def get_evaluation(answers, *quiz): ############## Evaluate the user's responses to the quiz
244
+ results = -1
245
+
246
+ if not answers: print("Answers are empty.")
247
+ elif not quiz: print("Quiz is empty.")
248
+ elif not isinstance(answers, list): print(f"Answers is a {type(answers)} but should be {type(list())}.")
249
+ else:
250
+ results = sum(1 for ans, resp in zip(answers, list(quiz)) if ans == resp) / len(answers)
251
+ results = round(results, 4)
252
+
253
+ if 0.9 <= results <= 1.0: evaluation = f"Excellent! You scored {results * 100}%."
254
+ elif 0.8 <= results < 0.9: evaluation = f"Great job! You scored {results * 100}%."
255
+ elif 0.7 <= results < 0.8: evaluation = f"Good effort! You scored {results * 100}%."
256
+ elif 0.6 <= results < 0.7: evaluation = f"You scored {results * 100}%. Keep practicing!"
257
+ elif 0.5 <= results < 0.6: evaluation = f"You scored {results * 100}%. You can do better!"
258
+ elif results < 0: evaluation = f"Unable to evaluate. Please try again."
259
+ else: evaluation = f"You scored {results * 100}%. Keep trying!"
260
+
261
+ return show_eval(evaluation)
262
+
263
+ submit.click(get_evaluation, inputs=[answers, *quiz], outputs=[evaluation, read])
264
+
265
+ def read_articles(): ############################# Reset the interface to read articles again
266
+ return gr.Markdown("## News Articles"), *show_news(), *hide_quiz()
267
+
268
+ read.click(read_articles, outputs=[heading, num_headlines, category, get, read, evaluation, submit, *quiz])
269
+
270
+ demo.launch()
271
+