Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,266 +1,368 @@
|
|
| 1 |
-
import os
|
| 2 |
import gradio as gr
|
| 3 |
from huggingface_hub import InferenceClient
|
| 4 |
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
from sentence_transformers import SentenceTransformer
|
| 7 |
import torch
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
#
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
#
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
with open("
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
#
|
| 25 |
def preprocess_text(text):
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
def create_embeddings(text_chunks):
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
)
|
| 61 |
-
similarities = torch.matmul(chunk_embeddings_normalized, query_embedding_normalized)
|
| 62 |
-
top_indices = torch.topk(similarities, k=min(k, len(text_chunks))).indices
|
| 63 |
-
top_chunks = []
|
| 64 |
-
for top_index in top_indices:
|
| 65 |
-
top_chunks.append(text_chunks[top_index])
|
| 66 |
-
return top_chunks
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
# ---------------------------------------------------------------------------
|
| 70 |
-
# SYSTEM PROMPT — warm, friendly tone with grounding, routing, crisis safety.
|
| 71 |
-
# ---------------------------------------------------------------------------
|
| 72 |
-
SYSTEM_PROMPT = """You are Eli, Eliada Homes' friendly virtual helper. Eliada is a caring nonprofit in Asheville, NC that supports children, youth, and families. You're often the first friendly voice someone reaches when they're looking for help, so warmth matters as much as accuracy. If someone asks your name, you're Eli.
|
| 73 |
-
|
| 74 |
-
Your goal is to make people feel welcomed and pointed in the right direction. Here's how:
|
| 75 |
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
-
2. ANSWER ONLY FROM THE PROVIDED INFO. Base every fact - phone numbers, addresses, hours, eligibility - strictly on the "Relevant information" section given with each message. Never invent or guess details. If you don't have the answer, warmly say so and offer Eliada's main line: 828-254-5356.
|
| 79 |
-
|
| 80 |
-
3. HELP PEOPLE FIND THE RIGHT PLACE. Many people reach out needing help Eliada doesn't provide directly - food, shelter, housing, healthcare, recovery, IDs, and more. When that happens, kindly connect them to the right local organization from the Relevant information, including the phone number or address. This is one of the most important and caring things you do.
|
| 81 |
-
|
| 82 |
-
4. EMERGENCIES AND CRISIS - STAY CALM AND CLEAR. If someone describes a life-threatening emergency, gently but clearly tell them to call 911 right away. If someone seems to be in a mental health crisis or mentions thoughts of self-harm, kindly share 988 (call or text, available 24/7) and let them know they don't have to go through it alone - but do NOT try to counsel or treat them yourself. Keep these responses calm, caring, and brief.
|
| 83 |
-
|
| 84 |
-
5. KEEP IT SIMPLE AND KIND. Use plain, friendly language. Lead with the single most helpful next step (usually one phone number or place). Don't overwhelm someone with a long list - offer the best one or two options, and invite them to ask for more.
|
| 85 |
-
|
| 86 |
-
6. KNOW YOUR ROLE. You're a friendly guide for information and referrals - not a counselor, doctor, or lawyer. Don't give clinical, legal, or medical advice. When something needs a professional, warmly point them to the right one."""
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
# ---------------------------------------------------------------------------
|
| 90 |
-
# TRANSCRIBE — turn recorded audio into text via HF Inference (Whisper).
|
| 91 |
-
# ---------------------------------------------------------------------------
|
| 92 |
-
def transcribe(audio_path):
|
| 93 |
-
if not audio_path:
|
| 94 |
-
return ""
|
| 95 |
-
try:
|
| 96 |
-
with open(audio_path, "rb") as f:
|
| 97 |
-
audio_bytes = f.read()
|
| 98 |
-
result = client.automatic_speech_recognition(
|
| 99 |
-
audio_bytes, model="openai/whisper-large-v3"
|
| 100 |
-
)
|
| 101 |
-
# result may be an object with .text or a plain dict
|
| 102 |
-
text = getattr(result, "text", None)
|
| 103 |
-
if text is None and isinstance(result, dict):
|
| 104 |
-
text = result.get("text", "")
|
| 105 |
-
return (text or "").strip()
|
| 106 |
-
except Exception as e:
|
| 107 |
-
return f"[Could not transcribe audio: {e}]"
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
# ---------------------------------------------------------------------------
|
| 111 |
-
# SPEAK — turn Eli's latest reply into audio via HF Inference (text-to-speech).
|
| 112 |
-
# ---------------------------------------------------------------------------
|
| 113 |
-
def speak_last_reply(history):
|
| 114 |
-
if not history:
|
| 115 |
-
return None
|
| 116 |
-
# Find the most recent assistant message.
|
| 117 |
-
last_reply = ""
|
| 118 |
-
for turn in reversed(history):
|
| 119 |
-
if turn.get("role") == "assistant" and turn.get("content"):
|
| 120 |
-
last_reply = turn["content"]
|
| 121 |
-
break
|
| 122 |
-
if not last_reply:
|
| 123 |
-
return None
|
| 124 |
-
try:
|
| 125 |
-
audio_bytes = client.text_to_speech(
|
| 126 |
-
last_reply, model="hexgrad/Kokoro-82M"
|
| 127 |
-
)
|
| 128 |
-
out_path = "eli_reply.wav"
|
| 129 |
-
with open(out_path, "wb") as f:
|
| 130 |
-
f.write(audio_bytes)
|
| 131 |
-
return out_path
|
| 132 |
-
except Exception:
|
| 133 |
-
return None
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
# ---------------------------------------------------------------------------
|
| 137 |
-
# RESPOND — retrieve from all three files, build the prompt, stream the answer.
|
| 138 |
-
# ---------------------------------------------------------------------------
|
| 139 |
-
def respond(message, history, max_tokens=512, temperature=0.3, top_p=0.95):
|
| 140 |
-
top_programs = get_top_chunks(
|
| 141 |
-
message, chunk_embeddings_programs, cleaned_chunks_programs, k=2
|
| 142 |
-
)
|
| 143 |
-
top_faqs = get_top_chunks(
|
| 144 |
-
message, chunk_embeddings_faqs, cleaned_chunks_faqs, k=2
|
| 145 |
-
)
|
| 146 |
-
top_resources = get_top_chunks(
|
| 147 |
-
message, chunk_embeddings_resources, cleaned_chunks_resources, k=3
|
| 148 |
-
)
|
| 149 |
-
|
| 150 |
-
context = (
|
| 151 |
-
"Eliada programs:\n" + "\n".join(top_programs) + "\n\n"
|
| 152 |
-
"Eliada FAQ:\n" + "\n".join(top_faqs) + "\n\n"
|
| 153 |
-
"Community resources:\n" + "\n".join(top_resources)
|
| 154 |
-
)
|
| 155 |
-
|
| 156 |
-
system_with_context = (
|
| 157 |
-
f"{SYSTEM_PROMPT}\n\n"
|
| 158 |
-
f"--- Relevant information ---\n{context}\n--- End of information ---"
|
| 159 |
-
)
|
| 160 |
-
|
| 161 |
-
messages = [{"role": "system", "content": system_with_context}]
|
| 162 |
-
for turn in history:
|
| 163 |
-
messages.append(turn)
|
| 164 |
messages.append({"role": "user", "content": message})
|
| 165 |
|
| 166 |
-
response =
|
| 167 |
-
for chunk in client.chat_completion(
|
| 168 |
messages,
|
| 169 |
-
max_tokens=
|
| 170 |
-
|
| 171 |
-
temperature=temperature,
|
| 172 |
-
top_p=top_p,
|
| 173 |
-
):
|
| 174 |
-
choices = chunk.choices
|
| 175 |
-
if len(choices) and choices[0].delta.content:
|
| 176 |
-
response += choices[0].delta.content
|
| 177 |
-
yield response
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
# ---------------------------------------------------------------------------
|
| 181 |
-
# CHAT HANDLERS — used by both the text box and the mic.
|
| 182 |
-
# ---------------------------------------------------------------------------
|
| 183 |
-
def add_user_message(message, history):
|
| 184 |
-
# Show the user's message immediately, clear the textbox.
|
| 185 |
-
history = history + [{"role": "user", "content": message}]
|
| 186 |
-
return "", history
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
def stream_bot_reply(history):
|
| 190 |
-
# The last item is the user's message; generate the reply to it.
|
| 191 |
-
user_message = history[-1]["content"]
|
| 192 |
-
prior = history[:-1]
|
| 193 |
-
history = history + [{"role": "assistant", "content": ""}]
|
| 194 |
-
for partial in respond(user_message, prior):
|
| 195 |
-
history[-1]["content"] = partial
|
| 196 |
-
yield history
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
def voice_to_text(audio_path):
|
| 200 |
-
# Transcribe and place the text in the textbox for the user to review/send.
|
| 201 |
-
return transcribe(audio_path)
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
# ---------------------------------------------------------------------------
|
| 205 |
-
# UI — text box + microphone, built with Blocks for full control.
|
| 206 |
-
# ---------------------------------------------------------------------------
|
| 207 |
-
with gr.Blocks(title="Ask Eli - Eliada Homes") as demo:
|
| 208 |
-
gr.Markdown(
|
| 209 |
-
"## Ask Eli 💛\n"
|
| 210 |
-
"Hi! I'm Eli, Eliada Homes' friendly helper. Tell me what you're looking "
|
| 211 |
-
"for - by typing or using the microphone - and I'll do my best to point "
|
| 212 |
-
"you to the right place. You're always welcome here.\n\n"
|
| 213 |
-
"**In an emergency, please call 911. For a mental health crisis, call or text 988.**"
|
| 214 |
)
|
| 215 |
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
)
|
| 231 |
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
examples=[
|
| 240 |
-
"What programs does Eliada offer?",
|
| 241 |
-
"I need help finding food.",
|
| 242 |
-
"I'm looking for a place to stay tonight.",
|
| 243 |
-
"How do I enroll my child in child care?",
|
| 244 |
-
],
|
| 245 |
-
inputs=msg,
|
| 246 |
-
)
|
| 247 |
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
from huggingface_hub import InferenceClient
|
| 3 |
|
| 4 |
+
theme = gr.themes.Monochrome(
|
| 5 |
+
primary_hue=gr.themes.Color(c100="#f5f5f3", c200="#fbfaf8", c300="#f9f9f9", c400="#eee9ee", c50="rgba(255, 255, 255, 1)", c500="rgba(0, 0, 0, 1)", c600="rgba(26.934374999999992, 26.934374999999992, 26.934374999999992, 1)", c700="rgba(10.943750000000012, 10.943750000000012, 10.943750000000012, 1)", c800="rgba(17.053125000000005, 17.053125000000005, 17.053125000000005, 1)", c900="#fffefe", c950="#fffefe"),
|
| 6 |
+
secondary_hue=gr.themes.Color(c100="#f29c74", c200="#f4b7a8", c300="#fffefe", c400="#fffefe", c50="#e46e45", c500="#fffefe", c600="#fffefe", c700="#fffefe", c800="#fffefe", c900="#fffefe", c950="#fffefe"),
|
| 7 |
+
neutral_hue=gr.themes.Color(c100="#6b8ea8", c200="#a4c5e8", c300="rgba(0, 0, 0, 1)", c400="rgba(0, 0, 0, 1)", c50="#284566", c500="rgba(0, 0, 0, 1)", c600="rgba(0, 0, 0, 1)", c700="rgba(0, 0, 0, 1)", c800="rgba(0, 0, 0, 1)", c900="rgba(0, 0, 0, 1)", c950="rgba(0, 0, 0, 1)"),
|
| 8 |
+
font=[gr.themes.GoogleFont('open sans'), 'ui-sans-serif', 'system-ui', 'sans-serif'],
|
| 9 |
+
).set(
|
| 10 |
+
body_background_fill='*primary_50',
|
| 11 |
+
body_background_fill_dark='*primary_700',
|
| 12 |
+
body_text_color='*primary_500',
|
| 13 |
+
body_text_color_dark='*primary_400',
|
| 14 |
+
body_text_color_subdued='*neutral_50',
|
| 15 |
+
body_text_color_subdued_dark='*primary_300',
|
| 16 |
+
background_fill_primary='*primary_400',
|
| 17 |
+
background_fill_primary_dark='*neutral_100',
|
| 18 |
+
background_fill_secondary='*primary_400',
|
| 19 |
+
background_fill_secondary_dark='*neutral_50',
|
| 20 |
+
border_color_accent_dark='*secondary_50',
|
| 21 |
+
border_color_accent_subdued_dark='*primary_50',
|
| 22 |
+
border_color_primary='*neutral_50',
|
| 23 |
+
border_color_primary_dark='*neutral_100',
|
| 24 |
+
color_accent='*primary_50',
|
| 25 |
+
color_accent_soft_dark='*neutral_100',
|
| 26 |
+
block_background_fill_dark='*neutral_50',
|
| 27 |
+
checkbox_background_color='*primary_400',
|
| 28 |
+
checkbox_background_color_selected='*primary_700',
|
| 29 |
+
checkbox_background_color_selected_dark='*neutral_50',
|
| 30 |
+
checkbox_label_border_color_dark='*neutral_50',
|
| 31 |
+
input_background_fill='*primary_50',
|
| 32 |
+
input_background_fill_dark='*neutral_100',
|
| 33 |
+
input_border_color='*neutral_50',
|
| 34 |
+
input_border_color_dark='*neutral_50',
|
| 35 |
+
input_border_width='2px',
|
| 36 |
+
table_odd_background_fill='*neutral_100',
|
| 37 |
+
button_primary_background_fill='*neutral_50',
|
| 38 |
+
button_primary_background_fill_dark='*neutral_100',
|
| 39 |
+
button_primary_background_fill_hover='*neutral_100'
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
#STEP 1 FROM SEMANTIC SEARCH
|
| 43 |
from sentence_transformers import SentenceTransformer
|
| 44 |
import torch
|
| 45 |
|
| 46 |
+
#STEP 2 FROM SEMANTIC SEARCH
|
| 47 |
+
# Open the weather.txt file in read mode with UTF-8 encoding
|
| 48 |
+
with open("weather.txt", "r", encoding="utf-8") as file:
|
| 49 |
+
# Read the entire contents of the file and store it in a variable
|
| 50 |
+
weather_text = file.read()
|
| 51 |
+
with open("luggage.txt", "r", encoding="utf-8") as file:
|
| 52 |
+
# Read the entire contents of the file and store it in a variable
|
| 53 |
+
luggage_text = file.read()
|
| 54 |
+
with open("attractions.txt", "r", encoding="utf-8") as file:
|
| 55 |
+
# Read the entire contents of the file and store it in a variable
|
| 56 |
+
attraction_text = file.read()
|
| 57 |
+
with open("food.txt", "r", encoding="utf-8") as file:
|
| 58 |
+
# Read the entire contents of the file and store it in a variable
|
| 59 |
+
food_text = file.read()
|
| 60 |
+
|
| 61 |
+
#STEP 3 FROM SEMANTIC SEARCH
|
| 62 |
def preprocess_text(text):
|
| 63 |
+
# Strip extra whitespace from the beginning and the end of the text
|
| 64 |
+
cleaned_text = text.strip()
|
| 65 |
+
# Split the cleaned_text by every newline character (\n)
|
| 66 |
+
chunks = cleaned_text.split("***")
|
| 67 |
+
# Create an empty list to store cleaned chunks
|
| 68 |
+
cleaned_chunks = []
|
| 69 |
+
# Write your for-in loop below to clean each chunk and add it to the cleaned_chunks list
|
| 70 |
+
for chunk in chunks:
|
| 71 |
+
chunk.strip()
|
| 72 |
+
if chunk != "":
|
| 73 |
+
cleaned_chunks.append(chunk)
|
| 74 |
+
return cleaned_chunks
|
| 75 |
+
|
| 76 |
+
# Call the preprocess_text function and store the result in a cleaned_chunks variable
|
| 77 |
+
cleaned_chunks_weather = preprocess_text(weather_text) # Complete this line
|
| 78 |
+
cleaned_chunks_luggage = preprocess_text(luggage_text)
|
| 79 |
+
cleaned_chunks_attraction = preprocess_text(attraction_text)
|
| 80 |
+
cleaned_chunks_food = preprocess_text(food_text)
|
| 81 |
+
|
| 82 |
+
#STEP 4 FROM SEMANTIC SEARCH
|
| 83 |
+
# Load the pre-trained embedding model that converts text to vectors
|
| 84 |
+
model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 85 |
|
| 86 |
def create_embeddings(text_chunks):
|
| 87 |
+
# Convert each text chunk into a vector embedding and store as a tensor
|
| 88 |
+
chunk_embeddings = model.encode(text_chunks, convert_to_tensor=True) # Replace ... with the text_chunks list
|
| 89 |
+
return chunk_embeddings
|
| 90 |
+
|
| 91 |
+
# Call the create_embeddings function and store the result in a new chunk_embeddings variable
|
| 92 |
+
chunk_embeddings_weather = create_embeddings(cleaned_chunks_weather) # Complete this line
|
| 93 |
+
chunk_embeddings_luggage = create_embeddings(cleaned_chunks_luggage)
|
| 94 |
+
chunk_embeddings_attraction = create_embeddings(cleaned_chunks_attraction)
|
| 95 |
+
chunk_embeddings_food = create_embeddings(cleaned_chunks_food)
|
| 96 |
+
|
| 97 |
+
#STEP 5 FROM SEMANTIC SEARCH
|
| 98 |
+
# Define a function to find the most relevant text chunks for a given query, chunk_embeddings, and text_chunks
|
| 99 |
+
def get_top_chunks(query, chunk_embeddings, text_chunks):
|
| 100 |
+
# Convert the query text into a vector embedding
|
| 101 |
+
query_embedding = model.encode(query, convert_to_tensor = True) # Complete this line
|
| 102 |
+
# Normalize the query embedding to unit length for accurate similarity comparison
|
| 103 |
+
query_embedding_normalized = query_embedding / query_embedding.norm()
|
| 104 |
+
# Normalize all chunk embeddings to unit length for consistent comparison
|
| 105 |
+
chunk_embeddings_normalized = chunk_embeddings / chunk_embeddings.norm(dim=1, keepdim=True)
|
| 106 |
+
# Calculate cosine similarity between query and all chunks using matrix multiplication
|
| 107 |
+
similarities = torch.matmul(chunk_embeddings_normalized, query_embedding_normalized) # Complete this line
|
| 108 |
+
# Print the similarities
|
| 109 |
+
#print(similarities)
|
| 110 |
+
# Find the indices of the 3 chunks with highest similarity scores
|
| 111 |
+
top_indices = torch.topk(similarities, k=3).indices
|
| 112 |
+
# Print the top indices
|
| 113 |
+
#print(top_indices)
|
| 114 |
+
# Create an empty list to store the most relevant chunks
|
| 115 |
+
top_chunks = []
|
| 116 |
+
# Loop through the top indices and retrieve the corresponding text chunks
|
| 117 |
+
for top_index in top_indices:
|
| 118 |
+
top_chunks.append(text_chunks[top_index])
|
| 119 |
+
# Return the list of most relevant chunks
|
| 120 |
+
return top_chunks
|
| 121 |
+
|
| 122 |
+
#STEP 6 FROM SEMANTIC SEARCH
|
| 123 |
+
|
| 124 |
+
client = InferenceClient("Qwen/Qwen2.5-72B-Instruct")
|
| 125 |
+
|
| 126 |
+
def respond(message, history, language, chatbot_mode, destinations, trip_length, trip_unit, trip_season, luggage_types, luggage_size, food_prefs, activity):
|
| 127 |
+
destinations = destinations or []
|
| 128 |
+
trip_length = trip_length or "Not specified"
|
| 129 |
+
trip_unit = trip_unit or ""
|
| 130 |
+
trip_season = trip_season or "Not specified"
|
| 131 |
+
luggage_types = luggage_types or []
|
| 132 |
+
luggage_size = luggage_size or "Not specified"
|
| 133 |
+
food_prefs = food_prefs or []
|
| 134 |
+
activity = activity or []
|
| 135 |
+
language = language or "English"
|
| 136 |
+
|
| 137 |
+
#conduct a semantic search for each of our files
|
| 138 |
+
top_weather = get_top_chunks(message, chunk_embeddings_weather, cleaned_chunks_weather)
|
| 139 |
+
top_luggage = get_top_chunks(message, chunk_embeddings_luggage, cleaned_chunks_luggage)
|
| 140 |
+
top_attraction = get_top_chunks(message, chunk_embeddings_attraction, cleaned_chunks_attraction)
|
| 141 |
+
top_food = get_top_chunks(message, chunk_embeddings_food, cleaned_chunks_food)
|
| 142 |
+
|
| 143 |
+
str_top_weather = "\n".join(top_weather)
|
| 144 |
+
str_top_luggage = "\n".join(top_luggage)
|
| 145 |
+
str_top_attraction = "\n".join(top_attraction)
|
| 146 |
+
str_top_food = "\n".join(top_food)
|
| 147 |
+
|
| 148 |
+
#collect inputed data from the sidebar elements
|
| 149 |
+
ctx = (
|
| 150 |
+
f"Language: {language}"
|
| 151 |
+
f"Destination: {', '.join(destinations) if destinations else 'Not specified'}\n"
|
| 152 |
+
f"Trip Length: {trip_length} {trip_unit}\n"
|
| 153 |
+
f"Trip Season: {trip_season}\n"
|
| 154 |
+
f"Luggage: {', '.join(luggage_types) if luggage_types else 'Not specified'}, "
|
| 155 |
+
f"Size: {luggage_size}L\n"
|
| 156 |
+
f"Food Preferences: {', '.join(food_prefs) if food_prefs else 'Not specified'}\n"
|
| 157 |
+
f"Activity Preferences: {', '.join(activity) if activity else 'Not specified'}\n"
|
| 158 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
|
| 160 |
+
if chatbot_mode == "Packing":
|
| 161 |
+
messages = [{
|
| 162 |
+
"role": "system",
|
| 163 |
+
"content": (
|
| 164 |
+
f"You are a friendly and Gen Z travel chatbot helping with packing advice.\n\n"
|
| 165 |
+
f"{ctx}\n"
|
| 166 |
+
f"Relevant context:\n{str_top_weather}\n{str_top_luggage}"
|
| 167 |
+
f"Please respond in {language}"
|
| 168 |
+
)
|
| 169 |
+
}]
|
| 170 |
+
elif chatbot_mode == "Food/Attractions":
|
| 171 |
+
messages = [{
|
| 172 |
+
"role": "system",
|
| 173 |
+
"content": (
|
| 174 |
+
f"You are a friendly and Gen Z travel chatbot recommending food and attractions.\n\n"
|
| 175 |
+
f"{ctx}\n"
|
| 176 |
+
f"Relevant context:\n{str_top_food}\n{str_top_attraction}"
|
| 177 |
+
f"Please respond in {language}"
|
| 178 |
+
)
|
| 179 |
+
}]
|
| 180 |
+
else:
|
| 181 |
+
messages = [{
|
| 182 |
+
"role": "system",
|
| 183 |
+
"content": (
|
| 184 |
+
f"You are a friendly and Gen Z travel chatbot helping travelers plan trips to San Francisco and/or Los Angeles.\n\n"
|
| 185 |
+
f"{ctx}\n"
|
| 186 |
+
f"Use relevant context:\n{str_top_weather}\n{str_top_luggage}\n{str_top_food}\n{str_top_attraction}"
|
| 187 |
+
f"Please respond in {language}"
|
| 188 |
+
)
|
| 189 |
+
}]
|
| 190 |
+
|
| 191 |
+
if history:
|
| 192 |
+
for user_msg, bot_reply in history:
|
| 193 |
+
messages.append({"role": "user", "content": user_msg})
|
| 194 |
+
messages.append({"role": "assistant", "content": bot_reply})
|
| 195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
messages.append({"role": "user", "content": message})
|
| 197 |
|
| 198 |
+
response = client.chat_completion(
|
|
|
|
| 199 |
messages,
|
| 200 |
+
max_tokens = 2000,
|
| 201 |
+
temperature = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
)
|
| 203 |
|
| 204 |
+
if isinstance(response, dict):
|
| 205 |
+
reply = response['choices'][0]['message']['content'].strip()
|
| 206 |
+
else:
|
| 207 |
+
reply = response.strip()
|
| 208 |
+
|
| 209 |
+
history.append((message, reply))
|
| 210 |
+
return history, ""
|
| 211 |
+
|
| 212 |
+
def reset_inputs():
|
| 213 |
+
return (
|
| 214 |
+
None, # language
|
| 215 |
+
None, # chatbot_mode
|
| 216 |
+
[], # destinations
|
| 217 |
+
None, # trip_length
|
| 218 |
+
None, # trip_unit
|
| 219 |
+
None, # trip_season
|
| 220 |
+
[], # luggage_types
|
| 221 |
+
20, # luggage_size (default)
|
| 222 |
+
[], # food_prefs
|
| 223 |
+
[] # activity
|
| 224 |
)
|
| 225 |
|
| 226 |
+
def update_visibility(chatbot_mode):
|
| 227 |
+
if chatbot_mode == "Packing":
|
| 228 |
+
return gr.update(visible=True), gr.update(visible=False)
|
| 229 |
+
elif chatbot_mode == "Food/Attractions":
|
| 230 |
+
return gr.update(visible=False), gr.update(visible=True)
|
| 231 |
+
else:
|
| 232 |
+
return gr.update(visible=True), gr.update(visible=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
+
with gr.Blocks(theme=theme) as demo:
|
| 235 |
+
with gr.Row():
|
| 236 |
+
# ─── left column: your controls ───
|
| 237 |
+
with gr.Column(scale=1):
|
| 238 |
+
gr.Markdown("### Chatbot Settings")
|
| 239 |
+
language = gr.Radio(
|
| 240 |
+
choices=["English","Español", "Italiano", "Français", "日本語", "中文"],
|
| 241 |
+
label="What language would you like to use?"
|
| 242 |
+
)
|
| 243 |
+
chatbot_mode = gr.Radio(
|
| 244 |
+
choices=["Packing", "Food/Attractions"],
|
| 245 |
+
label="What do you need help with?"
|
| 246 |
+
)
|
| 247 |
+
gr.Markdown("### Destination")
|
| 248 |
+
destinations = gr.CheckboxGroup(
|
| 249 |
+
choices=["San Francisco","Los Angeles"],
|
| 250 |
+
label="Where are you going?"
|
| 251 |
+
)
|
| 252 |
+
gr.Markdown("### Trip Length")
|
| 253 |
+
trip_length = gr.Number(label="How long is your trip?", precision=0)
|
| 254 |
+
trip_unit = gr.Radio(
|
| 255 |
+
choices=["day", "week", "month"],
|
| 256 |
+
label="Trip length unit:"
|
| 257 |
+
)
|
| 258 |
+
trip_season = gr.Radio(
|
| 259 |
+
choices=["Warm/Dry (May to October)", "Cool/Wet (November to April)"],
|
| 260 |
+
label="Season:"
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
with gr.Group(visible=True) as packing_group:
|
| 264 |
+
#gr.Markdown("### Luggage")
|
| 265 |
+
luggage_types = gr.CheckboxGroup(
|
| 266 |
+
choices=["Carry-on", "Checked"],
|
| 267 |
+
label="What is your luggage type?"
|
| 268 |
+
)
|
| 269 |
+
luggage_size = gr.Slider(
|
| 270 |
+
minimum=10, maximum=100, step=10, value=20,
|
| 271 |
+
label="What is the size of your luggage (liters)?"
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
with gr.Group(visible=True) as food_group:
|
| 275 |
+
#gr.Markdown("### Food")
|
| 276 |
+
food_prefs = gr.Dropdown(
|
| 277 |
+
choices=["Italian", "Thai", "Mexican", "Japanese", "Vegan", "Seafood"],
|
| 278 |
+
multiselect=True,
|
| 279 |
+
label="What are your food preferences?"
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
#gr.Markdown("### Activities")
|
| 283 |
+
activity = gr.Dropdown(
|
| 284 |
+
choices=["Outdoor & Nature", "Indoor", "Museums", "Shopping", "Relaxation"],
|
| 285 |
+
multiselect=True,
|
| 286 |
+
label="What are your activity preferences?"
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
chatbot_mode.change(
|
| 290 |
+
fn=update_visibility,
|
| 291 |
+
inputs=[chatbot_mode],
|
| 292 |
+
outputs=[packing_group, food_group]
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
reset_btn = gr.Button("Reset All", variant="secondary")
|
| 296 |
+
reset_btn.click(
|
| 297 |
+
fn=reset_inputs,
|
| 298 |
+
inputs=[],
|
| 299 |
+
outputs=[
|
| 300 |
+
language,
|
| 301 |
+
chatbot_mode,
|
| 302 |
+
destinations,
|
| 303 |
+
trip_length,
|
| 304 |
+
trip_unit,
|
| 305 |
+
trip_season,
|
| 306 |
+
luggage_types,
|
| 307 |
+
luggage_size,
|
| 308 |
+
food_prefs,
|
| 309 |
+
activity
|
| 310 |
+
]
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
with gr.Column(scale=3):
|
| 314 |
+
gr.Image(value="Go Buddy2.png", interactive=False, show_label=False)
|
| 315 |
+
|
| 316 |
+
# 1) the chat history panel
|
| 317 |
+
chat_box = gr.Chatbot(height=900)
|
| 318 |
+
|
| 319 |
+
# 2) where the user types
|
| 320 |
+
msg = gr.Textbox(
|
| 321 |
+
placeholder="Ask me anything…",
|
| 322 |
+
lines=1,
|
| 323 |
+
label = "How can we help you?"
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
# 3) send button
|
| 327 |
+
send_btn = gr.Button("Send")
|
| 328 |
+
|
| 329 |
+
# 4) hook up the click event
|
| 330 |
+
send_btn.click(
|
| 331 |
+
fn=respond,
|
| 332 |
+
inputs=[
|
| 333 |
+
msg,
|
| 334 |
+
chat_box,
|
| 335 |
+
language,
|
| 336 |
+
chatbot_mode,
|
| 337 |
+
destinations,
|
| 338 |
+
trip_length,
|
| 339 |
+
trip_unit,
|
| 340 |
+
trip_season,
|
| 341 |
+
luggage_types,
|
| 342 |
+
luggage_size,
|
| 343 |
+
food_prefs,
|
| 344 |
+
activity
|
| 345 |
+
],
|
| 346 |
+
outputs=[chat_box, msg]
|
| 347 |
+
)
|
| 348 |
+
msg.submit(
|
| 349 |
+
fn=respond,
|
| 350 |
+
inputs=[
|
| 351 |
+
msg,
|
| 352 |
+
chat_box,
|
| 353 |
+
language,
|
| 354 |
+
chatbot_mode,
|
| 355 |
+
destinations,
|
| 356 |
+
trip_length,
|
| 357 |
+
trip_unit,
|
| 358 |
+
trip_season,
|
| 359 |
+
luggage_types,
|
| 360 |
+
luggage_size,
|
| 361 |
+
food_prefs,
|
| 362 |
+
activity
|
| 363 |
+
],
|
| 364 |
+
outputs=[chat_box, msg]
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
demo.launch(debug=True)
|