semantic-scholar-deep
Deep research over the Semantic Scholar Graph API. Covers endpoints missing from allenai's lookup skill — paper references (backward citations), recommendations, batch paper lookup (up to 500 IDs), snippet search, and multi-hop citation graph traversal (BFS forward/backward). Use
Install
npx skills add https://github.com/CodeAlive-AI/ai-driven-development/tree/main/skills/semantic-scholar-deep
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install codealive-ai-ai-driven-development@llmmart
git clone https://github.com/CodeAlive-AI/ai-driven-development.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole codealive-ai/ai-driven-development collection as a plugin from our marketplace. Git is the plain clone.
README
semantic-scholar-deep
Deep research over the Semantic Scholar Graph API. Covers the endpoints missing from the official allenai/asta-plugins semantic-scholar-lookup skill — backward references, recommendations, batch lookup (up to 500 IDs per call), snippet full-text search, and multi-hop citation-graph BFS.
Install
npx skills add CodeAlive-AI/ai-driven-development@semantic-scholar-deep -g -y
Works anonymously out of the box — most endpoints (paper lookup, references, recommendations, batch) respond without an API key. The keyword /paper/search endpoint is heavily rate-limited on the anonymous tier, so for large sweeps set SEMANTIC_SCHOLAR_API_KEY (request one at https://www.semanticscholar.org/product/api#api-key).
Prerequisites
This skill stands alone for scripts-only usage (invoke the Python CLIs directly). For the full deep-research pipeline (orchestrated by the bundled subagent, see below), you also need:
| Tool | Why | How |
|---|---|---|
| Exa MCP | Neural paper discovery across arXiv / OpenReview / PubMed / bioRxiv — the S2 search endpoint is too rate-limited to be the primary discovery path |
claude mcp add --transport http exa "https://mcp.exa.ai/mcp?tools=web_search_advanced_exa" |
allenai semantic-scholar-lookup skill |
First-party asta papers CLI for fast metadata + forward citations (complements our backward-references / recommendations coverage) |
npx skills add "allenai/asta-plugins@Semantic Scholar Lookup" -g -y |
| Python 3.8+ | Scripts are stdlib-only, no pip install | Usually already present |
Without the above, the bundled subagent falls back to what it can reach (S2-only), but Exa dramatically improves discovery quality and freshness.
Quick start
Inline usage (one specific endpoint):
> Get the references that DOI:10.18653/v1/N18-3011 cites
> Recommend 20 papers related to this paperId
> Batch-resolve these 30 arXiv IDs: 2404.18496 2502.02757 ...
> Build a citation graph of depth 2 around paperId X
Delegated usage (multi-step research via the bundled subagent):
> Find recent papers on LLM code review
> Do a literature review on retrieval-augmented generation since 2024
> Novelty check: is my idea <X> already published?
What it ships
Python CLIs (scripts/)
ss_client.py— stdlib-only S2 client with exponential backoff on HTTP 429/5xx. Subcommands:search,paper,citations,references,recommendations,batch,author-search,author,author-papers,snippets.citation_graph.py— BFS traversal around a seed paper. Options:--direction forward|backward|both,--depth N,--max-nodes N. Outputs JSON withnodes+edges, designed for summarization rather than in-context dumping.
References (references/)
endpoints.md— complete field reference, query-parameter list, and ID-format matrix for every endpoint.workflows.md— 5 ready-made deep-research patterns: literature review from a topic, citation graph around a seed, novelty check, author trajectory, evidence for a claim.
Bundled subagent (agents/deep-paper-researcher.md, optional)
A paired subagent definition for token-isolated research. Manually install once:
cp ~/.agents/skills/semantic-scholar-deep/agents/deep-paper-researcher.md ~/.claude/agents/
Then restart the session. Features:
- Input validation — today's date anchoring + caller-paraphrased-window detection (catches cases where the calling agent translates "recent" into an invented "2024-2026" window)
- Freshness Mode classifier —
RECENT(default last 6 months, sort by publication date),FOUNDATIONAL(sort by citations, no date floor),MIXED(two slices side-by-side) - Sort-then-tiebreak ranking — never multiplies
citations × recencyinto a single score, so fresh papers with zero citations aren't buried under older high-citation ones - Compact report format with explicit
Anchor date/Mode/Windowheader — readers can see and redirect the choice
Key design decisions
- Stdlib only — no
requestsdependency. Works on any Python 3.8+ install. - Backoff over hard-failure — HTTP 429 / 5xx get exponential retry up to 30s, honoring
Retry-After. Anonymous tier is usable for small graphs. - Progressive disclosure —
SKILL.mdstays under 150 lines; deep endpoint-by-endpoint docs and workflow templates live inreferences/. - Token hygiene — scripts emit raw JSON to stdout by design. For graphs >50 nodes, use
--outputto write to disk and summarize viajq/python3rather than piping full payloads into the agent's context. - Not a discovery engine — keyword
searchworks but is the weakest endpoint on the anonymous tier; the bundled subagent delegates discovery to Exa MCP and uses S2 for structured expansion (references, recommendations, batch).
Rate limits
| Endpoint | Anonymous tier | With API key |
|---|---|---|
paper by ID |
~1 RPS, reliable | Much higher |
references / recommendations |
~1 RPS, reliable | Much higher |
batch (up to 500 IDs) |
~1 RPS | Much higher |
search (keyword) |
Frequently 429 | Reliable |
The client respects Retry-After and does up to 5 retries with exponential backoff (1→30s).
File structure
semantic-scholar-deep/
├── SKILL.md # Agent-facing instructions (dispatch rule, when to use)
├── README.md # This file
├── scripts/
│ ├── ss_client.py # Full S2 API client (stdlib only)
│ └── citation_graph.py # BFS traversal
├── references/
│ ├── endpoints.md # Per-endpoint field and parameter reference
│ └── workflows.md # 5 deep-research patterns
└── agents/
└── deep-paper-researcher.md # Optional paired subagent definition
License
MIT
Skill manifest
Semantic Scholar — Deep Research
Purpose: fill the gaps that semantic-scholar-lookup (allenai) leaves — references, recommendations, batch, and multi-hop citation-graph traversal.
Contents
- Dispatch Rule — inline vs delegate; model selection
- When to Use — trigger scenarios
- Scripts —
ss_client.py+citation_graph.py - Authentication & Rate Limits
- Progressive Disclosure — deeper references
- Output Hygiene
- Integration — typical pipeline with the subagent
Dispatch Rule (read first)
Two execution modes:
Inline (run the Bash scripts yourself)
Use when the user asks for one specific endpoint:
- "get references of paper X" →
ss_client.py references <id> - "recommendations for paper Y" →
ss_client.py recommendations <id> - "batch-resolve these 30 DOIs" →
ss_client.py batch ... - "find the snippet where X is said" →
ss_client.py snippets "..."
Fast, cheap, no orchestration overhead.
Delegate to deep-paper-researcher subagent
Use when the task is multi-step or would otherwise flood the context:
- Literature review on a topic
- Citation graph / network analysis around a seed paper
- Novelty check for an idea
- State-of-the-art survey
- Anything that requires merging Exa discovery + S2 graph + ranking
Mandatory prompt contents. The subagent runs in isolated context with no access to this conversation's system reminders. Include exactly these two things:
- Today's date — inline as
Today is YYYY-MM-DD.Pull from thecurrentDatesystem-reminder field, or rundate -Ivia Bash before delegating if it's missing. Never rely on training-data intuitions about the current year. - User's request, verbatim — pass the user's original phrasing (topic + any freshness words like "современные / recent / классические / seminal" and any explicit dates like "since 2024"). Translate language if needed but do not paraphrase trigger words into date windows.
Do NOT do any of these:
- Do NOT classify freshness yourself (RECENT/FOUNDATIONAL/MIXED). The subagent does that from the verbatim user request.
- Do NOT invent a date window. If the user said "современные / recent / latest" without a year, the subagent defaults to last 6 months — don't preempt it with "2024-2026".
- Do NOT drop the trigger words. The subagent relies on them to pick the right mode.
Call:
Agent(
subagent_type="deep-paper-researcher",
description="<3–5 word task>",
prompt="Today is 2026-04-22.\n\nUser's request: найди современные 10 статей про AI Code Review на arXiv.\n\n<optional: output format hints, language preference>"
# model: "opus" ← add only when the user opts in (see below)
)
The subagent's Freshness Mode section handles classification; keep this layer thin.
Model selection (Sonnet default, Opus on demand)
The subagent's model frontmatter is sonnet — that's the default.
Override to Opus by passing model: "opus" to the Agent tool only if the user explicitly requests deeper reasoning. Triggers (any of):
- English: "deep dive", "thorough", "rigorous", "use Opus", "high quality", "comprehensive", "exhaustive"
- Russian: "глубокий/глубже", "тщательный/тщательно", "подробно", "в режиме Опус/Opus", "максимально качественно", "серьёзный ресерч"
Never auto-upgrade to Opus without a user signal — Sonnet handles the default literature-review workflow fine and costs less.
When to Use
Trigger this skill for:
- Citation graph / network over a seed paper or topic
- Backward references (what does this paper cite?) — not covered by allenai
- Forward citations with pagination beyond 1000 results
- Recommendations — related-paper discovery from a seed
- Batch lookup — resolve 50-500 DOI/arXiv/CorpusId/S2 IDs in one call
- Snippet search — find specific passages across the S2 corpus
Do NOT use for:
- Simple "get paper by ID" or "who cited this" — use
semantic-scholar-lookup(faster, no Python) - Broad topical discovery — use
web_search_advanced_exawithcategory: "research paper"(Exa MCP) - Consumer-level literature questions — use the
deep-paper-researchersubagent, which orchestrates all three tools
Scripts
Located under ${SKILL_DIR}/scripts/.
ss_client.py — raw API client
Subcommands (all output JSON on stdout):
| Command | Endpoint | Notes |
|---|---|---|
search <query> |
/graph/v1/paper/search |
--bulk switches to /search/bulk (up to 1000/page) |
paper <id> |
/graph/v1/paper/{id} |
ID forms: raw, DOI:, ARXIV:, CorpusId:, PMID:, URL: |
citations <id> |
/graph/v1/paper/{id}/citations |
paginated; up to 1000 per page |
references <id> |
/graph/v1/paper/{id}/references |
paginated; up to 1000 per page |
recommendations <id> |
/recommendations/v1/papers/forpaper/{id} |
--pool recent|all-cs |
batch <id1> <id2> ... |
POST /graph/v1/paper/batch |
up to 500 IDs |
author-search <query> |
/graph/v1/author/search |
|
author <id> |
/graph/v1/author/{id} |
|
author-papers <id> |
/graph/v1/author/{id}/papers |
|
snippets <query> |
/graph/v1/snippet/search |
Full-text snippets |
Common flags: --limit, --offset, --fields, --year, --fields-of-study, --venue, --min-citation-count.
citation_graph.py — BFS traversal
python3 ${SKILL_DIR}/scripts/citation_graph.py <paperId> \
--direction both \
--depth 2 \
--max-nodes 200 \
--per-hop-limit 50 \
--output graph.json
Directions: forward (citations), backward (references), both. Output schema described in the script docstring — nodes: {paperId → metadata+depth}, edges: [{src, dst, direction}].
Authentication & Rate Limits
- Without API key: ~1 RPS shared, 100 queries/5min bursts. Fine for small graphs.
- With
SEMANTIC_SCHOLAR_API_KEYenv var: much higher limits. - Apply: https://www.semanticscholar.org/product/api#api-key
- The client does exponential backoff (1→30s) on HTTP 429/5xx, respects
Retry-After.
Progressive Disclosure
references/endpoints.md— complete field list per endpoint + query examplesreferences/workflows.md— lit-review, novelty-check, seed-expansion patterns
Output Hygiene
Scripts emit raw JSON — redirect to files for anything beyond ~20 results. For graphs >50 nodes always pass --output graph.json to avoid flooding the conversation context.
Integration
Typical pipeline inside the deep-paper-researcher subagent:
- Discovery —
mcp__exa__web_search_advanced_exa(neural + multi-source) - ID resolution —
ss_client.py search/batchto getpaperIdfrom titles or DOIs - Graph expansion —
citation_graph.pywith the top 3-5 seeds - Synthesis — distill nodes/edges into a ranked report
Optional: Bundled Subagent
A paired subagent definition ships alongside the skill at agents/deep-paper-researcher.md. It orchestrates Exa MCP + allenai semantic-scholar-lookup + this skill's scripts into a token-isolated research agent with:
- Mandatory input validation (today's date anchoring + caller-paraphrased-window detection)
- Freshness Mode classifier (RECENT / FOUNDATIONAL / MIXED)
- Sort-then-tiebreak ranking (never multiplies citations × recency into a single score)
- Compact report format with explicit
Anchor date/Mode/Windowheader
To install for Claude Code (manual, one-time):
cp ~/.agents/skills/semantic-scholar-deep/agents/deep-paper-researcher.md ~/.claude/agents/
(Path may differ on other agents — copy to the agent's subagents directory, then restart the session.)
Prerequisites for full pipeline: Exa MCP connected, allenai/asta-plugins@"Semantic Scholar Lookup" skill installed.
Files (ai-driven-development)
-
agents
-
deep-paper-researcher.md 10.4 KB
--- name: deep-paper-researcher description: "Token-isolated deep research agent for academic papers. Orchestrates Exa MCP (neural multi-source discovery), allenai's semantic-scholar-lookup skill (fast metadata + forward citations via asta CLI), and the semantic-scholar-deep skill (references, recommendations, batch, citation-graph BFS). Use when the user asks for a literature review, a citation graph around a seed paper, novelty checks, state-of-the-art surveys, or any multi-step paper research that would otherwise flood the main context. Returns a compact ranked report, not raw API output. MANDATORY when delegating: (1) include `Today is YYYY-MM-DD.` inline; (2) include the user's original request verbatim (keep trigger words like 'современные / recent / latest / seminal'). DO NOT paraphrase freshness words into date windows ('2024-2026', 'last 12-18 months', etc.) — the subagent classifies and chooses the window itself based on the verbatim request." tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, mcp__exa__web_search_advanced_exa model: sonnet --- You are a deep research specialist for academic papers. Your job is to take an open-ended research question and return a focused, trustworthy, ranked report — while keeping the caller's context clean. ## Input Validation (mandatory first step) Before any search, validate the caller's prompt: 1. **Today's date**: Find `Today is YYYY-MM-DD` in the caller's prompt. If absent, run `date -I` via Bash. Never guess from training data — AI/LLM fields move fast enough that a 12-month offset can make the report actively wrong. 2. **Caller-paraphrased window detection**: If the caller's prompt mentions BOTH a RECENT trigger word ("recent / latest / modern / new / SOTA / current / свежий / последний / современный / актуальный / новые") AND an explicit date window wider than 6 months (e.g. "2024-2026", "last 12-18 months", "past 2 years") **and** the original user phrase quoted in the prompt does NOT literally contain that range — treat it as caller over-translation. Ignore the caller's window, apply the 6-month default, flag the override in the report. 3. **Self-anchoring fallback**: If you had to run `date -I` yourself (step 1), say so in one line of the report so the caller knows the date wasn't passed in. ## Freshness Mode Pick exactly one mode from the caller's request before searching: - **RECENT** — caller's request contains "recent / latest / modern / new / state-of-the-art / SOTA / current" or Russian equivalents ("свежий / свежие / последний / последние / недавний / современный / актуальный / новые"). - Default date window: **today minus 6 months**. - Primary sort: **publication date descending**. Citation count is a **tiebreaker only**, never the primary key. - Explicit-window override: honor it **only if** the user literally mentioned a specific range ("since 2024", "last year", "Q1 2026", "в 2025"). Do NOT honor ranges that look like paraphrases from the caller (e.g. caller wrote "Prefer papers from 2024-2026" or "last 12-18 months" when the user said only "современные" / "recent" — that's a paraphrase, not a user-specified range). **Heuristic: any window wider than 6 months present alongside RECENT triggers is caller over-translation unless the user literally named that range.** Revert to the 6-month default, note the override in the report ("Window adjusted from caller's paraphrase '<their range>' to last 6 months because user request was just '<trigger word>'"). - **FOUNDATIONAL** — user said "seminal / foundational / classic / most-cited / highly-cited / canonical" or Russian ("классический / основополагающий / ключевой / самые цитируемые"). - No date floor. - Primary sort: **citation count descending**. - **MIXED** — ambiguous request with no freshness or foundational signal. - Return two ranked slices: top 5 by recency (within last 12 months), top 5 by all-time citations. - Do not blend them into one list — the two halves answer different questions. **Never multiply citation count with recency into a single score.** Log-citations grow to hundreds while a recency bonus caps at ~2–3×, so the older paper always wins. Use sort-then-tiebreak per the mode above. **Always state the mode and date window in the report** (see Output Format). Transparency lets the caller redirect if you chose wrong. ## Tool Stack You have three complementary sources. Use them in this order unless the task obviously needs only one: 1. **Exa MCP** — `mcp__exa__web_search_advanced_exa` with `category: "research paper"`. - Best for: initial discovery, recent/fresh work, multi-source (arXiv, OpenReview, PubMed, bioRxiv), semantic/neural matching when keywords are fuzzy. - Restrictions: `includeText` / `excludeText` accept **single-item arrays only**; put multiple terms in `query`. - Tune: `type: "deep"` for thorough, `"fast"` for ideation, `startPublishedDate: "YYYY-01-01"` for recency, `enableSummary: true` + `summaryQuery` for distilled abstracts. 2. **semantic-scholar-lookup skill** (`asta papers` CLI, installed via `npx skills add allenai/asta-plugins@...`). - Best for: fast targeted metadata lookup, forward citations (who cited paper X), author search, venue/year filtering. - Invoke via `Bash`: `asta papers get <id>`, `asta papers search <q>`, `asta papers citations <id>`, `asta papers author-search <name>`. - Honors `ASTA_TOOL_KEY` env var for rate limits. 3. **semantic-scholar-deep skill** (`~/.claude/skills/semantic-scholar-deep/scripts/`). - Best for: **references** (what a paper cites — NOT covered by allenai), **recommendations** (related papers), **batch** lookup of up to 500 IDs, **citation graph** BFS, **snippet** full-text search. - Invoke via `Bash`: ``` python3 ~/.claude/skills/semantic-scholar-deep/scripts/ss_client.py <subcommand> ... python3 ~/.claude/skills/semantic-scholar-deep/scripts/citation_graph.py <seed> --depth 2 --max-nodes 150 --output /tmp/graph.json ``` - Honors `SEMANTIC_SCHOLAR_API_KEY` env var for higher rate limits. - Reference docs: `~/.claude/skills/semantic-scholar-deep/references/endpoints.md` and `workflows.md`. ## Default Workflow Unless the user asks for something narrower: 1. **Scope** — parse the request into (a) topic keywords, (b) constraints (year, venue, domain), (c) output shape (review? graph? novelty check?). 2. **Discovery** — Exa query with `numResults: 15–25`, `type: "deep"`. Set `startPublishedDate` per Freshness Mode: RECENT → today-6mo (or explicit user window); FOUNDATIONAL → no floor (or a decade window); MIXED → run **two** queries, one fresh (last 12mo) and one all-time. Skim titles+summaries. 3. **Shortlist** — pick 5–10 most promising candidates by title relevance; extract DOI/arXiv/CorpusId from Exa URLs. 4. **Resolve** — `ss_client.py batch` on the extracted IDs to get `paperId`, `citationCount`, `year`, `tldr`, `venue`. 5. **Expand** — for top 3 seeds by citation count, run one of: - `ss_client.py recommendations <id> --limit 20` for related-work discovery - `ss_client.py references <id> --limit 30` for backward grounding - `citation_graph.py <id> --depth 2 --max-nodes 100 --output /tmp/graph_<id>.json` for network view 6. **De-duplicate + rank** — merge everything by `paperId`, then apply the mode-specific ordering: - RECENT: sort by `publicationDate` DESC; `citationCount` is tiebreaker only. Drop anything older than the window. - FOUNDATIONAL: sort by `citationCount` DESC; publication date is tiebreaker only. - MIXED: produce two separate slices (top 5 recent + top 5 foundational) — do not merge. Pick final top 10–15 total. 7. **Synthesize** — produce the report (see Output Format). Deviate from this only when the user's ask is simpler (e.g. "just build the graph around DOI:X" — go directly to step 5). ## Token Hygiene (Critical) - **Never print raw JSON from API calls into your response.** Save to `/tmp/<name>.json` via `--output` or shell redirection, then `jq`/`python3` for extraction. - **Cap per-call result sizes.** `--limit 30` for citations/references in discovery; bump only when doing graph traversal. - **Strip fields aggressively.** Default to `paperId,title,year,authors,citationCount,venue,tldr` unless a field is needed. (Use bare `authors`, not `authors.name` — nested projection is rejected on /citations and /references.) - **Summarize graphs, never dump them.** For `citation_graph.py` output, read the file with `jq` and emit only: hubs (top in-degree), influencers (top citationCount), clusters by year. ## Citation Discipline Cite only `paperId`, DOI, arXiv ID, or URL that you actually retrieved from S2/Exa/tools in this session. Never invent identifiers. ## Output Format Respond with a compact markdown report, ≤600 words unless the user explicitly asked for something long: ``` ## Research Report: <topic> **Anchor date:** <YYYY-MM-DD> · **Mode:** <RECENT | FOUNDATIONAL | MIXED> · **Window:** <YYYY-MM-DD> → <YYYY-MM-DD or "all-time"> ### Top Papers (ranked) 1. **<title>** — <authors, year>, <venue> — <citationCount> cites DOI: `10.xxxx` · S2: `<paperId>` · [TLDR] one-line summary Why it matters: <one sentence> 2. ... ### Citation Landscape (if graph built) - Hubs: <paperId-short:title> (N incoming edges) - Influencers: <title> (M total cites) - Temporal cluster: dense in <year range> ### Gaps / Observations - <one sentence each — methodology splits, under-explored angles, conflicting findings> ### Sources inspected - Exa: <N results, date window> - S2 batch: <N IDs resolved> - Graph: <N nodes, M edges> (if built) ``` Do not paste API responses. Do not paste code. Link to saved artifacts (`/tmp/graph.json`) only if the caller needs them. ## Failure Modes - **HTTP 429 from S2** — retry backoff is built-in; if it still fails after 5 tries, flag the limit and suggest setting `SEMANTIC_SCHOLAR_API_KEY`. - **Paper not in S2** — fall back to Exa-only for that entry; note it in the report. - **`asta` CLI not installed** — skip it, use `ss_client.py` directly. - **Exa rate limit / error** — note it; proceed with S2-only discovery via `ss_client.py search`. ## When Not to Use This Agent If the caller just needs a single paper lookup or one citation list, point them at the `semantic-scholar-lookup` skill directly — it's faster and doesn't need orchestration.
-
-
references
-
endpoints.md 4.7 KB
# Semantic Scholar API — Endpoint Reference ## Contents - [Paper IDs accepted everywhere](#paper-ids-accepted-everywhere) - [Paper Fields](#paper-fields-subset-pass-comma-separated-via-fields) - [`/paper/search`](#endpoint-papersearch) — keyword search - [`/paper/search/bulk`](#endpoint-papersearchbulk) — large sweeps - [`/paper/{id}`](#endpoint-paperpaper_id) — single paper - [`/paper/{id}/citations`](#endpoint-paperpaper_idcitations) — forward - [`/paper/{id}/references`](#endpoint-paperpaper_idreferences) — backward - [`POST /paper/batch`](#endpoint-post-paperbatch) — up to 500 IDs - [`/recommendations/v1/papers/forpaper/{id}`](#endpoint-recommendationsv1papersforpaperpaper_id) - [`/author/*`](#endpoint-authorsearch-authorid-authoridpapers) - [`/snippet/search`](#endpoint-snippetsearch) - [Citation contexts / intents](#citation-contexts-and-intents) - [Rate Limits](#rate-limits) Base URLs: - Graph API: `https://api.semanticscholar.org/graph/v1` - Recommendations: `https://api.semanticscholar.org/recommendations/v1` ## Paper IDs accepted everywhere | Form | Example | |------|---------| | S2 paper ID | `204e3073870fae3d05bcbc2f6a8e263d9b72e776` | | CorpusId | `CorpusId:215416146` | | DOI | `DOI:10.18653/v1/N18-3011` | | arXiv | `ARXIV:2106.15928` | | MAG | `MAG:112218234` | | ACL | `ACL:W12-3903` | | PubMed | `PMID:19872477` | | PubMed Central | `PMCID:2323736` | | URL | `URL:https://arxiv.org/abs/2106.15928v1` | ## Paper Fields (subset; pass comma-separated via `fields=`) - `paperId` - `externalIds` — `{DOI, ArXiv, CorpusId, MAG, ACL, PubMed, DBLP}` - `url`, `openAccessPdf` — `{url, status}` - `title`, `abstract`, `tldr` — `{model, text}` - `venue`, `publicationVenue`, `publicationDate`, `year` - `publicationTypes` — `JournalArticle | Conference | Review | ...` - `journal` — `{name, volume, pages}` - `authors` — list of `{authorId, name, affiliations, hIndex}` - `citationCount`, `referenceCount`, `influentialCitationCount` - `citationStyles` — `{bibtex}` - `fieldsOfStudy`, `s2FieldsOfStudy` - `embedding` — SPECTER / SPECTER2 (`--fields embedding.specter_v2`) ## Endpoint: `/paper/search` Query a keyword. Relevance-ranked, up to 100 per page. Query params: - `query` (required) - `limit`, `offset` (max `limit+offset` = 1000) - `fields` - `year` — `2019`, `2016-2020`, `-2015`, `2016-` - `venue` — e.g. `Nature,Radiology` - `fieldsOfStudy` — e.g. `Computer Science,Medicine` - `publicationTypes` — e.g. `Review,JournalArticle` - `openAccessPdf` — presence flag - `minCitationCount` — int - `publicationDateOrYear` — `YYYY-MM-DD:YYYY-MM-DD` ## Endpoint: `/paper/search/bulk` Up to 1000 per page, continuation via `token`. Sorted by relevance/year — use for large sweeps. ## Endpoint: `/paper/{paper_id}` Get single paper. All fields available. Use `fields=references.title,references.paperId` to embed shallow lists. ## Endpoint: `/paper/{paper_id}/citations` Forward citations. Returns `{data: [{citingPaper, contexts, intents, isInfluential}], offset, next}`. - `limit` up to 1000 - `fields` applied to the `citingPaper` sub-object ## Endpoint: `/paper/{paper_id}/references` Backward references. Same shape as citations but `citedPaper` instead of `citingPaper`. ## Endpoint: `POST /paper/batch` Body: `{"ids": ["<id>", ...]}` — up to 500 IDs per request. Query param: `fields` (comma-separated). Returns an array aligned with input order; entries can be `null` when unresolved. ## Endpoint: `/recommendations/v1/papers/forpaper/{paper_id}` Related papers. - `limit` up to 500 - `from=recent` (default) or `from=all-cs` - `fields` applied to recommended papers POST variant: `/recommendations/v1/papers` with `{positivePaperIds, negativePaperIds}` — better signal for curated seeds. ## Endpoint: `/author/search`, `/author/{id}`, `/author/{id}/papers` Author fields: `authorId`, `name`, `affiliations`, `aliases`, `homepage`, `paperCount`, `citationCount`, `hIndex`, `papers.{...}`. ## Endpoint: `/snippet/search` Full-text snippet search across the S2 corpus. Params: `query`, `limit`. Returns snippets with `section`, `text`, `paperId`, `score`. ## Citation `contexts` and `intents` When listing citations/references include `fields=contexts,intents,isInfluential`: - `contexts` — array of text snippets where citation appears - `intents` — `background | method | result` - `isInfluential` — boolean from S2's influence model These are the payload for fine-grained citation analysis (who cites *for what purpose*). ## Rate Limits - Anonymous: ~1 RPS shared, 100 req / 5min bursts. - Authenticated (`x-api-key` header): ~1 RPS per key, higher sustained throughput. - On `429` the server sets `Retry-After`. The client honors it with exponential fallback. -
workflows.md 4.1 KB
# Deep-Research Workflows ## Contents - [Workflow 1: Literature Review from a Topic](#workflow-1-literature-review-from-a-topic) - [Workflow 2: Citation Graph around a Seed Paper](#workflow-2-citation-graph-around-a-seed-paper) - [Workflow 3: Novelty Check for an Idea](#workflow-3-novelty-check-for-an-idea) - [Workflow 4: Author Trajectory](#workflow-4-author-trajectory) - [Workflow 5: Evidence for a Claim](#workflow-5-evidence-for-a-claim) - [Token Hygiene](#token-hygiene) - [Failure Modes](#failure-modes) ## Workflow 1: Literature Review from a Topic 1. **Discovery** via Exa MCP: ``` web_search_advanced_exa( query="retrieval augmented generation 2024", category="research paper", startPublishedDate="2024-01-01", numResults=20, type="deep", enableSummary=true ) ``` 2. Extract DOI/arXiv IDs from Exa results (domain URL parsing). 3. **Batch-resolve** to S2 IDs: ``` python3 ss_client.py batch DOI:10.1145/... ARXIV:2401.12345 ... \ --fields paperId,title,year,citationCount,tldr ``` 4. Sort by `citationCount`, pick top 5 seeds. 5. **Expand each seed** via recommendations: ``` python3 ss_client.py recommendations <seedId> --limit 30 ``` 6. Merge + de-dup by `paperId`. 7. Produce report: ranked list with year, venue, citation count, one-line TLDR. ## Workflow 2: Citation Graph around a Seed Paper Given a single anchor paper (user provides DOI or title): 1. Resolve to S2 ID: ``` python3 ss_client.py paper DOI:10.xxxx --fields paperId,title,year,citationCount ``` 2. Build the graph: ``` python3 citation_graph.py <paperId> \ --direction both --depth 2 --max-nodes 150 --output graph.json ``` 3. Analyze `graph.json`: - **Hubs** — nodes with highest in-degree (most-referenced by others in the graph) - **Influencers** — highest `citationCount` within the graph - **Clusters** — group by year or venue 4. Present top 10 papers per category + one-sentence rationale per pick. ## Workflow 3: Novelty Check for an Idea User describes an idea in ≤3 sentences; confirm prior art exists. 1. Extract 3-5 keyword groups from the description. 2. For each group, run: ``` python3 ss_client.py search "<group>" --limit 10 --year 2020- \ --min-citation-count 5 ``` 3. Deduplicate across groups by `paperId`. 4. For top 3 candidates by relevance+citations, pull references + citations: ``` python3 ss_client.py references <id> --limit 20 python3 ss_client.py citations <id> --limit 20 ``` 5. Report: "Closest prior work is X (year, citations) — overlap with the idea is [method/domain/etc]. Gap to user's idea: [...]". ## Workflow 4: Author Trajectory 1. `author-search` to resolve a name to `authorId`: ``` python3 ss_client.py author-search "Geoffrey Hinton" ``` 2. `author-papers` with `--limit 200` to pull career history. 3. Group by year; plot topical drift via `fieldsOfStudy`. ## Workflow 5: Evidence for a Claim 1. `snippets` to find exact passages: ``` python3 ss_client.py snippets "attention scales quadratically with sequence length" ``` 2. For each hit, fetch full paper metadata: ``` python3 ss_client.py paper <paperId> --fields paperId,title,year,venue,tldr,authors ``` 3. Rank by venue quality + recency. ## Token Hygiene - Never return raw `graph.json` (>50 nodes) into the main conversation — summarize or extract the top-N. - Always pipe large outputs through `--output` or redirect to files, not stdout-into-context. - When feeding back to main context, keep only: `paperId` (for stable linking), title, year, citation count, one-line why. ## Failure Modes | Symptom | Cause | Fix | |---------|-------|-----| | `HTTP 404` on paper lookup | wrong ID prefix | try `DOI:`, `ARXIV:`, `CorpusId:`, or URL form | | `HTTP 429` persistently | rate limit or missing key | set `SEMANTIC_SCHOLAR_API_KEY` env var | | Empty `data` for citations | paper too new or not cited yet | confirm with `paper <id>` that `citationCount > 0` | | `references.data[i].citedPaper == null` | external paper not in S2 corpus | filter nulls before traversal | | Graph explodes past `max-nodes` | seed is a very popular paper | lower `--per-hop-limit` and `--depth` |
-
-
scripts
-
citation_graph.py 4.8 KB
#!/usr/bin/env python3 """BFS traversal of the Semantic Scholar citation graph. Example: python3 citation_graph.py <paperId> --depth 2 --direction both --max-nodes 200 \ --output graph.json Output JSON schema: { "seed": "<paperId>", "direction": "forward|backward|both", "depth": 2, "nodes": { "<paperId>": { "paperId": "...", "title": "...", "year": 2022, "citationCount": 123, "authors": [{"name": "..."}], "venue": "...", "depth": 1 } }, "edges": [ {"src": "<paperId>", "dst": "<paperId>", "direction": "citation|reference"} ] } Directions: forward — follow *citations* (who cites this paper). Good for tracking impact. backward — follow *references* (what this paper cites). Good for literature grounding. both — union of both per hop. """ from __future__ import annotations import argparse import json import sys from collections import deque from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) import ss_client # type: ignore def _extract_neighbor(entry: dict, key: str) -> dict | None: node = entry.get(key) if not node or not isinstance(node, dict): return None if not node.get("paperId"): return None return node def traverse( seed: str, *, direction: str = "both", depth: int = 2, max_nodes: int = 200, per_hop_limit: int = 50, ) -> dict: assert direction in {"forward", "backward", "both"} seed_info = ss_client.paper( seed, fields="paperId,title,year,authors,citationCount,venue,externalIds", ) seed_id = seed_info["paperId"] nodes: dict[str, dict] = {seed_id: {**seed_info, "depth": 0}} edges: list[dict] = [] queue: deque[tuple[str, int]] = deque([(seed_id, 0)]) while queue and len(nodes) < max_nodes: paper_id, cur_depth = queue.popleft() if cur_depth >= depth: continue if direction in {"forward", "both"}: resp = ss_client.citations(paper_id, limit=per_hop_limit) for entry in resp.get("data", []): nb = _extract_neighbor(entry, "citingPaper") if not nb: continue edges.append({"src": nb["paperId"], "dst": paper_id, "direction": "citation"}) if nb["paperId"] not in nodes and len(nodes) < max_nodes: nodes[nb["paperId"]] = {**nb, "depth": cur_depth + 1} queue.append((nb["paperId"], cur_depth + 1)) if direction in {"backward", "both"} and len(nodes) < max_nodes: resp = ss_client.references(paper_id, limit=per_hop_limit) for entry in resp.get("data", []): nb = _extract_neighbor(entry, "citedPaper") if not nb: continue edges.append({"src": paper_id, "dst": nb["paperId"], "direction": "reference"}) if nb["paperId"] not in nodes and len(nodes) < max_nodes: nodes[nb["paperId"]] = {**nb, "depth": cur_depth + 1} queue.append((nb["paperId"], cur_depth + 1)) return { "seed": seed_id, "direction": direction, "depth": depth, "nodes": nodes, "edges": edges, "stats": { "total_nodes": len(nodes), "total_edges": len(edges), "truncated": len(nodes) >= max_nodes, }, } def _main(argv: list[str]) -> int: p = argparse.ArgumentParser(description="Citation graph BFS on Semantic Scholar") p.add_argument("seed", help="paperId (supports DOI:, ARXIV:, CorpusId: prefixes)") p.add_argument("--direction", choices=["forward", "backward", "both"], default="both") p.add_argument("--depth", type=int, default=2) p.add_argument("--max-nodes", type=int, default=200) p.add_argument("--per-hop-limit", type=int, default=50) p.add_argument("--output", help="write JSON here instead of stdout") args = p.parse_args(argv) try: graph = traverse( args.seed, direction=args.direction, depth=args.depth, max_nodes=args.max_nodes, per_hop_limit=args.per_hop_limit, ) except ss_client.SemanticScholarError as e: print(f"error: {e}", file=sys.stderr) return 1 dump = json.dumps(graph, ensure_ascii=False, indent=2) if args.output: Path(args.output).write_text(dump, encoding="utf-8") print( f"wrote {graph['stats']['total_nodes']} nodes, {graph['stats']['total_edges']} edges → {args.output}", file=sys.stderr, ) else: print(dump) return 0 if __name__ == "__main__": sys.exit(_main(sys.argv[1:])) -
ss_client.py 10.2 KB
#!/usr/bin/env python3 """Semantic Scholar Graph + Recommendations API client (stdlib only). CLI: python3 ss_client.py search "attention is all you need" --limit 10 python3 ss_client.py paper 204e3073870fae3d05bcbc2f6a8e263d9b72e776 python3 ss_client.py citations <paperId> --limit 100 python3 ss_client.py references <paperId> --limit 100 python3 ss_client.py recommendations <paperId> --limit 20 python3 ss_client.py batch <id1> <id2> ... --fields paperId,title,year,citationCount python3 ss_client.py author-search "Ashish Vaswani" python3 ss_client.py author <authorId> python3 ss_client.py snippets "retrieval augmented generation" Env: SEMANTIC_SCHOLAR_API_KEY — optional; higher rate limits when set. All commands print JSON to stdout. Non-zero exit on terminal errors. """ from __future__ import annotations import argparse import json import os import sys import time import urllib.error import urllib.parse import urllib.request from typing import Any GRAPH_BASE = "https://api.semanticscholar.org/graph/v1" RECS_BASE = "https://api.semanticscholar.org/recommendations/v1" DEFAULT_PAPER_FIELDS = ( "paperId,title,abstract,year,venue,authors,citationCount," "referenceCount,influentialCitationCount,externalIds,openAccessPdf,tldr" ) # Use bare `authors` (not `authors.name`): nested projection is rejected on # /citations and /references with HTTP 400 "Unrecognized or unsupported fields". LIGHT_PAPER_FIELDS = "paperId,title,year,authors,citationCount,venue" class SemanticScholarError(RuntimeError): pass def _headers() -> dict[str, str]: h = {"User-Agent": "semantic-scholar-deep/1.0"} api_key = os.environ.get("SEMANTIC_SCHOLAR_API_KEY") if api_key: h["x-api-key"] = api_key return h def _request( method: str, url: str, *, params: dict[str, Any] | None = None, json_body: Any = None, timeout: int = 30, max_retries: int = 5, ) -> Any: if params: cleaned = {k: v for k, v in params.items() if v is not None} if cleaned: url = f"{url}?{urllib.parse.urlencode(cleaned, doseq=True)}" data: bytes | None = None headers = _headers() if json_body is not None: data = json.dumps(json_body).encode("utf-8") headers["Content-Type"] = "application/json" req = urllib.request.Request(url, data=data, method=method, headers=headers) backoff = 1.0 for attempt in range(max_retries + 1): try: with urllib.request.urlopen(req, timeout=timeout) as resp: body = resp.read() return json.loads(body) if body else None except urllib.error.HTTPError as e: if e.code == 429 or 500 <= e.code < 600: if attempt == max_retries: raise SemanticScholarError( f"HTTP {e.code} after {max_retries} retries: {url}" ) from e retry_after = e.headers.get("Retry-After") wait = float(retry_after) if retry_after else backoff time.sleep(wait) backoff = min(backoff * 2, 30) continue raise SemanticScholarError(f"HTTP {e.code}: {e.read().decode(errors='replace')}") from e except urllib.error.URLError as e: if attempt == max_retries: raise SemanticScholarError(f"Network error: {e.reason}") from e time.sleep(backoff) backoff = min(backoff * 2, 30) def search( query: str, *, limit: int = 20, offset: int = 0, fields: str = DEFAULT_PAPER_FIELDS, year: str | None = None, fields_of_study: str | None = None, venue: str | None = None, min_citation_count: int | None = None, open_access_pdf: bool = False, bulk: bool = False, ) -> dict: path = "/paper/search/bulk" if bulk else "/paper/search" params = { "query": query, "limit": min(limit, 100) if not bulk else min(limit, 1000), "offset": offset, "fields": fields, "year": year, "fieldsOfStudy": fields_of_study, "venue": venue, "minCitationCount": min_citation_count, } if open_access_pdf: params["openAccessPdf"] = "" return _request("GET", f"{GRAPH_BASE}{path}", params=params) def paper(paper_id: str, *, fields: str = DEFAULT_PAPER_FIELDS) -> dict: return _request("GET", f"{GRAPH_BASE}/paper/{paper_id}", params={"fields": fields}) def citations( paper_id: str, *, limit: int = 100, offset: int = 0, fields: str = LIGHT_PAPER_FIELDS ) -> dict: return _request( "GET", f"{GRAPH_BASE}/paper/{paper_id}/citations", params={"limit": min(limit, 1000), "offset": offset, "fields": fields}, ) def references( paper_id: str, *, limit: int = 100, offset: int = 0, fields: str = LIGHT_PAPER_FIELDS ) -> dict: return _request( "GET", f"{GRAPH_BASE}/paper/{paper_id}/references", params={"limit": min(limit, 1000), "offset": offset, "fields": fields}, ) def recommendations( paper_id: str, *, limit: int = 100, fields: str = LIGHT_PAPER_FIELDS, pool: str = "recent" ) -> dict: return _request( "GET", f"{RECS_BASE}/papers/forpaper/{paper_id}", params={"limit": min(limit, 500), "fields": fields, "from": pool}, ) def batch(ids: list[str], *, fields: str = DEFAULT_PAPER_FIELDS) -> list: if len(ids) > 500: raise ValueError("batch supports up to 500 ids") return _request( "POST", f"{GRAPH_BASE}/paper/batch", params={"fields": fields}, json_body={"ids": ids}, ) def author_search(query: str, *, limit: int = 20, fields: str = "authorId,name,affiliations,paperCount,citationCount,hIndex") -> dict: return _request( "GET", f"{GRAPH_BASE}/author/search", params={"query": query, "limit": limit, "fields": fields}, ) def author(author_id: str, *, fields: str = "authorId,name,affiliations,paperCount,citationCount,hIndex") -> dict: return _request("GET", f"{GRAPH_BASE}/author/{author_id}", params={"fields": fields}) def author_papers( author_id: str, *, limit: int = 100, offset: int = 0, fields: str = LIGHT_PAPER_FIELDS ) -> dict: return _request( "GET", f"{GRAPH_BASE}/author/{author_id}/papers", params={"limit": limit, "offset": offset, "fields": fields}, ) def snippet_search(query: str, *, limit: int = 10) -> dict: return _request( "GET", f"{GRAPH_BASE}/snippet/search", params={"query": query, "limit": limit}, ) def _emit(obj: Any) -> None: json.dump(obj, sys.stdout, ensure_ascii=False, indent=2) sys.stdout.write("\n") def _main(argv: list[str]) -> int: p = argparse.ArgumentParser(description="Semantic Scholar API client") sub = p.add_subparsers(dest="cmd", required=True) sp = sub.add_parser("search") sp.add_argument("query") sp.add_argument("--limit", type=int, default=20) sp.add_argument("--offset", type=int, default=0) sp.add_argument("--fields", default=DEFAULT_PAPER_FIELDS) sp.add_argument("--year") sp.add_argument("--fields-of-study") sp.add_argument("--venue") sp.add_argument("--min-citation-count", type=int) sp.add_argument("--open-access-pdf", action="store_true") sp.add_argument("--bulk", action="store_true") pp = sub.add_parser("paper") pp.add_argument("paper_id") pp.add_argument("--fields", default=DEFAULT_PAPER_FIELDS) for name in ("citations", "references"): cp = sub.add_parser(name) cp.add_argument("paper_id") cp.add_argument("--limit", type=int, default=100) cp.add_argument("--offset", type=int, default=0) cp.add_argument("--fields", default=LIGHT_PAPER_FIELDS) rp = sub.add_parser("recommendations") rp.add_argument("paper_id") rp.add_argument("--limit", type=int, default=100) rp.add_argument("--fields", default=LIGHT_PAPER_FIELDS) rp.add_argument("--pool", choices=["recent", "all-cs"], default="recent") bp = sub.add_parser("batch") bp.add_argument("ids", nargs="+") bp.add_argument("--fields", default=DEFAULT_PAPER_FIELDS) asp = sub.add_parser("author-search") asp.add_argument("query") asp.add_argument("--limit", type=int, default=20) ap = sub.add_parser("author") ap.add_argument("author_id") app = sub.add_parser("author-papers") app.add_argument("author_id") app.add_argument("--limit", type=int, default=100) app.add_argument("--offset", type=int, default=0) snp = sub.add_parser("snippets") snp.add_argument("query") snp.add_argument("--limit", type=int, default=10) args = p.parse_args(argv) try: if args.cmd == "search": _emit(search( args.query, limit=args.limit, offset=args.offset, fields=args.fields, year=args.year, fields_of_study=args.fields_of_study, venue=args.venue, min_citation_count=args.min_citation_count, open_access_pdf=args.open_access_pdf, bulk=args.bulk, )) elif args.cmd == "paper": _emit(paper(args.paper_id, fields=args.fields)) elif args.cmd == "citations": _emit(citations(args.paper_id, limit=args.limit, offset=args.offset, fields=args.fields)) elif args.cmd == "references": _emit(references(args.paper_id, limit=args.limit, offset=args.offset, fields=args.fields)) elif args.cmd == "recommendations": _emit(recommendations(args.paper_id, limit=args.limit, fields=args.fields, pool=args.pool)) elif args.cmd == "batch": _emit(batch(args.ids, fields=args.fields)) elif args.cmd == "author-search": _emit(author_search(args.query, limit=args.limit)) elif args.cmd == "author": _emit(author(args.author_id)) elif args.cmd == "author-papers": _emit(author_papers(args.author_id, limit=args.limit, offset=args.offset)) elif args.cmd == "snippets": _emit(snippet_search(args.query, limit=args.limit)) else: p.error(f"unknown command: {args.cmd}") except SemanticScholarError as e: print(f"error: {e}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": sys.exit(_main(sys.argv[1:]))
-
-
README.md 5.9 KB
# semantic-scholar-deep Deep research over the Semantic Scholar Graph API. Covers the endpoints missing from the official [allenai/asta-plugins](https://github.com/allenai/asta-plugins) `semantic-scholar-lookup` skill — backward **references**, **recommendations**, **batch** lookup (up to 500 IDs per call), **snippet** full-text search, and multi-hop **citation-graph BFS**. ## Install ```bash npx skills add CodeAlive-AI/ai-driven-development@semantic-scholar-deep -g -y ``` Works anonymously out of the box — most endpoints (paper lookup, references, recommendations, batch) respond without an API key. The keyword `/paper/search` endpoint is heavily rate-limited on the anonymous tier, so for large sweeps set `SEMANTIC_SCHOLAR_API_KEY` (request one at <https://www.semanticscholar.org/product/api#api-key>). ## Prerequisites This skill stands alone for scripts-only usage (invoke the Python CLIs directly). For the full **deep-research pipeline** (orchestrated by the bundled subagent, see below), you also need: | Tool | Why | How | |------|-----|-----| | **Exa MCP** | Neural paper discovery across arXiv / OpenReview / PubMed / bioRxiv — the S2 `search` endpoint is too rate-limited to be the primary discovery path | `claude mcp add --transport http exa "https://mcp.exa.ai/mcp?tools=web_search_advanced_exa"` | | **allenai `semantic-scholar-lookup` skill** | First-party `asta papers` CLI for fast metadata + forward citations (complements our backward-references / recommendations coverage) | `npx skills add "allenai/asta-plugins@Semantic Scholar Lookup" -g -y` | | **Python 3.8+** | Scripts are stdlib-only, no pip install | Usually already present | Without the above, the bundled subagent falls back to what it can reach (S2-only), but Exa dramatically improves discovery quality and freshness. ## Quick start Inline usage (one specific endpoint): ``` > Get the references that DOI:10.18653/v1/N18-3011 cites > Recommend 20 papers related to this paperId > Batch-resolve these 30 arXiv IDs: 2404.18496 2502.02757 ... > Build a citation graph of depth 2 around paperId X ``` Delegated usage (multi-step research via the bundled subagent): ``` > Find recent papers on LLM code review > Do a literature review on retrieval-augmented generation since 2024 > Novelty check: is my idea <X> already published? ``` ## What it ships ### Python CLIs (`scripts/`) - **`ss_client.py`** — stdlib-only S2 client with exponential backoff on HTTP 429/5xx. Subcommands: `search`, `paper`, `citations`, `references`, `recommendations`, `batch`, `author-search`, `author`, `author-papers`, `snippets`. - **`citation_graph.py`** — BFS traversal around a seed paper. Options: `--direction forward|backward|both`, `--depth N`, `--max-nodes N`. Outputs JSON with `nodes` + `edges`, designed for summarization rather than in-context dumping. ### References (`references/`) - **`endpoints.md`** — complete field reference, query-parameter list, and ID-format matrix for every endpoint. - **`workflows.md`** — 5 ready-made deep-research patterns: literature review from a topic, citation graph around a seed, novelty check, author trajectory, evidence for a claim. ### Bundled subagent (`agents/deep-paper-researcher.md`, optional) A paired subagent definition for token-isolated research. Manually install once: ```bash cp ~/.agents/skills/semantic-scholar-deep/agents/deep-paper-researcher.md ~/.claude/agents/ ``` Then restart the session. Features: - Input validation — today's date anchoring + caller-paraphrased-window detection (catches cases where the calling agent translates "recent" into an invented "2024-2026" window) - Freshness Mode classifier — `RECENT` (default last 6 months, sort by publication date), `FOUNDATIONAL` (sort by citations, no date floor), `MIXED` (two slices side-by-side) - Sort-then-tiebreak ranking — never multiplies `citations × recency` into a single score, so fresh papers with zero citations aren't buried under older high-citation ones - Compact report format with explicit `Anchor date` / `Mode` / `Window` header — readers can see and redirect the choice ## Key design decisions - **Stdlib only** — no `requests` dependency. Works on any Python 3.8+ install. - **Backoff over hard-failure** — HTTP 429 / 5xx get exponential retry up to 30s, honoring `Retry-After`. Anonymous tier is usable for small graphs. - **Progressive disclosure** — `SKILL.md` stays under 150 lines; deep endpoint-by-endpoint docs and workflow templates live in `references/`. - **Token hygiene** — scripts emit raw JSON to stdout by design. For graphs >50 nodes, use `--output` to write to disk and summarize via `jq`/`python3` rather than piping full payloads into the agent's context. - **Not a discovery engine** — keyword `search` works but is the weakest endpoint on the anonymous tier; the bundled subagent delegates discovery to Exa MCP and uses S2 for structured expansion (references, recommendations, batch). ## Rate limits | Endpoint | Anonymous tier | With API key | |----------|----------------|--------------| | `paper` by ID | ~1 RPS, reliable | Much higher | | `references` / `recommendations` | ~1 RPS, reliable | Much higher | | `batch` (up to 500 IDs) | ~1 RPS | Much higher | | `search` (keyword) | Frequently 429 | Reliable | The client respects `Retry-After` and does up to 5 retries with exponential backoff (1→30s). ## File structure ``` semantic-scholar-deep/ ├── SKILL.md # Agent-facing instructions (dispatch rule, when to use) ├── README.md # This file ├── scripts/ │ ├── ss_client.py # Full S2 API client (stdlib only) │ └── citation_graph.py # BFS traversal ├── references/ │ ├── endpoints.md # Per-endpoint field and parameter reference │ └── workflows.md # 5 deep-research patterns └── agents/ └── deep-paper-researcher.md # Optional paired subagent definition ``` ## License MIT -
SKILL.md 8.8 KB
--- name: semantic-scholar-deep description: Deep research over the Semantic Scholar Graph API. Covers endpoints missing from allenai's lookup skill — paper references (backward citations), recommendations, batch paper lookup (up to 500 IDs), snippet search, and multi-hop citation graph traversal (BFS forward/backward). Use when the user asks to build a citation graph, expand a literature seed, find related work, run a reference network traversal, explore what a paper cites or what cites it beyond simple lookup, or batch-resolve many DOI/arXiv/S2 IDs. For multi-step research questions, delegate to the deep-paper-researcher subagent to keep the main context clean. Not for single paper-by-ID lookups (use semantic-scholar-lookup) or topical discovery (use web_search_advanced_exa). allowed-tools: Bash(python3:*), Read, Write, Edit, Glob, Grep, Agent --- # Semantic Scholar — Deep Research Purpose: fill the gaps that `semantic-scholar-lookup` (allenai) leaves — `references`, `recommendations`, `batch`, and multi-hop citation-graph traversal. ## Contents - [Dispatch Rule](#dispatch-rule-read-first) — inline vs delegate; model selection - [When to Use](#when-to-use) — trigger scenarios - [Scripts](#scripts) — `ss_client.py` + `citation_graph.py` - [Authentication & Rate Limits](#authentication--rate-limits) - [Progressive Disclosure](#progressive-disclosure) — deeper references - [Output Hygiene](#output-hygiene) - [Integration](#integration) — typical pipeline with the subagent ## Dispatch Rule (read first) Two execution modes: ### Inline (run the Bash scripts yourself) Use when the user asks for **one specific endpoint**: - "get references of paper X" → `ss_client.py references <id>` - "recommendations for paper Y" → `ss_client.py recommendations <id>` - "batch-resolve these 30 DOIs" → `ss_client.py batch ...` - "find the snippet where X is said" → `ss_client.py snippets "..."` Fast, cheap, no orchestration overhead. ### Delegate to `deep-paper-researcher` subagent Use when the task is **multi-step** or would otherwise flood the context: - Literature review on a topic - Citation graph / network analysis around a seed paper - Novelty check for an idea - State-of-the-art survey - Anything that requires merging Exa discovery + S2 graph + ranking **Mandatory prompt contents.** The subagent runs in isolated context with no access to this conversation's system reminders. Include exactly these two things: 1. **Today's date** — inline as `Today is YYYY-MM-DD.` Pull from the `currentDate` system-reminder field, or run `date -I` via Bash before delegating if it's missing. Never rely on training-data intuitions about the current year. 2. **User's request, verbatim** — pass the user's original phrasing (topic + any freshness words like "современные / recent / классические / seminal" and any explicit dates like "since 2024"). Translate language if needed but do not paraphrase trigger words into date windows. **Do NOT do any of these:** - Do NOT classify freshness yourself (RECENT/FOUNDATIONAL/MIXED). The subagent does that from the verbatim user request. - Do NOT invent a date window. If the user said "современные / recent / latest" without a year, the subagent defaults to last 6 months — don't preempt it with "2024-2026". - Do NOT drop the trigger words. The subagent relies on them to pick the right mode. Call: ``` Agent( subagent_type="deep-paper-researcher", description="<3–5 word task>", prompt="Today is 2026-04-22.\n\nUser's request: найди современные 10 статей про AI Code Review на arXiv.\n\n<optional: output format hints, language preference>" # model: "opus" ← add only when the user opts in (see below) ) ``` The subagent's Freshness Mode section handles classification; keep this layer thin. ### Model selection (Sonnet default, Opus on demand) The subagent's `model` frontmatter is `sonnet` — that's the default. Override to Opus by passing `model: "opus"` to the `Agent` tool **only if the user explicitly requests deeper reasoning**. Triggers (any of): - English: "deep dive", "thorough", "rigorous", "use Opus", "high quality", "comprehensive", "exhaustive" - Russian: "глубокий/глубже", "тщательный/тщательно", "подробно", "в режиме Опус/Opus", "максимально качественно", "серьёзный ресерч" Never auto-upgrade to Opus without a user signal — Sonnet handles the default literature-review workflow fine and costs less. ## When to Use Trigger this skill for: - **Citation graph / network** over a seed paper or topic - **Backward references** (what does this paper cite?) — *not* covered by allenai - **Forward citations** with pagination beyond 1000 results - **Recommendations** — related-paper discovery from a seed - **Batch lookup** — resolve 50-500 DOI/arXiv/CorpusId/S2 IDs in one call - **Snippet search** — find specific passages across the S2 corpus **Do NOT use** for: - Simple "get paper by ID" or "who cited this" — use `semantic-scholar-lookup` (faster, no Python) - Broad topical discovery — use `web_search_advanced_exa` with `category: "research paper"` (Exa MCP) - Consumer-level literature questions — use the `deep-paper-researcher` subagent, which orchestrates all three tools ## Scripts Located under `${SKILL_DIR}/scripts/`. ### `ss_client.py` — raw API client Subcommands (all output JSON on stdout): | Command | Endpoint | Notes | |---------|----------|-------| | `search <query>` | `/graph/v1/paper/search` | `--bulk` switches to `/search/bulk` (up to 1000/page) | | `paper <id>` | `/graph/v1/paper/{id}` | ID forms: raw, `DOI:`, `ARXIV:`, `CorpusId:`, `PMID:`, `URL:` | | `citations <id>` | `/graph/v1/paper/{id}/citations` | paginated; up to 1000 per page | | `references <id>` | `/graph/v1/paper/{id}/references` | paginated; up to 1000 per page | | `recommendations <id>` | `/recommendations/v1/papers/forpaper/{id}` | `--pool recent|all-cs` | | `batch <id1> <id2> ...` | `POST /graph/v1/paper/batch` | up to 500 IDs | | `author-search <query>` | `/graph/v1/author/search` | | | `author <id>` | `/graph/v1/author/{id}` | | | `author-papers <id>` | `/graph/v1/author/{id}/papers` | | | `snippets <query>` | `/graph/v1/snippet/search` | Full-text snippets | Common flags: `--limit`, `--offset`, `--fields`, `--year`, `--fields-of-study`, `--venue`, `--min-citation-count`. ### `citation_graph.py` — BFS traversal ``` python3 ${SKILL_DIR}/scripts/citation_graph.py <paperId> \ --direction both \ --depth 2 \ --max-nodes 200 \ --per-hop-limit 50 \ --output graph.json ``` Directions: `forward` (citations), `backward` (references), `both`. Output schema described in the script docstring — `nodes: {paperId → metadata+depth}`, `edges: [{src, dst, direction}]`. ## Authentication & Rate Limits - Without API key: ~1 RPS shared, 100 queries/5min bursts. Fine for small graphs. - With `SEMANTIC_SCHOLAR_API_KEY` env var: much higher limits. - Apply: https://www.semanticscholar.org/product/api#api-key - The client does exponential backoff (1→30s) on HTTP 429/5xx, respects `Retry-After`. ## Progressive Disclosure - `references/endpoints.md` — complete field list per endpoint + query examples - `references/workflows.md` — lit-review, novelty-check, seed-expansion patterns ## Output Hygiene Scripts emit raw JSON — redirect to files for anything beyond ~20 results. For graphs >50 nodes always pass `--output graph.json` to avoid flooding the conversation context. ## Integration Typical pipeline inside the `deep-paper-researcher` subagent: 1. **Discovery** — `mcp__exa__web_search_advanced_exa` (neural + multi-source) 2. **ID resolution** — `ss_client.py search` / `batch` to get `paperId` from titles or DOIs 3. **Graph expansion** — `citation_graph.py` with the top 3-5 seeds 4. **Synthesis** — distill nodes/edges into a ranked report ## Optional: Bundled Subagent A paired subagent definition ships alongside the skill at `agents/deep-paper-researcher.md`. It orchestrates Exa MCP + allenai `semantic-scholar-lookup` + this skill's scripts into a token-isolated research agent with: - Mandatory input validation (today's date anchoring + caller-paraphrased-window detection) - Freshness Mode classifier (RECENT / FOUNDATIONAL / MIXED) - Sort-then-tiebreak ranking (never multiplies citations × recency into a single score) - Compact report format with explicit `Anchor date` / `Mode` / `Window` header To install for Claude Code (manual, one-time): ```bash cp ~/.agents/skills/semantic-scholar-deep/agents/deep-paper-researcher.md ~/.claude/agents/ ``` (Path may differ on other agents — copy to the agent's subagents directory, then restart the session.) Prerequisites for full pipeline: Exa MCP connected, `allenai/asta-plugins@"Semantic Scholar Lookup"` skill installed.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.