DevTail0r commited on
Commit
90b6878
·
verified ·
1 Parent(s): 8ee168f

Update services/quiz_service.py

Browse files
Files changed (1) hide show
  1. services/quiz_service.py +20 -38
services/quiz_service.py CHANGED
@@ -7,16 +7,14 @@ import re
7
  import uuid
8
  from datetime import datetime
9
 
10
- from huggingface_hub import InferenceClient
11
 
12
  from state import Artifact, Notebook
13
 
14
  logger = logging.getLogger(__name__)
15
 
16
- GEN_MODEL = "mistralai/Mistral-7B-Instruct-v0.2"
17
- MAX_NEW_TOKENS = 2048
18
- TEMPERATURE = 0.3
19
- TIMEOUT_SEC = 60
20
 
21
 
22
  def _build_source_text(notebook: Notebook, max_chars: int = 8000) -> str:
@@ -25,7 +23,6 @@ def _build_source_text(notebook: Notebook, max_chars: int = 8000) -> str:
25
  from persistence.vector_store import VectorStore
26
  from ingestion_engine.embedding_generator import generate_query
27
 
28
- # Use a broad query to retrieve a wide sample of content
29
  query_vector = generate_query("key concepts main ideas summary")
30
  matches = VectorStore().query(
31
  query_vector=query_vector,
@@ -34,12 +31,10 @@ def _build_source_text(notebook: Notebook, max_chars: int = 8000) -> str:
34
  )
35
  chunks = [m.get("text", "") for m in matches if m.get("text")]
36
  if chunks:
37
- combined = "\n\n".join(chunks)
38
- return combined[:max_chars]
39
  except Exception as e:
40
  logger.warning("Could not retrieve chunks from vector store: %s", e)
41
 
42
- # Fallback: just list source filenames
43
  parts = [src.filename for src in notebook.sources if src.status == "ready"]
44
  return "Sources: " + ", ".join(parts) if parts else "No sources available."
45
 
@@ -75,7 +70,6 @@ Now generate {num_questions} questions. Return ONLY the JSON array:"""
75
 
76
 
77
  def _parse_quiz_json(raw: str) -> list[dict]:
78
- """Robustly extract and parse JSON array from LLM output."""
79
  cleaned = re.sub(r"```(?:json)?", "", raw).strip()
80
  start = cleaned.find("[")
81
  end = cleaned.rfind("]")
@@ -187,40 +181,30 @@ def _render_quiz_html(questions: list[dict], title: str) -> str:
187
 
188
 
189
  def generate_quiz(notebook: Notebook, num_questions: int) -> Artifact:
190
- """Generate a quiz artifact from notebook sources using HF InferenceClient."""
191
- token = os.environ.get("HF_TOKEN")
192
- client = InferenceClient(token=token, timeout=TIMEOUT_SEC)
193
 
194
  source_text = _build_source_text(notebook)
195
  logger.info("Quiz source text length: %d chars", len(source_text))
196
 
197
- prompt = _build_quiz_prompt(source_text, num_questions)
198
-
199
  try:
200
- response = client.chat_completion(
201
- model=GEN_MODEL,
202
- messages=[
203
- {
204
- "role": "system",
205
- "content": (
206
- "You are an expert quiz generator. Always respond with ONLY valid JSON arrays. "
207
- "Never include markdown formatting, code fences, or any text outside the JSON array."
208
- ),
209
- },
210
- {"role": "user", "content": prompt},
211
- ],
212
- max_tokens=MAX_NEW_TOKENS,
213
- temperature=TEMPERATURE,
214
  )
215
- raw = response.choices[0].message.content or ""
216
- logger.info("Raw LLM response length: %d chars, preview: %s", len(raw), raw[:200])
217
 
218
  questions = _parse_quiz_json(raw)
219
  questions = _validate_questions(questions)
220
 
221
  if not questions:
222
- raise ValueError(f"No valid questions parsed. Raw response: {raw[:300]}")
223
-
224
  if len(questions) > num_questions:
225
  questions = questions[:num_questions]
226
 
@@ -228,21 +212,19 @@ def generate_quiz(notebook: Notebook, num_questions: int) -> Artifact:
228
  logger.error("Quiz generation failed: %s", e)
229
  questions = [
230
  {
231
- "question": f"Quiz generation encountered an error. Please try again.",
232
- "options": ["A) Try again", "B) Check logs", "C) Verify HF_TOKEN is set", "D) Check model availability"],
233
  "answer": "A",
234
  "explanation": f"Error: {str(e)[:200]}",
235
  }
236
  ]
237
 
238
  title = f"Practice Quiz ({len(questions)} Questions)"
239
- html_content = _render_quiz_html(questions, title)
240
-
241
  return Artifact(
242
  id=str(uuid.uuid4()),
243
  type="quiz",
244
  title=title,
245
- content=html_content,
246
  audio_path=None,
247
  created_at=datetime.now().isoformat(),
248
  )
 
7
  import uuid
8
  from datetime import datetime
9
 
10
+ import anthropic
11
 
12
  from state import Artifact, Notebook
13
 
14
  logger = logging.getLogger(__name__)
15
 
16
+ MODEL = "claude-haiku-4-5-20251001"
17
+ MAX_TOKENS = 2048
 
 
18
 
19
 
20
  def _build_source_text(notebook: Notebook, max_chars: int = 8000) -> str:
 
23
  from persistence.vector_store import VectorStore
24
  from ingestion_engine.embedding_generator import generate_query
25
 
 
26
  query_vector = generate_query("key concepts main ideas summary")
27
  matches = VectorStore().query(
28
  query_vector=query_vector,
 
31
  )
32
  chunks = [m.get("text", "") for m in matches if m.get("text")]
33
  if chunks:
34
+ return "\n\n".join(chunks)[:max_chars]
 
35
  except Exception as e:
36
  logger.warning("Could not retrieve chunks from vector store: %s", e)
37
 
 
38
  parts = [src.filename for src in notebook.sources if src.status == "ready"]
39
  return "Sources: " + ", ".join(parts) if parts else "No sources available."
40
 
 
70
 
71
 
72
  def _parse_quiz_json(raw: str) -> list[dict]:
 
73
  cleaned = re.sub(r"```(?:json)?", "", raw).strip()
74
  start = cleaned.find("[")
75
  end = cleaned.rfind("]")
 
181
 
182
 
183
  def generate_quiz(notebook: Notebook, num_questions: int) -> Artifact:
184
+ """Generate a quiz artifact from notebook sources using Anthropic Claude."""
185
+ client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
 
186
 
187
  source_text = _build_source_text(notebook)
188
  logger.info("Quiz source text length: %d chars", len(source_text))
189
 
 
 
190
  try:
191
+ response = client.messages.create(
192
+ model=MODEL,
193
+ max_tokens=MAX_TOKENS,
194
+ system=(
195
+ "You are an expert quiz generator. Always respond with ONLY valid JSON arrays. "
196
+ "Never include markdown formatting, code fences, or any text outside the JSON array."
197
+ ),
198
+ messages=[{"role": "user", "content": _build_quiz_prompt(source_text, num_questions)}],
 
 
 
 
 
 
199
  )
200
+ raw = response.content[0].text
201
+ logger.info("Raw response preview: %s", raw[:200])
202
 
203
  questions = _parse_quiz_json(raw)
204
  questions = _validate_questions(questions)
205
 
206
  if not questions:
207
+ raise ValueError(f"No valid questions parsed. Raw: {raw[:300]}")
 
208
  if len(questions) > num_questions:
209
  questions = questions[:num_questions]
210
 
 
212
  logger.error("Quiz generation failed: %s", e)
213
  questions = [
214
  {
215
+ "question": "Quiz generation encountered an error. Please try again.",
216
+ "options": ["A) Try again", "B) Check logs", "C) Verify ANTHROPIC_API_KEY", "D) Check model"],
217
  "answer": "A",
218
  "explanation": f"Error: {str(e)[:200]}",
219
  }
220
  ]
221
 
222
  title = f"Practice Quiz ({len(questions)} Questions)"
 
 
223
  return Artifact(
224
  id=str(uuid.uuid4()),
225
  type="quiz",
226
  title=title,
227
+ content=_render_quiz_html(questions, title),
228
  audio_path=None,
229
  created_at=datetime.now().isoformat(),
230
  )