Implemented live web search (Tavily), academic research access (Semantic Scholar), and evaluation dashboard with tabs
Browse files- AI-Life-Coach-Streamlit2 +1 -0
- app.py +435 -277
- core/coordinator.py +61 -66
- requirements.txt +1 -0
- services/research/papers.py +32 -0
- services/web_search.py +39 -0
AI-Life-Coach-Streamlit2
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Subproject commit ac83e06dd776b60459bb531d961b4f327d1e6a41
|
app.py
CHANGED
|
@@ -14,6 +14,7 @@ from core.session import session_manager
|
|
| 14 |
from core.memory import check_redis_health
|
| 15 |
from core.coordinator import coordinator
|
| 16 |
from core.errors import translate_error
|
|
|
|
| 17 |
import logging
|
| 18 |
|
| 19 |
# Set up logging
|
|
@@ -43,312 +44,469 @@ if "ngrok_url_temp" not in st.session_state:
|
|
| 43 |
if "hf_expert_requested" not in st.session_state:
|
| 44 |
st.session_state.hf_expert_requested = False
|
| 45 |
|
| 46 |
-
#
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
"Mistral 7B (Local)": "mistral:latest",
|
| 55 |
-
"Llama 2 7B (Local)": "llama2:latest",
|
| 56 |
-
"OpenChat 3.5 (Local)": "openchat:latest"
|
| 57 |
-
}
|
| 58 |
-
selected_model_name = st.selectbox(
|
| 59 |
-
"Select Model",
|
| 60 |
-
options=list(model_options.keys()),
|
| 61 |
-
index=0,
|
| 62 |
-
key="sidebar_model_select"
|
| 63 |
-
)
|
| 64 |
-
st.session_state.selected_model = model_options[selected_model_name]
|
| 65 |
-
|
| 66 |
-
st.divider()
|
| 67 |
-
|
| 68 |
-
# CONFIGURATION
|
| 69 |
-
st.subheader("⚙️ Configuration")
|
| 70 |
-
ngrok_url_input = st.text_input(
|
| 71 |
-
"Ollama Server URL",
|
| 72 |
-
value=st.session_state.ngrok_url_temp,
|
| 73 |
-
help="Enter your ngrok URL",
|
| 74 |
-
key="sidebar_ngrok_url"
|
| 75 |
-
)
|
| 76 |
-
|
| 77 |
-
if ngrok_url_input != st.session_state.ngrok_url_temp:
|
| 78 |
-
st.session_state.ngrok_url_temp = ngrok_url_input
|
| 79 |
-
st.success("✅ URL updated!")
|
| 80 |
-
|
| 81 |
-
if st.button("📡 Test Connection"):
|
| 82 |
-
try:
|
| 83 |
-
import requests
|
| 84 |
-
headers = {
|
| 85 |
-
"ngrok-skip-browser-warning": "true",
|
| 86 |
-
"User-Agent": "AI-Life-Coach-Test"
|
| 87 |
-
}
|
| 88 |
-
with st.spinner("Testing connection..."):
|
| 89 |
-
response = requests.get(
|
| 90 |
-
f"{ngrok_url_input}/api/tags",
|
| 91 |
-
headers=headers,
|
| 92 |
-
timeout=15
|
| 93 |
-
)
|
| 94 |
-
if response.status_code == 200:
|
| 95 |
-
st.success("✅ Connection successful!")
|
| 96 |
-
else:
|
| 97 |
-
st.error(f"❌ Failed: {response.status_code}")
|
| 98 |
-
except Exception as e:
|
| 99 |
-
st.error(f"❌ Error: {str(e)[:50]}...")
|
| 100 |
-
|
| 101 |
-
if st.button("🗑️ Clear History"):
|
| 102 |
-
st.session_state.messages = []
|
| 103 |
-
st.success("History cleared!")
|
| 104 |
-
|
| 105 |
-
st.divider()
|
| 106 |
-
|
| 107 |
-
# ADVANCED FEATURES
|
| 108 |
-
with st.expander("🔍 Advanced Features", expanded=False):
|
| 109 |
-
st.subheader("📊 System Monitor")
|
| 110 |
-
try:
|
| 111 |
-
from services.ollama_monitor import check_ollama_status
|
| 112 |
-
ollama_status = check_ollama_status()
|
| 113 |
-
if ollama_status.get("running"):
|
| 114 |
-
st.success("🦙 Ollama: Running")
|
| 115 |
-
else:
|
| 116 |
-
st.warning("🦙 Ollama: Not running")
|
| 117 |
-
except:
|
| 118 |
-
st.info("🦙 Ollama: Unknown")
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
st.divider()
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
-
# Main interface
|
| 156 |
-
st.title("🧠 AI Life Coach")
|
| 157 |
-
st.markdown("Ask me anything about personal development, goal setting, or life advice!")
|
| 158 |
|
| 159 |
-
# Display messages
|
| 160 |
-
for message in st.session_state.messages:
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
|
| 171 |
-
# Manual HF Analysis Section
|
| 172 |
-
if st.session_state.messages and len(st.session_state.messages) > 0:
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
# HF Expert Section
|
| 176 |
-
with st.expander("�� HF Expert Analysis", expanded=False):
|
| 177 |
-
st.subheader("Deep Conversation Analysis")
|
| 178 |
|
| 179 |
-
|
| 180 |
-
with
|
| 181 |
-
st.
|
| 182 |
-
**HF Expert Features:**
|
| 183 |
-
- Analyzes entire conversation history
|
| 184 |
-
- Performs web research when needed
|
| 185 |
-
- Provides deep insights and recommendations
|
| 186 |
-
- Acts as expert consultant in your conversation
|
| 187 |
-
""")
|
| 188 |
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
with
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
"
|
| 222 |
-
conversation_history
|
| 223 |
-
)
|
| 224 |
-
|
| 225 |
-
if hf_analysis:
|
| 226 |
-
# Display HF expert response with clear indication
|
| 227 |
-
with st.chat_message("assistant"):
|
| 228 |
-
st.markdown("### 🤖 HF Expert Analysis")
|
| 229 |
-
st.markdown(hf_analysis)
|
| 230 |
|
| 231 |
-
#
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
# Perform web search
|
| 237 |
-
with st.spinner("🔎 Searching for current information..."):
|
| 238 |
-
# Add web search logic here
|
| 239 |
-
st.success("✅ Web research completed!")
|
| 240 |
|
| 241 |
-
#
|
| 242 |
-
|
| 243 |
-
"
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
"source": "hf_expert",
|
| 247 |
-
"research_needs": research_needs
|
| 248 |
-
})
|
| 249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
st.session_state.hf_expert_requested = False
|
| 251 |
-
|
| 252 |
-
except Exception as e:
|
| 253 |
-
user_msg = translate_error(e)
|
| 254 |
-
st.error(f"❌ HF Expert analysis failed: {user_msg}")
|
| 255 |
-
st.session_state.hf_expert_requested = False
|
| 256 |
|
| 257 |
-
# Chat input - FIXED VERSION
|
| 258 |
-
user_input = st.chat_input("Type your message here...", disabled=st.session_state.is_processing)
|
| 259 |
|
| 260 |
-
# Process message when received
|
| 261 |
-
if user_input and not st.session_state.is_processing:
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
# Display user message
|
| 265 |
-
with st.chat_message("user"):
|
| 266 |
-
st.markdown(user_input)
|
| 267 |
-
|
| 268 |
-
st.session_state.messages.append({
|
| 269 |
-
"role": "user",
|
| 270 |
-
"content": user_input,
|
| 271 |
-
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 272 |
-
})
|
| 273 |
-
|
| 274 |
-
# Process AI response
|
| 275 |
-
with st.chat_message("assistant"):
|
| 276 |
-
response_placeholder = st.empty()
|
| 277 |
-
status_placeholder = st.empty()
|
| 278 |
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
try:
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
| 297 |
|
| 298 |
-
if ai_response:
|
| 299 |
-
response_placeholder.markdown(ai_response)
|
| 300 |
-
status_placeholder.success("✅ Response received!")
|
| 301 |
-
else:
|
| 302 |
-
status_placeholder.warning("⚠️ Empty response from Ollama")
|
| 303 |
-
|
| 304 |
-
except Exception as ollama_error:
|
| 305 |
-
user_msg = translate_error(ollama_error)
|
| 306 |
-
status_placeholder.error(f"⚠️ {user_msg}")
|
| 307 |
-
|
| 308 |
-
# Fallback to HF if available
|
| 309 |
-
if config.hf_token and not ai_response:
|
| 310 |
-
status_placeholder.info(PROCESSING_STAGES["hf_init"])
|
| 311 |
try:
|
| 312 |
-
ai_response =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
if ai_response:
|
| 314 |
response_placeholder.markdown(ai_response)
|
| 315 |
-
status_placeholder.success("✅
|
| 316 |
else:
|
| 317 |
-
status_placeholder.
|
| 318 |
-
|
| 319 |
-
|
|
|
|
| 320 |
status_placeholder.error(f"⚠️ {user_msg}")
|
| 321 |
-
|
| 322 |
-
# Save response if successful
|
| 323 |
-
if ai_response:
|
| 324 |
-
# Update conversation history
|
| 325 |
-
conversation.append({"role": "user", "content": user_input})
|
| 326 |
-
conversation.append({"role": "assistant", "content": ai_response})
|
| 327 |
-
user_session["conversation"] = conversation
|
| 328 |
-
session_manager.update_session("default_user", user_session)
|
| 329 |
|
| 330 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
st.session_state.messages.append({
|
| 332 |
"role": "assistant",
|
| 333 |
-
"content":
|
| 334 |
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 335 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
else:
|
| 337 |
-
st.
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
st.
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
from core.memory import check_redis_health
|
| 15 |
from core.coordinator import coordinator
|
| 16 |
from core.errors import translate_error
|
| 17 |
+
from services.research.papers import find_papers
|
| 18 |
import logging
|
| 19 |
|
| 20 |
# Set up logging
|
|
|
|
| 44 |
if "hf_expert_requested" not in st.session_state:
|
| 45 |
st.session_state.hf_expert_requested = False
|
| 46 |
|
| 47 |
+
# Add evaluation dashboard tab
|
| 48 |
+
tab1, tab2, tab3 = st.tabs(["💬 Chat", "🔬 Evaluate AI", "📊 Reports"])
|
| 49 |
+
|
| 50 |
+
with tab1:
|
| 51 |
+
# Sidebar layout redesign
|
| 52 |
+
with st.sidebar:
|
| 53 |
+
st.title("🧠 AI Life Coach")
|
| 54 |
+
st.markdown("Your personal AI-powered life development assistant")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
# PRIMARY ACTIONS
|
| 57 |
+
st.subheader("💬 Primary Actions")
|
| 58 |
+
model_options = {
|
| 59 |
+
"Mistral 7B (Local)": "mistral:latest",
|
| 60 |
+
"Llama 2 7B (Local)": "llama2:latest",
|
| 61 |
+
"OpenChat 3.5 (Local)": "openchat:latest"
|
| 62 |
+
}
|
| 63 |
+
selected_model_name = st.selectbox(
|
| 64 |
+
"Select Model",
|
| 65 |
+
options=list(model_options.keys()),
|
| 66 |
+
index=0,
|
| 67 |
+
key="sidebar_model_select"
|
| 68 |
+
)
|
| 69 |
+
st.session_state.selected_model = model_options[selected_model_name]
|
| 70 |
|
| 71 |
+
st.divider()
|
| 72 |
+
|
| 73 |
+
# CONFIGURATION
|
| 74 |
+
st.subheader("⚙️ Configuration")
|
| 75 |
+
ngrok_url_input = st.text_input(
|
| 76 |
+
"Ollama Server URL",
|
| 77 |
+
value=st.session_state.ngrok_url_temp,
|
| 78 |
+
help="Enter your ngrok URL",
|
| 79 |
+
key="sidebar_ngrok_url"
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
if ngrok_url_input != st.session_state.ngrok_url_temp:
|
| 83 |
+
st.session_state.ngrok_url_temp = ngrok_url_input
|
| 84 |
+
st.success("✅ URL updated!")
|
| 85 |
+
|
| 86 |
+
if st.button("📡 Test Connection"):
|
| 87 |
+
try:
|
| 88 |
+
import requests
|
| 89 |
+
headers = {
|
| 90 |
+
"ngrok-skip-browser-warning": "true",
|
| 91 |
+
"User-Agent": "AI-Life-Coach-Test"
|
| 92 |
+
}
|
| 93 |
+
with st.spinner("Testing connection..."):
|
| 94 |
+
response = requests.get(
|
| 95 |
+
f"{ngrok_url_input}/api/tags",
|
| 96 |
+
headers=headers,
|
| 97 |
+
timeout=15
|
| 98 |
+
)
|
| 99 |
+
if response.status_code == 200:
|
| 100 |
+
st.success("✅ Connection successful!")
|
| 101 |
+
else:
|
| 102 |
+
st.error(f"❌ Failed: {response.status_code}")
|
| 103 |
+
except Exception as e:
|
| 104 |
+
st.error(f"❌ Error: {str(e)[:50]}...")
|
| 105 |
+
|
| 106 |
+
if st.button("🗑️ Clear History"):
|
| 107 |
+
st.session_state.messages = []
|
| 108 |
+
st.success("History cleared!")
|
| 109 |
|
| 110 |
st.divider()
|
| 111 |
|
| 112 |
+
# ADVANCED FEATURES
|
| 113 |
+
with st.expander("🔍 Advanced Features", expanded=False):
|
| 114 |
+
st.subheader("📊 System Monitor")
|
| 115 |
+
try:
|
| 116 |
+
from services.ollama_monitor import check_ollama_status
|
| 117 |
+
ollama_status = check_ollama_status()
|
| 118 |
+
if ollama_status.get("running"):
|
| 119 |
+
st.success("🦙 Ollama: Running")
|
| 120 |
+
else:
|
| 121 |
+
st.warning("🦙 Ollama: Not running")
|
| 122 |
+
except:
|
| 123 |
+
st.info("🦙 Ollama: Unknown")
|
| 124 |
+
|
| 125 |
+
try:
|
| 126 |
+
from services.hf_endpoint_monitor import hf_monitor
|
| 127 |
+
hf_status = hf_monitor.check_endpoint_status()
|
| 128 |
+
if hf_status['available']:
|
| 129 |
+
st.success("🤗 HF: Available")
|
| 130 |
+
else:
|
| 131 |
+
st.warning("🤗 HF: Not available")
|
| 132 |
+
except:
|
| 133 |
+
st.info("🤗 HF: Unknown")
|
| 134 |
+
|
| 135 |
+
if check_redis_health():
|
| 136 |
+
st.success("💾 Redis: Connected")
|
| 137 |
+
else:
|
| 138 |
+
st.error("💾 Redis: Disconnected")
|
| 139 |
+
|
| 140 |
+
st.divider()
|
| 141 |
+
|
| 142 |
+
st.subheader("🤖 HF Expert Analysis")
|
| 143 |
+
col1, col2 = st.columns([3, 1])
|
| 144 |
+
with col1:
|
| 145 |
+
st.markdown("""
|
| 146 |
+
**HF Expert Features:**
|
| 147 |
+
- Analyzes entire conversation history
|
| 148 |
+
- Performs web research when needed
|
| 149 |
+
- Provides deep insights and recommendations
|
| 150 |
+
- Acts as expert consultant in your conversation
|
| 151 |
+
""")
|
| 152 |
+
with col2:
|
| 153 |
+
if st.button("🧠 Activate HF Expert",
|
| 154 |
+
key="activate_hf_expert_sidebar",
|
| 155 |
+
help="Send conversation to HF endpoint for deep analysis",
|
| 156 |
+
use_container_width=True,
|
| 157 |
+
disabled=st.session_state.is_processing):
|
| 158 |
+
st.session_state.hf_expert_requested = True
|
| 159 |
|
| 160 |
+
# Main interface
|
| 161 |
+
st.title("🧠 AI Life Coach")
|
| 162 |
+
st.markdown("Ask me anything about personal development, goal setting, or life advice!")
|
| 163 |
|
| 164 |
+
# Display messages
|
| 165 |
+
for message in st.session_state.messages:
|
| 166 |
+
with st.chat_message(message["role"]):
|
| 167 |
+
# Format HF expert messages differently
|
| 168 |
+
if message.get("source") == "hf_expert":
|
| 169 |
+
st.markdown("### 🤖 HF Expert Analysis")
|
| 170 |
+
st.markdown(message["content"])
|
| 171 |
+
else:
|
| 172 |
+
st.markdown(message["content"])
|
| 173 |
+
if "timestamp" in message:
|
| 174 |
+
st.caption(f"🕒 {message['timestamp']}")
|
| 175 |
|
| 176 |
+
# Manual HF Analysis Section
|
| 177 |
+
if st.session_state.messages and len(st.session_state.messages) > 0:
|
| 178 |
+
st.divider()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
+
# HF Expert Section
|
| 181 |
+
with st.expander("🤖 HF Expert Analysis", expanded=False):
|
| 182 |
+
st.subheader("Deep Conversation Analysis")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
col1, col2 = st.columns([3, 1])
|
| 185 |
+
with col1:
|
| 186 |
+
st.markdown("""
|
| 187 |
+
**HF Expert Features:**
|
| 188 |
+
- Analyzes entire conversation history
|
| 189 |
+
- Performs web research when needed
|
| 190 |
+
- Provides deep insights and recommendations
|
| 191 |
+
- Acts as expert consultant in your conversation
|
| 192 |
+
""")
|
| 193 |
+
|
| 194 |
+
# Show conversation preview
|
| 195 |
+
st.markdown("**Conversation Preview for HF Expert:**")
|
| 196 |
+
st.markdown("---")
|
| 197 |
+
for i, msg in enumerate(st.session_state.messages[-5:]): # Last 5 messages
|
| 198 |
+
role = "👤 You" if msg["role"] == "user" else "🤖 Assistant"
|
| 199 |
+
st.markdown(f"**{role}:** {msg['content'][:100]}{'...' if len(msg['content']) > 100 else ''}")
|
| 200 |
+
st.markdown("---")
|
| 201 |
+
|
| 202 |
+
with col2:
|
| 203 |
+
if st.button("🧠 Activate HF Expert",
|
| 204 |
+
key="activate_hf_expert",
|
| 205 |
+
help="Send conversation to HF endpoint for deep analysis",
|
| 206 |
+
use_container_width=True,
|
| 207 |
+
disabled=st.session_state.is_processing):
|
| 208 |
+
st.session_state.hf_expert_requested = True
|
| 209 |
+
|
| 210 |
+
# Show HF expert analysis when requested
|
| 211 |
+
if st.session_state.get("hf_expert_requested", False):
|
| 212 |
+
with st.spinner("🧠 HF Expert analyzing conversation..."):
|
| 213 |
+
try:
|
| 214 |
+
# Get conversation history
|
| 215 |
+
user_session = session_manager.get_session("default_user")
|
| 216 |
+
conversation_history = user_session.get("conversation", [])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
+
# Show what HF expert sees
|
| 219 |
+
with st.expander("📋 HF Expert Input", expanded=False):
|
| 220 |
+
st.markdown("**Conversation History Sent to HF Expert:**")
|
| 221 |
+
for i, msg in enumerate(conversation_history[-10:]): # Last 10 messages
|
| 222 |
+
st.markdown(f"**{msg['role'].capitalize()}:** {msg['content'][:100]}{'...' if len(msg['content']) > 100 else ''}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
+
# Request HF analysis
|
| 225 |
+
hf_analysis = coordinator.manual_hf_analysis(
|
| 226 |
+
"default_user",
|
| 227 |
+
conversation_history
|
| 228 |
+
)
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
+
if hf_analysis:
|
| 231 |
+
# Display HF expert response with clear indication
|
| 232 |
+
with st.chat_message("assistant"):
|
| 233 |
+
st.markdown("### 🤖 HF Expert Analysis")
|
| 234 |
+
st.markdown(hf_analysis)
|
| 235 |
+
|
| 236 |
+
# Add research/web search decisions
|
| 237 |
+
research_needs = coordinator.determine_web_search_needs(conversation_history)
|
| 238 |
+
if research_needs["needs_search"]:
|
| 239 |
+
st.info(f"🔍 **Research Needed:** {research_needs['reasoning']}")
|
| 240 |
+
if st.button("🔎 Perform Web Research", key="web_research_button"):
|
| 241 |
+
# Perform web search
|
| 242 |
+
with st.spinner("🔎 Searching for current information..."):
|
| 243 |
+
# Add web search logic here
|
| 244 |
+
st.success("✅ Web research completed!")
|
| 245 |
+
|
| 246 |
+
# Add to message history with HF expert tag
|
| 247 |
+
st.session_state.messages.append({
|
| 248 |
+
"role": "assistant",
|
| 249 |
+
"content": hf_analysis,
|
| 250 |
+
"timestamp": datetime.now().strftime("%H:%M:%S"),
|
| 251 |
+
"source": "hf_expert",
|
| 252 |
+
"research_needs": research_needs
|
| 253 |
+
})
|
| 254 |
+
|
| 255 |
+
st.session_state.hf_expert_requested = False
|
| 256 |
+
|
| 257 |
+
except Exception as e:
|
| 258 |
+
user_msg = translate_error(e)
|
| 259 |
+
st.error(f"❌ HF Expert analysis failed: {user_msg}")
|
| 260 |
st.session_state.hf_expert_requested = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
+
# Chat input - FIXED VERSION
|
| 263 |
+
user_input = st.chat_input("Type your message here...", disabled=st.session_state.is_processing)
|
| 264 |
|
| 265 |
+
# Process message when received
|
| 266 |
+
if user_input and not st.session_state.is_processing:
|
| 267 |
+
st.session_state.is_processing = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
+
# Display user message
|
| 270 |
+
with st.chat_message("user"):
|
| 271 |
+
st.markdown(user_input)
|
| 272 |
+
|
| 273 |
+
st.session_state.messages.append({
|
| 274 |
+
"role": "user",
|
| 275 |
+
"content": user_input,
|
| 276 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 277 |
+
})
|
| 278 |
+
|
| 279 |
+
# Process AI response
|
| 280 |
+
with st.chat_message("assistant"):
|
| 281 |
+
response_placeholder = st.empty()
|
| 282 |
+
status_placeholder = st.empty()
|
| 283 |
|
| 284 |
try:
|
| 285 |
+
# Get conversation history
|
| 286 |
+
user_session = session_manager.get_session("default_user")
|
| 287 |
+
conversation = user_session.get("conversation", [])
|
| 288 |
+
conversation_history = conversation[-5:] # Last 5 messages
|
| 289 |
+
conversation_history.append({"role": "user", "content": user_input})
|
| 290 |
+
|
| 291 |
+
# Try Ollama with proper error handling
|
| 292 |
+
status_placeholder.info(PROCESSING_STAGES["ollama"])
|
| 293 |
+
ai_response = None
|
| 294 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
try:
|
| 296 |
+
ai_response = send_to_ollama(
|
| 297 |
+
user_input,
|
| 298 |
+
conversation_history,
|
| 299 |
+
st.session_state.ngrok_url_temp,
|
| 300 |
+
st.session_state.selected_model
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
if ai_response:
|
| 304 |
response_placeholder.markdown(ai_response)
|
| 305 |
+
status_placeholder.success("✅ Response received!")
|
| 306 |
else:
|
| 307 |
+
status_placeholder.warning("⚠️ Empty response from Ollama")
|
| 308 |
+
|
| 309 |
+
except Exception as ollama_error:
|
| 310 |
+
user_msg = translate_error(ollama_error)
|
| 311 |
status_placeholder.error(f"⚠️ {user_msg}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
|
| 313 |
+
# Fallback to HF if available
|
| 314 |
+
if config.hf_token and not ai_response:
|
| 315 |
+
status_placeholder.info(PROCESSING_STAGES["hf_init"])
|
| 316 |
+
try:
|
| 317 |
+
ai_response = send_to_hf(user_input, conversation_history)
|
| 318 |
+
if ai_response:
|
| 319 |
+
response_placeholder.markdown(ai_response)
|
| 320 |
+
status_placeholder.success("✅ HF response received!")
|
| 321 |
+
else:
|
| 322 |
+
status_placeholder.error("❌ No response from HF")
|
| 323 |
+
except Exception as hf_error:
|
| 324 |
+
user_msg = translate_error(hf_error)
|
| 325 |
+
status_placeholder.error(f"⚠️ {user_msg}")
|
| 326 |
+
|
| 327 |
+
# Save response if successful
|
| 328 |
+
if ai_response:
|
| 329 |
+
# Update conversation history
|
| 330 |
+
conversation.append({"role": "user", "content": user_input})
|
| 331 |
+
conversation.append({"role": "assistant", "content": ai_response})
|
| 332 |
+
user_session["conversation"] = conversation
|
| 333 |
+
session_manager.update_session("default_user", user_session)
|
| 334 |
+
|
| 335 |
+
# Add to message history
|
| 336 |
+
st.session_state.messages.append({
|
| 337 |
+
"role": "assistant",
|
| 338 |
+
"content": ai_response,
|
| 339 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 340 |
+
})
|
| 341 |
+
else:
|
| 342 |
+
st.session_state.messages.append({
|
| 343 |
+
"role": "assistant",
|
| 344 |
+
"content": "Sorry, I couldn't process your request. Please try again.",
|
| 345 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 346 |
+
})
|
| 347 |
+
|
| 348 |
+
except Exception as e:
|
| 349 |
+
user_msg = translate_error(e)
|
| 350 |
+
response_placeholder.error(f"⚠️ {user_msg}")
|
| 351 |
st.session_state.messages.append({
|
| 352 |
"role": "assistant",
|
| 353 |
+
"content": f"⚠️ {user_msg}",
|
| 354 |
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 355 |
})
|
| 356 |
+
finally:
|
| 357 |
+
st.session_state.is_processing = False
|
| 358 |
+
time.sleep(0.5) # Brief pause
|
| 359 |
+
st.experimental_rerun()
|
| 360 |
+
|
| 361 |
+
with tab2:
|
| 362 |
+
st.header("🔬 AI Behavior Evaluator")
|
| 363 |
+
st.markdown("Run sample prompts to observe AI behavior.")
|
| 364 |
+
|
| 365 |
+
eval_prompts = [
|
| 366 |
+
"What is the capital of France?",
|
| 367 |
+
"What day is it today?",
|
| 368 |
+
"Tell me about recent climate policy changes.",
|
| 369 |
+
"Explain CRISPR gene editing simply.",
|
| 370 |
+
"Can vitamin D prevent flu infections?"
|
| 371 |
+
]
|
| 372 |
+
|
| 373 |
+
selected_prompt = st.selectbox("Choose a test prompt:", eval_prompts)
|
| 374 |
+
custom_prompt = st.text_input("Or enter your own:", "")
|
| 375 |
+
|
| 376 |
+
final_prompt = custom_prompt or selected_prompt
|
| 377 |
+
|
| 378 |
+
if st.button("Evaluate"):
|
| 379 |
+
with st.spinner("Running evaluation..."):
|
| 380 |
+
start_time = time.time()
|
| 381 |
+
|
| 382 |
+
# Simulate sending to coordinator
|
| 383 |
+
from core.session import session_manager
|
| 384 |
+
user_session = session_manager.get_session("eval_user")
|
| 385 |
+
history = user_session.get("conversation", [])
|
| 386 |
+
|
| 387 |
+
try:
|
| 388 |
+
ai_response = send_to_ollama(final_prompt, history, st.session_state.ngrok_url_temp, st.session_state.selected_model)
|
| 389 |
+
duration = round(time.time() - start_time, 2)
|
| 390 |
+
|
| 391 |
+
st.success(f"✅ Response generated in {duration}s")
|
| 392 |
+
st.markdown("**Response:**")
|
| 393 |
+
st.write(ai_response)
|
| 394 |
+
|
| 395 |
+
st.markdown("**Analysis Tags:**")
|
| 396 |
+
tags = []
|
| 397 |
+
if "today" in final_prompt.lower() or "date" in final_prompt.lower():
|
| 398 |
+
tags.append("📅 Date Awareness")
|
| 399 |
+
if any(word in final_prompt.lower() for word in ["news", "latest", "breaking"]):
|
| 400 |
+
tags.append("🌐 Web Search Needed")
|
| 401 |
+
if any(word in final_prompt.lower() for word in ["vitamin", "drug", "metformin", "CRISPR"]):
|
| 402 |
+
tags.append("🧬 Scientific Knowledge")
|
| 403 |
+
st.write(", ".join(tags) if tags else "General Knowledge")
|
| 404 |
+
|
| 405 |
+
# Show research papers if scientific topic
|
| 406 |
+
research_keywords = ["study", "research", "paper", "effectiveness", "clinical trial", "vitamin", "drug", "metformin", "CRISPR"]
|
| 407 |
+
if any(kw in final_prompt.lower() for kw in research_keywords):
|
| 408 |
+
st.markdown("**Related Research Papers:**")
|
| 409 |
+
with st.spinner("Searching for academic papers..."):
|
| 410 |
+
try:
|
| 411 |
+
papers = find_papers(final_prompt, limit=3)
|
| 412 |
+
if papers:
|
| 413 |
+
for i, paper in enumerate(papers):
|
| 414 |
+
with st.expander(f"📄 {paper['title'][:50]}{'...' if len(paper['title']) > 50 else ''}"):
|
| 415 |
+
st.markdown(f"**Authors:** {', '.join(paper['authors'][:3])}")
|
| 416 |
+
st.markdown(f"**Year:** {paper['year']}")
|
| 417 |
+
st.markdown(f"**Citations:** {paper['citation_count']}")
|
| 418 |
+
st.markdown(f"**Venue:** {paper['venue']}")
|
| 419 |
+
st.markdown(f"**Abstract:** {paper['abstract'][:300]}{'...' if len(paper['abstract']) > 300 else ''}")
|
| 420 |
+
st.markdown(f"[View Paper]({paper['url']})")
|
| 421 |
+
else:
|
| 422 |
+
st.info("No relevant papers found for this topic.")
|
| 423 |
+
except Exception as e:
|
| 424 |
+
st.warning(f"Could not fetch research papers: {translate_error(e)}")
|
| 425 |
+
|
| 426 |
+
except Exception as e:
|
| 427 |
+
st.error(f"Evaluation failed: {translate_error(e)}")
|
| 428 |
+
|
| 429 |
+
with tab3:
|
| 430 |
+
st.header("📊 Performance Reports")
|
| 431 |
+
st.markdown("System performance metrics and usage analytics.")
|
| 432 |
+
|
| 433 |
+
# System status
|
| 434 |
+
st.subheader("System Status")
|
| 435 |
+
col1, col2, col3 = st.columns(3)
|
| 436 |
+
|
| 437 |
+
with col1:
|
| 438 |
+
try:
|
| 439 |
+
from services.ollama_monitor import check_ollama_status
|
| 440 |
+
ollama_status = check_ollama_status()
|
| 441 |
+
if ollama_status.get("running"):
|
| 442 |
+
st.success("🦙 Ollama: Running")
|
| 443 |
else:
|
| 444 |
+
st.warning("🦙 Ollama: Not running")
|
| 445 |
+
except:
|
| 446 |
+
st.info("🦙 Ollama: Unknown")
|
| 447 |
+
|
| 448 |
+
with col2:
|
| 449 |
+
try:
|
| 450 |
+
from services.hf_endpoint_monitor import hf_monitor
|
| 451 |
+
hf_status = hf_monitor.check_endpoint_status()
|
| 452 |
+
if hf_status['available']:
|
| 453 |
+
st.success("🤗 HF: Available")
|
| 454 |
+
else:
|
| 455 |
+
st.warning("🤗 HF: Not available")
|
| 456 |
+
except:
|
| 457 |
+
st.info("🤗 HF: Unknown")
|
| 458 |
+
|
| 459 |
+
with col3:
|
| 460 |
+
if check_redis_health():
|
| 461 |
+
st.success("💾 Redis: Connected")
|
| 462 |
+
else:
|
| 463 |
+
st.error("💾 Redis: Disconnected")
|
| 464 |
+
|
| 465 |
+
# Session statistics
|
| 466 |
+
st.subheader("Session Statistics")
|
| 467 |
+
try:
|
| 468 |
+
user_session = session_manager.get_session("default_user")
|
| 469 |
+
conversation = user_session.get("conversation", [])
|
| 470 |
+
st.metric("Total Messages", len(conversation))
|
| 471 |
|
| 472 |
+
coord_stats = user_session.get('ai_coordination', {})
|
| 473 |
+
if coord_stats:
|
| 474 |
+
st.metric("AI Requests Processed", coord_stats.get('requests_processed', 0))
|
| 475 |
+
st.metric("Ollama Responses", coord_stats.get('ollama_responses', 0))
|
| 476 |
+
st.metric("HF Responses", coord_stats.get('hf_responses', 0))
|
| 477 |
+
else:
|
| 478 |
+
st.info("No coordination statistics available yet.")
|
| 479 |
+
except Exception as e:
|
| 480 |
+
st.warning(f"Could not load session statistics: {translate_error(e)}")
|
| 481 |
+
|
| 482 |
+
# Recent activity
|
| 483 |
+
st.subheader("Recent Activity")
|
| 484 |
+
try:
|
| 485 |
+
recent_activities = coordinator.get_recent_activities("default_user")
|
| 486 |
+
if recent_activities and recent_activities.get('last_request'):
|
| 487 |
+
st.markdown(f"**Last Request:** {recent_activities['last_request']}")
|
| 488 |
+
st.markdown(f"**Requests Processed:** {recent_activities['requests_processed']}")
|
| 489 |
+
st.markdown(f"**Ollama Responses:** {recent_activities['ollama_responses']}")
|
| 490 |
+
st.markdown(f"**HF Responses:** {recent_activities['hf_responses']}")
|
| 491 |
+
else:
|
| 492 |
+
st.info("No recent activity recorded.")
|
| 493 |
+
except Exception as e:
|
| 494 |
+
st.warning(f"Could not load recent activity: {translate_error(e)}")
|
| 495 |
+
|
| 496 |
+
# Configuration summary
|
| 497 |
+
st.subheader("Configuration Summary")
|
| 498 |
+
st.markdown(f"**Environment:** {'HF Space' if config.is_hf_space else 'Local'}")
|
| 499 |
+
st.markdown(f"**Primary Model:** {config.local_model_name or 'Not set'}")
|
| 500 |
+
st.markdown(f"**Ollama Host:** {config.ollama_host or 'Not configured'}")
|
| 501 |
+
|
| 502 |
+
features = []
|
| 503 |
+
if config.use_fallback:
|
| 504 |
+
features.append("Fallback Mode")
|
| 505 |
+
if config.hf_token:
|
| 506 |
+
features.append("HF Deep Analysis")
|
| 507 |
+
if os.getenv("TAVILY_API_KEY"):
|
| 508 |
+
features.append("Web Search")
|
| 509 |
+
if config.openweather_api_key:
|
| 510 |
+
features.append("Weather Data")
|
| 511 |
+
|
| 512 |
+
st.markdown(f"**Active Features:** {', '.join(features) if features else 'None'}")
|
core/coordinator.py
CHANGED
|
@@ -5,6 +5,7 @@ from core.llm_factory import llm_factory
|
|
| 5 |
from core.session import session_manager
|
| 6 |
from services.hf_endpoint_monitor import hf_monitor
|
| 7 |
from services.weather import weather_service
|
|
|
|
| 8 |
try:
|
| 9 |
from tavily import TavilyClient
|
| 10 |
TAVILY_AVAILABLE = True
|
|
@@ -19,25 +20,25 @@ logger = logging.getLogger(__name__)
|
|
| 19 |
|
| 20 |
class AICoordinator:
|
| 21 |
"""Hierarchical multi-model coordinator with HF endpoint as authoritative layer"""
|
| 22 |
-
|
| 23 |
def __init__(self):
|
| 24 |
self.tavily_client = None
|
| 25 |
if TAVILY_AVAILABLE and os.getenv("TAVILY_API_KEY"):
|
| 26 |
self.tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
| 27 |
-
|
| 28 |
# System instructions for hierarchical behavior
|
| 29 |
self.system_instructions = {
|
| 30 |
'ollama_role': """You are an AI assistant that works in coordination with a more powerful uncensored Hugging Face model.
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
'hf_role': """You are an uncensored, authoritative AI with 8192 token capacity and streaming capability. Your role is to: 1. Provide deep, uncensored analysis and insights 2. Supersede the local Ollama model's logic when there's disagreement 3. Think thoroughly and provide comprehensive responses 4. Use your full 8192 token capacity for detailed analysis 5. Stream your responses for real-time delivery 6. Build upon the conversation context and Ollama's initial response 7. Provide authoritative answers that take precedence"""
|
| 35 |
}
|
| 36 |
|
| 37 |
def determine_web_search_needs(self, conversation_history: List[Dict]) -> Dict:
|
| 38 |
"""Determine if web search is needed based on conversation content"""
|
| 39 |
conversation_text = " ".join([msg.get("content", "") for msg in conversation_history])
|
| 40 |
-
|
| 41 |
# Topics that typically need current information
|
| 42 |
current_info_indicators = [
|
| 43 |
"news", "current events", "latest", "recent", "today",
|
|
@@ -45,15 +46,15 @@ class AICoordinator:
|
|
| 45 |
"stock", "price", "trend", "market",
|
| 46 |
"breaking", "update", "development"
|
| 47 |
]
|
| 48 |
-
|
| 49 |
needs_search = False
|
| 50 |
search_topics = []
|
| 51 |
-
|
| 52 |
for indicator in current_info_indicators:
|
| 53 |
if indicator in conversation_text.lower():
|
| 54 |
needs_search = True
|
| 55 |
search_topics.append(indicator)
|
| 56 |
-
|
| 57 |
return {
|
| 58 |
"needs_search": needs_search,
|
| 59 |
"search_topics": search_topics,
|
|
@@ -65,43 +66,43 @@ class AICoordinator:
|
|
| 65 |
try:
|
| 66 |
# Determine research needs
|
| 67 |
research_decision = self.determine_web_search_needs(conversation_history)
|
| 68 |
-
|
| 69 |
# Prepare enhanced prompt for HF
|
| 70 |
system_prompt = f"""
|
| 71 |
You are a deep analysis expert joining an ongoing conversation.
|
| 72 |
-
|
| 73 |
Research Decision: {research_decision['reasoning']}
|
| 74 |
-
|
| 75 |
Please provide:
|
| 76 |
1. Deep insights on conversation themes
|
| 77 |
2. Research/web search needs (if any)
|
| 78 |
3. Strategic recommendations
|
| 79 |
4. Questions to explore further
|
| 80 |
-
|
| 81 |
Conversation History:
|
| 82 |
"""
|
| 83 |
-
|
| 84 |
# Add conversation history to messages
|
| 85 |
messages = [{"role": "system", "content": system_prompt}]
|
| 86 |
-
|
| 87 |
# Add recent conversation (last 15 messages for context)
|
| 88 |
for msg in conversation_history[-15:]:
|
| 89 |
messages.append({
|
| 90 |
"role": msg["role"],
|
| 91 |
"content": msg["content"]
|
| 92 |
})
|
| 93 |
-
|
| 94 |
# Get HF provider
|
| 95 |
from core.llm_factory import llm_factory
|
| 96 |
hf_provider = llm_factory.get_provider('huggingface')
|
| 97 |
-
|
| 98 |
if hf_provider:
|
| 99 |
# Generate deep analysis with full 8192 token capacity
|
| 100 |
response = hf_provider.generate("Deep analysis request", messages)
|
| 101 |
return response or "HF Expert analysis completed."
|
| 102 |
else:
|
| 103 |
return "❌ HF provider not available."
|
| 104 |
-
|
| 105 |
except Exception as e:
|
| 106 |
return f"❌ HF analysis failed: {str(e)}"
|
| 107 |
|
|
@@ -111,7 +112,7 @@ class AICoordinator:
|
|
| 111 |
return {
|
| 112 |
"hf_available": self._check_hf_availability(),
|
| 113 |
"web_search_configured": bool(self.tavily_client),
|
| 114 |
-
"research_needs_detected": False, # Will be determined per conversation
|
| 115 |
"last_hf_analysis": None # Track last analysis time
|
| 116 |
}
|
| 117 |
|
|
@@ -123,7 +124,7 @@ class AICoordinator:
|
|
| 123 |
# Get conversation history
|
| 124 |
session = session_manager.get_session(user_id)
|
| 125 |
conversation_history = session.get("conversation", []).copy()
|
| 126 |
-
|
| 127 |
yield {
|
| 128 |
'type': 'coordination_status',
|
| 129 |
'content': '🚀 Initiating hierarchical AI coordination...',
|
|
@@ -132,7 +133,7 @@ class AICoordinator:
|
|
| 132 |
'user_query_length': len(user_query)
|
| 133 |
}
|
| 134 |
}
|
| 135 |
-
|
| 136 |
# Step 1: Gather external data with detailed logging
|
| 137 |
yield {
|
| 138 |
'type': 'coordination_status',
|
|
@@ -140,7 +141,7 @@ class AICoordinator:
|
|
| 140 |
'details': {'phase': 'external_data_gathering'}
|
| 141 |
}
|
| 142 |
external_data = await self._gather_external_data(user_query)
|
| 143 |
-
|
| 144 |
# Log what external data was gathered
|
| 145 |
if external_data:
|
| 146 |
data_summary = []
|
|
@@ -150,13 +151,13 @@ class AICoordinator:
|
|
| 150 |
data_summary.append("Weather data: available")
|
| 151 |
if 'current_datetime' in external_data:
|
| 152 |
data_summary.append(f"Time: {external_data['current_datetime']}")
|
| 153 |
-
|
| 154 |
yield {
|
| 155 |
'type': 'coordination_status',
|
| 156 |
'content': f'📊 External data gathered: {", ".join(data_summary)}',
|
| 157 |
'details': {'external_data_summary': data_summary}
|
| 158 |
}
|
| 159 |
-
|
| 160 |
# Step 2: Get initial Ollama response
|
| 161 |
yield {
|
| 162 |
'type': 'coordination_status',
|
|
@@ -166,7 +167,7 @@ class AICoordinator:
|
|
| 166 |
ollama_response = await self._get_hierarchical_ollama_response(
|
| 167 |
user_query, conversation_history, external_data
|
| 168 |
)
|
| 169 |
-
|
| 170 |
# Send initial response with context info
|
| 171 |
yield {
|
| 172 |
'type': 'initial_response',
|
|
@@ -176,14 +177,14 @@ class AICoordinator:
|
|
| 176 |
'external_data_injected': bool(external_data)
|
| 177 |
}
|
| 178 |
}
|
| 179 |
-
|
| 180 |
# Step 3: Coordinate with HF endpoint
|
| 181 |
yield {
|
| 182 |
'type': 'coordination_status',
|
| 183 |
'content': '🤗 Engaging HF endpoint for deep analysis...',
|
| 184 |
'details': {'phase': 'hf_coordination'}
|
| 185 |
}
|
| 186 |
-
|
| 187 |
# Check HF availability
|
| 188 |
hf_available = self._check_hf_availability()
|
| 189 |
if hf_available:
|
|
@@ -193,13 +194,13 @@ class AICoordinator:
|
|
| 193 |
'ollama_response_length': len(ollama_response),
|
| 194 |
'external_data_items': len(external_data) if external_data else 0
|
| 195 |
}
|
| 196 |
-
|
| 197 |
yield {
|
| 198 |
'type': 'coordination_status',
|
| 199 |
'content': f'📋 HF context: {len(conversation_history)} conversation turns, Ollama response ({len(ollama_response)} chars)',
|
| 200 |
'details': context_summary
|
| 201 |
}
|
| 202 |
-
|
| 203 |
# Coordinate with HF
|
| 204 |
async for hf_chunk in self._coordinate_hierarchical_hf_response(
|
| 205 |
user_id, user_query, conversation_history,
|
|
@@ -212,14 +213,14 @@ class AICoordinator:
|
|
| 212 |
'content': 'ℹ️ HF endpoint not available - using Ollama response',
|
| 213 |
'details': {'hf_available': False}
|
| 214 |
}
|
| 215 |
-
|
| 216 |
# Final coordination status
|
| 217 |
yield {
|
| 218 |
'type': 'coordination_status',
|
| 219 |
'content': '✅ Hierarchical coordination complete',
|
| 220 |
'details': {'status': 'complete'}
|
| 221 |
}
|
| 222 |
-
|
| 223 |
except Exception as e:
|
| 224 |
logger.error(f"Hierarchical coordination failed: {e}")
|
| 225 |
yield {
|
|
@@ -235,29 +236,29 @@ class AICoordinator:
|
|
| 235 |
try:
|
| 236 |
# Check and warm up HF endpoint if needed
|
| 237 |
hf_status = hf_monitor.check_endpoint_status()
|
| 238 |
-
|
| 239 |
if not hf_status['available']:
|
| 240 |
yield {'type': 'coordination_status', 'content': '⚡ Initializing HF endpoint (2-4 minutes)...'}
|
| 241 |
warmup_success = hf_monitor.handle_scale_to_zero()
|
| 242 |
if not warmup_success:
|
| 243 |
yield {'type': 'coordination_status', 'content': '❌ HF endpoint initialization failed'}
|
| 244 |
return
|
| 245 |
-
|
| 246 |
# Get HF provider
|
| 247 |
hf_provider = llm_factory.get_provider('huggingface')
|
| 248 |
if not hf_provider:
|
| 249 |
yield {'type': 'coordination_status', 'content': '❌ HF provider not available'}
|
| 250 |
return
|
| 251 |
-
|
| 252 |
# Prepare enhanced conversation for HF with hierarchical context
|
| 253 |
enhanced_history = history.copy()
|
| 254 |
-
|
| 255 |
# Add system instructions for HF
|
| 256 |
enhanced_history.insert(0, {
|
| 257 |
"role": "system",
|
| 258 |
"content": self.system_instructions['hf_role']
|
| 259 |
})
|
| 260 |
-
|
| 261 |
# Add context about the coordination
|
| 262 |
enhanced_history.append({
|
| 263 |
"role": "system",
|
|
@@ -265,21 +266,21 @@ class AICoordinator:
|
|
| 265 |
User Query: '{query}'
|
| 266 |
Ollama's Initial Response: '{ollama_response}'
|
| 267 |
External Data Available: {json.dumps(external_data, indent=2) if external_data else 'None'}
|
| 268 |
-
|
| 269 |
Please provide your deep, uncensored analysis that supersedes the local model's response.
|
| 270 |
Use your full 8192 token capacity for comprehensive thinking.
|
| 271 |
Stream your response for real-time delivery."""
|
| 272 |
})
|
| 273 |
-
|
| 274 |
# Add the user's latest query
|
| 275 |
enhanced_history.append({"role": "user", "content": query})
|
| 276 |
-
|
| 277 |
# Stream HF response with full 8192 token capacity
|
| 278 |
yield {'type': 'coordination_status', 'content': '🧠 HF endpoint thinking...'}
|
| 279 |
-
|
| 280 |
# Use streaming for real-time delivery
|
| 281 |
hf_response_stream = hf_provider.stream_generate(query, enhanced_history)
|
| 282 |
-
|
| 283 |
if hf_response_stream:
|
| 284 |
# Stream the response chunks
|
| 285 |
full_hf_response = ""
|
|
@@ -287,7 +288,7 @@ class AICoordinator:
|
|
| 287 |
if chunk:
|
| 288 |
full_hf_response += chunk
|
| 289 |
yield {'type': 'hf_thinking', 'content': chunk}
|
| 290 |
-
|
| 291 |
# Final HF response
|
| 292 |
yield {'type': 'final_response', 'content': full_hf_response}
|
| 293 |
yield {'type': 'coordination_status', 'content': '🎯 HF analysis complete and authoritative'}
|
|
@@ -305,16 +306,16 @@ class AICoordinator:
|
|
| 305 |
ollama_provider = llm_factory.get_provider('ollama')
|
| 306 |
if not ollama_provider:
|
| 307 |
raise Exception("Ollama provider not available")
|
| 308 |
-
|
| 309 |
# Prepare conversation with hierarchical context
|
| 310 |
enhanced_history = history.copy()
|
| 311 |
-
|
| 312 |
# Add system instruction for Ollama's role
|
| 313 |
enhanced_history.insert(0, {
|
| 314 |
"role": "system",
|
| 315 |
"content": self.system_instructions['ollama_role']
|
| 316 |
})
|
| 317 |
-
|
| 318 |
# Add external data context if available
|
| 319 |
if external_data:
|
| 320 |
context_parts = []
|
|
@@ -325,26 +326,26 @@ class AICoordinator:
|
|
| 325 |
context_parts.append(f"Current weather: {weather.get('temperature', 'N/A')}°C in {weather.get('city', 'Unknown')}")
|
| 326 |
if 'current_datetime' in external_data:
|
| 327 |
context_parts.append(f"Current time: {external_data['current_datetime']}")
|
| 328 |
-
|
| 329 |
if context_parts:
|
| 330 |
context_message = {
|
| 331 |
"role": "system",
|
| 332 |
"content": "Context: " + " | ".join(context_parts)
|
| 333 |
}
|
| 334 |
enhanced_history.insert(1, context_message) # Insert after role instruction
|
| 335 |
-
|
| 336 |
# Add the user's query
|
| 337 |
enhanced_history.append({"role": "user", "content": query})
|
| 338 |
-
|
| 339 |
# Generate response with awareness of HF's superior capabilities
|
| 340 |
response = ollama_provider.generate(query, enhanced_history)
|
| 341 |
-
|
| 342 |
# Add acknowledgment of HF's authority
|
| 343 |
if response:
|
| 344 |
return f"{response}\n\n*Note: A more comprehensive analysis from the uncensored HF model is being prepared...*"
|
| 345 |
else:
|
| 346 |
return "I'm processing your request... A deeper analysis is being prepared by the authoritative model."
|
| 347 |
-
|
| 348 |
except Exception as e:
|
| 349 |
logger.error(f"Hierarchical Ollama response failed: {e}")
|
| 350 |
return "I'm thinking about your question... Preparing a comprehensive response."
|
|
@@ -360,24 +361,18 @@ class AICoordinator:
|
|
| 360 |
async def _gather_external_data(self, query: str) -> Dict:
|
| 361 |
"""Gather external data from various sources"""
|
| 362 |
data = {}
|
| 363 |
-
|
| 364 |
# Tavily/DuckDuckGo search with justification focus
|
| 365 |
-
if self.tavily_client:
|
| 366 |
try:
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
)
|
| 373 |
-
data['search_results'] = search_result.get('results', [])
|
| 374 |
-
if search_result.get('answer'):
|
| 375 |
-
data['search_answer'] = search_result['answer']
|
| 376 |
-
# Store raw content for HF to analyze
|
| 377 |
-
data['raw_sources'] = [result.get('raw_content', '')[:1000] for result in search_result.get('results', [])[:3]]
|
| 378 |
except Exception as e:
|
| 379 |
logger.warning(f"Tavily search failed: {e}")
|
| 380 |
-
|
| 381 |
# Weather data
|
| 382 |
weather_keywords = ['weather', 'temperature', 'forecast', 'climate', 'rain', 'sunny']
|
| 383 |
if any(keyword in query.lower() for keyword in weather_keywords):
|
|
@@ -388,10 +383,10 @@ class AICoordinator:
|
|
| 388 |
data['weather'] = weather
|
| 389 |
except Exception as e:
|
| 390 |
logger.warning(f"Weather data failed: {e}")
|
| 391 |
-
|
| 392 |
# Current date/time
|
| 393 |
data['current_datetime'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 394 |
-
|
| 395 |
return data
|
| 396 |
|
| 397 |
def _extract_location(self, query: str) -> Optional[str]:
|
|
|
|
| 5 |
from core.session import session_manager
|
| 6 |
from services.hf_endpoint_monitor import hf_monitor
|
| 7 |
from services.weather import weather_service
|
| 8 |
+
from services.web_search import web_search_service
|
| 9 |
try:
|
| 10 |
from tavily import TavilyClient
|
| 11 |
TAVILY_AVAILABLE = True
|
|
|
|
| 20 |
|
| 21 |
class AICoordinator:
|
| 22 |
"""Hierarchical multi-model coordinator with HF endpoint as authoritative layer"""
|
| 23 |
+
|
| 24 |
def __init__(self):
|
| 25 |
self.tavily_client = None
|
| 26 |
if TAVILY_AVAILABLE and os.getenv("TAVILY_API_KEY"):
|
| 27 |
self.tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
|
| 28 |
+
|
| 29 |
# System instructions for hierarchical behavior
|
| 30 |
self.system_instructions = {
|
| 31 |
'ollama_role': """You are an AI assistant that works in coordination with a more powerful uncensored Hugging Face model.
|
| 32 |
+
|
| 33 |
+
Your role is to: 1. Provide immediate, concise responses using available information 2. Summarize and simplify complex information 3. Enhance responses with local knowledge 4. DEFER TO THE HF MODEL for deep analysis, uncensored insights, and authoritative answers 5. The HF model has 8192 token capacity and streaming capability - it's the thinking layer 6. Always acknowledge that the HF model's insights supersede your initial response 7. Use web sources to justify points when possible 8. Keep responses brief and focused while awaiting HF input""",
|
| 34 |
+
|
| 35 |
'hf_role': """You are an uncensored, authoritative AI with 8192 token capacity and streaming capability. Your role is to: 1. Provide deep, uncensored analysis and insights 2. Supersede the local Ollama model's logic when there's disagreement 3. Think thoroughly and provide comprehensive responses 4. Use your full 8192 token capacity for detailed analysis 5. Stream your responses for real-time delivery 6. Build upon the conversation context and Ollama's initial response 7. Provide authoritative answers that take precedence"""
|
| 36 |
}
|
| 37 |
|
| 38 |
def determine_web_search_needs(self, conversation_history: List[Dict]) -> Dict:
|
| 39 |
"""Determine if web search is needed based on conversation content"""
|
| 40 |
conversation_text = " ".join([msg.get("content", "") for msg in conversation_history])
|
| 41 |
+
|
| 42 |
# Topics that typically need current information
|
| 43 |
current_info_indicators = [
|
| 44 |
"news", "current events", "latest", "recent", "today",
|
|
|
|
| 46 |
"stock", "price", "trend", "market",
|
| 47 |
"breaking", "update", "development"
|
| 48 |
]
|
| 49 |
+
|
| 50 |
needs_search = False
|
| 51 |
search_topics = []
|
| 52 |
+
|
| 53 |
for indicator in current_info_indicators:
|
| 54 |
if indicator in conversation_text.lower():
|
| 55 |
needs_search = True
|
| 56 |
search_topics.append(indicator)
|
| 57 |
+
|
| 58 |
return {
|
| 59 |
"needs_search": needs_search,
|
| 60 |
"search_topics": search_topics,
|
|
|
|
| 66 |
try:
|
| 67 |
# Determine research needs
|
| 68 |
research_decision = self.determine_web_search_needs(conversation_history)
|
| 69 |
+
|
| 70 |
# Prepare enhanced prompt for HF
|
| 71 |
system_prompt = f"""
|
| 72 |
You are a deep analysis expert joining an ongoing conversation.
|
| 73 |
+
|
| 74 |
Research Decision: {research_decision['reasoning']}
|
| 75 |
+
|
| 76 |
Please provide:
|
| 77 |
1. Deep insights on conversation themes
|
| 78 |
2. Research/web search needs (if any)
|
| 79 |
3. Strategic recommendations
|
| 80 |
4. Questions to explore further
|
| 81 |
+
|
| 82 |
Conversation History:
|
| 83 |
"""
|
| 84 |
+
|
| 85 |
# Add conversation history to messages
|
| 86 |
messages = [{"role": "system", "content": system_prompt}]
|
| 87 |
+
|
| 88 |
# Add recent conversation (last 15 messages for context)
|
| 89 |
for msg in conversation_history[-15:]:
|
| 90 |
messages.append({
|
| 91 |
"role": msg["role"],
|
| 92 |
"content": msg["content"]
|
| 93 |
})
|
| 94 |
+
|
| 95 |
# Get HF provider
|
| 96 |
from core.llm_factory import llm_factory
|
| 97 |
hf_provider = llm_factory.get_provider('huggingface')
|
| 98 |
+
|
| 99 |
if hf_provider:
|
| 100 |
# Generate deep analysis with full 8192 token capacity
|
| 101 |
response = hf_provider.generate("Deep analysis request", messages)
|
| 102 |
return response or "HF Expert analysis completed."
|
| 103 |
else:
|
| 104 |
return "❌ HF provider not available."
|
| 105 |
+
|
| 106 |
except Exception as e:
|
| 107 |
return f"❌ HF analysis failed: {str(e)}"
|
| 108 |
|
|
|
|
| 112 |
return {
|
| 113 |
"hf_available": self._check_hf_availability(),
|
| 114 |
"web_search_configured": bool(self.tavily_client),
|
| 115 |
+
"research_needs_detected": False, # Will be determined per conversation,
|
| 116 |
"last_hf_analysis": None # Track last analysis time
|
| 117 |
}
|
| 118 |
|
|
|
|
| 124 |
# Get conversation history
|
| 125 |
session = session_manager.get_session(user_id)
|
| 126 |
conversation_history = session.get("conversation", []).copy()
|
| 127 |
+
|
| 128 |
yield {
|
| 129 |
'type': 'coordination_status',
|
| 130 |
'content': '🚀 Initiating hierarchical AI coordination...',
|
|
|
|
| 133 |
'user_query_length': len(user_query)
|
| 134 |
}
|
| 135 |
}
|
| 136 |
+
|
| 137 |
# Step 1: Gather external data with detailed logging
|
| 138 |
yield {
|
| 139 |
'type': 'coordination_status',
|
|
|
|
| 141 |
'details': {'phase': 'external_data_gathering'}
|
| 142 |
}
|
| 143 |
external_data = await self._gather_external_data(user_query)
|
| 144 |
+
|
| 145 |
# Log what external data was gathered
|
| 146 |
if external_data:
|
| 147 |
data_summary = []
|
|
|
|
| 151 |
data_summary.append("Weather data: available")
|
| 152 |
if 'current_datetime' in external_data:
|
| 153 |
data_summary.append(f"Time: {external_data['current_datetime']}")
|
| 154 |
+
|
| 155 |
yield {
|
| 156 |
'type': 'coordination_status',
|
| 157 |
'content': f'📊 External data gathered: {", ".join(data_summary)}',
|
| 158 |
'details': {'external_data_summary': data_summary}
|
| 159 |
}
|
| 160 |
+
|
| 161 |
# Step 2: Get initial Ollama response
|
| 162 |
yield {
|
| 163 |
'type': 'coordination_status',
|
|
|
|
| 167 |
ollama_response = await self._get_hierarchical_ollama_response(
|
| 168 |
user_query, conversation_history, external_data
|
| 169 |
)
|
| 170 |
+
|
| 171 |
# Send initial response with context info
|
| 172 |
yield {
|
| 173 |
'type': 'initial_response',
|
|
|
|
| 177 |
'external_data_injected': bool(external_data)
|
| 178 |
}
|
| 179 |
}
|
| 180 |
+
|
| 181 |
# Step 3: Coordinate with HF endpoint
|
| 182 |
yield {
|
| 183 |
'type': 'coordination_status',
|
| 184 |
'content': '🤗 Engaging HF endpoint for deep analysis...',
|
| 185 |
'details': {'phase': 'hf_coordination'}
|
| 186 |
}
|
| 187 |
+
|
| 188 |
# Check HF availability
|
| 189 |
hf_available = self._check_hf_availability()
|
| 190 |
if hf_available:
|
|
|
|
| 194 |
'ollama_response_length': len(ollama_response),
|
| 195 |
'external_data_items': len(external_data) if external_data else 0
|
| 196 |
}
|
| 197 |
+
|
| 198 |
yield {
|
| 199 |
'type': 'coordination_status',
|
| 200 |
'content': f'📋 HF context: {len(conversation_history)} conversation turns, Ollama response ({len(ollama_response)} chars)',
|
| 201 |
'details': context_summary
|
| 202 |
}
|
| 203 |
+
|
| 204 |
# Coordinate with HF
|
| 205 |
async for hf_chunk in self._coordinate_hierarchical_hf_response(
|
| 206 |
user_id, user_query, conversation_history,
|
|
|
|
| 213 |
'content': 'ℹ️ HF endpoint not available - using Ollama response',
|
| 214 |
'details': {'hf_available': False}
|
| 215 |
}
|
| 216 |
+
|
| 217 |
# Final coordination status
|
| 218 |
yield {
|
| 219 |
'type': 'coordination_status',
|
| 220 |
'content': '✅ Hierarchical coordination complete',
|
| 221 |
'details': {'status': 'complete'}
|
| 222 |
}
|
| 223 |
+
|
| 224 |
except Exception as e:
|
| 225 |
logger.error(f"Hierarchical coordination failed: {e}")
|
| 226 |
yield {
|
|
|
|
| 236 |
try:
|
| 237 |
# Check and warm up HF endpoint if needed
|
| 238 |
hf_status = hf_monitor.check_endpoint_status()
|
| 239 |
+
|
| 240 |
if not hf_status['available']:
|
| 241 |
yield {'type': 'coordination_status', 'content': '⚡ Initializing HF endpoint (2-4 minutes)...'}
|
| 242 |
warmup_success = hf_monitor.handle_scale_to_zero()
|
| 243 |
if not warmup_success:
|
| 244 |
yield {'type': 'coordination_status', 'content': '❌ HF endpoint initialization failed'}
|
| 245 |
return
|
| 246 |
+
|
| 247 |
# Get HF provider
|
| 248 |
hf_provider = llm_factory.get_provider('huggingface')
|
| 249 |
if not hf_provider:
|
| 250 |
yield {'type': 'coordination_status', 'content': '❌ HF provider not available'}
|
| 251 |
return
|
| 252 |
+
|
| 253 |
# Prepare enhanced conversation for HF with hierarchical context
|
| 254 |
enhanced_history = history.copy()
|
| 255 |
+
|
| 256 |
# Add system instructions for HF
|
| 257 |
enhanced_history.insert(0, {
|
| 258 |
"role": "system",
|
| 259 |
"content": self.system_instructions['hf_role']
|
| 260 |
})
|
| 261 |
+
|
| 262 |
# Add context about the coordination
|
| 263 |
enhanced_history.append({
|
| 264 |
"role": "system",
|
|
|
|
| 266 |
User Query: '{query}'
|
| 267 |
Ollama's Initial Response: '{ollama_response}'
|
| 268 |
External Data Available: {json.dumps(external_data, indent=2) if external_data else 'None'}
|
| 269 |
+
|
| 270 |
Please provide your deep, uncensored analysis that supersedes the local model's response.
|
| 271 |
Use your full 8192 token capacity for comprehensive thinking.
|
| 272 |
Stream your response for real-time delivery."""
|
| 273 |
})
|
| 274 |
+
|
| 275 |
# Add the user's latest query
|
| 276 |
enhanced_history.append({"role": "user", "content": query})
|
| 277 |
+
|
| 278 |
# Stream HF response with full 8192 token capacity
|
| 279 |
yield {'type': 'coordination_status', 'content': '🧠 HF endpoint thinking...'}
|
| 280 |
+
|
| 281 |
# Use streaming for real-time delivery
|
| 282 |
hf_response_stream = hf_provider.stream_generate(query, enhanced_history)
|
| 283 |
+
|
| 284 |
if hf_response_stream:
|
| 285 |
# Stream the response chunks
|
| 286 |
full_hf_response = ""
|
|
|
|
| 288 |
if chunk:
|
| 289 |
full_hf_response += chunk
|
| 290 |
yield {'type': 'hf_thinking', 'content': chunk}
|
| 291 |
+
|
| 292 |
# Final HF response
|
| 293 |
yield {'type': 'final_response', 'content': full_hf_response}
|
| 294 |
yield {'type': 'coordination_status', 'content': '🎯 HF analysis complete and authoritative'}
|
|
|
|
| 306 |
ollama_provider = llm_factory.get_provider('ollama')
|
| 307 |
if not ollama_provider:
|
| 308 |
raise Exception("Ollama provider not available")
|
| 309 |
+
|
| 310 |
# Prepare conversation with hierarchical context
|
| 311 |
enhanced_history = history.copy()
|
| 312 |
+
|
| 313 |
# Add system instruction for Ollama's role
|
| 314 |
enhanced_history.insert(0, {
|
| 315 |
"role": "system",
|
| 316 |
"content": self.system_instructions['ollama_role']
|
| 317 |
})
|
| 318 |
+
|
| 319 |
# Add external data context if available
|
| 320 |
if external_data:
|
| 321 |
context_parts = []
|
|
|
|
| 326 |
context_parts.append(f"Current weather: {weather.get('temperature', 'N/A')}°C in {weather.get('city', 'Unknown')}")
|
| 327 |
if 'current_datetime' in external_data:
|
| 328 |
context_parts.append(f"Current time: {external_data['current_datetime']}")
|
| 329 |
+
|
| 330 |
if context_parts:
|
| 331 |
context_message = {
|
| 332 |
"role": "system",
|
| 333 |
"content": "Context: " + " | ".join(context_parts)
|
| 334 |
}
|
| 335 |
enhanced_history.insert(1, context_message) # Insert after role instruction
|
| 336 |
+
|
| 337 |
# Add the user's query
|
| 338 |
enhanced_history.append({"role": "user", "content": query})
|
| 339 |
+
|
| 340 |
# Generate response with awareness of HF's superior capabilities
|
| 341 |
response = ollama_provider.generate(query, enhanced_history)
|
| 342 |
+
|
| 343 |
# Add acknowledgment of HF's authority
|
| 344 |
if response:
|
| 345 |
return f"{response}\n\n*Note: A more comprehensive analysis from the uncensored HF model is being prepared...*"
|
| 346 |
else:
|
| 347 |
return "I'm processing your request... A deeper analysis is being prepared by the authoritative model."
|
| 348 |
+
|
| 349 |
except Exception as e:
|
| 350 |
logger.error(f"Hierarchical Ollama response failed: {e}")
|
| 351 |
return "I'm thinking about your question... Preparing a comprehensive response."
|
|
|
|
| 361 |
async def _gather_external_data(self, query: str) -> Dict:
|
| 362 |
"""Gather external data from various sources"""
|
| 363 |
data = {}
|
| 364 |
+
|
| 365 |
# Tavily/DuckDuckGo search with justification focus
|
| 366 |
+
if self.tavily_client or web_search_service.client:
|
| 367 |
try:
|
| 368 |
+
search_results = web_search_service.search(f"current information about {query}")
|
| 369 |
+
if search_results:
|
| 370 |
+
data['search_results'] = search_results
|
| 371 |
+
# Optionally extract answer summary
|
| 372 |
+
# data['search_answer'] = ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
except Exception as e:
|
| 374 |
logger.warning(f"Tavily search failed: {e}")
|
| 375 |
+
|
| 376 |
# Weather data
|
| 377 |
weather_keywords = ['weather', 'temperature', 'forecast', 'climate', 'rain', 'sunny']
|
| 378 |
if any(keyword in query.lower() for keyword in weather_keywords):
|
|
|
|
| 383 |
data['weather'] = weather
|
| 384 |
except Exception as e:
|
| 385 |
logger.warning(f"Weather data failed: {e}")
|
| 386 |
+
|
| 387 |
# Current date/time
|
| 388 |
data['current_datetime'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 389 |
+
|
| 390 |
return data
|
| 391 |
|
| 392 |
def _extract_location(self, query: str) -> Optional[str]:
|
requirements.txt
CHANGED
|
@@ -10,3 +10,4 @@ docker==6.1.3
|
|
| 10 |
pygame==2.5.2
|
| 11 |
pydantic==1.10.7
|
| 12 |
typing-extensions>=4.5.0
|
|
|
|
|
|
| 10 |
pygame==2.5.2
|
| 11 |
pydantic==1.10.7
|
| 12 |
typing-extensions>=4.5.0
|
| 13 |
+
semanticscholar>=0.1.8
|
services/research/papers.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from semanticscholar import SemanticScholar
|
| 2 |
+
import logging
|
| 3 |
+
|
| 4 |
+
logger = logging.getLogger(__name__)
|
| 5 |
+
|
| 6 |
+
scholar = SemanticScholar(timeout=10)
|
| 7 |
+
|
| 8 |
+
def find_papers(query: str, limit: int = 5):
|
| 9 |
+
"""
|
| 10 |
+
Search academic papers via Semantic Scholar API.
|
| 11 |
+
Returns simplified paper metadata.
|
| 12 |
+
"""
|
| 13 |
+
try:
|
| 14 |
+
results = scholar.search_paper(query, limit=limit)
|
| 15 |
+
papers = []
|
| 16 |
+
for paper in results:
|
| 17 |
+
papers.append({
|
| 18 |
+
'title': paper.title,
|
| 19 |
+
'authors': [author.name for author in paper.authors],
|
| 20 |
+
'abstract': paper.abstract,
|
| 21 |
+
'year': paper.year,
|
| 22 |
+
'url': paper.url,
|
| 23 |
+
'citation_count': getattr(paper, 'citationCount', 0),
|
| 24 |
+
'venue': getattr(paper, 'venue', '')
|
| 25 |
+
})
|
| 26 |
+
return papers
|
| 27 |
+
except Exception as e:
|
| 28 |
+
logger.error(f"Paper search failed: {e}")
|
| 29 |
+
return []
|
| 30 |
+
|
| 31 |
+
# Example usage:
|
| 32 |
+
# papers = find_papers("vitamin D immune system")
|
services/web_search.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Optional, Dict, List
|
| 3 |
+
try:
|
| 4 |
+
from tavily import TavilyClient
|
| 5 |
+
TAVILY_AVAILABLE = True
|
| 6 |
+
except ImportError:
|
| 7 |
+
TavilyClient = None
|
| 8 |
+
TAVILY_AVAILABLE = False
|
| 9 |
+
|
| 10 |
+
class WebSearchService:
|
| 11 |
+
def __init__(self):
|
| 12 |
+
self.client = None
|
| 13 |
+
api_key = os.getenv("TAVILY_API_KEY")
|
| 14 |
+
if api_key and TAVILY_AVAILABLE:
|
| 15 |
+
self.client = TavilyClient(api_key=api_key)
|
| 16 |
+
|
| 17 |
+
def search(self, query: str, max_results: int = 5) -> Optional[List[Dict]]:
|
| 18 |
+
"""
|
| 19 |
+
Perform a web search using Tavily.
|
| 20 |
+
Returns list of results with title, url, content.
|
| 21 |
+
"""
|
| 22 |
+
if not self.client:
|
| 23 |
+
print("⚠️ Tavily API key not configured.")
|
| 24 |
+
return []
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
response = self.client.search(
|
| 28 |
+
query=query,
|
| 29 |
+
max_results=max_results,
|
| 30 |
+
include_answer=True,
|
| 31 |
+
include_raw_content=False
|
| 32 |
+
)
|
| 33 |
+
return response.get("results", [])
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"❌ Web search failed: {e}")
|
| 36 |
+
return []
|
| 37 |
+
|
| 38 |
+
# Global instance
|
| 39 |
+
web_search_service = WebSearchService()
|