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