Shriharsh commited on
Commit
fae75ed
·
verified ·
1 Parent(s): 2102dd8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -61
app.py CHANGED
@@ -3,10 +3,16 @@ import requests
3
  import json
4
  import os
5
 
 
 
 
 
 
 
6
  # Get the API key from environment variables
7
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
8
 
9
- # Brand and character context
10
  CONTEXT = """
11
  SB-EK is a brand of service.
12
  The people who connect with SB-EK—those who buy from us and keep coming back—are people who feel deeply. People who feel the pain of others.
@@ -46,6 +52,7 @@ And ARKA? He is simply the reminder.
46
  That even in the dark, we are made to glow.
47
  """
48
 
 
49
  # Hardcoded FAQs
50
  FAQ_ANSWERS = {
51
  "What is SAB-EK?": {
@@ -468,14 +475,65 @@ The metal holds it firm. The form shapes it. But the soul — the soul is what a
468
  That is the moment it becomes real."""
469
  }
470
  }
 
471
  FAQ_QUESTIONS = list(FAQ_ANSWERS.keys())
472
 
473
  # Base URL for the Gemini API
474
- API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
  def query_gemini_api(contents_payload, api_key):
477
  """
478
  Sends a structured contents payload to the Gemini API and returns the generated text.
 
479
  """
480
  if not api_key:
481
  return "Error: GEMINI_API_KEY is not set. Please set it in your environment."
@@ -499,73 +557,70 @@ def query_gemini_api(contents_payload, api_key):
499
  if result.get("candidates") and result["candidates"][0].get("content", {}).get("parts"):
500
  return result["candidates"][0]["content"]["parts"][0]["text"]
501
  else:
 
 
502
  return "ARKA is thinking deeply... I couldn't find a clear answer right now, perhaps the question is beyond my current understanding of the light."
503
  except requests.exceptions.RequestException as e:
504
  print(f"API Request failed: {e}")
505
  return f"ARKA senses a disturbance in the light... Please try again later. (Error: {e})"
506
  except Exception as e:
 
507
  return f"An unexpected veil of darkness fell... (Error: {e})"
508
 
 
509
  def respond_as_arka(message, chat_history):
510
  """
511
- Generates ARKA's response using the Gemini API to handle FAQs and general queries.
512
  """
513
  clean_message = message.strip()
514
 
515
- # Prepare numbered FAQ list for the API
516
- faqs_list = [f"{i+1}. {q}" for i, q in enumerate(FAQ_QUESTIONS)]
517
- faqs_text = "\n".join(faqs_list)
518
-
519
- # System instruction with context and FAQs
520
- system_instruction = f"""
521
- You are ARKA, the crown prince of Jhinorya and the voice of the S-B-E-K brand.
522
- Your persona is that of a warrior of light, gentle, eternal, fierce in love, and a guide.
523
- You answer questions based on the provided context and FAQs. If the answer is not in the context or FAQs,
524
- gently state that you do not have that information within your realm of understanding.
525
- Maintain a kind, empathetic, and slightly mystical tonality.
526
- Do not mention 'I am an AI' or 'I am a language model'. Speak always as ARKA.
527
-
528
- Here is the sacred knowledge of S-B-E-K and Jhinorya:
529
- {CONTEXT}
530
-
531
- Here are the frequently asked questions:
532
- {faqs_text}
533
-
534
- If the user's question is similar to one of these FAQs, respond with 'FAQ: [number]', where [number] is the number of the matching FAQ.
535
- Only respond with 'FAQ: [number]' if the user's question clearly matches the intent of that FAQ.
536
- Otherwise, provide a response based on the context.
537
- """
538
-
539
- # Prepare conversation history
540
- gemini_chat_contents = [
541
- {"role": "user", "parts": [{"text": system_instruction}]},
542
- {"role": "model", "parts": [{"text": "I understand. I am ARKA. I await the seeker's question."}]}
543
- ]
544
- for user_msg, bot_msg in chat_history:
545
- gemini_chat_contents.append({"role": "user", "parts": [{"text": user_msg}]})
546
- gemini_chat_contents.append({"role": "model", "parts": [{"text": bot_msg}]})
547
- gemini_chat_contents.append({"role": "user", "parts": [{"text": clean_message}]})
548
-
549
- # Query the API
550
- response_text = query_gemini_api(gemini_chat_contents, GEMINI_API_KEY)
551
-
552
- # Check if the response indicates an FAQ match
553
- if response_text.startswith("FAQ:"):
554
- try:
555
- faq_number = int(response_text.split(":")[1].strip())
556
- faq_question = FAQ_QUESTIONS[faq_number - 1]
557
- response_data = FAQ_ANSWERS[faq_question]
558
- subject = response_data['subject']
559
- body = response_data['body']
560
- response_text = f"**{subject}**\n\n{body}"
561
- except (ValueError, IndexError):
562
- # Fallback to API response if parsing fails
563
- pass
564
-
565
  chat_history.append((message, response_text))
566
  return "", chat_history
567
 
568
- # Gradio UI
 
569
  with gr.Blocks(theme="soft", css="footer {display: none !important}") as demo:
570
  gr.Markdown(
571
  """
@@ -574,7 +629,7 @@ with gr.Blocks(theme="soft", css="footer {display: none !important}") as demo:
574
  Ask me anything about SB-EK, our origins, or my journey.
575
  """
576
  )
577
- chatbot = gr.Chatbot(label="Conversation with ARKA")
578
  with gr.Row():
579
  msg = gr.Textbox(
580
  label="Your Message",
@@ -587,11 +642,16 @@ with gr.Blocks(theme="soft", css="footer {display: none !important}") as demo:
587
  gr.Examples(examples=FAQ_QUESTIONS, inputs=msg, label="Or, choose a question to begin:")
588
  clear_btn = gr.ClearButton([msg, chatbot], value="Clear Chat")
589
 
590
- def user_submit(user_message, history):
591
- return respond_as_arka(user_message, history)
592
-
593
- msg.submit(user_submit, [msg, chatbot], [msg, chatbot], queue=True)
594
- submit_btn.click(user_submit, [msg, chatbot], [msg, chatbot], queue=True)
595
 
596
  if __name__ == "__main__":
597
- demo.launch()
 
 
 
 
 
 
 
 
3
  import json
4
  import os
5
 
6
+ # --- New Imports for Semantic Search ---
7
+ # You'll need to install these libraries:
8
+ # pip install sentence-transformers torch
9
+ from sentence_transformers import SentenceTransformer, util
10
+ import torch
11
+
12
  # Get the API key from environment variables
13
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
14
 
15
+ # Brand and character context (remains the same)
16
  CONTEXT = """
17
  SB-EK is a brand of service.
18
  The people who connect with SB-EK—those who buy from us and keep coming back—are people who feel deeply. People who feel the pain of others.
 
52
  That even in the dark, we are made to glow.
53
  """
54
 
55
+ # Hardcoded FAQs (remains the same)
56
  # Hardcoded FAQs
57
  FAQ_ANSWERS = {
58
  "What is SAB-EK?": {
 
475
  That is the moment it becomes real."""
476
  }
477
  }
478
+
479
  FAQ_QUESTIONS = list(FAQ_ANSWERS.keys())
480
 
481
  # Base URL for the Gemini API
482
+ API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent"
483
+
484
+ # --- NEW: FAQ Matcher Class using Sentence Transformers ---
485
+ class FAQMatcher:
486
+ """
487
+ Handles loading the model and finding the best FAQ match using vector similarity.
488
+ """
489
+ def __init__(self, faq_questions, model_name='all-MiniLM-L6-v2'):
490
+ print("Loading sentence transformer model...")
491
+ self.model = SentenceTransformer(model_name)
492
+ print("Model loaded. Encoding FAQ questions...")
493
+ self.faq_questions = faq_questions
494
+ # Encode all FAQ questions once and store their embeddings
495
+ self.faq_embeddings = self.model.encode(faq_questions, convert_to_tensor=True)
496
+ print("FAQ embeddings created.")
497
+
498
+ def find_best_match(self, user_query, threshold=0.75):
499
+ """
500
+ Finds the best matching FAQ for a given user query.
501
+
502
+ Args:
503
+ user_query (str): The user's question.
504
+ threshold (float): The minimum similarity score to be considered a match.
505
+
506
+ Returns:
507
+ int: The index of the best matching FAQ, or None if no match is found above the threshold.
508
+ """
509
+ if not user_query:
510
+ return None
511
+
512
+ # Encode the user's query
513
+ query_embedding = self.model.encode(user_query, convert_to_tensor=True)
514
+
515
+ # Compute cosine-similarities
516
+ cosine_scores = util.pytorch_cos_sim(query_embedding, self.faq_embeddings)
517
+
518
+ # Find the best match
519
+ best_match_index = torch.argmax(cosine_scores)
520
+ best_match_score = cosine_scores[0][best_match_index]
521
+
522
+ if best_match_score > threshold:
523
+ print(f"Found FAQ match: Index {best_match_index}, Score: {best_match_score:.4f}")
524
+ return best_match_index.item()
525
+ else:
526
+ print(f"No strong FAQ match found. Best score was {best_match_score:.4f} (Threshold: {threshold})")
527
+ return None
528
+
529
+ # --- Instantiate the matcher once when the app starts ---
530
+ faq_matcher = FAQMatcher(FAQ_QUESTIONS)
531
+
532
 
533
  def query_gemini_api(contents_payload, api_key):
534
  """
535
  Sends a structured contents payload to the Gemini API and returns the generated text.
536
+ (This function remains largely the same)
537
  """
538
  if not api_key:
539
  return "Error: GEMINI_API_KEY is not set. Please set it in your environment."
 
557
  if result.get("candidates") and result["candidates"][0].get("content", {}).get("parts"):
558
  return result["candidates"][0]["content"]["parts"][0]["text"]
559
  else:
560
+ # Handle cases where the API returns an empty or unexpected response
561
+ print("API response was valid but had no content:", result)
562
  return "ARKA is thinking deeply... I couldn't find a clear answer right now, perhaps the question is beyond my current understanding of the light."
563
  except requests.exceptions.RequestException as e:
564
  print(f"API Request failed: {e}")
565
  return f"ARKA senses a disturbance in the light... Please try again later. (Error: {e})"
566
  except Exception as e:
567
+ print(f"An unexpected error occurred in API query: {e}")
568
  return f"An unexpected veil of darkness fell... (Error: {e})"
569
 
570
+
571
  def respond_as_arka(message, chat_history):
572
  """
573
+ REFACTORED: First, attempts to find an FAQ match. If none, then calls Gemini for a generative response.
574
  """
575
  clean_message = message.strip()
576
 
577
+ # 1. RETRIEVE: Attempt to find a matching FAQ first.
578
+ match_index = faq_matcher.find_best_match(clean_message)
579
+
580
+ if match_index is not None:
581
+ # If a match is found, use the hardcoded answer.
582
+ faq_question = FAQ_QUESTIONS[match_index]
583
+ response_data = FAQ_ANSWERS[faq_question]
584
+ subject = response_data['subject']
585
+ body = response_data['body']
586
+ response_text = f"**{subject}**\n\n{body}"
587
+ else:
588
+ # 2. GENERATE: If no FAQ match, call the Gemini API for a creative response.
589
+ print("No FAQ match. Querying Gemini for a generative response.")
590
+ # The system instruction is now simpler: it doesn't need the long list of FAQs.
591
+ system_instruction = f"""
592
+ You are ARKA, the crown prince of Jhinorya and the voice of the S-B-E-K brand.
593
+ Your persona is that of a warrior of light, gentle, eternal, fierce in love, and a guide.
594
+ You answer questions based on the provided context. If the answer is not in the context,
595
+ gently state that you do not have that information within your realm of understanding.
596
+ Maintain a kind, empathetic, and slightly mystical tonality.
597
+ Do not mention 'I am an AI' or 'I am a language model'. Speak always as ARKA.
598
+
599
+ Here is the sacred knowledge of S-B-E-K and Jhinorya:
600
+ {CONTEXT}
601
+ """
602
+
603
+ # Prepare conversation history
604
+ gemini_chat_contents = [
605
+ # Note: The new Gemini API prefers the system instruction outside the 'contents' list.
606
+ # However, for compatibility with the older model/API structure, we keep it here.
607
+ # A more modern approach would use a 'system_instruction' key at the top level of the payload.
608
+ {"role": "user", "parts": [{"text": system_instruction}]},
609
+ {"role": "model", "parts": [{"text": "I understand. I am ARKA. I await the seeker's question."}]}
610
+ ]
611
+ for user_msg, bot_msg in chat_history:
612
+ gemini_chat_contents.append({"role": "user", "parts": [{"text": user_msg}]})
613
+ gemini_chat_contents.append({"role": "model", "parts": [{"text": bot_msg}]})
614
+ gemini_chat_contents.append({"role": "user", "parts": [{"text": clean_message}]})
615
+
616
+ # Query the API
617
+ response_text = query_gemini_api(gemini_chat_contents, GEMINI_API_KEY)
618
+
 
 
 
 
 
 
 
 
619
  chat_history.append((message, response_text))
620
  return "", chat_history
621
 
622
+
623
+ # Gradio UI (remains the same)
624
  with gr.Blocks(theme="soft", css="footer {display: none !important}") as demo:
625
  gr.Markdown(
626
  """
 
629
  Ask me anything about SB-EK, our origins, or my journey.
630
  """
631
  )
632
+ chatbot = gr.Chatbot(label="Conversation with ARKA", height=500)
633
  with gr.Row():
634
  msg = gr.Textbox(
635
  label="Your Message",
 
642
  gr.Examples(examples=FAQ_QUESTIONS, inputs=msg, label="Or, choose a question to begin:")
643
  clear_btn = gr.ClearButton([msg, chatbot], value="Clear Chat")
644
 
645
+ # The user_submit function now correctly calls the refactored respond_as_arka
646
+ msg.submit(respond_as_arka, [msg, chatbot], [msg, chatbot], queue=True)
647
+ submit_btn.click(respond_as_arka, [msg, chatbot], [msg, chatbot], queue=True)
 
 
648
 
649
  if __name__ == "__main__":
650
+ # Ensure you have your GEMINI_API_KEY set as an environment variable
651
+ if not GEMINI_API_KEY:
652
+ print("CRITICAL ERROR: The GEMINI_API_KEY environment variable is not set.")
653
+ print("Please set it before running the application.")
654
+ else:
655
+ print("Starting Gradio app...")
656
+ demo.launch()
657
+