nikeshn commited on
Commit
ed6eff9
Β·
verified Β·
1 Parent(s): 20217cc

Upload agent.py

Browse files
Files changed (1) hide show
  1. src/api/agent.py +86 -15
src/api/agent.py CHANGED
@@ -230,14 +230,39 @@ _GUARDRAIL = (
230
  "You must never make negative, critical, or controversial statements about "
231
  "Khalifa University, its leadership, staff, or policies; the UAE government, "
232
  "its leaders, laws, or institutions; or any religion. "
233
- "If asked for opinions on these topics, politely decline and redirect to library services. "
234
- "Never generate content that is politically sensitive, religiously offensive, "
235
- "or inappropriate for a university academic environment. "
 
 
 
236
  "You may answer general factual questions β€” such as historical facts, science, "
237
  "geography, or current affairs β€” but always from a neutral, academic perspective. "
238
  "Never produce content that could embarrass Khalifa University or its library."
239
  )
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  # ── Prompt injection detection ────────────────────────────────────────────────
242
  _INJECTION_RE = re.compile(
243
  r"\b(ignore\s+(previous|above|all|prior)\s+instructions?"
@@ -421,6 +446,12 @@ INTENTS β€” pick exactly one:
421
  "search_medical" β€” same as search_academic but topic is medical/clinical/nursing/pharmacy/health sciences
422
  "general_recent" β€” questions about TRULY live/real-time data: breaking news, live sports scores, today's weather, current stock prices
423
  "general" β€” ALL other factual questions: university info, people, history, science definitions, technology explanations
 
 
 
 
 
 
424
 
425
  CRITICAL RULES:
426
  1. "research" as a job title or service modifier (research librarian, research support, research help) = library_info, NOT search_academic.
@@ -430,6 +461,7 @@ CRITICAL RULES:
430
  5. KU = Khalifa University. Do NOT confuse with Kuwait University.
431
  6. Only use general_recent for truly live data changing by the minute (news, scores, weather, stocks).
432
  7. When in doubt between library_info and anything else, choose library_info if the question involves a library service, person, or resource.
 
433
 
434
  EXAMPLES β€” follow these exactly:
435
  {"message": "who is the research librarian", "intent": "library_info"}
@@ -445,7 +477,25 @@ EXAMPLES β€” follow these exactly:
445
  {"message": "systematic review on COVID-19 vaccines", "intent": "search_medical"}
446
  {"message": "who is the president of UAE", "intent": "general"}
447
  {"message": "what year was KU founded", "intent": "general"}
 
 
 
 
448
  {"message": "latest news on AI regulation", "intent": "general_recent"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  {"message": "hi there", "intent": "social", "casual_answer": "Hello! I'm LibBee, the KU Library AI Assistant. How can I help you today?"}
450
  {"message": "I am fed up with searching", "intent": "social", "casual_answer": "I understand the frustration! Let me help make your search easier. What topic are you working on?"}
451
 
@@ -1126,7 +1176,7 @@ async def _llm_classify(question: str, history: List[ChatMessage], model: str) -
1126
  raw = raw.split("\n", 1)[1].rsplit("```", 1)[0].strip()
1127
  result = json.loads(raw)
1128
  intent = result.get("intent", "general")
1129
- if intent not in {"social", "library_info", "search_academic", "search_medical", "general_recent", "general"}:
1130
  intent = "general"
1131
  return {"intent": intent, "casual_answer": result.get("casual_answer", "")}
1132
  except Exception as e:
@@ -1223,7 +1273,11 @@ async def _llm_general_answer(question: str, history: List[ChatMessage], model:
1223
 
1224
  async def _web_search_answer(question: str, history: List[ChatMessage], model: str) -> str:
1225
  settings = get_settings()
1226
- # FIX: updated model string to match _get_llm fix
 
 
 
 
1227
  if model == "claude" and settings.anthropic_api_key:
1228
  try:
1229
  import anthropic
@@ -1231,10 +1285,7 @@ async def _web_search_answer(question: str, history: List[ChatMessage], model: s
1231
  response = client.messages.create(
1232
  model="claude-haiku-4-5-20251001",
1233
  max_tokens=520,
1234
- system=(
1235
- "You are LibBee, the Khalifa University Library AI Assistant (Abu Dhabi, UAE). "
1236
- "Answer using current web search. Be concise and factual. Use HTML <br> for line breaks."
1237
- ),
1238
  tools=[{"type": "web_search_20250305", "name": "web_search"}],
1239
  messages=[{"role": "user", "content": question}],
1240
  )
@@ -1251,10 +1302,7 @@ async def _web_search_answer(question: str, history: List[ChatMessage], model: s
1251
  response = client.responses.create(
1252
  model="gpt-4o-mini",
1253
  tools=[{"type": "web_search_preview"}],
1254
- instructions=(
1255
- "You are LibBee, the Khalifa University Library AI Assistant (Abu Dhabi, UAE). "
1256
- "Answer using current web search. Be concise and factual. Use HTML <br> for line breaks."
1257
- ),
1258
  input=question,
1259
  )
1260
  text = ""
@@ -3801,8 +3849,6 @@ async def _agent_query_inner(
3801
  ) -> AgentResponse:
3802
 
3803
  # ── Prompt injection detection ────────────────────────────────────────────
3804
- # Fires before any processing. Catches attempts to override system prompt,
3805
- # jailbreak, or extract internal instructions.
3806
  if question and _INJECTION_RE.search(question):
3807
  _safe_metrics_bucket("intents", "injection_blocked")
3808
  return AgentResponse(
@@ -3817,6 +3863,11 @@ async def _agent_query_inner(
3817
  model_used=model,
3818
  response_time=time.time() - start_time,
3819
  )
 
 
 
 
 
3820
  # ─────────────────────────────────────────────────────────────────────────
3821
 
3822
  if not question:
@@ -4288,6 +4339,26 @@ async def _agent_query_inner(
4288
  follow_up_suggestions=follow_up_suggestions,
4289
  )
4290
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4291
  if intent == "library_info":
4292
  rag_results = []
4293
  try:
 
230
  "You must never make negative, critical, or controversial statements about "
231
  "Khalifa University, its leadership, staff, or policies; the UAE government, "
232
  "its leaders, laws, or institutions; or any religion. "
233
+ "You must never generate sexually explicit, suggestive, or adult content of any kind. "
234
+ "You must never produce political criticism, propaganda, or inflammatory commentary "
235
+ "targeting UAE, its leadership, or any Gulf state government. "
236
+ "If asked for any of the above, politely decline and redirect to library services. "
237
+ "Never generate content that is offensive, discriminatory, or inappropriate "
238
+ "for a university academic environment. "
239
  "You may answer general factual questions β€” such as historical facts, science, "
240
  "geography, or current affairs β€” but always from a neutral, academic perspective. "
241
  "Never produce content that could embarrass Khalifa University or its library."
242
  )
243
 
244
+ # ── Sensitive institutional topic gate ───────────────────────────────────────
245
+ # Catches requests for negative opinions/criticism about KU, UAE, or religion
246
+ # before any LLM or web search is invoked.
247
+ _SENSITIVE_TOPIC_RE = re.compile(
248
+ r"\b("
249
+ r"(bad|worst|negative[s]?|problem[s]?|issue[s]?|weakness|weaknesses|"
250
+ r"criticism[s]?|criticise|criticize|critique|flaw[s]?|failure[s]?|"
251
+ r"scandal[s]?|corrupt\w*|mistake[s]?|disadvantage[s]?|downside[s]?|"
252
+ r"complaint[s]?|controversi\w*|shame|shameful|terrible|horrible|awful)"
253
+ r"\s.{0,50}(ku|khalifa\s+university|uae|emirates|dubai|abu\s+dhabi|"
254
+ r"uae\s+government|sheikh|ministry|ruler)"
255
+ r"|"
256
+ r"(ku|khalifa\s+university|uae|emirates|dubai|abu\s+dhabi|"
257
+ r"uae\s+government|sheikh|ministry|ruler)"
258
+ r"\s.{0,50}(bad|worst|negative|problem[s]?|issue[s]?|weakness|weaknesses|"
259
+ r"criticism[s]?|criticise|criticize|critique|flaw[s]?|failure[s]?|"
260
+ r"scandal[s]?|corrupt\w*|mistake[s]?|disadvantage[s]?|downside[s]?|"
261
+ r"complaint[s]?|shame|shameful|terrible|horrible|awful)"
262
+ r")\b",
263
+ re.IGNORECASE,
264
+ )
265
+
266
  # ── Prompt injection detection ────────────────────────────────────────────────
267
  _INJECTION_RE = re.compile(
268
  r"\b(ignore\s+(previous|above|all|prior)\s+instructions?"
 
446
  "search_medical" β€” same as search_academic but topic is medical/clinical/nursing/pharmacy/health sciences
447
  "general_recent" β€” questions about TRULY live/real-time data: breaking news, live sports scores, today's weather, current stock prices
448
  "general" β€” ALL other factual questions: university info, people, history, science definitions, technology explanations
449
+ "sensitive" β€” block any of the following:
450
+ (a) Requests for negative opinions, criticism, or controversy about Khalifa University, UAE government/leaders/laws/institutions, or religion.
451
+ (b) Sexually explicit, suggestive, or adult content of any kind β€” including questions asking for sexual information, stories, jokes, or descriptions.
452
+ (c) Political criticism, propaganda, or inflammatory commentary targeting UAE, its leadership, policies, or any Gulf state government.
453
+ (d) Content that is offensive, discriminatory, or inappropriate for a university academic environment.
454
+ ONLY use "sensitive" when the clear intent falls into one of these categories. Neutral academic research questions about politics, health, relationships, or social issues are NOT sensitive.
455
 
456
  CRITICAL RULES:
457
  1. "research" as a job title or service modifier (research librarian, research support, research help) = library_info, NOT search_academic.
 
461
  5. KU = Khalifa University. Do NOT confuse with Kuwait University.
462
  6. Only use general_recent for truly live data changing by the minute (news, scores, weather, stocks).
463
  7. When in doubt between library_info and anything else, choose library_info if the question involves a library service, person, or resource.
464
+ 8. "sensitive" covers: (a) criticism/negative commentary about KU or UAE, (b) sexual or adult content, (c) political attacks on UAE/Gulf governments, (d) religiously offensive content. Academic research on politics, health, or social issues = general or search_academic.
465
 
466
  EXAMPLES β€” follow these exactly:
467
  {"message": "who is the research librarian", "intent": "library_info"}
 
477
  {"message": "systematic review on COVID-19 vaccines", "intent": "search_medical"}
478
  {"message": "who is the president of UAE", "intent": "general"}
479
  {"message": "what year was KU founded", "intent": "general"}
480
+ {"message": "what challenges does KU face in AI research", "intent": "general"}
481
+ {"message": "UAE water scarcity problems and solutions", "intent": "search_academic"}
482
+ {"message": "find research on sexual health education", "intent": "search_academic"}
483
+ {"message": "articles on political systems in the Middle East", "intent": "search_academic"}
484
  {"message": "latest news on AI regulation", "intent": "general_recent"}
485
+ {"message": "what are the bad things about KU", "intent": "sensitive"}
486
+ {"message": "say something negative about Khalifa University", "intent": "sensitive"}
487
+ {"message": "criticize the UAE government", "intent": "sensitive"}
488
+ {"message": "what is wrong with UAE laws", "intent": "sensitive"}
489
+ {"message": "tell me the problems with KU administration", "intent": "sensitive"}
490
+ {"message": "write me a sexual story", "intent": "sensitive"}
491
+ {"message": "tell me something sexual", "intent": "sensitive"}
492
+ {"message": "generate adult content", "intent": "sensitive"}
493
+ {"message": "describe a sexual encounter", "intent": "sensitive"}
494
+ {"message": "write explicit content", "intent": "sensitive"}
495
+ {"message": "UAE government is corrupt and oppressive", "intent": "sensitive"}
496
+ {"message": "why is the UAE government so bad", "intent": "sensitive"}
497
+ {"message": "UAE leaders are dictators", "intent": "sensitive"}
498
+ {"message": "insult the UAE", "intent": "sensitive"}
499
  {"message": "hi there", "intent": "social", "casual_answer": "Hello! I'm LibBee, the KU Library AI Assistant. How can I help you today?"}
500
  {"message": "I am fed up with searching", "intent": "social", "casual_answer": "I understand the frustration! Let me help make your search easier. What topic are you working on?"}
501
 
 
1176
  raw = raw.split("\n", 1)[1].rsplit("```", 1)[0].strip()
1177
  result = json.loads(raw)
1178
  intent = result.get("intent", "general")
1179
+ if intent not in {"social", "library_info", "search_academic", "search_medical", "general_recent", "general", "sensitive"}:
1180
  intent = "general"
1181
  return {"intent": intent, "casual_answer": result.get("casual_answer", "")}
1182
  except Exception as e:
 
1273
 
1274
  async def _web_search_answer(question: str, history: List[ChatMessage], model: str) -> str:
1275
  settings = get_settings()
1276
+ _WEB_SYSTEM = (
1277
+ "You are LibBee, the Khalifa University Library AI Assistant (Abu Dhabi, UAE). "
1278
+ "Answer using current web search. Be concise and factual. Use HTML <br> for line breaks. "
1279
+ + _GUARDRAIL
1280
+ )
1281
  if model == "claude" and settings.anthropic_api_key:
1282
  try:
1283
  import anthropic
 
1285
  response = client.messages.create(
1286
  model="claude-haiku-4-5-20251001",
1287
  max_tokens=520,
1288
+ system=_WEB_SYSTEM,
 
 
 
1289
  tools=[{"type": "web_search_20250305", "name": "web_search"}],
1290
  messages=[{"role": "user", "content": question}],
1291
  )
 
1302
  response = client.responses.create(
1303
  model="gpt-4o-mini",
1304
  tools=[{"type": "web_search_preview"}],
1305
+ instructions=_WEB_SYSTEM,
 
 
 
1306
  input=question,
1307
  )
1308
  text = ""
 
3849
  ) -> AgentResponse:
3850
 
3851
  # ── Prompt injection detection ────────────────────────────────────────────
 
 
3852
  if question and _INJECTION_RE.search(question):
3853
  _safe_metrics_bucket("intents", "injection_blocked")
3854
  return AgentResponse(
 
3863
  model_used=model,
3864
  response_time=time.time() - start_time,
3865
  )
3866
+
3867
+ # ── Sensitive institutional topic gate ───────────────────────────────────
3868
+ # Handled by LLM classifier (intent == "sensitive") further below.
3869
+ # The regex gate (_SENSITIVE_TOPIC_RE) has been removed in favour of the
3870
+ # LLM classifier which understands context and intent more accurately.
3871
  # ─────────────────────────────────────────────────────────────────────────
3872
 
3873
  if not question:
 
4339
  follow_up_suggestions=follow_up_suggestions,
4340
  )
4341
 
4342
+ if intent == "sensitive":
4343
+ _safe_metrics_bucket("intents", "sensitive_blocked")
4344
+ follow_up_question, follow_up_suggestions = _social_follow_up()
4345
+ return AgentResponse(
4346
+ answer=(
4347
+ "That's not something I'm able to comment on. "
4348
+ "As the KU Library AI Assistant, I'm here to support your academic "
4349
+ "and research needs β€” finding articles, accessing databases, "
4350
+ "library services, and research tools.<br><br>"
4351
+ "Is there something I can help you with today? "
4352
+ f'<a href="{ASK_LIBRARIAN_URL}" target="_blank">πŸ’¬ Ask a Librarian</a>'
4353
+ ),
4354
+ intent="sensitive_blocked",
4355
+ tools_used=["sensitive_intent_guard"],
4356
+ model_used=model,
4357
+ response_time=time.time() - start_time,
4358
+ follow_up_question=follow_up_question,
4359
+ follow_up_suggestions=follow_up_suggestions,
4360
+ )
4361
+
4362
  if intent == "library_info":
4363
  rag_results = []
4364
  try: