c56bef871b
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
95 lines
3.7 KiB
Python
95 lines
3.7 KiB
Python
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import base64
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from haystack import component, logging
|
|
from haystack.components.converters.utils import get_bytestream_from_source, normalize_metadata
|
|
from haystack.dataclasses import ByteStream, FileContent
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
_EMPTY_BYTE_STRING = b""
|
|
|
|
|
|
@component
|
|
class FileToFileContent:
|
|
"""
|
|
Converts files to FileContent objects to be included in ChatMessage objects.
|
|
|
|
### Usage example
|
|
<!-- test-ignore -->
|
|
```python
|
|
from haystack.components.converters import FileToFileContent
|
|
|
|
converter = FileToFileContent()
|
|
sources = ["test/test_files/pdf/react_paper.pdf", "test/test_files/images/haystack-logo.png"]
|
|
file_contents = converter.run(sources=sources)["file_contents"]
|
|
|
|
print(file_contents)
|
|
# >> [FileContent(base64_data='...', mime_type='application/pdf', filename='react_paper.pdf', extra={}),
|
|
# >> FileContent(base64_data='...', mime_type='image/png', filename='haystack-logo.png', extra={})
|
|
# >>]
|
|
```
|
|
"""
|
|
|
|
@component.output_types(file_contents=list[FileContent])
|
|
def run(
|
|
self, sources: list[str | Path | ByteStream], *, extra: dict[str, Any] | list[dict[str, Any]] | None = None
|
|
) -> dict[str, list[FileContent]]:
|
|
"""
|
|
Converts files to FileContent objects.
|
|
|
|
:param sources:
|
|
List of file paths or ByteStream objects to convert.
|
|
:param extra:
|
|
Optional extra information to attach to the FileContent objects. Can be used to store provider-specific
|
|
information.
|
|
To avoid serialization issues, values should be JSON serializable.
|
|
This value can be a list of dictionaries or a single dictionary.
|
|
If it's a single dictionary, its content is added to the extra of all produced FileContent objects.
|
|
If it's a list, its length must match the number of sources as they're zipped together.
|
|
|
|
:returns:
|
|
A dictionary with the following keys:
|
|
- `file_contents`: A list of FileContent objects.
|
|
"""
|
|
if not sources:
|
|
return {"file_contents": []}
|
|
|
|
file_contents = []
|
|
|
|
extra_list = normalize_metadata(extra, sources_count=len(sources))
|
|
|
|
for source, extra_dict in zip(sources, extra_list, strict=True):
|
|
if isinstance(source, str):
|
|
source = Path(source)
|
|
|
|
filename = source.name if isinstance(source, Path) else None
|
|
|
|
try:
|
|
bytestream = get_bytestream_from_source(source, guess_mime_type=True)
|
|
except Exception as e:
|
|
logger.warning("Could not read {source}. Skipping it. Error: {error}", source=source, error=e)
|
|
continue
|
|
|
|
if bytestream.data == _EMPTY_BYTE_STRING:
|
|
logger.warning("File {source} is empty. Skipping it.", source=source)
|
|
continue
|
|
|
|
base64_data = base64.b64encode(bytestream.data).decode("utf-8")
|
|
# ``normalize_metadata`` returns the same dict object for every source when ``extra`` is a
|
|
# single dict (or ``None``), so give each FileContent its own copy. Otherwise mutating one
|
|
# file's ``extra`` downstream would leak into all the others. The other converters avoid this
|
|
# implicitly by merging ``extra`` into a fresh ``{**bytestream.meta, ...}`` dict.
|
|
file_content = FileContent(
|
|
base64_data=base64_data, mime_type=bytestream.mime_type, filename=filename, extra=dict(extra_dict)
|
|
)
|
|
file_contents.append(file_content)
|
|
|
|
return {"file_contents": file_contents}
|