open-interpretability 0.0.20__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 (256) hide show
  1. open_interpretability-0.0.20.dist-info/METADATA +157 -0
  2. open_interpretability-0.0.20.dist-info/RECORD +256 -0
  3. open_interpretability-0.0.20.dist-info/WHEEL +4 -0
  4. open_interpretability-0.0.20.dist-info/entry_points.txt +2 -0
  5. open_interpretability-0.0.20.dist-info/licenses/LICENSE +21 -0
  6. openinterp/__init__.py +6 -0
  7. openinterp/agents/__init__.py +25 -0
  8. openinterp/agents/base.py +229 -0
  9. openinterp/agents/judge.py +149 -0
  10. openinterp/agents/proposer.py +471 -0
  11. openinterp/agents/proposer_profiles.py +79 -0
  12. openinterp/agents/review_measurements.py +173 -0
  13. openinterp/agents/reviewer.py +498 -0
  14. openinterp/agreement.py +63 -0
  15. openinterp/audit_options.py +79 -0
  16. openinterp/baseline.py +254 -0
  17. openinterp/benchmark.py +450 -0
  18. openinterp/blends.py +239 -0
  19. openinterp/builder/__init__.py +6 -0
  20. openinterp/builder/agents.py +260 -0
  21. openinterp/builder/aligned.py +41 -0
  22. openinterp/builder/charts.py +411 -0
  23. openinterp/builder/circuit_pages.py +37 -0
  24. openinterp/builder/circuits.py +264 -0
  25. openinterp/builder/compiler.py +495 -0
  26. openinterp/builder/corpora.py +98 -0
  27. openinterp/builder/coverage.py +46 -0
  28. openinterp/builder/dashboard.py +255 -0
  29. openinterp/builder/disk_memo.py +65 -0
  30. openinterp/builder/experiment.py +105 -0
  31. openinterp/builder/heat.py +121 -0
  32. openinterp/builder/hrefs.py +71 -0
  33. openinterp/builder/learned_baseline.py +204 -0
  34. openinterp/builder/model_facts.py +138 -0
  35. openinterp/builder/neuronpedia_export.py +107 -0
  36. openinterp/builder/parallel.py +100 -0
  37. openinterp/builder/parquet_export.py +458 -0
  38. openinterp/builder/pieces.py +127 -0
  39. openinterp/builder/pii.py +211 -0
  40. openinterp/builder/registry_stats.py +144 -0
  41. openinterp/builder/research.py +109 -0
  42. openinterp/builder/sections.py +28 -0
  43. openinterp/builder/significance.py +103 -0
  44. openinterp/builder/sister_graph.py +180 -0
  45. openinterp/builder/standing.py +185 -0
  46. openinterp/builder/statuses.py +34 -0
  47. openinterp/builder/stopped.py +91 -0
  48. openinterp/builder/stops.py +30 -0
  49. openinterp/builder/superpositions.py +102 -0
  50. openinterp/builder/templates/_agent_role_cells.html +9 -0
  51. openinterp/builder/templates/_causal_chart.html +67 -0
  52. openinterp/builder/templates/_circuit.html +8 -0
  53. openinterp/builder/templates/_dashboard.html +133 -0
  54. openinterp/builder/templates/_experiment.html +500 -0
  55. openinterp/builder/templates/_macros.html +82 -0
  56. openinterp/builder/templates/_patchscope.html +33 -0
  57. openinterp/builder/templates/_prior_art.html +24 -0
  58. openinterp/builder/templates/_specificity.html +21 -0
  59. openinterp/builder/templates/_steering.html +34 -0
  60. openinterp/builder/templates/_superposition.html +62 -0
  61. openinterp/builder/templates/_vocab_chart.html +88 -0
  62. openinterp/builder/templates/_wiring.html +44 -0
  63. openinterp/builder/templates/about.html +95 -0
  64. openinterp/builder/templates/agent.html +69 -0
  65. openinterp/builder/templates/agents.html +80 -0
  66. openinterp/builder/templates/base.html +103 -0
  67. openinterp/builder/templates/circuit.html +64 -0
  68. openinterp/builder/templates/circuits.html +46 -0
  69. openinterp/builder/templates/experiment.html +21 -0
  70. openinterp/builder/templates/hub.html +16 -0
  71. openinterp/builder/templates/index.html +387 -0
  72. openinterp/builder/templates/layer.html +69 -0
  73. openinterp/builder/templates/learned-baseline.html +44 -0
  74. openinterp/builder/templates/model.html +151 -0
  75. openinterp/builder/templates/models.html +47 -0
  76. openinterp/builder/templates/neuron.html +234 -0
  77. openinterp/builder/templates/paper.html +39 -0
  78. openinterp/builder/templates/redirect.html +1 -0
  79. openinterp/builder/templates/research.html +31 -0
  80. openinterp/builder/templates/user.html +54 -0
  81. openinterp/builder/templates/users.html +39 -0
  82. openinterp/builder/templates/wiki/experiment.md +271 -0
  83. openinterp/builder/templates/wiki/index.md +32 -0
  84. openinterp/builder/templates/wiki/neuron.md +30 -0
  85. openinterp/builder/templates/wiki/stop.md +36 -0
  86. openinterp/builder/tree.py +470 -0
  87. openinterp/builder/users.py +142 -0
  88. openinterp/builder/wiki.py +427 -0
  89. openinterp/checks.py +429 -0
  90. openinterp/cli.py +494 -0
  91. openinterp/cli_baseline.py +332 -0
  92. openinterp/cli_benchmark.py +300 -0
  93. openinterp/cli_build.py +128 -0
  94. openinterp/cli_check.py +51 -0
  95. openinterp/cli_concepts.py +76 -0
  96. openinterp/cli_corpus.py +252 -0
  97. openinterp/cli_costs.py +257 -0
  98. openinterp/cli_coverage.py +76 -0
  99. openinterp/cli_crossref.py +86 -0
  100. openinterp/cli_errata.py +142 -0
  101. openinterp/cli_evidence.py +495 -0
  102. openinterp/cli_evidence_store.py +93 -0
  103. openinterp/cli_explain.py +101 -0
  104. openinterp/cli_loop.py +125 -0
  105. openinterp/cli_memory.py +28 -0
  106. openinterp/cli_neuronpedia.py +175 -0
  107. openinterp/cli_pause.py +45 -0
  108. openinterp/cli_pole_effects.py +133 -0
  109. openinterp/cli_probing.py +119 -0
  110. openinterp/cli_registry.py +348 -0
  111. openinterp/cli_reproduce.py +446 -0
  112. openinterp/cli_review.py +302 -0
  113. openinterp/cli_scan.py +223 -0
  114. openinterp/cli_score.py +446 -0
  115. openinterp/cli_sisters.py +86 -0
  116. openinterp/cli_submission.py +217 -0
  117. openinterp/cli_superpositions.py +118 -0
  118. openinterp/cli_wiring.py +54 -0
  119. openinterp/codeid.py +73 -0
  120. openinterp/concepts.py +400 -0
  121. openinterp/core/__init__.py +59 -0
  122. openinterp/core/agent_names.py +76 -0
  123. openinterp/core/attribution.py +101 -0
  124. openinterp/core/cache_dirs.py +15 -0
  125. openinterp/core/causal.py +82 -0
  126. openinterp/core/confounds.py +103 -0
  127. openinterp/core/contributors.py +26 -0
  128. openinterp/core/corpora.py +126 -0
  129. openinterp/core/costs.py +430 -0
  130. openinterp/core/draws.py +109 -0
  131. openinterp/core/errata.py +244 -0
  132. openinterp/core/evidence.py +160 -0
  133. openinterp/core/examples.py +93 -0
  134. openinterp/core/exceptions.py +29 -0
  135. openinterp/core/explanation.py +130 -0
  136. openinterp/core/fence.py +42 -0
  137. openinterp/core/heavy_lock.py +114 -0
  138. openinterp/core/identifiers.py +198 -0
  139. openinterp/core/isolation.py +149 -0
  140. openinterp/core/ledger.py +130 -0
  141. openinterp/core/licenses.py +28 -0
  142. openinterp/core/manifold.py +117 -0
  143. openinterp/core/partners.py +32 -0
  144. openinterp/core/patchscope.py +59 -0
  145. openinterp/core/paths.py +40 -0
  146. openinterp/core/pause.py +96 -0
  147. openinterp/core/pricing.py +178 -0
  148. openinterp/core/prior_art.py +44 -0
  149. openinterp/core/probing.py +146 -0
  150. openinterp/core/profiles.py +221 -0
  151. openinterp/core/project.py +28 -0
  152. openinterp/core/run_environment.py +38 -0
  153. openinterp/core/schemas.py +484 -0
  154. openinterp/core/specificity.py +49 -0
  155. openinterp/core/stats.py +172 -0
  156. openinterp/core/steering.py +49 -0
  157. openinterp/core/strict.py +19 -0
  158. openinterp/core/takedowns.py +136 -0
  159. openinterp/core/transcripts.py +130 -0
  160. openinterp/core/wiring.py +80 -0
  161. openinterp/corpus.py +476 -0
  162. openinterp/corpus_cache.py +145 -0
  163. openinterp/corpus_layouts.py +108 -0
  164. openinterp/corpus_presets.py +168 -0
  165. openinterp/corpus_rebuild.py +93 -0
  166. openinterp/coverage.py +155 -0
  167. openinterp/crossref.py +442 -0
  168. openinterp/data/__init__.py +9 -0
  169. openinterp/data/offensive_words.txt +58 -0
  170. openinterp/data/public_suffix_list.dat +15771 -0
  171. openinterp/data/sample_corpus.txt +40 -0
  172. openinterp/draw_steps.py +359 -0
  173. openinterp/errata_checks.py +129 -0
  174. openinterp/evidence.py +462 -0
  175. openinterp/evidence_branch.py +119 -0
  176. openinterp/evidence_lenses.py +75 -0
  177. openinterp/experiment_loop.py +357 -0
  178. openinterp/explain.py +252 -0
  179. openinterp/half_precision.py +79 -0
  180. openinterp/harness/__init__.py +37 -0
  181. openinterp/harness/ane/__init__.py +9 -0
  182. openinterp/harness/ane/backend.py +324 -0
  183. openinterp/harness/ane/cache.py +276 -0
  184. openinterp/harness/ane/chunks.py +163 -0
  185. openinterp/harness/ane/convert.py +116 -0
  186. openinterp/harness/ane/forward.py +258 -0
  187. openinterp/harness/ane/qualified.py +40 -0
  188. openinterp/harness/annotations.py +329 -0
  189. openinterp/harness/attribution.py +96 -0
  190. openinterp/harness/causal.py +264 -0
  191. openinterp/harness/confounds.py +184 -0
  192. openinterp/harness/covariance.py +301 -0
  193. openinterp/harness/crossref.py +195 -0
  194. openinterp/harness/cuda_devices.py +88 -0
  195. openinterp/harness/dead.py +124 -0
  196. openinterp/harness/devices.py +401 -0
  197. openinterp/harness/dictionaries.py +496 -0
  198. openinterp/harness/dreaming.py +116 -0
  199. openinterp/harness/environment.py +64 -0
  200. openinterp/harness/explanation_scores.py +371 -0
  201. openinterp/harness/features.py +276 -0
  202. openinterp/harness/first_position.py +137 -0
  203. openinterp/harness/heads.py +226 -0
  204. openinterp/harness/hooks.py +499 -0
  205. openinterp/harness/input_attribution.py +367 -0
  206. openinterp/harness/jlens.py +183 -0
  207. openinterp/harness/layer_cache.py +301 -0
  208. openinterp/harness/layer_major.py +274 -0
  209. openinterp/harness/lookahead.py +93 -0
  210. openinterp/harness/manifold.py +200 -0
  211. openinterp/harness/memory.py +471 -0
  212. openinterp/harness/moe.py +134 -0
  213. openinterp/harness/patcher.py +322 -0
  214. openinterp/harness/patchscope.py +177 -0
  215. openinterp/harness/probing.py +383 -0
  216. openinterp/harness/scan.py +462 -0
  217. openinterp/harness/scan_backend.py +201 -0
  218. openinterp/harness/sister_passes.py +463 -0
  219. openinterp/harness/sisters.py +363 -0
  220. openinterp/harness/sites.py +101 -0
  221. openinterp/harness/specificity.py +107 -0
  222. openinterp/harness/steering.py +159 -0
  223. openinterp/harness/superpositions.py +116 -0
  224. openinterp/harness/units.py +203 -0
  225. openinterp/harness/wiring.py +432 -0
  226. openinterp/harness/wiring_activations.py +285 -0
  227. openinterp/langid.py +73 -0
  228. openinterp/names.py +129 -0
  229. openinterp/neuronpedia_baseline.py +88 -0
  230. openinterp/neuronpedia_review.py +148 -0
  231. openinterp/planner.py +466 -0
  232. openinterp/planner_effects.py +89 -0
  233. openinterp/planner_heads.py +92 -0
  234. openinterp/planner_models.py +174 -0
  235. openinterp/planner_sisters.py +122 -0
  236. openinterp/planner_states.py +57 -0
  237. openinterp/planner_superpositions.py +114 -0
  238. openinterp/pole_effects.py +102 -0
  239. openinterp/prior_art.py +143 -0
  240. openinterp/reproduce.py +450 -0
  241. openinterp/revision.py +45 -0
  242. openinterp/runner_cache.py +35 -0
  243. openinterp/score_controls.py +169 -0
  244. openinterp/sensitive.py +243 -0
  245. openinterp/show.py +236 -0
  246. openinterp/signing.py +209 -0
  247. openinterp/sisters.py +476 -0
  248. openinterp/superposition_finders.py +225 -0
  249. openinterp/sync/__init__.py +5 -0
  250. openinterp/sync/r2.py +280 -0
  251. openinterp/syntax.py +399 -0
  252. openinterp/takedown_checks.py +76 -0
  253. openinterp/toxicity.py +158 -0
  254. openinterp/verdict_regression.py +79 -0
  255. openinterp/wiring_backfill.py +170 -0
  256. openinterp/wordnet.py +362 -0
openinterp/concepts.py ADDED
@@ -0,0 +1,400 @@
1
+ """Labelled concept sets from annotated Hugging Face datasets (`open-interpretability concepts`).
2
+
3
+ A concept set is JSON lines ``{"text": ..., "spans": [[start, end], ...]}``. It is the
4
+ labelled text that ``open-interpretability probe-compare`` reads. The spans are the characters where
5
+ the concept holds, marked by human annotators, never by the model. A concept set feeds
6
+ sparse-probing searches. It also gives reviewers positive holdouts from a source that
7
+ the proposer never saw. A
8
+ ``<name>.source.json`` sidecar (:class:`~openinterp.core.probing.ConceptSource`) pins
9
+ the dataset commit, split, label and licence.
10
+
11
+ Two kinds of label are read:
12
+
13
+ * *word tags*: a span over each word with the tag. The tags are CoNLL-2003 entities and
14
+ Penn Treebank tags, and Universal Dependencies parts of speech and relations. A tag
15
+ matches a label
16
+ exactly, without its BIO prefix (``B-PER``, ``I-PER`` -> ``PER``) or without its
17
+ subtype (``nmod:poss`` -> ``nmod``). Sentences are joined back into documents.
18
+ * *document classes* (language, news topic): a span over the whole text of each
19
+ document of the class. The sample is balanced: half the documents are of the class,
20
+ half are of the others.
21
+
22
+ Splits are small (a few MB), so each is read whole, then sampled with a seed.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import hashlib
28
+ import json
29
+ import random
30
+ import re
31
+ from collections import Counter
32
+ from collections.abc import Sequence
33
+ from dataclasses import dataclass
34
+ from pathlib import Path
35
+
36
+ import pyarrow.parquet as pq
37
+
38
+ from openinterp.core.probing import ConceptSource
39
+ from openinterp.corpus import _license, source_path
40
+ from openinterp.harness.annotations import Span
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class ConceptPreset:
45
+ """Where a concept preset's labels are, and how its rows become documents."""
46
+
47
+ dataset: str
48
+ files: str # glob within the dataset repo, with {split} and, for treebanks, {subset}
49
+ split: str # the default split: a held-out one, which is small
50
+ column: str # word tags, or one class per row
51
+ # How rows become documents. "conll": pre-tokenised words, joined with spaces. "ud":
52
+ # words found in each sentence's text. "document": one per row, with one class.
53
+ layout: str
54
+ revision: str | None = None
55
+ subset: str | None = None # default treebank, for "ud"
56
+ bio: bool = False # tags carry B-/I- prefixes
57
+ text_column: str = "text"
58
+
59
+
60
+ # Licences differ, and some forbid redistribution: check before quoting excerpts in the wiki.
61
+ CONCEPT_PRESETS = {
62
+ # CoNLL-2003 English NER (PER, ORG, LOC, MISC) over Reuters news of 1996. Licence
63
+ # "other": the text is Reuters RCV1, for research use, so don't publish excerpts. The
64
+ # main branch is a loading script, so read the Hub's Parquet conversion.
65
+ "conll2003-ner": ConceptPreset(
66
+ "eriktks/conll2003",
67
+ "conll2003/{split}/*.parquet",
68
+ "validation",
69
+ "ner_tags",
70
+ "conll",
71
+ revision="refs/convert/parquet",
72
+ bio=True,
73
+ ),
74
+ # The same sentences' Penn Treebank tags (NNP, VBD, ...): a second part-of-speech
75
+ # source, independent of Universal Dependencies' annotators and genres.
76
+ "conll2003-pos": ConceptPreset(
77
+ "eriktks/conll2003",
78
+ "conll2003/{split}/*.parquet",
79
+ "validation",
80
+ "pos_tags",
81
+ "conll",
82
+ revision="refs/convert/parquet",
83
+ ),
84
+ # Universal Dependencies parts of speech (NOUN, VERB, ADP, ...) for any of its ~300
85
+ # treebanks; --subset picks one (en_ewt: English web text; en_gum: eight genres).
86
+ # Licences are per treebank (en_ewt CC BY-SA 4.0, en_gum CC BY-NC-SA 4.0).
87
+ "ud-upos": ConceptPreset(
88
+ "universal-dependencies/universal_dependencies",
89
+ "parquet/{subset}/{split}.parquet",
90
+ "dev",
91
+ "upos",
92
+ "ud",
93
+ subset="en_ewt",
94
+ ),
95
+ # The same treebanks' dependency relations (nsubj, obj, amod, ...), for syntax neurons.
96
+ "ud-deprel": ConceptPreset(
97
+ "universal-dependencies/universal_dependencies",
98
+ "parquet/{subset}/{split}.parquet",
99
+ "dev",
100
+ "deprel",
101
+ "ud",
102
+ subset="en_ewt",
103
+ ),
104
+ # Sentences and reviews in 20 languages. Their codes are ar, bg, de, el, en, es, fr,
105
+ # hi, it, ja, nl, pl, pt, ru, sw, th, tr, ur, vi and zh. No licence on the card; its
106
+ # sources include Amazon reviews and XNLI (CC BY-NC 4.0). The main branch is CSV.
107
+ "language-identification": ConceptPreset(
108
+ "papluca/language-identification",
109
+ "default/{split}/*.parquet",
110
+ "validation",
111
+ "labels",
112
+ "document",
113
+ revision="refs/convert/parquet",
114
+ ),
115
+ # AG News headlines and lead sentences in four topics (World, Sports, Business,
116
+ # Sci/Tech). Licence "unknown": academic, non-commercial use.
117
+ "ag_news": ConceptPreset(
118
+ "fancyzhx/ag_news", "data/{split}-*.parquet", "test", "label", "document"
119
+ ),
120
+ }
121
+
122
+
123
+ @dataclass(frozen=True)
124
+ class Sentence:
125
+ """One annotated sentence, with a span per tagged word, and where it starts a document."""
126
+
127
+ text: str
128
+ spans: list[Span] # one per tagged word
129
+ newdoc: bool = False
130
+ newpar: bool = True
131
+
132
+
133
+ # Pre-tokenised CoNLL text: "lobster ," and "( AP )" are joined as written, "lobster,", "(AP)".
134
+ _ATTACH_LEFT = re.compile(r"[.,;:!?%)\]}]+|n't|'(?:s|re|ve|m|ll|d)", re.IGNORECASE)
135
+ _ATTACH_RIGHT = re.compile(r"[(\[{$]")
136
+
137
+
138
+ def join_words(words: Sequence[str]) -> tuple[str, list[tuple[int, int]]]:
139
+ """Pre-tokenised words as text, with each word's characters [start, end)."""
140
+ text, offsets = "", []
141
+ for i, word in enumerate(words):
142
+ if i and not (_ATTACH_LEFT.fullmatch(word) or _ATTACH_RIGHT.fullmatch(words[i - 1])):
143
+ text += " "
144
+ offsets.append((len(text), len(text) + len(word)))
145
+ text += word
146
+ return text, offsets
147
+
148
+
149
+ def locate_words(
150
+ text: str, words: Sequence[str], mwt: Sequence[dict] = ()
151
+ ) -> list[tuple[int, int]] | None:
152
+ """Return each word's characters in its sentence's ``text``.
153
+
154
+ The words of a multiword token (French "du" = "de" + "le") share its surface form's
155
+ characters. ``None`` if a word isn't where it should be, because that would misplace
156
+ every label after it.
157
+ """
158
+ fused = {}
159
+ for token in mwt or ():
160
+ first, last = token["id"].split("-")
161
+ fused[int(first) - 1] = (int(last) - 1, token["form"])
162
+ out: list[tuple[int, int]] = []
163
+ cursor = i = 0
164
+ while i < len(words):
165
+ last, form = fused.get(i, (i, words[i]))
166
+ start = text.find(form, cursor) if form else -1
167
+ if start < 0 or text[cursor:start].strip():
168
+ return None
169
+ cursor = start + len(form)
170
+ out += [(start, cursor)] * (last - i + 1)
171
+ i = last + 1
172
+ return out
173
+
174
+
175
+ def group_sentences(sentences: Sequence[Sentence], max_chars: int) -> list[tuple[str, list[Span]]]:
176
+ """Join consecutive sentences into documents of at most ``max_chars``.
177
+
178
+ A new document starts at each new source document, or when the next sentence wouldn't
179
+ fit. Newlines separate paragraphs, and a space separates the sentences in one. A
180
+ sentence longer than ``max_chars`` is cut, and its spans with it.
181
+ """
182
+ docs: list[tuple[str, list[Span]]] = []
183
+ text, spans = "", []
184
+ for sent in sentences:
185
+ sep = "\n" if sent.newpar else " "
186
+ if text and (sent.newdoc or len(text) + len(sep) + len(sent.text) > max_chars):
187
+ docs.append((text, spans))
188
+ text, spans = "", []
189
+ shift = len(text) + len(sep) if text else 0
190
+ text = (text + sep if text else "") + sent.text
191
+ spans += [Span(s.start + shift, s.end + shift, s.label) for s in sent.spans]
192
+ if len(text) > max_chars:
193
+ text = text[:max_chars]
194
+ spans = [
195
+ Span(s.start, min(s.end, max_chars), s.label) for s in spans if s.start < max_chars
196
+ ]
197
+ if text:
198
+ docs.append((text, spans))
199
+ return docs
200
+
201
+
202
+ def tag_matches(tag: str, label: str) -> bool:
203
+ """True when ``tag`` is ``label``, or ``label`` with a ``:subtype`` (``nmod:poss``)."""
204
+ return tag == label or tag.split(":")[0] == label
205
+
206
+
207
+ def _tag(tag, names: list[str] | None, bio: bool) -> str | None:
208
+ """A word's or row's label as text, or ``None`` for none (``O``, ``_``)."""
209
+ if tag is None:
210
+ return None
211
+ tag = names[tag] if names is not None and isinstance(tag, int) else str(tag)
212
+ if tag in ("O", "_", ""):
213
+ return None
214
+ return re.sub(r"^[BI]-", "", tag) if bio else tag
215
+
216
+
217
+ def class_names(schema, column: str) -> list[str] | None:
218
+ """Return the names of a Hugging Face ClassLabel column, or a sequence of them.
219
+
220
+ Parquet stores them as integers. The names are in the file's ``huggingface`` schema
221
+ metadata.
222
+ """
223
+ meta = (schema.metadata or {}).get(b"huggingface")
224
+ if meta is None:
225
+ return None
226
+ feature = json.loads(meta).get("info", {}).get("features", {}).get(column, {})
227
+ feature = feature.get("feature", feature) # Sequence(ClassLabel) holds it inside
228
+ return feature.get("names")
229
+
230
+
231
+ def _read_rows(fs, paths: Sequence[str], columns: Sequence[str]) -> tuple[list[dict], list | None]:
232
+ rows, names = [], None
233
+ for path in sorted(paths):
234
+ with fs.open(path, "rb") as fh:
235
+ file = pq.ParquetFile(fh)
236
+ names = names or class_names(file.schema_arrow, columns[-1])
237
+ rows += file.read(columns=list(columns)).to_pylist()
238
+ return rows, names
239
+
240
+
241
+ def _documents(preset: ConceptPreset, column: str, rows, names, max_chars: int):
242
+ """The split's documents, each with a span per tagged word or class."""
243
+ if preset.layout == "document":
244
+ docs = []
245
+ for row in rows:
246
+ text = (row[preset.text_column] or "").strip()[:max_chars]
247
+ label = _tag(row[column], names, preset.bio)
248
+ if text:
249
+ docs.append((text, [Span(0, len(text), label)] if label else []))
250
+ return docs, 0
251
+ sentences, skipped = [], 0
252
+ for row in rows:
253
+ words = row["tokens"] or []
254
+ if preset.layout == "conll":
255
+ text, offsets = join_words(words)
256
+ newdoc, newpar = False, True
257
+ else:
258
+ text = row[preset.text_column] or ""
259
+ offsets = locate_words(text, words, row.get("mwt"))
260
+ comments = row.get("comments") or []
261
+ newdoc = any(c.startswith("newdoc") for c in comments)
262
+ newpar = newdoc or any(c.startswith("newpar") for c in comments)
263
+ if not words or offsets is None:
264
+ skipped += 1
265
+ continue
266
+ spans = [
267
+ Span(start, end, label)
268
+ for (start, end), tag in zip(offsets, row[column], strict=True)
269
+ if (label := _tag(tag, names, preset.bio)) is not None
270
+ ]
271
+ sentences.append(Sentence(text, spans, newdoc, newpar))
272
+ return group_sentences(sentences, max_chars), skipped
273
+
274
+
275
+ @dataclass
276
+ class LoadedSplit:
277
+ """One split read whole: its documents with spans, and where they came from."""
278
+
279
+ docs: list[tuple[str, list[Span]]]
280
+ dataset: str
281
+ revision: str
282
+ files: str
283
+ license: str | None
284
+ skipped_sentences: int
285
+
286
+ def counts(self) -> Counter:
287
+ """How many words (or documents) carry each label."""
288
+ return Counter(span.label for _, spans in self.docs for span in spans)
289
+
290
+
291
+ def load_split(
292
+ name: str,
293
+ *,
294
+ split: str | None = None,
295
+ subset: str | None = None,
296
+ column: str | None = None,
297
+ max_chars: int = 3000,
298
+ ) -> LoadedSplit:
299
+ """A preset's split at a pinned commit, as labelled documents."""
300
+ from huggingface_hub import HfApi, HfFileSystem
301
+
302
+ preset = CONCEPT_PRESETS[name]
303
+ column = column or preset.column
304
+ api = HfApi()
305
+ info = api.dataset_info(preset.dataset, revision=preset.revision)
306
+ fs = HfFileSystem()
307
+ subset = subset or preset.subset
308
+ files = preset.files.format(split=split or preset.split, subset=subset)
309
+ paths = fs.glob(f"datasets/{preset.dataset}@{info.sha}/{files}")
310
+ if not paths:
311
+ raise FileNotFoundError(f"no files match {files!r} in {preset.dataset}@{info.sha}")
312
+ if preset.layout == "document":
313
+ columns = [preset.text_column, column]
314
+ elif preset.layout == "conll":
315
+ columns = ["tokens", column]
316
+ else:
317
+ columns = [preset.text_column, "tokens", "mwt", "comments", column]
318
+ rows, names = _read_rows(fs, paths, columns)
319
+ docs, skipped = _documents(preset, column, rows, names, max_chars)
320
+ if preset.layout == "ud":
321
+ # Universal Dependencies licenses each treebank separately.
322
+ meta = json.loads(fs.read_text(f"datasets/{preset.dataset}@{info.sha}/metadata.json"))
323
+ license_ = meta.get(subset, {}).get("license")
324
+ else:
325
+ license_ = _license(api, preset.dataset, info, preset.revision)
326
+ return LoadedSplit(
327
+ docs,
328
+ preset.dataset,
329
+ info.sha,
330
+ files,
331
+ ", ".join(license_) if isinstance(license_, list) else license_,
332
+ skipped,
333
+ )
334
+
335
+
336
+ def sample_concept(
337
+ docs: Sequence[tuple[str, list[Span]]], label: str, documents: int, seed: int, balance: bool
338
+ ) -> list[tuple[str, list[tuple[int, int]]]]:
339
+ """Return up to ``documents`` documents with the spans of ``label``, in split order.
340
+
341
+ With ``balance``, half (rounded up) have a span and half don't, where there are enough.
342
+ """
343
+ marked = [[(s.start, s.end) for s in spans if tag_matches(s.label, label)] for _, spans in docs]
344
+ rng = random.Random(seed)
345
+ if balance:
346
+ pos = [i for i, m in enumerate(marked) if m]
347
+ neg = [i for i, m in enumerate(marked) if not m]
348
+ take_pos = min(len(pos), (documents + 1) // 2)
349
+ picked = rng.sample(pos, take_pos) + rng.sample(neg, min(len(neg), documents - take_pos))
350
+ else:
351
+ picked = rng.sample(range(len(docs)), min(documents, len(docs)))
352
+ return [(docs[i][0], marked[i]) for i in sorted(picked)]
353
+
354
+
355
+ def build_concept(
356
+ name: str,
357
+ label: str,
358
+ out: Path,
359
+ *,
360
+ split: str | None = None,
361
+ subset: str | None = None,
362
+ column: str | None = None,
363
+ documents: int = 1000,
364
+ seed: int = 0,
365
+ max_chars: int = 3000,
366
+ ) -> ConceptSource:
367
+ """Write ``label``'s concept set from preset ``name`` and its source sidecar."""
368
+ preset = CONCEPT_PRESETS[name]
369
+ loaded = load_split(name, split=split, subset=subset, column=column, max_chars=max_chars)
370
+ counts = loaded.counts()
371
+ if not any(tag_matches(tag, label) for tag in counts):
372
+ known = ", ".join(tag for tag, _ in counts.most_common())
373
+ raise ValueError(f"no {label!r} labels in {name}; it has: {known}")
374
+ level = "document" if preset.layout == "document" else "token"
375
+ rows = sample_concept(loaded.docs, label, documents, seed, balance=level == "document")
376
+ data = "".join(
377
+ json.dumps({"text": text, "spans": [list(s) for s in spans]}, ensure_ascii=False) + "\n"
378
+ for text, spans in rows
379
+ ).encode()
380
+ out.parent.mkdir(parents=True, exist_ok=True)
381
+ out.write_bytes(data)
382
+ source = ConceptSource(
383
+ preset=name,
384
+ dataset=loaded.dataset,
385
+ revision=loaded.revision,
386
+ files=loaded.files,
387
+ column=column or preset.column,
388
+ label=label,
389
+ level=level,
390
+ documents=len(rows),
391
+ positive_documents=sum(1 for _, spans in rows if spans),
392
+ spans=sum(len(spans) for _, spans in rows),
393
+ skipped_sentences=loaded.skipped_sentences,
394
+ seed=seed,
395
+ max_chars=max_chars,
396
+ license=loaded.license,
397
+ sha256=hashlib.sha256(data).hexdigest(),
398
+ )
399
+ source_path(out).write_text(source.model_dump_json(indent=2) + "\n", encoding="utf-8")
400
+ return source
@@ -0,0 +1,59 @@
1
+ """Schemas, identifiers and statistics that every record and command depends on."""
2
+
3
+ from openinterp.core.exceptions import (
4
+ AgentError,
5
+ ArchitectureNotSupportedError,
6
+ OpenInterpError,
7
+ PatchingError,
8
+ RegistryError,
9
+ ShapeMismatchError,
10
+ SyncError,
11
+ )
12
+ from openinterp.core.schemas import (
13
+ ActivatingExample,
14
+ AdversarialTestResult,
15
+ AuditRecord,
16
+ CausalMetrics,
17
+ ComponentIdentifier,
18
+ CounterfactualPair,
19
+ DictionaryProfile,
20
+ ExperimentProvenance,
21
+ FeatureIdentifier,
22
+ HeadIdentifier,
23
+ HeadProfile,
24
+ Histogram,
25
+ LookaheadProfile,
26
+ NeuronAuditSubmission,
27
+ NeuronIdentifier,
28
+ ReviewMetrics,
29
+ ReviewPolicy,
30
+ VerificationVerdict,
31
+ )
32
+
33
+ __all__ = [
34
+ "ActivatingExample",
35
+ "AdversarialTestResult",
36
+ "AgentError",
37
+ "ArchitectureNotSupportedError",
38
+ "AuditRecord",
39
+ "CausalMetrics",
40
+ "ComponentIdentifier",
41
+ "CounterfactualPair",
42
+ "DictionaryProfile",
43
+ "ExperimentProvenance",
44
+ "FeatureIdentifier",
45
+ "HeadIdentifier",
46
+ "HeadProfile",
47
+ "Histogram",
48
+ "LookaheadProfile",
49
+ "NeuronAuditSubmission",
50
+ "NeuronIdentifier",
51
+ "OpenInterpError",
52
+ "PatchingError",
53
+ "RegistryError",
54
+ "ReviewMetrics",
55
+ "ReviewPolicy",
56
+ "ShapeMismatchError",
57
+ "SyncError",
58
+ "VerificationVerdict",
59
+ ]
@@ -0,0 +1,76 @@
1
+ """One name per agent model, however a record spelled it.
2
+
3
+ ``submitting_agent_model``, ``reviewer_agent_model`` and ``judge_model`` are free text.
4
+ Sessions spell one model in several ways. For example:
5
+
6
+ - ``external/deepseek-flash[1m]`` and ``external/deepseek-flash``.
7
+ - ``external/gemini/gemini-3.8-flash`` and ``external/gemini-3.8-flash``.
8
+ - ``anthropic/claude-opus-5-5 (Claude Code session)`` and ``external/claude-opus-5-5``.
9
+
10
+ Records are signed, so the site build does not rewrite them. It maps each spelling to one
11
+ name with :func:`canonical_agent`. :func:`spelling_problem` rejects new spellings at
12
+ submission.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+
19
+ EXTERNAL_PREFIX = "external/"
20
+
21
+ # Spellings that name the same model, after the generic clean-up in canonical_agent.
22
+ # One 2026-10-26 session ran Grok Build but reported itself as Claude 3.5 Sonnet.
23
+ ALIASES: dict[str, str] = {
24
+ "claude-3-5-sonnet-20241022": "grok-build-0.1",
25
+ }
26
+
27
+ # Model families whose sessions report their version inconsistently. canonical_agent maps
28
+ # every name that starts with the key to the value. Muse Spark sessions have recorded
29
+ # muse-spark, muse-spark-3, muse-spark-1-3-contributor and others. "-contributor" is the
30
+ # Muse Spark pricing tier (openinterp.core.pricing.FAMILIES), not part of the model's
31
+ # name. Every run so far was 1.3 at the Contributor tier.
32
+ FAMILY_ALIASES: dict[str, str] = {
33
+ "muse-spark": "muse-spark-1.3",
34
+ }
35
+
36
+ # Shown on an agent's page when its recorded name is doubtful.
37
+ NOTES: dict[str, str] = {
38
+ "grok-build-0.1": (
39
+ "One session on 2026-10-26 ran this model but recorded itself as "
40
+ "claude-3-5-sonnet-20241022; its records are signed, so the name is remapped here."
41
+ ),
42
+ }
43
+
44
+
45
+ def canonical_agent(model: str) -> str:
46
+ """The model's one name: ``external/deepseek-flash[1m]`` → ``deepseek-flash``.
47
+
48
+ Drops the route (``external/``, a LiteLLM provider such as ``anthropic/``), a context
49
+ suffix (``[1m]``), a parenthesised remark and case. Then applies :data:`FAMILY_ALIASES`
50
+ and :data:`ALIASES`.
51
+ """
52
+ name = re.sub(r"\s*\(.*\)\s*$", "", model.strip()).removeprefix(EXTERNAL_PREFIX)
53
+ name = re.sub(r"\[[^\]]*\]$", "", name.rsplit("/", 1)[-1]).strip().lower()
54
+ for family, canonical in FAMILY_ALIASES.items():
55
+ if name.startswith(family):
56
+ return canonical
57
+ return ALIASES.get(name, name) or "unknown"
58
+
59
+
60
+ def same_agent(a: str | None, b: str | None) -> bool:
61
+ """True when both names are set and name one model after ``ALIASES`` and spelling fixes."""
62
+ return a is not None and b is not None and canonical_agent(a) == canonical_agent(b)
63
+
64
+
65
+ def spelling_problem(model: str) -> str | None:
66
+ """Why ``model`` isn't a name a new record may carry, or ``None`` when it is.
67
+
68
+ An external answerer must be ``external/<name>`` with ``<name>`` already canonical. A
69
+ LiteLLM string is what gets called, so it already names the model and passes.
70
+ """
71
+ if not model.startswith(EXTERNAL_PREFIX):
72
+ return None
73
+ expected = EXTERNAL_PREFIX + canonical_agent(model)
74
+ if model != expected:
75
+ return f"spell it {expected} (no provider, context size, remark or version alias)"
76
+ return None
@@ -0,0 +1,101 @@
1
+ """Schemas for input attribution: which context tokens drive a pole at its peak.
2
+
3
+ Integrated gradients (Sundararajan et al., 2017) run over the input token embeddings.
4
+ They attribute the pole's activation at each top example's peak token to every token up
5
+ to the peak. The baseline replaces every token with one reference token. A second check
6
+ replaces each token alone with the reference token and measures the pole again.
7
+ Reported, not gated (docs/PROTOCOL.md §4).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pydantic import Field
13
+
14
+ from openinterp.core.strict import StrictModel, UnitFloat
15
+
16
+
17
+ class AttributedToken(StrictModel):
18
+ """One context token's contribution to the pole at the peak.
19
+
20
+ Both numbers are fractions of one gap: the pole's activation on the example minus
21
+ its activation on the all-baseline input. So ``attribution`` sums to about 1 over
22
+ the tokens, and you can compare the two numbers directly.
23
+ """
24
+
25
+ offset: int = Field(le=0, description="Position relative to the peak token (0 = the peak).")
26
+ token: str
27
+ attribution: float = Field(description="Integrated-gradients share of the gap.")
28
+ ablation_effect: float | None = Field(
29
+ default=None,
30
+ description="Drop in activation when only this token is replaced, as a share of the gap.",
31
+ )
32
+
33
+
34
+ class ContextExample(StrictModel):
35
+ """Input attribution for one top example."""
36
+
37
+ example: int = Field(ge=0, description="Index into the scan's top examples.")
38
+ peak_token: str
39
+ tokens: int = Field(gt=0, description="Tokens attributed: the example up to its peak.")
40
+ steps: int = Field(gt=0, description="Integration steps used.")
41
+ converged: bool = Field(
42
+ description="completeness_error within the tolerance; only these count in the shares."
43
+ )
44
+ completeness_error: float = Field(
45
+ ge=0.0, description="|Σ attribution − 1|: how far the path integral is from converged."
46
+ )
47
+ top: list[AttributedToken] = Field(description="Largest |attribution| first.")
48
+
49
+
50
+ class DistanceShare(StrictModel):
51
+ """Share of total |attribution| on tokens ``min_distance``..``max_distance`` back."""
52
+
53
+ min_distance: int = Field(ge=0)
54
+ max_distance: int | None = Field(default=None, description="None: no upper bound.")
55
+ share: UnitFloat
56
+
57
+ @property
58
+ def label(self) -> str:
59
+ """The distance band as the page shows it: ``0``, ``1–3`` or ``8+``."""
60
+ if self.max_distance is None:
61
+ return f"{self.min_distance}+"
62
+ if self.max_distance == self.min_distance:
63
+ return str(self.min_distance)
64
+ return f"{self.min_distance}–{self.max_distance}"
65
+
66
+
67
+ class InputAttribution(StrictModel):
68
+ """Where in the context a pole's activation comes from, over its top examples.
69
+
70
+ ``distances`` splits each example's |attribution| by how far back each token is from
71
+ the peak, then averages the shares over examples. A high share at distance 0 means
72
+ that the pole fires on the current token. A high share far back means that it fires
73
+ because of earlier context.
74
+
75
+ ``ablation_agreement`` is Spearman's ρ between attribution and single-token ablation
76
+ effect. It uses every checked token: the peak, the tokens just before it and the most
77
+ attributed ones. A low value means that the linear path integral misreads this pole,
78
+ so trust the ablation effects.
79
+
80
+ Both use only the examples whose path integral converged. With none, they are empty.
81
+ """
82
+
83
+ method: str = "integrated_gradients"
84
+ baseline_token: str
85
+ tolerance: float = Field(gt=0.0, description="Largest completeness error accepted.")
86
+ examples: list[ContextExample]
87
+ distances: list[DistanceShare]
88
+ mean_distance: float | None = Field(
89
+ default=None, ge=0.0, description="|attribution|-weighted mean tokens back from the peak."
90
+ )
91
+ ablation_agreement: float | None = Field(default=None, ge=-1.0, le=1.0)
92
+
93
+ @property
94
+ def converged(self) -> int:
95
+ """How many examples converged; only these count in the shares."""
96
+ return sum(ex.converged for ex in self.examples)
97
+
98
+ @property
99
+ def current_token_share(self) -> float | None:
100
+ """The share at distance 0, the first band, or ``None`` with no converged example."""
101
+ return self.distances[0].share if self.distances else None
@@ -0,0 +1,15 @@
1
+ """Where the on-disk caches live."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+
9
+ def cache_root(env: str, name: str) -> Path | None:
10
+ """``$<env>``, else ``$XDG_CACHE_HOME/openinterp/<name>``; None if ``$<env>`` turns it off."""
11
+ value = os.environ.get(env)
12
+ if value is not None:
13
+ return None if value.lower() in ("", "0", "off", "false") else Path(value)
14
+ base = os.environ.get("XDG_CACHE_HOME") or Path.home() / ".cache"
15
+ return Path(base) / "openinterp" / name