# How Open Notebook Classifies AI Provider Exceptions Using error_classifier.py

> Learn how Open Notebook uses error_classifier.py to classify AI provider exceptions. Discover the keyword mapping system that transforms raw errors into structured, user-friendly types.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-21

---

**The [`error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/error_classifier.py) utility transforms raw AI provider exceptions into structured, user-friendly error types through a keyword-based mapping system.**

The `lfnovo/open-notebook` repository manages interactions with multiple large language model (LLM) providers through LangChain and Esperanto. To deliver consistent error handling across these integrations, the project implements a centralized classification system in [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py) that converts provider-specific failures into canonical exception types used throughout the application.

## The Classification Pipeline

### Import of Canonical Exceptions

The module relies on a custom exception hierarchy defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py). All notebook-specific errors inherit from `OpenNotebookError`, with concrete subclasses including `AuthenticationError`, `RateLimitError`, `NetworkError`, and `ExternalServiceError`. This hierarchy allows the rest of the codebase to catch and handle errors by category rather than parsing provider-specific strings.

### Keyword-Based Classification Rules

At the heart of the system lies the `_CLASSIFICATION_RULES` list, which maps substring patterns to canonical error classes and user-facing messages:

```python
_CLASSIFICATION_RULES = [
    (["authentication", "unauthorized", "invalid api key", "invalid_api_key", "401"],
     AuthenticationError,
     "Authentication failed. Please check your API key in Settings → Credentials."),
    (["rate limit", "rate_limit", "429", "too many requests", "quota exceeded"],
     RateLimitError,
     "Rate limit exceeded. Please wait a moment and try again."),
    # … additional rules …

]

```

Each tuple contains three elements:
- **Keywords**: A list of lowercase substrings to match against the exception text or class name
- **Exception class**: The specific `OpenNotebookError` subclass to instantiate
- **User message**: A ready-made string for end users, or `None` to preserve the original (truncated) message

### The classify_error Function

The `classify_error` function accepts any `BaseException` and returns a tuple of the appropriate error class and message:

```python
def classify_error(exception: BaseException) -> tuple[type[OpenNotebookError], str]:
    error_str = str(exception).lower()
    error_type_name = type(exception).__name__.lower()
    combined = f"{error_type_name}: {error_str}"
    
    for keywords, exc_class, user_message in _CLASSIFICATION_RULES:
        if any(keyword in combined for keyword in keywords):
            return exc_class, user_message or _truncate(str(exception))
    
    # Fallback path

    return ExternalServiceError, _truncate(str(exception))

```

The function concatenates the exception type name with its string representation to catch variations like `ProviderRateLimitError: 429 Too Many Requests`. If no rule matches, it logs a warning and falls back to `ExternalServiceError` with a generic message containing the truncated original exception. The `_truncate` helper caps raw messages at 200 characters to prevent accidental exposure of sensitive payload details.

## Integration Across LangGraph Workflows

### Wrapping LLM Calls in Graph Nodes

Every LangGraph workflow that communicates with LLM providers wraps model invocations with the classifier. In [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), [`ask.py`](https://github.com/lfnovo/open-notebook/blob/main/ask.py), [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py), and [`transformation.py`](https://github.com/lfnovo/open-notebook/blob/main/transformation.py), the pattern follows this structure:

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

async def invoke_llm(state):
    try:
        return await model.invoke(state["prompt"])
    except Exception as e:
        error_class, user_message = classify_error(e)
        raise error_class(user_message) from e

```

This ensures that whether the underlying provider raises an `openai.RateLimitError` or an `anthropic.AuthenticationError`, the workflow consistently raises `RateLimitError` or `AuthenticationError` from the Open Notebook hierarchy.

### API Layer Error Handling

API routers such as [`api/routers/search.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/search.py) and [`api/routers/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/source_chat.py) catch these classified exceptions and forward the user-friendly messages directly to HTTP responses. Because the classifier already normalized the error type, the API layer can handle all provider failures with uniform logic rather than implementing provider-specific parsing in every endpoint.

## Summary

- **Centralized mapping**: [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py) contains all provider-to-canonical error translations in one maintainable location
- **Keyword matching**: The system uses substring search against combined exception type names and messages to identify error categories
- **Consistent UX**: End users receive actionable guidance (e.g., "check your API key") instead of opaque stack traces
- **Safety controls**: The `_truncate` mechanism limits message length to 200 characters, preventing sensitive data leakage
- **Broad integration**: Classification occurs in every LangGraph workflow node and API router that contacts external LLM providers

## Frequently Asked Questions

### How does error_classifier.py determine which error type to return?

The `classify_error` function converts the incoming exception to lowercase and combines its type name with its string representation. It iterates through `_CLASSIFICATION_RULES` and returns the exception class associated with the first rule where any keyword appears in this combined string. This approach catches both explicit error types and message content variations across different AI providers.

### What happens when an error doesn't match any classification rule?

If no keywords match, the function logs a warning and returns `ExternalServiceError` along with a truncated version of the original exception message. This fallback ensures the application never crashes on unrecognized provider errors while still providing some diagnostic information to the user.

### Where is the error classification actually invoked in the codebase?

The classifier is invoked in every LangGraph workflow file that contacts LLMs, specifically [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), [`ask.py`](https://github.com/lfnovo/open-notebook/blob/main/ask.py), [`source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/source_chat.py), and [`transformation.py`](https://github.com/lfnovo/open-notebook/blob/main/transformation.py). Additionally, API routers like [`api/routers/search.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/search.py) and [`api/routers/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/source_chat.py) utilize the classified errors to generate appropriate HTTP responses with user-friendly messages.

### Why does the classifier truncate error messages to 200 characters?

The `_truncate` helper limits raw exception text to 200 characters as a security measure. Provider error messages occasionally contain sensitive information such as API key fragments, internal URLs, or request payloads. By truncating when no specific user message is defined, the system prevents accidental exposure of these details to end users while still preserving enough context for debugging.