Girish Jeswani commited on
Commit
36fa2f1
·
1 Parent(s): 8aecc96

old routes

Browse files
multi_llm_chatbot_backend/app/api/routes_old.py ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from fastapi import APIRouter, Body, HTTPException
3
+ import httpx
4
+ from app.llm.llm_client import LLMClient
5
+ from app.llm.gemini_client import GeminiClient
6
+ from app.llm.short_ollama_client import ShortResponseOllamaClient
7
+ from app.models.persona import Persona
8
+ from app.core.orchestrator import ChatOrchestrator
9
+ from app.core.seamless_orchestrator import SeamlessOrchestrator
10
+ from app.core.context import GlobalSessionContext
11
+ from app.models.default_personas import get_default_personas
12
+ from pydantic import BaseModel
13
+ from typing import Optional, List
14
+ from fastapi import UploadFile, File
15
+ from app.utils.document_extractor import extract_text_from_file
16
+ from app.core.orchestrator import answer_with_persona_context
17
+ from app.utils.chroma_client import add_persona_doc
18
+ import hashlib
19
+ from app.utils.file_limits import is_within_upload_limit
20
+
21
+ router = APIRouter()
22
+
23
+ # Provider management
24
+ current_provider = "gemini"
25
+ available_providers = ["ollama", "gemini"]
26
+
27
+ def create_llm_client(provider: str = None) -> LLMClient:
28
+ """Create LLM client based on provider"""
29
+ if provider is None:
30
+ provider = current_provider
31
+
32
+ if provider == "gemini":
33
+ try:
34
+ return GeminiClient(model_name=os.getenv("GEMINI_MODEL"))
35
+ except ValueError as e:
36
+ # Fallback to Ollama if Gemini API key is not available
37
+ print(f"Gemini API key not found, falling back to Ollama: {e}")
38
+ return ShortResponseOllamaClient(model_name="llama3.2:1b")
39
+ elif provider == "ollama":
40
+ return ShortResponseOllamaClient(model_name="llama3.2:1b")
41
+ else:
42
+ raise ValueError(f"Unknown provider: {provider}")
43
+
44
+ # Initialize with default provider
45
+ llm = create_llm_client()
46
+ chat_orchestrator = ChatOrchestrator()
47
+ seamless_orchestrator = SeamlessOrchestrator(llm=llm)
48
+
49
+ session_context = GlobalSessionContext()
50
+
51
+ # Initialize personas
52
+ DEFAULT_PERSONAS = get_default_personas(llm)
53
+
54
+ for persona in DEFAULT_PERSONAS:
55
+ chat_orchestrator.register_persona(persona)
56
+
57
+ # Data models
58
+ class UserInput(BaseModel):
59
+ user_input: str
60
+
61
+ class PersonaInput(BaseModel):
62
+ id: str
63
+ name: str
64
+ system_prompt: str
65
+
66
+ class ChatMessage(BaseModel):
67
+ user_input: str
68
+ session_id: Optional[str] = None
69
+ response_length: Optional[str] = "medium"
70
+
71
+ class ReplyToAdvisor(BaseModel):
72
+ user_input: str
73
+ advisor_id: str
74
+ original_message_id: Optional[str] = None
75
+
76
+ class ProviderSwitch(BaseModel):
77
+ provider: str
78
+
79
+ # Helper functions for response validation
80
+ def _is_valid_response(response: str, persona_id: str) -> bool:
81
+ """Validate response quality"""
82
+ if len(response) < 2 or len(response) > 5000:
83
+ return False
84
+
85
+ # Check for AI confusion indicators
86
+ confusion_indicators = [
87
+ f"Thank you, Dr. {persona_id.title()}",
88
+ "Assistant:",
89
+ f"Dr. {persona_id.title()} Advisor:",
90
+ "excellent discussion, Assistant"
91
+ ]
92
+
93
+ return not any(indicator in response for indicator in confusion_indicators)
94
+
95
+ def _get_persona_fallback(persona_id: str) -> str:
96
+ """Get persona-specific fallback responses"""
97
+ fallbacks = {
98
+ "methodist": "Focus on ensuring your methodology aligns with your research question. What specific method are you considering?",
99
+ "theorist": "Consider the theoretical framework underlying your approach. What assumptions guide your thinking?",
100
+ "pragmatist": "Let's break this down into actionable steps. What's the most important thing you need to decide today?"
101
+ }
102
+ return fallbacks.get(persona_id, "I'd be happy to help. Could you provide more details?")
103
+
104
+ # Provider management endpoints
105
+ @router.get("/current-provider")
106
+ async def get_current_provider():
107
+ return {
108
+ "current_provider": current_provider,
109
+ "available_providers": available_providers,
110
+ "model_info": {
111
+ "name": llm.model_name if hasattr(llm, 'model_name') else "gemini-2.0-flash",
112
+ "provider": current_provider
113
+ }
114
+ }
115
+
116
+ @router.post("/switch-provider")
117
+ async def switch_provider(provider_data: ProviderSwitch):
118
+ global current_provider, llm
119
+
120
+ if provider_data.provider not in available_providers:
121
+ raise HTTPException(
122
+ status_code=400,
123
+ detail=f"Unknown provider: {provider_data.provider}. Available: {available_providers}"
124
+ )
125
+
126
+ try:
127
+ # Update current provider
128
+ current_provider = provider_data.provider
129
+
130
+ # Create new LLM client
131
+ new_llm = create_llm_client(current_provider)
132
+ llm = new_llm
133
+
134
+ # Update all personas with new LLM
135
+ new_personas = get_default_personas(new_llm)
136
+ chat_orchestrator.personas.clear()
137
+ for persona in new_personas:
138
+ chat_orchestrator.register_persona(persona)
139
+
140
+ # Update seamless orchestrator
141
+ seamless_orchestrator.llm = new_llm
142
+
143
+ return {
144
+ "message": f"Successfully switched to {current_provider}",
145
+ "current_provider": current_provider,
146
+ "model_info": {
147
+ "name": new_llm.model_name if hasattr(new_llm, 'model_name') else "gemini-2.0-flash",
148
+ "provider": current_provider
149
+ }
150
+ }
151
+
152
+ except Exception as e:
153
+ raise HTTPException(
154
+ status_code=500,
155
+ detail=f"Failed to switch to {provider_data.provider}: {str(e)}"
156
+ )
157
+
158
+ # Sequential advisor responses endpoint
159
+ @router.post("/chat-sequential")
160
+ async def chat_sequential(message: ChatMessage):
161
+ """Generate advisor responses with improved quality controls"""
162
+
163
+ try:
164
+ orchestrator_result = await seamless_orchestrator.process_message(message.user_input)
165
+
166
+ if orchestrator_result["status"] == "orchestrator_asking":
167
+ return {
168
+ "type": "orchestrator_question",
169
+ "responses": [{
170
+ "persona": "PhD Advisor Assistant",
171
+ "response": orchestrator_result["orchestrator_question"]
172
+ }],
173
+ "collected_info": orchestrator_result["collected_info"]
174
+ }
175
+
176
+ elif orchestrator_result["status"] == "ready_for_advisors":
177
+ enhanced_context = orchestrator_result["enhanced_context"]
178
+
179
+ # Clear previous advisor responses to avoid confusion
180
+ session_context.clear()
181
+ session_context.append("user", message.user_input)
182
+ session_context.append("orchestrator", enhanced_context)
183
+
184
+ advisor_order = chat_orchestrator.get_response_order()
185
+ print("Advisor Order:")
186
+ print(advisor_order)
187
+ responses = []
188
+
189
+ for persona_id in advisor_order:
190
+ try:
191
+ persona = chat_orchestrator.personas[persona_id]
192
+ reply = await persona.respond(session_context.full_log, response_length="medium")
193
+ print("Replies:")
194
+ print(reply)
195
+
196
+ # Validate response before adding
197
+ if _is_valid_response(reply, persona_id):
198
+ responses.append({
199
+ "persona": persona.name,
200
+ "persona_id": persona_id,
201
+ "response": reply,
202
+ })
203
+ else:
204
+ # Fallback response for invalid responses
205
+ responses.append({
206
+ "persona": persona.name,
207
+ "persona_id": persona_id,
208
+ "response": _get_persona_fallback(persona_id),
209
+ })
210
+
211
+ session_context.append(persona_id, reply)
212
+
213
+ except Exception as e:
214
+ print(f"Error generating response for {persona_id}: {e}")
215
+ responses.append({
216
+ "persona": chat_orchestrator.personas[persona_id].name,
217
+ "persona_id": persona_id,
218
+ "response": _get_persona_fallback(persona_id),
219
+ })
220
+
221
+ print("Response Block: " )
222
+ print(responses)
223
+ return {
224
+ "type": "sequential_responses",
225
+ "responses": responses,
226
+ "collected_info": orchestrator_result["collected_info"]
227
+ }
228
+
229
+ except Exception as e:
230
+ print(f"Error in chat_sequential: {e}")
231
+ return {
232
+ "type": "error",
233
+ "responses": [{
234
+ "persona": "System",
235
+ "response": "I'm having trouble processing your request. Could you please try again?"
236
+ }]
237
+ }
238
+
239
+
240
+ # Individual advisor endpoint with context
241
+ @router.post("/chat/{persona_id}")
242
+ async def chat_with_specific_advisor(persona_id: str, input: UserInput):
243
+ """Chat with a specific advisor"""
244
+ try:
245
+ if persona_id not in chat_orchestrator.personas:
246
+ raise HTTPException(status_code=404, detail=f"Persona '{persona_id}' not found")
247
+
248
+ session_context.append("user", input.user_input)
249
+ persona = chat_orchestrator.personas[persona_id]
250
+ context = session_context.full_log.copy()
251
+ reply = await persona.respond(context, response_length="medium")
252
+ session_context.append(persona_id, reply)
253
+
254
+ return {
255
+ "persona": persona.name,
256
+ "persona_id": persona_id,
257
+ "response": reply
258
+ }
259
+ except HTTPException:
260
+ raise
261
+ except Exception as e:
262
+ print(f"Error in chat_with_specific_advisor: {e}")
263
+ return {
264
+ "persona": "System",
265
+ "response": "I'm having trouble generating a response right now. Please try again."
266
+ }
267
+
268
+ # Reply to specific advisor endpoint
269
+ @router.post("/reply-to-advisor")
270
+ async def reply_to_advisor(reply: ReplyToAdvisor):
271
+ """Reply to a specific advisor and get response only from that advisor"""
272
+
273
+ try:
274
+ if reply.advisor_id not in chat_orchestrator.personas:
275
+ raise HTTPException(status_code=404, detail=f"Advisor '{reply.advisor_id}' not found")
276
+
277
+ # Add user reply to context
278
+ session_context.append("user", reply.user_input)
279
+
280
+ # Get response from specific advisor
281
+ persona = chat_orchestrator.personas[reply.advisor_id]
282
+
283
+ # Generate response
284
+ reply_response = await persona.respond(session_context.full_log, response_length="medium")
285
+ session_context.append(reply.advisor_id, reply_response)
286
+
287
+ return {
288
+ "type": "advisor_reply",
289
+ "persona": persona.name,
290
+ "persona_id": reply.advisor_id,
291
+ "response": reply_response,
292
+ "original_message_id": reply.original_message_id
293
+ }
294
+
295
+ except HTTPException:
296
+ raise
297
+ except Exception as e:
298
+ print(f"Error in reply_to_advisor: {e}")
299
+ return {
300
+ "type": "error",
301
+ "persona": "System",
302
+ "response": "I'm having trouble generating a reply right now. Please try again."
303
+ }
304
+
305
+ # Reset session
306
+ @router.post("/reset-session")
307
+ async def reset_session():
308
+ try:
309
+ seamless_orchestrator.reset()
310
+ session_context.clear()
311
+ return {"status": "reset", "message": "Session reset successfully"}
312
+ except Exception as e:
313
+ print(f"Error resetting session: {e}")
314
+ return {"status": "error", "message": "Failed to reset session"}
315
+
316
+ # Context inspection
317
+ @router.get("/context")
318
+ def get_context():
319
+ return session_context.full_log
320
+
321
+ # Legacy model switching endpoint (now redirects to provider switching)
322
+ @router.post("/switch-model")
323
+ async def switch_model(model_name: str = Body(...)):
324
+ # For backward compatibility, try to map model names to providers
325
+ if "gemini" in model_name.lower():
326
+ return await switch_provider(ProviderSwitch(provider="gemini"))
327
+ else:
328
+ return await switch_provider(ProviderSwitch(provider="ollama"))
329
+
330
+ @router.get("/current-model")
331
+ async def get_current_model():
332
+ # For backward compatibility
333
+ model_name = llm.model_name if hasattr(llm, 'model_name') else "gemini-2.0-flash"
334
+ return {
335
+ "model": model_name,
336
+ "provider": current_provider
337
+ }
338
+
339
+ @router.post("/upload-document")
340
+ async def upload_document(file: UploadFile = File(...)):
341
+ # Validate file type
342
+ if file.content_type not in [
343
+ "application/pdf",
344
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
345
+ "text/plain"
346
+ ]:
347
+ raise HTTPException(status_code=400, detail="Unsupported file type.")
348
+
349
+ try:
350
+ # Read file bytes
351
+ file_bytes = await file.read()
352
+
353
+ # Check file size limit
354
+ if not is_within_upload_limit("default", file_bytes, session_context):
355
+ raise HTTPException(status_code=400, detail="Upload exceeds session document size limit (10 MB).")
356
+
357
+ # Extract and validate text
358
+ content = extract_text_from_file(file_bytes, file.content_type)
359
+ if not content.strip():
360
+ raise HTTPException(status_code=400, detail="Document is empty or unreadable.")
361
+
362
+ # Track file size and name
363
+ session_context.append("Document", f"[Uploaded Document Content]\n{content.strip()}")
364
+ session_context.uploaded_files.append(file.filename)
365
+ session_context.total_upload_size += len(file_bytes)
366
+
367
+ return {"message": "Document uploaded and added to context successfully."}
368
+
369
+ except Exception as e:
370
+ raise HTTPException(status_code=500, detail=f"Error processing document: {str(e)}")
371
+
372
+ # Debug endpoint
373
+ @router.get("/debug/personas")
374
+ async def debug_personas():
375
+ return {
376
+ "personas": {
377
+ pid: {
378
+ "name": persona.name,
379
+ "prompt": persona.system_prompt[:100] + "..."
380
+ } for pid, persona in chat_orchestrator.personas.items()
381
+ },
382
+ "context_length": len(session_context.full_log),
383
+ "current_provider": current_provider
384
+ }
385
+
386
+ class PersonaQuery(BaseModel):
387
+ question: str
388
+ persona: str
389
+
390
+ @router.post("/ask/")
391
+ async def ask_question(query: PersonaQuery):
392
+ response = await answer_with_persona_context(query.question, query.persona)
393
+
394
+ # Store Q&A in vector DB
395
+ combined_text = f"Q: {query.question}\nA: {response}"
396
+ doc_id = hashlib.md5(combined_text.encode()).hexdigest() # Create a unique doc ID
397
+
398
+ add_persona_doc(combined_text, query.persona, doc_id)
399
+
400
+ return {"response": response}
401
+
402
+ @router.get("/uploaded-files")
403
+ def get_uploaded_filenames():
404
+ return {"files": session_context.uploaded_files}