nikeshn commited on
Commit
9783ac5
·
verified ·
1 Parent(s): b873e20

Update src/agent.py

Browse files
Files changed (1) hide show
  1. src/agent.py +185 -22
src/agent.py CHANGED
@@ -1,8 +1,18 @@
1
  from fastapi import APIRouter, HTTPException, Request
2
  from pydantic import BaseModel
3
- from typing import List, Optional
4
  import time
5
  import os
 
 
 
 
 
 
 
 
 
 
6
 
7
  router = APIRouter()
8
 
@@ -18,32 +28,185 @@ class AgentRequest(BaseModel):
18
  class AgentResponse(BaseModel):
19
  answer: str
20
  intent: str = "general"
21
- tools_used: list = []
22
- response_time: float = 0
 
23
  model_used: str = "gpt"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- # Temporary: Simple echo to test connectivity
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  @router.post("", response_model=AgentResponse)
27
  async def agent_query(request: Request, req: AgentRequest):
28
- """
29
- Main agent endpoint.
30
- For now, it echoes back. You'll migrate your app81.py logic here.
31
- """
32
  start = time.time()
33
 
34
- # TODO: Migrate your classification logic from app81.py here
35
- # For now, just return a welcome message
36
 
37
- response_time = time.time() - start
38
 
39
- return AgentResponse(
40
- answer=f"Hello! You asked: '{req.question}'. I'm ready to help once you migrate the full logic from app81.py.",
41
- intent="test",
42
- response_time=response_time,
43
- model_used=req.model
44
- )
45
-
46
- @router.post("/test")
47
- async def test_endpoint():
48
- """Quick test that doesn't require complex logic"""
49
- return {"status": "ok", "message": "Agent endpoint is working!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import APIRouter, HTTPException, Request
2
  from pydantic import BaseModel
3
+ from typing import List, Optional, Dict
4
  import time
5
  import os
6
+ import re
7
+ import json
8
+ from datetime import datetime
9
+
10
+ # Import OpenAI for classification
11
+ try:
12
+ from langchain_openai import ChatOpenAI
13
+ from langchain_anthropic import ChatAnthropic
14
+ except ImportError:
15
+ pass
16
 
17
  router = APIRouter()
18
 
 
28
  class AgentResponse(BaseModel):
29
  answer: str
30
  intent: str = "general"
31
+ tools_used: List[str] = []
32
+ search_results: List[dict] = []
33
+ sources: List[dict] = []
34
  model_used: str = "gpt"
35
+ response_time: float = 0
36
+
37
+ # ========== STAFF DIRECTORY DATA (from app81.py) ==========
38
+ STAFF_DIRECTORY = [
39
+ {
40
+ "full_name": "Dr. Abdulla Al Hefeiti",
41
+ "role": "Library Director / Assistant Provost, Libraries",
42
+ "details": "He can help with strategic and institutional matters, library leadership, and partnerships. Email: abdulla.alhefeiti@ku.ac.ae | Phone: +971 2 312 3331",
43
+ "tokens": ["abdulla", "hefeiti", "abdulla al hefeiti", "al hefeiti"],
44
+ },
45
+ {
46
+ "full_name": "Nikesh Narayanan",
47
+ "role": "Research & Access Services Librarian",
48
+ "details": "He can help with research support, Khazna institutional repository, ORCID, SciVal, Scopus, research impact, AI tools for research, bibliometrics, scholarly communication, and LibGuides. Email: nikesh.narayanan@ku.ac.ae | Phone: +971 2 312 3980",
49
+ "tokens": ["nikesh", "narayanan", "nikesh narayanan"],
50
+ },
51
+ {
52
+ "full_name": "Jason Fetty",
53
+ "role": "Medical Librarian",
54
+ "details": "He can help with medical and health sciences research, systematic reviews, PubMed, Embase, CINAHL, UpToDate, and clinical databases. Email: jason.fetty@ku.ac.ae | Phone: +971 2 312 4722",
55
+ "tokens": ["jason", "fetty", "jason fetty"],
56
+ },
57
+ {
58
+ "full_name": "Walter Brian Hall",
59
+ "role": "Digital & Technology Services Librarian / Systems Librarian",
60
+ "details": "He can help with library website, systems, technology, digital infrastructure, ORCID, STEAM, coding, Open Access publishing, APC funding, general library services, and public services. Email: walter.hall@ku.ac.ae | Phone: +971 2 312 3163",
61
+ "tokens": ["walter", "brian", "hall", "walter brian hall"],
62
+ },
63
+ ]
64
+
65
+ # ========== REGEX PATTERNS (from app81.py) ==========
66
+ SOCIAL_RE = re.compile(r"^(hi|hello|hey|good morning|good afternoon|good evening|how are you|who are you|what can you do|thanks|thank you|bye|goodbye)\b", re.IGNORECASE)
67
+ PURE_GREETING_RE = re.compile(r"^(hi|hello|hey|good morning|good afternoon|good evening)[!.\s]*$", re.IGNORECASE)
68
+ HOURS_RE = re.compile(r"\b(library hours?|opening hours?|when.*open|when.*close|open today|closed today)\b", re.IGNORECASE)
69
+ LIBRARY_CUE_RE = re.compile(r"\b(library|librarian|borrow|primo|database|article|book|ill|interlibrary|full text)\b", re.IGNORECASE)
70
+ MEDICAL_KEYWORDS = ['medicine','medical','health','clinical','nursing','pharmacy','pubmed','embase']
71
+
72
+ _CLASSIFIER_SYSTEM = """You are the routing brain of LibBee...""" # [Truncated for brevity - copy from app81.py lines ~875-915]
73
+
74
+ # ========== HELPER FUNCTIONS ==========
75
+ def _normalize_name_query(text: str):
76
+ return [t for t in re.sub(r"[^a-z0-9 ]+", " ", (text or "").lower()).split() if t]
77
+
78
+ def _match_staff_name(question: str) -> Optional[Dict]:
79
+ tokens = _normalize_name_query(question)
80
+ if not tokens:
81
+ return None
82
+ for staff in STAFF_DIRECTORY:
83
+ staff_tokens = set()
84
+ for tok in staff.get("tokens", []):
85
+ staff_tokens.update(_normalize_name_query(tok))
86
+ if all(tok in staff_tokens for tok in tokens):
87
+ return staff
88
+ return None
89
+
90
+ def _staff_name_answer(staff: dict) -> str:
91
+ return f"Did you mean <strong>{staff['full_name']}</strong>?<br><br><strong>{staff['full_name']}</strong> is the <strong>{staff['role']}</strong>. {staff['details']}"
92
+
93
+ def _looks_library_hours_question(question: str) -> bool:
94
+ return bool(HOURS_RE.search(question.lower()))
95
 
96
+ def _hours_redirect_answer() -> str:
97
+ return """
98
+ Here are the current <strong>Khalifa University Library</strong> hours:<br><br>
99
+ <strong>Habshan Library (SAN Campus):</strong><br>
100
+ Sunday – Wednesday: 8:00 AM – 7:00 PM<br>
101
+ Thursday: 8:00 AM – 4:00 PM<br>
102
+ Friday: Closed<br>
103
+ Saturday: 12:00 PM – 8:00 PM<br><br>
104
+ ⚠️ Check official page for latest times: <a href="https://library.ku.ac.ae/hours">library.ku.ac.ae/hours</a>
105
+ """
106
+
107
+ async def _llm_classify(question: str, history: list, model: str = "gpt"):
108
+ """Copy this function from app81.py lines ~918-960"""
109
+ # Simplified version for now:
110
+ q = question.lower()
111
+ if SOCIAL_RE.match(q):
112
+ return {"intent": "social", "casual_answer": "Hello! I'm LibBee, the KU Library AI Assistant."}
113
+ elif any(k in q for k in MEDICAL_KEYWORDS):
114
+ return {"intent": "search_medical"}
115
+ elif LIBRARY_CUE_RE.search(q):
116
+ return {"intent": "library_info"}
117
+ else:
118
+ return {"intent": "general"}
119
+
120
+ # ========== MAIN ENDPOINT ==========
121
  @router.post("", response_model=AgentResponse)
122
  async def agent_query(request: Request, req: AgentRequest):
 
 
 
 
123
  start = time.time()
124
 
125
+ question = req.question.strip()
 
126
 
127
+ # 1. Instant handlers (from app81.py Layer 0)
128
 
129
+ # Staff name match
130
+ staff_match = _match_staff_name(question)
131
+ if staff_match:
132
+ return AgentResponse(
133
+ answer=_staff_name_answer(staff_match),
134
+ intent="library_info",
135
+ tools_used=["staff_name_match"],
136
+ model_used=req.model,
137
+ response_time=time.time() - start
138
+ )
139
+
140
+ # Hours question
141
+ if _looks_library_hours_question(question):
142
+ return AgentResponse(
143
+ answer=_hours_redirect_answer(),
144
+ intent="library_info",
145
+ tools_used=["hours_hardcoded"],
146
+ model_used=req.model,
147
+ response_time=time.time() - start
148
+ )
149
+
150
+ # Greeting
151
+ if PURE_GREETING_RE.match(question):
152
+ return AgentResponse(
153
+ answer="Hi! I'm <strong>LibBee</strong>, the Khalifa University Library AI Assistant. I can help you find articles and books, search databases, and answer library questions. What would you like help with?",
154
+ intent="social_greeting",
155
+ tools_used=[],
156
+ model_used=req.model,
157
+ response_time=time.time() - start
158
+ )
159
+
160
+ # 2. LLM Classification (Layer 1)
161
+ try:
162
+ classification = await _llm_classify(question, req.history, req.model)
163
+ intent = classification.get("intent", "general")
164
+
165
+ # 3. Route by intent
166
+ if intent == "social":
167
+ return AgentResponse(
168
+ answer=classification.get("casual_answer", "Hello! How can I help?"),
169
+ intent="social",
170
+ model_used=req.model,
171
+ response_time=time.time() - start
172
+ )
173
+
174
+ elif intent == "library_info":
175
+ # For now, return generic library info response
176
+ # Later: Add RAG here
177
+ return AgentResponse(
178
+ answer="I can help with library information. This is where your RAG logic will go.",
179
+ intent="library_info",
180
+ tools_used=["get_library_info"],
181
+ model_used=req.model,
182
+ response_time=time.time() - start
183
+ )
184
+
185
+ elif intent in ["search_academic", "search_medical"]:
186
+ return AgentResponse(
187
+ answer=f"I'll help you search for {'medical' if intent == 'search_medical' else 'academic'} literature on: {question}",
188
+ intent=intent,
189
+ tools_used=["search_primo"],
190
+ model_used=req.model,
191
+ response_time=time.time() - start
192
+ )
193
+
194
+ else:
195
+ return AgentResponse(
196
+ answer="I'm the KU Library AI Assistant. I can help you find articles, books, and answer library questions.",
197
+ intent="general",
198
+ model_used=req.model,
199
+ response_time=time.time() - start
200
+ )
201
+
202
+ except Exception as e:
203
+ return AgentResponse(
204
+ answer=f"Error processing request: {str(e)}",
205
+ intent="error",
206
+ model_used=req.model,
207
+ response_time=time.time() - start
208
+ )
209
+
210
+ @router.get("/test")
211
+ async def test():
212
+ return {"status": "ok", "message": "Agent is working!"}