Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
base.py348 linesDownload Raw Back to documents
1"""Base classes for media and documents.2 3This module contains core abstractions for **data retrieval and processing workflows**:4 5- `BaseMedia`: Base class providing `id` and `metadata` fields6- `Blob`: Raw data loading (files, binary data) - used by document loaders7- `Document`: Text content for retrieval (RAG, vector stores, semantic search)8 9!!! note "Not for LLM chat messages"10 11    These classes are for data processing pipelines, not LLM I/O. For multimodal12    content in chat messages (images, audio in conversations), see13    `langchain.messages` content blocks instead.14"""15 16from __future__ import annotations17 18import contextlib19import mimetypes20from io import BufferedReader, BytesIO21from pathlib import Path, PurePath22from typing import TYPE_CHECKING, Any, Literal, cast23 24from pydantic import ConfigDict, Field, model_validator25 26from langchain_core.load.serializable import Serializable27 28if TYPE_CHECKING:29    from collections.abc import Generator30 31PathLike = str | PurePath32 33 34class BaseMedia(Serializable):35    """Base class for content used in retrieval and data processing workflows.36 37    Provides common fields for content that needs to be stored, indexed, or searched.38 39    !!! note40 41        For multimodal content in **chat messages** (images, audio sent to/from LLMs),42        use `langchain.messages` content blocks instead.43    """44 45    # The ID field is optional at the moment.46    # It will likely become required in a future major release after47    # it has been adopted by enough VectorStore implementations.48    id: str | None = Field(default=None, coerce_numbers_to_str=True)49    """An optional identifier for the document.50 51    Ideally this should be unique across the document collection and formatted52    as a UUID, but this will not be enforced.53    """54 55    metadata: dict = Field(default_factory=dict)56    """Arbitrary metadata associated with the content."""57 58 59class Blob(BaseMedia):60    """Raw data abstraction for document loading and file processing.61 62    Represents raw bytes or text, either in-memory or by file reference. Used63    primarily by document loaders to decouple data loading from parsing.64 65    Inspired by [Mozilla's `Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)66 67    ???+ example "Initialize a blob from in-memory data"68 69        ```python70        from langchain_core.documents import Blob71 72        blob = Blob.from_data("Hello, world!")73 74        # Read the blob as a string75        print(blob.as_string())76 77        # Read the blob as bytes78        print(blob.as_bytes())79 80        # Read the blob as a byte stream81        with blob.as_bytes_io() as f:82            print(f.read())83        ```84 85    ??? example "Load from memory and specify MIME type and metadata"86 87        ```python88        from langchain_core.documents import Blob89 90        blob = Blob.from_data(91            data="Hello, world!",92            mime_type="text/plain",93            metadata={"source": "https://example.com"},94        )95        ```96 97    ??? example "Load the blob from a file"98 99        ```python100        from langchain_core.documents import Blob101 102        blob = Blob.from_path("path/to/file.txt")103 104        # Read the blob as a string105        print(blob.as_string())106 107        # Read the blob as bytes108        print(blob.as_bytes())109 110        # Read the blob as a byte stream111        with blob.as_bytes_io() as f:112            print(f.read())113        ```114    """115 116    data: bytes | str | None = None117    """Raw data associated with the `Blob`."""118 119    mimetype: str | None = None120    """MIME type, not to be confused with a file extension."""121 122    encoding: str = "utf-8"123    """Encoding to use if decoding the bytes into a string.124 125    Uses `utf-8` as default encoding if decoding to string.126    """127 128    path: PathLike | None = None129    """Location where the original content was found."""130 131    model_config = ConfigDict(132        arbitrary_types_allowed=True,133        frozen=True,134    )135 136    @property137    def source(self) -> str | None:138        """The source location of the blob as string if known otherwise none.139 140        If a path is associated with the `Blob`, it will default to the path location.141 142        Unless explicitly set via a metadata field called `'source'`, in which143        case that value will be used instead.144        """145        if self.metadata and "source" in self.metadata:146            return cast("str | None", self.metadata["source"])147        return str(self.path) if self.path else None148 149    @model_validator(mode="before")150    @classmethod151    def check_blob_is_valid(cls, values: dict[str, Any]) -> Any:152        """Verify that either data or path is provided."""153        if "data" not in values and "path" not in values:154            msg = "Either data or path must be provided"155            raise ValueError(msg)156        return values157 158    def as_string(self) -> str:159        """Read data as a string.160 161        Raises:162            ValueError: If the blob cannot be represented as a string.163 164        Returns:165            The data as a string.166        """167        if self.data is None and self.path:168            return Path(self.path).read_text(encoding=self.encoding)169        if isinstance(self.data, bytes):170            return self.data.decode(self.encoding)171        if isinstance(self.data, str):172            return self.data173        msg = f"Unable to get string for blob {self}"174        raise ValueError(msg)175 176    def as_bytes(self) -> bytes:177        """Read data as bytes.178 179        Raises:180            ValueError: If the blob cannot be represented as bytes.181 182        Returns:183            The data as bytes.184        """185        if isinstance(self.data, bytes):186            return self.data187        if isinstance(self.data, str):188            return self.data.encode(self.encoding)189        if self.data is None and self.path:190            return Path(self.path).read_bytes()191        msg = f"Unable to get bytes for blob {self}"192        raise ValueError(msg)193 194    @contextlib.contextmanager195    def as_bytes_io(self) -> Generator[BytesIO | BufferedReader, None, None]:196        """Read data as a byte stream.197 198        Raises:199            NotImplementedError: If the blob cannot be represented as a byte stream.200 201        Yields:202            The data as a byte stream.203        """204        if isinstance(self.data, bytes):205            yield BytesIO(self.data)206        elif self.data is None and self.path:207            with Path(self.path).open("rb") as f:208                yield f209        else:210            msg = f"Unable to convert blob {self}"211            raise NotImplementedError(msg)212 213    @classmethod214    def from_path(215        cls,216        path: PathLike,217        *,218        encoding: str = "utf-8",219        mime_type: str | None = None,220        guess_type: bool = True,221        metadata: dict | None = None,222    ) -> Blob:223        """Load the blob from a path like object.224 225        Args:226            path: Path-like object to file to be read227            encoding: Encoding to use if decoding the bytes into a string228            mime_type: If provided, will be set as the MIME type of the data229            guess_type: If `True`, the MIME type will be guessed from the file230                extension, if a MIME type was not provided231            metadata: Metadata to associate with the `Blob`232 233        Returns:234            `Blob` instance235        """236        if mime_type is None and guess_type:237            mimetype = mimetypes.guess_type(path)[0]238        else:239            mimetype = mime_type240        # We do not load the data immediately, instead we treat the blob as a241        # reference to the underlying data.242        return cls(243            data=None,244            mimetype=mimetype,245            encoding=encoding,246            path=path,247            metadata=metadata if metadata is not None else {},248        )249 250    @classmethod251    def from_data(252        cls,253        data: str | bytes,254        *,255        encoding: str = "utf-8",256        mime_type: str | None = None,257        path: str | None = None,258        metadata: dict | None = None,259    ) -> Blob:260        """Initialize the `Blob` from in-memory data.261 262        Args:263            data: The in-memory data associated with the `Blob`264            encoding: Encoding to use if decoding the bytes into a string265            mime_type: If provided, will be set as the MIME type of the data266            path: If provided, will be set as the source from which the data came267            metadata: Metadata to associate with the `Blob`268 269        Returns:270            `Blob` instance271        """272        return cls(273            data=data,274            mimetype=mime_type,275            encoding=encoding,276            path=path,277            metadata=metadata if metadata is not None else {},278        )279 280    def __repr__(self) -> str:281        """Return the blob representation."""282        str_repr = f"Blob {id(self)}"283        if self.source:284            str_repr += f" {self.source}"285        return str_repr286 287 288class Document(BaseMedia):289    """Class for storing a piece of text and associated metadata.290 291    !!! note292 293        `Document` is for **retrieval workflows**, not chat I/O. For sending text294        to an LLM in a conversation, use message types from `langchain.messages`.295 296    Example:297        ```python298        from langchain_core.documents import Document299 300        document = Document(301            page_content="Hello, world!", metadata={"source": "https://example.com"}302        )303        ```304    """305 306    page_content: str307    """String text."""308 309    type: Literal["Document"] = "Document"310 311    def __init__(self, page_content: str, **kwargs: Any) -> None:312        """Pass page_content in as positional or named arg."""313        # my-py is complaining that page_content is not defined on the base class.314        # Here, we're relying on pydantic base class to handle the validation.315        super().__init__(page_content=page_content, **kwargs)  # type: ignore[call-arg,unused-ignore]316 317    @classmethod318    def is_lc_serializable(cls) -> bool:319        """Return `True` as this class is serializable."""320        return True321 322    @classmethod323    def get_lc_namespace(cls) -> list[str]:324        """Get the namespace of the LangChain object.325 326        Returns:327            `["langchain", "schema", "document"]`328        """329        return ["langchain", "schema", "document"]330 331    def __str__(self) -> str:332        """Override `__str__` to restrict it to page_content and metadata.333 334        Returns:335            A string representation of the `Document`.336        """337        # The format matches pydantic format for __str__.338        #339        # The purpose of this change is to make sure that user code that feeds340        # Document objects directly into prompts remains unchanged due to the addition341        # of the id field (or any other fields in the future).342        #343        # This override will likely be removed in the future in favor of a more general344        # solution of formatting content directly inside the prompts.345        if self.metadata:346            return f"page_content='{self.page_content}' metadata={self.metadata}"347        return f"page_content='{self.page_content}'"348 
codekingpro/portable-devtools · Team Ai