nikeshn commited on
Commit
d52355f
Β·
verified Β·
1 Parent(s): a30afbd

Update src/api/agent.py

Browse files
Files changed (1) hide show
  1. src/api/agent.py +45 -39
src/api/agent.py CHANGED
@@ -1400,57 +1400,64 @@ async def _semantic_scholar_search(topic: str, limit: int = 8) -> List[dict]:
1400
  async def _openalex_search(topic: str, limit: int = 8) -> List[dict]:
1401
  """
1402
  Search OpenAlex using title.search + abstract.search filters for precision.
1403
- This targets only the title and abstract fields β€” far more relevant than the
1404
- general `search` parameter which also hits author names, journals, institutions.
1405
  Free, no API key, typically < 2s.
1406
  """
1407
  try:
1408
- from urllib.parse import urlencode
1409
 
1410
- # Build a clean search term β€” strip boolean operators, keep the core phrase
1411
  clean = re.sub(r'\b(AND|OR|NOT)\b|[()"\']', ' ', topic, flags=re.IGNORECASE)
1412
  clean = re.sub(r'\s+', ' ', clean).strip()[:200]
 
1413
 
1414
- # Use title.search OR abstract.search β€” papers that match in either field
1415
- params = {
1416
- "filter": (
1417
- f"title.search:{clean}|abstract.search:{clean},"
1418
- f"has_abstract:true,type:article,is_paratext:false"
1419
- ),
1420
- "sort": "relevance_score:desc",
1421
- "per_page": str(limit),
1422
- "select": ",".join([
1423
- "id", "title", "authorships", "publication_year",
1424
- "primary_location", "abstract_inverted_index",
1425
- "doi", "cited_by_count", "open_access",
1426
- ]),
1427
- }
1428
- url = "https://api.openalex.org/works?" + urlencode(params)
1429
  headers = {"User-Agent": "LibBee/3.2 (mailto:libse@ku.ac.ae)"}
1430
 
1431
  async with httpx.AsyncClient(timeout=10) as client:
1432
  r = await client.get(url, headers=headers)
1433
  if r.status_code != 200:
1434
- logger.warning(f"OpenAlex returned {r.status_code} for topic '{clean[:40]}'")
1435
- return []
 
 
 
 
 
 
 
 
 
 
 
 
1436
 
1437
- data = r.json()
1438
- works = data.get("results", [])
1439
 
1440
- # If title+abstract search returns nothing, fall back to general search
1441
  if not works:
1442
- logger.info(f"OpenAlex title+abstract search returned 0 β€” retrying with general search")
1443
- params_fallback = {
1444
- "search": clean,
1445
- "filter": "has_abstract:true,type:article,is_paratext:false",
1446
- "sort": "relevance_score:desc",
1447
- "per_page": str(limit),
1448
- "select": params["select"],
1449
- }
1450
- r2 = await client.get(
1451
- "https://api.openalex.org/works?" + urlencode(params_fallback),
1452
- headers=headers,
1453
  )
 
1454
  if r2.status_code == 200:
1455
  works = r2.json().get("results", [])
1456
 
@@ -1472,7 +1479,6 @@ async def _openalex_search(topic: str, limit: int = 8) -> List[dict]:
1472
  if not abstract:
1473
  continue
1474
 
1475
- # Authors
1476
  authorships = work.get("authorships") or []
1477
  author_names = [
1478
  a.get("author", {}).get("display_name", "")
@@ -1493,9 +1499,9 @@ async def _openalex_search(topic: str, limit: int = 8) -> List[dict]:
1493
  or work.get("id", "https://openalex.org")
1494
  )
1495
  venue = (
1496
- (work.get("primary_location") or {})
1497
- .get("source", {}) or {}
1498
- ).get("display_name") or ""
1499
 
1500
  results.append({
1501
  "title": title,
 
1400
  async def _openalex_search(topic: str, limit: int = 8) -> List[dict]:
1401
  """
1402
  Search OpenAlex using title.search + abstract.search filters for precision.
1403
+ Targets only title and abstract fields β€” more relevant than general search.
 
1404
  Free, no API key, typically < 2s.
1405
  """
1406
  try:
1407
+ from urllib.parse import quote
1408
 
1409
+ # Strip boolean operators β€” OpenAlex filter search uses its own relevance engine
1410
  clean = re.sub(r'\b(AND|OR|NOT)\b|[()"\']', ' ', topic, flags=re.IGNORECASE)
1411
  clean = re.sub(r'\s+', ' ', clean).strip()[:200]
1412
+ encoded = quote(clean, safe='')
1413
 
1414
+ # Build URL manually β€” urlencode encodes | and : which breaks OpenAlex filter syntax
1415
+ filter_str = (
1416
+ f"title.search:{encoded}|abstract.search:{encoded},"
1417
+ f"has_abstract:true,type:article,is_paratext:false"
1418
+ )
1419
+ select = "id,title,authorships,publication_year,primary_location,abstract_inverted_index,doi,cited_by_count,open_access"
1420
+ url = (
1421
+ f"https://api.openalex.org/works"
1422
+ f"?filter={filter_str}"
1423
+ f"&sort=relevance_score:desc"
1424
+ f"&per_page={limit}"
1425
+ f"&select={select}"
1426
+ )
 
 
1427
  headers = {"User-Agent": "LibBee/3.2 (mailto:libse@ku.ac.ae)"}
1428
 
1429
  async with httpx.AsyncClient(timeout=10) as client:
1430
  r = await client.get(url, headers=headers)
1431
  if r.status_code != 200:
1432
+ logger.warning(f"OpenAlex title+abstract search {r.status_code} β€” retrying with general search")
1433
+ # Fallback: general search
1434
+ url_fb = (
1435
+ f"https://api.openalex.org/works"
1436
+ f"?search={encoded}"
1437
+ f"&filter=has_abstract:true,type:article,is_paratext:false"
1438
+ f"&sort=relevance_score:desc"
1439
+ f"&per_page={limit}"
1440
+ f"&select={select}"
1441
+ )
1442
+ r = await client.get(url_fb, headers=headers)
1443
+ if r.status_code != 200:
1444
+ logger.warning(f"OpenAlex fallback also failed: {r.status_code}")
1445
+ return []
1446
 
1447
+ works = r.json().get("results", [])
 
1448
 
1449
+ # If title+abstract returns nothing, retry with general search
1450
  if not works:
1451
+ logger.info("OpenAlex title+abstract returned 0 β€” retrying with general search")
1452
+ url_fb = (
1453
+ f"https://api.openalex.org/works"
1454
+ f"?search={encoded}"
1455
+ f"&filter=has_abstract:true,type:article,is_paratext:false"
1456
+ f"&sort=relevance_score:desc"
1457
+ f"&per_page={limit}"
1458
+ f"&select={select}"
 
 
 
1459
  )
1460
+ r2 = await client.get(url_fb, headers=headers)
1461
  if r2.status_code == 200:
1462
  works = r2.json().get("results", [])
1463
 
 
1479
  if not abstract:
1480
  continue
1481
 
 
1482
  authorships = work.get("authorships") or []
1483
  author_names = [
1484
  a.get("author", {}).get("display_name", "")
 
1499
  or work.get("id", "https://openalex.org")
1500
  )
1501
  venue = (
1502
+ ((work.get("primary_location") or {}).get("source") or {})
1503
+ .get("display_name") or ""
1504
+ )
1505
 
1506
  results.append({
1507
  "title": title,