Implement Ollama-HF conversation coordination with expert-commentary approach
Browse files- src/llm/coordinated_provider.py +180 -0
- src/llm/ollama_provider.py +105 -56
- src/services/conversation_coordinator.py +79 -0
- src/ui/chat_handler.py +54 -129
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.
|
| 71 |
return False
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
def _generate_impl(self, prompt: str, conversation_history: List[Dict]) -> str:
|
| 74 |
-
"""Implementation of synchronous generation
|
| 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
|
| 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":
|
| 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 |
-
|
| 171 |
-
|
| 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.
|
| 6 |
from core.session import session_manager
|
| 7 |
|
| 8 |
logger = logging.getLogger(__name__)
|
| 9 |
|
| 10 |
class ChatHandler:
|
| 11 |
-
"""Handles chat interactions with
|
| 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
|
| 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 |
-
|
| 24 |
-
|
| 25 |
-
|
| 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
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 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 |
-
|
| 65 |
-
|
| 66 |
-
|
| 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
|
| 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 |
-
#
|
| 84 |
-
|
| 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 |
-
#
|
| 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 =
|
| 98 |
except Exception as e:
|
| 99 |
-
logger.error(f"
|
| 100 |
raise
|
| 101 |
|
| 102 |
if response and response.strip():
|
| 103 |
-
status_placeholder.success("✅
|
| 104 |
response_placeholder.markdown(response)
|
| 105 |
|
| 106 |
-
# Add to session history
|
| 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":
|
| 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 |
-
|
| 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":
|
| 178 |
-
"timestamp": timestamp
|
|
|
|
| 179 |
})
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 189 |
-
|
| 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()
|