Girish Jeswani commited on
Commit
ef8ae4e
·
1 Parent(s): a9328c2

routes with new fixes

Browse files
multi_llm_chatbot_backend/app/api/routes.py CHANGED
@@ -1,26 +1,25 @@
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
 
@@ -31,30 +30,26 @@ def create_llm_client(provider: str = None) -> LLMClient:
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
 
@@ -76,16 +71,52 @@ class ReplyToAdvisor(BaseModel):
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
  ]
@@ -101,7 +132,7 @@ def _get_persona_fallback(persona_id: str) -> str:
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 {
@@ -124,22 +155,15 @@ async def switch_provider(provider_data: ProviderSwitch):
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,
@@ -155,79 +179,61 @@ async def switch_provider(provider_data: ProviderSwitch):
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": [{
@@ -236,109 +242,94 @@ async def chat_sequential(message: ChatMessage):
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",
@@ -347,58 +338,157 @@ async def upload_document(file: UploadFile = File(...)):
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}
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ from fastapi import APIRouter, Body, HTTPException, Header, UploadFile, File, Request
3
+ from typing import Optional, List
4
  import httpx
5
  from app.llm.llm_client import LLMClient
6
+ from app.llm.improved_gemini_client import ImprovedGeminiClient
7
+ from app.llm.improved_ollama_client import ImprovedOllamaClient
8
  from app.models.persona import Persona
9
+ from app.core.improved_orchestrator import ImprovedChatOrchestrator
10
+ from app.core.session_manager import get_session_manager
 
11
  from app.models.default_personas import get_default_personas
 
 
 
12
  from app.utils.document_extractor import extract_text_from_file
 
 
 
13
  from app.utils.file_limits import is_within_upload_limit
14
+ from pydantic import BaseModel
15
+ import hashlib
16
+ import logging
17
+
18
+ logger = logging.getLogger(__name__)
19
 
20
  router = APIRouter()
21
 
22
+ # Provider management (same as before)
23
  current_provider = "gemini"
24
  available_providers = ["ollama", "gemini"]
25
 
 
30
 
31
  if provider == "gemini":
32
  try:
33
+ return ImprovedGeminiClient(model_name=os.getenv("GEMINI_MODEL"))
34
  except ValueError as e:
35
+ logger.warning(f"Gemini API key not found, falling back to Ollama: {e}")
36
+ return ImprovedOllamaClient(model_name="llama3.2:1b")
 
37
  elif provider == "ollama":
38
+ return ImprovedOllamaClient(model_name="llama3.2:1b")
39
  else:
40
  raise ValueError(f"Unknown provider: {provider}")
41
 
42
  # Initialize with default provider
43
  llm = create_llm_client()
44
+ chat_orchestrator = ImprovedChatOrchestrator()
45
+ session_manager = get_session_manager()
 
 
46
 
47
  # Initialize personas
48
  DEFAULT_PERSONAS = get_default_personas(llm)
 
49
  for persona in DEFAULT_PERSONAS:
50
  chat_orchestrator.register_persona(persona)
51
 
52
+ # Keep all the same data models as before
53
  class UserInput(BaseModel):
54
  user_input: str
55
 
 
71
  class ProviderSwitch(BaseModel):
72
  provider: str
73
 
74
+ # ==============================================================
75
+ # SESSION MANAGEMENT COMPATIBILITY LAYER
76
+ # ==============================================================
77
+
78
+ def get_or_create_session_for_request(request: Request,
79
+ session_id_override: Optional[str] = None) -> str:
80
+ """
81
+ Get or create session for request using multiple strategies:
82
+ 1. Use provided session_id if given
83
+ 2. Use X-Session-ID header if present
84
+ 3. Use client IP as fallback for backward compatibility
85
+ 4. Create new session if nothing available
86
+
87
+ This allows the old stateless API to work with session management
88
+ """
89
+ # Strategy 1: Explicit session ID (for new clients)
90
+ if session_id_override:
91
+ return session_id_override
92
+
93
+ # Strategy 2: Check for session header (optional for frontend)
94
+ session_header = request.headers.get("X-Session-ID")
95
+ if session_header:
96
+ return session_header
97
+
98
+ # Strategy 3: Use client IP for backward compatibility
99
+ # This gives each client IP their own persistent session
100
+ client_ip = request.client.host if request.client else "unknown"
101
+ ip_session_id = f"ip_{client_ip}"
102
+
103
+ # Get or create session for this IP
104
+ session = session_manager.get_session(ip_session_id)
105
+ return session.session_id
106
+
107
+
108
+
109
+ # Helper functions (same as before)
110
  def _is_valid_response(response: str, persona_id: str) -> bool:
111
  """Validate response quality"""
112
  if len(response) < 2 or len(response) > 5000:
113
  return False
114
 
 
115
  confusion_indicators = [
116
  f"Thank you, Dr. {persona_id.title()}",
117
  "Assistant:",
118
+ f"Dr. {persona_id.title()}",
119
+ "Assistant:",
120
  f"Dr. {persona_id.title()} Advisor:",
121
  "excellent discussion, Assistant"
122
  ]
 
132
  }
133
  return fallbacks.get(persona_id, "I'd be happy to help. Could you provide more details?")
134
 
135
+ # Provider management endpoints (EXACTLY THE SAME)
136
  @router.get("/current-provider")
137
  async def get_current_provider():
138
  return {
 
155
  )
156
 
157
  try:
 
158
  current_provider = provider_data.provider
 
 
159
  new_llm = create_llm_client(current_provider)
160
  llm = new_llm
161
 
 
162
  new_personas = get_default_personas(new_llm)
163
  chat_orchestrator.personas.clear()
164
  for persona in new_personas:
165
  chat_orchestrator.register_persona(persona)
166
 
 
 
 
167
  return {
168
  "message": f"Successfully switched to {current_provider}",
169
  "current_provider": current_provider,
 
179
  detail=f"Failed to switch to {provider_data.provider}: {str(e)}"
180
  )
181
 
182
+ # Main chat endpoint (SAME INTERFACE, improved backend)
183
  @router.post("/chat-sequential")
184
+ async def chat_sequential(message: ChatMessage, request: Request):
185
+ """
186
+ SAME INTERFACE AS BEFORE - Generate advisor responses
187
+ Now with improved session management behind the scenes
188
+ """
189
  try:
190
+ # Get session using compatibility layer
191
+ session_id = get_or_create_session_for_request(request, message.session_id)
192
+
193
+ # Use the new orchestrator with session management
194
+ result = await chat_orchestrator.process_message(
195
+ user_input=message.user_input,
196
+ session_id=session_id,
197
+ response_length=message.response_length
198
+ )
199
+
200
+ # Convert new format back to old format for backward compatibility
201
+ if result["type"] == "clarification":
202
  return {
203
  "type": "orchestrator_question",
204
  "responses": [{
205
  "persona": "PhD Advisor Assistant",
206
+ "response": result["message"]
207
  }],
208
+ "collected_info": {}
209
  }
210
+
211
+ elif result["type"] == "persona_responses":
212
+ # Convert new response format to old format
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  return {
214
+ "type": "sequential_responses",
215
+ "responses": [
216
+ {
217
+ "persona": resp["persona_name"],
218
+ "persona_id": resp["persona_id"],
219
+ "response": resp["response"]
220
+ }
221
+ for resp in result["responses"]
222
+ ],
223
+ "collected_info": {}
224
+ }
225
+
226
+ else:
227
+ return {
228
+ "type": "error",
229
+ "responses": [{
230
+ "persona": "System",
231
+ "response": result.get("message", "Please try again.")
232
+ }]
233
  }
234
 
235
  except Exception as e:
236
+ logger.error(f"Error in chat_sequential: {e}")
237
  return {
238
  "type": "error",
239
  "responses": [{
 
242
  }]
243
  }
244
 
245
+ # Individual advisor endpoint (SAME INTERFACE)
 
246
  @router.post("/chat/{persona_id}")
247
+ async def chat_with_specific_advisor(persona_id: str, input: UserInput, request: Request):
248
+ """Chat with a specific advisor - SAME INTERFACE"""
249
  try:
250
  if persona_id not in chat_orchestrator.personas:
251
  raise HTTPException(status_code=404, detail=f"Persona '{persona_id}' not found")
252
 
253
+ # Get session using compatibility layer
254
+ session_id = get_or_create_session_for_request(request)
255
+
256
+ # Use new orchestrator
257
+ result = await chat_orchestrator.chat_with_persona(
258
+ user_input=input.user_input,
259
+ persona_id=persona_id,
260
+ session_id=session_id
261
+ )
262
+
263
+ if result["type"] == "single_persona_response":
264
+ persona_data = result["persona"]
265
+ return {
266
+ "persona": persona_data["persona_name"],
267
+ "persona_id": persona_data["persona_id"],
268
+ "response": persona_data["response"]
269
+ }
270
+ else:
271
+ return {
272
+ "persona": "System",
273
+ "response": result.get("message", "I'm having trouble generating a response right now. Please try again.")
274
+ }
275
+
276
  except HTTPException:
277
  raise
278
  except Exception as e:
279
+ logger.error(f"Error in chat_with_specific_advisor: {e}")
280
  return {
281
  "persona": "System",
282
  "response": "I'm having trouble generating a response right now. Please try again."
283
  }
284
 
285
+ # Reply to advisor endpoint (SAME INTERFACE)
286
  @router.post("/reply-to-advisor")
287
+ async def reply_to_advisor(reply: ReplyToAdvisor, request: Request):
288
+ """Reply to a specific advisor - SAME INTERFACE"""
 
289
  try:
290
  if reply.advisor_id not in chat_orchestrator.personas:
291
  raise HTTPException(status_code=404, detail=f"Advisor '{reply.advisor_id}' not found")
292
 
293
+ # Get session using compatibility layer
294
+ session_id = get_or_create_session_for_request(request)
 
 
 
295
 
296
+ # Use new orchestrator
297
+ result = await chat_orchestrator.chat_with_persona(
298
+ user_input=reply.user_input,
299
+ persona_id=reply.advisor_id,
300
+ session_id=session_id
301
+ )
302
 
303
+ if result["type"] == "single_persona_response":
304
+ persona_data = result["persona"]
305
+ return {
306
+ "type": "advisor_reply",
307
+ "persona": persona_data["persona_name"],
308
+ "persona_id": persona_data["persona_id"],
309
+ "response": persona_data["response"],
310
+ "original_message_id": reply.original_message_id
311
+ }
312
+ else:
313
+ return {
314
+ "type": "error",
315
+ "persona": "System",
316
+ "response": result.get("message", "I'm having trouble generating a reply right now. Please try again.")
317
+ }
318
 
319
  except HTTPException:
320
  raise
321
  except Exception as e:
322
+ logger.error(f"Error in reply_to_advisor: {e}")
323
  return {
324
  "type": "error",
325
  "persona": "System",
326
  "response": "I'm having trouble generating a reply right now. Please try again."
327
  }
328
 
329
+ # Document upload (SAME INTERFACE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  @router.post("/upload-document")
331
+ async def upload_document(file: UploadFile = File(...), request: Request = None):
332
+ """Upload document - SAME INTERFACE"""
333
  if file.content_type not in [
334
  "application/pdf",
335
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
 
338
  raise HTTPException(status_code=400, detail="Unsupported file type.")
339
 
340
  try:
341
+ # Get session using compatibility layer
342
+ session_id = get_or_create_session_for_request(request)
343
+ session = session_manager.get_session(session_id)
344
+
345
  file_bytes = await file.read()
346
 
347
+ # Simple size check
348
+ MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
349
+ if len(file_bytes) > MAX_FILE_SIZE:
350
  raise HTTPException(status_code=400, detail="Upload exceeds session document size limit (10 MB).")
351
 
 
352
  content = extract_text_from_file(file_bytes, file.content_type)
353
  if not content.strip():
354
  raise HTTPException(status_code=400, detail="Document is empty or unreadable.")
355
 
356
+ # Add to session context using new system
357
+ session.add_uploaded_file(file.filename, content, len(file_bytes))
 
 
358
 
359
  return {"message": "Document uploaded and added to context successfully."}
360
 
361
+ except HTTPException:
362
+ raise
363
  except Exception as e:
364
+ logger.error(f"Error uploading document: {str(e)}")
365
  raise HTTPException(status_code=500, detail=f"Error processing document: {str(e)}")
366
 
367
+ # Get uploaded files (SAME INTERFACE)
368
+ @router.get("/uploaded-files")
369
+ async def get_uploaded_filenames(request: Request):
370
+ """Get uploaded files - SAME INTERFACE"""
371
+ try:
372
+ session_id = get_or_create_session_for_request(request)
373
+ session = session_manager.get_session(session_id)
374
+ return {"files": session.uploaded_files}
375
+ except Exception as e:
376
+ logger.error(f"Error getting uploaded files: {str(e)}")
377
+ return {"files": []}
378
+
379
+ # Context endpoint (SAME INTERFACE)
380
+ @router.get("/context")
381
+ async def get_context(request: Request):
382
+ """Get context - SAME INTERFACE"""
383
+ try:
384
+ session_id = get_or_create_session_for_request(request)
385
+ session = session_manager.get_session(session_id)
386
+ return session.messages # Return messages in same format as before
387
+ except Exception as e:
388
+ logger.error(f"Error getting context: {str(e)}")
389
+ return []
390
+
391
+ # Reset session (SAME INTERFACE)
392
+ @router.post("/reset-session")
393
+ async def reset_session(request: Request):
394
+ """Reset session - SAME INTERFACE"""
395
+ try:
396
+ session_id = get_or_create_session_for_request(request)
397
+ success = chat_orchestrator.reset_session(session_id)
398
+
399
+ if success:
400
+ return {"status": "reset", "message": "Session reset successfully"}
401
+ else:
402
+ return {"status": "error", "message": "Failed to reset session"}
403
+ except Exception as e:
404
+ logger.error(f"Error resetting session: {e}")
405
+ return {"status": "error", "message": "Failed to reset session"}
406
+
407
+ # Legacy model endpoints (SAME INTERFACE)
408
+ @router.post("/switch-model")
409
+ async def switch_model(model_name: str = Body(...)):
410
+ """Legacy model switching - SAME INTERFACE"""
411
+ if "gemini" in model_name.lower():
412
+ return await switch_provider(ProviderSwitch(provider="gemini"))
413
+ else:
414
+ return await switch_provider(ProviderSwitch(provider="ollama"))
415
+
416
+ @router.get("/current-model")
417
+ async def get_current_model():
418
+ """Legacy model info - SAME INTERFACE"""
419
+ model_name = llm.model_name if hasattr(llm, 'model_name') else "gemini-2.0-flash"
420
  return {
421
+ "model": model_name,
422
+ "provider": current_provider
 
 
 
 
 
 
423
  }
424
 
425
+ # Debug endpoint (SAME INTERFACE)
426
+ @router.get("/debug/personas")
427
+ async def debug_personas(request: Request):
428
+ """Debug personas - SAME INTERFACE"""
429
+ try:
430
+ session_id = get_or_create_session_for_request(request)
431
+ session = session_manager.get_session(session_id)
432
+
433
+ return {
434
+ "personas": {
435
+ pid: {
436
+ "name": persona.name,
437
+ "prompt": persona.system_prompt[:100] + "..."
438
+ } for pid, persona in chat_orchestrator.personas.items()
439
+ },
440
+ "context_length": len(session.messages),
441
+ "current_provider": current_provider
442
+ }
443
+ except Exception as e:
444
+ logger.error(f"Error in debug endpoint: {str(e)}")
445
+ return {
446
+ "personas": {},
447
+ "context_length": 0,
448
+ "current_provider": current_provider
449
+ }
450
+
451
+ # Ask endpoint (SAME INTERFACE)
452
  class PersonaQuery(BaseModel):
453
  question: str
454
  persona: str
455
 
456
  @router.post("/ask/")
457
+ async def ask_question(query: PersonaQuery, request: Request):
458
+ """Ask question - SAME INTERFACE"""
459
+ try:
460
+ session_id = get_or_create_session_for_request(request)
461
+
462
+ # Use the new orchestrator
463
+ result = await chat_orchestrator.chat_with_persona(
464
+ user_input=query.question,
465
+ persona_id=query.persona,
466
+ session_id=session_id
467
+ )
468
+
469
+ if result["type"] == "single_persona_response":
470
+ response_text = result["persona"]["response"]
471
+ else:
472
+ response_text = result.get("message", "I'm having trouble responding right now.")
473
+
474
+ return {"response": response_text}
475
+
476
+ except Exception as e:
477
+ logger.error(f"Error in ask endpoint: {str(e)}")
478
+ return {"response": "I encountered an error. Please try again."}
479
 
480
+ # Root endpoint (SAME INTERFACE)
481
+ @router.get("/")
482
+ def root():
483
+ """Root endpoint - SAME INTERFACE with updated info"""
484
+ return {
485
+ "message": "Multi-LLM PhD Advisor Backend is up and running",
486
+ "version": "1.0.0", # Updated version
487
+ "features": [
488
+ "Improved Session Management",
489
+ "Unified Context Handling",
490
+ "Ollama Support",
491
+ "Gemini API Support",
492
+ "Provider Switching"
493
+ ]
494
+ }
multi_llm_chatbot_backend/app/main.py CHANGED
@@ -1,34 +1,42 @@
1
- # app/main.py
2
  from fastapi import FastAPI
3
  from fastapi.middleware.cors import CORSMiddleware
4
- from app.api.routes import router
5
  from dotenv import load_dotenv
 
6
  import os
7
 
8
- # Load environment variables
 
 
 
 
9
  load_dotenv()
10
 
11
  app = FastAPI(
12
  title="Multi-LLM Chatbot Backend",
13
- version="0.2"
14
  )
15
 
16
- # Add CORS middleware
17
  app.add_middleware(
18
  CORSMiddleware,
19
- allow_origins=["http://localhost:3000"], # React dev server
20
  allow_credentials=True,
21
  allow_methods=["*"],
22
  allow_headers=["*"],
23
  )
24
 
25
- # Include route definitions
26
  app.include_router(router)
27
 
28
  @app.get("/")
29
  def root():
30
  return {
31
  "message": "Multi-LLM PhD Advisor Backend is up and running",
32
- "version": "0.2",
33
- "features": ["Ollama Support", "Gemini API Support", "Provider Switching"]
 
 
 
 
 
 
34
  }
 
 
1
  from fastapi import FastAPI
2
  from fastapi.middleware.cors import CORSMiddleware
3
+ from app.api.routes import router # This line stays the same!
4
  from dotenv import load_dotenv
5
+ import logging
6
  import os
7
 
8
+ logging.basicConfig(
9
+ level=logging.INFO,
10
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
11
+ )
12
+
13
  load_dotenv()
14
 
15
  app = FastAPI(
16
  title="Multi-LLM Chatbot Backend",
17
+ version="1.0.0" # Updated version
18
  )
19
 
 
20
  app.add_middleware(
21
  CORSMiddleware,
22
+ allow_origins=["http://localhost:3000"],
23
  allow_credentials=True,
24
  allow_methods=["*"],
25
  allow_headers=["*"],
26
  )
27
 
 
28
  app.include_router(router)
29
 
30
  @app.get("/")
31
  def root():
32
  return {
33
  "message": "Multi-LLM PhD Advisor Backend is up and running",
34
+ "version": "1.0.0",
35
+ "features": [
36
+ "Improved Session Management",
37
+ "Unified Context Handling",
38
+ "Ollama Support",
39
+ "Gemini API Support",
40
+ "Provider Switching"
41
+ ]
42
  }