nikeshn commited on
Commit
8b9d96a
Β·
verified Β·
1 Parent(s): 9ac502a

Update src/api/agent.py

Browse files
Files changed (1) hide show
  1. src/api/agent.py +65 -23
src/api/agent.py CHANGED
@@ -1224,16 +1224,49 @@ def _contact_footer_for_query(question: str, context: Optional[SearchContextPayl
1224
  return footer
1225
 
1226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1227
  def _search_trace_block(question: str, context: SearchContextPayload) -> str:
1228
  """
1229
- 'How I searched' reasoning panel β€” shown at the top of every search response.
1230
- Collapsible <details> so it doesn't dominate the answer.
1231
  """
1232
  topic = _escape(context.display_topic or context.topic)
1233
  primo_boolean = _escape(context.primo_boolean_query or "")
1234
  source_label = "PubMed" if context.source == "pubmed" else "KU PRIMO Library Discovery"
1235
 
1236
- # Build reasoning sentence
1237
  intent_label = {
1238
  "search_academic": "academic literature search",
1239
  "search_medical": "medical / clinical literature search",
@@ -1255,9 +1288,9 @@ def _search_trace_block(question: str, context: SearchContextPayload) -> str:
1255
  filters_str = ", ".join(filters_parts)
1256
 
1257
  reasoning = (
1258
- f"I identified this as a <strong>{intent_label}</strong>. "
1259
- f"I extracted the topic <strong>{topic}</strong>, "
1260
- f"built an LLM-optimised boolean query with quoted phrases and synonyms, "
1261
  f"then searched <strong>{source_label}</strong> for <strong>{filters_str}</strong>."
1262
  )
1263
  if context.intent == "search_medical" and context.source != "pubmed":
@@ -1278,7 +1311,8 @@ def _search_trace_block(question: str, context: SearchContextPayload) -> str:
1278
  f'<code style="display:block;margin:4px 0;padding:6px 10px;background:#1a1a2e;color:#C8A951;'
1279
  f'border-radius:7px;font-size:.78rem;word-break:break-all">{primo_boolean}</code>'
1280
  )
1281
- block += f'<strong>Reasoning:</strong> {reasoning}'
 
1282
  block += '</div></details>'
1283
  return block
1284
 
@@ -2113,7 +2147,7 @@ async def agent_query(request: Request, req: AgentRequest):
2113
  _safe_metrics_bucket("intents", "staff_lookup")
2114
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2115
  return AgentResponse(
2116
- answer=staff_name_answer(staff_match) + _source_badge("ku_library", "https://library.ku.ac.ae/"),
2117
  intent="library_info",
2118
  tools_used=["staff_name_match"],
2119
  model_used=model,
@@ -2127,7 +2161,7 @@ async def agent_query(request: Request, req: AgentRequest):
2127
  _safe_metrics_bucket("intents", "staff_lookup")
2128
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2129
  return AgentResponse(
2130
- answer=staff_role_answer(staff_role_match, question) + _source_badge("ku_library", "https://library.ku.ac.ae/"),
2131
  intent="library_info",
2132
  tools_used=["staff_role_match"],
2133
  model_used=model,
@@ -2139,7 +2173,7 @@ async def agent_query(request: Request, req: AgentRequest):
2139
  if _is_greeting_menu_followup(question, history):
2140
  follow_up_question, follow_up_suggestions = _social_follow_up()
2141
  return AgentResponse(
2142
- answer=_greeting_menu_clarify_answer() + _source_badge("libbee"),
2143
  intent="social_greeting",
2144
  is_follow_up=True,
2145
  model_used=model,
@@ -2151,7 +2185,7 @@ async def agent_query(request: Request, req: AgentRequest):
2151
  if _looks_library_hours_question(question):
2152
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2153
  return AgentResponse(
2154
- answer=_hours_answer() + _source_badge("ku_library", LIBRARY_HOURS_URL),
2155
  intent="library_info",
2156
  tools_used=["hours_hardcoded"],
2157
  ask_librarian=False,
@@ -2164,7 +2198,7 @@ async def agent_query(request: Request, req: AgentRequest):
2164
  if _looks_campus_question(question):
2165
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2166
  return AgentResponse(
2167
- answer=_campus_answer() + _source_badge("ku_library", "https://library.ku.ac.ae/"),
2168
  intent="library_info",
2169
  tools_used=["campus_hardcoded"],
2170
  ask_librarian=False,
@@ -2180,7 +2214,7 @@ async def agent_query(request: Request, req: AgentRequest):
2180
  answer += _source_badge("ku_library", "https://library.ku.ac.ae/eresources")
2181
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2182
  return AgentResponse(
2183
- answer=answer,
2184
  intent="library_info",
2185
  tools_used=["database_recommendation"],
2186
  model_used=model,
@@ -2200,10 +2234,13 @@ async def agent_query(request: Request, req: AgentRequest):
2200
  if follow_up_action == "summarize_topic":
2201
  answer, search_results, sources = await _research_snapshot(context, model)
2202
  else:
 
2203
  answer = (
 
2204
  f"<strong>Research guide for {_escape(context.display_topic or context.topic)}</strong><br><br>"
2205
- f"{_database_guidance_for_topic(context)}"
2206
  + _source_badge("libbee")
 
2207
  )
2208
  search_results = []
2209
  sources = []
@@ -2269,7 +2306,7 @@ async def agent_query(request: Request, req: AgentRequest):
2269
  )
2270
  follow_up_question, follow_up_suggestions = _social_follow_up()
2271
  return AgentResponse(
2272
- answer=answer + _source_badge("libbee"),
2273
  intent="social_greeting",
2274
  tools_used=["libbee_casual"],
2275
  model_used=model,
@@ -2279,7 +2316,6 @@ async def agent_query(request: Request, req: AgentRequest):
2279
  )
2280
 
2281
  if intent == "library_info":
2282
- # FIX: use get_rag_service() accessor β€” safe against circular import failures
2283
  rag_results = []
2284
  try:
2285
  from app import get_rag_service
@@ -2297,19 +2333,22 @@ async def agent_query(request: Request, req: AgentRequest):
2297
  answer += _source_badge("ku_library", ASK_LIBRARIAN_URL)
2298
  sources = []
2299
  tools_used = ["rag_no_results"]
 
 
2300
  else:
2301
  answer = await _rag_answer(question, rag_results, history, model)
2302
  answer += _source_badge("rag", "https://library.ku.ac.ae/")
2303
  sources = [
2304
  {"title": r.get("title", "Library KB"), "url": r.get("source", "")}
2305
- for r in rag_results
2306
- if r.get("source")
2307
  ]
2308
  tools_used = ["rag_hybrid_search"]
 
 
2309
 
2310
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2311
  return AgentResponse(
2312
- answer=answer,
2313
  intent="library_info",
2314
  tools_used=tools_used,
2315
  sources=sources,
@@ -2346,8 +2385,10 @@ async def agent_query(request: Request, req: AgentRequest):
2346
 
2347
  if summary_mode or deep_research_mode:
2348
  answer, search_results, sources = await _research_snapshot(context, model)
2349
- if deep_research_mode and "Research guide" not in answer:
2350
- answer += f"<br><br><strong>Research guide:</strong> {_database_guidance_for_topic(context)}"
 
 
2351
  follow_up_question, follow_up_suggestions = await _search_follow_up(context, model, summary_mode=True)
2352
  _safe_metrics_increment("search_handoffs")
2353
  return AgentResponse(
@@ -2398,7 +2439,7 @@ async def agent_query(request: Request, req: AgentRequest):
2398
  answer = await _web_search_answer(question, history, model) + _source_badge("web_live")
2399
  follow_up_question, follow_up_suggestions = _general_follow_up(question)
2400
  return AgentResponse(
2401
- answer=answer,
2402
  intent="general_recent",
2403
  tools_used=["web_search"],
2404
  model_used=model,
@@ -2407,10 +2448,11 @@ async def agent_query(request: Request, req: AgentRequest):
2407
  follow_up_suggestions=follow_up_suggestions,
2408
  )
2409
 
 
2410
  answer = await _web_search_answer(question, history, model) + _source_badge("web")
2411
  follow_up_question, follow_up_suggestions = _general_follow_up(question)
2412
  return AgentResponse(
2413
- answer=answer,
2414
  intent="general",
2415
  tools_used=["web_search"],
2416
  model_used=model,
 
1224
  return footer
1225
 
1226
 
1227
+ def _thought_block(intent: str, tool: str, question: str, extra: str = "") -> str:
1228
+ """
1229
+ Lightweight collapsible reasoning block shown at the top of every non-search response.
1230
+ """
1231
+ labels = {
1232
+ "library_info": ("πŸ“‹", "Library information lookup", "I searched the KU Library knowledge base for relevant content."),
1233
+ "general": ("🌐", "General knowledge / web search", "I answered using a web search for current information."),
1234
+ "general_recent": ("πŸ”΄", "Live web search", "I ran a live web search for the most up-to-date information."),
1235
+ "social_greeting": ("🐝", "Conversational response", "I responded directly as LibBee, the KU Library AI Assistant."),
1236
+ "social": ("🐝", "Conversational response", "I responded directly as LibBee, the KU Library AI Assistant."),
1237
+ "staff_lookup": ("πŸ‘€", "Staff directory lookup", "I matched your request to the KU Library staff directory."),
1238
+ "hours": ("⏰", "Library hours lookup", "I retrieved the current library hours from the KU Library knowledge base."),
1239
+ "campus": ("πŸ“", "Campus information lookup", "I retrieved KU Library campus information from the knowledge base."),
1240
+ "database_rec": ("πŸ—„οΈ", "Database recommendation", "I matched your subject to the KU Library e-resources database."),
1241
+ "rag_search": ("πŸ“–", "Knowledge base search", "I ran a hybrid semantic + keyword search across the KU Library knowledge base."),
1242
+ }
1243
+ icon, label, reasoning = labels.get(intent, ("πŸ€–", "AI response", "I processed your question and generated a response."))
1244
+ if extra:
1245
+ reasoning += " " + extra
1246
+
1247
+ return (
1248
+ f'<details style="margin:0 0 12px 0;border:1px solid #e5e7eb;border-radius:10px;'
1249
+ f'background:#fafafa;overflow:hidden">'
1250
+ f'<summary style="padding:8px 12px;cursor:pointer;font-size:.75rem;font-weight:700;'
1251
+ f'color:#6b7280;list-style:none;display:flex;align-items:center;gap:5px">'
1252
+ f'<span>{icon}</span> My approach β€” click to see</summary>'
1253
+ f'<div style="padding:4px 12px 10px 12px;font-size:.79rem;line-height:1.7;border-top:1px solid #e5e7eb">'
1254
+ f'<strong>Question:</strong> {_escape(question)}<br>'
1255
+ f'<strong>Classified as:</strong> {label}<br>'
1256
+ f'<strong>How I answered:</strong> {reasoning}'
1257
+ f'</div></details>'
1258
+ )
1259
+
1260
+
1261
  def _search_trace_block(question: str, context: SearchContextPayload) -> str:
1262
  """
1263
+ Richer collapsible reasoning block for search responses.
1264
+ Shows topic, boolean query, and reasoning. Also hints that AI tools are at the end.
1265
  """
1266
  topic = _escape(context.display_topic or context.topic)
1267
  primo_boolean = _escape(context.primo_boolean_query or "")
1268
  source_label = "PubMed" if context.source == "pubmed" else "KU PRIMO Library Discovery"
1269
 
 
1270
  intent_label = {
1271
  "search_academic": "academic literature search",
1272
  "search_medical": "medical / clinical literature search",
 
1288
  filters_str = ", ".join(filters_parts)
1289
 
1290
  reasoning = (
1291
+ f"I classified this as a <strong>{intent_label}</strong>, "
1292
+ f"extracted the topic <strong>{topic}</strong>, "
1293
+ f"built an LLM-optimised boolean query with quoted phrases and OR synonyms, "
1294
  f"then searched <strong>{source_label}</strong> for <strong>{filters_str}</strong>."
1295
  )
1296
  if context.intent == "search_medical" and context.source != "pubmed":
 
1311
  f'<code style="display:block;margin:4px 0;padding:6px 10px;background:#1a1a2e;color:#C8A951;'
1312
  f'border-radius:7px;font-size:.78rem;word-break:break-all">{primo_boolean}</code>'
1313
  )
1314
+ block += f'<strong>Reasoning:</strong> {reasoning}<br>'
1315
+ block += '<span style="color:#5b21b6;font-size:.78rem">✨ AI research tools for deeper exploration are listed at the end of this answer.</span>'
1316
  block += '</div></details>'
1317
  return block
1318
 
 
2147
  _safe_metrics_bucket("intents", "staff_lookup")
2148
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2149
  return AgentResponse(
2150
+ answer=_thought_block("staff_lookup", "staff_name_match", question) + staff_name_answer(staff_match) + _source_badge("ku_library", "https://library.ku.ac.ae/"),
2151
  intent="library_info",
2152
  tools_used=["staff_name_match"],
2153
  model_used=model,
 
2161
  _safe_metrics_bucket("intents", "staff_lookup")
2162
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2163
  return AgentResponse(
2164
+ answer=_thought_block("staff_lookup", "staff_role_match", question) + staff_role_answer(staff_role_match, question) + _source_badge("ku_library", "https://library.ku.ac.ae/"),
2165
  intent="library_info",
2166
  tools_used=["staff_role_match"],
2167
  model_used=model,
 
2173
  if _is_greeting_menu_followup(question, history):
2174
  follow_up_question, follow_up_suggestions = _social_follow_up()
2175
  return AgentResponse(
2176
+ answer=_thought_block("social_greeting", "greeting_menu", question) + _greeting_menu_clarify_answer() + _source_badge("libbee"),
2177
  intent="social_greeting",
2178
  is_follow_up=True,
2179
  model_used=model,
 
2185
  if _looks_library_hours_question(question):
2186
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2187
  return AgentResponse(
2188
+ answer=_thought_block("hours", "hours_hardcoded", question) + _hours_answer() + _source_badge("ku_library", LIBRARY_HOURS_URL),
2189
  intent="library_info",
2190
  tools_used=["hours_hardcoded"],
2191
  ask_librarian=False,
 
2198
  if _looks_campus_question(question):
2199
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2200
  return AgentResponse(
2201
+ answer=_thought_block("campus", "campus_hardcoded", question) + _campus_answer() + _source_badge("ku_library", "https://library.ku.ac.ae/"),
2202
  intent="library_info",
2203
  tools_used=["campus_hardcoded"],
2204
  ask_librarian=False,
 
2214
  answer += _source_badge("ku_library", "https://library.ku.ac.ae/eresources")
2215
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2216
  return AgentResponse(
2217
+ answer=_thought_block("database_rec", "database_recommendation", question) + answer,
2218
  intent="library_info",
2219
  tools_used=["database_recommendation"],
2220
  model_used=model,
 
2234
  if follow_up_action == "summarize_topic":
2235
  answer, search_results, sources = await _research_snapshot(context, model)
2236
  else:
2237
+ # deep_research_tools / best_databases follow-up
2238
  answer = (
2239
+ f"{_search_trace_block(question, context)}"
2240
  f"<strong>Research guide for {_escape(context.display_topic or context.topic)}</strong><br><br>"
2241
+ f"Here are the best AI tools and resources for a deep dive on this topic."
2242
  + _source_badge("libbee")
2243
+ + _ai_tools_footer(context)
2244
  )
2245
  search_results = []
2246
  sources = []
 
2306
  )
2307
  follow_up_question, follow_up_suggestions = _social_follow_up()
2308
  return AgentResponse(
2309
+ answer=_thought_block("social", "libbee_casual", question) + answer + _source_badge("libbee"),
2310
  intent="social_greeting",
2311
  tools_used=["libbee_casual"],
2312
  model_used=model,
 
2316
  )
2317
 
2318
  if intent == "library_info":
 
2319
  rag_results = []
2320
  try:
2321
  from app import get_rag_service
 
2333
  answer += _source_badge("ku_library", ASK_LIBRARIAN_URL)
2334
  sources = []
2335
  tools_used = ["rag_no_results"]
2336
+ thought = _thought_block("library_info", "rag_no_results", question,
2337
+ "No matching content was found β€” I am directing you to Ask a Librarian.")
2338
  else:
2339
  answer = await _rag_answer(question, rag_results, history, model)
2340
  answer += _source_badge("rag", "https://library.ku.ac.ae/")
2341
  sources = [
2342
  {"title": r.get("title", "Library KB"), "url": r.get("source", "")}
2343
+ for r in rag_results if r.get("source")
 
2344
  ]
2345
  tools_used = ["rag_hybrid_search"]
2346
+ thought = _thought_block("rag_search", "rag_hybrid_search", question,
2347
+ f"Hybrid FAISS + BM25 search returned {len(rag_results)} relevant chunk(s) from the KB.")
2348
 
2349
  follow_up_question, follow_up_suggestions = _library_follow_up(question)
2350
  return AgentResponse(
2351
+ answer=thought + answer,
2352
  intent="library_info",
2353
  tools_used=tools_used,
2354
  sources=sources,
 
2385
 
2386
  if summary_mode or deep_research_mode:
2387
  answer, search_results, sources = await _research_snapshot(context, model)
2388
+ # _research_snapshot already includes _search_trace_block and _ai_tools_footer
2389
+ # Only append extra deep-research guidance if not already present
2390
+ if deep_research_mode and "Research guide" not in answer and "AI tools" not in answer:
2391
+ answer += f"<br><br>{_ai_tools_footer(context)}"
2392
  follow_up_question, follow_up_suggestions = await _search_follow_up(context, model, summary_mode=True)
2393
  _safe_metrics_increment("search_handoffs")
2394
  return AgentResponse(
 
2439
  answer = await _web_search_answer(question, history, model) + _source_badge("web_live")
2440
  follow_up_question, follow_up_suggestions = _general_follow_up(question)
2441
  return AgentResponse(
2442
+ answer=_thought_block("general_recent", "web_search", question) + answer,
2443
  intent="general_recent",
2444
  tools_used=["web_search"],
2445
  model_used=model,
 
2448
  follow_up_suggestions=follow_up_suggestions,
2449
  )
2450
 
2451
+ # general fallback
2452
  answer = await _web_search_answer(question, history, model) + _source_badge("web")
2453
  follow_up_question, follow_up_suggestions = _general_follow_up(question)
2454
  return AgentResponse(
2455
+ answer=_thought_block("general", "web_search", question) + answer,
2456
  intent="general",
2457
  tools_used=["web_search"],
2458
  model_used=model,