lfx-nightly 0.2.0.dev0__py3-none-any.whl → 0.2.0.dev41__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lfx/_assets/component_index.json +1 -1
- lfx/base/agents/agent.py +21 -4
- lfx/base/agents/altk_base_agent.py +393 -0
- lfx/base/agents/altk_tool_wrappers.py +565 -0
- lfx/base/agents/events.py +2 -1
- lfx/base/composio/composio_base.py +159 -224
- lfx/base/data/base_file.py +97 -20
- lfx/base/data/docling_utils.py +61 -10
- lfx/base/data/storage_utils.py +301 -0
- lfx/base/data/utils.py +178 -14
- lfx/base/mcp/util.py +2 -2
- lfx/base/models/anthropic_constants.py +21 -12
- lfx/base/models/groq_constants.py +74 -58
- lfx/base/models/groq_model_discovery.py +265 -0
- lfx/base/models/model.py +1 -1
- lfx/base/models/model_utils.py +100 -0
- lfx/base/models/openai_constants.py +7 -0
- lfx/base/models/watsonx_constants.py +32 -8
- lfx/base/tools/run_flow.py +601 -129
- lfx/cli/commands.py +9 -4
- lfx/cli/common.py +2 -2
- lfx/cli/run.py +1 -1
- lfx/cli/script_loader.py +53 -11
- lfx/components/Notion/create_page.py +1 -1
- lfx/components/Notion/list_database_properties.py +1 -1
- lfx/components/Notion/list_pages.py +1 -1
- lfx/components/Notion/list_users.py +1 -1
- lfx/components/Notion/page_content_viewer.py +1 -1
- lfx/components/Notion/search.py +1 -1
- lfx/components/Notion/update_page_property.py +1 -1
- lfx/components/__init__.py +19 -5
- lfx/components/{agents → altk}/__init__.py +5 -9
- lfx/components/altk/altk_agent.py +193 -0
- lfx/components/apify/apify_actor.py +1 -1
- lfx/components/composio/__init__.py +70 -18
- lfx/components/composio/apollo_composio.py +11 -0
- lfx/components/composio/bitbucket_composio.py +11 -0
- lfx/components/composio/canva_composio.py +11 -0
- lfx/components/composio/coda_composio.py +11 -0
- lfx/components/composio/composio_api.py +10 -0
- lfx/components/composio/discord_composio.py +1 -1
- lfx/components/composio/elevenlabs_composio.py +11 -0
- lfx/components/composio/exa_composio.py +11 -0
- lfx/components/composio/firecrawl_composio.py +11 -0
- lfx/components/composio/fireflies_composio.py +11 -0
- lfx/components/composio/gmail_composio.py +1 -1
- lfx/components/composio/googlebigquery_composio.py +11 -0
- lfx/components/composio/googlecalendar_composio.py +1 -1
- lfx/components/composio/googledocs_composio.py +1 -1
- lfx/components/composio/googlemeet_composio.py +1 -1
- lfx/components/composio/googlesheets_composio.py +1 -1
- lfx/components/composio/googletasks_composio.py +1 -1
- lfx/components/composio/heygen_composio.py +11 -0
- lfx/components/composio/mem0_composio.py +11 -0
- lfx/components/composio/peopledatalabs_composio.py +11 -0
- lfx/components/composio/perplexityai_composio.py +11 -0
- lfx/components/composio/serpapi_composio.py +11 -0
- lfx/components/composio/slack_composio.py +3 -574
- lfx/components/composio/slackbot_composio.py +1 -1
- lfx/components/composio/snowflake_composio.py +11 -0
- lfx/components/composio/tavily_composio.py +11 -0
- lfx/components/composio/youtube_composio.py +2 -2
- lfx/components/cuga/__init__.py +34 -0
- lfx/components/cuga/cuga_agent.py +730 -0
- lfx/components/data/__init__.py +78 -28
- lfx/components/data_source/__init__.py +58 -0
- lfx/components/{data → data_source}/api_request.py +26 -3
- lfx/components/{data → data_source}/csv_to_data.py +15 -10
- lfx/components/{data → data_source}/json_to_data.py +15 -8
- lfx/components/{data → data_source}/news_search.py +1 -1
- lfx/components/{data → data_source}/rss.py +1 -1
- lfx/components/{data → data_source}/sql_executor.py +1 -1
- lfx/components/{data → data_source}/url.py +1 -1
- lfx/components/{data → data_source}/web_search.py +1 -1
- lfx/components/datastax/astradb_cql.py +1 -1
- lfx/components/datastax/astradb_graph.py +1 -1
- lfx/components/datastax/astradb_tool.py +1 -1
- lfx/components/datastax/astradb_vectorstore.py +1 -1
- lfx/components/datastax/hcd.py +1 -1
- lfx/components/deactivated/json_document_builder.py +1 -1
- lfx/components/docling/__init__.py +0 -3
- lfx/components/docling/chunk_docling_document.py +3 -1
- lfx/components/docling/export_docling_document.py +3 -1
- lfx/components/elastic/elasticsearch.py +1 -1
- lfx/components/files_and_knowledge/__init__.py +47 -0
- lfx/components/{data → files_and_knowledge}/directory.py +1 -1
- lfx/components/{data → files_and_knowledge}/file.py +304 -24
- lfx/components/{knowledge_bases → files_and_knowledge}/retrieval.py +2 -2
- lfx/components/{data → files_and_knowledge}/save_file.py +218 -31
- lfx/components/flow_controls/__init__.py +58 -0
- lfx/components/{logic → flow_controls}/conditional_router.py +1 -1
- lfx/components/{logic → flow_controls}/loop.py +43 -9
- lfx/components/flow_controls/run_flow.py +108 -0
- lfx/components/glean/glean_search_api.py +1 -1
- lfx/components/groq/groq.py +35 -28
- lfx/components/helpers/__init__.py +102 -0
- lfx/components/ibm/watsonx.py +7 -1
- lfx/components/input_output/__init__.py +3 -1
- lfx/components/input_output/chat.py +4 -3
- lfx/components/input_output/chat_output.py +10 -4
- lfx/components/input_output/text.py +1 -1
- lfx/components/input_output/text_output.py +1 -1
- lfx/components/{data → input_output}/webhook.py +1 -1
- lfx/components/knowledge_bases/__init__.py +59 -4
- lfx/components/langchain_utilities/character.py +1 -1
- lfx/components/langchain_utilities/csv_agent.py +84 -16
- lfx/components/langchain_utilities/json_agent.py +67 -12
- lfx/components/langchain_utilities/language_recursive.py +1 -1
- lfx/components/llm_operations/__init__.py +46 -0
- lfx/components/{processing → llm_operations}/batch_run.py +17 -8
- lfx/components/{processing → llm_operations}/lambda_filter.py +1 -1
- lfx/components/{logic → llm_operations}/llm_conditional_router.py +1 -1
- lfx/components/{processing/llm_router.py → llm_operations/llm_selector.py} +3 -3
- lfx/components/{processing → llm_operations}/structured_output.py +1 -1
- lfx/components/logic/__init__.py +126 -0
- lfx/components/mem0/mem0_chat_memory.py +11 -0
- lfx/components/models/__init__.py +64 -9
- lfx/components/models_and_agents/__init__.py +49 -0
- lfx/components/{agents → models_and_agents}/agent.py +6 -4
- lfx/components/models_and_agents/embedding_model.py +353 -0
- lfx/components/models_and_agents/language_model.py +398 -0
- lfx/components/{agents → models_and_agents}/mcp_component.py +53 -44
- lfx/components/{helpers → models_and_agents}/memory.py +1 -1
- lfx/components/nvidia/system_assist.py +1 -1
- lfx/components/olivya/olivya.py +1 -1
- lfx/components/ollama/ollama.py +24 -5
- lfx/components/processing/__init__.py +9 -60
- lfx/components/processing/converter.py +1 -1
- lfx/components/processing/dataframe_operations.py +1 -1
- lfx/components/processing/parse_json_data.py +2 -2
- lfx/components/processing/parser.py +1 -1
- lfx/components/processing/split_text.py +1 -1
- lfx/components/qdrant/qdrant.py +1 -1
- lfx/components/redis/redis.py +1 -1
- lfx/components/twelvelabs/split_video.py +10 -0
- lfx/components/twelvelabs/video_file.py +12 -0
- lfx/components/utilities/__init__.py +43 -0
- lfx/components/{helpers → utilities}/calculator_core.py +1 -1
- lfx/components/{helpers → utilities}/current_date.py +1 -1
- lfx/components/{processing → utilities}/python_repl_core.py +1 -1
- lfx/components/vectorstores/local_db.py +9 -0
- lfx/components/youtube/youtube_transcripts.py +118 -30
- lfx/custom/custom_component/component.py +57 -1
- lfx/custom/custom_component/custom_component.py +68 -6
- lfx/custom/directory_reader/directory_reader.py +5 -2
- lfx/graph/edge/base.py +43 -20
- lfx/graph/state/model.py +15 -2
- lfx/graph/utils.py +6 -0
- lfx/graph/vertex/param_handler.py +10 -7
- lfx/helpers/__init__.py +12 -0
- lfx/helpers/flow.py +117 -0
- lfx/inputs/input_mixin.py +24 -1
- lfx/inputs/inputs.py +13 -1
- lfx/interface/components.py +161 -83
- lfx/log/logger.py +5 -3
- lfx/schema/image.py +2 -12
- lfx/services/database/__init__.py +5 -0
- lfx/services/database/service.py +25 -0
- lfx/services/deps.py +87 -22
- lfx/services/interfaces.py +5 -0
- lfx/services/manager.py +24 -10
- lfx/services/mcp_composer/service.py +1029 -162
- lfx/services/session.py +5 -0
- lfx/services/settings/auth.py +18 -11
- lfx/services/settings/base.py +56 -30
- lfx/services/settings/constants.py +8 -0
- lfx/services/storage/local.py +108 -46
- lfx/services/storage/service.py +171 -29
- lfx/template/field/base.py +3 -0
- lfx/utils/image.py +29 -11
- lfx/utils/ssrf_protection.py +384 -0
- lfx/utils/validate_cloud.py +26 -0
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/METADATA +38 -22
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/RECORD +189 -160
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/WHEEL +1 -1
- lfx/components/agents/altk_agent.py +0 -366
- lfx/components/agents/cuga_agent.py +0 -1013
- lfx/components/docling/docling_remote_vlm.py +0 -284
- lfx/components/logic/run_flow.py +0 -71
- lfx/components/models/embedding_model.py +0 -195
- lfx/components/models/language_model.py +0 -144
- lfx/components/processing/dataframe_to_toolset.py +0 -259
- /lfx/components/{data → data_source}/mock_data.py +0 -0
- /lfx/components/{knowledge_bases → files_and_knowledge}/ingestion.py +0 -0
- /lfx/components/{logic → flow_controls}/data_conditional_router.py +0 -0
- /lfx/components/{logic → flow_controls}/flow_tool.py +0 -0
- /lfx/components/{logic → flow_controls}/listen.py +0 -0
- /lfx/components/{logic → flow_controls}/notify.py +0 -0
- /lfx/components/{logic → flow_controls}/pass_message.py +0 -0
- /lfx/components/{logic → flow_controls}/sub_flow.py +0 -0
- /lfx/components/{processing → models_and_agents}/prompt.py +0 -0
- /lfx/components/{helpers → processing}/create_list.py +0 -0
- /lfx/components/{helpers → processing}/output_parser.py +0 -0
- /lfx/components/{helpers → processing}/store_message.py +0 -0
- /lfx/components/{helpers → utilities}/id_generator.py +0 -0
- {lfx_nightly-0.2.0.dev0.dist-info → lfx_nightly-0.2.0.dev41.dist-info}/entry_points.txt +0 -0
lfx/services/storage/service.py
CHANGED
|
@@ -1,54 +1,196 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from abc import
|
|
4
|
-
from
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
import anyio
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
"""Abstract base class for storage services."""
|
|
8
|
+
from lfx.services.base import Service
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import AsyncIterator
|
|
12
|
+
|
|
13
|
+
from lfx.services.settings.service import SettingsService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StorageService(Service):
|
|
17
|
+
"""Abstract base class for file storage services.
|
|
18
|
+
|
|
19
|
+
This class defines the interface for file storage operations that can be
|
|
20
|
+
implemented by different backends (local filesystem, S3, etc.).
|
|
21
|
+
|
|
22
|
+
All file operations are namespaced by flow_id to isolate files between
|
|
23
|
+
different flows or users.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
name = "storage_service"
|
|
27
|
+
|
|
28
|
+
def __init__(self, session_service, settings_service: SettingsService):
|
|
11
29
|
"""Initialize the storage service.
|
|
12
30
|
|
|
13
31
|
Args:
|
|
14
|
-
|
|
32
|
+
session_service: The session service instance
|
|
33
|
+
settings_service: The settings service instance containing configuration
|
|
34
|
+
"""
|
|
35
|
+
self.settings_service = settings_service
|
|
36
|
+
self.session_service = session_service
|
|
37
|
+
self.data_dir: anyio.Path = anyio.Path(settings_service.settings.config_dir)
|
|
38
|
+
self.set_ready()
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def build_full_path(self, flow_id: str, file_name: str) -> str:
|
|
42
|
+
"""Build the full path/key for a file.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
flow_id: The flow/user identifier for namespacing
|
|
46
|
+
file_name: The name of the file
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: The full path or key for the file
|
|
15
50
|
"""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
51
|
+
raise NotImplementedError
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def parse_file_path(self, full_path: str) -> tuple[str, str]:
|
|
55
|
+
"""Parse a full storage path to extract flow_id and file_name.
|
|
56
|
+
|
|
57
|
+
This reverses the build_full_path operation.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
full_path: Full path as returned by build_full_path
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
tuple[str, str]: A tuple of (flow_id, file_name)
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ValueError: If the path format is invalid or doesn't match expected structure
|
|
67
|
+
"""
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def resolve_component_path(self, logical_path: str) -> str:
|
|
72
|
+
"""Convert a logical path to a format that components can use directly.
|
|
73
|
+
|
|
74
|
+
Logical paths are in the format "{flow_id}/{filename}" as stored in the database.
|
|
75
|
+
This method converts them to a format appropriate for the storage backend:
|
|
76
|
+
- Local storage: Absolute filesystem path (/data_dir/flow_id/filename)
|
|
77
|
+
- S3 storage: Logical path as-is (flow_id/filename)
|
|
78
|
+
|
|
79
|
+
Components receive this resolved path and can use it without knowing the
|
|
80
|
+
storage implementation details.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
logical_path: Path in the format "flow_id/filename"
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
str: A path that components can use directly
|
|
87
|
+
"""
|
|
88
|
+
raise NotImplementedError
|
|
20
89
|
|
|
21
90
|
def set_ready(self) -> None:
|
|
22
91
|
"""Mark the service as ready."""
|
|
23
92
|
self._ready = True
|
|
24
|
-
# Ensure the data directory exists
|
|
25
|
-
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def ready(self) -> bool:
|
|
29
|
-
"""Check if the service is ready."""
|
|
30
|
-
return self._ready
|
|
31
93
|
|
|
32
94
|
@abstractmethod
|
|
33
|
-
def
|
|
34
|
-
"""
|
|
95
|
+
async def save_file(self, flow_id: str, file_name: str, data: bytes, *, append: bool = False) -> None:
|
|
96
|
+
"""Save a file to storage.
|
|
35
97
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
98
|
+
Args:
|
|
99
|
+
flow_id: The flow/user identifier for namespacing
|
|
100
|
+
file_name: The name of the file to save
|
|
101
|
+
data: The file content as bytes
|
|
102
|
+
append: If True, append to existing file instead of overwriting.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
Exception: If the file cannot be saved
|
|
106
|
+
"""
|
|
107
|
+
raise NotImplementedError
|
|
39
108
|
|
|
40
109
|
@abstractmethod
|
|
41
110
|
async def get_file(self, flow_id: str, file_name: str) -> bytes:
|
|
42
|
-
"""Retrieve a file.
|
|
111
|
+
"""Retrieve a file from storage.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
flow_id: The flow/user identifier for namespacing
|
|
115
|
+
file_name: The name of the file to retrieve
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bytes: The file content
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
FileNotFoundError: If the file does not exist
|
|
122
|
+
"""
|
|
123
|
+
raise NotImplementedError
|
|
124
|
+
|
|
125
|
+
async def get_file_stream(self, flow_id: str, file_name: str, chunk_size: int = 8192) -> AsyncIterator[bytes]:
|
|
126
|
+
"""Retrieve a file from storage as a stream.
|
|
127
|
+
|
|
128
|
+
Default implementation loads the entire file and yields it in chunks.
|
|
129
|
+
Subclasses can override this for more efficient streaming.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
flow_id: The flow/user identifier for namespacing
|
|
133
|
+
file_name: The name of the file to retrieve
|
|
134
|
+
chunk_size: Size of chunks to yield (default: 8192 bytes)
|
|
135
|
+
|
|
136
|
+
Yields:
|
|
137
|
+
bytes: Chunks of the file content
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
FileNotFoundError: If the file does not exist
|
|
141
|
+
"""
|
|
142
|
+
# Default implementation - subclasses can override for true streaming
|
|
143
|
+
content = await self.get_file(flow_id, file_name)
|
|
144
|
+
for i in range(0, len(content), chunk_size):
|
|
145
|
+
yield content[i : i + chunk_size]
|
|
43
146
|
|
|
44
147
|
@abstractmethod
|
|
45
148
|
async def list_files(self, flow_id: str) -> list[str]:
|
|
46
|
-
"""List files in a flow.
|
|
149
|
+
"""List all files in a flow's storage namespace.
|
|
47
150
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
151
|
+
Args:
|
|
152
|
+
flow_id: The flow/user identifier for namespacing
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
list[str]: List of file names in the namespace
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
FileNotFoundError: If the namespace directory does not exist
|
|
159
|
+
"""
|
|
160
|
+
raise NotImplementedError
|
|
51
161
|
|
|
52
162
|
@abstractmethod
|
|
53
163
|
async def get_file_size(self, flow_id: str, file_name: str) -> int:
|
|
54
|
-
"""Get the size of a file.
|
|
164
|
+
"""Get the size of a file in bytes.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
flow_id: The flow/user identifier for namespacing
|
|
168
|
+
file_name: The name of the file
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
int: Size of the file in bytes
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
FileNotFoundError: If the file does not exist
|
|
175
|
+
"""
|
|
176
|
+
raise NotImplementedError
|
|
177
|
+
|
|
178
|
+
@abstractmethod
|
|
179
|
+
async def delete_file(self, flow_id: str, file_name: str) -> None:
|
|
180
|
+
"""Delete a file from storage.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
flow_id: The flow/user identifier for namespacing
|
|
184
|
+
file_name: The name of the file to delete
|
|
185
|
+
|
|
186
|
+
Note:
|
|
187
|
+
Should not raise an error if the file doesn't exist
|
|
188
|
+
"""
|
|
189
|
+
raise NotImplementedError
|
|
190
|
+
|
|
191
|
+
async def teardown(self) -> None:
|
|
192
|
+
"""Perform cleanup operations when the service is being shut down.
|
|
193
|
+
|
|
194
|
+
Subclasses should override this to clean up any resources (connections, etc.)
|
|
195
|
+
"""
|
|
196
|
+
raise NotImplementedError
|
lfx/template/field/base.py
CHANGED
|
@@ -208,6 +208,9 @@ class Output(BaseModel):
|
|
|
208
208
|
allows_loop: bool = Field(default=False)
|
|
209
209
|
"""Specifies if the output allows looping."""
|
|
210
210
|
|
|
211
|
+
loop_types: list[str] | None = Field(default=None)
|
|
212
|
+
"""List of additional types to include for loop inputs when allows_loop is True."""
|
|
213
|
+
|
|
211
214
|
group_outputs: bool = Field(default=False)
|
|
212
215
|
"""Specifies if all outputs should be grouped and shown without dropdowns."""
|
|
213
216
|
|
lfx/utils/image.py
CHANGED
|
@@ -6,14 +6,19 @@ import base64
|
|
|
6
6
|
from functools import lru_cache
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
from lfx.log import logger
|
|
10
|
+
from lfx.services.deps import get_storage_service
|
|
11
|
+
from lfx.utils.async_helpers import run_until_complete
|
|
9
12
|
from lfx.utils.helpers import get_mime_type
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
def convert_image_to_base64(image_path: str | Path) -> str:
|
|
13
16
|
"""Convert an image file to a base64 encoded string.
|
|
14
17
|
|
|
18
|
+
Handles both local files and S3 storage paths.
|
|
19
|
+
|
|
15
20
|
Args:
|
|
16
|
-
image_path: Path to the image file
|
|
21
|
+
image_path: Path to the image file (local or S3 path like "flow_id/filename")
|
|
17
22
|
|
|
18
23
|
Returns:
|
|
19
24
|
Base64 encoded string of the image
|
|
@@ -22,6 +27,20 @@ def convert_image_to_base64(image_path: str | Path) -> str:
|
|
|
22
27
|
FileNotFoundError: If the image file doesn't exist
|
|
23
28
|
"""
|
|
24
29
|
image_path = Path(image_path)
|
|
30
|
+
|
|
31
|
+
storage_service = get_storage_service()
|
|
32
|
+
if storage_service:
|
|
33
|
+
flow_id, file_name = storage_service.parse_file_path(str(image_path))
|
|
34
|
+
try:
|
|
35
|
+
file_content = run_until_complete(
|
|
36
|
+
storage_service.get_file(flow_id=flow_id, file_name=file_name) # type: ignore[call-arg]
|
|
37
|
+
)
|
|
38
|
+
return base64.b64encode(file_content).decode("utf-8")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.error(f"Error reading image file: {e}")
|
|
41
|
+
raise
|
|
42
|
+
|
|
43
|
+
# Fall back to local file access
|
|
25
44
|
if not image_path.exists():
|
|
26
45
|
msg = f"Image file not found: {image_path}"
|
|
27
46
|
raise FileNotFoundError(msg)
|
|
@@ -34,7 +53,7 @@ def create_data_url(image_path: str | Path, mime_type: str | None = None) -> str
|
|
|
34
53
|
"""Create a data URL from an image file.
|
|
35
54
|
|
|
36
55
|
Args:
|
|
37
|
-
image_path: Path to the image file
|
|
56
|
+
image_path: Path to the image file (local or S3 path like "flow_id/filename")
|
|
38
57
|
mime_type: MIME type of the image. If None, will be auto-detected
|
|
39
58
|
|
|
40
59
|
Returns:
|
|
@@ -44,9 +63,6 @@ def create_data_url(image_path: str | Path, mime_type: str | None = None) -> str
|
|
|
44
63
|
FileNotFoundError: If the image file doesn't exist
|
|
45
64
|
"""
|
|
46
65
|
image_path = Path(image_path)
|
|
47
|
-
if not image_path.exists():
|
|
48
|
-
msg = f"Image file not found: {image_path}"
|
|
49
|
-
raise FileNotFoundError(msg)
|
|
50
66
|
|
|
51
67
|
if mime_type is None:
|
|
52
68
|
mime_type = get_mime_type(image_path)
|
|
@@ -57,14 +73,16 @@ def create_data_url(image_path: str | Path, mime_type: str | None = None) -> str
|
|
|
57
73
|
|
|
58
74
|
@lru_cache(maxsize=50)
|
|
59
75
|
def create_image_content_dict(
|
|
60
|
-
image_path: str | Path,
|
|
76
|
+
image_path: str | Path,
|
|
77
|
+
mime_type: str | None = None,
|
|
78
|
+
model_name: str | None = None, # noqa: ARG001
|
|
61
79
|
) -> dict:
|
|
62
80
|
"""Create a content dictionary for multimodal inputs from an image file.
|
|
63
81
|
|
|
64
82
|
Args:
|
|
65
|
-
image_path: Path to the image file
|
|
83
|
+
image_path: Path to the image file (local or S3 path like "flow_id/filename")
|
|
66
84
|
mime_type: MIME type of the image. If None, will be auto-detected
|
|
67
|
-
model_name: Optional model parameter
|
|
85
|
+
model_name: Optional model parameter (kept for backward compatibility, no longer used)
|
|
68
86
|
|
|
69
87
|
Returns:
|
|
70
88
|
Content dictionary with type and image_url fields
|
|
@@ -74,6 +92,6 @@ def create_image_content_dict(
|
|
|
74
92
|
"""
|
|
75
93
|
data_url = create_data_url(image_path, mime_type)
|
|
76
94
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return {"type": "
|
|
95
|
+
# Standard format for OpenAI, Anthropic, Gemini, and most providers
|
|
96
|
+
# Format: {"type": "image_url", "image_url": {"url": "data:..."}}
|
|
97
|
+
return {"type": "image_url", "image_url": {"url": data_url}}
|