nikeshn commited on
Commit
a76fb53
·
verified ·
1 Parent(s): c888860

Update src/api/agent.py

Browse files
Files changed (1) hide show
  1. src/api/agent.py +33 -20
src/api/agent.py CHANGED
@@ -114,6 +114,8 @@ class AgentResponse(BaseModel):
114
  primo_search_url: Optional[str] = None
115
  primo_ai_url: Optional[str] = None
116
  pubmed_search_url: Optional[str] = None
 
 
117
 
118
 
119
  # ── Constants ─────────────────────────────────────────────────────────────────
@@ -1024,12 +1026,16 @@ async def _search_pubmed_articles(context: SearchContextPayload, limit: int = 5)
1024
  def _tool_urls(context: SearchContextPayload) -> Dict[str, str]:
1025
  ai_query = quote(context.ai_tool_query or context.display_topic or context.topic)
1026
  clean_topic = _strip_resource_noise(context.topic)
 
1027
  return {
1028
  "leapspace": f"https://www.sciencedirect.com/leapspace?query={ai_query}",
1029
  "scopus_ai": f"https://www-scopus-com.khalifa.idm.oclc.org/pages/ai?query={ai_query}",
1030
  "ebsco_ai": f"https://research-ebsco-com.khalifa.idm.oclc.org/c/ntdlkp/search?q={ai_query}",
1031
  "primo_ai": f"{PRIMO_AI_URL}{ai_query}",
1032
  "consensus": f"https://consensus.app/results/?q={ai_query}",
 
 
 
1033
  "primo_discovery": _primo_clean_url(context),
1034
  "pubmed": build_pubmed_url(clean_topic, context.year_from, context.year_to) if context.intent == "search_medical" else "",
1035
  }
@@ -1043,7 +1049,8 @@ def _database_guidance_for_topic(context: SearchContextPayload) -> str:
1043
  f'<a href="{urls["scopus_ai"]}" target="_blank" style="color:#1e40af;font-weight:700;text-decoration:none">Scopus AI</a> · '
1044
  f'<a href="{urls["ebsco_ai"]}" target="_blank" style="color:#9d174d;font-weight:700;text-decoration:none">EBSCO AI</a> · '
1045
  f'<a href="{urls["primo_ai"]}" target="_blank" style="color:#5b21b6;font-weight:700;text-decoration:none">PRIMO AI Assistant</a> · '
1046
- f'<a href="{urls["consensus"]}" target="_blank" style="color:#86198f;font-weight:700;text-decoration:none">Consensus</a>'
 
1047
  )
1048
  more_results = f'<a href="{urls["primo_discovery"]}" target="_blank" style="color:#5b21b6;font-weight:700;text-decoration:none">PRIMO Library Discovery</a>'
1049
  if context.intent == "search_medical" and urls["pubmed"]:
@@ -1308,28 +1315,24 @@ def _apply_follow_up_action(base_context: SearchContextPayload, action: Optional
1308
 
1309
  async def _generate_topic_follow_ups(topic: str, model: str) -> List[dict]:
1310
  """
1311
- FIX: Generate 3 topic-specific intellectual follow-up questions using LLM.
1312
- These are ASPECT-based (related subtopics, methodological angles, applications)
1313
- not generic filter suggestions like "peer reviewed only".
1314
  """
1315
  settings = get_settings()
1316
  if not settings.openai_api_key:
1317
  return []
1318
  try:
1319
- llm = _get_llm(model, temperature=0.5, max_tokens=200)
1320
  response = await llm.ainvoke([
1321
  {"role": "system", "content": (
1322
  "You are a research librarian helping a user explore a topic more deeply. "
1323
- "Given a research topic, generate exactly 3 short follow-up questions that explore "
1324
- "DIFFERENT ASPECTS of the topic — such as subtopics, methodological angles, "
1325
- "applications, comparisons, or related fields. "
1326
- "Do NOT suggest filter actions like peer-reviewed, date ranges, or format changes. "
1327
- "Return ONLY a JSON array of 3 strings, each under 12 words. "
1328
- "No explanation, no preamble, no markdown.\n"
1329
- "Example for 'quantum computing':\n"
1330
- '[\"What are the main hardware approaches in quantum computing?\", '
1331
- '"How does quantum error correction work?\", '
1332
- '"Applications of quantum computing in cryptography\"]'
1333
  )},
1334
  {"role": "user", "content": f"Topic: {topic}"},
1335
  ])
@@ -1339,16 +1342,26 @@ async def _generate_topic_follow_ups(topic: str, model: str) -> List[dict]:
1339
  questions = json.loads(raw)
1340
  if not isinstance(questions, list):
1341
  return []
1342
- return [
1343
- {"label": q.strip("?") + "?", "question": q.strip("?") + "?"}
1344
- for q in questions[:3]
1345
- if isinstance(q, str) and q.strip()
1346
- ]
 
 
 
 
 
 
 
 
 
1347
  except Exception as e:
1348
  logger.warning(f"Topic follow-up generation failed: {e}")
1349
  return []
1350
 
1351
 
 
1352
  async def _search_follow_up(
1353
  context: SearchContextPayload, model: str, summary_mode: bool = False
1354
  ) -> Tuple[str, List[dict]]:
 
114
  primo_search_url: Optional[str] = None
115
  primo_ai_url: Optional[str] = None
116
  pubmed_search_url: Optional[str] = None
117
+ primo_ai_url: Optional[str] = None
118
+ pubmed_search_url: Optional[str] = None
119
 
120
 
121
  # ── Constants ─────────────────────────────────────────────────────────────────
 
1026
  def _tool_urls(context: SearchContextPayload) -> Dict[str, str]:
1027
  ai_query = quote(context.ai_tool_query or context.display_topic or context.topic)
1028
  clean_topic = _strip_resource_noise(context.topic)
1029
+ semantic_q = quote((context.ai_tool_query or context.display_topic or context.topic).replace(" ", "+"))
1030
  return {
1031
  "leapspace": f"https://www.sciencedirect.com/leapspace?query={ai_query}",
1032
  "scopus_ai": f"https://www-scopus-com.khalifa.idm.oclc.org/pages/ai?query={ai_query}",
1033
  "ebsco_ai": f"https://research-ebsco-com.khalifa.idm.oclc.org/c/ntdlkp/search?q={ai_query}",
1034
  "primo_ai": f"{PRIMO_AI_URL}{ai_query}",
1035
  "consensus": f"https://consensus.app/results/?q={ai_query}",
1036
+ "openalex": f"https://openalex.org/works?page=1&sort=relevance_score:desc&search.semantic={semantic_q}",
1037
+ "scispace": f"https://scispace.com/search?q={ai_query}",
1038
+ "perplexity": f"https://www.perplexity.ai/search?q={ai_query}",
1039
  "primo_discovery": _primo_clean_url(context),
1040
  "pubmed": build_pubmed_url(clean_topic, context.year_from, context.year_to) if context.intent == "search_medical" else "",
1041
  }
 
1049
  f'<a href="{urls["scopus_ai"]}" target="_blank" style="color:#1e40af;font-weight:700;text-decoration:none">Scopus AI</a> · '
1050
  f'<a href="{urls["ebsco_ai"]}" target="_blank" style="color:#9d174d;font-weight:700;text-decoration:none">EBSCO AI</a> · '
1051
  f'<a href="{urls["primo_ai"]}" target="_blank" style="color:#5b21b6;font-weight:700;text-decoration:none">PRIMO AI Assistant</a> · '
1052
+ f'<a href="{urls["consensus"]}" target="_blank" style="color:#86198f;font-weight:700;text-decoration:none">Consensus</a> · '
1053
+ f'<a href="{urls["openalex"]}" target="_blank" style="color:#0c4a6e;font-weight:700;text-decoration:none">OpenAlex</a>'
1054
  )
1055
  more_results = f'<a href="{urls["primo_discovery"]}" target="_blank" style="color:#5b21b6;font-weight:700;text-decoration:none">PRIMO Library Discovery</a>'
1056
  if context.intent == "search_medical" and urls["pubmed"]:
 
1315
 
1316
  async def _generate_topic_follow_ups(topic: str, model: str) -> List[dict]:
1317
  """
1318
+ Generate 3 topic-specific follow-up questions that ALWAYS preserve the original topic anchor.
1319
+ These should start a fresh contextual search, not a refinement action.
 
1320
  """
1321
  settings = get_settings()
1322
  if not settings.openai_api_key:
1323
  return []
1324
  try:
1325
+ llm = _get_llm(model, temperature=0.5, max_tokens=220)
1326
  response = await llm.ainvoke([
1327
  {"role": "system", "content": (
1328
  "You are a research librarian helping a user explore a topic more deeply. "
1329
+ "Given a research topic, generate exactly 3 short follow-up questions that explore different aspects of the SAME topic. "
1330
+ "Every suggestion must keep the original topic explicitly present. "
1331
+ "For example, if the topic is 'global warming', acceptable suggestions include "
1332
+ "'What are the main adaptation strategies for global warming?' and "
1333
+ "'How does global warming affect water security?'. "
1334
+ "Do NOT suggest generic filters like peer reviewed or last 5 years. "
1335
+ "Return ONLY a JSON array of 3 strings."
 
 
 
1336
  )},
1337
  {"role": "user", "content": f"Topic: {topic}"},
1338
  ])
 
1342
  questions = json.loads(raw)
1343
  if not isinstance(questions, list):
1344
  return []
1345
+ out = []
1346
+ topic_l = (topic or "").lower()
1347
+ for q in questions[:3]:
1348
+ if not isinstance(q, str) or not q.strip():
1349
+ continue
1350
+ clean = q.strip().rstrip('?')
1351
+ if topic_l not in clean.lower():
1352
+ clean = f"{clean} for {topic}"
1353
+ full_q = clean + "?"
1354
+ out.append({
1355
+ "label": full_q,
1356
+ "question": f"Find research articles on {full_q}",
1357
+ })
1358
+ return out
1359
  except Exception as e:
1360
  logger.warning(f"Topic follow-up generation failed: {e}")
1361
  return []
1362
 
1363
 
1364
+
1365
  async def _search_follow_up(
1366
  context: SearchContextPayload, model: str, summary_mode: bool = False
1367
  ) -> Tuple[str, List[dict]]: