rdune71 commited on
Commit
b127732
·
1 Parent(s): 75f72a7

Fix critical LLM provider bugs, Redis config, and Streamlit null safety issues

Browse files
Files changed (8) hide show
  1. README.md +0 -39
  2. api/main.py +10 -0
  3. app.py +37 -24
  4. core/llm.py +11 -13
  5. ngrok.yml.txt +9 -0
  6. test_setup.py +33 -0
  7. utils/__pycache__/config.cpython-313.pyc +0 -0
  8. utils/config.py +8 -3
README.md CHANGED
@@ -1,39 +0,0 @@
1
- ---
2
- title: AI Life Coach
3
- emoji: 🧘
4
- colorFrom: blue
5
- colorTo: green
6
- sdk: streamlit
7
- sdk_version: 1.24.0
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- ---
12
-
13
- # AI Life Coach
14
-
15
- A personal development assistant powered by LLMs.
16
-
17
- ## Features
18
-
19
- - Ollama & Hugging Face models
20
- - Redis-based memory
21
- - User sessions (Rob & Sarah)
22
- - FastAPI backend
23
- - Streamlit UI
24
-
25
- ## Deployment
26
-
27
- The application is designed to work in Hugging Face Spaces environment. For local LLM inference, it connects to a remote Ollama instance via ngrok tunnel at `https://ace32bd59aef.ngrok-free.app`. This allows the application to access powerful local models without requiring them to be installed directly in the Space.
28
-
29
- In case the remote Ollama instance is unavailable, the system gracefully falls back to checking a local instance, and handles unavailability by showing appropriate status messages in the UI.
30
-
31
- ## Troubleshooting
32
-
33
- If you're experiencing connection issues with Ollama, you can run the diagnostic script:
34
-
35
- ```
36
- python diagnose_ollama.py
37
- ```
38
-
39
- This will test connectivity to your configured Ollama host and provide detailed information about any connection problems.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
api/main.py CHANGED
@@ -1,6 +1,7 @@
1
  from fastapi import FastAPI
2
  from api.status import router as status_router
3
  from api.chat import router as chat_router
 
4
 
5
  app = FastAPI()
6
 
@@ -11,3 +12,12 @@ app.include_router(chat_router, prefix="/api")
11
  @app.get("/")
12
  async def root():
13
  return {"message": "AI Life Coach API is running"}
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI
2
  from api.status import router as status_router
3
  from api.chat import router as chat_router
4
+ from core.memory import check_redis_health
5
 
6
  app = FastAPI()
7
 
 
12
  @app.get("/")
13
  async def root():
14
  return {"message": "AI Life Coach API is running"}
15
+
16
+ @app.get("/health")
17
+ async def health_check():
18
+ """Health check endpoint"""
19
+ redis_healthy = check_redis_health()
20
+ return {
21
+ "status": "healthy" if redis_healthy else "degraded",
22
+ "redis": "healthy" if redis_healthy else "unhealthy"
23
+ }
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # Force redeploy trigger - version 1.8
2
  import streamlit as st
3
  from utils.config import config
4
  import requests
@@ -12,13 +12,10 @@ st.set_page_config(page_title="AI Life Coach", page_icon="🧘", layout="centere
12
  # Initialize session state
13
  if 'ngrok_url' not in st.session_state:
14
  st.session_state.ngrok_url = config.ollama_host
15
-
16
  if 'model_status' not in st.session_state:
17
  st.session_state.model_status = "checking"
18
-
19
  if 'available_models' not in st.session_state:
20
  st.session_state.available_models = []
21
-
22
  if 'selected_model' not in st.session_state:
23
  st.session_state.selected_model = config.local_model_name
24
 
@@ -47,7 +44,7 @@ NGROK_HEADERS = {
47
  def fetch_available_models(ngrok_url):
48
  try:
49
  response = requests.get(
50
- f"{ngrok_url}/api/tags",
51
  headers=NGROK_HEADERS,
52
  timeout=5
53
  )
@@ -72,11 +69,11 @@ st.sidebar.markdown("---")
72
  st.sidebar.subheader("Model Selection")
73
  if st.session_state.available_models:
74
  selected_model = st.sidebar.selectbox(
75
- "Select Model",
76
  st.session_state.available_models,
77
- index=st.session_state.available_models.index(st.session_state.selected_model)
78
- if st.session_state.selected_model in st.session_state.available_models
79
- else 0
80
  )
81
  st.session_state.selected_model = selected_model
82
  else:
@@ -94,7 +91,7 @@ IS_HF_SPACE = bool(BASE_URL)
94
  def get_ollama_status(ngrok_url):
95
  try:
96
  response = requests.get(
97
- f"{ngrok_url}/api/tags",
98
  headers=NGROK_HEADERS,
99
  timeout=10
100
  )
@@ -106,7 +103,7 @@ def get_ollama_status(ngrok_url):
106
  if models:
107
  selected_model_available = st.session_state.selected_model in model_names
108
  return {
109
- "running": True,
110
  "model_loaded": st.session_state.selected_model if selected_model_available else model_names[0],
111
  "remote_host": ngrok_url,
112
  "available_models": model_names,
@@ -115,15 +112,23 @@ def get_ollama_status(ngrok_url):
115
  else:
116
  st.session_state.model_status = "no_models"
117
  return {
118
- "running": False,
119
  "model_loaded": None,
120
  "remote_host": ngrok_url,
121
  "message": "Connected to Ollama but no models found"
122
  }
 
 
 
 
 
 
 
 
123
  except Exception as e:
124
  st.session_state.model_status = "unreachable"
125
  return {
126
- "running": False,
127
  "model_loaded": None,
128
  "error": str(e),
129
  "remote_host": ngrok_url
@@ -139,9 +144,18 @@ def get_conversation_history(user_id):
139
  st.warning(f"Could not load conversation history: {e}")
140
  return []
141
 
142
- # Check Ollama status
143
  ollama_status = get_ollama_status(st.session_state.ngrok_url)
144
 
 
 
 
 
 
 
 
 
 
145
  # Update model status
146
  if ollama_status.get("running", False):
147
  if ollama_status.get("available_models"):
@@ -151,9 +165,13 @@ if ollama_status.get("running", False):
151
  else:
152
  st.session_state.model_status = "unreachable"
153
 
154
- # Display Ollama status
 
 
 
155
  use_fallback = not ollama_status.get("running", False) or config.use_fallback
156
 
 
157
  if use_fallback:
158
  st.sidebar.warning("🌐 Using Hugging Face fallback (Ollama not available)")
159
  if "error" in ollama_status:
@@ -213,14 +231,12 @@ def send_to_ollama(user_input, conversation_history, ngrok_url, model_name):
213
  "top_p": 0.9
214
  }
215
  }
216
-
217
  response = requests.post(
218
  f"{ngrok_url}/api/chat",
219
  json=payload,
220
  headers=NGROK_HEADERS,
221
  timeout=60
222
  )
223
-
224
  if response.status_code == 200:
225
  response_data = response.json()
226
  return response_data.get("message", {}).get("content", "")
@@ -237,7 +253,6 @@ def send_to_hf(user_input, conversation_history):
237
  try:
238
  from core.llm import LLMClient
239
  llm_client = LLMClient(provider="huggingface")
240
-
241
  # Format for HF
242
  prompt = "You are a helpful life coach. "
243
  for msg in conversation_history:
@@ -246,7 +261,6 @@ def send_to_hf(user_input, conversation_history):
246
  elif msg["role"] == "assistant":
247
  prompt += f"Assistant: {msg['content']} "
248
  prompt += "Assistant:"
249
-
250
  response = llm_client.generate(prompt, max_tokens=500, stream=False)
251
  return response
252
  except Exception as e:
@@ -268,10 +282,9 @@ if st.button("Send"):
268
  else:
269
  # Display user message
270
  st.markdown(f"**You:** {user_input}")
271
-
272
  # Prepare conversation history
273
- conversation_history = [{"role": msg["role"], "content": msg["content"]}
274
- for msg in conversation[-5:]]
275
  conversation_history.append({"role": "user", "content": user_input})
276
 
277
  # Send to appropriate backend
@@ -281,8 +294,8 @@ if st.button("Send"):
281
  backend_used = "Hugging Face"
282
  else:
283
  ai_response = send_to_ollama(
284
- user_input,
285
- conversation_history,
286
  st.session_state.ngrok_url,
287
  st.session_state.selected_model
288
  )
 
1
+ # Force redeploy trigger - version 1.9
2
  import streamlit as st
3
  from utils.config import config
4
  import requests
 
12
  # Initialize session state
13
  if 'ngrok_url' not in st.session_state:
14
  st.session_state.ngrok_url = config.ollama_host
 
15
  if 'model_status' not in st.session_state:
16
  st.session_state.model_status = "checking"
 
17
  if 'available_models' not in st.session_state:
18
  st.session_state.available_models = []
 
19
  if 'selected_model' not in st.session_state:
20
  st.session_state.selected_model = config.local_model_name
21
 
 
44
  def fetch_available_models(ngrok_url):
45
  try:
46
  response = requests.get(
47
+ f"{ngrok_url}/api/tags",
48
  headers=NGROK_HEADERS,
49
  timeout=5
50
  )
 
69
  st.sidebar.subheader("Model Selection")
70
  if st.session_state.available_models:
71
  selected_model = st.sidebar.selectbox(
72
+ "Select Model",
73
  st.session_state.available_models,
74
+ index=st.session_state.available_models.index(st.session_state.selected_model)
75
+ if st.session_state.selected_model in st.session_state.available_models
76
+ else 0
77
  )
78
  st.session_state.selected_model = selected_model
79
  else:
 
91
  def get_ollama_status(ngrok_url):
92
  try:
93
  response = requests.get(
94
+ f"{ngrok_url}/api/tags",
95
  headers=NGROK_HEADERS,
96
  timeout=10
97
  )
 
103
  if models:
104
  selected_model_available = st.session_state.selected_model in model_names
105
  return {
106
+ "running": True,
107
  "model_loaded": st.session_state.selected_model if selected_model_available else model_names[0],
108
  "remote_host": ngrok_url,
109
  "available_models": model_names,
 
112
  else:
113
  st.session_state.model_status = "no_models"
114
  return {
115
+ "running": False,
116
  "model_loaded": None,
117
  "remote_host": ngrok_url,
118
  "message": "Connected to Ollama but no models found"
119
  }
120
+ else:
121
+ st.session_state.model_status = "unreachable"
122
+ return {
123
+ "running": False,
124
+ "model_loaded": None,
125
+ "error": f"HTTP {response.status_code}",
126
+ "remote_host": ngrok_url
127
+ }
128
  except Exception as e:
129
  st.session_state.model_status = "unreachable"
130
  return {
131
+ "running": False,
132
  "model_loaded": None,
133
  "error": str(e),
134
  "remote_host": ngrok_url
 
144
  st.warning(f"Could not load conversation history: {e}")
145
  return []
146
 
147
+ # Get Ollama status with null safety
148
  ollama_status = get_ollama_status(st.session_state.ngrok_url)
149
 
150
+ # Add null safety check
151
+ if ollama_status is None:
152
+ ollama_status = {
153
+ "running": False,
154
+ "model_loaded": None,
155
+ "error": "Failed to get Ollama status",
156
+ "remote_host": st.session_state.ngrok_url
157
+ }
158
+
159
  # Update model status
160
  if ollama_status.get("running", False):
161
  if ollama_status.get("available_models"):
 
165
  else:
166
  st.session_state.model_status = "unreachable"
167
 
168
+ # Ensure ollama_status is a dict even if None
169
+ ollama_status = ollama_status or {}
170
+
171
+ # Determine if we should use fallback
172
  use_fallback = not ollama_status.get("running", False) or config.use_fallback
173
 
174
+ # Display Ollama status
175
  if use_fallback:
176
  st.sidebar.warning("🌐 Using Hugging Face fallback (Ollama not available)")
177
  if "error" in ollama_status:
 
231
  "top_p": 0.9
232
  }
233
  }
 
234
  response = requests.post(
235
  f"{ngrok_url}/api/chat",
236
  json=payload,
237
  headers=NGROK_HEADERS,
238
  timeout=60
239
  )
 
240
  if response.status_code == 200:
241
  response_data = response.json()
242
  return response_data.get("message", {}).get("content", "")
 
253
  try:
254
  from core.llm import LLMClient
255
  llm_client = LLMClient(provider="huggingface")
 
256
  # Format for HF
257
  prompt = "You are a helpful life coach. "
258
  for msg in conversation_history:
 
261
  elif msg["role"] == "assistant":
262
  prompt += f"Assistant: {msg['content']} "
263
  prompt += "Assistant:"
 
264
  response = llm_client.generate(prompt, max_tokens=500, stream=False)
265
  return response
266
  except Exception as e:
 
282
  else:
283
  # Display user message
284
  st.markdown(f"**You:** {user_input}")
285
+
286
  # Prepare conversation history
287
+ conversation_history = [{"role": msg["role"], "content": msg["content"]} for msg in conversation[-5:]]
 
288
  conversation_history.append({"role": "user", "content": user_input})
289
 
290
  # Send to appropriate backend
 
294
  backend_used = "Hugging Face"
295
  else:
296
  ai_response = send_to_ollama(
297
+ user_input,
298
+ conversation_history,
299
  st.session_state.ngrok_url,
300
  st.session_state.selected_model
301
  )
core/llm.py CHANGED
@@ -8,7 +8,6 @@ from utils.config import config
8
 
9
  class LLMProvider(ABC):
10
  """Abstract base class for all LLM providers"""
11
-
12
  def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
13
  self.model_name = model_name
14
  self.timeout = timeout
@@ -29,10 +28,9 @@ class LLMProvider(ABC):
29
  last_exception = e
30
  if attempt < self.retries:
31
  time.sleep(1 * (2 ** attempt)) # Exponential backoff
32
- continue
33
  raise last_exception
34
 
35
-
36
  class OllamaProvider(LLMProvider):
37
  def __init__(self, model_name: str, host: str = None, timeout: int = 30, retries: int = 3):
38
  super().__init__(model_name, timeout, retries)
@@ -78,10 +76,10 @@ class OllamaProvider(LLMProvider):
78
  return stream_response()
79
  else:
80
  return response.json()["response"]
81
-
 
82
  return self._retry_request(_make_request)
83
 
84
-
85
  class HuggingFaceProvider(LLMProvider):
86
  def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
87
  super().__init__(model_name, timeout, retries)
@@ -108,10 +106,10 @@ class HuggingFaceProvider(LLMProvider):
108
  return stream_response()
109
  else:
110
  return response.choices[0].message.content
111
-
 
112
  return self._retry_request(_make_request)
113
 
114
-
115
  class OpenAIProvider(LLMProvider):
116
  def __init__(self, model_name: str, api_key: str = None, timeout: int = 30, retries: int = 3):
117
  super().__init__(model_name, timeout, retries)
@@ -135,17 +133,17 @@ class OpenAIProvider(LLMProvider):
135
  return stream_response()
136
  else:
137
  return response.choices[0].message.content
138
-
 
139
  return self._retry_request(_make_request)
140
 
141
-
142
  class LLMClient:
143
  PROVIDER_MAP = {
144
  "ollama": OllamaProvider,
145
  "huggingface": HuggingFaceProvider,
146
  "openai": OpenAIProvider
147
  }
148
-
149
  def __init__(self, provider: str = "ollama", model_name: str = None, **provider_kwargs):
150
  self.provider_name = provider.lower()
151
  self.model_name = model_name or self._get_default_model()
@@ -155,7 +153,7 @@ class LLMClient:
155
 
156
  provider_class = self.PROVIDER_MAP[self.provider_name]
157
  self.provider = provider_class(self.model_name, **provider_kwargs)
158
-
159
  def _get_default_model(self) -> str:
160
  """Get default model based on provider"""
161
  defaults = {
@@ -164,11 +162,11 @@ class LLMClient:
164
  "openai": "gpt-3.5-turbo"
165
  }
166
  return defaults.get(self.provider_name, "mistral")
167
-
168
  def generate(self, prompt: str, max_tokens: int = 500, stream: bool = False) -> Union[str, Generator[str, None, None]]:
169
  """Unified generate method that delegates to provider"""
170
  return self.provider.generate(prompt, max_tokens, stream)
171
-
172
  @classmethod
173
  def get_available_providers(cls) -> list:
174
  """Return list of supported providers"""
 
8
 
9
  class LLMProvider(ABC):
10
  """Abstract base class for all LLM providers"""
 
11
  def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
12
  self.model_name = model_name
13
  self.timeout = timeout
 
28
  last_exception = e
29
  if attempt < self.retries:
30
  time.sleep(1 * (2 ** attempt)) # Exponential backoff
31
+ continue
32
  raise last_exception
33
 
 
34
  class OllamaProvider(LLMProvider):
35
  def __init__(self, model_name: str, host: str = None, timeout: int = 30, retries: int = 3):
36
  super().__init__(model_name, timeout, retries)
 
76
  return stream_response()
77
  else:
78
  return response.json()["response"]
79
+
80
+ # Fixed: Moved return outside the _make_request function
81
  return self._retry_request(_make_request)
82
 
 
83
  class HuggingFaceProvider(LLMProvider):
84
  def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
85
  super().__init__(model_name, timeout, retries)
 
106
  return stream_response()
107
  else:
108
  return response.choices[0].message.content
109
+
110
+ # Fixed: Moved return outside the _make_request function
111
  return self._retry_request(_make_request)
112
 
 
113
  class OpenAIProvider(LLMProvider):
114
  def __init__(self, model_name: str, api_key: str = None, timeout: int = 30, retries: int = 3):
115
  super().__init__(model_name, timeout, retries)
 
133
  return stream_response()
134
  else:
135
  return response.choices[0].message.content
136
+
137
+ # Fixed: Moved return outside the _make_request function
138
  return self._retry_request(_make_request)
139
 
 
140
  class LLMClient:
141
  PROVIDER_MAP = {
142
  "ollama": OllamaProvider,
143
  "huggingface": HuggingFaceProvider,
144
  "openai": OpenAIProvider
145
  }
146
+
147
  def __init__(self, provider: str = "ollama", model_name: str = None, **provider_kwargs):
148
  self.provider_name = provider.lower()
149
  self.model_name = model_name or self._get_default_model()
 
153
 
154
  provider_class = self.PROVIDER_MAP[self.provider_name]
155
  self.provider = provider_class(self.model_name, **provider_kwargs)
156
+
157
  def _get_default_model(self) -> str:
158
  """Get default model based on provider"""
159
  defaults = {
 
162
  "openai": "gpt-3.5-turbo"
163
  }
164
  return defaults.get(self.provider_name, "mistral")
165
+
166
  def generate(self, prompt: str, max_tokens: int = 500, stream: bool = False) -> Union[str, Generator[str, None, None]]:
167
  """Unified generate method that delegates to provider"""
168
  return self.provider.generate(prompt, max_tokens, stream)
169
+
170
  @classmethod
171
  def get_available_providers(cls) -> list:
172
  """Return list of supported providers"""
ngrok.yml.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ version: "2"
2
+ authtoken: 32HaXMF3tuRxfas1siT3CIhLjH4_2AXbGGma38NnCF1tjyJNZ
3
+ tunnels:
4
+ ai-coach-api:
5
+ addr: 8000
6
+ proto: http
7
+ ai-coach-ui:
8
+ addr: 8501
9
+ proto: http
test_setup.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ # Test Ollama
8
+ ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434")
9
+ model_name = os.getenv("LOCAL_MODEL_NAME", "mistral:latest")
10
+
11
+ print(f"Testing Ollama at: {ollama_host}")
12
+ try:
13
+ response = requests.get(f"{ollama_host}/api/tags")
14
+ print(f"Ollama Status: {response.status_code}")
15
+ print(f"Models available: {response.json()}")
16
+ except Exception as e:
17
+ print(f"Ollama Error: {e}")
18
+
19
+ # Test model generation
20
+ print(f"\nTesting model: {model_name}")
21
+ try:
22
+ response = requests.post(f"{ollama_host}/api/generate", json={
23
+ "model": model_name,
24
+ "prompt": "Hello, world!",
25
+ "stream": False
26
+ })
27
+ print(f"Model Test Status: {response.status_code}")
28
+ if response.status_code == 200:
29
+ print("✅ Ollama and model are working correctly!")
30
+ else:
31
+ print(f"❌ Model test failed: {response.text}")
32
+ except Exception as e:
33
+ print(f"Model Test Error: {e}")
utils/__pycache__/config.cpython-313.pyc ADDED
Binary file (2.34 kB). View file
 
utils/config.py CHANGED
@@ -6,15 +6,20 @@ class Config:
6
  load_dotenv()
7
  self.hf_token = os.getenv("HF_TOKEN")
8
  self.hf_api_url = os.getenv("HF_API_ENDPOINT_URL")
 
9
  self.tavily_api_key = os.getenv("TAVILY_API_KEY")
10
  self.openweather_api_key = os.getenv("OPENWEATHER_API_KEY")
11
  self.nasa_api_key = os.getenv("NASA_API_KEY")
12
- self.redis_host = os.getenv("REDIS_HOST")
 
 
13
  self.redis_port = int(os.getenv("REDIS_PORT", "6379"))
14
  self.redis_username = os.getenv("REDIS_USERNAME")
15
  self.redis_password = os.getenv("REDIS_PASSWORD")
16
- self.local_model_name = os.getenv("LOCAL_MODEL_NAME", "mistral-7b")
17
- # Use the correct remote Ollama host from the ngrok warning page
 
 
18
  self.ollama_host = os.getenv("OLLAMA_HOST", "https://ace32bd59aef.ngrok-free.app")
19
 
20
  config = Config()
 
6
  load_dotenv()
7
  self.hf_token = os.getenv("HF_TOKEN")
8
  self.hf_api_url = os.getenv("HF_API_ENDPOINT_URL")
9
+ self.use_fallback = os.getenv("USE_FALLBACK", "false").lower() == "true"
10
  self.tavily_api_key = os.getenv("TAVILY_API_KEY")
11
  self.openweather_api_key = os.getenv("OPENWEATHER_API_KEY")
12
  self.nasa_api_key = os.getenv("NASA_API_KEY")
13
+
14
+ # Redis configuration with proper defaults
15
+ self.redis_host = os.getenv("REDIS_HOST", "localhost")
16
  self.redis_port = int(os.getenv("REDIS_PORT", "6379"))
17
  self.redis_username = os.getenv("REDIS_USERNAME")
18
  self.redis_password = os.getenv("REDIS_PASSWORD")
19
+ self.redis_retries = int(os.getenv("REDIS_RETRIES", "3"))
20
+ self.redis_retry_delay = int(os.getenv("REDIS_RETRY_DELAY", "1"))
21
+
22
+ self.local_model_name = os.getenv("LOCAL_MODEL_NAME", "mistral")
23
  self.ollama_host = os.getenv("OLLAMA_HOST", "https://ace32bd59aef.ngrok-free.app")
24
 
25
  config = Config()