Upload agent.py
Browse files- src/api/agent.py +8 -78
src/api/agent.py
CHANGED
|
@@ -777,11 +777,15 @@ async def _events_answer(days: int = 30) -> str:
|
|
| 777 |
"""
|
| 778 |
Return upcoming library events from LibCal.
|
| 779 |
Falls back to library events calendar link if unavailable.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
"""
|
| 781 |
EVENTS_URL = "https://kustar.libcal.com/calendar/events"
|
| 782 |
events = await _libcal_fetch_events(days=days, limit=15)
|
| 783 |
|
| 784 |
-
if
|
| 785 |
return (
|
| 786 |
"<strong>π
Khalifa University Library Events</strong><br><br>"
|
| 787 |
"Live event data is currently unavailable. Browse all upcoming events here:<br>"
|
|
@@ -789,7 +793,7 @@ async def _events_answer(days: int = 30) -> str:
|
|
| 789 |
f'style="font-weight:700;color:#003366">{EVENTS_URL}</a>'
|
| 790 |
)
|
| 791 |
|
| 792 |
-
if
|
| 793 |
return (
|
| 794 |
"<strong>π
Khalifa University Library Events</strong><br><br>"
|
| 795 |
f"No upcoming events found in the next {days} days.<br><br>"
|
|
@@ -3526,10 +3530,8 @@ def _log_agent_query(question: str, response: AgentResponse, start_time: float)
|
|
| 3526 |
except Exception:
|
| 3527 |
pass
|
| 3528 |
try:
|
| 3529 |
-
|
| 3530 |
-
|
| 3531 |
-
loop.create_task(_post_to_worker())
|
| 3532 |
-
except Exception:
|
| 3533 |
pass
|
| 3534 |
except Exception:
|
| 3535 |
pass
|
|
@@ -3701,77 +3703,6 @@ async def _agent_query_inner(
|
|
| 3701 |
|
| 3702 |
is_follow_up, follow_up_action, base_context = _detect_follow_up(question, client_state)
|
| 3703 |
|
| 3704 |
-
# ββ Catalogue availability: "books available in the KU library on X" βββββββββ
|
| 3705 |
-
# Catches questions asking what the library *holds*, not topic searches.
|
| 3706 |
-
# Must fire BEFORE the LLM classifier which would route these to search_academic.
|
| 3707 |
-
_CATALOGUE_RE = re.compile(
|
| 3708 |
-
r"\b("
|
| 3709 |
-
r"available in (the )?(ku|khalifa) library"
|
| 3710 |
-
r"|in (the )?(ku|khalifa) library"
|
| 3711 |
-
r"|does (the )?(ku|khalifa) library have"
|
| 3712 |
-
r"|does (the )?library (have|carry|stock|hold)"
|
| 3713 |
-
r"|(ku|khalifa) library (has|have|carry|holds?|stocks?)"
|
| 3714 |
-
r"|ku library (books?|copies|titles?)"
|
| 3715 |
-
r")\b",
|
| 3716 |
-
re.IGNORECASE,
|
| 3717 |
-
)
|
| 3718 |
-
_BOOK_RE = re.compile(r"\b(book|books|ebook|ebooks|textbook|textbooks|copy|copies|title|titles)\b", re.IGNORECASE)
|
| 3719 |
-
|
| 3720 |
-
if _CATALOGUE_RE.search(question) and _BOOK_RE.search(question):
|
| 3721 |
-
# Route as library_info β RAG will handle or fall back to Ask a Librarian
|
| 3722 |
-
# Also build a PRIMO books URL so the user can search directly
|
| 3723 |
-
from urllib.parse import quote as _quote
|
| 3724 |
-
_topic_raw = re.sub(
|
| 3725 |
-
r"\b(can you (help me )?find|find|search|look for|show me|help me find"
|
| 3726 |
-
r"|books? (available|in|on)|available in.{0,30}library|in the ku library"
|
| 3727 |
-
r"|on|about|regarding|related to|ku library|khalifa university library"
|
| 3728 |
-
r"|machine learning|please|thanks)\b",
|
| 3729 |
-
" ", question, flags=re.IGNORECASE,
|
| 3730 |
-
)
|
| 3731 |
-
_topic = _normalize_whitespace(_topic_raw).strip(" ,.")
|
| 3732 |
-
_primo_books_url = (
|
| 3733 |
-
"https://khalifa.primo.exlibrisgroup.com/discovery/search"
|
| 3734 |
-
f"?query=any,contains,{_quote(_topic or question)},AND"
|
| 3735 |
-
"&tab=Everything&search_scope=MyInst_and_CI"
|
| 3736 |
-
"&vid=971KUOSTAR_INST:KU&lang=en&mode=advanced"
|
| 3737 |
-
"&qInclude=facet_rtype,exact,books"
|
| 3738 |
-
)
|
| 3739 |
-
follow_up_question, follow_up_suggestions = _library_follow_up(question)
|
| 3740 |
-
|
| 3741 |
-
# Try RAG first for any policy/service context
|
| 3742 |
-
rag_results = []
|
| 3743 |
-
try:
|
| 3744 |
-
from app import get_rag_service
|
| 3745 |
-
rag_results = await get_rag_service().hybrid_search(question, top_k=3)
|
| 3746 |
-
except Exception:
|
| 3747 |
-
pass
|
| 3748 |
-
|
| 3749 |
-
if rag_results:
|
| 3750 |
-
rag_answer = await _rag_answer(question, rag_results, [], model)
|
| 3751 |
-
else:
|
| 3752 |
-
rag_answer = ""
|
| 3753 |
-
|
| 3754 |
-
catalogue_answer = (
|
| 3755 |
-
f"To find books available in the KU Library on <strong>{_escape(_topic or 'this topic')}</strong>, "
|
| 3756 |
-
f"search the KU Library catalogue directly:<br><br>"
|
| 3757 |
-
f'<a href="{_primo_books_url}" target="_blank" style="display:inline-block;padding:9px 16px;'
|
| 3758 |
-
f'background:#003366;color:#fff;border-radius:8px;font-weight:700;text-decoration:none;font-size:.88rem">'
|
| 3759 |
-
f"π Search KU Library catalogue for books β</a><br><br>"
|
| 3760 |
-
f"The catalogue shows real-time availability β whether a copy is on the shelf, checked out, or available as an e-book."
|
| 3761 |
-
)
|
| 3762 |
-
if rag_answer:
|
| 3763 |
-
catalogue_answer += f"<br><br>{rag_answer}"
|
| 3764 |
-
|
| 3765 |
-
return AgentResponse(
|
| 3766 |
-
answer=_thought_block("library_info", "catalogue_books", question) + catalogue_answer + _source_badge("primo", _primo_books_url),
|
| 3767 |
-
intent="library_info",
|
| 3768 |
-
tools_used=["catalogue_books_redirect"],
|
| 3769 |
-
model_used=model,
|
| 3770 |
-
response_time=time.time() - start_time,
|
| 3771 |
-
follow_up_question=follow_up_question,
|
| 3772 |
-
follow_up_suggestions=follow_up_suggestions,
|
| 3773 |
-
)
|
| 3774 |
-
|
| 3775 |
if is_follow_up and base_context:
|
| 3776 |
_safe_metrics_increment("follow_up_hits")
|
| 3777 |
context = _apply_follow_up_action(base_context, follow_up_action)
|
|
@@ -4150,7 +4081,6 @@ async def _agent_query_inner(
|
|
| 4150 |
_prepare_queries(question, _ctx_books, model, False),
|
| 4151 |
)
|
| 4152 |
# No API β just build PRIMO links for articles and books
|
| 4153 |
-
_ctx_articles = await _prepare_queries(question, _ctx_articles, model, False)
|
| 4154 |
_src_url_a = _primo_clean_url(_ctx_articles)
|
| 4155 |
_src_url_b = _primo_clean_url(_ctx_books)
|
| 4156 |
|
|
|
|
| 777 |
"""
|
| 778 |
Return upcoming library events from LibCal.
|
| 779 |
Falls back to library events calendar link if unavailable.
|
| 780 |
+
_libcal_fetch_events returns:
|
| 781 |
+
None β API/network failure
|
| 782 |
+
[] β API succeeded but no events in the window
|
| 783 |
+
[...] β events found
|
| 784 |
"""
|
| 785 |
EVENTS_URL = "https://kustar.libcal.com/calendar/events"
|
| 786 |
events = await _libcal_fetch_events(days=days, limit=15)
|
| 787 |
|
| 788 |
+
if events is None:
|
| 789 |
return (
|
| 790 |
"<strong>π
Khalifa University Library Events</strong><br><br>"
|
| 791 |
"Live event data is currently unavailable. Browse all upcoming events here:<br>"
|
|
|
|
| 793 |
f'style="font-weight:700;color:#003366">{EVENTS_URL}</a>'
|
| 794 |
)
|
| 795 |
|
| 796 |
+
if len(events) == 0:
|
| 797 |
return (
|
| 798 |
"<strong>π
Khalifa University Library Events</strong><br><br>"
|
| 799 |
f"No upcoming events found in the next {days} days.<br><br>"
|
|
|
|
| 3530 |
except Exception:
|
| 3531 |
pass
|
| 3532 |
try:
|
| 3533 |
+
_al.get_running_loop().create_task(_post_to_worker())
|
| 3534 |
+
except RuntimeError:
|
|
|
|
|
|
|
| 3535 |
pass
|
| 3536 |
except Exception:
|
| 3537 |
pass
|
|
|
|
| 3703 |
|
| 3704 |
is_follow_up, follow_up_action, base_context = _detect_follow_up(question, client_state)
|
| 3705 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3706 |
if is_follow_up and base_context:
|
| 3707 |
_safe_metrics_increment("follow_up_hits")
|
| 3708 |
context = _apply_follow_up_action(base_context, follow_up_action)
|
|
|
|
| 4081 |
_prepare_queries(question, _ctx_books, model, False),
|
| 4082 |
)
|
| 4083 |
# No API β just build PRIMO links for articles and books
|
|
|
|
| 4084 |
_src_url_a = _primo_clean_url(_ctx_articles)
|
| 4085 |
_src_url_b = _primo_clean_url(_ctx_books)
|
| 4086 |
|