Files
2026-07-13 12:08:54 +08:00

195 lines
7.1 KiB
Python

from __future__ import annotations
import httpx
from typing import Literal
class APIStatusError(Exception):
"""Raised when an API response has a status code of 4xx or 5xx."""
response: httpx.Response
status_code: int
request_id: str | None
def __init__(
self, message: str, *, response: httpx.Response, body: object | None
) -> None:
super().__init__(message)
self.request = response.request
self.body = body
self.response = response
self.status_code = response.status_code
self.request_id = response.headers.get("x-request-id")
class APIConnectionError(Exception):
def __init__(
self, *, message: str = "Connection error.", request: httpx.Request | None
) -> None:
super().__init__(message)
self.request = request
class BadRequestError(APIStatusError):
status_code: Literal[400] = 400 # pyright: ignore[reportIncompatibleVariableOverride]
class AuthenticationError(APIStatusError):
status_code: Literal[401] = 401 # pyright: ignore[reportIncompatibleVariableOverride]
class PermissionDeniedError(APIStatusError):
status_code: Literal[403] = 403 # pyright: ignore[reportIncompatibleVariableOverride]
class NotFoundError(APIStatusError):
status_code: Literal[404] = 404 # pyright: ignore[reportIncompatibleVariableOverride]
class ConflictError(APIStatusError):
status_code: Literal[409] = 409 # pyright: ignore[reportIncompatibleVariableOverride]
class UnprocessableEntityError(APIStatusError):
status_code: Literal[422] = 422 # pyright: ignore[reportIncompatibleVariableOverride]
class RateLimitError(APIStatusError):
status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride]
class APITimeoutError(APIConnectionError):
def __init__(self, request: httpx.Request | None) -> None:
super().__init__(message="Request timed out.", request=request)
class StorageNotInitializedError(RuntimeError):
"""Raised when storage operations are attempted before initialization."""
def __init__(self, storage_type: str = "Storage"):
super().__init__(
f"{storage_type} not initialized. Please ensure proper initialization:\n"
f"\n"
f" rag = LightRAG(...)\n"
f" await rag.initialize_storages() # Required - auto-initializes pipeline_status\n"
f"\n"
f"See: https://github.com/HKUDS/LightRAG#important-initialization-requirements"
)
class PipelineNotInitializedError(KeyError):
"""Raised when pipeline status is accessed before initialization."""
def __init__(self, namespace: str = ""):
msg = (
f"Pipeline namespace '{namespace}' not found.\n"
f"\n"
f"Pipeline status should be auto-initialized by initialize_storages().\n"
f"If you see this error, please ensure:\n"
f"\n"
f" 1. You called await rag.initialize_storages()\n"
f" 2. For multi-workspace setups, each LightRAG instance was properly initialized\n"
f"\n"
f"Standard initialization:\n"
f" rag = LightRAG(workspace='your_workspace')\n"
f" await rag.initialize_storages() # Auto-initializes pipeline_status\n"
f"\n"
f"If you need manual control (advanced):\n"
f" from lightrag.kg.shared_storage import initialize_pipeline_status\n"
f" await initialize_pipeline_status(workspace='your_workspace')"
)
super().__init__(msg)
class PipelineCancelledException(Exception):
"""Raised when pipeline processing is cancelled by user request."""
def __init__(self, message: str = "User cancelled"):
super().__init__(message)
self.message = message
class IndexFlushError(Exception):
"""Raised when a storage backend fails to flush buffered index ops.
Carries the storage driver name and namespace so the pipeline can abort
the batch with an actionable reason. The underlying error is preserved as
the exception ``__cause__`` (set via ``raise ... from cause``).
"""
def __init__(self, storage_name: str, namespace: str, cause: BaseException):
self.storage_name = storage_name
self.namespace = namespace
super().__init__(f"{storage_name}[{namespace}] index flush failed: {cause}")
class ChunkTokenLimitExceededError(ValueError):
"""Raised when a chunk exceeds the configured token limit."""
def __init__(
self,
chunk_tokens: int,
chunk_token_limit: int,
chunk_preview: str | None = None,
) -> None:
preview = chunk_preview.strip() if chunk_preview else None
truncated_preview = preview[:80] if preview else None
preview_note = f" Preview: '{truncated_preview}'" if truncated_preview else ""
message = (
f"Chunk token length {chunk_tokens} exceeds chunk_token_size {chunk_token_limit}."
f"{preview_note}"
)
super().__init__(message)
self.chunk_tokens = chunk_tokens
self.chunk_token_limit = chunk_token_limit
self.chunk_preview = truncated_preview
class ChunkBlockMatchError(ValueError):
"""Raised when a chunk cannot be located in the document's blocks.jsonl.
Sidecar backfill (``lightrag.sidecar.backfill``) maps F/R/V chunks back to
their source block(s) by matching chunk content against the parse-time
``*.blocks.jsonl`` merged text. When a sidecar-less chunk cannot be located,
this is raised so the pipeline marks the document FAILED rather than
persisting chunks with missing/incorrect provenance.
"""
def __init__(
self,
chunk_order_index: int,
chunk_preview: str | None = None,
blocks_path: str | None = None,
) -> None:
preview = chunk_preview.strip() if chunk_preview else None
truncated_preview = preview[:80] if preview else None
preview_note = f" Preview: '{truncated_preview}'" if truncated_preview else ""
path_note = f" (blocks: {blocks_path})" if blocks_path else ""
message = (
f"Chunk #{chunk_order_index} could not be located in the document "
f"blocks during sidecar backfill.{preview_note}{path_note}"
)
super().__init__(message)
self.chunk_order_index = chunk_order_index
self.chunk_preview = truncated_preview
self.blocks_path = blocks_path
class DataMigrationError(Exception):
"""Raised when data migration from legacy collection/table fails."""
def __init__(self, message: str):
super().__init__(message)
self.message = message
class MultimodalAnalysisError(RuntimeError):
"""Raised when multimodal analysis must fail the current document.
Hard failures (missing required field, schema mismatch, model not
available, sidecar already carries ``status="failure"``) bubble this
exception so the pipeline marks the document failed instead of writing
an unusable analyze result. Callers persist a ``status="failure"``
sidecar entry alongside the raise so a re-run sees the failure.
"""