Spaces:
Sleeping
Sleeping
File size: 10,062 Bytes
a3e0475 ffbccbb 1fd6803 fc5f1c7 d6f5773 fc5f1c7 a3e0475 5c095c6 3efbe63 fc42bd4 ab8074b 258dcf5 b256ef1 5c095c6 e3d3d36 5c095c6 1fd6803 5c095c6 1fd6803 391ca85 e3d3d36 94ac9e7 5c095c6 fc5f1c7 5c095c6 a3e0475 258dcf5 5c095c6 a3e0475 94ac9e7 258dcf5 94ac9e7 5c095c6 94ac9e7 e3d3d36 073538f 258dcf5 69fe7cc 258dcf5 ac55e19 7e790cb a40b229 7e790cb 258dcf5 a40b229 fc5f1c7 f7f1088 258dcf5 fc5f1c7 6ef9d8a 1fd6803 6ef9d8a fc5f1c7 3efbe63 258dcf5 7e790cb 258dcf5 7e790cb fc5f1c7 f77b42d 258dcf5 5c095c6 2a239ae 5c095c6 fc5f1c7 5c095c6 258dcf5 5c095c6 ad1c148 d4c5e46 1fd6803 7e790cb 258dcf5 2a239ae 3efbe63 2a239ae 3efbe63 e3d3d36 7e790cb 258dcf5 f77b42d 3efbe63 258dcf5 7e790cb f77b42d 7e790cb f77b42d 7e790cb f77b42d 7e790cb 391ca85 f77b42d e3d3d36 0b6b797 5c095c6 2a239ae 258dcf5 391ca85 5c095c6 258dcf5 6db00e7 1fd6803 f543f0b ad0b8d6 5c095c6 ad0b8d6 5c095c6 ad0b8d6 5c095c6 258dcf5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 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 240 241 242 243 244 245 246 247 248 249 250 251 |
import os
import re
import random
import requests
import streamlit as st
from langchain_huggingface import HuggingFaceEndpoint
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from transformers import pipeline
from langdetect import detect
# Use environment variables for keys
HF_TOKEN = os.getenv("HF_TOKEN")
if HF_TOKEN is None:
raise ValueError("HF_TOKEN environment variable not set. Please set it in your Hugging Face Space settings.")
NASA_API_KEY = os.getenv("NASA_API_KEY")
if NASA_API_KEY is None:
raise ValueError("NASA_API_KEY environment variable not set. Please set it in your Hugging Face Space settings.")
# Set up Streamlit UI
st.set_page_config(page_title="HAL - NASA ChatBot", page_icon="π")
# --- Initialize Session State Variables ---
if "chat_history" not in st.session_state:
st.session_state.chat_history = [{"role": "assistant", "content": "Hello! How can I assist you today?"}]
if "response_ready" not in st.session_state:
st.session_state.response_ready = False
if "follow_up" not in st.session_state:
st.session_state.follow_up = ""
# --- Set Up Model & API Functions ---
model_id = "mistralai/Mistral-7B-Instruct-v0.3"
sentiment_analyzer = pipeline(
"sentiment-analysis",
model="distilbert/distilbert-base-uncased-finetuned-sst-2-english",
revision="714eb0f"
)
def get_llm_hf_inference(model_id=model_id, max_new_tokens=128, temperature=0.7):
return HuggingFaceEndpoint(
repo_id=model_id,
max_new_tokens=max_new_tokens,
temperature=temperature,
token=HF_TOKEN,
task="text-generation"
)
def get_nasa_apod():
url = f"https://api.nasa.gov/planetary/apod?api_key={NASA_API_KEY}"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
return data.get("url", ""), data.get("title", ""), data.get("explanation", "")
else:
return "", "NASA Data Unavailable", "I couldn't fetch data from NASA right now. Please try again later."
def analyze_sentiment(user_text):
result = sentiment_analyzer(user_text)[0]
return result['label']
def is_apod_query(user_text):
"""
Checks if the user's question contains keywords indicating they are asking for
the Astronomy Picture of the Day.
"""
keywords = ["apod", "image", "picture", "photo", "astronomy picture"]
return any(keyword in user_text.lower() for keyword in keywords)
def predict_action(user_text):
"""
Determines the type of response for the user's query.
If the query contains "NASA" or "space" and also indicates an APOD query,
it returns "nasa_info". Otherwise, it returns "general_query", even if "NASA" is present.
"""
user_text_lower = user_text.lower()
if "nasa" in user_text_lower or "space" in user_text_lower:
if is_apod_query(user_text):
return "nasa_info"
return "general_query"
def generate_follow_up(user_text):
"""
Generates two variant follow-up questions and randomly selects one.
It also cleans up any unwanted quotation marks or extra meta commentary.
"""
prompt_text = (
f"Based on the user's question: '{user_text}', generate two concise, friendly follow-up questions "
"that invite further discussion. For example, one might be 'Would you like to know more about the six types of quarks?' "
"and another might be 'Would you like to explore another aspect of quantum physics?' Do not include extra commentary ."
"Answer exclusively in English, and do not include extra commentary."
)
hf = get_llm_hf_inference(max_new_tokens=80, temperature=0.9)
output = hf.invoke(input=prompt_text).strip()
variants = re.split(r"\n|[;]+", output)
cleaned = [v.strip(' "\'') for v in variants if v.strip()]
if not cleaned:
cleaned = ["Would you like to explore this topic further?"]
return random.choice(cleaned)
from langdetect import detect
def ensure_english(text):
"""Check if the model accidentally generated a non-English response."""
try:
detected_lang = detect(text)
if detected_lang != "en":
return "β οΈ Sorry, I only respond in English. Can you rephrase your question?"
except:
return "β οΈ Language detection failed. Please ask your question again."
return text
def get_response(system_message, chat_history, user_text, max_new_tokens=512):
"""
Generates HAL's answer with depth and a follow-up question.
The prompt instructs the model to provide a detailed explanation and then generate a follow-up.
If the answer comes back empty, a fallback answer is used.
"""
sentiment = analyze_sentiment(user_text)
action = predict_action(user_text)
# Extract style instruction if present
style_instruction = ""
lower_text = user_text.lower()
if "in the voice of" in lower_text or "speaking as" in lower_text:
match = re.search(r"(in the voice of|speaking as)(.*)", lower_text)
if match:
style_instruction = match.group(2).strip().capitalize()
style_instruction = f" Please respond in the voice of {style_instruction}."
if action == "nasa_info":
nasa_url, nasa_title, nasa_explanation = get_nasa_apod()
response = f"**{nasa_title}**\n\n{nasa_explanation}"
chat_history.append({'role': 'user', 'content': user_text})
chat_history.append({'role': 'assistant', 'content': response})
follow_up = generate_follow_up(user_text)
chat_history.append({'role': 'assistant', 'content': follow_up})
return response, follow_up, chat_history, nasa_url
hf = get_llm_hf_inference(max_new_tokens=max_new_tokens, temperature=0.9)
filtered_history = ""
for message in chat_history:
if message["role"] == "assistant" and message["content"].strip() == "Hello! How can I assist you today?":
continue
filtered_history += f"{message['role']}: {message['content']}\n"
style_clause = style_instruction if style_instruction else ""
# Instruct the model to generate a detailed, in-depth answer.
prompt = PromptTemplate.from_template(
(
"[INST] {system_message}\n\nCurrent Conversation:\n{chat_history}\n\n"
"User: {user_text}.\n [/INST]\n"
"AI: Please provide a detailed explanation in depth. "
"Ensure your response covers the topic thoroughly and is written in a friendly, conversational style, "
"starting with a phrase like 'Certainly!', 'Of course!', or 'Great question!'."
"π¨ IMPORTANT: Answer exclusively in **English only**. Do not generate responses in any other language."
"\nHAL:"
)
)
chat = prompt | hf.bind(skip_prompt=True) | StrOutputParser(output_key='content')
response = chat.invoke(input=dict(system_message=system_message, user_text=user_text, chat_history=filtered_history))
response = response.split("HAL:")[-1].strip()
# π¨ Ensure the response is in English
response = ensure_english(response)
# Fallback in case the generated answer is empty
if not response:
response = "Certainly, here is an in-depth explanation: [Fallback explanation]."
chat_history.append({'role': 'user', 'content': user_text})
chat_history.append({'role': 'assistant', 'content': response})
# π FIX: Only override if strongly negative and NOT a question
if sentiment == "NEGATIVE" and not user_text.strip().endswith("?"):
response = "I'm sorry you're feeling this way. I'm here to help. What can I do to assist you further?"
chat_history[-1]['content'] = response
follow_up = None # π¨ Don't generate follow-up if negative sentiment triggers a different message
else:
follow_up = generate_follow_up(user_text)
chat_history.append({'role': 'assistant', 'content': follow_up})
return response, follow_up, chat_history, None
# --- Chat UI ---
st.title("π HAL - Your NASA AI Assistant")
st.markdown("π *Ask me about space, NASA, and beyond!*")
#Reset Button
if st.sidebar.button("Reset Chat"):
st.session_state.chat_history = [{"role": "assistant", "content": "Hello! How can I assist you today?"}]
st.session_state.response_ready = False
st.session_state.follow_up = ""
st.experimental_rerun()
#Style and Appearance
st.markdown("""
<style>
.user-msg {
background-color: #696969;
color: white;
padding: 10px;
border-radius: 10px;
margin-bottom: 5px;
width: fit-content;
max-width: 80%;
}
.assistant-msg {
background-color: #333333;
color: white;
padding: 10px;
border-radius: 10px;
margin-bottom: 5px;
width: fit-content;
max-width: 80%;
}
.container {
display: flex;
flex-direction: column;
align-items: flex-start;
}
@media (max-width: 600px) {
.user-msg, .assistant-msg { font-size: 16px; max-width: 100%; }
}
</style>
""", unsafe_allow_html=True)
user_input = st.chat_input("Type your message here...")
if user_input:
response, follow_up, st.session_state.chat_history, image_url = get_response(
system_message="You are a helpful AI assistant.",
user_text=user_input,
chat_history=st.session_state.chat_history
)
if image_url:
st.image(image_url, caption="NASA Image of the Day")
st.session_state.follow_up = follow_up
st.session_state.response_ready = True
st.markdown("<div class='container'>", unsafe_allow_html=True)
for message in st.session_state.chat_history:
if message["role"] == "user":
st.markdown(f"<div class='user-msg'><strong>You:</strong> {message['content']}</div>", unsafe_allow_html=True)
else:
st.markdown(f"<div class='assistant-msg'><strong>HAL:</strong> {message['content']}</div>", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True) |