Update src/agentcore/scholarly.py
Browse files- src/agentcore/scholarly.py +60 -1
src/agentcore/scholarly.py
CHANGED
|
@@ -16,7 +16,7 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
| 16 |
|
| 17 |
from src.config import get_settings, LIBBEE_VERSION
|
| 18 |
|
| 19 |
-
from src.agentcore.utils import _strip_resource_noise
|
| 20 |
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
|
|
@@ -237,3 +237,62 @@ async def enrich_papers_with_oa(papers: List[dict], max_lookups: int = 5) -> Lis
|
|
| 237 |
if meta.get("venue") and not p.get("venue"):
|
| 238 |
p["venue"] = meta["venue"]
|
| 239 |
return papers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
from src.config import get_settings, LIBBEE_VERSION
|
| 18 |
|
| 19 |
+
from src.agentcore.utils import _escape, _strip_resource_noise
|
| 20 |
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
|
|
|
|
| 237 |
if meta.get("venue") and not p.get("venue"):
|
| 238 |
p["venue"] = meta["venue"]
|
| 239 |
return papers
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
# ββ Shared evidence panel ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 243 |
+
# Used by BOTH search handlers (_run_search_mode and _research_snapshot) so the
|
| 244 |
+
# panel appears on ordinary searches, not only on "deep research" phrasing.
|
| 245 |
+
# Returns (papers, html). Never raises: on any failure returns ([], "") and the
|
| 246 |
+
# caller renders exactly its pre-3.8 output.
|
| 247 |
+
|
| 248 |
+
async def fetch_evidence_panel(topic: str, limit: int = 6, max_lookups: int = 5):
|
| 249 |
+
papers: List[dict] = []
|
| 250 |
+
try:
|
| 251 |
+
papers = await _openalex_search(topic, limit=limit)
|
| 252 |
+
if not papers:
|
| 253 |
+
papers = await _semantic_scholar_search(topic, limit=limit)
|
| 254 |
+
if papers:
|
| 255 |
+
papers = await enrich_papers_with_oa(papers, max_lookups=max_lookups)
|
| 256 |
+
except Exception as exc:
|
| 257 |
+
logger.warning(f"Evidence panel failed for '{topic}': {exc}")
|
| 258 |
+
return [], ""
|
| 259 |
+
if not papers:
|
| 260 |
+
return [], ""
|
| 261 |
+
|
| 262 |
+
rows = ""
|
| 263 |
+
for paper in papers[:5]:
|
| 264 |
+
_t = _escape(str(paper.get("title") or "Untitled"))
|
| 265 |
+
_y = _escape(str(paper.get("year") or "n.d."))
|
| 266 |
+
_v = _escape(str(paper.get("venue") or ""))
|
| 267 |
+
_c = paper.get("cited_by_count")
|
| 268 |
+
_link = paper.get("link") or (f"https://doi.org/{paper['doi']}" if paper.get("doi") else "")
|
| 269 |
+
_oa = paper.get("oa_pdf_url") or ""
|
| 270 |
+
_bits = [b for b in (_y, _v) if b]
|
| 271 |
+
if isinstance(_c, int) and _c > 0:
|
| 272 |
+
_bits.append(f"{_c:,} citations")
|
| 273 |
+
_meta = " \u00b7 ".join(_bits)
|
| 274 |
+
_title_html = (
|
| 275 |
+
f'<a href="{_escape(_link)}" target="_blank" style="color:#003366;font-weight:600">{_t}</a>'
|
| 276 |
+
if _link else f'<span style="font-weight:600">{_t}</span>'
|
| 277 |
+
)
|
| 278 |
+
_oa_html = (
|
| 279 |
+
f' \u00b7 <a href="{_escape(_oa)}" target="_blank" '
|
| 280 |
+
f'style="color:#047857;font-weight:700">\U0001f513 OA full text</a>'
|
| 281 |
+
if _oa else ""
|
| 282 |
+
)
|
| 283 |
+
rows += (
|
| 284 |
+
f'<div style="margin:7px 0;padding-left:2px;font-size:.84rem;line-height:1.55">'
|
| 285 |
+
f'{_title_html}<br>'
|
| 286 |
+
f'<span style="color:#6b7280;font-size:.78rem">{_meta}{_oa_html}</span></div>'
|
| 287 |
+
)
|
| 288 |
+
html = (
|
| 289 |
+
'<div style="margin-top:16px;padding:12px 14px;border:1px solid #d1fae5;'
|
| 290 |
+
'border-radius:10px;background:#f0fdf9">'
|
| 291 |
+
'<div style="font-weight:700;color:#065f46;font-size:.86rem;margin-bottom:4px">'
|
| 292 |
+
'\U0001f50e Open scholarly evidence</div>'
|
| 293 |
+
'<div style="font-size:.76rem;color:#047857;margin-bottom:8px">'
|
| 294 |
+
'Top open-index results (OpenAlex / Semantic Scholar) with legal open-access '
|
| 295 |
+
'links via Unpaywall and metadata via Crossref where available.</div>'
|
| 296 |
+
f'{rows}</div>'
|
| 297 |
+
)
|
| 298 |
+
return papers, html
|