Update app.py
Browse files
app.py
CHANGED
|
@@ -701,7 +701,7 @@ def _make_boolean(text: str) -> str:
|
|
| 701 |
return f'"{text}"'
|
| 702 |
|
| 703 |
def _clean_database_keywords(boolean_query: str) -> str:
|
| 704 |
-
return re.sub(r'\s+', ' ', re.sub(r'
|
| 705 |
|
| 706 |
async def _build_search_plan(query: str, year: int = 2026) -> dict:
|
| 707 |
prompt = f"""You are a search expert for Khalifa University Library.
|
|
@@ -728,7 +728,7 @@ Query: "{query}"""
|
|
| 728 |
corrected = result.get("corrected", query).strip() or query
|
| 729 |
natural = result.get("natural", corrected).strip() or corrected
|
| 730 |
boolean = (result.get("boolean") or "").strip()
|
| 731 |
-
has_operators = bool(re.search(r'
|
| 732 |
has_parens = "(" in boolean if boolean else False
|
| 733 |
if not has_operators or not has_parens:
|
| 734 |
boolean = _make_boolean(corrected)
|
|
@@ -756,6 +756,103 @@ Query: "{query}"""
|
|
| 756 |
"open_access": False,
|
| 757 |
}
|
| 758 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 759 |
def _looks_library_question(question: str) -> bool:
|
| 760 |
return bool(LIBRARY_CUE_RE.search(question))
|
| 761 |
|
|
@@ -766,7 +863,7 @@ def _looks_research_question(question: str) -> bool:
|
|
| 766 |
q = question.lower()
|
| 767 |
if RESEARCH_CUE_RE.search(q):
|
| 768 |
return True
|
| 769 |
-
return bool(re.search(r'
|
| 770 |
|
| 771 |
def _looks_medical_search(question: str) -> bool:
|
| 772 |
q = question.lower()
|
|
|
|
| 701 |
return f'"{text}"'
|
| 702 |
|
| 703 |
def _clean_database_keywords(boolean_query: str) -> str:
|
| 704 |
+
return re.sub(r'\s+', ' ', re.sub(r'\b(AND|OR|NOT)\b|[()"]', ' ', boolean_query, flags=re.IGNORECASE)).strip()
|
| 705 |
|
| 706 |
async def _build_search_plan(query: str, year: int = 2026) -> dict:
|
| 707 |
prompt = f"""You are a search expert for Khalifa University Library.
|
|
|
|
| 728 |
corrected = result.get("corrected", query).strip() or query
|
| 729 |
natural = result.get("natural", corrected).strip() or corrected
|
| 730 |
boolean = (result.get("boolean") or "").strip()
|
| 731 |
+
has_operators = bool(re.search(r'\b(AND|OR)\b', boolean)) if boolean else False
|
| 732 |
has_parens = "(" in boolean if boolean else False
|
| 733 |
if not has_operators or not has_parens:
|
| 734 |
boolean = _make_boolean(corrected)
|
|
|
|
| 756 |
"open_access": False,
|
| 757 |
}
|
| 758 |
|
| 759 |
+
|
| 760 |
+
|
| 761 |
+
def get_behavior_instructions() -> str:
|
| 762 |
+
base = get_config("bot_personality", "You are a helpful, friendly, and knowledgeable library assistant at Khalifa University, Abu Dhabi, UAE. KU = Khalifa University, NOT Kuwait University. Be concise (2-4 sentences). Always include relevant URLs.")
|
| 763 |
+
extra = get_config("custom_instructions", "")
|
| 764 |
+
return (base + ("\n" + extra if extra else "")).strip()
|
| 765 |
+
|
| 766 |
+
|
| 767 |
+
def _looks_social_or_greeting(question: str) -> bool:
|
| 768 |
+
return bool(SOCIAL_RE.search((question or "").strip()))
|
| 769 |
+
|
| 770 |
+
|
| 771 |
+
async def _interpret_semantics(question: str, history=None) -> dict:
|
| 772 |
+
q = (question or "").strip()
|
| 773 |
+
ql = q.lower()
|
| 774 |
+
canonical_terms = []
|
| 775 |
+
grounding_keys = []
|
| 776 |
+
social = False
|
| 777 |
+
|
| 778 |
+
def add(key: str, *terms: str):
|
| 779 |
+
if key and key not in grounding_keys:
|
| 780 |
+
grounding_keys.append(key)
|
| 781 |
+
for t in terms:
|
| 782 |
+
if t and t not in canonical_terms:
|
| 783 |
+
canonical_terms.append(t)
|
| 784 |
+
|
| 785 |
+
if _looks_social_or_greeting(q):
|
| 786 |
+
social = True
|
| 787 |
+
return {"intent_hint": "general", "canonical_terms": canonical_terms, "grounding_keys": grounding_keys, "social": True}
|
| 788 |
+
|
| 789 |
+
if _looks_library_hours_question(q):
|
| 790 |
+
add("circulation", "library hours", "opening hours", "closing hours")
|
| 791 |
+
return {"intent_hint": "library_info", "canonical_terms": canonical_terms, "grounding_keys": grounding_keys, "social": False}
|
| 792 |
+
|
| 793 |
+
if _looks_nonlibrary_ku_question(q):
|
| 794 |
+
return {"intent_hint": "general", "canonical_terms": canonical_terms, "grounding_keys": grounding_keys, "social": False}
|
| 795 |
+
|
| 796 |
+
# Staff / role semantics
|
| 797 |
+
if re.search(r"\b(systems? librarian|system librarian|website|digital services|library systems?|technology help)\b", ql):
|
| 798 |
+
add("systems_help", "Walter Brian Hall", "Systems Librarian", "website", "technology")
|
| 799 |
+
if re.search(r"\b(database access|e-?resources?|remote access|off campus|off-campus|login issue|access problem|vendor issue)\b", ql):
|
| 800 |
+
add("database_access", "Rani Anand", "E-Resources", "database access")
|
| 801 |
+
if re.search(r"\b(orcid|open access|apc|article processing charge|research impact|bibliometric|bibliometrics|scival|scopus metrics?)\b", ql):
|
| 802 |
+
add("orcid_oa", "Nikesh Narayanan", "ORCID", "Open Access", "research impact")
|
| 803 |
+
if re.search(r"\b(medical librarian|pubmed help|embase|cinahl|cochrane|uptodate|systematic review|clinical databases?)\b", ql):
|
| 804 |
+
add("medical_help", "Jason Fetty", "Medical Librarian", "PubMed", "systematic review")
|
| 805 |
+
if re.search(r"\b(acquisitions?|collection development|suggest a book|request a title|new title request)\b", ql):
|
| 806 |
+
add("acquisitions", "Alia Al-Harrasi", "Acquisitions", "collection development")
|
| 807 |
+
if re.search(r"\b(circulation|borrow|renew|due date|fine|hold request|my library account|public services?)\b", ql):
|
| 808 |
+
add("circulation", "borrowing", "renewal", "My Library Account")
|
| 809 |
+
if re.search(r"\b(ask a librarian|research help|research consultation|reference help|consultation)\b", ql):
|
| 810 |
+
add("research_help", "Ask a Librarian", "research consultation")
|
| 811 |
+
|
| 812 |
+
# Service / resource semantics
|
| 813 |
+
if re.search(r"\b(inter\s*library loan|interlibrary loan|\bill\b|document delivery|full text unavailable|no full text|can't get (an )?article|cannot get (an )?article|article not available)\b", ql):
|
| 814 |
+
add("ill", "Interlibrary Loan", "ILL", "document delivery")
|
| 815 |
+
add("fulltext", "full text", "LibKey Nomad", "article access")
|
| 816 |
+
if re.search(r"\b(primo|catalog|discovery|holdings|publication finder|does the library have)\b", ql):
|
| 817 |
+
add("primo", "PRIMO", "catalog", "Publication Finder")
|
| 818 |
+
|
| 819 |
+
# Database recommendation semantics
|
| 820 |
+
if re.search(r"\b(standards?|astm|ieee standards?|asme|asce)\b", ql):
|
| 821 |
+
add("standards", "ASTM Compass", "IEEE standards", "ASME", "ASCE")
|
| 822 |
+
if re.search(r"\b(dissertations?|theses|khazna)\b", ql):
|
| 823 |
+
add("theses", "ProQuest Dissertations & Theses", "Khazna")
|
| 824 |
+
if re.search(r"\b(impact factor|journal metrics?|jcr|citescore|sjr)\b", ql):
|
| 825 |
+
add("metrics", "Journal Citation Reports", "CiteScore")
|
| 826 |
+
if re.search(r"\b(chemistry|chemical)\b", ql):
|
| 827 |
+
add("chemistry", "ACS", "SciFindern", "Reaxys")
|
| 828 |
+
if re.search(r"\b(physics|optics?|photonics?)\b", ql):
|
| 829 |
+
add("physics", "APS", "AIP", "IOPScience")
|
| 830 |
+
if re.search(r"\b(engineering|civil engineering|mechanical engineering|electrical engineering|computer science|ai)\b", ql):
|
| 831 |
+
add("engineering", "IEEE Xplore", "ACM", "Knovel", "ASCE", "ASME")
|
| 832 |
+
|
| 833 |
+
# Name/title style library questions
|
| 834 |
+
if re.search(r"\b(who is|who handles|who can help|whom should i contact|contact for)\b", ql) and (_looks_library_question(q) or grounding_keys):
|
| 835 |
+
if not grounding_keys:
|
| 836 |
+
add("research_help", "staff directory", "library staff")
|
| 837 |
+
|
| 838 |
+
# Decide intent
|
| 839 |
+
if _looks_medical_search(q):
|
| 840 |
+
intent = "search_medical"
|
| 841 |
+
elif _looks_research_question(q):
|
| 842 |
+
intent = "search_academic"
|
| 843 |
+
elif grounding_keys or _looks_library_question(q):
|
| 844 |
+
intent = "library_info"
|
| 845 |
+
elif _looks_current_question(q):
|
| 846 |
+
intent = "general_recent"
|
| 847 |
+
else:
|
| 848 |
+
intent = "general"
|
| 849 |
+
|
| 850 |
+
return {
|
| 851 |
+
"intent_hint": intent,
|
| 852 |
+
"canonical_terms": canonical_terms,
|
| 853 |
+
"grounding_keys": grounding_keys,
|
| 854 |
+
"social": social,
|
| 855 |
+
}
|
| 856 |
def _looks_library_question(question: str) -> bool:
|
| 857 |
return bool(LIBRARY_CUE_RE.search(question))
|
| 858 |
|
|
|
|
| 863 |
q = question.lower()
|
| 864 |
if RESEARCH_CUE_RE.search(q):
|
| 865 |
return True
|
| 866 |
+
return bool(re.search(r'\bimpact of\b|\beffects? of\b|\bcauses? of\b|\brelationship between\b|\bliterature on\b', q))
|
| 867 |
|
| 868 |
def _looks_medical_search(question: str) -> bool:
|
| 869 |
q = question.lower()
|