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.
Files changed (65) hide show
  1. cotlab/__init__.py +3 -0
  2. cotlab/analyse_experiments.py +392 -0
  3. cotlab/analysis/__init__.py +11 -0
  4. cotlab/analysis/cot_parser.py +243 -0
  5. cotlab/analysis/faithfulness_metrics.py +192 -0
  6. cotlab/backends/__init__.py +16 -0
  7. cotlab/backends/base.py +78 -0
  8. cotlab/backends/transformers_backend.py +335 -0
  9. cotlab/backends/vllm_backend.py +227 -0
  10. cotlab/cli.py +83 -0
  11. cotlab/core/__init__.py +34 -0
  12. cotlab/core/base.py +749 -0
  13. cotlab/core/config.py +90 -0
  14. cotlab/core/registry.py +68 -0
  15. cotlab/datasets/__init__.py +45 -0
  16. cotlab/datasets/loaders.py +1889 -0
  17. cotlab/experiment/__init__.py +315 -0
  18. cotlab/experiments/__init__.py +43 -0
  19. cotlab/experiments/activation_compare.py +290 -0
  20. cotlab/experiments/activation_patching.py +1050 -0
  21. cotlab/experiments/attention_analysis.py +885 -0
  22. cotlab/experiments/classification.py +235 -0
  23. cotlab/experiments/composite_shift_detector.py +524 -0
  24. cotlab/experiments/cot_ablation.py +277 -0
  25. cotlab/experiments/cot_faithfulness.py +187 -0
  26. cotlab/experiments/cot_heads.py +208 -0
  27. cotlab/experiments/full_layer_cot.py +232 -0
  28. cotlab/experiments/full_layer_patching.py +225 -0
  29. cotlab/experiments/h_neuron_analysis.py +712 -0
  30. cotlab/experiments/logit_lens.py +439 -0
  31. cotlab/experiments/multi_head_cot.py +220 -0
  32. cotlab/experiments/multi_head_patching.py +229 -0
  33. cotlab/experiments/probing_classifier.py +402 -0
  34. cotlab/experiments/residual_norm_ood.py +413 -0
  35. cotlab/experiments/sae_feature_analysis.py +673 -0
  36. cotlab/experiments/steering_vectors.py +223 -0
  37. cotlab/experiments/sycophancy_heads.py +224 -0
  38. cotlab/logging/__init__.py +5 -0
  39. cotlab/logging/json_logger.py +161 -0
  40. cotlab/main.py +317 -0
  41. cotlab/patching/__init__.py +24 -0
  42. cotlab/patching/cache.py +141 -0
  43. cotlab/patching/hooks.py +558 -0
  44. cotlab/patching/interventions.py +86 -0
  45. cotlab/patching/patcher.py +439 -0
  46. cotlab/patching/sae.py +181 -0
  47. cotlab/prompts/__init__.py +43 -0
  48. cotlab/prompts/cardiology.py +378 -0
  49. cotlab/prompts/histopathology.py +265 -0
  50. cotlab/prompts/length_matched_strategies.py +157 -0
  51. cotlab/prompts/mcq.py +193 -0
  52. cotlab/prompts/neurology.py +353 -0
  53. cotlab/prompts/oncology.py +367 -0
  54. cotlab/prompts/plab.py +162 -0
  55. cotlab/prompts/pubhealthbench.py +82 -0
  56. cotlab/prompts/pubmedqa.py +173 -0
  57. cotlab/prompts/radiology.py +414 -0
  58. cotlab/prompts/strategies.py +939 -0
  59. cotlab/prompts/tcga.py +168 -0
  60. cotlab/runner.py +204 -0
  61. cotlab-0.8.0.dist-info/METADATA +166 -0
  62. cotlab-0.8.0.dist-info/RECORD +65 -0
  63. cotlab-0.8.0.dist-info/WHEEL +4 -0
  64. cotlab-0.8.0.dist-info/entry_points.txt +3 -0
  65. 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"