Implement efficiency improvements: HF endpoint monitoring optimization, Redis connection pooling, API request caching, session management optimization, async processing, model validation caching, and config singleton pattern
Browse files- app.py +119 -38
- core/coordinator.py +46 -2
- core/providers/huggingface.py +20 -7
- core/redis_client.py +10 -5
- core/session.py +18 -17
- services/weather.py +17 -3
- utils/config.py +35 -21
app.py
CHANGED
|
@@ -37,6 +37,8 @@ if "cosmic_mode" not in st.session_state:
|
|
| 37 |
st.session_state.cosmic_mode = True # Default to cosmic mode
|
| 38 |
if "show_welcome" not in st.session_state:
|
| 39 |
st.session_state.show_welcome = True
|
|
|
|
|
|
|
| 40 |
|
| 41 |
# Sidebar layout redesign
|
| 42 |
with st.sidebar:
|
|
@@ -114,7 +116,7 @@ with st.sidebar:
|
|
| 114 |
st.warning("🦙 Ollama: Not running")
|
| 115 |
except:
|
| 116 |
st.info("🦙 Ollama: Unknown")
|
| 117 |
-
|
| 118 |
try:
|
| 119 |
hf_status = hf_monitor.check_endpoint_status()
|
| 120 |
# Enhanced HF status display
|
|
@@ -135,12 +137,12 @@ with st.sidebar:
|
|
| 135 |
st.error("🔴 Unavailable")
|
| 136 |
except Exception as e:
|
| 137 |
st.info("⏳ Initializing...")
|
| 138 |
-
|
| 139 |
if check_redis_health():
|
| 140 |
st.success("💾 Redis: Connected")
|
| 141 |
else:
|
| 142 |
st.error("💾 Redis: Disconnected")
|
| 143 |
-
|
| 144 |
st.divider()
|
| 145 |
|
| 146 |
st.subheader("🐛 Debug Info")
|
|
@@ -222,6 +224,8 @@ def render_message(role, content, source=None, timestamp=None):
|
|
| 222 |
st.markdown(f"### ❌ Error:")
|
| 223 |
elif source == "space_story":
|
| 224 |
st.markdown(f"### 🐱 Cosmic Kitten Story:")
|
|
|
|
|
|
|
| 225 |
else:
|
| 226 |
st.markdown(f"### {source}")
|
| 227 |
st.markdown(content)
|
|
@@ -237,6 +241,83 @@ for message in st.session_state.messages:
|
|
| 237 |
message.get("timestamp")
|
| 238 |
)
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
# Input validation function
|
| 241 |
def validate_user_input(text):
|
| 242 |
"""Validate and sanitize user input"""
|
|
@@ -244,12 +325,12 @@ def validate_user_input(text):
|
|
| 244 |
return False, "Input cannot be empty"
|
| 245 |
if len(text) > 1000:
|
| 246 |
return False, "Input too long (max 1000 characters)"
|
| 247 |
-
|
| 248 |
# Check for potentially harmful patterns
|
| 249 |
harmful_patterns = ["<script", "javascript:", "onload=", "onerror="]
|
| 250 |
if any(pattern in text.lower() for pattern in harmful_patterns):
|
| 251 |
return False, "Potentially harmful input detected"
|
| 252 |
-
|
| 253 |
return True, text.strip()
|
| 254 |
|
| 255 |
# Chat input - FIXED VERSION (moved outside of tabs)
|
|
@@ -264,23 +345,23 @@ if user_input and not st.session_state.is_processing:
|
|
| 264 |
st.session_state.is_processing = False
|
| 265 |
else:
|
| 266 |
st.session_state.is_processing = True
|
| 267 |
-
|
| 268 |
# Display user message
|
| 269 |
with st.chat_message("user"):
|
| 270 |
st.markdown(validated_input)
|
| 271 |
-
|
| 272 |
# Add to message history - ensure proper format
|
| 273 |
st.session_state.messages.append({
|
| 274 |
"role": "user",
|
| 275 |
"content": validated_input,
|
| 276 |
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 277 |
})
|
| 278 |
-
|
| 279 |
# Process AI response
|
| 280 |
with st.chat_message("assistant"):
|
| 281 |
response_placeholder = st.empty()
|
| 282 |
status_placeholder = st.empty()
|
| 283 |
-
|
| 284 |
try:
|
| 285 |
# Get conversation history
|
| 286 |
user_session = session_manager.get_session("default_user")
|
|
@@ -400,28 +481,28 @@ if user_input and not st.session_state.is_processing:
|
|
| 400 |
user_msg = translate_error(ollama_error)
|
| 401 |
status_placeholder.error(f"⚠️ {user_msg}")
|
| 402 |
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
ai_response = send_to_hf(validated_input, conversation_history)
|
| 414 |
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
user_msg = translate_error(hf_error)
|
| 423 |
-
status_placeholder.error(f"⚠️ {user_msg}")
|
| 424 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
# Save response if successful
|
| 426 |
if ai_response:
|
| 427 |
# Update conversation history
|
|
@@ -462,10 +543,10 @@ if user_input and not st.session_state.is_processing:
|
|
| 462 |
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 463 |
})
|
| 464 |
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
|
| 470 |
# Add evaluation dashboard tab (separate from chat interface)
|
| 471 |
st.divider()
|
|
@@ -548,7 +629,7 @@ with tab2:
|
|
| 548 |
st.success("💾 Redis: Connected")
|
| 549 |
else:
|
| 550 |
st.error("💾 Redis: Disconnected")
|
| 551 |
-
|
| 552 |
# Session statistics
|
| 553 |
st.subheader("Session Statistics")
|
| 554 |
try:
|
|
@@ -608,10 +689,10 @@ with tab2:
|
|
| 608 |
|
| 609 |
col1, col2, col3 = st.columns(3)
|
| 610 |
col1.metric("Total Exchanges", len(user_messages))
|
| 611 |
-
col2.metric("Avg Response Length",
|
| 612 |
-
|
| 613 |
-
col3.metric("Topics Discussed",
|
| 614 |
-
|
| 615 |
|
| 616 |
# Show most common words/topics
|
| 617 |
all_text = " ".join([msg.get("content", "") for msg in conversation]).lower()
|
|
|
|
| 37 |
st.session_state.cosmic_mode = True # Default to cosmic mode
|
| 38 |
if "show_welcome" not in st.session_state:
|
| 39 |
st.session_state.show_welcome = True
|
| 40 |
+
if "hf_expert_requested" not in st.session_state:
|
| 41 |
+
st.session_state.hf_expert_requested = False
|
| 42 |
|
| 43 |
# Sidebar layout redesign
|
| 44 |
with st.sidebar:
|
|
|
|
| 116 |
st.warning("🦙 Ollama: Not running")
|
| 117 |
except:
|
| 118 |
st.info("🦙 Ollama: Unknown")
|
| 119 |
+
|
| 120 |
try:
|
| 121 |
hf_status = hf_monitor.check_endpoint_status()
|
| 122 |
# Enhanced HF status display
|
|
|
|
| 137 |
st.error("🔴 Unavailable")
|
| 138 |
except Exception as e:
|
| 139 |
st.info("⏳ Initializing...")
|
| 140 |
+
|
| 141 |
if check_redis_health():
|
| 142 |
st.success("💾 Redis: Connected")
|
| 143 |
else:
|
| 144 |
st.error("💾 Redis: Disconnected")
|
| 145 |
+
|
| 146 |
st.divider()
|
| 147 |
|
| 148 |
st.subheader("🐛 Debug Info")
|
|
|
|
| 224 |
st.markdown(f"### ❌ Error:")
|
| 225 |
elif source == "space_story":
|
| 226 |
st.markdown(f"### 🐱 Cosmic Kitten Story:")
|
| 227 |
+
elif source == "hf_expert_analysis":
|
| 228 |
+
st.markdown(f"### 🤖 HF Expert Analysis:")
|
| 229 |
else:
|
| 230 |
st.markdown(f"### {source}")
|
| 231 |
st.markdown(content)
|
|
|
|
| 241 |
message.get("timestamp")
|
| 242 |
)
|
| 243 |
|
| 244 |
+
# Manual HF Analysis Section
|
| 245 |
+
if st.session_state.messages and len(st.session_state.messages) > 0:
|
| 246 |
+
st.divider()
|
| 247 |
+
|
| 248 |
+
# HF Expert Section
|
| 249 |
+
with st.expander("🤖 HF Expert Analysis", expanded=False):
|
| 250 |
+
st.subheader("Deep Conversation Analysis")
|
| 251 |
+
|
| 252 |
+
col1, col2 = st.columns([3, 1])
|
| 253 |
+
with col1:
|
| 254 |
+
st.markdown("""
|
| 255 |
+
**HF Expert Features:**
|
| 256 |
+
- Analyzes entire conversation history
|
| 257 |
+
- Performs web research when needed
|
| 258 |
+
- Provides deep insights and recommendations
|
| 259 |
+
- Acts as expert consultant in your conversation
|
| 260 |
+
""")
|
| 261 |
+
|
| 262 |
+
with col2:
|
| 263 |
+
if st.button("🧠 Activate HF Expert",
|
| 264 |
+
key="activate_hf_expert",
|
| 265 |
+
help="Send conversation to HF endpoint for deep analysis",
|
| 266 |
+
use_container_width=True,
|
| 267 |
+
disabled=st.session_state.is_processing):
|
| 268 |
+
st.session_state.hf_expert_requested = True
|
| 269 |
+
|
| 270 |
+
# Show HF expert analysis when requested
|
| 271 |
+
if st.session_state.get("hf_expert_requested", False):
|
| 272 |
+
with st.spinner("🧠 HF Expert analyzing conversation..."):
|
| 273 |
+
try:
|
| 274 |
+
# Get conversation history
|
| 275 |
+
user_session = session_manager.get_session("default_user")
|
| 276 |
+
conversation_history = user_session.get("conversation", [])
|
| 277 |
+
|
| 278 |
+
# Show what HF expert sees
|
| 279 |
+
with st.expander("📋 HF Expert Input", expanded=False):
|
| 280 |
+
st.markdown("**Conversation History Sent to HF Expert:**")
|
| 281 |
+
for i, msg in enumerate(conversation_history[-10:]): # Last 10 messages
|
| 282 |
+
st.markdown(f"**{msg['role'].capitalize()}:** {msg['content'][:100]}{'...' if len(msg['content']) > 100 else ''}")
|
| 283 |
+
|
| 284 |
+
# Request HF analysis
|
| 285 |
+
hf_analysis = coordinator.manual_hf_analysis(
|
| 286 |
+
"default_user",
|
| 287 |
+
conversation_history
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
if hf_analysis:
|
| 291 |
+
# Display HF expert response with clear indication
|
| 292 |
+
with st.chat_message("assistant"):
|
| 293 |
+
st.markdown("### 🤖 HF Expert Analysis")
|
| 294 |
+
st.markdown(hf_analysis)
|
| 295 |
+
|
| 296 |
+
# Add research/web search decisions
|
| 297 |
+
research_needs = coordinator.determine_web_search_needs(conversation_history)
|
| 298 |
+
if research_needs["needs_search"]:
|
| 299 |
+
st.info(f"🔍 **Research Needed:** {research_needs['reasoning']}")
|
| 300 |
+
if st.button("🔎 Perform Web Research", key="web_research_button"):
|
| 301 |
+
# Perform web search
|
| 302 |
+
with st.spinner("🔎 Searching for current information..."):
|
| 303 |
+
# Add web search logic here
|
| 304 |
+
st.success("✅ Web research completed!")
|
| 305 |
+
|
| 306 |
+
# Add to message history with HF expert tag
|
| 307 |
+
st.session_state.messages.append({
|
| 308 |
+
"role": "assistant",
|
| 309 |
+
"content": hf_analysis,
|
| 310 |
+
"timestamp": datetime.now().strftime("%H:%M:%S"),
|
| 311 |
+
"source": "hf_expert_analysis",
|
| 312 |
+
"research_needs": research_needs
|
| 313 |
+
})
|
| 314 |
+
|
| 315 |
+
st.session_state.hf_expert_requested = False
|
| 316 |
+
|
| 317 |
+
except Exception as e:
|
| 318 |
+
st.error(f"❌ HF Expert analysis failed: {str(e)}")
|
| 319 |
+
st.session_state.hf_expert_requested = False
|
| 320 |
+
|
| 321 |
# Input validation function
|
| 322 |
def validate_user_input(text):
|
| 323 |
"""Validate and sanitize user input"""
|
|
|
|
| 325 |
return False, "Input cannot be empty"
|
| 326 |
if len(text) > 1000:
|
| 327 |
return False, "Input too long (max 1000 characters)"
|
| 328 |
+
|
| 329 |
# Check for potentially harmful patterns
|
| 330 |
harmful_patterns = ["<script", "javascript:", "onload=", "onerror="]
|
| 331 |
if any(pattern in text.lower() for pattern in harmful_patterns):
|
| 332 |
return False, "Potentially harmful input detected"
|
| 333 |
+
|
| 334 |
return True, text.strip()
|
| 335 |
|
| 336 |
# Chat input - FIXED VERSION (moved outside of tabs)
|
|
|
|
| 345 |
st.session_state.is_processing = False
|
| 346 |
else:
|
| 347 |
st.session_state.is_processing = True
|
| 348 |
+
|
| 349 |
# Display user message
|
| 350 |
with st.chat_message("user"):
|
| 351 |
st.markdown(validated_input)
|
| 352 |
+
|
| 353 |
# Add to message history - ensure proper format
|
| 354 |
st.session_state.messages.append({
|
| 355 |
"role": "user",
|
| 356 |
"content": validated_input,
|
| 357 |
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 358 |
})
|
| 359 |
+
|
| 360 |
# Process AI response
|
| 361 |
with st.chat_message("assistant"):
|
| 362 |
response_placeholder = st.empty()
|
| 363 |
status_placeholder = st.empty()
|
| 364 |
+
|
| 365 |
try:
|
| 366 |
# Get conversation history
|
| 367 |
user_session = session_manager.get_session("default_user")
|
|
|
|
| 481 |
user_msg = translate_error(ollama_error)
|
| 482 |
status_placeholder.error(f"⚠️ {user_msg}")
|
| 483 |
|
| 484 |
+
# Fallback to HF if available
|
| 485 |
+
if config.hf_token and not ai_response:
|
| 486 |
+
status_placeholder.info("⚡ Initializing HF Endpoint (2–4 minutes)...")
|
| 487 |
+
|
| 488 |
+
try:
|
| 489 |
+
# Check HF status first
|
| 490 |
+
hf_status = hf_monitor.check_endpoint_status()
|
| 491 |
+
if not hf_status['available']:
|
| 492 |
+
status_placeholder.info(personality.get_initializing_message())
|
|
|
|
|
|
|
| 493 |
|
| 494 |
+
ai_response = send_to_hf(validated_input, conversation_history)
|
| 495 |
+
|
| 496 |
+
if ai_response:
|
| 497 |
+
response_placeholder.markdown(ai_response)
|
| 498 |
+
status_placeholder.success("✅ HF response received!")
|
| 499 |
+
else:
|
| 500 |
+
status_placeholder.error("❌ No response from HF")
|
|
|
|
|
|
|
| 501 |
|
| 502 |
+
except Exception as hf_error:
|
| 503 |
+
user_msg = translate_error(hf_error)
|
| 504 |
+
status_placeholder.error(f"⚠️ {user_msg}")
|
| 505 |
+
|
| 506 |
# Save response if successful
|
| 507 |
if ai_response:
|
| 508 |
# Update conversation history
|
|
|
|
| 543 |
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 544 |
})
|
| 545 |
|
| 546 |
+
# Moved finally block to proper location
|
| 547 |
+
st.session_state.is_processing = False
|
| 548 |
+
time.sleep(0.5) # Brief pause
|
| 549 |
+
st.experimental_rerun()
|
| 550 |
|
| 551 |
# Add evaluation dashboard tab (separate from chat interface)
|
| 552 |
st.divider()
|
|
|
|
| 629 |
st.success("💾 Redis: Connected")
|
| 630 |
else:
|
| 631 |
st.error("💾 Redis: Disconnected")
|
| 632 |
+
|
| 633 |
# Session statistics
|
| 634 |
st.subheader("Session Statistics")
|
| 635 |
try:
|
|
|
|
| 689 |
|
| 690 |
col1, col2, col3 = st.columns(3)
|
| 691 |
col1.metric("Total Exchanges", len(user_messages))
|
| 692 |
+
col2.metric("Avg Response Length",
|
| 693 |
+
round(sum(len(msg.get("content", "")) for msg in ai_messages) / len(ai_messages)) if ai_messages else 0)
|
| 694 |
+
col3.metric("Topics Discussed",
|
| 695 |
+
len(set(["life", "goal", "health", "career"]) & set(" ".join([msg.get("content", "") for msg in conversation]).lower().split())))
|
| 696 |
|
| 697 |
# Show most common words/topics
|
| 698 |
all_text = " ".join([msg.get("content", "") for msg in conversation]).lower()
|
core/coordinator.py
CHANGED
|
@@ -18,7 +18,7 @@ from datetime import datetime
|
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
| 20 |
class AICoordinator:
|
| 21 |
-
"""Hierarchical multi-model coordinator with cosmic cascade flow"""
|
| 22 |
|
| 23 |
def __init__(self):
|
| 24 |
self.tavily_client = None
|
|
@@ -72,6 +72,47 @@ class AICoordinator:
|
|
| 72 |
"reasoning": f"Found topics requiring current info: {', '.join(search_topics)}" if search_topics else "No current info needed"
|
| 73 |
}
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
async def coordinate_cosmic_response(self, user_id: str, user_query: str) -> AsyncGenerator[Dict, None]:
|
| 76 |
"""
|
| 77 |
Three-stage cosmic response cascade:
|
|
@@ -595,7 +636,10 @@ Stream your response for real-time delivery."""
|
|
| 595 |
if any(keyword in query.lower() for keyword in weather_keywords):
|
| 596 |
try:
|
| 597 |
location = self._extract_location(query) or "New York"
|
| 598 |
-
weather = weather_service.
|
|
|
|
|
|
|
|
|
|
| 599 |
if weather:
|
| 600 |
data['weather'] = weather
|
| 601 |
except Exception as e:
|
|
|
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
| 20 |
class AICoordinator:
|
| 21 |
+
"""Hierarchical multi-model coordinator with cosmic cascade flow and async support"""
|
| 22 |
|
| 23 |
def __init__(self):
|
| 24 |
self.tavily_client = None
|
|
|
|
| 72 |
"reasoning": f"Found topics requiring current info: {', '.join(search_topics)}" if search_topics else "No current info needed"
|
| 73 |
}
|
| 74 |
|
| 75 |
+
async def coordinate_response_async(self, user_id: str, user_query: str):
|
| 76 |
+
"""Asynchronously coordinate responses with parallel execution"""
|
| 77 |
+
try:
|
| 78 |
+
# Get conversation history
|
| 79 |
+
session = session_manager.get_session(user_id)
|
| 80 |
+
|
| 81 |
+
# Inject current time into context
|
| 82 |
+
current_time = datetime.now().strftime("%A, %B %d, %Y at %I:%M %p")
|
| 83 |
+
time_context = {
|
| 84 |
+
"role": "system",
|
| 85 |
+
"content": f"[Current Date & Time: {current_time}]"
|
| 86 |
+
}
|
| 87 |
+
conversation_history = [time_context] + session.get("conversation", []).copy()
|
| 88 |
+
|
| 89 |
+
# Parallel execution - gather external data while processing local response
|
| 90 |
+
external_data_task = asyncio.create_task(
|
| 91 |
+
self._gather_external_data(user_query)
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
# Get local response while gathering external data
|
| 95 |
+
local_response = await self._get_local_ollama_response(user_query, conversation_history)
|
| 96 |
+
|
| 97 |
+
# Wait for external data
|
| 98 |
+
external_data = await external_data_task
|
| 99 |
+
|
| 100 |
+
# Process cloud response asynchronously if needed
|
| 101 |
+
hf_task = None
|
| 102 |
+
if self._check_hf_availability():
|
| 103 |
+
hf_task = asyncio.create_task(
|
| 104 |
+
self._get_hf_analysis(user_query, conversation_history)
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
return {
|
| 108 |
+
'local_response': local_response,
|
| 109 |
+
'hf_task': hf_task,
|
| 110 |
+
'external_data': external_data
|
| 111 |
+
}
|
| 112 |
+
except Exception as e:
|
| 113 |
+
logger.error(f"Async coordination failed: {e}")
|
| 114 |
+
raise
|
| 115 |
+
|
| 116 |
async def coordinate_cosmic_response(self, user_id: str, user_query: str) -> AsyncGenerator[Dict, None]:
|
| 117 |
"""
|
| 118 |
Three-stage cosmic response cascade:
|
|
|
|
| 636 |
if any(keyword in query.lower() for keyword in weather_keywords):
|
| 637 |
try:
|
| 638 |
location = self._extract_location(query) or "New York"
|
| 639 |
+
weather = weather_service.get_current_weather_cached(
|
| 640 |
+
location,
|
| 641 |
+
ttl_hash=weather_service._get_ttl_hash(300)
|
| 642 |
+
)
|
| 643 |
if weather:
|
| 644 |
data['weather'] = weather
|
| 645 |
except Exception as e:
|
core/providers/huggingface.py
CHANGED
|
@@ -15,7 +15,7 @@ except ImportError:
|
|
| 15 |
OpenAI = None
|
| 16 |
|
| 17 |
class HuggingFaceProvider(LLMProvider):
|
| 18 |
-
"""Hugging Face LLM provider implementation"""
|
| 19 |
|
| 20 |
def __init__(self, model_name: str, timeout: int = 30, max_retries: int = 3):
|
| 21 |
super().__init__(model_name, timeout, max_retries)
|
|
@@ -41,6 +41,11 @@ class HuggingFaceProvider(LLMProvider):
|
|
| 41 |
logger.error(f"Error type: {type(e)}")
|
| 42 |
raise
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
def generate(self, prompt: str, conversation_history: List[Dict]) -> Optional[str]:
|
| 45 |
"""Generate a response synchronously"""
|
| 46 |
try:
|
|
@@ -58,11 +63,16 @@ class HuggingFaceProvider(LLMProvider):
|
|
| 58 |
return None
|
| 59 |
|
| 60 |
def validate_model(self) -> bool:
|
| 61 |
-
"""Validate if the model is available"""
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
try:
|
| 64 |
-
# Simple connectivity check
|
| 65 |
self.client.models.list()
|
|
|
|
|
|
|
| 66 |
return True
|
| 67 |
except Exception as e:
|
| 68 |
logger.warning(f"Hugging Face model validation failed: {e}")
|
|
@@ -72,7 +82,7 @@ class HuggingFaceProvider(LLMProvider):
|
|
| 72 |
"""Implementation of synchronous generation with proper configuration and context injection"""
|
| 73 |
# Inject context message with current time/date/weather
|
| 74 |
current_time = datetime.now().strftime("%A, %B %d, %Y at %I:%M %p")
|
| 75 |
-
weather_summary =
|
| 76 |
context_msg = {
|
| 77 |
"role": "system",
|
| 78 |
"content": f"[Current Context: {current_time} | Weather: {weather_summary}]"
|
|
@@ -114,7 +124,7 @@ class HuggingFaceProvider(LLMProvider):
|
|
| 114 |
"""Implementation of streaming generation with proper configuration and context injection"""
|
| 115 |
# Inject context message with current time/date/weather
|
| 116 |
current_time = datetime.now().strftime("%A, %B %d, %Y at %I:%M %p")
|
| 117 |
-
weather_summary =
|
| 118 |
context_msg = {
|
| 119 |
"role": "system",
|
| 120 |
"content": f"[Current Context: {current_time} | Weather: {weather_summary}]"
|
|
@@ -178,7 +188,10 @@ class HuggingFaceProvider(LLMProvider):
|
|
| 178 |
def _get_weather_summary(self) -> str:
|
| 179 |
"""Get formatted weather summary"""
|
| 180 |
try:
|
| 181 |
-
weather = weather_service.
|
|
|
|
|
|
|
|
|
|
| 182 |
if weather:
|
| 183 |
return f"{weather.get('temperature', 'N/A')}°C, {weather.get('description', 'Clear skies')}"
|
| 184 |
else:
|
|
|
|
| 15 |
OpenAI = None
|
| 16 |
|
| 17 |
class HuggingFaceProvider(LLMProvider):
|
| 18 |
+
"""Hugging Face LLM provider implementation with cached validation"""
|
| 19 |
|
| 20 |
def __init__(self, model_name: str, timeout: int = 30, max_retries: int = 3):
|
| 21 |
super().__init__(model_name, timeout, max_retries)
|
|
|
|
| 41 |
logger.error(f"Error type: {type(e)}")
|
| 42 |
raise
|
| 43 |
|
| 44 |
+
# Add caching attributes for model validation
|
| 45 |
+
self._model_validated = False
|
| 46 |
+
self._last_validation = 0
|
| 47 |
+
self._validation_cache_duration = 300 # 5 minutes
|
| 48 |
+
|
| 49 |
def generate(self, prompt: str, conversation_history: List[Dict]) -> Optional[str]:
|
| 50 |
"""Generate a response synchronously"""
|
| 51 |
try:
|
|
|
|
| 63 |
return None
|
| 64 |
|
| 65 |
def validate_model(self) -> bool:
|
| 66 |
+
"""Validate if the model is available with caching"""
|
| 67 |
+
current_time = time.time()
|
| 68 |
+
if (self._model_validated and
|
| 69 |
+
current_time - self._last_validation < self._validation_cache_duration):
|
| 70 |
+
return True
|
| 71 |
+
|
| 72 |
try:
|
|
|
|
| 73 |
self.client.models.list()
|
| 74 |
+
self._model_validated = True
|
| 75 |
+
self._last_validation = current_time
|
| 76 |
return True
|
| 77 |
except Exception as e:
|
| 78 |
logger.warning(f"Hugging Face model validation failed: {e}")
|
|
|
|
| 82 |
"""Implementation of synchronous generation with proper configuration and context injection"""
|
| 83 |
# Inject context message with current time/date/weather
|
| 84 |
current_time = datetime.now().strftime("%A, %B %d, %Y at %I:%M %p")
|
| 85 |
+
weather_summary = weather_service.get_weather_summary()
|
| 86 |
context_msg = {
|
| 87 |
"role": "system",
|
| 88 |
"content": f"[Current Context: {current_time} | Weather: {weather_summary}]"
|
|
|
|
| 124 |
"""Implementation of streaming generation with proper configuration and context injection"""
|
| 125 |
# Inject context message with current time/date/weather
|
| 126 |
current_time = datetime.now().strftime("%A, %B %d, %Y at %I:%M %p")
|
| 127 |
+
weather_summary = weather_service.get_weather_summary()
|
| 128 |
context_msg = {
|
| 129 |
"role": "system",
|
| 130 |
"content": f"[Current Context: {current_time} | Weather: {weather_summary}]"
|
|
|
|
| 188 |
def _get_weather_summary(self) -> str:
|
| 189 |
"""Get formatted weather summary"""
|
| 190 |
try:
|
| 191 |
+
weather = weather_service.get_current_weather_cached(
|
| 192 |
+
"New York",
|
| 193 |
+
ttl_hash=weather_service._get_ttl_hash(300)
|
| 194 |
+
)
|
| 195 |
if weather:
|
| 196 |
return f"{weather.get('temperature', 'N/A')}°C, {weather.get('description', 'Clear skies')}"
|
| 197 |
else:
|
core/redis_client.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
| 1 |
import redis
|
| 2 |
import logging
|
| 3 |
from typing import Optional
|
|
|
|
| 4 |
|
| 5 |
logger = logging.getLogger(__name__)
|
| 6 |
|
| 7 |
class RedisClient:
|
| 8 |
-
"""Hardcoded Redis client with non-SSL configuration"""
|
| 9 |
|
| 10 |
_instance = None
|
| 11 |
_redis_client = None
|
|
@@ -18,10 +19,11 @@ class RedisClient:
|
|
| 18 |
def __init__(self):
|
| 19 |
if not hasattr(self, '_initialized'):
|
| 20 |
self._initialized = True
|
|
|
|
| 21 |
self._connect()
|
| 22 |
|
| 23 |
def _connect(self):
|
| 24 |
-
"""Establish Redis connection without SSL"""
|
| 25 |
logger.info("=== Redis Connection (Non-SSL) ===")
|
| 26 |
host = 'redis-16717.c85.us-east-1-2.ec2.redns.redis-cloud.com'
|
| 27 |
port = 16717
|
|
@@ -36,20 +38,23 @@ class RedisClient:
|
|
| 36 |
logger.info("==============================")
|
| 37 |
|
| 38 |
try:
|
| 39 |
-
logger.info("Creating Redis
|
| 40 |
-
self.
|
| 41 |
host=host,
|
| 42 |
port=port,
|
| 43 |
username=username,
|
| 44 |
password=password,
|
| 45 |
decode_responses=True,
|
|
|
|
| 46 |
socket_connect_timeout=15,
|
| 47 |
socket_timeout=15,
|
| 48 |
health_check_interval=30,
|
| 49 |
retry_on_timeout=True
|
| 50 |
-
# NO SSL PARAMETERS
|
| 51 |
)
|
| 52 |
|
|
|
|
|
|
|
|
|
|
| 53 |
logger.info("Attempting to ping Redis...")
|
| 54 |
result = self._redis_client.ping()
|
| 55 |
logger.info(f"✅ Ping successful: {result}")
|
|
|
|
| 1 |
import redis
|
| 2 |
import logging
|
| 3 |
from typing import Optional
|
| 4 |
+
from redis import ConnectionPool
|
| 5 |
|
| 6 |
logger = logging.getLogger(__name__)
|
| 7 |
|
| 8 |
class RedisClient:
|
| 9 |
+
"""Hardcoded Redis client with non-SSL configuration and connection pooling"""
|
| 10 |
|
| 11 |
_instance = None
|
| 12 |
_redis_client = None
|
|
|
|
| 19 |
def __init__(self):
|
| 20 |
if not hasattr(self, '_initialized'):
|
| 21 |
self._initialized = True
|
| 22 |
+
self.pool = None
|
| 23 |
self._connect()
|
| 24 |
|
| 25 |
def _connect(self):
|
| 26 |
+
"""Establish Redis connection without SSL using connection pooling"""
|
| 27 |
logger.info("=== Redis Connection (Non-SSL) ===")
|
| 28 |
host = 'redis-16717.c85.us-east-1-2.ec2.redns.redis-cloud.com'
|
| 29 |
port = 16717
|
|
|
|
| 38 |
logger.info("==============================")
|
| 39 |
|
| 40 |
try:
|
| 41 |
+
logger.info("Creating Redis connection pool...")
|
| 42 |
+
self.pool = ConnectionPool(
|
| 43 |
host=host,
|
| 44 |
port=port,
|
| 45 |
username=username,
|
| 46 |
password=password,
|
| 47 |
decode_responses=True,
|
| 48 |
+
max_connections=20,
|
| 49 |
socket_connect_timeout=15,
|
| 50 |
socket_timeout=15,
|
| 51 |
health_check_interval=30,
|
| 52 |
retry_on_timeout=True
|
|
|
|
| 53 |
)
|
| 54 |
|
| 55 |
+
logger.info("Creating Redis client with connection pool...")
|
| 56 |
+
self._redis_client = redis.Redis(connection_pool=self.pool)
|
| 57 |
+
|
| 58 |
logger.info("Attempting to ping Redis...")
|
| 59 |
result = self._redis_client.ping()
|
| 60 |
logger.info(f"✅ Ping successful: {result}")
|
core/session.py
CHANGED
|
@@ -10,7 +10,7 @@ logging.basicConfig(level=logging.INFO)
|
|
| 10 |
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
class SessionManager:
|
| 13 |
-
"""Manages user sessions and conversation context"""
|
| 14 |
|
| 15 |
def __init__(self, session_timeout: int = 3600):
|
| 16 |
"""Initialize session manager
|
|
@@ -55,7 +55,7 @@ class SessionManager:
|
|
| 55 |
return self._create_new_session()
|
| 56 |
|
| 57 |
def update_session(self, user_id: str, data: Dict[str, Any]) -> bool:
|
| 58 |
-
"""Update user session data
|
| 59 |
Args:
|
| 60 |
user_id: Unique identifier for the user
|
| 61 |
data: Data to update in the session
|
|
@@ -63,31 +63,32 @@ class SessionManager:
|
|
| 63 |
Boolean indicating success
|
| 64 |
"""
|
| 65 |
try:
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
|
| 70 |
session.update(data)
|
| 71 |
session['last_activity'] = time.time()
|
| 72 |
|
| 73 |
-
#
|
|
|
|
|
|
|
|
|
|
| 74 |
redis_data = {}
|
| 75 |
for key, value in session.items():
|
| 76 |
if isinstance(value, (list, dict)):
|
| 77 |
redis_data[key] = json.dumps(value, default=str)
|
| 78 |
-
elif isinstance(value, (int, float, str, bool)):
|
| 79 |
-
redis_data[key] = value
|
| 80 |
else:
|
| 81 |
-
redis_data[key] =
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
if result:
|
| 86 |
-
logger.debug(f"Successfully updated session for user {user_id}")
|
| 87 |
-
else:
|
| 88 |
-
logger.warning(f"Failed to save session for user {user_id}")
|
| 89 |
|
| 90 |
-
|
|
|
|
| 91 |
except Exception as e:
|
| 92 |
logger.error(f"Error updating session for user {user_id}: {e}")
|
| 93 |
return False
|
|
|
|
| 10 |
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
class SessionManager:
|
| 13 |
+
"""Manages user sessions and conversation context with optimized operations"""
|
| 14 |
|
| 15 |
def __init__(self, session_timeout: int = 3600):
|
| 16 |
"""Initialize session manager
|
|
|
|
| 55 |
return self._create_new_session()
|
| 56 |
|
| 57 |
def update_session(self, user_id: str, data: Dict[str, Any]) -> bool:
|
| 58 |
+
"""Update user session data using Redis pipelining for efficiency
|
| 59 |
Args:
|
| 60 |
user_id: Unique identifier for the user
|
| 61 |
data: Data to update in the session
|
|
|
|
| 63 |
Boolean indicating success
|
| 64 |
"""
|
| 65 |
try:
|
| 66 |
+
from core.redis_client import redis_client
|
| 67 |
+
client = redis_client.get_client()
|
| 68 |
+
if not client:
|
| 69 |
+
logger.error("Redis client not available")
|
| 70 |
+
return False
|
| 71 |
|
| 72 |
+
session = self.get_session(user_id)
|
| 73 |
session.update(data)
|
| 74 |
session['last_activity'] = time.time()
|
| 75 |
|
| 76 |
+
# Use pipeline for batch operations
|
| 77 |
+
pipe = client.pipeline()
|
| 78 |
+
|
| 79 |
+
# Smart serialization - only serialize complex data types
|
| 80 |
redis_data = {}
|
| 81 |
for key, value in session.items():
|
| 82 |
if isinstance(value, (list, dict)):
|
| 83 |
redis_data[key] = json.dumps(value, default=str)
|
|
|
|
|
|
|
| 84 |
else:
|
| 85 |
+
redis_data[key] = value
|
| 86 |
+
|
| 87 |
+
pipe.hset(f"user:{user_id}", mapping=redis_data)
|
| 88 |
+
pipe.execute()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
+
logger.debug(f"Successfully updated session for user {user_id}")
|
| 91 |
+
return True
|
| 92 |
except Exception as e:
|
| 93 |
logger.error(f"Error updating session for user {user_id}: {e}")
|
| 94 |
return False
|
services/weather.py
CHANGED
|
@@ -2,14 +2,25 @@ import requests
|
|
| 2 |
import os
|
| 3 |
from typing import Optional, Dict, Any
|
| 4 |
from utils.config import config
|
|
|
|
|
|
|
| 5 |
|
| 6 |
class WeatherService:
|
| 7 |
-
"""Service for fetching weather information"""
|
| 8 |
|
| 9 |
def __init__(self):
|
| 10 |
self.api_key = config.openweather_api_key
|
| 11 |
self.base_url = "http://api.openweathermap.org/data/2.5"
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
def get_current_weather(self, city: str) -> Optional[Dict[str, Any]]:
|
| 14 |
"""Get current weather for a city"""
|
| 15 |
if not self.api_key:
|
|
@@ -86,9 +97,12 @@ class WeatherService:
|
|
| 86 |
return None
|
| 87 |
|
| 88 |
def get_weather_summary(self, city="New York") -> str:
|
| 89 |
-
"""Get formatted weather summary"""
|
| 90 |
try:
|
| 91 |
-
weather = self.
|
|
|
|
|
|
|
|
|
|
| 92 |
if weather:
|
| 93 |
return f"{weather.get('temperature', 'N/A')}°C, {weather.get('description', 'Clear skies')}"
|
| 94 |
else:
|
|
|
|
| 2 |
import os
|
| 3 |
from typing import Optional, Dict, Any
|
| 4 |
from utils.config import config
|
| 5 |
+
from functools import lru_cache
|
| 6 |
+
import time
|
| 7 |
|
| 8 |
class WeatherService:
|
| 9 |
+
"""Service for fetching weather information with caching"""
|
| 10 |
|
| 11 |
def __init__(self):
|
| 12 |
self.api_key = config.openweather_api_key
|
| 13 |
self.base_url = "http://api.openweathermap.org/data/2.5"
|
| 14 |
|
| 15 |
+
def _get_ttl_hash(self, seconds=300):
|
| 16 |
+
"""Helper function to invalidate cache periodically"""
|
| 17 |
+
return round(time.time() / seconds)
|
| 18 |
+
|
| 19 |
+
@lru_cache(maxsize=128)
|
| 20 |
+
def get_current_weather_cached(self, city: str, ttl_hash=None):
|
| 21 |
+
"""Cached version of weather API calls"""
|
| 22 |
+
return self.get_current_weather(city)
|
| 23 |
+
|
| 24 |
def get_current_weather(self, city: str) -> Optional[Dict[str, Any]]:
|
| 25 |
"""Get current weather for a city"""
|
| 26 |
if not self.api_key:
|
|
|
|
| 97 |
return None
|
| 98 |
|
| 99 |
def get_weather_summary(self, city="New York") -> str:
|
| 100 |
+
"""Get formatted weather summary with caching"""
|
| 101 |
try:
|
| 102 |
+
weather = self.get_current_weather_cached(
|
| 103 |
+
city,
|
| 104 |
+
ttl_hash=self._get_ttl_hash(300)
|
| 105 |
+
)
|
| 106 |
if weather:
|
| 107 |
return f"{weather.get('temperature', 'N/A')}°C, {weather.get('description', 'Clear skies')}"
|
| 108 |
else:
|
utils/config.py
CHANGED
|
@@ -4,28 +4,42 @@ import urllib.parse
|
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
|
| 6 |
class Config:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
def __init__(self):
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
def _sanitize_url(self, url: str) -> str:
|
| 31 |
"""Sanitize URL by removing whitespace and control characters"""
|
|
|
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
|
| 6 |
class Config:
|
| 7 |
+
"""Singleton configuration class with thread-safe implementation"""
|
| 8 |
+
|
| 9 |
+
_instance = None
|
| 10 |
+
_initialized = False
|
| 11 |
+
|
| 12 |
+
def __new__(cls):
|
| 13 |
+
if cls._instance is None:
|
| 14 |
+
cls._instance = super(Config, cls).__new__(cls)
|
| 15 |
+
return cls._instance
|
| 16 |
+
|
| 17 |
def __init__(self):
|
| 18 |
+
if not self._initialized:
|
| 19 |
+
load_dotenv()
|
| 20 |
+
|
| 21 |
+
# Detect if running on HF Spaces
|
| 22 |
+
self.is_hf_space = bool(os.getenv("SPACE_ID"))
|
| 23 |
+
|
| 24 |
+
# API tokens
|
| 25 |
+
self.hf_token = os.getenv("HF_TOKEN")
|
| 26 |
+
self.openai_api_key = os.getenv("OPENAI_API_KEY")
|
| 27 |
+
self.nasa_api_key = os.getenv("NASA_API_KEY")
|
| 28 |
+
|
| 29 |
+
# API endpoints
|
| 30 |
+
self.hf_api_url = self._sanitize_url(os.getenv("HF_API_ENDPOINT_URL", "https://api-inference.huggingface.co/v1/"))
|
| 31 |
+
|
| 32 |
+
# Fallback settings
|
| 33 |
+
self.use_fallback = os.getenv("USE_FALLBACK", "true").lower() == "true"
|
| 34 |
+
|
| 35 |
+
# Local model configuration
|
| 36 |
+
self.local_model_name = os.getenv("LOCAL_MODEL_NAME", "mistral:latest")
|
| 37 |
+
self.ollama_host = self._sanitize_url(os.getenv("OLLAMA_HOST", ""))
|
| 38 |
+
|
| 39 |
+
# OpenWeather API
|
| 40 |
+
self.openweather_api_key = os.getenv("OPENWEATHER_API_KEY")
|
| 41 |
+
|
| 42 |
+
self._initialized = True
|
| 43 |
|
| 44 |
def _sanitize_url(self, url: str) -> str:
|
| 45 |
"""Sanitize URL by removing whitespace and control characters"""
|