Skip to content

teff.errors

teff.errors

Public exception hierarchy for teff.

Everything the framework raises derives from :class:TeffError, so a single except teff.TeffError is enough to catch any library error, while specific subclasses let callers branch on the failure mode.

The hierarchy deliberately multiple-inherits from builtin exceptions to stay backwards-compatible: code that already does except KeyError, except ValueError or except RuntimeError keeps working.

TeffError
├── ConfigError        (also KeyError) — invalid config / unknown types
├── WorkflowError      (also RuntimeError) — workflow-level failures
│   ├── NodeError      — a node raised (carries ``node_id``/``node_type``)
│   └── LLMError       — a model call failed after retries/fallbacks
├── InterruptError     — HITL resume misuse
├── GraphInterrupt     — workflow paused for human input
└── StructuredOutputError (also ValueError) — schema validation failed

Note: transport-level failures (timeouts, HTTP status errors, connection errors) propagate as the underlying httpx exceptions so that existing except httpx.XError handlers keep working.

Classes:

Name Description
ConfigError

Invalid configuration: bad workflow YAML, unknown node/tool type.

InterruptError

Raised when an interrupt/resume contract is violated.

LLMError

A model call failed after exhausting retries and fallbacks.

NodeError

A node failed; carries the failing node_id and node_type.

TeffError

Base class for every exception raised by the teff framework.

WorkflowError

A workflow failed at runtime (loop guards, execution invariants).

Functions:

Name Description
as_node_error

Wrap exc into a :class:NodeError carrying node context.

redact

Mask secret-looking values in value (recursively).

ConfigError

Bases: TeffError, KeyError

Invalid configuration: bad workflow YAML, unknown node/tool type.

Subclasses :class:KeyError so legacy except KeyError blocks continue to catch unknown-type lookups.

Source code in teff/errors.py
54
55
56
57
58
59
class ConfigError(TeffError, KeyError):
    """Invalid configuration: bad workflow YAML, unknown node/tool type.

    Subclasses :class:`KeyError` so legacy ``except KeyError`` blocks
    continue to catch unknown-type lookups.
    """

InterruptError

Bases: TeffError

Raised when an interrupt/resume contract is violated.

For example: resuming a run that has no pending interrupt, or resuming without the checkpoint that holds the pause.

Source code in teff/errors.py
92
93
94
95
96
97
class InterruptError(TeffError):
    """Raised when an interrupt/resume contract is violated.

    For example: resuming a run that has no pending interrupt, or
    resuming without the checkpoint that holds the pause.
    """

LLMError

Bases: WorkflowError

A model call failed after exhausting retries and fallbacks.

The original transport exception is available as __cause__.

Source code in teff/errors.py
85
86
87
88
89
class LLMError(WorkflowError):
    """A model call failed after exhausting retries and fallbacks.

    The original transport exception is available as ``__cause__``.
    """

NodeError

Bases: WorkflowError

A node failed; carries the failing node_id and node_type.

Source code in teff/errors.py
70
71
72
73
74
75
76
77
78
79
80
81
82
class NodeError(WorkflowError):
    """A node failed; carries the failing ``node_id`` and ``node_type``."""

    def __init__(
        self,
        message: str,
        *,
        node_id: str | None = None,
        node_type: str | None = None,
    ):
        super().__init__(message)
        self.node_id = node_id
        self.node_type = node_type

TeffError

Bases: Exception

Base class for every exception raised by the teff framework.

Source code in teff/errors.py
50
51
class TeffError(Exception):
    """Base class for every exception raised by the teff framework."""

WorkflowError

Bases: TeffError, RuntimeError

A workflow failed at runtime (loop guards, execution invariants).

Subclasses :class:RuntimeError so legacy except RuntimeError blocks (e.g. max_iterations) keep working.

Source code in teff/errors.py
62
63
64
65
66
67
class WorkflowError(TeffError, RuntimeError):
    """A workflow failed at runtime (loop guards, execution invariants).

    Subclasses :class:`RuntimeError` so legacy ``except RuntimeError``
    blocks (e.g. ``max_iterations``) keep working.
    """

as_node_error

as_node_error(exc, *, node_id, node_type)

Wrap exc into a :class:NodeError carrying node context.

Source code in teff/errors.py
100
101
102
103
104
105
106
107
108
109
110
def as_node_error(
    exc: Exception, *, node_id: str | None, node_type: str | None
) -> NodeError:
    """Wrap *exc* into a :class:`NodeError` carrying node context."""
    error = NodeError(
        f"{node_type or 'node'} '{node_id}' failed: {exc}",
        node_id=node_id,
        node_type=node_type,
    )
    error.__cause__ = exc
    return error

redact

redact(value, keys=())

Mask secret-looking values in value (recursively).

  • dict values whose (lowercased) key names a secret are replaced with "***" — this catches {"Authorization": "Bearer sk-..."}.
  • string values are scanned for key=... / key: ... pairs that name a secret (e.g. ?api_key=sk-abc inside a URL) and the value is masked in place.

keys is the word-list used for the string regex; when empty a sensible default list is used.

Source code in teff/errors.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def redact(value: Any, keys: tuple[str, ...] = ()) -> Any:
    """Mask secret-looking values in *value* (recursively).

    - dict values whose (lowercased) key names a secret are replaced with
      ``"***"`` — this catches ``{"Authorization": "Bearer sk-..."}``.
    - string values are scanned for ``key=...`` / ``key: ...`` pairs that
      name a secret (e.g. ``?api_key=sk-abc`` inside a URL) and the value
      is masked in place.

    *keys* is the word-list used for the string regex; when empty a
    sensible default list is used.
    """
    if isinstance(value, dict):
        keyset = {k.lower() for k in keys} if keys else _default_keys
        return {
            k: redact(v, keys) if k.lower() not in keyset else "***"
            for k, v in value.items()
        }
    if isinstance(value, list):
        return [redact(v, keys) for v in value]
    if isinstance(value, tuple):
        return tuple(redact(v, keys) for v in value)
    if isinstance(value, str):
        if not keys:
            return _SECRET_KEY_RE.sub(r"\1***", value)
        pattern = re.compile(
            r'("?'
            + "|".join(re.escape(k) for k in keys)
            + r'"?\s*[:=]\s*)("?)(?:[^\s,;&"]+\s+)*[^\s,;&"]{6,}\2',
            re.IGNORECASE,
        )
        return pattern.sub(r"\1***", value)
    return value