Eng-Musa commited on
Commit
2f7e32d
·
2 Parent(s): 3ca8476e0f9b1a

Merge branch 'online-st'

Browse files
__pycache__/main.cpython-312.pyc ADDED
Binary file (3.53 kB). View file
 
__pycache__/optimized_quiz.cpython-312.pyc ADDED
Binary file (3.81 kB). View file
 
clients/__pycache__/groq_client.cpython-312.pyc CHANGED
Binary files a/clients/__pycache__/groq_client.cpython-312.pyc and b/clients/__pycache__/groq_client.cpython-312.pyc differ
 
clients/groq_client.py CHANGED
@@ -176,6 +176,8 @@ class GroqClient:
176
 
177
  # ---------------MAIN-----------------
178
 
 
 
179
  def ask(self, raw_query: str) -> str:
180
  if (
181
  not raw_query
@@ -255,3 +257,91 @@ class GroqClient:
255
 
256
  # If all models fail
257
  return "I'm temporarily experiencing high demand. Please try again in a few minutes or rephrase your question."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  # ---------------MAIN-----------------
178
 
179
+ # Non-streaming ask method for backwards compatibility
180
+ # ============================================
181
  def ask(self, raw_query: str) -> str:
182
  if (
183
  not raw_query
 
257
 
258
  # If all models fail
259
  return "I'm temporarily experiencing high demand. Please try again in a few minutes or rephrase your question."
260
+
261
+ # Streaming ask method (Generator)
262
+ # ============================================
263
+ def ask_stream(self, raw_query: str):
264
+ """Generator function that yields response chunks in real-time"""
265
+
266
+ # Validation
267
+ if not raw_query or raw_query is None or raw_query == "" or len(raw_query) > 1000:
268
+ yield "Please provide a valid query under 1,000 characters."
269
+ return
270
+
271
+ if raw_query.lower() in GREETINGS_TRIGGERS:
272
+ yield random.choice(GREETINGS)
273
+ return
274
+
275
+ # Retrieve documents
276
+ try:
277
+ docs = self.retriever.invoke(raw_query)
278
+ except Exception as e:
279
+ yield f"Error retrieving documents: {e}"
280
+ return
281
+
282
+ if not docs:
283
+ yield self.handle_unknown_query()
284
+ return
285
+
286
+ # Prepare context and prompt
287
+ context = "\n".join([d.page_content for d in docs])
288
+ fallback = self.handle_unknown_query()
289
+ prompt = self.PROMPT_TEMPLATE.format(
290
+ context=context, question=raw_query, fallback_response=fallback
291
+ )
292
+
293
+ messages = [
294
+ {
295
+ "role": "system",
296
+ "content": self.SYSTEM_MESSSAGE,
297
+ },
298
+ {"role": "user", "content": prompt},
299
+ ]
300
+
301
+ # Try multiple models with fallback
302
+ models_to_try = [
303
+ "openai/gpt-oss-120b",
304
+ "openai/gpt-oss-20b",
305
+ "compound-beta-mini",
306
+ "llama-3.1-8b-instant",
307
+ "llama-3.3-70b-versatile",
308
+ ]
309
+
310
+ random.shuffle(models_to_try)
311
+
312
+ for model in models_to_try:
313
+ try:
314
+ completion = self.client.chat.completions.create(
315
+ model=model,
316
+ messages=messages,
317
+ temperature=TEMPERATURE,
318
+ max_completion_tokens=MAX_TOKENS,
319
+ top_p=TOP_P,
320
+ stream=True, # Enable streaming
321
+ )
322
+
323
+ # Stream chunks as they arrive
324
+ has_content = False
325
+ for chunk in completion:
326
+ if chunk.choices[0].delta.content:
327
+ has_content = True
328
+ yield chunk.choices[0].delta.content
329
+
330
+ # Check if stream finished
331
+ if chunk.choices[0].finish_reason:
332
+ break
333
+
334
+ # If we got content, we're done
335
+ if has_content:
336
+ return
337
+
338
+ except Exception as e:
339
+ # Check if it's a rate limit error
340
+ if "rate_limit_exceeded" in str(e) or "429" in str(e):
341
+ continue # Try next model
342
+ else:
343
+ yield f"Error while calling LLM: {e}"
344
+ return
345
+
346
+ # If all models fail
347
+ yield "I'm temporarily experiencing high demand. Please try again in a few minutes or rephrase your question."
main.py CHANGED
@@ -3,6 +3,7 @@ from fastapi.middleware.cors import CORSMiddleware
3
  from pydantic import BaseModel
4
  from functools import lru_cache
5
  from clients.groq_client import GroqClient
 
6
 
7
  app = FastAPI()
8
 
@@ -12,7 +13,7 @@ origins = [
12
  "http://localhost:4200",
13
  "https://mosesmangabo.vercel.app",
14
  "https://chatbot-bridge-service.onrender.com",
15
- "https://eng-musa-chatbotbridge.hf.space"
16
  ]
17
 
18
  app.add_middleware(
@@ -28,11 +29,15 @@ class QueryRequest(BaseModel):
28
  query: str
29
  ip: str
30
 
 
31
  # Lazy singleton for GroqClient
32
  @lru_cache(maxsize=1)
33
  def get_groq_client():
34
  return GroqClient()
35
 
 
 
 
36
  @app.post("/chat")
37
  async def chat_endpoint(request: QueryRequest):
38
  try:
@@ -46,10 +51,49 @@ async def chat_endpoint(request: QueryRequest):
46
  "next_questions": groq_client.get_next_questions(request.ip),
47
  }
48
  except Exception as e:
49
- return {"status_code": 500, "response": f"Internal error: {e}", "next_questions": []}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  @app.get("/")
52
  def healthcheck():
53
  return {"status": "ok"}
54
 
55
- #python -m uvicorn main:app --reload
 
 
3
  from pydantic import BaseModel
4
  from functools import lru_cache
5
  from clients.groq_client import GroqClient
6
+ from fastapi.responses import StreamingResponse
7
 
8
  app = FastAPI()
9
 
 
13
  "http://localhost:4200",
14
  "https://mosesmangabo.vercel.app",
15
  "https://chatbot-bridge-service.onrender.com",
16
+ "https://eng-musa-chatbotbridge.hf.space",
17
  ]
18
 
19
  app.add_middleware(
 
29
  query: str
30
  ip: str
31
 
32
+
33
  # Lazy singleton for GroqClient
34
  @lru_cache(maxsize=1)
35
  def get_groq_client():
36
  return GroqClient()
37
 
38
+
39
+ # Non - Streaming
40
+ # ============================================
41
  @app.post("/chat")
42
  async def chat_endpoint(request: QueryRequest):
43
  try:
 
51
  "next_questions": groq_client.get_next_questions(request.ip),
52
  }
53
  except Exception as e:
54
+ return {
55
+ "status_code": 500,
56
+ "response": f"Internal error: {e}",
57
+ "next_questions": [],
58
+ }
59
+
60
+
61
+ # Streaming
62
+ # ============================================
63
+ @app.post("/chat-stream")
64
+ async def chat_stream_endpoint(request: QueryRequest):
65
+ """Streaming endpoint - returns chunks in real-time"""
66
+ try:
67
+ groq_client = get_groq_client()
68
+
69
+ def generate():
70
+ try:
71
+ for chunk in groq_client.ask_stream(request.query):
72
+ yield f"data: {chunk}\n\n"
73
+
74
+ # Send completion signal
75
+ yield "data: [DONE]\n\n"
76
+
77
+ except Exception as e:
78
+ yield f"data: Error: {str(e)}\n\n"
79
+
80
+ return StreamingResponse(
81
+ generate(),
82
+ media_type="text/event-stream",
83
+ headers={
84
+ "Cache-Control": "no-cache",
85
+ "Connection": "keep-alive",
86
+ "X-Accel-Buffering": "no", # Disable nginx buffering
87
+ },
88
+ )
89
+
90
+ except Exception as e:
91
+ return {"status_code": 500, "response": f"Internal error: {e}"}
92
+
93
 
94
  @app.get("/")
95
  def healthcheck():
96
  return {"status": "ok"}
97
 
98
+
99
+ # python -m uvicorn main:app --reload