rdune71 commited on
Commit
28471a4
·
1 Parent(s): e0ec429

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 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
- # Fallback to HF if available
404
- if config.hf_token and not ai_response:
405
- status_placeholder.info("⚡ Initializing HF Endpoint (2–4 minutes)...")
406
-
407
- try:
408
- # Check HF status first
409
- hf_status = hf_monitor.check_endpoint_status()
410
- if not hf_status['available']:
411
- status_placeholder.info(personality.get_initializing_message())
412
-
413
- ai_response = send_to_hf(validated_input, conversation_history)
414
 
415
- if ai_response:
416
- response_placeholder.markdown(ai_response)
417
- status_placeholder.success("✅ HF response received!")
418
- else:
419
- status_placeholder.error(" No response from HF")
420
-
421
- except Exception as hf_error:
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
- # Moved finally block to proper location
466
- st.session_state.is_processing = False
467
- time.sleep(0.5) # Brief pause
468
- st.experimental_rerun()
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
- round(sum(len(msg.get("content", "")) for msg in ai_messages) / len(ai_messages)) if ai_messages else 0)
613
- col3.metric("Topics Discussed",
614
- len(set(["life", "goal", "health", "career"]) & set(" ".join([msg.get("content", "") for msg in conversation]).lower().split())))
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.get_current_weather(location)
 
 
 
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
- # For Hugging Face endpoints, we'll assume the model is valid if we can connect
 
 
 
 
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 = self._get_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 = self._get_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.get_current_weather("New York")
 
 
 
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 client without SSL...")
40
- self._redis_client = redis.Redis(
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
- # Get existing session
67
- session = self.get_session(user_id)
 
 
 
68
 
69
- # Update with new data
70
  session.update(data)
71
  session['last_activity'] = time.time()
72
 
73
- # Convert complex data types to strings for Redis
 
 
 
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] = str(value)
82
-
83
- # Save updated session
84
- result = save_user_state(user_id, redis_data)
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
- return result
 
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.get_current_weather(city)
 
 
 
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
- load_dotenv()
9
-
10
- # Detect if running on HF Spaces
11
- self.is_hf_space = bool(os.getenv("SPACE_ID"))
12
-
13
- # API tokens
14
- self.hf_token = os.getenv("HF_TOKEN")
15
- self.openai_api_key = os.getenv("OPENAI_API_KEY")
16
-
17
- # API endpoints
18
- self.hf_api_url = self._sanitize_url(os.getenv("HF_API_ENDPOINT_URL", "https://api-inference.huggingface.co/v1/"))
19
-
20
- # Fallback settings
21
- self.use_fallback = os.getenv("USE_FALLBACK", "true").lower() == "true"
22
-
23
- # Local model configuration
24
- self.local_model_name = os.getenv("LOCAL_MODEL_NAME", "mistral:latest")
25
- self.ollama_host = self._sanitize_url(os.getenv("OLLAMA_HOST", ""))
26
-
27
- # OpenWeather API
28
- self.openweather_api_key = os.getenv("OPENWEATHER_API_KEY")
 
 
 
 
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"""