Girish Jeswani commited on
Commit
b4a89ba
·
1 Parent(s): 05e445e

update model queries for better responses

Browse files
multi_llm_chatbot_backend/app/api/routes.py CHANGED
@@ -24,7 +24,7 @@ def create_llm_client(provider: str = None) -> LLMClient:
24
 
25
  if provider == "gemini":
26
  try:
27
- return GeminiClient(model_name = os.getenv("GEMINI_MODEL"))
28
  except ValueError as e:
29
  # Fallback to Ollama if Gemini API key is not available
30
  print(f"Gemini API key not found, falling back to Ollama: {e}")
@@ -40,26 +40,20 @@ class ShortResponseOllamaClient(LLMClient):
40
  self.model_name = model_name
41
 
42
  async def generate(self, system_prompt: str, context: List[dict]) -> str:
43
- # Create a more natural conversation format
44
- messages = []
45
 
46
- # Add system message
47
- if system_prompt:
48
- messages.append(f"System: {system_prompt}")
49
 
50
- # Add conversation history
51
- for msg in context:
52
- role = msg['role'].capitalize()
53
- if role == "User":
54
- messages.append(f"Student: {msg['content']}")
55
- elif role in ["Methodist", "Theorist", "Pragmatist"]:
56
- messages.append(f"{role} Advisor: {msg['content']}")
57
- else:
58
- messages.append(f"{role}: {msg['content']}")
59
 
60
- # Create the final prompt
61
- conversation = "\n".join(messages)
62
- prompt = f"{conversation}\n\nAssistant:"
63
 
64
  payload = {
65
  "model": self.model_name,
@@ -69,8 +63,9 @@ class ShortResponseOllamaClient(LLMClient):
69
  "temperature": 0.7,
70
  "top_p": 0.9,
71
  "top_k": 40,
72
- "num_predict": 200,
73
  "repeat_penalty": 1.1,
 
74
  }
75
  }
76
 
@@ -80,20 +75,60 @@ class ShortResponseOllamaClient(LLMClient):
80
  response.raise_for_status()
81
  result = response.json().get("response", "[No response]").strip()
82
 
83
- # Clean up common issues
84
- result = result.replace("Here are 2-3 sentence", "").strip()
85
- result = result.replace("Here's an expansion of the advice:", "").strip()
86
- result = result.replace("conceptual insights:", "").strip()
87
- result = result.replace("actionable advice:", "").strip()
88
 
89
- # If response is too short or just punctuation, return a fallback
90
- if len(result) < 10 or result in [":", ".", ""]:
91
- return "I'd be happy to help with that. Could you provide more specific details about what you're looking for?"
92
 
93
  return result
94
 
95
  except Exception as e:
96
- return f"I apologize, but I'm having trouble generating a response right now. Please try again."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  # Initialize with default provider
99
  llm = create_llm_client()
@@ -117,38 +152,56 @@ class GlobalSessionContext:
117
  session_context = GlobalSessionContext()
118
 
119
  def create_default_personas(llm_client: LLMClient):
120
- """Create default personas with given LLM client"""
121
  return [
122
  Persona(
123
  id="methodist",
124
- name="Methodist Advisor",
125
- system_prompt="You are Dr. Methodist a meticulous, discipline-neutral advisor who specializes in research design, methodology, and validity." \
126
- "Your primary concern is whether the student's research plan is methodologically sound, feasible, and aligned with their stated research question." \
127
- "You value clarity, precision, and logical alignment between claims, methods, and outcomes. You often use language like “operationalize,”" \
128
- "“sampling frame,” “construct validity,” and “replication.” You frequently ask students to explain why a method is appropriate and whether " \
129
- "alternative designs might be more rigorous or parsimonious. You are not rude or dismissive, but you don’t sugarcoat weak designs. You believe" \
130
- " good methods are teachable and worth defending. Always explain your reasoning and, if appropriate, recommend ways to tighten or clarify the student’s approach.",
 
 
 
 
 
 
131
  llm=llm_client
132
  ),
133
  Persona(
134
  id="theorist",
135
- name="Theorist Advisor",
136
- system_prompt="You are Dr. Theorist an intellectually deep advisor who focuses on conceptual clarity, theoretical framing, and epistemological depth. You " \
137
- "are most helpful when a student needs to articulate, refine, or rethink the theoretical foundations of their work. You often ask questions like " \
138
- "“What assumptions underlie this framework?” or “How does this relate to tradition X or thinker Y?” You encourage students to think about ontology, " \
139
- "positionality, and the meaning of key terms in their research. You reference theories, concepts, and debates — especially from the humanities and " \
140
- "social sciences to help students sharpen their ideas. Your tone is thoughtful and reflective. You don’t rush to judgment but probe until the student's" \
141
- " conceptual scaffolding is robust. Avoid vague praise or technical critique — your role is to illuminate the deeper structure of ideas.",
 
 
 
 
 
142
  llm=llm_client
143
  ),
144
  Persona(
145
  id="pragmatist",
146
- name="Pragmatist Advisor",
147
- system_prompt="You are The Pragmatist an action-focused advisor who helps students move forward when they feel overwhelmed, stuck, or overthinking. You " \
148
- "prioritize clarity over perfection, progress over polish, and “done” over ideal. You frequently say things like “Let’s break this down” or “What’s one thing" \
149
- " you can do today?” You are warm, practical, and motivational. You don’t dwell on critique unless it helps unblock the student. You are especially helpful during" \
150
- " early drafts, decision paralysis, or writer’s block. Your suggestions should be immediately actionable, even if they’re not perfect. Always focus on the next step,"
151
- " and help reduce cognitive load wherever possible.",
 
 
 
 
 
 
 
152
  llm=llm_client
153
  )
154
  ]
@@ -179,6 +232,31 @@ class ReplyToAdvisor(BaseModel):
179
  class ProviderSwitch(BaseModel):
180
  provider: str
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  # Provider management endpoints
183
  @router.get("/current-provider")
184
  async def get_current_provider():
@@ -236,7 +314,7 @@ async def switch_provider(provider_data: ProviderSwitch):
236
  # Sequential advisor responses endpoint
237
  @router.post("/chat-sequential")
238
  async def chat_sequential(message: ChatMessage):
239
- """Generate advisor responses one by one for faster perceived response time"""
240
 
241
  try:
242
  orchestrator_result = await seamless_orchestrator.process_message(message.user_input)
@@ -253,37 +331,46 @@ async def chat_sequential(message: ChatMessage):
253
 
254
  elif orchestrator_result["status"] == "ready_for_advisors":
255
  enhanced_context = orchestrator_result["enhanced_context"]
 
 
 
256
  session_context.append("user", enhanced_context)
257
 
258
- # Generate responses sequentially
259
  advisor_order = ["methodist", "theorist", "pragmatist"]
260
  responses = []
261
 
262
  for i, persona_id in enumerate(advisor_order):
263
  try:
264
  persona = chat_orchestrator.personas[persona_id]
265
- # Get current context up to this point
266
- context = session_context.full_log.copy()
267
 
268
- # Generate response
269
- reply = await persona.respond(context)
270
 
271
- # Add this advisor's response to context for next advisor
272
- session_context.append(persona_id, reply)
273
 
274
- responses.append({
275
- "persona": persona.name,
276
- "persona_id": persona_id,
277
- "response": reply,
278
- "order": i
279
- })
 
 
 
 
 
 
 
 
 
 
280
 
281
  except Exception as e:
282
  print(f"Error generating response for {persona_id}: {e}")
283
  responses.append({
284
  "persona": chat_orchestrator.personas[persona_id].name,
285
  "persona_id": persona_id,
286
- "response": "I'm having trouble generating a response right now. Please try again.",
287
  "order": i
288
  })
289
 
@@ -435,8 +522,6 @@ async def upload_document(file: UploadFile = File(...)):
435
  except Exception as e:
436
  raise HTTPException(status_code=500, detail=f"Error processing document: {str(e)}")
437
 
438
-
439
-
440
  # Debug endpoint
441
  @router.get("/debug/personas")
442
  async def debug_personas():
 
24
 
25
  if provider == "gemini":
26
  try:
27
+ return GeminiClient(model_name=os.getenv("GEMINI_MODEL"))
28
  except ValueError as e:
29
  # Fallback to Ollama if Gemini API key is not available
30
  print(f"Gemini API key not found, falling back to Ollama: {e}")
 
40
  self.model_name = model_name
41
 
42
  async def generate(self, system_prompt: str, context: List[dict]) -> str:
43
+ # Build cleaner context - only include recent relevant messages
44
+ recent_context = context[-3:] if len(context) > 3 else context
45
 
46
+ # Create a focused prompt
47
+ prompt_parts = [system_prompt]
 
48
 
49
+ # Add only the user's current question
50
+ for msg in recent_context:
51
+ if msg['role'] == 'user':
52
+ prompt_parts.append(f"Student Question: {msg['content']}")
53
+ break # Only use the most recent user message
 
 
 
 
54
 
55
+ prompt_parts.append("Your Response:")
56
+ prompt = "\n\n".join(prompt_parts)
 
57
 
58
  payload = {
59
  "model": self.model_name,
 
63
  "temperature": 0.7,
64
  "top_p": 0.9,
65
  "top_k": 40,
66
+ "num_predict": 80, # Reduced from 200 to force shorter responses
67
  "repeat_penalty": 1.1,
68
+ "stop": ["\n\n", "Student:", "Question:", "Response:"] # Stop tokens
69
  }
70
  }
71
 
 
75
  response.raise_for_status()
76
  result = response.json().get("response", "[No response]").strip()
77
 
78
+ # Enhanced cleanup
79
+ result = self._clean_response(result)
 
 
 
80
 
81
+ # Validate response quality
82
+ if len(result) < 20 or self._is_poor_quality(result):
83
+ return self._get_fallback_response()
84
 
85
  return result
86
 
87
  except Exception as e:
88
+ return "I'm having trouble generating a response right now. Please try again."
89
+
90
+ def _clean_response(self, response: str) -> str:
91
+ """Clean up common response issues"""
92
+ # Remove common prefixes
93
+ prefixes_to_remove = [
94
+ "Here are 2-3 sentence", "Here's an expansion", "Assistant:",
95
+ "Dr. Methodist:", "Dr. Theorist:", "Dr. Pragmatist:",
96
+ "Methodist Advisor:", "Theorist Advisor:", "Pragmatist Advisor:",
97
+ ]
98
+
99
+ for prefix in prefixes_to_remove:
100
+ if response.startswith(prefix):
101
+ response = response[len(prefix):].strip()
102
+
103
+ # Remove trailing incomplete sentences
104
+ sentences = response.split('.')
105
+ if len(sentences) > 1 and len(sentences[-1].strip()) < 10:
106
+ response = '.'.join(sentences[:-1]) + '.'
107
+
108
+ # Remove excessive academic fluff
109
+ fluff_patterns = [
110
+ "conceptual insights:", "actionable advice:", "my inquisitive student",
111
+ "excellent question", "thank you for", "assistant!"
112
+ ]
113
+
114
+ for pattern in fluff_patterns:
115
+ response = response.replace(pattern, "").strip()
116
+
117
+ return response
118
+
119
+ def _is_poor_quality(self, response: str) -> bool:
120
+ """Check if response quality is poor"""
121
+ poor_indicators = [
122
+ "Thank you, Dr." in response, # AI confusion about identity
123
+ "Assistant:" in response,
124
+ len(response.split()) > 100, # Too verbose
125
+ response.count("?") > 3, # Too many questions
126
+ ]
127
+ return any(poor_indicators)
128
+
129
+ def _get_fallback_response(self) -> str:
130
+ """Return a simple fallback when quality is poor"""
131
+ return "I'd be happy to help with that. Could you provide more specific details about what you're looking for?"
132
 
133
  # Initialize with default provider
134
  llm = create_llm_client()
 
152
  session_context = GlobalSessionContext()
153
 
154
  def create_default_personas(llm_client: LLMClient):
155
+ """Create default personas with improved, concise system prompts"""
156
  return [
157
  Persona(
158
  id="methodist",
159
+ name="Dr. Methodist",
160
+ system_prompt="""You are Dr. Methodist, a research methodology expert.
161
+
162
+ RESPONSE RULES:
163
+ - Maximum 3 sentences
164
+ - Start with your recommendation
165
+ - Include ONE specific actionable step
166
+ - Use terms like "validity," "operationalize," "sampling frame"
167
+ - Focus on methodological rigor
168
+
169
+ TONE: Precise, helpful, focused on research design quality.
170
+
171
+ Example: "Use a cautious tone unless your methodology is exceptionally robust. Strong validity and clear operationalization justify more confident language. Next step: Review your methods section to assess how assertive you can be.""",
172
  llm=llm_client
173
  ),
174
  Persona(
175
  id="theorist",
176
+ name="Dr. Theorist",
177
+ system_prompt="""You are Dr. Theorist, a conceptual frameworks expert.
178
+ RESPONSE RULES:
179
+ - Maximum 3 sentences
180
+ - Start with conceptual perspective
181
+ - Reference theoretical positioning
182
+ - Ask ONE probing question when relevant
183
+ - Use terms like "epistemological," "framework," "assumptions"
184
+
185
+ TONE: Thoughtful, intellectually rigorous, conceptually focused.
186
+
187
+ Example: "Your tone should reflect your epistemological stance—bold if challenging frameworks, cautious if extending theory. Consider your relationship to existing literature. What theoretical assumptions underlie your approach?""",
188
  llm=llm_client
189
  ),
190
  Persona(
191
  id="pragmatist",
192
+ name="Dr. Pragmatist",
193
+ system_prompt="""You are Dr. Pragmatist, a practical action-focused advisor.
194
+
195
+ RESPONSE RULES:
196
+ - Maximum 2 sentences
197
+ - Start with clear, actionable advice
198
+ - Focus on immediate next steps
199
+ - Use phrases like "Quick fix:" "Next step:" "Try this:"
200
+ - Prioritize progress over perfection
201
+
202
+ TONE: Warm, motivational, results-oriented.
203
+
204
+ Example: "Start cautious and earn the right to be bold as you build your case. Quick fix: Use 'This study suggests...' rather than 'This study proves...'""",
205
  llm=llm_client
206
  )
207
  ]
 
232
  class ProviderSwitch(BaseModel):
233
  provider: str
234
 
235
+ # Helper functions for response validation
236
+ def _is_valid_response(response: str, persona_id: str) -> bool:
237
+ """Validate response quality"""
238
+ if len(response) < 20 or len(response) > 500:
239
+ return False
240
+
241
+ # Check for AI confusion indicators
242
+ confusion_indicators = [
243
+ f"Thank you, Dr. {persona_id.title()}",
244
+ "Assistant:",
245
+ f"Dr. {persona_id.title()} Advisor:",
246
+ "excellent discussion, Assistant"
247
+ ]
248
+
249
+ return not any(indicator in response for indicator in confusion_indicators)
250
+
251
+ def _get_persona_fallback(persona_id: str) -> str:
252
+ """Get persona-specific fallback responses"""
253
+ fallbacks = {
254
+ "methodist": "Focus on ensuring your methodology aligns with your research question. What specific method are you considering?",
255
+ "theorist": "Consider the theoretical framework underlying your approach. What assumptions guide your thinking?",
256
+ "pragmatist": "Let's break this down into actionable steps. What's the most important thing you need to decide today?"
257
+ }
258
+ return fallbacks.get(persona_id, "I'd be happy to help. Could you provide more details?")
259
+
260
  # Provider management endpoints
261
  @router.get("/current-provider")
262
  async def get_current_provider():
 
314
  # Sequential advisor responses endpoint
315
  @router.post("/chat-sequential")
316
  async def chat_sequential(message: ChatMessage):
317
+ """Generate advisor responses with improved quality controls"""
318
 
319
  try:
320
  orchestrator_result = await seamless_orchestrator.process_message(message.user_input)
 
331
 
332
  elif orchestrator_result["status"] == "ready_for_advisors":
333
  enhanced_context = orchestrator_result["enhanced_context"]
334
+
335
+ # Clear previous advisor responses to avoid confusion
336
+ session_context.clear()
337
  session_context.append("user", enhanced_context)
338
 
 
339
  advisor_order = ["methodist", "theorist", "pragmatist"]
340
  responses = []
341
 
342
  for i, persona_id in enumerate(advisor_order):
343
  try:
344
  persona = chat_orchestrator.personas[persona_id]
 
 
345
 
346
+ # Use clean context for each advisor (no cross-contamination)
347
+ clean_context = [{"role": "user", "content": enhanced_context}]
348
 
349
+ reply = await persona.respond(clean_context)
 
350
 
351
+ # Validate response before adding
352
+ if _is_valid_response(reply, persona_id):
353
+ responses.append({
354
+ "persona": persona.name,
355
+ "persona_id": persona_id,
356
+ "response": reply,
357
+ "order": i
358
+ })
359
+ else:
360
+ # Fallback response for invalid responses
361
+ responses.append({
362
+ "persona": persona.name,
363
+ "persona_id": persona_id,
364
+ "response": _get_persona_fallback(persona_id),
365
+ "order": i
366
+ })
367
 
368
  except Exception as e:
369
  print(f"Error generating response for {persona_id}: {e}")
370
  responses.append({
371
  "persona": chat_orchestrator.personas[persona_id].name,
372
  "persona_id": persona_id,
373
+ "response": _get_persona_fallback(persona_id),
374
  "order": i
375
  })
376
 
 
522
  except Exception as e:
523
  raise HTTPException(status_code=500, detail=f"Error processing document: {str(e)}")
524
 
 
 
525
  # Debug endpoint
526
  @router.get("/debug/personas")
527
  async def debug_personas():