File size: 1,983 Bytes
026f63e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | """Task-agnostic decision interfaces.
MiniJEV always answers the same kind of request::
DecisionRequest(state=..., question="...", choices=["a", "b", ...]) -> Decision
`state` is opaque to the core: each environment/task plugin knows how to turn
its states into tensors (see `minijev.tasks.Task`). Choices are plain strings,
so the number and the meaning of choices are *inputs*, not model constants.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Sequence
State = Any # task-specific (SnakeState, str, dict, ...)
@dataclass
class DecisionRequest:
state: State
question: str
choices: Sequence[str]
@dataclass
class Decision:
choice: str
index: int
confidence: float # calibrated probability of `choice`
scores: dict[str, float] # calibrated probability per choice
logits: list[float] = field(default_factory=list)
embedding: list[float] | None = None # optional shared state representation
raw_scores: dict[str, float] | None = None # softmax(logits), before temperature calibration
temperature: float = 1.0
@property
def distribution(self) -> dict[str, float]:
return self.scores
@property
def raw_confidence(self) -> float:
return self.raw_scores[self.choice] if self.raw_scores else self.confidence
def to_dict(self) -> dict:
d = {"choice": self.choice, "confidence": round(self.confidence, 4),
"distribution": {k: round(v, 4) for k, v in self.scores.items()}}
if self.raw_scores is not None:
d["raw_distribution"] = {k: round(v, 4) for k, v in self.raw_scores.items()}
if self.embedding is not None:
d["embedding"] = self.embedding
return d
@dataclass
class BooleanDecision:
answer: bool
confidence: float # calibrated probability of `answer`
p_yes: float
raw_p_yes: float
def __bool__(self) -> bool:
return self.answer
|