nikeshn commited on
Commit
22c5c73
·
verified ·
1 Parent(s): 1a79bfe

Update src/agent.py

Browse files
Files changed (1) hide show
  1. src/agent.py +374 -1
src/agent.py CHANGED
@@ -58,4 +58,377 @@ PURE_GREETING_RE = re.compile(r"^(hi|hello|hey|good morning|good afternoon|good
58
  GREETING_FOLLOWUP_RE = re.compile(r"^(yes|yes please|yes check|sure|okay|ok|yeah|yep|please do|go ahead)$", re.IGNORECASE)
59
  HOURS_RE = re.compile(r"\b(library hours?|opening hours?|closing hours?|open hours?|open today|open tomorrow|open now|open on|open this|closed today|closed tomorrow|closed on|closed this|when.*open|when.*close|when.*does.*library|what time.*open|what time.*close|what time.*library|opening time|closing time|is.*library.*open|is.*campus.*library|is.*habshan.*open|is.*san.*open|is.*library.*closed|will.*library.*open|will.*it.*open|does.*library.*open|library.*open.*today|library.*close.*today|habshan.*open|san.*open|main campus.*open|ramadan hours?|weekend hours?|friday hours?|saturday hours?|sunday hours?|library.*schedule|schedule.*library|what.*hours|till.*when|until.*when|what day.*open|which day.*open|what day.*closed|which day.*closed)\b", re.IGNORECASE)
60
  KU_CAMPUS_RE = re.compile(r"\b(where is the library|where is.*library|library.*location|location.*library|which building|what building|which floor|main campus library|san campus library|habshan library|sas al nakhl library|library.*address|address.*library|how to get to.*library|how.*reach.*library|how.*find.*library|library.*campus(es)?|campus(es)?.*library|how many.*librar|how many.*branch|library.*branch(es)?|branch(es)?.*library|which campus.*library|library.*which campus|library.*map|map.*library|how many libraries|what libraries|which libraries|(ku|khalifa).*(has|have|got).*(librar|branch)|(librar|branch).*(ku|khalifa).*(has|have)|(all|both|two|2).*(libraries|library|branches)|tell me about.*(campus|libraries|branches)|describe.*(campus|libraries)|(what|where).*(campus|campuses).*(librar))\b", re.IGNORECASE)
61
- RESEARCH_CUE_RE = re.compile(r"\b(find|search|look for|show me|get me|give me|locate|recommend|suggest|articles?|papers?|books?|journals?|studies|literature|research|systematic review|evidence|sources|database|databases|peer reviewed|open access|advances?|approach(es)?|method(s|ology)?|*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  GREETING_FOLLOWUP_RE = re.compile(r"^(yes|yes please|yes check|sure|okay|ok|yeah|yep|please do|go ahead)$", re.IGNORECASE)
59
  HOURS_RE = re.compile(r"\b(library hours?|opening hours?|closing hours?|open hours?|open today|open tomorrow|open now|open on|open this|closed today|closed tomorrow|closed on|closed this|when.*open|when.*close|when.*does.*library|what time.*open|what time.*close|what time.*library|opening time|closing time|is.*library.*open|is.*campus.*library|is.*habshan.*open|is.*san.*open|is.*library.*closed|will.*library.*open|will.*it.*open|does.*library.*open|library.*open.*today|library.*close.*today|habshan.*open|san.*open|main campus.*open|ramadan hours?|weekend hours?|friday hours?|saturday hours?|sunday hours?|library.*schedule|schedule.*library|what.*hours|till.*when|until.*when|what day.*open|which day.*open|what day.*closed|which day.*closed)\b", re.IGNORECASE)
60
  KU_CAMPUS_RE = re.compile(r"\b(where is the library|where is.*library|library.*location|location.*library|which building|what building|which floor|main campus library|san campus library|habshan library|sas al nakhl library|library.*address|address.*library|how to get to.*library|how.*reach.*library|how.*find.*library|library.*campus(es)?|campus(es)?.*library|how many.*librar|how many.*branch|library.*branch(es)?|branch(es)?.*library|which campus.*library|library.*which campus|library.*map|map.*library|how many libraries|what libraries|which libraries|(ku|khalifa).*(has|have|got).*(librar|branch)|(librar|branch).*(ku|khalifa).*(has|have)|(all|both|two|2).*(libraries|library|branches)|tell me about.*(campus|libraries|branches)|describe.*(campus|libraries)|(what|where).*(campus|campuses).*(librar))\b", re.IGNORECASE)
61
+ RESEARCH_CUE_RE = re.compile(r"\b(find|search|look for|show me|get me|give me|locate|recommend|suggest|articles?|papers?|books?|journals?|studies|literature|research|systematic review|evidence|sources|database|databases|peer reviewed|open access|advances?|approach(es)?|method(s|ology)?|technique[s]?|application[s]?|framework[s]?|model[s]?|algorithm[s]?|publication[s]?|recent work|current state|state of the art)\b", re.IGNORECASE)
62
+ LIBRARY_CUE_RE = re.compile(r"\b(library|librarian|borrow|loan|renew|fine|study room|room booking|reserve a room|account|my library|interlibrary|ill|khazna|orcid|open access|apc|refworks|libkey|hours|location|contact|visitor|alumni|database access|off campus|remote access)\b", re.IGNORECASE)
63
+ MEDICAL_SEARCH_RE = re.compile(r"\b(pubmed|embase|cinahl|clinicalkey|cochrane|uptodate|medline|systematic review|clinical trial|biomedical literature|medical literature)\b", re.IGNORECASE)
64
+ MEDICAL_KEYWORDS = [
65
+ 'medicine','medical','health','clinical','nursing','pharmacy','biomedical',
66
+ 'anatomy','physiology','epidemiology','oncology','cardiology','neuroscience',
67
+ 'genomics','pathology','surgery','mental health','nutrition','public health',
68
+ 'disease','therapy','treatment','diagnosis','patient','drug','pharmaceutical',
69
+ 'hospital','cancer','diabetes','heart','brain','cell biology','immunology',
70
+ 'biochemistry','microbiology','virology','pediatric','psychiatric','dental',
71
+ 'ophthalmology','radiology','anesthesia','emergency medicine','geriatric',
72
+ 'crispr','gene therapy','gene editing','genomic medicine','precision medicine',
73
+ ]
74
+
75
+ CURRENT_INFO_RE = re.compile(r"\b(current|currently|latest|today|recent|recently|now|this week|this month|news|update|updated|president|prime minister|ceo|minister|king|ruler|who won)\b", re.IGNORECASE)
76
+
77
+ # Hardcoded answers
78
+ LIBRARY_HOURS_URL = "https://library.ku.ac.ae/hours"
79
+ KU_MAIN_URL = "https://www.ku.ac.ae/"
80
+
81
+ GROUNDED_LIBRARY_MAP = {
82
+ "ill": "interlibrary loan ILL document delivery request article request book unavailable not in collection borrow from another library",
83
+ "fulltext": "full text libkey nomad article access pdf article not available no full text get full text request article",
84
+ "primo": "PRIMO discovery catalog holdings publication finder library has this journal books articles search catalog",
85
+ "circulation": "borrowing circulation renew renewal loan period due date hold request fines fees my library account checked out overdue",
86
+ "research_help": "Ask a Librarian research consultation reference help subject guides research skills instruction tours workshops",
87
+ "orcid_oa": "ORCID open access APC publishing research impact Scopus SciVal bibliometrics openaccess",
88
+ "database_access": "database access e-resources remote access off campus login vendor issue Rani Anand e-books patents",
89
+ "medical_help": "medical librarian Jason Fetty PubMed Embase CINAHL Cochrane UpToDate systematic review clinical databases",
90
+ "systems_help": "Walter Brian Hall systems website technology digital services library systems coding STEAM open access APC",
91
+ "acquisitions": "Alia Al-Harrasi Meera Alnaqbi acquisitions request title suggest a book collection development technical services cataloguing metadata",
92
+ "cataloguing": "Shamsa Alhosani William Hutchinson Alia Al-Harrasi cataloguing cataloging metadata technical services",
93
+ "rooms": "study room reserve a room room booking group study rooms libcal book room room reservation",
94
+ }
95
+
96
+ # LLM Classifier Prompt
97
+ _CLASSIFIER_SYSTEM = """You are the routing brain of LibBee, the Khalifa University Library AI Assistant (Abu Dhabi, UAE).
98
+ Your only job: read the user message and return a single JSON object routing it to the right handler.
99
+
100
+ INTENTS — pick exactly one:
101
+ "social" — casual chat, greetings, emotions, compliments, complaints, vague help requests
102
+ "library_info" — questions about KU Library services, hours, rooms, borrowing, fines, ILL, staff, databases, ORCID, RefWorks, open access, APC, account, printing, remote access, LibKey, acquisitions, Khazna, thesis submission, library policies, alumni access
103
+ "search_academic"— user wants to FIND articles / papers / books / literature on a specific academic topic
104
+ "search_medical" — same as search_academic but topic is medical/clinical/nursing/pharmacy/health sciences
105
+ "general_recent" — questions about current events, news, latest info, who holds a position now, real-time data (stock prices, today's weather, breaking news)
106
+ "general" — factual/knowledge question NOT about KU Library and NOT a research search (explain a concept, historical facts, definitions)
107
+
108
+ RULES:
109
+ 1. When in doubt between library_info and social, choose library_info if ANY library keyword is present.
110
+ 2. "I don't know how to search for articles" = library_info (has library task content).
111
+ 3. "I am fed up with searching" alone (no specific topic/database) = social (expressing frustration).
112
+ 4. "I need help finding articles on diabetes" = search_medical.
113
+ 5. KU = Khalifa University. Do NOT confuse with Kuwait University.
114
+ 6. ANY question asking about advances, approaches, methods, effectiveness, impact, shifts, trends, bottlenecks, or relationship on a specific academic/scientific topic = search_academic or search_medical.
115
+
116
+ For "social" intent ONLY: also include "casual_answer" — a warm 1-3 sentence LibBee response (friendly librarian tone, no markdown, no bullet points). Offer to help with library services.
117
+ For all other intents: omit "casual_answer" entirely.
118
+
119
+ Respond ONLY with valid JSON, no preamble, no markdown fences.
120
+
121
+ Examples:
122
+ {"intent":"social","casual_answer":"Of course, I'm here to help! I'm LibBee, the KU Library AI Assistant. What would you like to find today?"}
123
+ {"intent":"library_info"}
124
+ {"intent":"search_academic"}
125
+ {"intent":"search_medical"}
126
+ {"intent":"general_recent"}
127
+ {"intent":"general"}"""
128
+
129
+ # ========== HELPER FUNCTIONS ==========
130
+
131
+ def _is_greeting_menu_followup(question: str, history: List[ChatMessage]) -> bool:
132
+ q = (question or "").strip()
133
+ if not GREETING_FOLLOWUP_RE.match(q):
134
+ return False
135
+ # Check last assistant message
136
+ for m in reversed(history):
137
+ if m.role == "assistant":
138
+ last = m.content.lower()
139
+ return "i'm <strong>libbee</strong>" in last or "are you looking for one of these" in last
140
+ return False
141
+
142
+ def _greeting_menu_clarify_answer() -> str:
143
+ return (
144
+ "Sure — what would you like help with?<br><br>"
145
+ "You can choose one of these or type your question directly:<br>"
146
+ "• Find articles and books<br>"
147
+ "• Get full text for an article<br>"
148
+ "• Submit an Interlibrary Loan (ILL) request<br>"
149
+ "• Contact a librarian<br>"
150
+ "• Check library hours"
151
+ )
152
+
153
+ def _looks_library_hours_question(question: str) -> bool:
154
+ q = question.strip().lower()
155
+ return bool(HOURS_RE.search(q)) and any(
156
+ kw in q for kw in (
157
+ 'library', 'campus', 'habshan', 'san', 'hours', 'open', 'close',
158
+ 'schedule', 'till', 'until', 'today', 'tomorrow', 'friday',
159
+ 'saturday', 'sunday', 'week', 'ramadan'
160
+ )
161
+ )
162
+
163
+ def _looks_campus_question(question: str) -> bool:
164
+ return bool(KU_CAMPUS_RE.search(question))
165
+
166
+ def _looks_medical_search(question: str) -> bool:
167
+ q = question.lower()
168
+ if not any(kw in q for kw in MEDICAL_KEYWORDS):
169
+ return False
170
+ return bool(MEDICAL_SEARCH_RE.search(q) or RESEARCH_CUE_RE.search(q))
171
+
172
+ def _looks_research_question(question: str) -> bool:
173
+ q = question.lower()
174
+ if RESEARCH_CUE_RE.search(q):
175
+ return True
176
+ return False
177
+
178
+ def _hours_redirect_answer() -> str:
179
+ return (
180
+ "Here are the current <strong>Khalifa University Library</strong> hours:<br><br>"
181
+ "<strong>Habshan Library (SAN Campus):</strong><br>"
182
+ "Sunday – Wednesday: 8:00 AM – 7:00 PM<br>"
183
+ "Thursday: 8:00 AM – 4:00 PM<br>"
184
+ "Friday: Closed<br>"
185
+ "Saturday: 12:00 PM – 8:00 PM<br><br>"
186
+ "⚠️ Hours may change during exam periods, public holidays, and Ramadan. "
187
+ "Always check the official Library Hours page for the latest times:<br>"
188
+ f'<a href="{LIBRARY_HOURS_URL}" target="_blank">{LIBRARY_HOURS_URL}</a>'
189
+ )
190
+
191
+ def _ku_campus_answer() -> str:
192
+ return (
193
+ "Khalifa University Library has <strong>two branches</strong>:<br><br>"
194
+ "<strong>1. Main Campus Library</strong><br>"
195
+ "Location: E Building, Main Campus, Abu Dhabi<br>"
196
+ "Reference desk: <a href=\"tel:+97123124604\">+971 2 312 4604</a><br><br>"
197
+ "<strong>2. Habshan Library (SAN Campus)</strong><br>"
198
+ "Location: Building 5, Ground Floor &amp; First Floor, "
199
+ "Sas Al Nakhl (SAN) Campus, Abu Dhabi<br>"
200
+ "Service desk: <a href=\"tel:+97123123160\">+971 2 312 3160</a><br><br>"
201
+ "Both branches offer study spaces, computer workstations, printing, scanning, "
202
+ "and access to physical collections.<br><br>"
203
+ "For general library enquiries: "
204
+ "<a href=\"https://library.ku.ac.ae/AskUs\" target=\"_blank\">Ask a Librarian</a> "
205
+ "or email <a href=\"mailto:libse@ku.ac.ae\">libse@ku.ac.ae</a>."
206
+ )
207
+
208
+ def _ku_general_redirect_answer() -> str:
209
+ return (
210
+ f"I'm <strong>LibBee</strong>, the Khalifa University <strong>Library</strong> AI Assistant, so I'm best for library resources, services, databases, research help, and staff contacts.<br><br>"
211
+ f"For general Khalifa University questions, please visit the main KU website: <a href=\"{KU_MAIN_URL}\" target=\"_blank\">{KU_MAIN_URL}</a>. "
212
+ "A broader university chatbot is available there for non-library questions."
213
+ )
214
+
215
+ def _is_social_or_greeting(question: str) -> bool:
216
+ return bool(SOCIAL_RE.match((question or "").strip()))
217
+
218
+ async def _llm_classify(question: str, history: List[ChatMessage], model: str) -> Dict:
219
+ """Classify intent using LLM"""
220
+ settings = get_settings()
221
+
222
+ if not settings.openai_api_key:
223
+ # Fallback to rule-based if no API key
224
+ q = question.lower()
225
+ if SOCIAL_RE.match(q):
226
+ return {"intent": "social", "casual_answer": "Hello! I'm LibBee, the KU Library AI Assistant. I'm here to help you with articles, books, databases, and library services. What would you like to find today?"}
227
+ elif _looks_medical_search(question):
228
+ return {"intent": "search_medical"}
229
+ elif _looks_research_question(question):
230
+ return {"intent": "search_academic"}
231
+ elif LIBRARY_CUE_RE.search(q):
232
+ return {"intent": "library_info"}
233
+ elif CURRENT_INFO_RE.search(q):
234
+ return {"intent": "general_recent"}
235
+ else:
236
+ return {"intent": "general"}
237
+
238
+ try:
239
+ ctx_parts = []
240
+ for m in history[-4:]:
241
+ if m.role in ("user", "assistant") and m.content:
242
+ ctx_parts.append(f"{m.role}: {m.content[:100]}")
243
+ ctx = " | ".join(ctx_parts) if ctx_parts else "none"
244
+
245
+ user_msg = f"Message: {question[:400]}\nContext: {ctx}\nJSON:"
246
+
247
+ if model == "claude" and settings.anthropic_api_key:
248
+ from langchain_anthropic import ChatAnthropic
249
+ llm = ChatAnthropic(model="claude-3-haiku-20240307", temperature=0, max_tokens=150)
250
+ else:
251
+ from langchain_openai import ChatOpenAI
252
+ llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, max_tokens=150)
253
+
254
+ response = llm.invoke([
255
+ {"role": "system", "content": _CLASSIFIER_SYSTEM},
256
+ {"role": "user", "content": user_msg},
257
+ ])
258
+
259
+ raw = response.content.strip()
260
+ if raw.startswith("```"):
261
+ raw = raw.split("\n", 1)[1].rsplit("```", 1)[0].strip()
262
+
263
+ result = json.loads(raw)
264
+ intent = result.get("intent", "general")
265
+ if intent not in {"social", "library_info", "search_academic", "search_medical", "general_recent", "general"}:
266
+ intent = "general"
267
+
268
+ return {"intent": intent, "casual_answer": result.get("casual_answer", "")}
269
+
270
+ except Exception as e:
271
+ print(f"Classifier error: {e}")
272
+ # Fallback
273
+ return {"intent": "general", "casual_answer": ""}
274
+
275
+ async def _libbee_casual_response(question: str, history: List[ChatMessage], model: str, hint: str = "") -> str:
276
+ """Generate casual response"""
277
+ if hint and len(hint.strip()) > 25:
278
+ return hint.strip()
279
+
280
+ settings = get_settings()
281
+ behavior = "You are LibBee, the Khalifa University Library AI Assistant. Respond warmly and naturally in 1-3 sentences as a friendly, knowledgeable librarian."
282
+
283
+ try:
284
+ if model == "claude" and settings.anthropic_api_key:
285
+ from langchain_anthropic import ChatAnthropic
286
+ llm = ChatAnthropic(model="claude-3-haiku-20240307", temperature=0.6, max_tokens=200)
287
+ else:
288
+ from langchain_openai import ChatOpenAI
289
+ llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.6, max_tokens=200)
290
+
291
+ msgs = [{"role": "system", "content": behavior}]
292
+ for m in history[-4:]:
293
+ if m.role in ("user", "assistant"):
294
+ msgs.append({"role": m.role, "content": m.content[:200]})
295
+ msgs.append({"role": "user", "content": question})
296
+
297
+ return llm.invoke(msgs).content.strip()
298
+ except Exception:
299
+ return "I'm <strong>LibBee</strong>, the Khalifa University Library AI Assistant — always happy to help! What can I find for you today?"
300
+
301
+ # ========== MAIN ENDPOINT ==========
302
+
303
+ @router.post("", response_model=AgentResponse)
304
+ async def agent_query(request: Request, req: AgentRequest):
305
+ start_time = time.time()
306
+ question = req.question.strip()
307
+ history = req.history
308
+ model = req.model
309
+
310
+ # LAYER 0: Instant Handlers
311
+
312
+ # 1. Pure greeting
313
+ if PURE_GREETING_RE.match(question):
314
+ return AgentResponse(
315
+ answer=(
316
+ "Hi! I'm <strong>LibBee</strong>, the Khalifa University Library AI Assistant.<br><br>"
317
+ "I can help you find articles and books, search databases, access full text, "
318
+ "request Interlibrary Loan (ILL), and answer questions about library services and staff.<br><br>"
319
+ "Are you looking for one of these? You can also type your question directly."
320
+ ),
321
+ intent="social_greeting",
322
+ tools_used=[],
323
+ model_used=model,
324
+ response_time=time.time() - start_time
325
+ )
326
+
327
+ # 2. Staff name match
328
+ staff_match = match_staff_name(question)
329
+ if staff_match:
330
+ return AgentResponse(
331
+ answer=staff_name_answer(staff_match),
332
+ intent="library_info",
333
+ tools_used=["staff_name_match"],
334
+ model_used=model,
335
+ response_time=time.time() - start_time
336
+ )
337
+
338
+ # 3. Staff role match
339
+ staff_role_match = match_staff_role(question)
340
+ if staff_role_match:
341
+ return AgentResponse(
342
+ answer=staff_role_answer(staff_role_match, question),
343
+ intent="library_info",
344
+ tools_used=["staff_role_match"],
345
+ model_used=model,
346
+ response_time=time.time() - start_time
347
+ )
348
+
349
+ # 4. Greeting menu followup
350
+ if _is_greeting_menu_followup(question, history):
351
+ return AgentResponse(
352
+ answer=_greeting_menu_clarify_answer(),
353
+ intent="social_greeting",
354
+ tools_used=[],
355
+ model_used=model,
356
+ is_follow_up=True,
357
+ response_time=time.time() - start_time
358
+ )
359
+
360
+ # 5. Library hours
361
+ if _looks_library_hours_question(question):
362
+ return AgentResponse(
363
+ answer=_hours_redirect_answer(),
364
+ intent="library_info",
365
+ tools_used=["hours_hardcoded"],
366
+ model_used=model,
367
+ ask_librarian=False,
368
+ response_time=time.time() - start_time
369
+ )
370
+
371
+ # 6. Campus/location
372
+ if _looks_campus_question(question):
373
+ return AgentResponse(
374
+ answer=_ku_campus_answer(),
375
+ intent="library_info",
376
+ tools_used=["campus_hardcoded"],
377
+ model_used=model,
378
+ ask_librarian=False,
379
+ response_time=time.time() - start_time
380
+ )
381
+
382
+ # LAYER 1: LLM Classification
383
+ cls_result = await _llm_classify(question, history, model)
384
+ intent = cls_result.get("intent", "general")
385
+ casual_hint = cls_result.get("casual_answer", "")
386
+
387
+ # LAYER 2: Execute by Intent
388
+
389
+ if intent == "social":
390
+ casual = await _libbee_casual_response(question, history, model, casual_hint)
391
+ return AgentResponse(
392
+ answer=casual,
393
+ intent="social_greeting",
394
+ tools_used=["libbee_casual"],
395
+ model_used=model,
396
+ response_time=time.time() - start_time
397
+ )
398
+
399
+ elif intent == "library_info":
400
+ # For now, return helpful message. Later add RAG here.
401
+ return AgentResponse(
402
+ answer="I can help with library information. For specific questions about hours, locations, or services, please check the Library Services tab, or ask me about:<br>• Library hours<br>• Study room booking<br>• Interlibrary Loan (ILL)<br>• Database access",
403
+ intent="library_info",
404
+ tools_used=["get_library_info"],
405
+ model_used=model,
406
+ ask_librarian=True,
407
+ response_time=time.time() - start_time
408
+ )
409
+
410
+ elif intent in ["search_academic", "search_medical"]:
411
+ # Return search guidance
412
+ return AgentResponse(
413
+ answer=f"I'll help you search for {'medical' if intent == 'search_medical' else 'academic'} literature on: <strong>{question}</strong>.<br><br>Use the <strong>AI Search</strong> tab for best results, or I can help you refine your search terms.",
414
+ intent=intent,
415
+ tools_used=["search_primo"],
416
+ model_used=model,
417
+ corrected_query=question,
418
+ natural_query=question,
419
+ database_query=question,
420
+ response_time=time.time() - start_time
421
+ )
422
+
423
+ else: # general or general_recent
424
+ return AgentResponse(
425
+ answer=_ku_general_redirect_answer(),
426
+ intent="general",
427
+ tools_used=["ku_general_redirect"],
428
+ model_used=model,
429
+ response_time=time.time() - start_time
430
+ )
431
+
432
+ @router.get("/test")
433
+ async def test():
434
+ return {"status": "ok", "message": "Agent endpoint is working!", "staff_loaded": len(STAFF_DIRECTORY)}