# How Open Notebook's error_classifier Maps Raw LLM Exceptions to Typed OpenNotebookError Subclasses

> Learn how Open Notebook's error_classifier maps raw LLM exceptions to typed OpenNotebookError subclasses using keyword pattern matching for unified error handling.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: internals
- Published: 2026-06-28

---

**The error_classifier module converts diverse provider-specific exceptions into a unified hierarchy of OpenNotebookError subclasses using keyword-based pattern matching against exception messages and types.**

Open Notebook normalizes the chaotic landscape of AI provider errors through a deterministic classification system. The `error_classifier` module in the `lfnovo/open-notebook` repository acts as a centralized router that transforms low-level exceptions from Esperanto, LangChain, and other providers into predictable, user-friendly error types. This mapping ensures downstream components handle failures consistently, whether the root cause is an expired API key, network timeout, or rate limit.

## The Classification Rules Engine in error_classifier.py

The core logic resides in [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py) [[source]](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py), specifically within the `_CLASSIFICATION_RULES` list. This structure defines how raw exceptions map to specific `OpenNotebookError` subclasses defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) [[source]](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py).

Each rule in `_CLASSIFICATION_RULES` is a tuple containing three elements:

- **Keywords**: A list of strings searched for in the exception message and type
- **Target subclass**: The specific `OpenNotebookError` subclass to instantiate
- **User message**: An optional friendly message; if `None`, the original exception text is used (truncated)

For example, authentication errors map as follows:

```python
(["authentication", "unauthorized", "invalid api key", "401"],
 AuthenticationError,
 "Authentication failed. Please check your API key in Settings -> Credentials.")

```

## How classify_error Normalizes Exception Data

When the application catches an unhandled exception from an LLM provider, it calls `classify_error(exc)`, which executes a deterministic matching workflow:

1. **String preparation**: The function lowercases both the exception's class name and string representation, then concatenates them into a single `combined` string formatted as `"<type>: <message>"`.

2. **Pattern matching**: It iterates through `_CLASSIFICATION_RULES`, checking if any keyword from each rule appears in the `combined` string.

3. **First-match wins**: The first matching rule determines the exception class. The function returns a tuple of `(exception_class, user_message)`, where `user_message` is either the custom text from the rule or the original exception text processed through `_truncate`.

4. **Fallback handling**: If no rule matches, the function logs a warning and defaults to `ExternalServiceError` with a truncated generic message.

## Safety Mechanisms and Message Truncation

The module implements defense-in-depth through the `_truncate` helper function. This utility limits all propagated messages to **200 characters**, preventing verbose internal exception details from leaking to users or logs. Whether using the original exception text or a fallback message, the classifier ensures no sensitive stack traces or implementation details exceed this boundary.

## Practical Implementation Example

Integrating the classifier into LLM call sites requires wrapping provider-specific execution in a try-except block:

```python
from open_notebook.utils.error_classifier import classify_error
from open_notebook.exceptions import OpenNotebookError

def call_llm(provider, payload):
    try:
        # Example provider call – may raise any exception from the LLM lib

        return provider.generate(payload)
    except Exception as exc:                     # Catch any raw provider exception

        exc_cls, message = classify_error(exc)   # Map to OpenNotebookError subclass

        raise exc_cls(message) from exc          # Re‑raise the typed error

# ---- Usage in the application ----

try:
    result = call_llm(my_provider, my_prompt)
except OpenNotebookError as e:
    # All provider‑specific errors are now OpenNotebookError subclasses

    logger.error(f"LLM call failed: {e}")
    # UI can display e.args[0] (the friendly message)

```

This pattern ensures that:

- **AuthenticationError** is raised for messages containing "401", "unauthorized", or "invalid api key"
- **RateLimitError** is raised for "429" or "too many requests"
- **NetworkError** handles connection failures and timeouts
- **ExternalServiceError** catches all remaining provider-specific failures

## Summary

- **[`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py)** contains the `_CLASSIFICATION_RULES` list and `classify_error` function that map raw LLM exceptions to typed subclasses.
- The classifier uses **keyword-based pattern matching** against a normalized string combining the exception type and message.
- **First-match wins** semantics ensure deterministic error classification, with `ExternalServiceError` serving as the universal fallback.
- The **`_truncate`** function enforces a 200-character limit on all error messages to prevent information leakage.
- Target exception classes like `AuthenticationError`, `RateLimitError`, and `NetworkError` are defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py).

## Frequently Asked Questions

### What happens if an LLM exception doesn't match any classification rule?

If `classify_error` cannot match the exception against any entry in `_CLASSIFICATION_RULES`, it logs a warning and returns `ExternalServiceError` along with a truncated generic message. This ensures the application never propagates unclassified raw exceptions.

### How does the error_classifier prevent sensitive data from leaking in error messages?

The classifier applies the `_truncate` function to all messages, hard-limiting them to 200 characters. This safety mechanism prevents verbose stack traces or internal implementation details from reaching user-facing interfaces or logs, regardless of whether the message comes from the original exception or a predefined rule.

### Can I customize the error messages shown to users?

Yes. Each entry in `_CLASSIFICATION_RULES` accepts an optional third parameter for the user-friendly message. If you provide a custom string, that text is used instead of the original exception message. If you pass `None`, the classifier uses the original exception text (still truncated to 200 characters via `_truncate`).

### Where are the OpenNotebookError subclasses defined?

The typed exception hierarchy—including `AuthenticationError`, `RateLimitError`, `NetworkError`, and `ExternalServiceError`—is defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py). These classes inherit from the base `OpenNotebookError` class, allowing downstream code to catch all provider-related failures with a single except clause.