chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
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.
|
||||
"""
|
||||
Reference in New Issue
Block a user