{"record":{"id":"208cfc13301d407b","repo":"datawhalechina/hello-agents","slug":"api-429-too-many-requests-nsemantic-sch","errorCode":null,"errorMessage":"API 请求频率已达上限（429 Too Many Requests）。\\nSemantic Scholar 免费额度为 100 次/5 分钟。\\n请稍等 1-5 分钟后重试，或申请免费 API Key：\\nhttps://www.semanticscholar.org/product/api\\n获取后在 .env 中设置 SEMANTIC_SCHOLAR_API_KEY","messagePattern":"API 请求频率已达上限（429 Too Many Requests）。\\\\nSemantic Scholar 免费额度为 100 次/5 分钟。\\\\n请稍等 1-5 分钟后重试，或申请免费 API Key：\\\\nhttps://www\\.semanticscholar\\.org/product/api\\\\n获取后在 \\.env 中设置 SEMANTIC_SCHOLAR_API_KEY","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"Co-creation-projects/chengH425-PaperAssistant/src/literature_tool.py","lineNumber":209,"sourceCode":"                    headers={\n                        \"User-Agent\": \"PaperAssistant/1.0\",\n                        \"Accept\": \"application/json\"\n                    }\n                )\n                if api_key:\n                    req.add_header(\"x-api-key\", api_key)\n\n                with urllib.request.urlopen(req, timeout=20) as resp:\n                    return json.loads(resp.read().decode(\"utf-8\"))\n\n            except urllib.error.HTTPError as e:\n                if e.code == 429:\n                    # 速率限制：等待后重试\n                    wait = 2 ** (attempt + 1)  # 2s, 4s, 8s\n                    if attempt < max_retries - 1:\n                        time.sleep(wait)\n                        continue\n                    raise RuntimeError(\n                        \"API 请求频率已达上限（429 Too Many Requests）。\\n\"\n                        \"Semantic Scholar 免费额度为 100 次/5 分钟。\\n\"\n                        \"请稍等 1-5 分钟后重试，或申请免费 API Key：\\n\"\n                        \"https://www.semanticscholar.org/product/api\\n\"\n                        \"获取后在 .env 中设置 SEMANTIC_SCHOLAR_API_KEY\"\n                    ) from e\n                raise RuntimeError(\n                    f\"Semantic Scholar API 返回 HTTP {e.code}: {e.reason}\"\n                ) from e\n            except urllib.error.URLError as e:\n                last_error = e\n                if attempt < max_retries - 1:\n                    time.sleep(2 ** (attempt + 1))\n                    continue\n                raise RuntimeError(f\"网络连接失败: {str(e.reason)}\") from e\n\n        raise RuntimeError(f\"请求失败（已重试 {max_retries} 次）: {last_error}\")\n","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/chengH425-PaperAssistant/src/literature_tool.py#L191-L227","documentation":"The Semantic Scholar literature tool raises RuntimeError with HTTP 429 context after its retry loop (max_retries attempts with 2s/4s/8s exponential backoff) still receives 429 responses. The unauthenticated free tier is limited to 100 requests per 5-minute window, so sustained querying exhausts it.","triggerScenarios":"Running multiple `literature_search` tool calls in quick succession (batch literature reviews, agent loops issuing several searches per turn) exceeds 100 requests/5 min without an API key; retries with short backoff fire inside the same throttled window and also get 429'd.","commonSituations":"Agent-driven workflows that fan out many keyword searches; shared IP/NAT with other users burning the same quota; missing `SEMANTIC_SCHOLAR_API_KEY` in .env.","solutions":["Request a free API key at https://www.semanticscholar.org/product/api and set `SEMANTIC_SCHOLAR_API_KEY` in .env.","Wait 1-5 minutes for the 5-minute window to reset before retrying.","Reduce query volume: batch fewer keywords per turn, raise `max_results` instead of issuing many narrow queries.","Longer term: increase the backoff ceiling (e.g. 30-60s) so retries can cross the rate-limit window."],"exampleFix":"// before\nwait = 2 ** (attempt + 1)  # 2s, 4s, 8s\n\n# after\nwait = min(2 ** (attempt + 1), 60)  # cap backoff so retries can outlast the 5-min window","handlingStrategy":"retry","validationCode":"import time\n\ndef rate_budget_ok(call_count: list, window_start: list, limit: int = 95, window_s: int = 300) -> bool:\n    \"\"\"Track client-side request count against the 100/5min free-tier budget.\"\"\"\n    now = time.time()\n    if now - window_start[0] > window_s:\n        window_start[0], call_count[0] = now, 0\n    return call_count[0] < limit","typeGuard":null,"tryCatchPattern":"try:\n    results = lit_tool.run({\"keyword\": kw})\nexcept RuntimeError as e:\n    if \"429\" in str(e):\n        time.sleep(300)  # let the 5-minute window fully reset\n        results = lit_tool.run({\"keyword\": kw})\n    else:\n        raise","preventionTips":["Set SEMANTIC_SCHOLAR_API_KEY in .env to raise the quota.","Keep a client-side counter under 100 requests per 5 minutes (leave headroom for retries).","Prefer one broad query with max_results=20 over many narrow queries."],"tags":["rate-limit","http-429","semantic-scholar","retry","network","python"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}