c56bef871b
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
190 lines
6.6 KiB
Python
190 lines
6.6 KiB
Python
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import base64
|
|
import mimetypes
|
|
import os
|
|
from dataclasses import asdict, dataclass, field
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from urllib.parse import unquote, urlparse
|
|
|
|
import filetype
|
|
|
|
from haystack import logging
|
|
from haystack.utils.dataclasses import _warn_on_inplace_mutation
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@_warn_on_inplace_mutation
|
|
@dataclass
|
|
class FileContent:
|
|
"""
|
|
The file content of a chat message.
|
|
|
|
:param base64_data: A base64 string representing the file.
|
|
:param mime_type: The MIME type of the file (e.g. "application/pdf").
|
|
Providing this value is recommended, as most LLM providers require it.
|
|
If not provided, the MIME type is guessed from the base64 string, which can be slow and not always reliable.
|
|
:param filename: Optional filename of the file. Some LLM providers use this information.
|
|
:param extra: Dictionary of extra information about the file. Can be used to store provider-specific information.
|
|
To avoid serialization issues, values should be JSON serializable.
|
|
:param validation: If True (default), a validation process is performed:
|
|
- Check whether the base64 string is valid;
|
|
- Guess the MIME type if not provided.
|
|
Set to False to skip validation and speed up initialization.
|
|
"""
|
|
|
|
base64_data: str
|
|
mime_type: str | None = None
|
|
filename: str | None = None
|
|
extra: dict[str, Any] = field(default_factory=dict)
|
|
validation: bool = True
|
|
|
|
def __post_init__(self) -> None:
|
|
if not self.validation:
|
|
return
|
|
|
|
try:
|
|
decoded_data = base64.b64decode(self.base64_data, validate=True)
|
|
except Exception as e:
|
|
raise ValueError("The base64 string is not valid") from e
|
|
|
|
# mime_type is an important information, so we try to guess it if not provided
|
|
if not self.mime_type:
|
|
guess = filetype.guess(decoded_data)
|
|
if guess:
|
|
self.mime_type = guess.mime
|
|
else:
|
|
msg = (
|
|
"Failed to guess the MIME type of the file. Omitting the MIME type may result in "
|
|
"processing errors or incorrect handling of the file by LLM providers."
|
|
)
|
|
logger.warning(msg)
|
|
|
|
def __repr__(self) -> str:
|
|
"""
|
|
Return a string representation of the FileContent, truncating the base64_data to 100 bytes.
|
|
"""
|
|
fields = []
|
|
|
|
truncated_data = self.base64_data[:100] + "..." if len(self.base64_data) > 100 else self.base64_data
|
|
fields.append(f"base64_data={truncated_data!r}")
|
|
fields.append(f"mime_type={self.mime_type!r}")
|
|
fields.append(f"filename={self.filename!r}")
|
|
fields.append(f"extra={self.extra!r}")
|
|
fields_str = ", ".join(fields)
|
|
return f"{self.__class__.__name__}({fields_str})"
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""
|
|
Convert FileContent into a dictionary.
|
|
"""
|
|
return asdict(self)
|
|
|
|
def _to_trace_dict(self) -> dict[str, Any]:
|
|
"""
|
|
Convert the FileContent to a dictionary representation for tracing.
|
|
|
|
The base64_data is replaced with a placeholder string to avoid sending large payloads to the tracing backend.
|
|
|
|
:returns:
|
|
Serialized version of the object only for tracing purposes.
|
|
"""
|
|
data = self.to_dict()
|
|
data["base64_data"] = f"Base64 string ({len(self.base64_data)} characters)"
|
|
return data
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> "FileContent":
|
|
"""
|
|
Create an FileContent from a dictionary.
|
|
"""
|
|
return FileContent(**data)
|
|
|
|
@classmethod
|
|
def from_file_path(
|
|
cls, file_path: str | Path, *, filename: str | None = None, extra: dict[str, Any] | None = None
|
|
) -> "FileContent":
|
|
"""
|
|
Create an FileContent object from a file path.
|
|
|
|
:param file_path:
|
|
The path to the file.
|
|
:param filename:
|
|
Optional file name. Some LLM providers use this information. If not provided, the filename is extracted
|
|
from the file path.
|
|
:param extra:
|
|
Dictionary of extra information about the file. Can be used to store provider-specific information.
|
|
To avoid serialization issues, values should be JSON serializable.
|
|
|
|
:returns:
|
|
An FileContent object.
|
|
"""
|
|
if isinstance(file_path, str):
|
|
file_path = Path(file_path)
|
|
|
|
mime_type = mimetypes.guess_type(file_path.as_posix())[0]
|
|
filename = filename or file_path.name
|
|
|
|
with open(file_path, "rb") as f:
|
|
data = f.read()
|
|
|
|
return cls(
|
|
base64_data=base64.b64encode(data).decode("utf-8"),
|
|
mime_type=mime_type,
|
|
filename=filename,
|
|
extra=extra or {},
|
|
validation=False,
|
|
)
|
|
|
|
@classmethod
|
|
def from_url(
|
|
cls,
|
|
url: str,
|
|
*,
|
|
retry_attempts: int = 2,
|
|
timeout: int = 10,
|
|
filename: str | None = None,
|
|
extra: dict[str, Any] | None = None,
|
|
) -> "FileContent":
|
|
"""
|
|
Create an FileContent object from a URL. The file is downloaded and converted to a base64 string.
|
|
|
|
:param url:
|
|
The URL of the file.
|
|
:param retry_attempts:
|
|
The number of times to retry to fetch the URL's content.
|
|
:param timeout:
|
|
Timeout in seconds for the request.
|
|
:param filename:
|
|
Optional filename of the file. Some LLM providers use this information. If not provided, the filename is
|
|
extracted from the URL.
|
|
:param extra:
|
|
Dictionary of extra information about the file. Can be used to store provider-specific information.
|
|
To avoid serialization issues, values should be JSON serializable.
|
|
|
|
:returns:
|
|
An FileContent object.
|
|
"""
|
|
from haystack.components.fetchers.link_content import LinkContentFetcher
|
|
|
|
fetcher = LinkContentFetcher(raise_on_failure=True, retry_attempts=retry_attempts, timeout=timeout)
|
|
bytestream = fetcher.run(urls=[url])["streams"][0]
|
|
|
|
mime_type = bytestream.mime_type
|
|
data = bytestream.data
|
|
|
|
if not filename:
|
|
filename = os.path.basename(unquote(urlparse(url).path))
|
|
|
|
return cls(
|
|
base64_data=base64.b64encode(data).decode("utf-8"),
|
|
mime_type=mime_type,
|
|
filename=filename,
|
|
extra=extra or {},
|
|
validation=False,
|
|
)
|