mirror of
https://github.com/lfnovo/open-notebook.git
synced 2026-04-28 11:30:00 +00:00
- Add batching to generate_embeddings() (50 texts per batch with per-batch retry) to prevent 413 Payload Too Large errors on large documents - Add 413 error classification rule for user-friendly error messages - Fix misleading "Created 0 embedded chunks" log in process_source_command by removing premature get_embedded_chunks() call (embedding is fire-and-forget) Closes #594
103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
"""
|
|
Error classification utility for LLM provider errors.
|
|
|
|
Maps raw exceptions from AI providers/Esperanto/LangChain to user-friendly
|
|
error messages and appropriate exception types.
|
|
"""
|
|
|
|
from loguru import logger
|
|
|
|
from open_notebook.exceptions import (
|
|
AuthenticationError,
|
|
ConfigurationError,
|
|
ExternalServiceError,
|
|
NetworkError,
|
|
OpenNotebookError,
|
|
RateLimitError,
|
|
)
|
|
|
|
# Classification rules: (keywords, exception_class, user_message or None to pass through)
|
|
_CLASSIFICATION_RULES: list[tuple[list[str], type[OpenNotebookError], str | None]] = [
|
|
# Authentication errors
|
|
(
|
|
["authentication", "unauthorized", "invalid api key", "invalid_api_key", "401"],
|
|
AuthenticationError,
|
|
"Authentication failed. Please check your API key in Settings -> Credentials.",
|
|
),
|
|
# Rate limit errors
|
|
(
|
|
["rate limit", "rate_limit", "429", "too many requests", "quota exceeded"],
|
|
RateLimitError,
|
|
"Rate limit exceeded. Please wait a moment and try again.",
|
|
),
|
|
# Model not found (pass through original message)
|
|
(
|
|
["model not found", "does not exist", "model_not_found"],
|
|
ConfigurationError,
|
|
None,
|
|
),
|
|
# Configuration errors from provision.py (pass through)
|
|
(
|
|
["no model configured", "please go to settings"],
|
|
ConfigurationError,
|
|
None,
|
|
),
|
|
# Network errors
|
|
(
|
|
["connecterror", "timeoutexception", "connection refused", "connection error", "timed out", "timeout"],
|
|
NetworkError,
|
|
"Could not connect to the AI provider. Please check your network connection and provider URL.",
|
|
),
|
|
# Context length errors
|
|
(
|
|
["context length", "token limit", "maximum context", "context_length_exceeded", "max_tokens"],
|
|
ExternalServiceError,
|
|
"Content too large for the selected model. Try using a smaller selection or a model with a larger context window.",
|
|
),
|
|
# Payload too large errors
|
|
(
|
|
["413", "payload too large", "request entity too large"],
|
|
ExternalServiceError,
|
|
"The request payload is too large for the AI provider. Try reducing the content size or using a different model.",
|
|
),
|
|
# Provider availability errors
|
|
(
|
|
["500", "502", "503", "service unavailable", "overloaded", "internal server error"],
|
|
ExternalServiceError,
|
|
"The AI provider is temporarily unavailable. Please try again in a few minutes.",
|
|
),
|
|
]
|
|
|
|
|
|
def classify_error(exception: BaseException) -> tuple[type[OpenNotebookError], str]:
|
|
"""
|
|
Classify a raw exception into a user-friendly error type and message.
|
|
|
|
Args:
|
|
exception: Any exception from LLM providers/Esperanto/LangChain
|
|
|
|
Returns:
|
|
Tuple of (exception_class, user_friendly_message)
|
|
"""
|
|
error_str = str(exception).lower()
|
|
error_type_name = type(exception).__name__.lower()
|
|
combined = f"{error_type_name}: {error_str}"
|
|
|
|
for keywords, exc_class, message in _CLASSIFICATION_RULES:
|
|
for keyword in keywords:
|
|
if keyword in combined:
|
|
user_message = message if message is not None else _truncate(str(exception))
|
|
return exc_class, user_message
|
|
|
|
# Unclassified error - log for future improvement
|
|
logger.warning(
|
|
f"Unclassified LLM error ({type(exception).__name__}): {exception}"
|
|
)
|
|
return ExternalServiceError, f"AI service error: {_truncate(str(exception))}"
|
|
|
|
|
|
def _truncate(text: str, max_length: int = 200) -> str:
|
|
"""Truncate text to max_length to avoid leaking verbose internal details."""
|
|
if len(text) <= max_length:
|
|
return text
|
|
return text[:max_length] + "..."
|