Upload agent.py
Browse files- 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 |
-
"
|
| 234 |
-
"
|
| 235 |
-
"
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|