rdune71 commited on
Commit
86b116d
·
1 Parent(s): 7878c29

Update app.py imports and send button logic, enhance config detection for HF Spaces, improve HF token validation

Browse files
Files changed (3) hide show
  1. app.py +97 -367
  2. core/llm.py +47 -81
  3. utils/config.py +9 -8
app.py CHANGED
@@ -1,390 +1,120 @@
1
- # Force redeploy trigger - version 2.3
2
  import streamlit as st
3
- from utils.config import config
4
- import requests
5
- import json
6
  import os
7
- from core.memory import load_user_state, check_redis_health
8
-
9
- # Set page config FIRST (before any other Streamlit commands)
10
- st.set_page_config(page_title="AI Life Coach", page_icon="🧘", layout="centered")
11
-
12
- # ROBUST SESSION STATE INITIALIZATION
13
- # This must happen before ANY widget creation
14
- def init_session_state():
15
- """Initialize all session state variables with proper defaults"""
16
- defaults = {
17
- 'ngrok_url': config.ollama_host,
18
- 'model_status': "checking",
19
- 'available_models': [],
20
- 'selected_model': config.local_model_name,
21
- 'selected_model_index': 0,
22
- 'user_message_input': "",
23
- 'user_selector': "Rob" # Add missing default
24
- }
25
-
26
- for key, default_value in defaults.items():
27
- if key not in st.session_state:
28
- st.session_state[key] = default_value
29
-
30
- # CALL THIS FIRST
31
- init_session_state()
32
-
33
- # Sidebar for user selection
34
- st.sidebar.title("🧘 AI Life Coach")
35
- user = st.sidebar.selectbox("Select User", ["Rob", "Sarah"], key="user_selector")
36
 
37
- # Ngrok URL input in sidebar - UPDATED VERSION
38
- st.sidebar.markdown("---")
39
- st.sidebar.subheader("Ollama Connection")
40
- ngrok_input = st.sidebar.text_input(
41
- "Ngrok URL",
42
- value=st.session_state.ngrok_url,
43
- key="ngrok_url_input" # Explicit key
44
- )
45
-
46
- if st.sidebar.button("Update Ngrok URL", key="update_ngrok_button"):
47
- # Explicit key
48
- st.session_state.ngrok_url = ngrok_input
49
- st.session_state.model_status = "checking"
50
- st.session_state.available_models = []
51
- st.session_state.selected_model_index = 0 # Reset model index
52
- st.sidebar.success("Ngrok URL updated!")
53
- st.experimental_rerun()
54
-
55
- # Headers to skip ngrok browser warning
56
- NGROK_HEADERS = {
57
- "ngrok-skip-browser-warning": "true",
58
- "User-Agent": "AI-Life-Coach-App"
59
- }
60
-
61
- # Fetch available models
62
- def fetch_available_models(ngrok_url):
63
- try:
64
- response = requests.get(
65
- f"{ngrok_url}/api/tags",
66
- headers=NGROK_HEADERS,
67
- timeout=5
68
- )
69
- if response.status_code == 200:
70
- models_data = response.json().get("models", [])
71
- return [m.get("name") for m in models_data]
72
- except Exception:
73
- pass
74
- return []
75
-
76
- # Update available models if st.session_state.ngrok_url and st.session_state.model_status != "unreachable":
77
- model_names = fetch_available_models(st.session_state.ngrok_url)
78
- if model_names:
79
- st.session_state.available_models = model_names
80
- # If current selected model not in list, select the first one
81
- if st.session_state.selected_model not in model_names:
82
- st.session_state.selected_model = model_names[0]
83
- st.session_state.selected_model_index = 0
84
 
85
- # Model selector dropdown - REPLACED ENTIRE SECTION
86
- st.sidebar.markdown("---")
87
- st.sidebar.subheader("Model Selection")
88
 
89
- # Initialize selected_model_index in session state if not present
90
- if 'selected_model_index' not in st.session_state:
91
- st.session_state.selected_model_index = 0
92
 
93
- if st.session_state.available_models:
94
- # Ensure selected_model_index is within bounds
95
- if st.session_state.selected_model_index >= len(st.session_state.available_models):
96
- st.session_state.selected_model_index = 0
97
 
98
- # Find index of currently selected model
99
- if st.session_state.selected_model in st.session_state.available_models:
100
- st.session_state.selected_model_index = st.session_state.available_models.index(st.session_state.selected_model)
101
- else:
102
- # If current model not available, select first one
103
- st.session_state.selected_model_index = 0
104
- if st.session_state.available_models:
105
- st.session_state.selected_model = st.session_state.available_models[0]
106
 
107
- # Use index-based selection to avoid widget key issues
108
- selected_model = st.sidebar.selectbox(
109
  "Select Model",
110
- st.session_state.available_models,
111
- index=st.session_state.selected_model_index,
112
- key="model_selector" # Explicit key to prevent conflicts
113
  )
114
- st.session_state.selected_model = selected_model
115
- else:
116
- st.sidebar.warning("No models available - check Ollama connection")
117
- model_input = st.sidebar.text_input(
118
- "Or enter model name",
119
- value=st.session_state.selected_model,
120
- key="manual_model_input" # Explicit key
 
121
  )
122
- st.session_state.selected_model = model_input
123
-
124
- st.sidebar.markdown("---")
125
-
126
- # Get environment info
127
- BASE_URL = os.environ.get("SPACE_ID", "")
128
- IS_HF_SPACE = bool(BASE_URL)
129
-
130
- # Fetch Ollama status with enhanced error handling
131
- def get_ollama_status(ngrok_url):
132
- try:
133
- response = requests.get(
134
- f"{ngrok_url}/api/tags",
135
- headers=NGROK_HEADERS,
136
- timeout=15 # Increased timeout
137
- )
138
- if response.status_code == 200:
139
- models = response.json().get("models", [])
140
- model_names = [m.get("name") for m in models]
141
- st.session_state.available_models = model_names
142
- if models:
143
- selected_model_available = st.session_state.selected_model in model_names
144
- return {
145
- "running": True,
146
- "model_loaded": st.session_state.selected_model if selected_model_available else model_names[0],
147
- "remote_host": ngrok_url,
148
- "available_models": model_names,
149
- "selected_model_available": selected_model_available
150
- }
151
- else:
152
- st.session_state.model_status = "no_models"
153
- return {
154
- "running": True, # Server is running but no models
155
- "model_loaded": None,
156
- "remote_host": ngrok_url,
157
- "message": "Connected to Ollama but no models found"
158
- }
159
- elif response.status_code == 404:
160
- # Server might be running but endpoint not available
161
- response2 = requests.get(f"{ngrok_url}", headers=NGROK_HEADERS, timeout=10)
162
- if response2.status_code == 200:
163
- st.session_state.model_status = "checking"
164
- return {
165
- "running": True,
166
- "model_loaded": "unknown",
167
- "remote_host": ngrok_url,
168
- "message": "Server running, endpoint check inconclusive"
169
- }
170
- else:
171
- st.session_state.model_status = "unreachable"
172
- return {
173
- "running": False,
174
- "model_loaded": None,
175
- "error": f"HTTP {response.status_code}",
176
- "remote_host": ngrok_url
177
- }
178
- else:
179
- st.session_state.model_status = "unreachable"
180
- return {
181
- "running": False,
182
- "model_loaded": None,
183
- "error": f"HTTP {response.status_code}",
184
- "remote_host": ngrok_url
185
- }
186
- except requests.exceptions.Timeout:
187
- st.session_state.model_status = "unreachable"
188
- return {
189
- "running": False,
190
- "model_loaded": None,
191
- "error": "Timeout - server not responding",
192
- "remote_host": ngrok_url
193
- }
194
- except Exception as e:
195
- st.session_state.model_status = "unreachable"
196
- return {
197
- "running": False,
198
- "model_loaded": None,
199
- "error": str(e),
200
- "remote_host": ngrok_url
201
- }
202
-
203
- # Load conversation history
204
- def get_conversation_history(user_id):
205
- try:
206
- user_state = load_user_state(user_id)
207
- if user_state and "conversation" in user_state:
208
- return json.loads(user_state["conversation"])
209
- except Exception as e:
210
- st.warning(f"Could not load conversation history: {e}")
211
- return []
212
-
213
- # Get Ollama status with null safety
214
- ollama_status = get_ollama_status(st.session_state.ngrok_url)
215
-
216
- # Add null safety check
217
- if ollama_status is None:
218
- ollama_status = {
219
- "running": False,
220
- "model_loaded": None,
221
- "error": "Failed to get Ollama status",
222
- "remote_host": st.session_state.ngrok_url
223
- }
224
-
225
- # Update model status with better logic
226
- if ollama_status and ollama_status.get("running", False):
227
- if ollama_status.get("available_models") and len(ollama_status.get("available_models", [])) > 0:
228
- st.session_state.model_status = "ready"
229
- elif ollama_status.get("model_loaded") == "unknown":
230
- st.session_state.model_status = "ready" # Assume ready if server responds
231
- else:
232
- st.session_state.model_status = "no_models"
233
- else:
234
- st.session_state.model_status = "unreachable"
235
-
236
- # Ensure ollama_status is a dict even if None
237
- ollama_status = ollama_status or {}
238
-
239
- # Determine if we should use fallback
240
- use_fallback = not ollama_status.get("running", False) or config.use_fallback
241
-
242
- # Display Ollama status - Enhanced section with Hugging Face scaling behavior info
243
- if use_fallback:
244
- st.sidebar.warning("🌐 Using Hugging Face fallback (Ollama not available)")
245
- # Add special note for Hugging Face scaling behavior
246
- if config.hf_api_url and "endpoints.huggingface.cloud" in config.hf_api_url:
247
- st.sidebar.info("ℹ️ HF Endpoint may be initializing (up to 4 min)")
248
- if "error" in ollama_status:
249
- st.sidebar.caption(f"Error: {ollama_status['error'][:50]}...")
250
- else:
251
- model_status_msg = ollama_status.get('model_loaded', 'Unknown')
252
- if ollama_status.get('selected_model_available', True):
253
- st.sidebar.success(f"🧠 Ollama Model: {model_status_msg}")
254
- else:
255
- st.sidebar.warning(f"🧠 Ollama Model: {model_status_msg} (selected model not available)")
256
- st.sidebar.info(f"Connected to: {ollama_status['remote_host']}")
257
-
258
- # Status indicators
259
- model_status_container = st.sidebar.empty()
260
- if st.session_state.model_status == "ready":
261
- model_status_container.success("✅ Model Ready")
262
- elif st.session_state.model_status == "checking":
263
- model_status_container.info("🔍 Checking model...")
264
- elif st.session_state.model_status == "no_models":
265
- model_status_container.warning("⚠️ No models found")
266
- else:
267
- model_status_container.error("❌ Ollama unreachable")
268
-
269
- redis_status_container = st.sidebar.empty()
270
- if check_redis_health():
271
- redis_status_container.success("✅ Redis Connected")
272
- else:
273
- redis_status_container.warning("⚠️ Redis Not Available")
274
 
275
  # Main chat interface
276
- st.title("🧘 AI Life Coach")
277
- st.markdown("Talk to your personal development assistant.")
278
-
279
- # Show detailed status with st.expander("🔍 Connection Status"):
280
- st.write("Ollama Status:", ollama_status)
281
- st.write("Model Status:", st.session_state.model_status)
282
- st.write("Selected Model:", st.session_state.selected_model)
283
- st.write("Available Models:", st.session_state.available_models)
284
- st.write("Environment Info:")
285
- st.write("- Is HF Space:", IS_HF_SPACE)
286
- st.write("- Base URL:", BASE_URL or "Not in HF Space")
287
- st.write("- Current Ngrok URL:", st.session_state.ngrok_url)
288
- st.write("- Using Fallback:", use_fallback)
289
- st.write("- Redis Health:", check_redis_health())
290
-
291
- # Function to send message to Ollama
292
- def send_to_ollama(user_input, conversation_history, ngrok_url, model_name):
293
- try:
294
- # Use the correct chat endpoint with proper payload
295
- payload = {
296
- "model": model_name,
297
- "messages": conversation_history,
298
- "stream": False,
299
- "options": {
300
- "temperature": 0.7,
301
- "top_p": 0.9
302
- }
303
- }
304
- response = requests.post(
305
- f"{ngrok_url}/api/chat",
306
- json=payload,
307
- headers=NGROK_HEADERS,
308
- timeout=60
309
- )
310
- if response.status_code == 200:
311
- response_data = response.json()
312
- return response_data.get("message", {}).get("content", "")
313
- else:
314
- st.error(f"Ollama API error: {response.status_code}")
315
- st.error(response.text[:200])
316
- return None
317
- except Exception as e:
318
- st.error(f"Connection error: {e}")
319
- return None
320
-
321
- # Function to send message to Hugging Face (fallback)
322
- def send_to_hf(user_input, conversation_history):
323
- try:
324
- from core.llm import LLMClient
325
- llm_client = LLMClient(provider="huggingface")
326
- # Format for HF
327
- prompt = "You are a helpful life coach. "
328
- for msg in conversation_history:
329
- if msg["role"] == "user":
330
- prompt += f"Human: {msg['content']} "
331
- elif msg["role"] == "assistant":
332
- prompt += f"Assistant: {msg['content']} "
333
- prompt += "Assistant:"
334
- response = llm_client.generate(prompt, max_tokens=500, stream=False)
335
- return response
336
- except Exception as e:
337
- st.error(f"Hugging Face API error: {e}")
338
- return None
339
-
340
- # Display conversation history
341
- conversation = get_conversation_history(user)
342
- for msg in conversation:
343
- role = msg["role"].capitalize()
344
- content = msg["content"]
345
- st.markdown(f"{role}: {content}")
346
-
347
- # Chat input - REPLACED SECTION
348
- user_input = st.text_input(
349
- "Your message...",
350
- key="user_message_input", # Explicit key
351
- placeholder="Type your message here...",
352
- value=st.session_state.user_message_input
353
- )
354
 
355
- # Use a separate key for the button to avoid conflicts
356
- send_button = st.button("Send", key="send_message_button")
357
 
358
- if send_button:
359
- if user_input.strip() == "":
360
- st.warning("Please enter a message.")
361
- else:
362
- # Display user message
363
- st.markdown(f"You: {user_input}")
364
-
365
- # Prepare conversation history
366
- conversation_history = [{"role": msg["role"], "content": msg["content"]} for msg in conversation[-5:]]
367
- conversation_history.append({"role": "user", "content": user_input})
368
-
369
- # Send to appropriate backend
 
 
 
370
  with st.spinner("AI Coach is thinking..."):
371
- if use_fallback:
372
- ai_response = send_to_hf(user_input, conversation_history)
373
- backend_used = "Hugging Face"
374
- else:
 
375
  ai_response = send_to_ollama(
376
- user_input,
377
- conversation_history,
378
- st.session_state.ngrok_url,
379
  st.session_state.selected_model
380
  )
381
  backend_used = "Ollama"
382
 
 
 
 
 
 
383
  if ai_response:
384
- st.markdown(f"AI Coach ({backend_used}): {ai_response}")
 
 
 
 
 
 
 
385
  else:
386
- st.error(f"Failed to get response from {backend_used}.")
387
-
388
- # Clear input after sending (this helps prevent duplicate sends)
389
- st.session_state.user_message_input = ""
390
- st.experimental_rerun()
 
 
1
  import streamlit as st
2
+ import time
 
 
3
  import os
4
+ import sys
5
+ from pathlib import Path
6
+ sys.path.append(str(Path(__file__).parent))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ from utils.config import config
9
+ from core.llm import send_to_ollama, send_to_hf
10
+ from core.memory import get_conversation_history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ st.set_page_config(page_title="AI Life Coach", page_icon="🧠", layout="wide")
 
 
13
 
14
+ # Initialize session state
15
+ if "messages" not in st.session_state:
16
+ st.session_state.messages = []
17
 
18
+ # Sidebar
19
+ with st.sidebar:
20
+ st.title("AI Life Coach")
21
+ st.markdown("Your personal AI-powered life development assistant")
22
 
23
+ # Model selection
24
+ model_options = {
25
+ "Mistral 7B (Local)": "mistral:latest",
26
+ "Llama 2 7B (Local)": "llama2:latest",
27
+ "OpenChat 3.5 (Local)": "openchat:latest"
28
+ }
 
 
29
 
30
+ selected_model_name = st.selectbox(
 
31
  "Select Model",
32
+ options=list(model_options.keys()),
33
+ index=0
 
34
  )
35
+
36
+ st.session_state.selected_model = model_options[selected_model_name]
37
+
38
+ # Ollama URL input
39
+ st.session_state.ngrok_url = st.text_input(
40
+ "Ollama Server URL",
41
+ value=st.session_state.get("ngrok_url", "http://localhost:11434"),
42
+ help="Enter the URL to your Ollama server"
43
  )
44
+
45
+ # Conversation history
46
+ st.subheader("Conversation History")
47
+ if st.button("Clear History"):
48
+ st.session_state.messages = []
49
+ st.success("History cleared!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  # Main chat interface
52
+ st.title("🧠 AI Life Coach")
53
+ st.markdown("Ask me anything about personal development, goal setting, or life advice!")
54
+
55
+ # Display chat messages
56
+ for message in st.session_state.messages:
57
+ with st.chat_message(message["role"]):
58
+ st.markdown(message["content"])
59
+
60
+ # Chat input and send button
61
+ col1, col2 = st.columns([4, 1])
62
+ with col1:
63
+ user_input = st.text_input(
64
+ "Your message...",
65
+ key="user_message_input",
66
+ placeholder="Type your message here...",
67
+ label_visibility="collapsed"
68
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ with col2:
71
+ send_button = st.button("Send", key="send_message_button", use_container_width=True)
72
 
73
+ if send_button and user_input.strip():
74
+ # Display user message
75
+ with st.chat_message("user"):
76
+ st.markdown(user_input)
77
+
78
+ # Add user message to history
79
+ st.session_state.messages.append({"role": "user", "content": user_input})
80
+
81
+ # Get conversation history
82
+ conversation = get_conversation_history("default_user")
83
+ conversation_history = conversation[-5:] # Last 5 messages
84
+ conversation_history.append({"role": "user", "content": user_input})
85
+
86
+ # Send to backend
87
+ with st.chat_message("assistant"):
88
  with st.spinner("AI Coach is thinking..."):
89
+ ai_response = None
90
+ backend_used = ""
91
+
92
+ # Try Ollama first if not falling back
93
+ if not config.use_fallback:
94
  ai_response = send_to_ollama(
95
+ user_input,
96
+ conversation_history,
97
+ st.session_state.ngrok_url,
98
  st.session_state.selected_model
99
  )
100
  backend_used = "Ollama"
101
 
102
+ # Fallback to Hugging Face
103
+ if not ai_response and config.hf_token:
104
+ ai_response = send_to_hf(user_input, conversation_history)
105
+ backend_used = "Hugging Face"
106
+
107
  if ai_response:
108
+ st.markdown(f"{ai_response}")
109
+
110
+ # Update conversation history (stub – actual save will come later)
111
+ conversation.append({"role": "user", "content": user_input})
112
+ conversation.append({"role": "assistant", "content": ai_response})
113
+
114
+ # Add assistant response to history
115
+ st.session_state.messages.append({"role": "assistant", "content": ai_response})
116
  else:
117
+ st.error("Failed to get response from both providers.")
118
+
119
+ # Clear input by forcing rerun
120
+ st.experimental_rerun()
 
core/llm.py CHANGED
@@ -1,10 +1,8 @@
1
  import openai
 
2
  import time
3
- from typing import Dict, Any, List, Optional
4
- from core.config import config
5
- import logging
6
-
7
- logger = logging.getLogger(__name__)
8
 
9
  class LLMProvider:
10
  def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
@@ -12,91 +10,59 @@ class LLMProvider:
12
  self.timeout = timeout
13
  self.retries = retries
14
 
15
- def generate_response(self, messages: List[Dict[str, str]], **kwargs) -> str:
16
- raise NotImplementedError
17
-
18
- class HuggingFaceProvider(LLMProvider):
19
- def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
20
- super().__init__(model_name, timeout, retries)
21
- # Remove proxies parameter that causes the error
22
- self.client = openai.OpenAI(
23
- base_url=config.hf_api_url,
24
- api_key=config.hf_token
25
- # Removed: proxies parameter
26
- )
27
-
28
- def generate_response(self, messages: List[Dict[str, str]], **kwargs) -> str:
29
  for attempt in range(self.retries):
30
  try:
31
- response = self.client.chat.completions.create(
32
- model=self.model_name,
33
- messages=messages,
34
- timeout=self.timeout,
35
- **kwargs
36
- )
37
- return response.choices[0].message.content
38
  except Exception as e:
39
- logger.error(f"HuggingFace API error (attempt {attempt + 1}/{self.retries}): {e}")
40
  if attempt == self.retries - 1:
41
- raise
 
42
  time.sleep(2 ** attempt) # Exponential backoff
43
- return ""
44
 
45
- class OllamaProvider(LLMProvider):
46
  def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
47
  super().__init__(model_name, timeout, retries)
 
 
 
48
  self.client = openai.OpenAI(
49
- base_url=config.ollama_host + "/v1",
50
- api_key="ollama" # Ollama doesn't require an API key
51
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
- def generate_response(self, messages: List[Dict[str, str]], **kwargs) -> str:
54
- for attempt in range(self.retries):
55
- try:
56
- response = self.client.chat.completions.create(
57
- model=self.model_name,
58
- messages=messages,
59
- timeout=self.timeout,
60
- **kwargs
61
- )
62
- return response.choices[0].message.content
63
- except Exception as e:
64
- logger.error(f"Ollama API error (attempt {attempt + 1}/{self.retries}): {e}")
65
- if attempt == self.retries - 1:
66
- raise
67
- time.sleep(2 ** attempt) # Exponential backoff
68
- return ""
69
-
70
- class OpenAIProvider(LLMProvider):
71
- def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
72
- super().__init__(model_name, timeout, retries)
73
- self.client = openai.OpenAI(api_key=config.openai_api_key)
74
-
75
- def generate_response(self, messages: List[Dict[str, str]], **kwargs) -> str:
76
- for attempt in range(self.retries):
77
- try:
78
- response = self.client.chat.completions.create(
79
- model=self.model_name,
80
- messages=messages,
81
- timeout=self.timeout,
82
- **kwargs
83
- )
84
- return response.choices[0].message.content
85
- except Exception as e:
86
- logger.error(f"OpenAI API error (attempt {attempt + 1}/{self.retries}): {e}")
87
- if attempt == self.retries - 1:
88
- raise
89
- time.sleep(2 ** attempt) # Exponential backoff
90
- return ""
91
 
92
- def get_llm_provider(provider_name: str, model_name: str) -> LLMProvider:
93
- providers = {
94
- "huggingface": HuggingFaceProvider,
95
- "ollama": OllamaProvider,
96
- "openai": OpenAIProvider
97
- }
98
-
99
- if provider_name not in providers:
100
- raise ValueError(f"Unsupported provider: {provider_name}")
101
-
102
- return providers[provider_name](model_name)
 
1
  import openai
2
+ import requests
3
  import time
4
+ from typing import List, Dict, Optional
5
+ from utils.config import config
 
 
 
6
 
7
  class LLMProvider:
8
  def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
 
10
  self.timeout = timeout
11
  self.retries = retries
12
 
13
+ class OllamaProvider(LLMProvider):
14
+ def generate_response(self, prompt: str, conversation_history: List[Dict]) -> Optional[str]:
15
+ url = f"{config.ollama_host}/api/chat"
16
+ messages = conversation_history
17
+
18
+ payload = {
19
+ "model": self.model_name,
20
+ "messages": messages,
21
+ "stream": False
22
+ }
23
+
 
 
 
24
  for attempt in range(self.retries):
25
  try:
26
+ response = requests.post(url, json=payload, timeout=self.timeout)
27
+ response.raise_for_status()
28
+ return response.json()["message"]["content"]
 
 
 
 
29
  except Exception as e:
 
30
  if attempt == self.retries - 1:
31
+ print(f"Error after {self.retries} attempts: {e}")
32
+ return None
33
  time.sleep(2 ** attempt) # Exponential backoff
34
+ return None
35
 
36
+ class HuggingFaceProvider(LLMProvider):
37
  def __init__(self, model_name: str, timeout: int = 30, retries: int = 3):
38
  super().__init__(model_name, timeout, retries)
39
+ if not config.hf_token:
40
+ raise ValueError("HF_TOKEN not set - required for Hugging Face provider")
41
+
42
  self.client = openai.OpenAI(
43
+ base_url=config.hf_api_url,
44
+ api_key=config.hf_token
45
  )
46
+
47
+ def generate_response(self, prompt: str, conversation_history: List[Dict]) -> Optional[str]:
48
+ try:
49
+ response = self.client.chat.completions.create(
50
+ model=self.model_name,
51
+ messages=conversation_history,
52
+ max_tokens=500,
53
+ temperature=0.7
54
+ )
55
+ return response.choices[0].message.content
56
+ except Exception as e:
57
+ print(f"Hugging Face API error: {e}")
58
+ return None
59
 
60
+ def send_to_ollama(prompt: str, conversation_history: List[Dict], ollama_url: str, model: str) -> Optional[str]:
61
+ config.ollama_host = ollama_url
62
+ provider = OllamaProvider(model)
63
+ return provider.generate_response(prompt, conversation_history)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ def send_to_hf(prompt: str, conversation_history: List[Dict]) -> Optional[str]:
66
+ # Using a common model that works well for coaching
67
+ provider = HuggingFaceProvider("meta-llama/Llama-2-7b-chat-hf")
68
+ return provider.generate_response(prompt, conversation_history)
 
 
 
 
 
 
 
utils/config.py CHANGED
@@ -4,14 +4,15 @@ from dotenv import load_dotenv
4
  class Config:
5
  def __init__(self):
6
  load_dotenv()
 
 
 
 
7
  self.hf_token = os.getenv("HF_TOKEN")
8
  self.hf_api_url = os.getenv("HF_API_ENDPOINT_URL", "https://api-inference.huggingface.co/v1/")
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 for local development
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", "")
@@ -20,8 +21,8 @@ class Config:
20
  self.redis_retry_delay = int(os.getenv("REDIS_RETRY_DELAY", "1"))
21
 
22
  # Local model configuration
23
- self.local_model_name = os.getenv("LOCAL_MODEL_NAME", "mistral")
24
- # Update to use the working ngrok URL from your logs
25
- self.ollama_host = os.getenv("OLLAMA_HOST", "https://f943b91f0a0c.ngrok-free.app")
26
 
 
27
  config = Config()
 
4
  class Config:
5
  def __init__(self):
6
  load_dotenv()
7
+
8
+ # Detect if running on HF Spaces
9
+ self.is_hf_space = bool(os.getenv("SPACE_ID"))
10
+
11
  self.hf_token = os.getenv("HF_TOKEN")
12
  self.hf_api_url = os.getenv("HF_API_ENDPOINT_URL", "https://api-inference.huggingface.co/v1/")
13
+ self.use_fallback = os.getenv("USE_FALLBACK", "true").lower() == "true"
 
 
 
14
 
15
+ # Redis configuration (optional for HF)
16
  self.redis_host = os.getenv("REDIS_HOST", "localhost")
17
  self.redis_port = int(os.getenv("REDIS_PORT", "6379"))
18
  self.redis_username = os.getenv("REDIS_USERNAME", "")
 
21
  self.redis_retry_delay = int(os.getenv("REDIS_RETRY_DELAY", "1"))
22
 
23
  # Local model configuration
24
+ self.local_model_name = os.getenv("LOCAL_MODEL_NAME", "mistral:latest")
25
+ self.ollama_host = os.getenv("OLLAMA_HOST", "")
 
26
 
27
+ # Global config instance
28
  config = Config()