tensorcode 0.1.0a1__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.
- tensorcode/__init__.py +84 -0
- tensorcode/actions.py +137 -0
- tensorcode/answer_type.py +222 -0
- tensorcode/awareness.py +344 -0
- tensorcode/backends/__init__.py +0 -0
- tensorcode/backends/builtin.py +167 -0
- tensorcode/backends/hf_local.py +89 -0
- tensorcode/backends/linear.py +133 -0
- tensorcode/backends/neural.py +361 -0
- tensorcode/causal.py +262 -0
- tensorcode/change.py +566 -0
- tensorcode/chunking.py +195 -0
- tensorcode/cognition.py +311 -0
- tensorcode/context.py +97 -0
- tensorcode/control.py +291 -0
- tensorcode/cues.py +192 -0
- tensorcode/expectation.py +270 -0
- tensorcode/frames.py +232 -0
- tensorcode/language/__init__.py +36 -0
- tensorcode/language/chart.py +558 -0
- tensorcode/language/discourse.py +132 -0
- tensorcode/language/domains/__init__.py +0 -0
- tensorcode/language/domains/desktop.py +552 -0
- tensorcode/language/english.py +459 -0
- tensorcode/language/features.py +112 -0
- tensorcode/language/generate.py +574 -0
- tensorcode/language/grammar.py +893 -0
- tensorcode/language/semantics.py +349 -0
- tensorcode/learning/__init__.py +30 -0
- tensorcode/learning/certificate.py +148 -0
- tensorcode/learning/induce.py +304 -0
- tensorcode/learning/library.py +217 -0
- tensorcode/learning/literals.py +126 -0
- tensorcode/learning/verify.py +253 -0
- tensorcode/memory.py +303 -0
- tensorcode/metacognition.py +351 -0
- tensorcode/ops.py +207 -0
- tensorcode/outcomes.py +99 -0
- tensorcode/permanence.py +376 -0
- tensorcode/priming.py +191 -0
- tensorcode/py.typed +0 -0
- tensorcode/quantity.py +311 -0
- tensorcode/records.py +728 -0
- tensorcode/relation.py +771 -0
- tensorcode/runtime.py +471 -0
- tensorcode/semantics_bridge.py +308 -0
- tensorcode/social.py +380 -0
- tensorcode/temporal.py +189 -0
- tensorcode/wants.py +185 -0
- tensorcode-0.1.0a1.dist-info/METADATA +196 -0
- tensorcode-0.1.0a1.dist-info/RECORD +53 -0
- tensorcode-0.1.0a1.dist-info/WHEEL +4 -0
- tensorcode-0.1.0a1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Knowing what one knows: competence, decomposed confidence, repair, and agency.
|
|
2
|
+
|
|
3
|
+
Four faculties that a system needs about *itself*, each built because a measurement said
|
|
4
|
+
the thing it replaces does not work:
|
|
5
|
+
|
|
6
|
+
* :class:`SelfModel` — competence per *kind* of thing attempted, learned from outcomes and
|
|
7
|
+
consulted before trying. This is not the per-item capability router that
|
|
8
|
+
``docs/revival/13`` measured at AUC 0.55 (SQuAD) and 0.46 (HotpotQA, below chance): that
|
|
9
|
+
asked "will I get *this item* right" from features of the item. A competence prior asks
|
|
10
|
+
the cheaper question "how do I do on *this kind of thing*", which needs no per-item
|
|
11
|
+
signal at all — only that the kind is knowable before attempting, and that accuracy
|
|
12
|
+
actually varies by kind. Where it does not vary, the prior is worthless, and
|
|
13
|
+
:func:`SelfModel.competence` says so rather than inventing a number.
|
|
14
|
+
|
|
15
|
+
* :class:`Confidences` — one scalar cannot say "I am sure what you asked for but unsure
|
|
16
|
+
which file you meant" (``docs/revival/15`` §15.8). Confidence is carried per *commitment*,
|
|
17
|
+
so a gate can act on the part that is weak: an uncertain slot becomes a question about
|
|
18
|
+
that slot instead of a refusal of the whole request.
|
|
19
|
+
|
|
20
|
+
* :class:`Monitor` — noticing "that did not work", naming the failure, and choosing a
|
|
21
|
+
repair from a repertoire. The pathology it exists to stop is measured: a teacher loop
|
|
22
|
+
that re-issued an identical failing command three times, and an assistant that stopped
|
|
23
|
+
dead rather than trying differently.
|
|
24
|
+
|
|
25
|
+
* :func:`attribute` — every observed change is mine (predicted from my own action) or the
|
|
26
|
+
world's (unpredicted). Without this distinction surprise is meaningless, because every
|
|
27
|
+
consequence of one's own action looks like news.
|
|
28
|
+
|
|
29
|
+
Nothing here introspects on a model's own posterior: ``docs/revival/15`` measured that at
|
|
30
|
+
+0.9 points of selective accuracy for 2.8% refusals, with 446 of 464 items in the top
|
|
31
|
+
confidence bin. The signals here are external — outcome history, tier disagreement,
|
|
32
|
+
observed failure, and efference copy.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from dataclasses import dataclass, field
|
|
38
|
+
from math import sqrt
|
|
39
|
+
from typing import Any, Iterable, Literal, Mapping, Sequence
|
|
40
|
+
|
|
41
|
+
from .outcomes import Score, Unknown, Verdict
|
|
42
|
+
|
|
43
|
+
MIN_TRIALS = 8 # below this a rate is not a competence estimate; back off to a coarser kind
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# --------------------------------------------------------------- competence
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _wilson_lower(correct: int, attempts: int, z: float = 1.96) -> float:
|
|
50
|
+
"""The conservative end of a Wilson interval: what I can claim, not what I hope."""
|
|
51
|
+
if attempts == 0:
|
|
52
|
+
return 0.0
|
|
53
|
+
p = correct / attempts
|
|
54
|
+
d = 1 + z * z / attempts
|
|
55
|
+
centre = p + z * z / (2 * attempts)
|
|
56
|
+
spread = z * sqrt(p * (1 - p) / attempts + z * z / (4 * attempts * attempts))
|
|
57
|
+
return max(0.0, (centre - spread) / d)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class Competence:
|
|
62
|
+
"""How I have done at one kind of thing, and how sure that estimate is."""
|
|
63
|
+
|
|
64
|
+
kind: str
|
|
65
|
+
attempts: int
|
|
66
|
+
correct: int
|
|
67
|
+
backed_off_from: str | None = None # the finer kind that had too little history
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def rate(self) -> float:
|
|
71
|
+
return self.correct / self.attempts if self.attempts else 0.0
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def lower(self) -> float:
|
|
75
|
+
return _wilson_lower(self.correct, self.attempts)
|
|
76
|
+
|
|
77
|
+
def score(self) -> Score:
|
|
78
|
+
"""The conservative estimate, as a probability whose basis names the history."""
|
|
79
|
+
basis = f"self-model:{self.kind}@{self.attempts}"
|
|
80
|
+
if self.backed_off_from:
|
|
81
|
+
basis += f" (backed off from {self.backed_off_from})"
|
|
82
|
+
return Score(self.lower, "probability", basis=basis)
|
|
83
|
+
|
|
84
|
+
def describe(self) -> str:
|
|
85
|
+
via = f", via {self.backed_off_from}" if self.backed_off_from else ""
|
|
86
|
+
return f"{self.kind}: {self.correct}/{self.attempts} = {self.rate:.3f} (lower {self.lower:.3f}{via})"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class SelfModel:
|
|
91
|
+
"""Outcome history per kind, with backoff from a specific kind to a coarser family.
|
|
92
|
+
|
|
93
|
+
``kinds`` are caller-chosen strings ordered specific-to-general, e.g.
|
|
94
|
+
``("squad2/how_many", "squad2", "extractive_qa")``. Backoff is what makes the model
|
|
95
|
+
usable on a kind it has never seen: a never-attempted question type still inherits the
|
|
96
|
+
dataset's rate, and an unknown dataset inherits the family's.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
name: str = "self-model"
|
|
100
|
+
attempts: dict[str, int] = field(default_factory=dict)
|
|
101
|
+
correct: dict[str, int] = field(default_factory=dict)
|
|
102
|
+
|
|
103
|
+
def record(self, kinds: Sequence[str], ok: bool) -> None:
|
|
104
|
+
"""Credit an outcome to every level of the hierarchy at once."""
|
|
105
|
+
for kind in kinds:
|
|
106
|
+
self.attempts[kind] = self.attempts.get(kind, 0) + 1
|
|
107
|
+
self.correct[kind] = self.correct.get(kind, 0) + (1 if ok else 0)
|
|
108
|
+
|
|
109
|
+
def competence(self, kinds: Sequence[str], *, min_trials: int = MIN_TRIALS) -> Competence | Unknown:
|
|
110
|
+
"""The most specific kind with enough history to speak for, or ``Unknown``."""
|
|
111
|
+
finest = kinds[0] if kinds else "?"
|
|
112
|
+
for i, kind in enumerate(kinds):
|
|
113
|
+
n = self.attempts.get(kind, 0)
|
|
114
|
+
if n >= min_trials:
|
|
115
|
+
return Competence(kind, n, self.correct.get(kind, 0), backed_off_from=finest if i else None)
|
|
116
|
+
return Unknown("no_competence_history", f"fewer than {min_trials} attempts at any of {list(kinds)}")
|
|
117
|
+
|
|
118
|
+
def worth_attempting(self, kinds: Sequence[str], *, floor: float, **kw: Any) -> Verdict:
|
|
119
|
+
"""Should I try? ``unknown`` when I have no history — which is not the same as no.
|
|
120
|
+
|
|
121
|
+
A caller that treats ``unknown`` as a refusal will never attempt anything new; one
|
|
122
|
+
that treats it as permission is merely uninformed. The distinction is the point.
|
|
123
|
+
"""
|
|
124
|
+
got = self.competence(kinds, **kw)
|
|
125
|
+
if isinstance(got, Unknown):
|
|
126
|
+
return Verdict("unknown", (got.detail,))
|
|
127
|
+
if got.lower >= floor:
|
|
128
|
+
return Verdict("holds", (f"{got.describe()} at or above floor {floor:.2f}",))
|
|
129
|
+
return Verdict("fails", (f"{got.describe()} below floor {floor:.2f}",))
|
|
130
|
+
|
|
131
|
+
def table(self, *, min_trials: int = MIN_TRIALS) -> list[Competence]:
|
|
132
|
+
rows = [Competence(k, n, self.correct.get(k, 0)) for k, n in self.attempts.items() if n >= min_trials]
|
|
133
|
+
return sorted(rows, key=lambda c: c.lower)
|
|
134
|
+
|
|
135
|
+
def spread(self, kinds: Iterable[str], *, min_trials: int = MIN_TRIALS) -> float:
|
|
136
|
+
"""How much competence varies across these kinds — how much a prior could buy.
|
|
137
|
+
|
|
138
|
+
Near zero means every kind is alike and a competence prior is worthless however
|
|
139
|
+
well estimated. This is the number to look at *before* building a gate.
|
|
140
|
+
"""
|
|
141
|
+
rates = [self.correct.get(k, 0) / self.attempts[k] for k in kinds if self.attempts.get(k, 0) >= min_trials]
|
|
142
|
+
return max(rates) - min(rates) if len(rates) > 1 else 0.0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ------------------------------------------------------ decomposed confidence
|
|
146
|
+
|
|
147
|
+
Commitment = Literal["speech_act", "act", "slot", "value", "evidence"]
|
|
148
|
+
#: what a gate should do about the weakest commitment of each kind
|
|
149
|
+
REPAIRABLE: dict[str, str] = {"slot": "ask", "value": "refuse", "act": "ask", "speech_act": "refuse", "evidence": "refuse"}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True)
|
|
153
|
+
class Belief:
|
|
154
|
+
"""One thing the system has committed to, with its own confidence."""
|
|
155
|
+
|
|
156
|
+
commitment: Commitment
|
|
157
|
+
about: str # which slot / which act / which value
|
|
158
|
+
value: Any
|
|
159
|
+
score: Score | None = None # None means "no confidence reported", not zero
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def strength(self) -> float:
|
|
163
|
+
return self.score.value if self.score else 0.0
|
|
164
|
+
|
|
165
|
+
def describe(self) -> str:
|
|
166
|
+
s = f"{self.score.value:.2f}" if self.score else "unreported"
|
|
167
|
+
return f"{self.commitment}:{self.about}={self.value!r} ({s})"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass(frozen=True)
|
|
171
|
+
class Gate:
|
|
172
|
+
"""What to do, and about what."""
|
|
173
|
+
|
|
174
|
+
decision: Literal["act", "ask", "refuse"]
|
|
175
|
+
about: str = ""
|
|
176
|
+
why: str = ""
|
|
177
|
+
|
|
178
|
+
def describe(self) -> str:
|
|
179
|
+
target = f" about {self.about}" if self.about else ""
|
|
180
|
+
return f"{self.decision}{target}: {self.why}"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@dataclass(frozen=True)
|
|
184
|
+
class Confidences:
|
|
185
|
+
"""Confidence per commitment, and a gate that acts on the weakest one."""
|
|
186
|
+
|
|
187
|
+
parts: tuple[Belief, ...]
|
|
188
|
+
|
|
189
|
+
def weakest(self) -> Belief | None:
|
|
190
|
+
return min(self.parts, key=lambda b: b.strength) if self.parts else None
|
|
191
|
+
|
|
192
|
+
def of(self, commitment: str) -> tuple[Belief, ...]:
|
|
193
|
+
return tuple(b for b in self.parts if b.commitment == commitment)
|
|
194
|
+
|
|
195
|
+
def gate(self, *, act_at: float, ask_below: float) -> Gate:
|
|
196
|
+
"""Act when every commitment is strong; otherwise repair the weakest.
|
|
197
|
+
|
|
198
|
+
The difference from a single scalar: a weak *slot* asks a question about that slot,
|
|
199
|
+
while a weak *value* or unreadable *speech act* refuses. A flat gate cannot tell
|
|
200
|
+
those apart and refuses all three.
|
|
201
|
+
"""
|
|
202
|
+
weak = self.weakest()
|
|
203
|
+
if weak is None:
|
|
204
|
+
return Gate("refuse", why="nothing was committed to")
|
|
205
|
+
if weak.strength >= act_at:
|
|
206
|
+
return Gate("act", why=f"weakest commitment {weak.describe()} at or above {act_at:.2f}")
|
|
207
|
+
how = REPAIRABLE.get(weak.commitment, "refuse")
|
|
208
|
+
if how == "ask" and weak.strength < ask_below:
|
|
209
|
+
return Gate("refuse", weak.about, f"{weak.describe()} too weak even to ask about")
|
|
210
|
+
if how == "ask":
|
|
211
|
+
return Gate("ask", weak.about, f"{weak.describe()} is the weak part; the rest is fine")
|
|
212
|
+
return Gate("refuse", weak.about, f"{weak.describe()} below {act_at:.2f} and not a question I can ask")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def agreement(readings: Sequence[Any], *, basis: str) -> Score:
|
|
216
|
+
"""Confidence from independent readers agreeing, not from one reader's posterior.
|
|
217
|
+
|
|
218
|
+
``docs/revival/15`` measured a trained model's own posterior as nearly useless for
|
|
219
|
+
abstention (+0.9 points for 2.8% refusals). Disagreement between differently-built
|
|
220
|
+
readers is an external signal about the same commitment.
|
|
221
|
+
"""
|
|
222
|
+
kept = [r for r in readings if r is not None]
|
|
223
|
+
if not kept:
|
|
224
|
+
return Score(0.0, "vote_share", basis=f"{basis}: nothing read")
|
|
225
|
+
top = max(kept.count(r) for r in kept)
|
|
226
|
+
return Score(top / len(readings), "vote_share", basis=f"{basis}: {top}/{len(readings)} readers agree")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# -------------------------------------------------------- error and repair
|
|
230
|
+
|
|
231
|
+
FailureKind = Literal[
|
|
232
|
+
"no_effect", # the act went through and nothing changed
|
|
233
|
+
"error_output", # the environment said no
|
|
234
|
+
"target_missing", # what I aimed at is not there
|
|
235
|
+
"stale_reference", # it was there and has moved or changed
|
|
236
|
+
"timeout", # it never finished
|
|
237
|
+
"wrong_result", # it finished and the result is not what was expected
|
|
238
|
+
"crashed", # my own machinery broke
|
|
239
|
+
]
|
|
240
|
+
RepairKind = Literal["retry", "retry_differently", "reperceive", "ask", "give_up"]
|
|
241
|
+
|
|
242
|
+
#: first repair to consider per failure, before history is taken into account
|
|
243
|
+
FIRST_REPAIR: dict[str, RepairKind] = {
|
|
244
|
+
"no_effect": "retry_differently",
|
|
245
|
+
"error_output": "retry_differently",
|
|
246
|
+
"target_missing": "ask",
|
|
247
|
+
"stale_reference": "reperceive",
|
|
248
|
+
"timeout": "retry",
|
|
249
|
+
"wrong_result": "retry_differently",
|
|
250
|
+
"crashed": "give_up",
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@dataclass(frozen=True)
|
|
255
|
+
class Attempt:
|
|
256
|
+
action: str
|
|
257
|
+
failure: FailureKind | None = None
|
|
258
|
+
detail: str = ""
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def failed(self) -> bool:
|
|
262
|
+
return self.failure is not None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@dataclass(frozen=True)
|
|
266
|
+
class Repair:
|
|
267
|
+
kind: RepairKind
|
|
268
|
+
why: str
|
|
269
|
+
avoid: tuple[str, ...] = () # actions already known not to work
|
|
270
|
+
|
|
271
|
+
def describe(self) -> str:
|
|
272
|
+
skip = f" (not {', '.join(self.avoid)})" if self.avoid else ""
|
|
273
|
+
return f"{self.kind}: {self.why}{skip}"
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@dataclass
|
|
277
|
+
class Monitor:
|
|
278
|
+
"""A record of what has already failed, and a repertoire of what to do next.
|
|
279
|
+
|
|
280
|
+
The one rule it will not break: never propose repeating an action that has already
|
|
281
|
+
failed the same way. That is the measured pathology — an identical failing command
|
|
282
|
+
re-issued three times — and it is a property of the *repertoire*, not of any model.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
history: list[Attempt] = field(default_factory=list)
|
|
286
|
+
patience: int = 2 # identical-failure retries allowed before escalating the repair
|
|
287
|
+
|
|
288
|
+
def note(self, action: str, failure: FailureKind | None = None, detail: str = "") -> Attempt:
|
|
289
|
+
attempt = Attempt(action, failure, detail)
|
|
290
|
+
self.history.append(attempt)
|
|
291
|
+
return attempt
|
|
292
|
+
|
|
293
|
+
def failed_before(self, action: str, failure: FailureKind | None = None) -> int:
|
|
294
|
+
return sum(1 for a in self.history if a.action == action and a.failed and (failure is None or a.failure == failure))
|
|
295
|
+
|
|
296
|
+
def dead_ends(self) -> tuple[str, ...]:
|
|
297
|
+
return tuple(dict.fromkeys(a.action for a in self.history if a.failed))
|
|
298
|
+
|
|
299
|
+
def repair(self, action: str, failure: FailureKind, *, detail: str = "") -> Repair:
|
|
300
|
+
"""Choose a repair, escalating as the same failure recurs."""
|
|
301
|
+
seen = self.failed_before(action, failure)
|
|
302
|
+
first = FIRST_REPAIR.get(failure, "retry_differently")
|
|
303
|
+
avoid = self.dead_ends()
|
|
304
|
+
if seen > self.patience:
|
|
305
|
+
return Repair("give_up", f"{action!r} failed {seen}x with {failure}; no repair left to try", avoid)
|
|
306
|
+
if first == "retry" and seen >= 1:
|
|
307
|
+
return Repair("retry_differently", f"{action!r} already failed {seen}x with {failure}; a plain retry is the same act", avoid)
|
|
308
|
+
if first == "retry_differently" and seen >= self.patience:
|
|
309
|
+
return Repair("ask", f"{action!r} failed {seen}x with {failure}; I cannot find a different way alone", avoid)
|
|
310
|
+
why = f"{failure}" + (f": {detail}" if detail else "")
|
|
311
|
+
return Repair(first, why, avoid)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ---------------------------------------------------------------- agency
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
Attribution = Literal["self", "world", "both", "unexplained"]
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def attribute(observed: Mapping[str, Any], *, before: Mapping[str, Any], predicted: Mapping[str, Any] | None = None,
|
|
321
|
+
acted: bool = True) -> dict[str, Attribution]:
|
|
322
|
+
"""Which changes were mine, and which the world's.
|
|
323
|
+
|
|
324
|
+
``predicted`` is the efference copy: what my own action said would change. A changed
|
|
325
|
+
aspect that my action predicted is mine; one it did not predict is the world's; an
|
|
326
|
+
aspect that changed while I did nothing is the world's by construction. Predicting a
|
|
327
|
+
change that did not happen is *not* a change, and is the business of
|
|
328
|
+
:func:`tensorcode.expectation.check`, not of this function.
|
|
329
|
+
"""
|
|
330
|
+
predicted = predicted or {}
|
|
331
|
+
out: dict[str, Attribution] = {}
|
|
332
|
+
for aspect, now in observed.items():
|
|
333
|
+
if aspect in before and before[aspect] == now:
|
|
334
|
+
continue # nothing changed; nothing to attribute
|
|
335
|
+
if not acted:
|
|
336
|
+
out[aspect] = "world"
|
|
337
|
+
continue
|
|
338
|
+
if aspect not in predicted:
|
|
339
|
+
out[aspect] = "world"
|
|
340
|
+
elif predicted[aspect] == now:
|
|
341
|
+
out[aspect] = "self"
|
|
342
|
+
else:
|
|
343
|
+
# my action touched this aspect but the result is not what it predicted:
|
|
344
|
+
# something of mine and something else both moved it
|
|
345
|
+
out[aspect] = "both"
|
|
346
|
+
return out
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def surprising(attributions: Mapping[str, Attribution]) -> tuple[str, ...]:
|
|
350
|
+
"""The aspects worth attention: the ones I did not cause."""
|
|
351
|
+
return tuple(a for a, kind in attributions.items() if kind in ("world", "both", "unexplained"))
|
tensorcode/ops.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Typed operation facades.
|
|
2
|
+
|
|
3
|
+
Each facade fixes the *meaning* of an operation and validates outputs against it.
|
|
4
|
+
Which code actually runs (rules, a parser, a classifier, a solver, a model) is
|
|
5
|
+
decided by the bound ``Runtime``; with nothing bound, inferential facades return
|
|
6
|
+
``Unknown(reason="no_implementation")``. None of them implies a model call.
|
|
7
|
+
|
|
8
|
+
Families (see docs/revival/03-operations-and-backends.md):
|
|
9
|
+
infer: parse, classify, choose, rank
|
|
10
|
+
check: check, verify
|
|
11
|
+
rewrite: propose (``Store.apply`` commits)
|
|
12
|
+
invoke: invoke (actions.py)
|
|
13
|
+
convert: to_records, from_records, pack (records.py, context.py)
|
|
14
|
+
query: Store.claims / match / neighborhood (records.py)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import enum
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Any, Callable, Sequence, TypeVar
|
|
22
|
+
|
|
23
|
+
from .outcomes import Receipt, Score, Unknown, Verdict
|
|
24
|
+
from .records import Patch
|
|
25
|
+
from .runtime import Request, current
|
|
26
|
+
|
|
27
|
+
T = TypeVar("T")
|
|
28
|
+
A = TypeVar("A")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _is_instance_of(target: Any) -> Callable[[Any], bool]:
|
|
32
|
+
def check(value: Any) -> bool:
|
|
33
|
+
try:
|
|
34
|
+
return isinstance(value, target)
|
|
35
|
+
except TypeError: # parametrized generics, Literal, ...: the implementation owns validation
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
return check
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ------------------------------------------------------------------- infer
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse(source: Any, into: type[T], **params: Any) -> T | Unknown:
|
|
45
|
+
"""Interpret ``source`` (text, a document, an observation) as a value of type ``into``.
|
|
46
|
+
|
|
47
|
+
Fails closed: an output that is not an ``into`` is treated as a failed attempt.
|
|
48
|
+
"""
|
|
49
|
+
return current().call(Request("parse", source, into, params), validate=_is_instance_of(into)).value
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse_many(sources: Sequence[Any], into: type[T], **params: Any) -> list[T | Unknown]:
|
|
53
|
+
outs = current().call_many([Request("parse", s, into, params) for s in sources], validate=_is_instance_of(into))
|
|
54
|
+
return [o.value for o in outs]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
parse.many = _parse_many # type: ignore[attr-defined]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def classify(item: Any, labels: type[T], **params: Any) -> T | Unknown:
|
|
61
|
+
"""Estimate which label *is true* of ``item``. Not a decision about what to do."""
|
|
62
|
+
return current().call(Request("classify", item, labels, params), validate=_label_check(labels)).value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _classify_many(items: Sequence[Any], labels: type[T], **params: Any) -> list[T | Unknown]:
|
|
66
|
+
outs = current().call_many([Request("classify", x, labels, params) for x in items], validate=_label_check(labels))
|
|
67
|
+
return [o.value for o in outs]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _classify_scored(item: Any, labels: type[T], **params: Any) -> tuple[T | Unknown, Score | None]:
|
|
71
|
+
"""The label *and* the confidence its implementation reported, for callers that gate on it.
|
|
72
|
+
|
|
73
|
+
An implementation that reports no confidence yields ``None`` — which a gate must treat as
|
|
74
|
+
"unknown confidence", not as zero, or an accurate tier with no score silently becomes an
|
|
75
|
+
escalation. Measured: a keyword tier answering 173/3,080 at 97.1% precision made a cascade
|
|
76
|
+
*worse* than the learned tier alone until its precision was reported as a probability.
|
|
77
|
+
"""
|
|
78
|
+
out = current().call(Request("classify", item, labels, params), validate=_label_check(labels))
|
|
79
|
+
return out.value, out.score
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _classify_many_scored(items: Sequence[Any], labels: type[T], **params: Any) -> list[tuple[T | Unknown, Score | None]]:
|
|
83
|
+
outs = current().call_many([Request("classify", x, labels, params) for x in items], validate=_label_check(labels))
|
|
84
|
+
return [(o.value, o.score) for o in outs]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
classify.many = _classify_many # type: ignore[attr-defined]
|
|
88
|
+
classify.scored = _classify_scored # type: ignore[attr-defined]
|
|
89
|
+
classify.many_scored = _classify_many_scored # type: ignore[attr-defined]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _label_check(labels: Any) -> Callable[[Any], bool]:
|
|
93
|
+
if isinstance(labels, type) and issubclass(labels, enum.Enum):
|
|
94
|
+
return lambda v: isinstance(v, labels)
|
|
95
|
+
return lambda v: v in labels
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True)
|
|
99
|
+
class Objective:
|
|
100
|
+
"""What makes one feasible option better than another."""
|
|
101
|
+
|
|
102
|
+
name: str
|
|
103
|
+
description: str
|
|
104
|
+
utility: Callable[[Any, Any], float] | None = None # (option, given) -> utility, when computable
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class Constraint:
|
|
109
|
+
"""A hard requirement checked by TensorCode itself, never delegated to a backend."""
|
|
110
|
+
|
|
111
|
+
name: str
|
|
112
|
+
test: Callable[[Any, Any], bool | Unknown] # (option, given)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def choose(options: Sequence[A], *, objective: Objective, given: Any = None, constraints: Sequence[Constraint] = ()) -> A | Unknown:
|
|
116
|
+
"""Select an action. Unlike ``classify``, this involves an objective and hard constraints.
|
|
117
|
+
|
|
118
|
+
Constraints are evaluated here, before any backend sees the options. An
|
|
119
|
+
undetermined constraint (``Unknown``) excludes the option. The backend can only
|
|
120
|
+
return one of the feasible options; anything else is an invalid attempt.
|
|
121
|
+
"""
|
|
122
|
+
feasible, notes = [], []
|
|
123
|
+
for option in options:
|
|
124
|
+
failed = []
|
|
125
|
+
for c in constraints:
|
|
126
|
+
ok = c.test(option, given)
|
|
127
|
+
if isinstance(ok, Unknown):
|
|
128
|
+
failed.append(f"{c.name}=unknown({ok.reason})")
|
|
129
|
+
elif not ok:
|
|
130
|
+
failed.append(c.name)
|
|
131
|
+
if failed:
|
|
132
|
+
notes.append(f"excluded {option!r}: {', '.join(failed)}")
|
|
133
|
+
else:
|
|
134
|
+
feasible.append(option)
|
|
135
|
+
rt = current()
|
|
136
|
+
if not feasible:
|
|
137
|
+
span = rt.trace.open("choose", target=objective.name, input=tuple(options))
|
|
138
|
+
span.notes.extend(notes or ["no options were proposed"])
|
|
139
|
+
return span.close(Unknown("no_feasible_option", "; ".join(notes)), "unknown")
|
|
140
|
+
if len(feasible) == 1:
|
|
141
|
+
span = rt.trace.open("choose", target=objective.name, input=tuple(options))
|
|
142
|
+
span.notes.extend(notes + ["only one feasible option; no backend consulted"])
|
|
143
|
+
return span.close(feasible[0], "answer")
|
|
144
|
+
request = Request("choose", tuple(feasible), objective, {"given": given})
|
|
145
|
+
return rt.call(request, validate=lambda v: any(v is o or v == o for o in feasible), notes=notes).value
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def rank(query: Any, candidates: Sequence[A], *, limit: int | None = None, **params: Any) -> list[tuple[A, Score]] | Unknown:
|
|
149
|
+
"""Order candidates by relevance to ``query``. Scores are comparable only within this ranking."""
|
|
150
|
+
|
|
151
|
+
def valid(v: Any) -> bool:
|
|
152
|
+
return isinstance(v, list) and all(
|
|
153
|
+
isinstance(pair, tuple) and len(pair) == 2 and isinstance(pair[1], Score) and pair[1].kind in ("relevance", "similarity")
|
|
154
|
+
for pair in v
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
out = current().call(Request("rank", query, None, {"candidates": tuple(candidates), **params}), validate=valid).value
|
|
158
|
+
return out if isinstance(out, Unknown) or limit is None else out[:limit]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ------------------------------------------------------------------- check
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def check(proposition: Any, *, evidence: Sequence[Any] = (), **params: Any) -> Verdict:
|
|
165
|
+
"""Evaluate a claim, candidate, or constraint against evidence. Unknown is not false."""
|
|
166
|
+
out = current().call(Request("check", proposition, None, {"evidence": tuple(evidence), **params}), validate=lambda v: isinstance(v, Verdict)).value
|
|
167
|
+
return Verdict("unknown", (f"{out.reason}: {out.detail}",)) if isinstance(out, Unknown) else out
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _check_many(propositions: Sequence[Any], *, evidence: Sequence[Any] = (), **params: Any) -> list[Verdict]:
|
|
171
|
+
requests = [Request("check", p, None, {"evidence": tuple(evidence), **params}) for p in propositions]
|
|
172
|
+
outs = current().call_many(requests, validate=lambda v: isinstance(v, Verdict))
|
|
173
|
+
return [Verdict("unknown", (f"{o.value.reason}: {o.value.detail}",)) if isinstance(o.value, Unknown) else o.value for o in outs]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
check.many = _check_many # type: ignore[attr-defined]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def verify(receipt: Receipt, *, observe: Callable[[], Any], expect: Callable[[Any], bool | Unknown]) -> Verdict:
|
|
180
|
+
"""Did an invoked action have its intended effect, according to a fresh observation?
|
|
181
|
+
|
|
182
|
+
A receipt saying ``applied`` is the executor's report, not verification.
|
|
183
|
+
"""
|
|
184
|
+
rt = current()
|
|
185
|
+
span = rt.trace.open("verify", target=type(receipt.action).__name__, input=receipt.status)
|
|
186
|
+
if receipt.status in ("rejected", "failed"):
|
|
187
|
+
verdict = Verdict("fails", (f"receipt status {receipt.status}: {receipt.error}",))
|
|
188
|
+
else:
|
|
189
|
+
observation = observe()
|
|
190
|
+
if isinstance(observation, Unknown):
|
|
191
|
+
verdict = Verdict("unknown", (f"observation unavailable: {observation.reason}",))
|
|
192
|
+
else:
|
|
193
|
+
ok = expect(observation)
|
|
194
|
+
if isinstance(ok, Unknown):
|
|
195
|
+
verdict = Verdict("unknown", (ok.reason,), (observation,))
|
|
196
|
+
else:
|
|
197
|
+
verdict = Verdict("holds" if ok else "fails", ("postcondition observed" if ok else "postcondition not observed",), (observation,))
|
|
198
|
+
return span.close(verdict, "answer" if verdict.status != "unknown" else "unknown")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ----------------------------------------------------------------- rewrite
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def propose(state: Any, *, goal: Any, base_revision: int, **params: Any) -> Patch | Unknown:
|
|
205
|
+
"""Propose a change to structured state. The result is inert until applied."""
|
|
206
|
+
request = Request("propose", state, goal, {"base_revision": base_revision, **params})
|
|
207
|
+
return current().call(request, validate=lambda v: isinstance(v, Patch) and v.base_revision == base_revision).value
|
tensorcode/outcomes.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Outcome values returned by operations.
|
|
2
|
+
|
|
3
|
+
These are ordinary values, not execution wrappers. Each one encodes a semantic
|
|
4
|
+
distinction that a caller must not silently collapse:
|
|
5
|
+
|
|
6
|
+
* ``Unknown`` is not ``False`` and not a low-confidence guess.
|
|
7
|
+
* ``Verdict`` has three states; ``fails`` and ``unknown`` are different.
|
|
8
|
+
* ``Receipt`` distinguishes "did not happen" from "may have happened".
|
|
9
|
+
* ``Score`` says what kind of number it is (a similarity is not a probability).
|
|
10
|
+
|
|
11
|
+
Execution metadata (backend, timing, cost, escalation) lives in the trace, not here.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from typing import Any, Literal
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _no_truth_value(self: Any) -> bool:
|
|
22
|
+
raise TypeError(
|
|
23
|
+
f"{type(self).__name__} has no truth value; handle it explicitly "
|
|
24
|
+
"(e.g. `isinstance(x, Unknown)` or `verdict.status == 'holds'`)"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class Unknown:
|
|
30
|
+
"""An operation could not produce an answer it is entitled to give.
|
|
31
|
+
|
|
32
|
+
``candidates`` are best-effort guesses with their scores. They are *not* answers.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
reason: str
|
|
36
|
+
detail: str = ""
|
|
37
|
+
candidates: tuple[tuple[Any, "Score"], ...] = ()
|
|
38
|
+
|
|
39
|
+
__bool__ = _no_truth_value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
ScoreKind = Literal[
|
|
43
|
+
"probability", # calibrated on a named dataset (see ``basis``)
|
|
44
|
+
"uncalibrated", # model-internal confidence; ordering only
|
|
45
|
+
"similarity", # geometric closeness; not a probability of anything
|
|
46
|
+
"relevance", # retrieval score; comparable only within one ranking
|
|
47
|
+
"utility", # objective value under a declared objective
|
|
48
|
+
"vote_share", # fraction of voters/samples
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class Score:
|
|
54
|
+
value: float
|
|
55
|
+
kind: ScoreKind
|
|
56
|
+
basis: str = "" # e.g. "banking77/validation@2026-09-16" for a calibrated probability
|
|
57
|
+
|
|
58
|
+
def __post_init__(self) -> None:
|
|
59
|
+
if self.kind == "probability" and not self.basis:
|
|
60
|
+
raise ValueError("a calibrated probability must name its calibration basis")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class Verdict:
|
|
65
|
+
"""Result of evaluating a proposition, candidate, or constraint against evidence."""
|
|
66
|
+
|
|
67
|
+
status: Literal["holds", "fails", "unknown"]
|
|
68
|
+
reasons: tuple[str, ...] = ()
|
|
69
|
+
evidence: tuple[Any, ...] = () # Refs or observations actually consulted
|
|
70
|
+
|
|
71
|
+
__bool__ = _no_truth_value
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def holds(self) -> bool:
|
|
75
|
+
return self.status == "holds"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
ReceiptStatus = Literal[
|
|
79
|
+
"applied", # the effect happened and the executor observed it
|
|
80
|
+
"rejected", # the executor refused before any effect (policy, validation, auth)
|
|
81
|
+
"failed", # attempted; known not to have taken effect
|
|
82
|
+
"indeterminate", # attempted; may or may not have taken effect (timeouts, lost replies)
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class Receipt:
|
|
88
|
+
"""What an executor can honestly say about one invocation of an action."""
|
|
89
|
+
|
|
90
|
+
action: Any
|
|
91
|
+
status: ReceiptStatus
|
|
92
|
+
retryable: bool = False # safe to retry *as far as the executor knows*
|
|
93
|
+
idempotency_key: str | None = None
|
|
94
|
+
effect_id: str | None = None
|
|
95
|
+
error: str | None = None
|
|
96
|
+
retry_after_s: float | None = None
|
|
97
|
+
at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
98
|
+
|
|
99
|
+
__bool__ = _no_truth_value
|