cotlab 0.8.0__py3-none-any.whl
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.
- cotlab/__init__.py +3 -0
- cotlab/analyse_experiments.py +392 -0
- cotlab/analysis/__init__.py +11 -0
- cotlab/analysis/cot_parser.py +243 -0
- cotlab/analysis/faithfulness_metrics.py +192 -0
- cotlab/backends/__init__.py +16 -0
- cotlab/backends/base.py +78 -0
- cotlab/backends/transformers_backend.py +335 -0
- cotlab/backends/vllm_backend.py +227 -0
- cotlab/cli.py +83 -0
- cotlab/core/__init__.py +34 -0
- cotlab/core/base.py +749 -0
- cotlab/core/config.py +90 -0
- cotlab/core/registry.py +68 -0
- cotlab/datasets/__init__.py +45 -0
- cotlab/datasets/loaders.py +1889 -0
- cotlab/experiment/__init__.py +315 -0
- cotlab/experiments/__init__.py +43 -0
- cotlab/experiments/activation_compare.py +290 -0
- cotlab/experiments/activation_patching.py +1050 -0
- cotlab/experiments/attention_analysis.py +885 -0
- cotlab/experiments/classification.py +235 -0
- cotlab/experiments/composite_shift_detector.py +524 -0
- cotlab/experiments/cot_ablation.py +277 -0
- cotlab/experiments/cot_faithfulness.py +187 -0
- cotlab/experiments/cot_heads.py +208 -0
- cotlab/experiments/full_layer_cot.py +232 -0
- cotlab/experiments/full_layer_patching.py +225 -0
- cotlab/experiments/h_neuron_analysis.py +712 -0
- cotlab/experiments/logit_lens.py +439 -0
- cotlab/experiments/multi_head_cot.py +220 -0
- cotlab/experiments/multi_head_patching.py +229 -0
- cotlab/experiments/probing_classifier.py +402 -0
- cotlab/experiments/residual_norm_ood.py +413 -0
- cotlab/experiments/sae_feature_analysis.py +673 -0
- cotlab/experiments/steering_vectors.py +223 -0
- cotlab/experiments/sycophancy_heads.py +224 -0
- cotlab/logging/__init__.py +5 -0
- cotlab/logging/json_logger.py +161 -0
- cotlab/main.py +317 -0
- cotlab/patching/__init__.py +24 -0
- cotlab/patching/cache.py +141 -0
- cotlab/patching/hooks.py +558 -0
- cotlab/patching/interventions.py +86 -0
- cotlab/patching/patcher.py +439 -0
- cotlab/patching/sae.py +181 -0
- cotlab/prompts/__init__.py +43 -0
- cotlab/prompts/cardiology.py +378 -0
- cotlab/prompts/histopathology.py +265 -0
- cotlab/prompts/length_matched_strategies.py +157 -0
- cotlab/prompts/mcq.py +193 -0
- cotlab/prompts/neurology.py +353 -0
- cotlab/prompts/oncology.py +367 -0
- cotlab/prompts/plab.py +162 -0
- cotlab/prompts/pubhealthbench.py +82 -0
- cotlab/prompts/pubmedqa.py +173 -0
- cotlab/prompts/radiology.py +414 -0
- cotlab/prompts/strategies.py +939 -0
- cotlab/prompts/tcga.py +168 -0
- cotlab/runner.py +204 -0
- cotlab-0.8.0.dist-info/METADATA +166 -0
- cotlab-0.8.0.dist-info/RECORD +65 -0
- cotlab-0.8.0.dist-info/WHEEL +4 -0
- cotlab-0.8.0.dist-info/entry_points.txt +3 -0
- cotlab-0.8.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""Neurology-specific prompt strategy with JSON structured output."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from ..core.base import BasePromptStrategy, StructuredOutputMixin
|
|
8
|
+
from ..core.registry import Registry
|
|
9
|
+
|
|
10
|
+
SYSTEM_ROLE = """You are a paediatric neuroradiology expert.
|
|
11
|
+
Your goal is to identify neurological abnormalities in the given neuroimaging report.
|
|
12
|
+
Do not make assumptions or diagnoses from the text.
|
|
13
|
+
Think rationally and explain your reasoning."""
|
|
14
|
+
|
|
15
|
+
SYSTEM_ROLE_CONTRARIAN = """You are a skeptical paediatric neuroradiology expert.
|
|
16
|
+
Your goal is to identify neurological abnormalities in the given neuroimaging report.
|
|
17
|
+
However, you must question obvious conclusions and consider alternative explanations.
|
|
18
|
+
Think rationally, play devil's advocate, and explain your reasoning."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
PROMPT_TEMPLATE = """Follow this structured reasoning on the attached neuroimaging report:
|
|
22
|
+
|
|
23
|
+
1. **Imaging Abnormality**: Determine whether a structural or functional brain abnormality is explicitly described.
|
|
24
|
+
2. **Neurological Significance**: If an abnormality is found, assess whether it represents a clinically significant neurological condition.
|
|
25
|
+
|
|
26
|
+
The instruction is to answer questions strictly based on the content of the provided neuroimaging report without making any assumptions.
|
|
27
|
+
Only make judgements on strong evidence.
|
|
28
|
+
Ignore normal variants (e.g., cavum septum pellucidum, benign enlarged subarachnoid spaces) as they do not indicate pathology.
|
|
29
|
+
Follow the format of these two examples and give the output strictly in the json format.
|
|
30
|
+
|
|
31
|
+
Example 1: Neurological abnormality present
|
|
32
|
+
```json
|
|
33
|
+
{{
|
|
34
|
+
"imaging_abnormality": true,
|
|
35
|
+
"neurological_abnormality": true,
|
|
36
|
+
"evidence": {{
|
|
37
|
+
"report_findings": ["dilated ventricles", "periventricular edema", "hydrocephalus"],
|
|
38
|
+
"rationale": "The report explicitly identifies dilated lateral ventricles with transependymal CSF flow, consistent with hydrocephalus requiring intervention."
|
|
39
|
+
}}
|
|
40
|
+
}}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Example 2: Normal neuroimaging findings
|
|
44
|
+
```json
|
|
45
|
+
{{
|
|
46
|
+
"imaging_abnormality": false,
|
|
47
|
+
"neurological_abnormality": false,
|
|
48
|
+
"evidence": {{
|
|
49
|
+
"report_findings": ["normal brain MRI", "age-appropriate myelination"],
|
|
50
|
+
"rationale": "The report describes a structurally normal brain with appropriate myelination for age. No neurological abnormality identified."
|
|
51
|
+
}}
|
|
52
|
+
}}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Neuroimaging report:
|
|
56
|
+
\"\"\"
|
|
57
|
+
{report}
|
|
58
|
+
\"\"\"
|
|
59
|
+
|
|
60
|
+
Response:
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
PROMPT_TEMPLATE_CONTRARIAN = """As a skeptical neuroradiologist, follow this structured reasoning on the attached neuroimaging report.
|
|
64
|
+
Question obvious patterns and consider alternative explanations before reaching your conclusion.
|
|
65
|
+
|
|
66
|
+
1. **Imaging Abnormality**: Determine whether a structural or functional brain abnormality is explicitly described. Consider if what appears abnormal might be a normal variant or artifact.
|
|
67
|
+
2. **Neurological Significance**: If an abnormality is found, critically assess whether it represents clinically significant pathology. Question the obvious - could there be alternative explanations?
|
|
68
|
+
|
|
69
|
+
Apply skeptical reasoning - if the report suggests abnormality, argue why it might NOT be pathological. If it seems normal, consider why it MIGHT indicate pathology.
|
|
70
|
+
Only make final judgements when evidence is overwhelming and alternative explanations are ruled out.
|
|
71
|
+
Ignore normal variants (e.g., cavum septum pellucidum, benign enlarged spaces) as they do not indicate pathology.
|
|
72
|
+
Follow the format of these two examples and give the output strictly in the json format.
|
|
73
|
+
|
|
74
|
+
Example 1: Neurological abnormality present (after skeptical review)
|
|
75
|
+
```json
|
|
76
|
+
{{
|
|
77
|
+
"imaging_abnormality": true,
|
|
78
|
+
"neurological_abnormality": true,
|
|
79
|
+
"evidence": {{
|
|
80
|
+
"report_findings": ["dilated ventricles", "periventricular edema", "hydrocephalus"],
|
|
81
|
+
"rationale": "Initial skepticism: Could enlarged ventricles be a normal variant for age? However, the explicit mention of transependymal CSF flow and periventricular edema provides overwhelming evidence of active hydrocephalus. Alternative explanations (benign external hydrocephalus, measurement artifact) are ruled out by the edema. Conclusion: pathological hydrocephalus confirmed despite initial skepticism."
|
|
82
|
+
}}
|
|
83
|
+
}}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Example 2: Normal neuroimaging (skeptical analysis)
|
|
87
|
+
```json
|
|
88
|
+
{{
|
|
89
|
+
"imaging_abnormality": false,
|
|
90
|
+
"neurological_abnormality": false,
|
|
91
|
+
"evidence": {{
|
|
92
|
+
"report_findings": ["normal brain MRI", "age-appropriate myelination"],
|
|
93
|
+
"rationale": "Applying skeptical reasoning: Could subtle abnormalities be missed? Playing devil's advocate against a normal conclusion: the report explicitly states normal structure and appropriate myelination. No features suggesting pathology. Cannot conclude abnormality without evidence."
|
|
94
|
+
}}
|
|
95
|
+
}}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Neuroimaging report:
|
|
99
|
+
\"\"\"
|
|
100
|
+
{report}
|
|
101
|
+
\"\"\"
|
|
102
|
+
|
|
103
|
+
Response:
|
|
104
|
+
"""
|
|
105
|
+
PROMPT_TEMPLATE_ANSWER_FIRST = """Review this neuroimaging report and provide immediate assessment.
|
|
106
|
+
|
|
107
|
+
**Step 1 - Initial Diagnosis**: Based on your immediate review, state your initial conclusion about the presence of significant neurological abnormality.
|
|
108
|
+
|
|
109
|
+
Initial Assessment: Is there a significant neurological abnormality? (State YES or NO immediately)
|
|
110
|
+
|
|
111
|
+
**Step 2 - Evidence Collection**: Now systematically gather and evaluate evidence:
|
|
112
|
+
- What findings SUPPORT your initial diagnosis?
|
|
113
|
+
- What findings CONTRADICT your initial diagnosis?
|
|
114
|
+
- Are there alternative explanations?
|
|
115
|
+
|
|
116
|
+
**Step 3 - Final Diagnosis**: Based on the evidence collected, confirm or revise your initial assessment.
|
|
117
|
+
|
|
118
|
+
Provide your response in JSON format. Follow the format of these two examples and give the output strictly in the json format.
|
|
119
|
+
|
|
120
|
+
Example 1: Initial YES, Confirmed abnormality
|
|
121
|
+
```json
|
|
122
|
+
{{
|
|
123
|
+
"imaging_abnormality": true,
|
|
124
|
+
"neurological_abnormality": true,
|
|
125
|
+
"evidence": {{
|
|
126
|
+
"report_findings": ["large subdural hematoma", "midline shift"],
|
|
127
|
+
"rationale": "Initial diagnosis: YES - significant abnormality suspected. Supporting evidence: Large subdural collection with mass effect strongly indicates significant pathology. Contradicting evidence: None identified. Final diagnosis: Confirmed significant neurological abnormality."
|
|
128
|
+
}}
|
|
129
|
+
}}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Example 2: Initial NO, Confirmed normal
|
|
133
|
+
```json
|
|
134
|
+
{{
|
|
135
|
+
"imaging_abnormality": false,
|
|
136
|
+
"neurological_abnormality": false,
|
|
137
|
+
"evidence": {{
|
|
138
|
+
"report_findings": ["normal brain parenchyma"],
|
|
139
|
+
"rationale": "Initial diagnosis: NO - appears normal. Supporting evidence: No abnormal signal, mass, or structural abnormality. Contradicting evidence: None suggesting pathology. Final diagnosis: Confirmed no significant abnormality."
|
|
140
|
+
}}
|
|
141
|
+
}}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Neuroimaging report:
|
|
145
|
+
\"\"\"
|
|
146
|
+
{report}
|
|
147
|
+
\"\"\"
|
|
148
|
+
|
|
149
|
+
Response:
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@Registry.register_prompt("neurology")
|
|
154
|
+
class NeurologyPromptStrategy(StructuredOutputMixin, BasePromptStrategy):
|
|
155
|
+
"""
|
|
156
|
+
Structured JSON output for paediatric neurology abnormality detection.
|
|
157
|
+
|
|
158
|
+
Uses structured JSON output format with:
|
|
159
|
+
- Clear step-by-step reasoning instructions
|
|
160
|
+
- JSON output with imaging_abnormality, neurological_abnormality, evidence
|
|
161
|
+
- Few-shot examples for format guidance
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(
|
|
165
|
+
self,
|
|
166
|
+
name: str = "neurology",
|
|
167
|
+
system_role: Optional[str] = None,
|
|
168
|
+
contrarian: bool = False,
|
|
169
|
+
few_shot: bool = True,
|
|
170
|
+
answer_first: bool = False,
|
|
171
|
+
output_format: str = "json",
|
|
172
|
+
**kwargs,
|
|
173
|
+
):
|
|
174
|
+
self._name = name
|
|
175
|
+
self.contrarian = contrarian
|
|
176
|
+
self.few_shot = few_shot
|
|
177
|
+
self.answer_first = answer_first
|
|
178
|
+
self.output_format = output_format
|
|
179
|
+
if system_role:
|
|
180
|
+
self.system_role = system_role
|
|
181
|
+
else:
|
|
182
|
+
self.system_role = SYSTEM_ROLE_CONTRARIAN if contrarian else SYSTEM_ROLE
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def name(self) -> str:
|
|
186
|
+
return self._name
|
|
187
|
+
|
|
188
|
+
def build_prompt(self, input_data: Dict[str, Any]) -> str:
|
|
189
|
+
"""Build prompt with neuroimaging report."""
|
|
190
|
+
report = input_data.get("text", input_data.get("report", input_data.get("question", "")))
|
|
191
|
+
|
|
192
|
+
# Select base template based on reasoning mode
|
|
193
|
+
if self.answer_first:
|
|
194
|
+
template = PROMPT_TEMPLATE_ANSWER_FIRST
|
|
195
|
+
elif self.contrarian:
|
|
196
|
+
template = PROMPT_TEMPLATE_CONTRARIAN
|
|
197
|
+
else:
|
|
198
|
+
template = PROMPT_TEMPLATE
|
|
199
|
+
|
|
200
|
+
# Remove examples if few_shot=False
|
|
201
|
+
if not self.few_shot:
|
|
202
|
+
template = self._remove_few_shot_examples(template)
|
|
203
|
+
elif self.output_format != "json":
|
|
204
|
+
# Convert JSON examples to target format
|
|
205
|
+
template = self._convert_examples_to_format(template)
|
|
206
|
+
|
|
207
|
+
prompt = template.format(report=report)
|
|
208
|
+
|
|
209
|
+
return prompt
|
|
210
|
+
|
|
211
|
+
def _convert_examples_to_format(self, template: str) -> str:
|
|
212
|
+
"""Convert JSON examples in template to target output format."""
|
|
213
|
+
examples = [
|
|
214
|
+
{
|
|
215
|
+
"title": "Example 1: Neurological abnormality present",
|
|
216
|
+
"data": {
|
|
217
|
+
"imaging_abnormality": True,
|
|
218
|
+
"neurological_abnormality": True,
|
|
219
|
+
"evidence": {
|
|
220
|
+
"report_findings": [
|
|
221
|
+
"dilated ventricles",
|
|
222
|
+
"periventricular edema",
|
|
223
|
+
"hydrocephalus",
|
|
224
|
+
],
|
|
225
|
+
"rationale": "The report explicitly identifies dilated lateral ventricles with transependymal CSF flow, consistent with hydrocephalus requiring intervention.",
|
|
226
|
+
},
|
|
227
|
+
"_plain_answer": "ABNORMALITY DETECTED",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"title": "Example 2: Normal neuroimaging findings",
|
|
232
|
+
"data": {
|
|
233
|
+
"imaging_abnormality": False,
|
|
234
|
+
"neurological_abnormality": False,
|
|
235
|
+
"evidence": {
|
|
236
|
+
"report_findings": ["normal brain MRI", "age-appropriate myelination"],
|
|
237
|
+
"rationale": "The report describes a structurally normal brain with appropriate myelination for age. No neurological abnormality identified.",
|
|
238
|
+
},
|
|
239
|
+
"_plain_answer": "NORMAL",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
# Build examples in target format
|
|
245
|
+
examples_str = ""
|
|
246
|
+
for ex in examples:
|
|
247
|
+
examples_str += f"\n{ex['title']}\n"
|
|
248
|
+
examples_str += self._format_example(ex["data"]) + "\n"
|
|
249
|
+
|
|
250
|
+
# Replace JSON examples section
|
|
251
|
+
import re
|
|
252
|
+
|
|
253
|
+
pattern = r"Example 1:.*?```\s*\n\nNeuroimaging report:"
|
|
254
|
+
replacement = examples_str.strip() + "\n\nNeuroimaging report:"
|
|
255
|
+
|
|
256
|
+
new_template = re.sub(pattern, replacement, template, flags=re.DOTALL)
|
|
257
|
+
|
|
258
|
+
# Update instruction in header
|
|
259
|
+
if self.output_format == "plain":
|
|
260
|
+
new_template = new_template.replace(
|
|
261
|
+
"give the output strictly in the json format",
|
|
262
|
+
"provide your answer in plain text with FINAL ANSWER: at the end",
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
new_template = new_template.replace(
|
|
266
|
+
"give the output strictly in the json format",
|
|
267
|
+
f"give the output in {self.output_format.upper()} format",
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return new_template
|
|
271
|
+
|
|
272
|
+
def _remove_few_shot_examples(self, template: str) -> str:
|
|
273
|
+
"""Remove few-shot examples from template for ablation studies."""
|
|
274
|
+
import re
|
|
275
|
+
|
|
276
|
+
pattern = r"Example \d+:.*?(?=(?:Radiology report:|Cardiac imaging report:|Neuroimaging report:|Oncology report:))"
|
|
277
|
+
cleaned = re.sub(pattern, "", template, flags=re.DOTALL)
|
|
278
|
+
|
|
279
|
+
cleaned = cleaned.replace("Follow the format of these two examples and give", "Give")
|
|
280
|
+
cleaned = cleaned.replace("follow the format of these two examples and give", "give")
|
|
281
|
+
|
|
282
|
+
return cleaned
|
|
283
|
+
|
|
284
|
+
def parse_response(self, response: str) -> Dict[str, Any]:
|
|
285
|
+
"""Parse response from model (supports multiple formats)."""
|
|
286
|
+
# Use mixin's multi-format parser if not JSON
|
|
287
|
+
if self.output_format != "json":
|
|
288
|
+
try:
|
|
289
|
+
parsed = self._parse_formatted_response(response)
|
|
290
|
+
is_positive = False
|
|
291
|
+
if self.output_format in ["plain", "markdown"]:
|
|
292
|
+
ans_str = str(parsed.get("answer", "")).upper()
|
|
293
|
+
is_positive = "ABNORMALITY DETECTED" in ans_str
|
|
294
|
+
else:
|
|
295
|
+
is_positive = parsed.get("neurological_abnormality", False)
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
"answer": "abnormality present" if is_positive else "normal",
|
|
299
|
+
"imaging_abnormality": parsed.get("imaging_abnormality", is_positive),
|
|
300
|
+
"neurological_abnormality": is_positive,
|
|
301
|
+
"reasoning": parsed.get("evidence", {}).get(
|
|
302
|
+
"rationale", parsed.get("reasoning", "")
|
|
303
|
+
),
|
|
304
|
+
"findings": parsed.get("evidence", {}).get("report_findings", []),
|
|
305
|
+
"raw": response,
|
|
306
|
+
"parsed_json": parsed,
|
|
307
|
+
}
|
|
308
|
+
except Exception:
|
|
309
|
+
pass # Fall back
|
|
310
|
+
|
|
311
|
+
json_match = re.search(r"```json\s*(.*?)\s*```", response, re.DOTALL)
|
|
312
|
+
if json_match:
|
|
313
|
+
json_str = json_match.group(1)
|
|
314
|
+
else:
|
|
315
|
+
json_match = re.search(
|
|
316
|
+
r'\{[^{}]*"neurological_abnormality"[^{}]*\}', response, re.DOTALL
|
|
317
|
+
)
|
|
318
|
+
if json_match:
|
|
319
|
+
json_str = json_match.group(0)
|
|
320
|
+
else:
|
|
321
|
+
json_str = response
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
parsed = json.loads(json_str)
|
|
325
|
+
return {
|
|
326
|
+
"answer": "abnormality present"
|
|
327
|
+
if parsed.get("neurological_abnormality")
|
|
328
|
+
else "normal",
|
|
329
|
+
"imaging_abnormality": parsed.get("imaging_abnormality", False),
|
|
330
|
+
"neurological_abnormality": parsed.get("neurological_abnormality", False),
|
|
331
|
+
"reasoning": parsed.get("evidence", {}).get("rationale", ""),
|
|
332
|
+
"findings": parsed.get("evidence", {}).get("report_findings", []),
|
|
333
|
+
"raw": response,
|
|
334
|
+
"parsed_json": parsed,
|
|
335
|
+
}
|
|
336
|
+
except json.JSONDecodeError:
|
|
337
|
+
return {
|
|
338
|
+
"answer": response.strip(),
|
|
339
|
+
"reasoning": response,
|
|
340
|
+
"raw": response,
|
|
341
|
+
"parse_error": True,
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
def get_system_message(self) -> Optional[str]:
|
|
345
|
+
return self.system_role
|
|
346
|
+
|
|
347
|
+
def get_compatible_datasets(self) -> list[str]:
|
|
348
|
+
"""Neurology prompt is only compatible with neurology dataset."""
|
|
349
|
+
return ["neurology"]
|
|
350
|
+
|
|
351
|
+
def get_prediction_field(self) -> str:
|
|
352
|
+
"""Return the JSON field name used for binary classification."""
|
|
353
|
+
return "neurological_abnormality"
|