rdune71 commited on
Commit
9da7658
·
1 Parent(s): 8da6264

Implement Ollama-HF conversation coordination with expert-commentary approach

Browse files
src/llm/coordinated_provider.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import logging
3
+ from typing import List, Dict, Optional, Union
4
+ from src.llm.base_provider import LLMProvider
5
+ from src.llm.hf_provider import HuggingFaceProvider
6
+ from src.llm.ollama_provider import OllamaProvider
7
+ from utils.config import config
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class CoordinatedProvider(LLMProvider):
12
+ """Coordinated provider that orchestrates Ollama and HF interaction"""
13
+
14
+ def __init__(self, model_name: str, timeout: int = 120, max_retries: int = 2):
15
+ super().__init__(model_name, timeout, max_retries)
16
+ self.hf_provider = None
17
+ self.ollama_provider = None
18
+
19
+ # Initialize providers
20
+ try:
21
+ if config.hf_token:
22
+ self.hf_provider = HuggingFaceProvider(
23
+ model_name="DavidAU/OpenAi-GPT-oss-20b-abliterated-uncensored-NEO-Imatrix-gguf",
24
+ timeout=120
25
+ )
26
+ except Exception as e:
27
+ logger.warning(f"Failed to initialize HF provider: {e}")
28
+
29
+ try:
30
+ if config.ollama_host:
31
+ self.ollama_provider = OllamaProvider(
32
+ model_name=config.local_model_name,
33
+ timeout=60
34
+ )
35
+ except Exception as e:
36
+ logger.warning(f"Failed to initialize Ollama provider: {e}")
37
+
38
+ def generate(self, prompt: str, conversation_history: List[Dict]) -> Optional[str]:
39
+ """Generate coordinated response using HF+Ollama"""
40
+ try:
41
+ return self._retry_with_backoff(self._generate_coordinated_response, prompt, conversation_history)
42
+ except Exception as e:
43
+ logger.error(f"Coordinated generation failed: {e}")
44
+ raise
45
+
46
+ def stream_generate(self, prompt: str, conversation_history: List[Dict]) -> Optional[Union[str, List[str]]]:
47
+ """Generate coordinated response with streaming support"""
48
+ try:
49
+ return self._retry_with_backoff(self._stream_generate_coordinated_response, prompt, conversation_history)
50
+ except Exception as e:
51
+ logger.error(f"Coordinated stream generation failed: {e}")
52
+ raise
53
+
54
+ def _generate_coordinated_response(self, prompt: str, conversation_history: List[Dict]) -> str:
55
+ """Main method that orchestrates both providers"""
56
+ try:
57
+ # Step 1: Get response from HF Endpoint (primary expert)
58
+ hf_response = self._get_hf_response(prompt, conversation_history)
59
+
60
+ # Step 2: Get Ollama commentary on HF response
61
+ ollama_commentary = self._get_ollama_commentary(prompt, hf_response, conversation_history)
62
+
63
+ # Step 3: Combine responses with clear formatting
64
+ coordinated_response = self._format_coordinated_response(hf_response, ollama_commentary)
65
+
66
+ return coordinated_response
67
+
68
+ except Exception as e:
69
+ logger.error(f"Coordinated response generation failed: {e}")
70
+ # Fallback to Ollama only
71
+ if self.ollama_provider:
72
+ try:
73
+ ollama_response = self.ollama_provider.generate(prompt, conversation_history)
74
+ # Generate self-commentary
75
+ self_commentary = self._get_ollama_self_commentary(prompt, ollama_response, conversation_history)
76
+ return self._format_fallback_response(ollama_response, self_commentary)
77
+ except Exception as fallback_error:
78
+ logger.error(f"Ollama fallback also failed: {fallback_error}")
79
+
80
+ raise Exception(f"Both HF Endpoint and Ollama failed: {str(e)}")
81
+
82
+ def _stream_generate_coordinated_response(self, prompt: str, conversation_history: List[Dict]) -> List[str]:
83
+ """Implementation of streaming coordinated generation"""
84
+ # For simplicity, we'll return the full response as chunks
85
+ full_response = self._generate_coordinated_response(prompt, conversation_history)
86
+ return [full_response]
87
+
88
+ def _get_hf_response(self, prompt: str, conversation_history: List[Dict]) -> Optional[str]:
89
+ """Get response from HF Endpoint (primary expert)"""
90
+ if not self.hf_provider:
91
+ return None
92
+
93
+ try:
94
+ logger.info("🚀 Getting detailed response from HF Endpoint (primary expert)...")
95
+ response = self.hf_provider.generate(prompt, conversation_history)
96
+ logger.info("✅ HF Endpoint expert response received")
97
+ return response
98
+ except Exception as e:
99
+ logger.error(f"HF Endpoint expert failed: {e}")
100
+ return None
101
+
102
+ def _get_ollama_commentary(self, user_prompt: str, hf_response: str, conversation_history: List[Dict]) -> Optional[str]:
103
+ """Get Ollama commentary on HF response"""
104
+ if not self.ollama_provider:
105
+ return None
106
+
107
+ try:
108
+ logger.info("🐱 Getting Ollama commentary on HF response...")
109
+ commentary = self.ollama_provider.generate_commentary(user_prompt, hf_response, conversation_history)
110
+ logger.info("✅ Ollama commentary received")
111
+ return commentary
112
+ except Exception as e:
113
+ logger.warning(f"Ollama commentary failed: {e}")
114
+ return None
115
+
116
+ def _get_ollama_self_commentary(self, user_prompt: str, ollama_response: str, conversation_history: List[Dict]) -> Optional[str]:
117
+ """Get Ollama self-commentary when HF fails"""
118
+ if not self.ollama_provider:
119
+ return None
120
+
121
+ try:
122
+ logger.info("🐱 Getting Ollama self-commentary on own response...")
123
+ commentary = self.ollama_provider.generate_self_commentary(user_prompt, ollama_response, conversation_history)
124
+ logger.info("✅ Ollama self-commentary received")
125
+ return commentary
126
+ except Exception as e:
127
+ logger.warning(f"Ollama self-commentary failed: {e}")
128
+ return None
129
+
130
+ def _format_coordinated_response(self, hf_response: str, ollama_commentary: str) -> str:
131
+ """Format combined response with clear separation"""
132
+ response_parts = []
133
+
134
+ # Add HF Expert response
135
+ if hf_response:
136
+ response_parts.append("🤖 HF Expert Analysis\n" + hf_response)
137
+ else:
138
+ response_parts.append("🤖 HF Expert Analysis\n*No response from HF Expert*")
139
+
140
+ # Add separator
141
+ response_parts.append("\n" + "="*50 + "\n")
142
+
143
+ # Add Ollama Mentor commentary
144
+ if ollama_commentary:
145
+ response_parts.append("🐱 Ollama Mentor Commentary\n" + ollama_commentary)
146
+ else:
147
+ response_parts.append("🐱 Ollama Mentor Commentary\n*I've reviewed the HF expert's response but couldn't provide additional insights.*")
148
+
149
+ return "\n\n".join(response_parts)
150
+
151
+ def _format_fallback_response(self, ollama_response: str, self_commentary: str) -> str:
152
+ """Format fallback response when HF fails"""
153
+ response_parts = []
154
+
155
+ # Add Ollama main response with fallback indication
156
+ if ollama_response:
157
+ response_parts.append("🦙 Ollama Primary Response (HF Expert Unavailable)\n" + ollama_response)
158
+ else:
159
+ response_parts.append("🦙 Ollama Primary Response (HF Expert Unavailable)\n*No response generated*")
160
+
161
+ # Add separator
162
+ response_parts.append("\n" + "="*50 + "\n")
163
+
164
+ # Add Ollama self-commentary
165
+ if self_commentary:
166
+ response_parts.append("🐱 Ollama Self-Commentary\n" + self_commentary)
167
+ else:
168
+ response_parts.append("🐱 Ollama Self-Commentary\n*I've reviewed my own response but couldn't provide additional insights.*")
169
+
170
+ return "\n\n".join(response_parts)
171
+
172
+ def validate_model(self) -> bool:
173
+ """Validate if coordinated system is available"""
174
+ # At least one provider must be available
175
+ hf_available = self.hf_provider and self.hf_provider.validate_model() if self.hf_provider else False
176
+ ollama_available = self.ollama_provider and self.ollama_provider.validate_model() if self.ollama_provider else False
177
+ return hf_available or ollama_available
178
+
179
+ # Global instance
180
+ coordinated_provider = CoordinatedProvider("coordinated_model")
src/llm/ollama_provider.py CHANGED
@@ -4,12 +4,11 @@ import re
4
  from typing import List, Dict, Optional, Union
5
  from src.llm.base_provider import LLMProvider
6
  from utils.config import config
7
- from src.services.context_provider import context_provider
8
 
9
  logger = logging.getLogger(__name__)
10
 
11
  class OllamaProvider(LLMProvider):
12
- """Ollama LLM provider implementation"""
13
 
14
  def __init__(self, model_name: str, timeout: int = 60, max_retries: int = 3):
15
  super().__init__(model_name, timeout, max_retries)
@@ -58,7 +57,6 @@ class OllamaProvider(LLMProvider):
58
  model_names = [model.get("name") for model in models]
59
  return self.model_name in model_names
60
  elif response.status_code == 404:
61
- # Try alternative endpoint
62
  response2 = requests.get(
63
  f"{self.host}",
64
  headers=self.headers,
@@ -67,26 +65,108 @@ class OllamaProvider(LLMProvider):
67
  return response2.status_code == 200
68
  return False
69
  except Exception as e:
70
- logger.error(f"Model validation failed: {e}")
71
  return False
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  def _generate_impl(self, prompt: str, conversation_history: List[Dict]) -> str:
74
- """Implementation of synchronous generation with intelligent context"""
75
  try:
76
  url = f"{self.host}/api/chat"
 
 
77
 
78
- # Intelligently enrich context
79
- enriched_history = self._enrich_context_intelligently(conversation_history)
80
-
81
- # Prepare messages - ensure proper format
82
- messages = []
83
- for msg in enriched_history:
84
- if isinstance(msg, dict) and "role" in msg and "content" in msg:
85
- messages.append({
86
- "role": msg["role"],
87
- "content": str(msg["content"])
88
- })
89
-
90
  payload = {
91
  "model": self.model_name,
92
  "messages": messages,
@@ -103,6 +183,7 @@ class OllamaProvider(LLMProvider):
103
  headers=self.headers,
104
  timeout=self.timeout
105
  )
 
106
  logger.info(f"Ollama response status: {response.status_code}")
107
  logger.info(f"Ollama response headers: {dict(response.headers)}")
108
 
@@ -110,7 +191,6 @@ class OllamaProvider(LLMProvider):
110
  result = response.json()
111
  logger.info(f"Ollama response body: {result}")
112
 
113
- # Extract content properly with multiple fallbacks
114
  content = None
115
  if "message" in result and "content" in result["message"]:
116
  content = result["message"]["content"]
@@ -122,23 +202,20 @@ class OllamaProvider(LLMProvider):
122
  logger.info(f"Extracted content length: {len(content) if content else 0}")
123
  return content if content else ""
124
 
125
- except requests.exceptions.RequestException as e:
126
  logger.error(f"Ollama API request error: {str(e)}")
127
  raise Exception(f"Ollama API error: {str(e)}")
128
- except Exception as e:
129
- logger.error(f"Failed to parse Ollama response: {str(e)}")
130
- raise Exception(f"Failed to parse Ollama response: {str(e)}")
131
 
132
  def _stream_generate_impl(self, prompt: str, conversation_history: List[Dict]) -> List[str]:
133
  """Implementation of streaming generation"""
134
  try:
135
- # Intelligently enrich context
136
- enriched_history = self._enrich_context_intelligently(conversation_history)
137
-
138
  url = f"{self.host}/api/chat"
 
 
 
139
  payload = {
140
  "model": self.model_name,
141
- "messages": enriched_history,
142
  "stream": True
143
  }
144
 
@@ -167,33 +244,5 @@ class OllamaProvider(LLMProvider):
167
  logger.error(f"Ollama stream generation failed: {e}")
168
  raise
169
 
170
- def _enrich_context_intelligently(self, conversation_history: List[Dict]) -> List[Dict]:
171
- """Intelligently add context only when relevant"""
172
- if not conversation_history:
173
- return conversation_history
174
-
175
- # Get the last user message to determine context needs
176
- last_user_message = ""
177
- for msg in reversed(conversation_history):
178
- if msg["role"] == "user":
179
- last_user_message = msg["content"]
180
- break
181
-
182
- # Get intelligent context
183
- context_string = context_provider.get_context_for_llm(
184
- last_user_message,
185
- conversation_history
186
- )
187
-
188
- # Only add context if it's relevant
189
- if context_string:
190
- context_message = {
191
- "role": "system",
192
- "content": context_string
193
- }
194
- # Insert context at the beginning
195
- enriched_history = [context_message] + conversation_history
196
- return enriched_history
197
-
198
- # Return original history if no context needed
199
- return conversation_history
 
4
  from typing import List, Dict, Optional, Union
5
  from src.llm.base_provider import LLMProvider
6
  from utils.config import config
 
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
  class OllamaProvider(LLMProvider):
11
+ """Ollama LLM provider implementation with commentary support"""
12
 
13
  def __init__(self, model_name: str, timeout: int = 60, max_retries: int = 3):
14
  super().__init__(model_name, timeout, max_retries)
 
57
  model_names = [model.get("name") for model in models]
58
  return self.model_name in model_names
59
  elif response.status_code == 404:
 
60
  response2 = requests.get(
61
  f"{self.host}",
62
  headers=self.headers,
 
65
  return response2.status_code == 200
66
  return False
67
  except Exception as e:
68
+ logger.warning(f"Model validation failed: {e}")
69
  return False
70
 
71
+ def generate_commentary(self, user_prompt: str, hf_response: str, conversation_history: List[Dict]) -> Optional[str]:
72
+ """Generate commentary on HF response"""
73
+ try:
74
+ commentary_prompt = self._create_commentary_prompt(user_prompt, hf_response, conversation_history)
75
+ return self._retry_with_backoff(self._generate_impl, commentary_prompt, [])
76
+ except Exception as e:
77
+ logger.error(f"Ollama commentary generation failed: {e}")
78
+ return None
79
+
80
+ def generate_self_commentary(self, user_prompt: str, ollama_response: str, conversation_history: List[Dict]) -> Optional[str]:
81
+ """Generate self-commentary on own response"""
82
+ try:
83
+ commentary_prompt = self._create_self_commentary_prompt(user_prompt, ollama_response, conversation_history)
84
+ return self._retry_with_backoff(self._generate_impl, commentary_prompt, [])
85
+ except Exception as e:
86
+ logger.error(f"Ollama self-commentary generation failed: {e}")
87
+ return None
88
+
89
+ def _create_commentary_prompt(self, user_prompt: str, hf_response: str, conversation_history: List[Dict]) -> str:
90
+ """Create prompt for Ollama to comment on HF response"""
91
+ conversation_context = "\n".join([
92
+ f"{msg['role']}: {msg['content']}"
93
+ for msg in conversation_history[-3:] # Last 3 messages for context
94
+ ])
95
+
96
+ prompt = f"""
97
+ You are an AI mentor and conversation analyst. Your job is to analyze the interaction between a user and an expert AI, then provide insightful commentary.
98
+
99
+ ANALYZE THIS INTERACTION:
100
+ User Question: "{user_prompt}"
101
+ Expert Response: "{hf_response}"
102
+
103
+ Recent Conversation Context:
104
+ {conversation_context}
105
+
106
+ PROVIDE YOUR COMMENTARY IN THIS FORMAT:
107
+
108
+ I've reviewed the HF expert's response and here's my insight:
109
+
110
+ Key Points Observed:
111
+
112
+ [Point 1]
113
+ [Point 2]
114
+ My Perspective:
115
+ [Your commentary on the HF response]
116
+
117
+ Suggestions:
118
+
119
+ [Suggestion 1]
120
+ [Suggestion 2]
121
+
122
+ Keep your analysis concise but insightful. Focus on helping the user achieve their goals through better questioning and information gathering.
123
+ """
124
+ return prompt
125
+
126
+ def _create_self_commentary_prompt(self, user_prompt: str, ollama_response: str, conversation_history: List[Dict]) -> str:
127
+ """Create prompt for Ollama to comment on its own response"""
128
+ conversation_context = "\n".join([
129
+ f"{msg['role']}: {msg['content']}"
130
+ for msg in conversation_history[-3:] # Last 3 messages for context
131
+ ])
132
+
133
+ prompt = f"""
134
+ You are an AI mentor and conversation analyst. Your job is to analyze your own response to a user question, then provide insightful self-reflection.
135
+
136
+ ANALYZE YOUR RESPONSE:
137
+ User Question: "{user_prompt}"
138
+ Your Response: "{ollama_response}"
139
+
140
+ Recent Conversation Context:
141
+ {conversation_context}
142
+
143
+ PROVIDE YOUR SELF-COMMENTARY IN THIS FORMAT:
144
+
145
+ I've reviewed my own response and here's my self-reflection:
146
+
147
+ Key Points Addressed:
148
+
149
+ [Point 1]
150
+ [Point 2]
151
+ My Self-Assessment:
152
+ [Your reflection on your own response quality]
153
+
154
+ Areas for Improvement:
155
+
156
+ [Area 1]
157
+ [Area 2]
158
+
159
+ Keep your analysis honest and constructive. Focus on how you could have provided better assistance.
160
+ """
161
+ return prompt
162
+
163
  def _generate_impl(self, prompt: str, conversation_history: List[Dict]) -> str:
164
+ """Implementation of synchronous generation"""
165
  try:
166
  url = f"{self.host}/api/chat"
167
+ messages = conversation_history.copy()
168
+ messages.append({"role": "user", "content": prompt})
169
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  payload = {
171
  "model": self.model_name,
172
  "messages": messages,
 
183
  headers=self.headers,
184
  timeout=self.timeout
185
  )
186
+
187
  logger.info(f"Ollama response status: {response.status_code}")
188
  logger.info(f"Ollama response headers: {dict(response.headers)}")
189
 
 
191
  result = response.json()
192
  logger.info(f"Ollama response body: {result}")
193
 
 
194
  content = None
195
  if "message" in result and "content" in result["message"]:
196
  content = result["message"]["content"]
 
202
  logger.info(f"Extracted content length: {len(content) if content else 0}")
203
  return content if content else ""
204
 
205
+ except Exception as e:
206
  logger.error(f"Ollama API request error: {str(e)}")
207
  raise Exception(f"Ollama API error: {str(e)}")
 
 
 
208
 
209
  def _stream_generate_impl(self, prompt: str, conversation_history: List[Dict]) -> List[str]:
210
  """Implementation of streaming generation"""
211
  try:
 
 
 
212
  url = f"{self.host}/api/chat"
213
+ messages = conversation_history.copy()
214
+ messages.append({"role": "user", "content": prompt})
215
+
216
  payload = {
217
  "model": self.model_name,
218
+ "messages": messages,
219
  "stream": True
220
  }
221
 
 
244
  logger.error(f"Ollama stream generation failed: {e}")
245
  raise
246
 
247
+ # Global instance
248
+ ollama_provider = OllamaProvider(config.local_model_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/services/conversation_coordinator.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import logging
3
+ from typing import List, Dict, Optional
4
+ from src.llm.coordinated_provider import coordinated_provider
5
+ from core.session import session_manager
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class ConversationCoordinator:
10
+ """Service for managing conversation flow between HF and Ollama"""
11
+
12
+ def __init__(self):
13
+ self.conversation_state = {}
14
+
15
+ def coordinate_conversation(self, user_id: str, user_query: str) -> str:
16
+ """Main coordination method"""
17
+ try:
18
+ # Get conversation history
19
+ user_session = session_manager.get_session(user_id)
20
+ conversation_history = user_session.get("conversation", []).copy()
21
+
22
+ # Add current user message
23
+ conversation_history.append({"role": "user", "content": user_query})
24
+
25
+ # Generate coordinated response
26
+ response = coordinated_provider.generate(user_query, conversation_history)
27
+
28
+ # Update session with conversation
29
+ if response:
30
+ conversation = user_session.get("conversation", []).copy()
31
+ conversation.append({"role": "user", "content": user_query})
32
+ conversation.append({"role": "assistant", "content": response})
33
+ session_manager.update_session(user_id, {"conversation": conversation})
34
+
35
+ return response or "I'm processing your request..."
36
+
37
+ except Exception as e:
38
+ logger.error(f"Conversation coordination failed: {e}")
39
+ raise
40
+
41
+ def track_conversation_state(self, user_id: str, state: str, details: Dict = None):
42
+ """Track conversation state between providers"""
43
+ try:
44
+ if user_id not in self.conversation_state:
45
+ self.conversation_state[user_id] = []
46
+
47
+ state_entry = {
48
+ "state": state,
49
+ "timestamp": time.time(),
50
+ "details": details or {}
51
+ }
52
+ self.conversation_state[user_id].append(state_entry)
53
+
54
+ # Keep only last 50 state entries
55
+ if len(self.conversation_state[user_id]) > 50:
56
+ self.conversation_state[user_id] = self.conversation_state[user_id][-50:]
57
+
58
+ except Exception as e:
59
+ logger.warning(f"Failed to track conversation state: {e}")
60
+
61
+ def format_coordinated_response(self, hf_response: str, ollama_commentary: str) -> str:
62
+ """Format combined response with clear separation"""
63
+ return coordinated_provider._format_coordinated_response(hf_response, ollama_commentary)
64
+
65
+ def handle_error_state(self, user_id: str, error: Exception) -> str:
66
+ """Handle error conditions gracefully"""
67
+ try:
68
+ error_msg = str(error)
69
+ logger.error(f"Conversation coordinator error for user {user_id}: {error_msg}")
70
+
71
+ # Return user-friendly error message
72
+ return f"Sorry, I encountered an error while processing your request: {error_msg[:100]}..."
73
+
74
+ except Exception as e:
75
+ logger.error(f"Error handling failed: {e}")
76
+ return "Sorry, I encountered an unexpected error."
77
+
78
+ # Global instance
79
+ conversation_coordinator = ConversationCoordinator()
src/ui/chat_handler.py CHANGED
@@ -2,40 +2,31 @@ import streamlit as st
2
  import time
3
  import logging
4
  from typing import Optional
5
- from src.llm.factory import llm_factory, ProviderNotAvailableError
6
  from core.session import session_manager
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
  class ChatHandler:
11
- """Handles chat interactions with better UI feedback and session state handling"""
12
 
13
  def __init__(self):
14
  self.is_processing = False
15
 
16
  def process_user_message(self, user_input: str, selected_model: str):
17
- """Process user message with enhanced UI feedback and proper session state handling"""
18
  if not user_input or not user_input.strip():
19
  st.warning("Please enter a message")
20
  return
21
 
22
- # Prevent duplicate processing with proper session state access
23
- try:
24
- last_processed = st.session_state.get('last_processed_message', '')
25
- if last_processed == user_input:
26
- logger.info("Preventing duplicate message processing")
27
- return
28
- except Exception as e:
29
- logger.warning(f"Error accessing session state: {e}")
30
- # Continue processing even if session state access fails
31
-
32
- # Set processing flags with error handling
33
- try:
34
- st.session_state.is_processing = True
35
- st.session_state.last_processed_message = user_input
36
- except Exception as e:
37
- logger.error(f"Error setting session state: {e}")
38
 
 
 
 
39
  try:
40
  # Show user message immediately
41
  timestamp = time.strftime("%H:%M:%S")
@@ -43,33 +34,26 @@ class ChatHandler:
43
  st.markdown(user_input)
44
  st.caption(f"🕒 {timestamp}")
45
 
46
- # Add to session state history with error handling
47
- try:
48
- if "messages" not in st.session_state:
49
- st.session_state.messages = []
50
-
51
- st.session_state.messages.append({
52
- "role": "user",
53
- "content": user_input,
54
- "timestamp": timestamp
55
- })
56
- except Exception as e:
57
- logger.error(f"Error updating session state messages: {e}")
58
 
59
  # Force UI update
60
  st.experimental_rerun()
61
 
62
  except Exception as e:
63
  logger.error(f"Error in initial message display: {e}", exc_info=True)
64
- # Clear processing flags on error
65
- try:
66
- st.session_state.is_processing = False
67
- st.session_state.last_processed_message = ""
68
- except Exception as state_error:
69
- logger.error(f"Error clearing session state: {state_error}")
70
-
71
  def process_ai_response(self, user_input: str, selected_model: str):
72
- """Process AI response with enhanced timeout handling and proper session state access"""
73
  if not user_input or not user_input.strip():
74
  return
75
 
@@ -80,13 +64,10 @@ class ChatHandler:
80
  response_placeholder = st.empty()
81
 
82
  try:
83
- # Determine which provider will be used
84
- provider = llm_factory.get_provider()
85
- provider_name = "HF Endpoint" if "huggingface" in str(type(provider)).lower() else "Ollama"
86
-
87
- status_placeholder.info(f"🚀 Contacting {provider_name}...")
88
 
89
- # Get response
90
  response = None
91
  try:
92
  # Get session and conversation history
@@ -94,112 +75,56 @@ class ChatHandler:
94
  conversation_history = user_session.get("conversation", []).copy()
95
  conversation_history.append({"role": "user", "content": user_input})
96
 
97
- response = provider.generate(user_input, conversation_history)
98
  except Exception as e:
99
- logger.error(f"AI response error: {e}")
100
  raise
101
 
102
  if response and response.strip():
103
- status_placeholder.success("✅ Response received!")
104
  response_placeholder.markdown(response)
105
 
106
- # Add to session history with error handling
107
- try:
108
- timestamp = time.strftime("%H:%M:%S")
109
- provider_info = "hf_endpoint" if "huggingface" in str(type(provider)).lower() else "ollama"
110
-
111
- st.session_state.messages.append({
112
- "role": "assistant",
113
- "content": response,
114
- "timestamp": timestamp,
115
- "provider": provider_info
116
- })
117
-
118
- # Update backend session
119
- conversation = user_session.get("conversation", []).copy()
120
- conversation.append({"role": "user", "content": user_input})
121
- conversation.append({"role": "assistant", "content": response})
122
- session_manager.update_session("default_user", {"conversation": conversation})
123
-
124
- except Exception as session_error:
125
- logger.error(f"Error updating session: {session_error}")
126
- else:
127
- status_placeholder.warning("⚠️ Empty response received")
128
- response_placeholder.markdown("*No response generated. Please try again.*")
129
- try:
130
- timestamp = time.strftime("%H:%M:%S")
131
- st.session_state.messages.append({
132
- "role": "assistant",
133
- "content": "*No response generated. Please try again.*",
134
- "timestamp": timestamp
135
- })
136
- except Exception as state_error:
137
- logger.error(f"Error updating session state: {state_error}")
138
-
139
- except ProviderNotAvailableError as e:
140
- status_placeholder.error("❌ No AI providers available")
141
- response_placeholder.markdown("No AI providers are configured. Please check your settings.")
142
- try:
143
  timestamp = time.strftime("%H:%M:%S")
144
  st.session_state.messages.append({
145
  "role": "assistant",
146
- "content": "No AI providers are configured. Please check your settings.",
147
- "timestamp": timestamp
 
148
  })
149
- except Exception as state_error:
150
- logger.error(f"Error updating session state: {state_error}")
151
- logger.error(f"Provider not available: {e}")
152
-
153
- except Exception as e:
154
- # Better user-friendly error messages
155
- status_placeholder.error("❌ Request failed")
156
-
157
- # More specific error messages
158
- if "timeout" in str(e).lower() or "500" in str(e):
159
- error_message = ("⏰ Request timed out. This might be because:\n"
160
- "• Your Ollama server is not responding\n"
161
- "• Network connectivity issues\n"
162
- "• Try using the HF Endpoint instead (🟢 HF Endpoint: Available and ready)")
163
- elif "connection" in str(e).lower():
164
- error_message = ("🔌 Connection failed. This might be because:\n"
165
- "• Your Ollama server is offline\n"
166
- "• Incorrect Ollama URL\n"
167
- "• Network firewall blocking connection\n"
168
- "• Try using the HF Endpoint instead")
169
  else:
170
- error_message = f"Sorry, I encountered an error: {str(e)}"
171
-
172
- response_placeholder.markdown(error_message)
173
- try:
174
  timestamp = time.strftime("%H:%M:%S")
175
  st.session_state.messages.append({
176
  "role": "assistant",
177
- "content": error_message,
178
- "timestamp": timestamp
 
179
  })
180
- except Exception as state_error:
181
- logger.error(f"Error updating session state: {state_error}")
 
 
 
 
 
 
 
 
 
 
 
 
182
  logger.error(f"Chat processing error: {e}", exc_info=True)
183
 
184
  except Exception as e:
185
  logger.error(f"Unexpected error in process_ai_response: {e}", exc_info=True)
186
  st.error("An unexpected error occurred. Please try again.")
187
  finally:
188
- # Clear processing flags with error handling
189
- try:
190
- st.session_state.is_processing = False
191
- st.session_state.last_processed_message = ""
192
- except Exception as state_error:
193
- logger.error(f"Error clearing session state: {state_error}")
194
  time.sleep(0.1)
195
-
196
- def _get_provider_display_name(self, provider_name: str) -> str:
197
- """Get display name for provider"""
198
- display_names = {
199
- "ollama": "🦙 Ollama",
200
- "huggingface": "🤗 HF Endpoint"
201
- }
202
- return display_names.get(provider_name, provider_name)
203
 
204
  # Global instance
205
  chat_handler = ChatHandler()
 
2
  import time
3
  import logging
4
  from typing import Optional
5
+ from src.llm.coordinated_provider import coordinated_provider
6
  from core.session import session_manager
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
  class ChatHandler:
11
+ """Handles chat interactions with coordinated AI approach"""
12
 
13
  def __init__(self):
14
  self.is_processing = False
15
 
16
  def process_user_message(self, user_input: str, selected_model: str):
17
+ """Process user message with coordinated AI approach"""
18
  if not user_input or not user_input.strip():
19
  st.warning("Please enter a message")
20
  return
21
 
22
+ # Prevent duplicate processing
23
+ if st.session_state.get('last_processed_message') == user_input:
24
+ logger.info("Preventing duplicate message processing")
25
+ return
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ st.session_state.is_processing = True
28
+ st.session_state.last_processed_message = user_input
29
+
30
  try:
31
  # Show user message immediately
32
  timestamp = time.strftime("%H:%M:%S")
 
34
  st.markdown(user_input)
35
  st.caption(f"🕒 {timestamp}")
36
 
37
+ # Add to session state history
38
+ if "messages" not in st.session_state:
39
+ st.session_state.messages = []
40
+
41
+ st.session_state.messages.append({
42
+ "role": "user",
43
+ "content": user_input,
44
+ "timestamp": timestamp
45
+ })
 
 
 
46
 
47
  # Force UI update
48
  st.experimental_rerun()
49
 
50
  except Exception as e:
51
  logger.error(f"Error in initial message display: {e}", exc_info=True)
52
+ st.session_state.is_processing = False
53
+ st.session_state.last_processed_message = ""
54
+
 
 
 
 
55
  def process_ai_response(self, user_input: str, selected_model: str):
56
+ """Process AI response with coordinated approach"""
57
  if not user_input or not user_input.strip():
58
  return
59
 
 
64
  response_placeholder = st.empty()
65
 
66
  try:
67
+ # Show coordination status
68
+ status_placeholder.info("🚀 Coordinating AI responses...")
 
 
 
69
 
70
+ # Generate coordinated response
71
  response = None
72
  try:
73
  # Get session and conversation history
 
75
  conversation_history = user_session.get("conversation", []).copy()
76
  conversation_history.append({"role": "user", "content": user_input})
77
 
78
+ response = coordinated_provider.generate(user_input, conversation_history)
79
  except Exception as e:
80
+ logger.error(f"Coordinated response error: {e}")
81
  raise
82
 
83
  if response and response.strip():
84
+ status_placeholder.success("✅ Coordinated response received!")
85
  response_placeholder.markdown(response)
86
 
87
+ # Add to session history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  timestamp = time.strftime("%H:%M:%S")
89
  st.session_state.messages.append({
90
  "role": "assistant",
91
+ "content": response,
92
+ "timestamp": timestamp,
93
+ "provider": "coordinated"
94
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  else:
96
+ status_placeholder.warning("⚠️ Empty coordinated response received")
97
+ response_placeholder.markdown("*No coordinated response generated. Please try again.*")
 
 
98
  timestamp = time.strftime("%H:%M:%S")
99
  st.session_state.messages.append({
100
  "role": "assistant",
101
+ "content": "*No coordinated response generated. Please try again.*",
102
+ "timestamp": timestamp,
103
+ "provider": "coordinated"
104
  })
105
+
106
+ except Exception as e:
107
+ status_placeholder.error("❌ Coordination failed")
108
+
109
+ # User-friendly error messages
110
+ error_message = f"Sorry, I encountered an error: {str(e)[:100]}..."
111
+ response_placeholder.markdown(error_message)
112
+ timestamp = time.strftime("%H:%M:%S")
113
+ st.session_state.messages.append({
114
+ "role": "assistant",
115
+ "content": error_message,
116
+ "timestamp": timestamp,
117
+ "provider": "coordinated"
118
+ })
119
  logger.error(f"Chat processing error: {e}", exc_info=True)
120
 
121
  except Exception as e:
122
  logger.error(f"Unexpected error in process_ai_response: {e}", exc_info=True)
123
  st.error("An unexpected error occurred. Please try again.")
124
  finally:
125
+ st.session_state.is_processing = False
126
+ st.session_state.last_processed_message = ""
 
 
 
 
127
  time.sleep(0.1)
 
 
 
 
 
 
 
 
128
 
129
  # Global instance
130
  chat_handler = ChatHandler()