rdune71 commited on
Commit
34a92ea
·
1 Parent(s): dc8760b

Fix Redis connection issues with proper SSL configuration and enhanced error handling

Browse files
Files changed (3) hide show
  1. README.md +74 -60
  2. core/redis_client.py +16 -20
  3. test_redis_connection.py +57 -0
README.md CHANGED
@@ -31,7 +31,7 @@ Your personal AI-powered life coaching assistant.
31
 
32
  ## Requirements
33
 
34
- All requirements are specified in `requirements.txt`. The app automatically handles:
35
  - Streamlit UI
36
  - FastAPI backend (for future expansion)
37
  - Redis connection for persistent memory
@@ -39,30 +39,30 @@ All requirements are specified in `requirements.txt`. The app automatically hand
39
 
40
  ## Environment Variables
41
 
42
- Configure these in your Hugging Face Space secrets or local `.env` file:
43
 
44
- - `OLLAMA_HOST`: Your Ollama server URL (default: ngrok URL)
45
- - `LOCAL_MODEL_NAME`: Default model name (default: mistral)
46
- - `HF_TOKEN`: Hugging Face API token (for Hugging Face models)
47
- - `HF_API_ENDPOINT_URL`: Hugging Face inference API endpoint
48
- - `USE_FALLBACK`: Whether to use fallback providers (true/false)
49
- - `REDIS_HOST`: Redis server hostname (default: localhost)
50
- - `REDIS_PORT`: Redis server port (default: 6379)
51
- - `REDIS_USERNAME`: Redis username (optional)
52
- - `REDIS_PASSWORD`: Redis password (optional)
53
 
54
  ## Provider Details
55
 
56
  ### Ollama (Primary Local Provider)
57
 
58
- **Setup:**
59
  1. Install Ollama: https://ollama.com/download
60
- 2. Pull a model: `ollama pull mistral`
61
- 3. Start server: `ollama serve`
62
- 4. Configure ngrok: `ngrok http 11434`
63
- 5. Set `OLLAMA_HOST` to your ngrok URL
64
 
65
- **Advantages:**
66
  - No cost for inference
67
  - Full control over models
68
  - Fast response times
@@ -70,75 +70,89 @@ Configure these in your Hugging Face Space secrets or local `.env` file:
70
 
71
  ### Hugging Face Inference API (Fallback)
72
 
73
- **Current Endpoint:** `https://zxzbfrlg3ssrk7d9.us-east-1.aws.endpoints.huggingface.cloud`
74
 
75
- **Important Scaling Behavior:**
76
- - ⚠️ **Scale-to-Zero**: Endpoint automatically scales to zero after 15 minutes of inactivity
77
- - ⏱️ **Cold Start**: Takes approximately 4 minutes to initialize when first requested
78
- - 🔄 **Automatic Wake-up**: Sending any request will automatically start the endpoint
79
- - 💰 **Cost**: \$0.536/hour while running (not billed when scaled to zero)
80
- - 📍 **Location**: AWS us-east-1 (Intel Sapphire Rapids, 16vCPUs, 32GB RAM)
81
 
82
- **Handling 503 Errors:**
83
  When using the Hugging Face fallback, you may encounter 503 errors initially. This indicates the endpoint is initializing. Simply retry your request after 30-60 seconds, or wait for the initialization to complete (typically 4 minutes).
84
 
85
- **Model:** OpenAI GPT OSS 20B (Uncensored variant)
86
 
87
  ### OpenAI (Alternative Fallback)
88
 
89
- Configure with `OPENAI_API_KEY` environment variable.
90
 
91
  ## Switching Between Providers
92
 
93
  ### For Local Development (Windows/Ollama):
94
 
95
- 1. **Install Ollama:**
96
- ```bash
97
  # Download from https://ollama.com/download/OllamaSetup.exe
98
- Pull and run models:
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- ollama pull mistral
101
- ollama pull llama3
102
- ollama serve
103
- Start ngrok tunnel:
104
-
105
- ngrok http 11434
106
- Update environment variables:
107
-
108
- OLLAMA_HOST=https://your-ngrok-url.ngrok-free.app
109
- LOCAL_MODEL_NAME=mistral
110
- USE_FALLBACK=false
111
  For Production Deployment:
112
- The application automatically handles provider fallback:
113
 
 
114
  Primary: Ollama (via ngrok)
115
  Secondary: Hugging Face Inference API
116
  Tertiary: OpenAI (if configured)
 
117
  Architecture
 
118
  This application consists of:
 
 
 
 
 
119
 
120
- Streamlit frontend (app.py)
121
- Core LLM abstraction (core/llm.py)
122
- Memory management (core/memory.py)
123
- Configuration management (utils/config.py)
124
- API endpoints (in api/ directory for future expansion)
125
  Built with Python, Streamlit, FastAPI, and Redis.
126
 
127
- Troubleshooting Common Issues:
 
128
  503 Errors with Hugging Face Fallback:
129
- Wait 4 minutes for cold start initialization
130
- Retry request after endpoint warms up
 
131
  Ollama Connection Issues:
132
- Verify ollama serve is running locally
133
- Check ngrok tunnel status
134
- Confirm ngrok URL matches OLLAMA_HOST
135
- Test with test_ollama_connection.py
 
136
  Redis Connection Problems:
137
- Set USE_FALLBACK=true to disable Redis requirement
138
- Or configure proper Redis credentials
 
 
 
 
 
 
139
  Model Not Found:
140
- Pull required model: ollama pull <model-name>
141
- Check available models: ollama list
 
142
  Diagnostic Scripts:
143
- Run python test_ollama_connection.py to verify Ollama connectivity.
144
- Run python diagnose_ollama.py for detailed connection diagnostics.
 
 
31
 
32
  ## Requirements
33
 
34
+ All requirements are specified in requirements.txt. The app automatically handles:
35
  - Streamlit UI
36
  - FastAPI backend (for future expansion)
37
  - Redis connection for persistent memory
 
39
 
40
  ## Environment Variables
41
 
42
+ Configure these in your Hugging Face Space secrets or local .env file:
43
 
44
+ - OLLAMA_HOST: Your Ollama server URL (default: ngrok URL)
45
+ - LOCAL_MODEL_NAME: Default model name (default: mistral)
46
+ - HF_TOKEN: Hugging Face API token (for Hugging Face models)
47
+ - HF_API_ENDPOINT_URL: Hugging Face inference API endpoint
48
+ - USE_FALLBACK: Whether to use fallback providers (true/false)
49
+ - REDIS_HOST: Redis server hostname (default: localhost)
50
+ - REDIS_PORT: Redis server port (default: 6379)
51
+ - REDIS_USERNAME: Redis username (optional)
52
+ - REDIS_PASSWORD: Redis password (optional)
53
 
54
  ## Provider Details
55
 
56
  ### Ollama (Primary Local Provider)
57
 
58
+ Setup:
59
  1. Install Ollama: https://ollama.com/download
60
+ 2. Pull a model: ollama pull mistral
61
+ 3. Start server: ollama serve
62
+ 4. Configure ngrok: ngrok http 11434
63
+ 5. Set OLLAMA_HOST to your ngrok URL
64
 
65
+ Advantages:
66
  - No cost for inference
67
  - Full control over models
68
  - Fast response times
 
70
 
71
  ### Hugging Face Inference API (Fallback)
72
 
73
+ Current Endpoint: https://zxzbfrlg3ssrk7d9.us-east-1.aws.endpoints.huggingface.cloud
74
 
75
+ Important Scaling Behavior:
76
+ - ⚠️ Scale-to-Zero: Endpoint automatically scales to zero after 15 minutes of inactivity
77
+ - ⏱️ Cold Start: Takes approximately 4 minutes to initialize when first requested
78
+ - 🔄 Automatic Wake-up: Sending any request will automatically start the endpoint
79
+ - 💰 Cost: $0.536/hour while running (not billed when scaled to zero)
80
+ - 📍 Location: AWS us-east-1 (Intel Sapphire Rapids, 16vCPUs, 32GB RAM)
81
 
82
+ Handling 503 Errors:
83
  When using the Hugging Face fallback, you may encounter 503 errors initially. This indicates the endpoint is initializing. Simply retry your request after 30-60 seconds, or wait for the initialization to complete (typically 4 minutes).
84
 
85
+ Model: OpenAI GPT OSS 20B (Uncensored variant)
86
 
87
  ### OpenAI (Alternative Fallback)
88
 
89
+ Configure with OPENAI_API_KEY environment variable.
90
 
91
  ## Switching Between Providers
92
 
93
  ### For Local Development (Windows/Ollama):
94
 
95
+ 1. Install Ollama:
96
+ bash
97
  # Download from https://ollama.com/download/OllamaSetup.exe
98
+
99
+ Pull and run models:
100
+ ollama pull mistral
101
+ ollama pull llama3
102
+ ollama serve
103
+
104
+ Start ngrok tunnel:
105
+ ngrok http 11434
106
+
107
+ Update environment variables:
108
+ OLLAMA_HOST=https://your-ngrok-url.ngrok-free.app
109
+ LOCAL_MODEL_NAME=mistral
110
+ USE_FALLBACK=false
111
 
 
 
 
 
 
 
 
 
 
 
 
112
  For Production Deployment:
 
113
 
114
+ The application automatically handles provider fallback:
115
  Primary: Ollama (via ngrok)
116
  Secondary: Hugging Face Inference API
117
  Tertiary: OpenAI (if configured)
118
+
119
  Architecture
120
+
121
  This application consists of:
122
+ - Streamlit frontend (app.py)
123
+ - Core LLM abstraction (core/llm.py)
124
+ - Memory management (core/memory.py)
125
+ - Configuration management (utils/config.py)
126
+ - API endpoints (in api/ directory for future expansion)
127
 
 
 
 
 
 
128
  Built with Python, Streamlit, FastAPI, and Redis.
129
 
130
+ ## Troubleshooting Common Issues
131
+
132
  503 Errors with Hugging Face Fallback:
133
+ - Wait 4 minutes for cold start initialization
134
+ - Retry request after endpoint warms up
135
+
136
  Ollama Connection Issues:
137
+ - Verify ollama serve is running locally
138
+ - Check ngrok tunnel status
139
+ - Confirm ngrok URL matches OLLAMA_HOST
140
+ - Test with test_ollama_connection.py
141
+
142
  Redis Connection Problems:
143
+ - Set USE_FALLBACK=true to disable Redis requirement
144
+ - Or configure proper Redis credentials:
145
+ REDIS_HOST=redis-10296.c245.us-east-1-3.ec2.redns.redis-cloud.com
146
+ REDIS_PORT=10296
147
+ REDIS_USERNAME=default
148
+ REDIS_PASSWORD=your_password_here
149
+ REDIS_DISABLE_SSL=false
150
+
151
  Model Not Found:
152
+ - Pull required model: ollama pull <model-name>
153
+ - Check available models: ollama list
154
+
155
  Diagnostic Scripts:
156
+ - Run python test_ollama_connection.py to verify Ollama connectivity.
157
+ - Run python diagnose_ollama.py for detailed connection diagnostics.
158
+ - Run python test_redis_connection.py to verify Redis connectivity.
core/redis_client.py CHANGED
@@ -9,20 +9,19 @@ logger = logging.getLogger(__name__)
9
 
10
  class RedisClient:
11
  """Enhanced Redis client with proper connection handling"""
12
-
13
  _instance = None
14
  _redis_client = None
15
-
16
  def __new__(cls):
17
  if cls._instance is None:
18
  cls._instance = super(RedisClient, cls).__new__(cls)
19
  return cls._instance
20
-
21
  def __init__(self):
22
  if not hasattr(self, '_initialized'):
23
  self._initialized = True
24
  self._connect()
25
-
26
  def _connect(self):
27
  """Establish Redis connection with proper error handling for Redis Cloud"""
28
  logger.info(f"Attempting Redis connection with:")
@@ -31,7 +30,7 @@ class RedisClient:
31
  logger.info(f" Username: {'SET' if config.redis_username else 'NOT SET'}")
32
  logger.info(f" Password: {'SET' if config.redis_password else 'NOT SET'}")
33
  logger.info(f" SSL Disabled: {config.redis_disable_ssl}")
34
-
35
  if not config.redis_host or config.redis_host == "localhost":
36
  logger.info("Redis not configured, skipping connection")
37
  return None
@@ -49,12 +48,11 @@ class RedisClient:
49
  username=config.redis_username if config.redis_username else "default",
50
  password=config.redis_password,
51
  decode_responses=True,
52
- socket_connect_timeout=10,
53
- socket_timeout=10,
54
  health_check_interval=30,
55
  retry_on_timeout=True
56
  )
57
-
58
  self._redis_client.ping()
59
  logger.info("Successfully connected to Redis without SSL")
60
  return
@@ -72,31 +70,29 @@ class RedisClient:
72
  username=config.redis_username if config.redis_username else "default",
73
  password=config.redis_password,
74
  decode_responses=True,
75
- socket_connect_timeout=10,
76
- socket_timeout=10,
77
  ssl=True,
78
- ssl_cert_reqs=None, # Important for Redis Cloud
79
  health_check_interval=30,
80
  retry_on_timeout=True
81
  )
82
-
83
  self._redis_client.ping()
84
  logger.info("Successfully connected to Redis Cloud with SSL")
85
  return
86
-
87
  except Exception as e:
88
  logger.error(f"Redis Cloud SSL connection failed: {e}")
89
  self._redis_client = None
90
-
91
  def _parse_host_port(self, host_string: str, default_port: int) -> tuple:
92
  """Parse host and port from host string"""
93
  if not host_string:
94
  return "localhost", default_port
95
-
96
  # Remove any whitespace and control characters
97
  host_string = host_string.strip()
98
  host_string = re.sub(r'[\r\n\t\0]+', '', host_string)
99
-
100
  # Handle case where port is included in REDIS_HOST (e.g., "host:port")
101
  if ':' in host_string and not host_string.startswith('['): # Not IPv6
102
  # Check if the part after : is a valid port number
@@ -113,13 +109,13 @@ class RedisClient:
113
  except ValueError:
114
  # Port is not a valid integer, use default
115
  return host_string, default_port
116
-
117
  return host_string, default_port
118
-
119
  def get_client(self) -> Optional[redis.Redis]:
120
  """Get Redis client instance"""
121
  return self._redis_client
122
-
123
  def is_healthy(self) -> bool:
124
  """Check if Redis connection is healthy"""
125
  if not self._redis_client:
@@ -129,7 +125,7 @@ class RedisClient:
129
  return True
130
  except:
131
  return False
132
-
133
  def reconnect(self):
134
  """Reconnect to Redis"""
135
  self._connect()
 
9
 
10
  class RedisClient:
11
  """Enhanced Redis client with proper connection handling"""
 
12
  _instance = None
13
  _redis_client = None
14
+
15
  def __new__(cls):
16
  if cls._instance is None:
17
  cls._instance = super(RedisClient, cls).__new__(cls)
18
  return cls._instance
19
+
20
  def __init__(self):
21
  if not hasattr(self, '_initialized'):
22
  self._initialized = True
23
  self._connect()
24
+
25
  def _connect(self):
26
  """Establish Redis connection with proper error handling for Redis Cloud"""
27
  logger.info(f"Attempting Redis connection with:")
 
30
  logger.info(f" Username: {'SET' if config.redis_username else 'NOT SET'}")
31
  logger.info(f" Password: {'SET' if config.redis_password else 'NOT SET'}")
32
  logger.info(f" SSL Disabled: {config.redis_disable_ssl}")
33
+
34
  if not config.redis_host or config.redis_host == "localhost":
35
  logger.info("Redis not configured, skipping connection")
36
  return None
 
48
  username=config.redis_username if config.redis_username else "default",
49
  password=config.redis_password,
50
  decode_responses=True,
51
+ socket_connect_timeout=15,
52
+ socket_timeout=15,
53
  health_check_interval=30,
54
  retry_on_timeout=True
55
  )
 
56
  self._redis_client.ping()
57
  logger.info("Successfully connected to Redis without SSL")
58
  return
 
70
  username=config.redis_username if config.redis_username else "default",
71
  password=config.redis_password,
72
  decode_responses=True,
73
+ socket_connect_timeout=15,
74
+ socket_timeout=15,
75
  ssl=True,
76
+ ssl_cert_reqs='required', # Proper SSL certificate validation
77
  health_check_interval=30,
78
  retry_on_timeout=True
79
  )
 
80
  self._redis_client.ping()
81
  logger.info("Successfully connected to Redis Cloud with SSL")
82
  return
 
83
  except Exception as e:
84
  logger.error(f"Redis Cloud SSL connection failed: {e}")
85
  self._redis_client = None
86
+
87
  def _parse_host_port(self, host_string: str, default_port: int) -> tuple:
88
  """Parse host and port from host string"""
89
  if not host_string:
90
  return "localhost", default_port
91
+
92
  # Remove any whitespace and control characters
93
  host_string = host_string.strip()
94
  host_string = re.sub(r'[\r\n\t\0]+', '', host_string)
95
+
96
  # Handle case where port is included in REDIS_HOST (e.g., "host:port")
97
  if ':' in host_string and not host_string.startswith('['): # Not IPv6
98
  # Check if the part after : is a valid port number
 
109
  except ValueError:
110
  # Port is not a valid integer, use default
111
  return host_string, default_port
112
+
113
  return host_string, default_port
114
+
115
  def get_client(self) -> Optional[redis.Redis]:
116
  """Get Redis client instance"""
117
  return self._redis_client
118
+
119
  def is_healthy(self) -> bool:
120
  """Check if Redis connection is healthy"""
121
  if not self._redis_client:
 
125
  return True
126
  except:
127
  return False
128
+
129
  def reconnect(self):
130
  """Reconnect to Redis"""
131
  self._connect()
test_redis_connection.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ from pathlib import Path
4
+
5
+ # Add project root to path
6
+ project_root = Path(__file__).parent
7
+ sys.path.append(str(project_root))
8
+
9
+ from core.redis_client import redis_client
10
+ from utils.config import config
11
+
12
+ def test_redis_connection():
13
+ """Test Redis connection with current configuration"""
14
+ print("Testing Redis connection...")
15
+ print(f"REDIS_HOST: {config.redis_host}")
16
+ print(f"REDIS_PORT: {config.redis_port}")
17
+ print(f"REDIS_USERNAME: {config.redis_username}")
18
+ print(f"REDIS_DISABLE_SSL: {config.redis_disable_ssl}")
19
+
20
+ # Initialize Redis client
21
+ client = redis_client.get_client()
22
+
23
+ if client is None:
24
+ print("❌ Redis client is None - connection failed")
25
+ return False
26
+
27
+ try:
28
+ # Test ping
29
+ result = client.ping()
30
+ print(f"✅ Ping result: {result}")
31
+
32
+ # Test basic set/get
33
+ test_key = "redis_test_key"
34
+ test_value = "redis_test_value"
35
+
36
+ client.set(test_key, test_value)
37
+ retrieved_value = client.get(test_key)
38
+
39
+ if retrieved_value == test_value:
40
+ print("✅ Set/Get test successful")
41
+ # Clean up
42
+ client.delete(test_key)
43
+ else:
44
+ print("❌ Set/Get test failed")
45
+
46
+ return True
47
+ except Exception as e:
48
+ print(f"❌ Redis operation failed: {e}")
49
+ return False
50
+
51
+ if __name__ == "__main__":
52
+ success = test_redis_connection()
53
+ if success:
54
+ print("\n🎉 Redis connection test passed!")
55
+ else:
56
+ print("\n💥 Redis connection test failed!")
57
+ sys.exit(1)