Spaces:
Sleeping
Sleeping
KevinIsInCoding Claude Sonnet 4.6 commited on
Commit ·
0569d05
1
Parent(s): ac8f675
feat(trials): replace hardcoded entity regex with LLM extraction
Browse files- Remove _KNOWN_TARGETS regex list — cannot handle novel compounds like AMX0114
- Add extract_target_entities_llm(): batches 10 trials per Claude Haiku call,
extracts biological target (gene/protein) not drug name, canonicalizes via
extraction/normalizer. AMX0114 now correctly maps to TARDBP.
- Add data/tools/extract_trial_targets.json tool schema
- Add TRIAL_EXTRACTION_TOOLS export to tools.py
- Widen default status filter from RECRUITING-only to also include
NOT_YET_RECRUITING and ACTIVE_NOT_RECRUITING
- ingest_trials.py: --status now accepts multiple values; creates Anthropic
client and passes it to fetch_als_trials()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- data/tools/extract_trial_targets.json +35 -0
- ingestion/clinicaltrials.py +118 -43
- scripts/ingest_trials.py +15 -5
- tools.py +10 -0
data/tools/extract_trial_targets.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"type": "object",
|
| 3 |
+
"properties": {
|
| 4 |
+
"nct_id": {
|
| 5 |
+
"type": "string",
|
| 6 |
+
"description": "The ClinicalTrials.gov identifier (e.g. NCT12345678)."
|
| 7 |
+
},
|
| 8 |
+
"targets": {
|
| 9 |
+
"type": "array",
|
| 10 |
+
"description": "Primary biological targets modulated or studied in this trial.",
|
| 11 |
+
"items": {
|
| 12 |
+
"type": "object",
|
| 13 |
+
"properties": {
|
| 14 |
+
"name": {
|
| 15 |
+
"type": "string",
|
| 16 |
+
"description": "Standard name of the target (e.g. TARDBP, SOD1, riluzole, neuroinflammation). Use canonical gene symbols where applicable."
|
| 17 |
+
},
|
| 18 |
+
"type": {
|
| 19 |
+
"type": "string",
|
| 20 |
+
"enum": ["Gene", "Protein", "Compound", "Mechanism"],
|
| 21 |
+
"description": "Category of the target."
|
| 22 |
+
},
|
| 23 |
+
"confidence": {
|
| 24 |
+
"type": "number",
|
| 25 |
+
"minimum": 0.0,
|
| 26 |
+
"maximum": 1.0,
|
| 27 |
+
"description": "Confidence that this is the intended biological target (1.0 = explicit in text)."
|
| 28 |
+
}
|
| 29 |
+
},
|
| 30 |
+
"required": ["name", "type", "confidence"]
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
"required": ["nct_id", "targets"]
|
| 35 |
+
}
|
ingestion/clinicaltrials.py
CHANGED
|
@@ -1,47 +1,42 @@
|
|
| 1 |
"""ClinicalTrials.gov v2 client for ALS trials (adapted from beacon/trials_api.py)."""
|
| 2 |
from __future__ import annotations
|
| 3 |
|
| 4 |
-
import re
|
| 5 |
import time
|
|
|
|
| 6 |
|
| 7 |
import httpx
|
| 8 |
|
| 9 |
-
from config import CTGOV_BASE
|
| 10 |
from logging_config import get_logger
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
_logger = get_logger("ingestion.clinicaltrials")
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
(
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
(re.compile(r"\bmexiletine\b", re.IGNORECASE), "mexiletine"),
|
| 32 |
-
(re.compile(r"\bantisense oligonucleotide\b", re.IGNORECASE), "antisense oligonucleotide"),
|
| 33 |
-
(re.compile(r"\bASO\b"), "antisense oligonucleotide"),
|
| 34 |
-
(re.compile(r"\bsiRNA\b", re.IGNORECASE), "siRNA"),
|
| 35 |
-
(re.compile(r"\bstem cell\b", re.IGNORECASE), "stem cell"),
|
| 36 |
-
(re.compile(r"\bgene therapy\b", re.IGNORECASE), "gene therapy"),
|
| 37 |
-
]
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
def fetch_als_trials(status: str = "RECRUITING") -> list[dict]:
|
| 41 |
"""Fetch ALS interventional trials. Returns flat dicts ready for JSONL serialization."""
|
|
|
|
|
|
|
| 42 |
params: dict[str, str | int] = {
|
| 43 |
"query.cond": "Amyotrophic Lateral Sclerosis",
|
| 44 |
-
"filter.overallStatus":
|
| 45 |
"aggFilters": "studyType:int",
|
| 46 |
"pageSize": 1000,
|
| 47 |
"format": "json",
|
|
@@ -58,9 +53,8 @@ def fetch_als_trials(status: str = "RECRUITING") -> list[dict]:
|
|
| 58 |
except httpx.HTTPError as exc:
|
| 59 |
if attempt == 2:
|
| 60 |
raise
|
| 61 |
-
wait = 2 ** attempt
|
| 62 |
_logger.warning(f"ClinicalTrials.gov error (attempt {attempt + 1}): {exc}")
|
| 63 |
-
time.sleep(
|
| 64 |
|
| 65 |
page_studies = body.get("studies", [])
|
| 66 |
all_studies.extend(page_studies)
|
|
@@ -74,6 +68,10 @@ def fetch_als_trials(status: str = "RECRUITING") -> list[dict]:
|
|
| 74 |
params["pageToken"] = next_token
|
| 75 |
|
| 76 |
trials = [_flatten_trial(s) for s in all_studies]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
_logger.info("ALS trial fetch complete", extra={"data": {"total": len(trials)}})
|
| 78 |
return trials
|
| 79 |
|
|
@@ -88,16 +86,14 @@ def _flatten_trial(study: dict) -> dict:
|
|
| 88 |
status_mod = proto.get("statusModule", {})
|
| 89 |
|
| 90 |
nct_id = id_mod.get("nctId", "")
|
| 91 |
-
title = id_mod.get("briefTitle", "")
|
| 92 |
interventions = [
|
| 93 |
{"type": iv.get("type", ""), "name": iv.get("name", "")}
|
| 94 |
for iv in arms_mod.get("interventions", [])
|
| 95 |
]
|
| 96 |
-
intervention_names = " ".join(iv["name"] for iv in interventions)
|
| 97 |
|
| 98 |
return {
|
| 99 |
"nct_id": nct_id,
|
| 100 |
-
"title":
|
| 101 |
"phase": ", ".join(design_mod.get("phases", [])) or "N/A",
|
| 102 |
"status": status_mod.get("overallStatus", ""),
|
| 103 |
"sponsor": sponsor_mod.get("leadSponsor", {}).get("name", ""),
|
|
@@ -105,14 +101,93 @@ def _flatten_trial(study: dict) -> dict:
|
|
| 105 |
"interventions": interventions,
|
| 106 |
"start_date": status_mod.get("startDateStruct", {}).get("date", ""),
|
| 107 |
"url": f"https://clinicaltrials.gov/study/{nct_id}" if nct_id else "",
|
| 108 |
-
"target_entities":
|
| 109 |
}
|
| 110 |
|
| 111 |
|
| 112 |
-
def
|
| 113 |
-
"""
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
"""ClinicalTrials.gov v2 client for ALS trials (adapted from beacon/trials_api.py)."""
|
| 2 |
from __future__ import annotations
|
| 3 |
|
|
|
|
| 4 |
import time
|
| 5 |
+
from typing import TYPE_CHECKING
|
| 6 |
|
| 7 |
import httpx
|
| 8 |
|
| 9 |
+
from config import CTGOV_BASE, EXTRACTION_MODEL
|
| 10 |
from logging_config import get_logger
|
| 11 |
|
| 12 |
+
if TYPE_CHECKING:
|
| 13 |
+
import anthropic
|
| 14 |
+
|
| 15 |
_logger = get_logger("ingestion.clinicaltrials")
|
| 16 |
|
| 17 |
+
_TRIAL_BATCH_SIZE = 10
|
| 18 |
+
|
| 19 |
+
_TRIAL_EXTRACTION_SYSTEM = """You are a biomedical NLP expert specializing in ALS (amyotrophic lateral sclerosis) clinical trials.
|
| 20 |
+
|
| 21 |
+
For each trial provided, identify the primary biological target(s) being tested or modulated:
|
| 22 |
+
- Genes silenced or corrected (e.g., SOD1, TARDBP, FUS, C9orf72, NEK1, VCP, TBK1)
|
| 23 |
+
- Proteins targeted (use canonical gene symbol, e.g. TARDBP for TDP-43 protein)
|
| 24 |
+
- Compounds/drugs — report the molecular or pathway target, not the drug name (e.g., a trial of AMX0114 targets TARDBP)
|
| 25 |
+
- Mechanisms (e.g., neuroinflammation, oxidative stress, glutamate excitotoxicity)
|
| 26 |
+
|
| 27 |
+
Call extract_trial_targets once per trial. Return an empty targets list only when no specific molecular or mechanistic target is identifiable."""
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def fetch_als_trials(
|
| 31 |
+
status: str | list[str] = ("RECRUITING", "NOT_YET_RECRUITING", "ACTIVE_NOT_RECRUITING"),
|
| 32 |
+
client: "anthropic.Anthropic | None" = None,
|
| 33 |
+
) -> list[dict]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
"""Fetch ALS interventional trials. Returns flat dicts ready for JSONL serialization."""
|
| 35 |
+
status_filter = ",".join(status) if isinstance(status, (list, tuple)) else status
|
| 36 |
+
|
| 37 |
params: dict[str, str | int] = {
|
| 38 |
"query.cond": "Amyotrophic Lateral Sclerosis",
|
| 39 |
+
"filter.overallStatus": status_filter,
|
| 40 |
"aggFilters": "studyType:int",
|
| 41 |
"pageSize": 1000,
|
| 42 |
"format": "json",
|
|
|
|
| 53 |
except httpx.HTTPError as exc:
|
| 54 |
if attempt == 2:
|
| 55 |
raise
|
|
|
|
| 56 |
_logger.warning(f"ClinicalTrials.gov error (attempt {attempt + 1}): {exc}")
|
| 57 |
+
time.sleep(2 ** attempt)
|
| 58 |
|
| 59 |
page_studies = body.get("studies", [])
|
| 60 |
all_studies.extend(page_studies)
|
|
|
|
| 68 |
params["pageToken"] = next_token
|
| 69 |
|
| 70 |
trials = [_flatten_trial(s) for s in all_studies]
|
| 71 |
+
|
| 72 |
+
if client is not None:
|
| 73 |
+
_enrich_targets_llm(trials, client)
|
| 74 |
+
|
| 75 |
_logger.info("ALS trial fetch complete", extra={"data": {"total": len(trials)}})
|
| 76 |
return trials
|
| 77 |
|
|
|
|
| 86 |
status_mod = proto.get("statusModule", {})
|
| 87 |
|
| 88 |
nct_id = id_mod.get("nctId", "")
|
|
|
|
| 89 |
interventions = [
|
| 90 |
{"type": iv.get("type", ""), "name": iv.get("name", "")}
|
| 91 |
for iv in arms_mod.get("interventions", [])
|
| 92 |
]
|
|
|
|
| 93 |
|
| 94 |
return {
|
| 95 |
"nct_id": nct_id,
|
| 96 |
+
"title": id_mod.get("briefTitle", ""),
|
| 97 |
"phase": ", ".join(design_mod.get("phases", [])) or "N/A",
|
| 98 |
"status": status_mod.get("overallStatus", ""),
|
| 99 |
"sponsor": sponsor_mod.get("leadSponsor", {}).get("name", ""),
|
|
|
|
| 101 |
"interventions": interventions,
|
| 102 |
"start_date": status_mod.get("startDateStruct", {}).get("date", ""),
|
| 103 |
"url": f"https://clinicaltrials.gov/study/{nct_id}" if nct_id else "",
|
| 104 |
+
"target_entities": [],
|
| 105 |
}
|
| 106 |
|
| 107 |
|
| 108 |
+
def _enrich_targets_llm(trials: list[dict], client: "anthropic.Anthropic") -> None:
|
| 109 |
+
"""Call Claude in batches to extract biological targets; mutates each trial in-place."""
|
| 110 |
+
from extraction.normalizer import normalize_entity
|
| 111 |
+
from tools import TRIAL_EXTRACTION_TOOLS
|
| 112 |
+
|
| 113 |
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn, TimeRemainingColumn
|
| 114 |
+
|
| 115 |
+
with Progress(
|
| 116 |
+
SpinnerColumn(),
|
| 117 |
+
TextColumn("[progress.description]{task.description}"),
|
| 118 |
+
BarColumn(),
|
| 119 |
+
TaskProgressColumn(),
|
| 120 |
+
TimeRemainingColumn(),
|
| 121 |
+
) as progress:
|
| 122 |
+
task = progress.add_task("Extracting trial targets (LLM)...", total=len(trials))
|
| 123 |
+
|
| 124 |
+
for i in range(0, len(trials), _TRIAL_BATCH_SIZE):
|
| 125 |
+
batch = trials[i : i + _TRIAL_BATCH_SIZE]
|
| 126 |
+
results = _call_claude_batch(client, batch, TRIAL_EXTRACTION_TOOLS)
|
| 127 |
+
|
| 128 |
+
for nct_id, raw_targets in results.items():
|
| 129 |
+
trial = next((t for t in batch if t["nct_id"] == nct_id), None)
|
| 130 |
+
if trial is None:
|
| 131 |
+
continue
|
| 132 |
+
canonical: list[str] = []
|
| 133 |
+
for t in raw_targets:
|
| 134 |
+
if t.get("confidence", 0) < 0.5:
|
| 135 |
+
continue
|
| 136 |
+
canon_id = normalize_entity(t["name"], t["type"])
|
| 137 |
+
# strip prefix (e.g. "protein:TARDBP" → "TARDBP")
|
| 138 |
+
canon_name = canon_id.split(":", 1)[-1]
|
| 139 |
+
if canon_name and canon_name not in canonical:
|
| 140 |
+
canonical.append(canon_name)
|
| 141 |
+
trial["target_entities"] = canonical
|
| 142 |
+
|
| 143 |
+
progress.advance(task, len(batch))
|
| 144 |
+
|
| 145 |
+
if i + _TRIAL_BATCH_SIZE < len(trials):
|
| 146 |
+
time.sleep(1.0)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def _call_claude_batch(
|
| 150 |
+
client: "anthropic.Anthropic",
|
| 151 |
+
batch: list[dict],
|
| 152 |
+
tools: list,
|
| 153 |
+
) -> dict[str, list[dict]]:
|
| 154 |
+
"""Send one batch of trials to Claude; return {nct_id: [target dicts]}."""
|
| 155 |
+
lines = [
|
| 156 |
+
f"Extract targets from each of the following {len(batch)} ALS clinical trials. "
|
| 157 |
+
"Call extract_trial_targets once per trial.\n"
|
| 158 |
+
]
|
| 159 |
+
for trial in batch:
|
| 160 |
+
iv_names = ", ".join(iv["name"] for iv in trial.get("interventions", [])) or "N/A"
|
| 161 |
+
summary = (trial.get("summary") or "")[:400]
|
| 162 |
+
lines.append(
|
| 163 |
+
f"--- NCT: {trial['nct_id']} ---\n"
|
| 164 |
+
f"Title: {trial['title']}\n"
|
| 165 |
+
f"Interventions: {iv_names}\n"
|
| 166 |
+
f"Summary: {summary}\n"
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
for attempt in range(3):
|
| 170 |
+
try:
|
| 171 |
+
response = client.messages.create(
|
| 172 |
+
model=EXTRACTION_MODEL,
|
| 173 |
+
max_tokens=4096,
|
| 174 |
+
system=_TRIAL_EXTRACTION_SYSTEM,
|
| 175 |
+
tools=tools,
|
| 176 |
+
tool_choice={"type": "any"},
|
| 177 |
+
messages=[{"role": "user", "content": "\n".join(lines)}],
|
| 178 |
+
)
|
| 179 |
+
break
|
| 180 |
+
except Exception as exc:
|
| 181 |
+
if attempt == 2:
|
| 182 |
+
_logger.warning(f"Claude trial extraction failed: {exc}")
|
| 183 |
+
return {}
|
| 184 |
+
time.sleep(30 if "rate" in str(exc).lower() else 2 ** attempt)
|
| 185 |
+
|
| 186 |
+
results: dict[str, list[dict]] = {}
|
| 187 |
+
for block in response.content:
|
| 188 |
+
if block.type == "tool_use" and block.name == "extract_trial_targets":
|
| 189 |
+
nct_id = block.input.get("nct_id", "")
|
| 190 |
+
if nct_id:
|
| 191 |
+
results[nct_id] = block.input.get("targets", [])
|
| 192 |
+
|
| 193 |
+
return results
|
scripts/ingest_trials.py
CHANGED
|
@@ -5,7 +5,7 @@ Ingest ALS clinical trials from ClinicalTrials.gov v2 API.
|
|
| 5 |
Usage:
|
| 6 |
uv run python scripts/ingest_trials.py
|
| 7 |
uv run python scripts/ingest_trials.py --status RECRUITING
|
| 8 |
-
uv run python scripts/ingest_trials.py --status COMPLETED
|
| 9 |
"""
|
| 10 |
from __future__ import annotations
|
| 11 |
|
|
@@ -20,6 +20,7 @@ from dotenv import load_dotenv
|
|
| 20 |
|
| 21 |
load_dotenv()
|
| 22 |
|
|
|
|
| 23 |
from rich.console import Console
|
| 24 |
|
| 25 |
from config import TRIALS_PATH
|
|
@@ -27,21 +28,30 @@ from ingestion.clinicaltrials import fetch_als_trials
|
|
| 27 |
|
| 28 |
console = Console()
|
| 29 |
|
|
|
|
|
|
|
| 30 |
|
| 31 |
def main() -> None:
|
| 32 |
parser = argparse.ArgumentParser(description="Ingest ALS clinical trials")
|
| 33 |
parser.add_argument(
|
| 34 |
"--status",
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
)
|
| 39 |
args = parser.parse_args()
|
| 40 |
|
| 41 |
TRIALS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
| 42 |
|
|
|
|
|
|
|
| 43 |
console.print(f"[cyan]Fetching ALS interventional trials (status={args.status})...[/cyan]")
|
| 44 |
-
trials = fetch_als_trials(status=args.status)
|
| 45 |
console.print(f"[green]Fetched {len(trials)} trials[/green]")
|
| 46 |
|
| 47 |
with open(TRIALS_PATH, "w", encoding="utf-8") as f:
|
|
|
|
| 5 |
Usage:
|
| 6 |
uv run python scripts/ingest_trials.py
|
| 7 |
uv run python scripts/ingest_trials.py --status RECRUITING
|
| 8 |
+
uv run python scripts/ingest_trials.py --status RECRUITING NOT_YET_RECRUITING COMPLETED
|
| 9 |
"""
|
| 10 |
from __future__ import annotations
|
| 11 |
|
|
|
|
| 20 |
|
| 21 |
load_dotenv()
|
| 22 |
|
| 23 |
+
import anthropic
|
| 24 |
from rich.console import Console
|
| 25 |
|
| 26 |
from config import TRIALS_PATH
|
|
|
|
| 28 |
|
| 29 |
console = Console()
|
| 30 |
|
| 31 |
+
_ALL_STATUSES = ["RECRUITING", "COMPLETED", "ACTIVE_NOT_RECRUITING", "NOT_YET_RECRUITING"]
|
| 32 |
+
|
| 33 |
|
| 34 |
def main() -> None:
|
| 35 |
parser = argparse.ArgumentParser(description="Ingest ALS clinical trials")
|
| 36 |
parser.add_argument(
|
| 37 |
"--status",
|
| 38 |
+
nargs="+",
|
| 39 |
+
default=["RECRUITING", "NOT_YET_RECRUITING", "ACTIVE_NOT_RECRUITING"],
|
| 40 |
+
choices=_ALL_STATUSES,
|
| 41 |
+
metavar="STATUS",
|
| 42 |
+
help=(
|
| 43 |
+
f"One or more trial statuses to fetch (default: RECRUITING NOT_YET_RECRUITING "
|
| 44 |
+
f"ACTIVE_NOT_RECRUITING). Choices: {_ALL_STATUSES}"
|
| 45 |
+
),
|
| 46 |
)
|
| 47 |
args = parser.parse_args()
|
| 48 |
|
| 49 |
TRIALS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
| 50 |
|
| 51 |
+
client = anthropic.Anthropic()
|
| 52 |
+
|
| 53 |
console.print(f"[cyan]Fetching ALS interventional trials (status={args.status})...[/cyan]")
|
| 54 |
+
trials = fetch_als_trials(status=args.status, client=client)
|
| 55 |
console.print(f"[green]Fetched {len(trials)} trials[/green]")
|
| 56 |
|
| 57 |
with open(TRIALS_PATH, "w", encoding="utf-8") as f:
|
tools.py
CHANGED
|
@@ -34,5 +34,15 @@ SEARCH_LANDSCAPE_TOOL: anthropic.types.ToolParam = {
|
|
| 34 |
"input_schema": _load("search_landscape"),
|
| 35 |
}
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
EXTRACTION_TOOLS: list[anthropic.types.ToolParam] = [EXTRACT_ENTITIES_TOOL]
|
|
|
|
| 38 |
RESEARCH_TOOLS: list[anthropic.types.ToolParam] = [SEARCH_LANDSCAPE_TOOL]
|
|
|
|
| 34 |
"input_schema": _load("search_landscape"),
|
| 35 |
}
|
| 36 |
|
| 37 |
+
EXTRACT_TRIAL_TARGETS_TOOL: anthropic.types.ToolParam = {
|
| 38 |
+
"name": "extract_trial_targets",
|
| 39 |
+
"description": (
|
| 40 |
+
"Extract the primary biological target(s) of an ALS clinical trial — the gene, protein, "
|
| 41 |
+
"compound, or mechanism being tested or modulated. Call once per trial."
|
| 42 |
+
),
|
| 43 |
+
"input_schema": _load("extract_trial_targets"),
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
EXTRACTION_TOOLS: list[anthropic.types.ToolParam] = [EXTRACT_ENTITIES_TOOL]
|
| 47 |
+
TRIAL_EXTRACTION_TOOLS: list[anthropic.types.ToolParam] = [EXTRACT_TRIAL_TARGETS_TOOL]
|
| 48 |
RESEARCH_TOOLS: list[anthropic.types.ToolParam] = [SEARCH_LANDSCAPE_TOOL]
|