Handle errors¶
Every failure the library anticipates raises one of its own exceptions, and the message names the thing that is wrong and what would fix it. Anything else (an out-of-memory error from torch, a network error from the Hub) propagates unchanged.
The hierarchy¶
Exception
└── TypeDecideError catch this to catch anything raised deliberately
├── SchemaError a decision, criterion or option is not well formed
├── DataError a dataset could not be read, or is unusable as given
├── PromptError the prompt and the tokenizer disagree
├── TrainingError training cannot start, or cannot be trusted
├── ExportError a model could not be turned into the artefact asked for
├── ConfigError a config file or invocation does not describe a runnable job
└── EvaluationError scoring started from usable inputs and could not produce a score
└── ScorerError a ScoreFn broke its contract (also a ConfigError; see below)
All of them are importable from the top-level package and from typedecide.errors:
from typedecide import (
ConfigError, DataError, EvaluationError, ExportError, PromptError,
SchemaError, ScorerError, TrainingError, TypeDecideError,
)
Which class, from where¶
| Exception | Raised by | Typical causes |
|---|---|---|
SchemaError |
typedecide.schema |
Invalid option id or criterion key; fewer than 2 or more than 20 options; duplicate option id; answer_id that is not one of the options; empty id, state or question; a reordered() argument that is not a permutation |
DataError |
typedecide.data (loaders, group_split, all_permutations, Finding), typedecide.evaluation.metrics, evaluate_with_scorer |
Unreadable or malformed file; missing column; ambiguous option shape; answer that matches no option; a missing parquet or datasets dependency; a split that would hide a class; an eval set with no labelled rows; mismatched metric inputs |
PromptError |
typedecide.prompt |
No stable answer-letter continuation for this tokenizer; a prompt that encodes to zero tokens; a state prefix too short to reuse |
TrainingError |
typedecide.training |
Missing torch, transformers or peft; unlabelled training rows; an empty dataset; a criterion too long for max_length; LoRA cannot attach; tokenizer or model cannot load. A PromptError during training is re-raised as TrainingError with the decision id attached |
ExportError |
typedecide.export |
Missing exporter dependency; adapter directory not found; adapter and base mismatch; opset below 14; invalid keep list; a failed untie check; Optimum export failure; invalid or mismatched slot_map.json; unknown quantisation mode |
ConfigError |
TrainConfig, typedecide.evaluation |
Unknown or missing config key; out-of-range value; unreadable config file; unknown debias mode; permutation with more than 6 options; batch_size < 1; a missing torch/transformers/peft when scoring; a tokenizer with neither pad nor EOS token |
ScorerError |
evaluate_with_scorer, and therefore evaluate |
A ScoreFn returned the wrong number of vectors, a vector of the wrong length, a negative value, a non-finite value (NaN or inf, usually a half-precision overflow in the model), or something that is not a sequence of numbers |
Loader errors are always DataError
When a row fails schema validation during loading, the SchemaError is wrapped in
a DataError that adds the file and row (export.csv row 12, id 'T-9': ...), with
the original as __cause__. You only see a bare SchemaError when you construct
Option, Criterion or Decision yourself or call decision_from_dict.
ScorerError and backward compatibility
ScorerError inherits from both EvaluationError and ConfigError. Version 0.1.0
raised ConfigError for a misbehaving scorer, so except ConfigError written
against it keeps working. That base is deprecated. Catch EvaluationError in new
code.
Patterns¶
One boundary for everything deliberate¶
import sys
from typedecide import TypeDecideError, group_split, load_decisions, validate
def prepare(path: str):
decisions = load_decisions(path)
report = validate(decisions)
if not report.ok:
raise SystemExit(report.render())
return group_split(decisions, eval_fraction=0.2, seed=0)
try:
train, held = prepare("tickets.csv")
except TypeDecideError as error:
# The message is the diagnosis. A traceback would only bury it.
print(f"error: {error}", file=sys.stderr)
raise SystemExit(2)
This is what the CLI does.
Reacting to specific classes¶
from typedecide import DataError, group_split, load_decisions
decisions = load_decisions("tickets.jsonl")
for seed in range(20):
try:
train, held = group_split(decisions, eval_fraction=0.2, seed=seed)
break
except DataError:
continue
else:
raise SystemExit("no seed covers every class; see the split-refuses runbook")
from pathlib import Path
from typedecide import ConfigError
from typedecide.training import TrainConfig
try:
config = TrainConfig.from_file(Path("train.yaml"))
except ConfigError as error:
raise SystemExit(f"bad training config: {error}")
A validation report is not an exception¶
validate never raises for a bad dataset. It returns a
DataReport, and you decide what to do with
report.ok. Only loading raises.
Match on classes and codes, never on message text¶
Message wording is not part of the public API and improves between releases. Branch on
the exception class, on Finding.code, or on the CLI exit status.
What the CLI does¶
| Situation | Output | Exit status |
|---|---|---|
| Success | The function's result, on standard output | 0 |
validate finds error findings |
The report | 1 |
Any TypeDecideError |
error: <message> on standard error, no traceback |
2 |
An OSError, such as an unwritable --out or --json-out |
error: <message> on standard error, no traceback |
2 |
| Ctrl+C | interrupted |
130 |
| Any other exception | A Python traceback | 1 |
Run with -v before the subcommand (typedecide -v train ...) for debug logging.
Logging¶
The library logs under the typedecide namespace
(typedecide.data.loaders, typedecide.training.trainer,
typedecide.evaluation.runner, and so on) and never prints outside the CLI. It does
not configure logging for you; it installs a NullHandler so nothing is emitted until
your application adds a handler.
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
logging.getLogger("typedecide").setLevel(logging.DEBUG)
Some conditions are deliberately warnings, not errors, because the run can still be trusted: unlabelled rows skipped by evaluation, a state truncated from the left, numeric answers of ambiguous base, a slot map whose separator could not be reproduced. Read the log of a long run.
Missing optional dependencies¶
A missing extra is reported as one of the classes above, never as a bare
ImportError, and the message contains the pip install command. The mapping is in
Install.