Skip to content

Errors

Errors in beacon are data. Every exception carries a stable machine-readable code, a stable human-readable message, and, where it makes sense, the exact position of the problem.

BeaconError
├── MalformedOutputError (ValueError) .code .position .line .column .raw
│ ├── NoJSONFoundError
│ └── NoToolCallFoundError
├── SchemaValidationError (ValueError) .errors = [{path, keyword, message}] .raw
├── SchemaError (ValueError) the schema itself is invalid/unsupported
├── TemplateError (ValueError)
│ ├── TemplateSyntaxError .position .line .column
│ ├── MissingVariableError .names (sorted)
│ └── ExtraVariableError .names (sorted)
└── SnapshotMismatchError (AssertionError) .path .diff

All are importable from beacon. Content errors also subclass ValueError, so existing except ValueError handlers keep working. Invalid arguments (an unknown select= mode, a bad length=) raise plain ValueError, and unsupported value types raise TypeError.

Positional messages end with (line L, column C). The attributes are:

Attribute Meaning
position code-point offset into the input (text[position] is the culprit)
line, column 1-based, counted in code points
text = '日本語 {"a": x}'
try:
extract_json(text)
except MalformedOutputError as err:
assert text[err.position] == "x" # works with any Unicode before it
try:
data = extract_json(raw, schema=SCHEMA)
except NoJSONFoundError:
data = fallback_answer(raw) # the model answered in prose
except MalformedOutputError as err:
if err.code == "json.unexpected_end":
data = retry_with_higher_max_tokens()
else:
raise

Exception types, codes and messages follow the determinism contract: they never change in a patch release. The complete list is in Error codes.