opencode-bioresearcher 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agents/bioresearcher-dr-worker.md +24 -12
- package/connector-meta.json +1 -1
- package/index.js +2 -2
- package/package.json +1 -1
- package/skills/bioresearcher-deep-research/SKILL.md +57 -11
- package/skills/bioresearcher-deep-research/references/article-literature.md +24 -0
- package/skills/bioresearcher-deep-research/references/best-practices.md +17 -1
- package/skills/bioresearcher-deep-research/references/citations.md +16 -4
- package/skills/bioresearcher-deep-research/references/report-template.md +2 -0
- package/skills/bioresearcher-deep-research/references/tool-selection.md +1 -1
- package/skills/bioresearcher-deep-research/references/utility-config.md +1 -1
- package/skills/bioresearcher-deep-research/references/worker-protocol.md +73 -14
- package/skills/bioresearcher-deep-research/scripts/evidence-ledger.py +1412 -0
- package/skills/bioresearcher-deep-research/scripts/ncbi_esummary.py +86 -0
- package/skills/bioresearcher-deep-research/scripts/vet-references.py +2 -71
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Shared NCBI PubMed esummary client for the bioresearcher-deep-research scripts.
|
|
3
|
+
|
|
4
|
+
Extracted verbatim from vet-references.py (DRY): both vet-references.py and
|
|
5
|
+
evidence-ledger.py import fetch_ncbi_summaries from here.
|
|
6
|
+
|
|
7
|
+
Zero external dependencies (pure Python standard library). Fail-safe: on
|
|
8
|
+
network or API failure, returns whatever was fetched (possibly {}); callers
|
|
9
|
+
treat missing records as "unverified" and never block on network errors.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
import urllib.error
|
|
17
|
+
import urllib.parse
|
|
18
|
+
import urllib.request
|
|
19
|
+
|
|
20
|
+
NCBI_ESUMMARY_URL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi"
|
|
21
|
+
USER_AGENT = "bioresearcher-skills/1.2 (evidence tools; +https://github.com/bioresearcher-agent)"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def fetch_ncbi_summaries(pmids: list, timeout: float = 15.0) -> dict:
|
|
25
|
+
"""Fetch esummary JSON for a list of PMIDs with rate-limiting and exponential backoff."""
|
|
26
|
+
if not pmids:
|
|
27
|
+
return {}
|
|
28
|
+
|
|
29
|
+
api_key = os.environ.get("NCBI_API_KEY", "").strip()
|
|
30
|
+
email = os.environ.get("NCBI_EMAIL", "bioresearcher-agent@noreply.github.com").strip()
|
|
31
|
+
min_interval = 0.100 if api_key else 0.334
|
|
32
|
+
|
|
33
|
+
results: dict = {}
|
|
34
|
+
batch_size = 100
|
|
35
|
+
|
|
36
|
+
for i in range(0, len(pmids), batch_size):
|
|
37
|
+
if i > 0:
|
|
38
|
+
time.sleep(min_interval)
|
|
39
|
+
|
|
40
|
+
batch = pmids[i : i + batch_size]
|
|
41
|
+
params = {
|
|
42
|
+
"db": "pubmed",
|
|
43
|
+
"id": ",".join(batch),
|
|
44
|
+
"retmode": "json",
|
|
45
|
+
"tool": "bioresearcher",
|
|
46
|
+
"email": email,
|
|
47
|
+
}
|
|
48
|
+
if api_key:
|
|
49
|
+
params["api_key"] = api_key
|
|
50
|
+
|
|
51
|
+
data = urllib.parse.urlencode(params).encode("utf-8")
|
|
52
|
+
req = urllib.request.Request(
|
|
53
|
+
NCBI_ESUMMARY_URL,
|
|
54
|
+
data=data,
|
|
55
|
+
headers={"User-Agent": USER_AGENT},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
for attempt in range(4):
|
|
59
|
+
try:
|
|
60
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
61
|
+
payload = json.loads(resp.read().decode("utf-8"))
|
|
62
|
+
result_data = payload.get("result", {})
|
|
63
|
+
for uid in batch:
|
|
64
|
+
doc = result_data.get(uid)
|
|
65
|
+
if doc and isinstance(doc, dict) and "error" not in doc:
|
|
66
|
+
results[uid] = doc
|
|
67
|
+
break
|
|
68
|
+
except urllib.error.HTTPError as e:
|
|
69
|
+
if e.code in (429, 500, 502, 503, 504) and attempt < 3:
|
|
70
|
+
retry_after_hdr = e.headers.get("Retry-After")
|
|
71
|
+
try:
|
|
72
|
+
retry_after = float(retry_after_hdr) if retry_after_hdr else float(1.5 * (2**attempt))
|
|
73
|
+
except ValueError:
|
|
74
|
+
retry_after = float(1.5 * (2**attempt))
|
|
75
|
+
time.sleep(retry_after)
|
|
76
|
+
continue
|
|
77
|
+
sys.stderr.write(f"[ncbi-esummary] HTTP {e.code} querying NCBI for batch {i}: {e.reason}\n")
|
|
78
|
+
break
|
|
79
|
+
except Exception as e:
|
|
80
|
+
if attempt < 3:
|
|
81
|
+
time.sleep(1.0 * (2**attempt))
|
|
82
|
+
continue
|
|
83
|
+
sys.stderr.write(f"[ncbi-esummary] Network error querying NCBI: {e}\n")
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
return results
|
|
@@ -16,14 +16,10 @@ import json
|
|
|
16
16
|
import os
|
|
17
17
|
import re
|
|
18
18
|
import sys
|
|
19
|
-
import time
|
|
20
|
-
import urllib.error
|
|
21
|
-
import urllib.parse
|
|
22
|
-
import urllib.request
|
|
23
19
|
from pathlib import Path
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
22
|
+
from ncbi_esummary import fetch_ncbi_summaries # noqa: E402,F401 (shared module, extracted verbatim)
|
|
27
23
|
|
|
28
24
|
REF_SECTION_RE = re.compile(
|
|
29
25
|
r'(?m)^(#{2,3}\s+(?:\d+[\.\s]+)?(?:References?|Bibliography|Literature Cited|Citations)\b.*?)(?=\n#{1,2}\s+|\Z)',
|
|
@@ -37,71 +33,6 @@ PMID_RE = re.compile(r'\bPMID[:\s]+\[?(\d{4,9})\]?', re.IGNORECASE)
|
|
|
37
33
|
DOI_RE = re.compile(r'(?:DOI[:\s]+|https?://(?:dx\.)?doi\.org/)?\b(10\.\d{4,9}/[^\s\]\)]+)', re.IGNORECASE)
|
|
38
34
|
|
|
39
35
|
|
|
40
|
-
def fetch_ncbi_summaries(pmids: list[str], timeout: float = 15.0) -> dict[str, dict]:
|
|
41
|
-
"""Fetch esummary JSON for a list of PMIDs with rate-limiting and exponential backoff."""
|
|
42
|
-
if not pmids:
|
|
43
|
-
return {}
|
|
44
|
-
|
|
45
|
-
api_key = os.environ.get("NCBI_API_KEY", "").strip()
|
|
46
|
-
email = os.environ.get("NCBI_EMAIL", "bioresearcher-agent@noreply.github.com").strip()
|
|
47
|
-
min_interval = 0.100 if api_key else 0.334
|
|
48
|
-
|
|
49
|
-
results: dict[str, dict] = {}
|
|
50
|
-
batch_size = 100
|
|
51
|
-
|
|
52
|
-
for i in range(0, len(pmids), batch_size):
|
|
53
|
-
if i > 0:
|
|
54
|
-
time.sleep(min_interval)
|
|
55
|
-
|
|
56
|
-
batch = pmids[i : i + batch_size]
|
|
57
|
-
params = {
|
|
58
|
-
"db": "pubmed",
|
|
59
|
-
"id": ",".join(batch),
|
|
60
|
-
"retmode": "json",
|
|
61
|
-
"tool": "bioresearcher",
|
|
62
|
-
"email": email,
|
|
63
|
-
}
|
|
64
|
-
if api_key:
|
|
65
|
-
params["api_key"] = api_key
|
|
66
|
-
|
|
67
|
-
data = urllib.parse.urlencode(params).encode("utf-8")
|
|
68
|
-
req = urllib.request.Request(
|
|
69
|
-
NCBI_ESUMMARY_URL,
|
|
70
|
-
data=data,
|
|
71
|
-
headers={"User-Agent": USER_AGENT},
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
for attempt in range(4):
|
|
75
|
-
try:
|
|
76
|
-
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
77
|
-
payload = json.loads(resp.read().decode("utf-8"))
|
|
78
|
-
result_data = payload.get("result", {})
|
|
79
|
-
for uid in batch:
|
|
80
|
-
doc = result_data.get(uid)
|
|
81
|
-
if doc and isinstance(doc, dict) and "error" not in doc:
|
|
82
|
-
results[uid] = doc
|
|
83
|
-
break
|
|
84
|
-
except urllib.error.HTTPError as e:
|
|
85
|
-
if e.code in (429, 500, 502, 503, 504) and attempt < 3:
|
|
86
|
-
retry_after_hdr = e.headers.get("Retry-After")
|
|
87
|
-
try:
|
|
88
|
-
retry_after = float(retry_after_hdr) if retry_after_hdr else float(1.5 * (2**attempt))
|
|
89
|
-
except ValueError:
|
|
90
|
-
retry_after = float(1.5 * (2**attempt))
|
|
91
|
-
time.sleep(retry_after)
|
|
92
|
-
continue
|
|
93
|
-
sys.stderr.write(f"[vet-references] HTTP {e.code} querying NCBI for batch {i}: {e.reason}\n")
|
|
94
|
-
break
|
|
95
|
-
except Exception as e:
|
|
96
|
-
if attempt < 3:
|
|
97
|
-
time.sleep(1.0 * (2**attempt))
|
|
98
|
-
continue
|
|
99
|
-
sys.stderr.write(f"[vet-references] Network error querying NCBI: {e}\n")
|
|
100
|
-
break
|
|
101
|
-
|
|
102
|
-
return results
|
|
103
|
-
|
|
104
|
-
|
|
105
36
|
def build_pub_locator(doc: dict) -> str:
|
|
106
37
|
"""Build canonical Year;Volume(Issue):Pages string from NCBI esummary."""
|
|
107
38
|
pubdate = doc.get("pubdate", "")
|