Update app.py
Browse files
app.py
CHANGED
|
@@ -1838,6 +1838,15 @@ async def agent_query(req: AgentRequest):
|
|
| 1838 |
rag = await tool_library_info(enriched_question, history[-5:] if history else None, model=req.model)
|
| 1839 |
tools_used = ["get_library_info"]
|
| 1840 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1841 |
if rag.get("has_answer") and rag.get("answer"):
|
| 1842 |
# Good RAG answer β synthesise and return
|
| 1843 |
context_parts = [f"Library Knowledge Base:\n{rag['answer']}"]
|
|
@@ -1845,7 +1854,8 @@ async def agent_query(req: AgentRequest):
|
|
| 1845 |
synthesis_prompt = (
|
| 1846 |
f"{behavior}\n\n"
|
| 1847 |
"You are the Khalifa University Library AI Assistant (Abu Dhabi, UAE). KU = Khalifa University.\n"
|
| 1848 |
-
"Answer in 2-4 sentences. Include URLs from the context when relevant.\n
|
|
|
|
| 1849 |
f"Context:\n{chr(10).join(context_parts)}\n\n"
|
| 1850 |
f"Question: {question}\nAnswer:"
|
| 1851 |
)
|
|
@@ -1860,26 +1870,140 @@ async def agent_query(req: AgentRequest):
|
|
| 1860 |
answer = rag["answer"]
|
| 1861 |
elapsed = time.time() - start
|
| 1862 |
return _make_agent_response(
|
| 1863 |
-
answer=answer, intent="library_info", tools_used=tools_used,
|
| 1864 |
search_results=[], sources=rag.get("sources", []),
|
| 1865 |
model=req.model, elapsed=elapsed, question=question,
|
| 1866 |
)
|
| 1867 |
else:
|
| 1868 |
-
# RAG
|
| 1869 |
-
|
| 1870 |
-
|
| 1871 |
-
#
|
| 1872 |
-
|
| 1873 |
-
|
| 1874 |
-
|
| 1875 |
-
|
| 1876 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1877 |
)
|
|
|
|
|
|
|
|
|
|
| 1878 |
elapsed = time.time() - start
|
| 1879 |
return _make_agent_response(
|
| 1880 |
-
answer=
|
| 1881 |
-
tools_used=
|
| 1882 |
-
search_results=[], sources=
|
| 1883 |
model=req.model, elapsed=elapsed, question=question,
|
| 1884 |
)
|
| 1885 |
|
|
|
|
| 1838 |
rag = await tool_library_info(enriched_question, history[-5:] if history else None, model=req.model)
|
| 1839 |
tools_used = ["get_library_info"]
|
| 1840 |
|
| 1841 |
+
# Ask a Librarian footer β appended to ALL library_info answers
|
| 1842 |
+
ASK_LIB = (
|
| 1843 |
+
'<br><br><span style="font-size:.82rem;color:#6b7280">'
|
| 1844 |
+
'π¬ Need more help? '
|
| 1845 |
+
'<a href="https://library.ku.ac.ae/AskUs" target="_blank" style="color:#003366;font-weight:600">Ask a Librarian</a>'
|
| 1846 |
+
' β or browse <a href="https://library.ku.ac.ae/lib" target="_blank" style="color:#003366">library.ku.ac.ae</a>.'
|
| 1847 |
+
'</span>'
|
| 1848 |
+
)
|
| 1849 |
+
|
| 1850 |
if rag.get("has_answer") and rag.get("answer"):
|
| 1851 |
# Good RAG answer β synthesise and return
|
| 1852 |
context_parts = [f"Library Knowledge Base:\n{rag['answer']}"]
|
|
|
|
| 1854 |
synthesis_prompt = (
|
| 1855 |
f"{behavior}\n\n"
|
| 1856 |
"You are the Khalifa University Library AI Assistant (Abu Dhabi, UAE). KU = Khalifa University.\n"
|
| 1857 |
+
"Answer in 2-4 sentences. Include URLs from the context when relevant.\n"
|
| 1858 |
+
"Answer ONLY from the provided context. Do not add information not present in the context.\n\n"
|
| 1859 |
f"Context:\n{chr(10).join(context_parts)}\n\n"
|
| 1860 |
f"Question: {question}\nAnswer:"
|
| 1861 |
)
|
|
|
|
| 1870 |
answer = rag["answer"]
|
| 1871 |
elapsed = time.time() - start
|
| 1872 |
return _make_agent_response(
|
| 1873 |
+
answer=answer + ASK_LIB, intent="library_info", tools_used=tools_used,
|
| 1874 |
search_results=[], sources=rag.get("sources", []),
|
| 1875 |
model=req.model, elapsed=elapsed, question=question,
|
| 1876 |
)
|
| 1877 |
else:
|
| 1878 |
+
# ββ RAG miss β three-tier fallback for library questions ββ
|
| 1879 |
+
#
|
| 1880 |
+
# Tier 1: Strict KU-library-only LLM prompt (no hallucination risk β
|
| 1881 |
+
# LLM is told to say it doesn't know rather than invent).
|
| 1882 |
+
# Tier 2: If tier 1 is vague/empty β web search scoped to library.ku.ac.ae
|
| 1883 |
+
# so all facts come from the real KU library website.
|
| 1884 |
+
# Tier 3: Always append "Ask a Librarian" + library homepage link.
|
| 1885 |
+
#
|
| 1886 |
+
# General web search (_answer_general) is NOT used here β that's only
|
| 1887 |
+
# for intent==general/general_recent where unrestricted search is correct.
|
| 1888 |
+
|
| 1889 |
+
ASK_LIB = (
|
| 1890 |
+
'<br><br><span style="font-size:.82rem;color:#6b7280">'
|
| 1891 |
+
'π¬ Need more help? '
|
| 1892 |
+
'<a href="https://library.ku.ac.ae/AskUs" target="_blank" style="color:#003366;font-weight:600">Ask a Librarian</a>'
|
| 1893 |
+
' β or browse <a href="https://library.ku.ac.ae/lib" target="_blank" style="color:#003366">library.ku.ac.ae</a>.'
|
| 1894 |
+
'</span>'
|
| 1895 |
+
)
|
| 1896 |
+
|
| 1897 |
+
# ββ Tier 1: Strict KU-only LLM ββ
|
| 1898 |
+
ku_only_prompt = (
|
| 1899 |
+
"You are LibBee, the Khalifa University Library AI Assistant (Abu Dhabi, UAE). "
|
| 1900 |
+
"KU = Khalifa University.\n\n"
|
| 1901 |
+
"STRICT RULES:\n"
|
| 1902 |
+
"1. Answer ONLY using your knowledge of Khalifa University Library services, "
|
| 1903 |
+
"databases, policies, staff, and resources.\n"
|
| 1904 |
+
"2. Be concise (2-4 sentences). Include real KU library URLs when relevant "
|
| 1905 |
+
"(e.g. https://library.ku.ac.ae/AskUs, https://library.ku.ac.ae/ill/, "
|
| 1906 |
+
"https://library.ku.ac.ae/eresources, https://library.ku.ac.ae/oa/).\n"
|
| 1907 |
+
"3. If you are not confident about a specific detail (phone number, exact policy, "
|
| 1908 |
+
"specific database URL), do NOT invent it β instead say you are not certain and "
|
| 1909 |
+
"direct the user to Ask a Librarian or the library website.\n"
|
| 1910 |
+
"4. Never make up staff names, contact details, or database names.\n"
|
| 1911 |
+
"5. Do NOT use general web knowledge or facts unrelated to KU Library.\n\n"
|
| 1912 |
+
f"Question: {question}\nAnswer:"
|
| 1913 |
+
)
|
| 1914 |
+
tier1_answer = ""
|
| 1915 |
+
try:
|
| 1916 |
+
if use_claude:
|
| 1917 |
+
from langchain_anthropic import ChatAnthropic
|
| 1918 |
+
t1_llm = ChatAnthropic(model="claude-haiku-4-5-20251001", temperature=0.1, max_tokens=400)
|
| 1919 |
+
else:
|
| 1920 |
+
t1_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.1, max_tokens=400)
|
| 1921 |
+
t1_response = t1_llm.invoke([
|
| 1922 |
+
{"role": "system", "content": ku_only_prompt},
|
| 1923 |
+
{"role": "user", "content": question},
|
| 1924 |
+
])
|
| 1925 |
+
tier1_answer = t1_response.content.strip()
|
| 1926 |
+
except Exception as t1_err:
|
| 1927 |
+
print(f"[LibBee tier1 LLM error] {t1_err}")
|
| 1928 |
+
|
| 1929 |
+
# ββ Tier 2: If tier 1 is vague, search library.ku.ac.ae ββ
|
| 1930 |
+
# Detect vague answers: short, uncertain, or explicitly admitting no knowledge
|
| 1931 |
+
vague_phrases = [
|
| 1932 |
+
"i'm not sure", "i am not sure", "i don't have", "i do not have",
|
| 1933 |
+
"not certain", "cannot confirm", "not available", "unable to find",
|
| 1934 |
+
"please contact", "you may want to", "i would recommend checking",
|
| 1935 |
+
"i don't have specific", "i lack specific",
|
| 1936 |
+
]
|
| 1937 |
+
is_vague = (
|
| 1938 |
+
not tier1_answer
|
| 1939 |
+
or len(tier1_answer.split()) < 15
|
| 1940 |
+
or any(p in tier1_answer.lower().replace("\u2019", "'") for p in vague_phrases)
|
| 1941 |
+
)
|
| 1942 |
+
|
| 1943 |
+
tier2_answer = ""
|
| 1944 |
+
tier2_sources = []
|
| 1945 |
+
if is_vague:
|
| 1946 |
+
try:
|
| 1947 |
+
import openai as _openai
|
| 1948 |
+
_client = _openai.OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
| 1949 |
+
# Scope web search to KU library domain only
|
| 1950 |
+
scoped_query = f"site:library.ku.ac.ae {question}"
|
| 1951 |
+
_resp = _client.responses.create(
|
| 1952 |
+
model="gpt-4o-mini",
|
| 1953 |
+
tools=[{"type": "web_search_preview"}],
|
| 1954 |
+
input=[{
|
| 1955 |
+
"role": "system",
|
| 1956 |
+
"content": (
|
| 1957 |
+
"You are LibBee, the Khalifa University Library AI Assistant. "
|
| 1958 |
+
"Search the KU library website (library.ku.ac.ae) only. "
|
| 1959 |
+
"Answer in 2-4 sentences using only what you find on the KU library website. "
|
| 1960 |
+
"Always include the source URL. "
|
| 1961 |
+
"If nothing relevant is found on library.ku.ac.ae, say so briefly."
|
| 1962 |
+
)
|
| 1963 |
+
}, {"role": "user", "content": scoped_query}],
|
| 1964 |
+
)
|
| 1965 |
+
for block in _resp.output:
|
| 1966 |
+
if hasattr(block, "content"):
|
| 1967 |
+
for item in block.content:
|
| 1968 |
+
if hasattr(item, "text"):
|
| 1969 |
+
tier2_answer += item.text
|
| 1970 |
+
if hasattr(item, "annotations"):
|
| 1971 |
+
for ann in item.annotations:
|
| 1972 |
+
if hasattr(ann, "url") and hasattr(ann, "title"):
|
| 1973 |
+
if "library.ku.ac.ae" in getattr(ann, "url", ""):
|
| 1974 |
+
tier2_sources.append({"url": ann.url, "title": ann.title})
|
| 1975 |
+
if not tier2_answer:
|
| 1976 |
+
tier2_answer = getattr(_resp, "output_text", "") or ""
|
| 1977 |
+
tier2_answer = tier2_answer.strip()
|
| 1978 |
+
except Exception as t2_err:
|
| 1979 |
+
print(f"[LibBee tier2 web search error] {t2_err}")
|
| 1980 |
+
|
| 1981 |
+
# ββ Combine best answer + Ask a Librarian footer ββ
|
| 1982 |
+
if tier2_answer and len(tier2_answer.split()) >= 10:
|
| 1983 |
+
# Web search found something useful
|
| 1984 |
+
final_answer = tier2_answer + ASK_LIB
|
| 1985 |
+
final_sources = tier2_sources
|
| 1986 |
+
final_tools = tools_used + ["ku_web_search"]
|
| 1987 |
+
elif tier1_answer and len(tier1_answer.split()) >= 10:
|
| 1988 |
+
# Tier 1 LLM gave a decent answer
|
| 1989 |
+
final_answer = tier1_answer + ASK_LIB
|
| 1990 |
+
final_sources = []
|
| 1991 |
+
final_tools = tools_used + ["ku_only_llm"]
|
| 1992 |
+
else:
|
| 1993 |
+
# Both failed β clean redirect only
|
| 1994 |
+
final_answer = (
|
| 1995 |
+
"I don't have that specific information in my knowledge base right now. "
|
| 1996 |
+
"The KU Library team will be able to help directly."
|
| 1997 |
+
+ ASK_LIB
|
| 1998 |
)
|
| 1999 |
+
final_sources = []
|
| 2000 |
+
final_tools = tools_used + ["redirected"]
|
| 2001 |
+
|
| 2002 |
elapsed = time.time() - start
|
| 2003 |
return _make_agent_response(
|
| 2004 |
+
answer=final_answer, intent="library_info",
|
| 2005 |
+
tools_used=final_tools,
|
| 2006 |
+
search_results=[], sources=final_sources,
|
| 2007 |
model=req.model, elapsed=elapsed, question=question,
|
| 2008 |
)
|
| 2009 |
|