lfx-nightly 0.1.12.dev27__py3-none-any.whl → 0.1.12.dev28__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/base/agents/agent.py +19 -11
- lfx/base/agents/utils.py +18 -0
- lfx/base/data/base_file.py +28 -19
- lfx/components/data/__init__.py +0 -6
- lfx/components/data/file.py +1 -1
- lfx/components/data/mock_data.py +5 -8
- lfx/components/data/save_file.py +625 -0
- lfx/components/data/web_search.py +225 -11
- lfx/components/docling/docling_remote.py +4 -1
- lfx/components/input_output/chat.py +8 -1
- lfx/components/nvidia/nvidia.py +1 -4
- lfx/components/processing/__init__.py +3 -3
- lfx/components/processing/dataframe_to_toolset.py +259 -0
- lfx/components/processing/lambda_filter.py +3 -3
- lfx/schema/image.py +72 -19
- lfx/schema/message.py +7 -2
- lfx/services/settings/base.py +7 -0
- lfx/utils/util.py +135 -0
- {lfx_nightly-0.1.12.dev27.dist-info → lfx_nightly-0.1.12.dev28.dist-info}/METADATA +1 -1
- {lfx_nightly-0.1.12.dev27.dist-info → lfx_nightly-0.1.12.dev28.dist-info}/RECORD +22 -23
- lfx/components/data/news_search.py +0 -164
- lfx/components/data/rss.py +0 -69
- lfx/components/processing/save_file.py +0 -225
- {lfx_nightly-0.1.12.dev27.dist-info → lfx_nightly-0.1.12.dev28.dist-info}/WHEEL +0 -0
- {lfx_nightly-0.1.12.dev27.dist-info → lfx_nightly-0.1.12.dev28.dist-info}/entry_points.txt +0 -0
lfx/base/agents/agent.py
CHANGED
|
@@ -10,7 +10,7 @@ from langchain_core.runnables import Runnable
|
|
|
10
10
|
|
|
11
11
|
from lfx.base.agents.callback import AgentAsyncHandler
|
|
12
12
|
from lfx.base.agents.events import ExceptionWithMessageError, process_agent_events
|
|
13
|
-
from lfx.base.agents.utils import data_to_messages
|
|
13
|
+
from lfx.base.agents.utils import data_to_messages, get_chat_output_sender_name
|
|
14
14
|
from lfx.custom.custom_component.component import Component, _get_component_toolkit
|
|
15
15
|
from lfx.field_typing import Tool
|
|
16
16
|
from lfx.inputs.inputs import InputTypes, MultilineInput
|
|
@@ -137,11 +137,15 @@ class LCAgentComponent(Component):
|
|
|
137
137
|
verbose=verbose,
|
|
138
138
|
max_iterations=max_iterations,
|
|
139
139
|
)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
else
|
|
144
|
-
|
|
140
|
+
# Convert input_value to proper format for agent
|
|
141
|
+
if hasattr(self.input_value, "to_lc_message") and callable(self.input_value.to_lc_message):
|
|
142
|
+
lc_message = self.input_value.to_lc_message()
|
|
143
|
+
input_text = lc_message.content if hasattr(lc_message, "content") else str(lc_message)
|
|
144
|
+
else:
|
|
145
|
+
lc_message = None
|
|
146
|
+
input_text = self.input_value
|
|
147
|
+
|
|
148
|
+
input_dict: dict[str, str | list[BaseMessage]] = {}
|
|
145
149
|
if hasattr(self, "system_prompt"):
|
|
146
150
|
input_dict["system_prompt"] = self.system_prompt
|
|
147
151
|
if hasattr(self, "chat_history") and self.chat_history:
|
|
@@ -154,11 +158,13 @@ class LCAgentComponent(Component):
|
|
|
154
158
|
# Handle both lfx.schema.message.Message and langflow.schema.message.Message types
|
|
155
159
|
if all(hasattr(m, "to_data") and callable(m.to_data) and "text" in m.data for m in self.chat_history):
|
|
156
160
|
input_dict["chat_history"] = data_to_messages(self.chat_history)
|
|
157
|
-
|
|
161
|
+
if all(isinstance(m, Message) for m in self.chat_history):
|
|
162
|
+
input_dict["chat_history"] = data_to_messages([m.to_data() for m in self.chat_history])
|
|
163
|
+
if hasattr(lc_message, "content") and isinstance(lc_message.content, list):
|
|
158
164
|
# ! Because the input has to be a string, we must pass the images in the chat_history
|
|
159
165
|
|
|
160
|
-
image_dicts = [item for item in
|
|
161
|
-
|
|
166
|
+
image_dicts = [item for item in lc_message.content if item.get("type") == "image"]
|
|
167
|
+
lc_message.content = [item for item in lc_message.content if item.get("type") != "image"]
|
|
162
168
|
|
|
163
169
|
if "chat_history" not in input_dict:
|
|
164
170
|
input_dict["chat_history"] = []
|
|
@@ -166,7 +172,7 @@ class LCAgentComponent(Component):
|
|
|
166
172
|
input_dict["chat_history"].extend(HumanMessage(content=[image_dict]) for image_dict in image_dicts)
|
|
167
173
|
else:
|
|
168
174
|
input_dict["chat_history"] = [HumanMessage(content=[image_dict]) for image_dict in image_dicts]
|
|
169
|
-
|
|
175
|
+
input_dict["input"] = input_text
|
|
170
176
|
if hasattr(self, "graph"):
|
|
171
177
|
session_id = self.graph.session_id
|
|
172
178
|
elif hasattr(self, "_session_id"):
|
|
@@ -174,9 +180,11 @@ class LCAgentComponent(Component):
|
|
|
174
180
|
else:
|
|
175
181
|
session_id = None
|
|
176
182
|
|
|
183
|
+
sender_name = get_chat_output_sender_name(self) or self.display_name or "AI"
|
|
184
|
+
|
|
177
185
|
agent_message = Message(
|
|
178
186
|
sender=MESSAGE_SENDER_AI,
|
|
179
|
-
sender_name=
|
|
187
|
+
sender_name=sender_name,
|
|
180
188
|
properties={"icon": "Bot", "state": "partial"},
|
|
181
189
|
content_blocks=[ContentBlock(title="Agent Steps", contents=[])],
|
|
182
190
|
session_id=session_id or uuid.uuid4(),
|
lfx/base/agents/utils.py
CHANGED
|
@@ -204,3 +204,21 @@ def maybe_unflatten_dict(flat: dict[str, Any]) -> dict[str, Any]:
|
|
|
204
204
|
cur = cur.setdefault(part, {})
|
|
205
205
|
|
|
206
206
|
return nested
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_chat_output_sender_name(self) -> str | None:
|
|
210
|
+
"""Get sender_name from ChatOutput component."""
|
|
211
|
+
if not hasattr(self, "graph") or not self.graph:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
for vertex in self.graph.vertices:
|
|
215
|
+
# Safely check if vertex has data attribute, correct type, and raw_params
|
|
216
|
+
if (
|
|
217
|
+
hasattr(vertex, "data")
|
|
218
|
+
and vertex.data.get("type") == "ChatOutput"
|
|
219
|
+
and hasattr(vertex, "raw_params")
|
|
220
|
+
and vertex.raw_params
|
|
221
|
+
):
|
|
222
|
+
return vertex.raw_params.get("sender_name")
|
|
223
|
+
|
|
224
|
+
return None
|
lfx/base/data/base_file.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import ast
|
|
2
|
-
import json
|
|
3
2
|
import shutil
|
|
4
3
|
import tarfile
|
|
5
4
|
from abc import ABC, abstractmethod
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from tempfile import TemporaryDirectory
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
9
8
|
from zipfile import ZipFile, is_zipfile
|
|
10
9
|
|
|
11
|
-
import
|
|
10
|
+
import orjson
|
|
12
11
|
import pandas as pd
|
|
13
12
|
|
|
14
13
|
from lfx.custom.custom_component.component import Component
|
|
@@ -224,7 +223,6 @@ class BaseFileComponent(Component, ABC):
|
|
|
224
223
|
# Delete temporary directories
|
|
225
224
|
for temp_dir in self._temp_dirs:
|
|
226
225
|
temp_dir.cleanup()
|
|
227
|
-
|
|
228
226
|
# Delete files marked for deletion
|
|
229
227
|
for file in final_files:
|
|
230
228
|
if file.delete_after_processing and file.path.exists():
|
|
@@ -244,15 +242,15 @@ class BaseFileComponent(Component, ABC):
|
|
|
244
242
|
return [Data()]
|
|
245
243
|
return data_list
|
|
246
244
|
|
|
247
|
-
|
|
245
|
+
def _extract_file_metadata(self, data_item) -> dict:
|
|
248
246
|
"""Extract metadata from a data item with file_path."""
|
|
249
|
-
metadata = {}
|
|
247
|
+
metadata: dict[str, Any] = {}
|
|
250
248
|
if not hasattr(data_item, "file_path"):
|
|
251
249
|
return metadata
|
|
252
250
|
|
|
253
251
|
file_path = data_item.file_path
|
|
254
|
-
file_path_obj =
|
|
255
|
-
file_size_stat =
|
|
252
|
+
file_path_obj = Path(file_path)
|
|
253
|
+
file_size_stat = file_path_obj.stat()
|
|
256
254
|
filename = file_path_obj.name
|
|
257
255
|
|
|
258
256
|
# Basic file metadata
|
|
@@ -280,7 +278,7 @@ class BaseFileComponent(Component, ABC):
|
|
|
280
278
|
return text if text is not None else str(data_item)
|
|
281
279
|
return str(data_item)
|
|
282
280
|
|
|
283
|
-
|
|
281
|
+
def load_files_message(self) -> Message:
|
|
284
282
|
"""Load files and return as Message.
|
|
285
283
|
|
|
286
284
|
Returns:
|
|
@@ -290,16 +288,27 @@ class BaseFileComponent(Component, ABC):
|
|
|
290
288
|
if not data_list:
|
|
291
289
|
return Message()
|
|
292
290
|
|
|
291
|
+
# Extract metadata from the first data item
|
|
292
|
+
metadata = self._extract_file_metadata(data_list[0])
|
|
293
|
+
|
|
293
294
|
sep: str = getattr(self, "separator", "\n\n") or "\n\n"
|
|
294
295
|
parts: list[str] = []
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
296
|
+
for d in data_list:
|
|
297
|
+
try:
|
|
298
|
+
data_text = self._extract_text(d)
|
|
299
|
+
if data_text and isinstance(data_text, str):
|
|
300
|
+
parts.append(data_text)
|
|
301
|
+
elif data_text:
|
|
302
|
+
# get_text() returned non-string, convert it
|
|
303
|
+
parts.append(str(data_text))
|
|
304
|
+
elif isinstance(d.data, dict):
|
|
305
|
+
# convert the data dict to a readable string
|
|
306
|
+
parts.append(orjson.dumps(d.data, default=str).decode())
|
|
307
|
+
else:
|
|
308
|
+
parts.append(str(d))
|
|
309
|
+
except (AttributeError, TypeError, ValueError):
|
|
310
|
+
# Final fallback - just try to convert to string
|
|
311
|
+
parts.append(str(d))
|
|
303
312
|
|
|
304
313
|
return Message(text=sep.join(parts), **metadata)
|
|
305
314
|
|
|
@@ -366,10 +375,10 @@ class BaseFileComponent(Component, ABC):
|
|
|
366
375
|
def parse_string_to_dict(self, s: str) -> dict:
|
|
367
376
|
# Try JSON first (handles true/false/null)
|
|
368
377
|
try:
|
|
369
|
-
result =
|
|
378
|
+
result = orjson.loads(s)
|
|
370
379
|
if isinstance(result, dict):
|
|
371
380
|
return result
|
|
372
|
-
except
|
|
381
|
+
except orjson.JSONDecodeError:
|
|
373
382
|
pass
|
|
374
383
|
|
|
375
384
|
# Fall back to Python literal evaluation
|
lfx/components/data/__init__.py
CHANGED
|
@@ -10,8 +10,6 @@ if TYPE_CHECKING:
|
|
|
10
10
|
from lfx.components.data.directory import DirectoryComponent
|
|
11
11
|
from lfx.components.data.file import FileComponent
|
|
12
12
|
from lfx.components.data.json_to_data import JSONToDataComponent
|
|
13
|
-
from lfx.components.data.news_search import NewsSearchComponent
|
|
14
|
-
from lfx.components.data.rss import RSSReaderComponent
|
|
15
13
|
from lfx.components.data.sql_executor import SQLComponent
|
|
16
14
|
from lfx.components.data.url import URLComponent
|
|
17
15
|
from lfx.components.data.web_search import WebSearchComponent
|
|
@@ -23,8 +21,6 @@ _dynamic_imports = {
|
|
|
23
21
|
"DirectoryComponent": "directory",
|
|
24
22
|
"FileComponent": "file",
|
|
25
23
|
"JSONToDataComponent": "json_to_data",
|
|
26
|
-
"NewsSearchComponent": "news_search",
|
|
27
|
-
"RSSReaderComponent": "rss",
|
|
28
24
|
"SQLComponent": "sql_executor",
|
|
29
25
|
"URLComponent": "url",
|
|
30
26
|
"WebSearchComponent": "web_search",
|
|
@@ -37,8 +33,6 @@ __all__ = [
|
|
|
37
33
|
"DirectoryComponent",
|
|
38
34
|
"FileComponent",
|
|
39
35
|
"JSONToDataComponent",
|
|
40
|
-
"NewsSearchComponent",
|
|
41
|
-
"RSSReaderComponent",
|
|
42
36
|
"SQLComponent",
|
|
43
37
|
"URLComponent",
|
|
44
38
|
"WebSearchComponent",
|
lfx/components/data/file.py
CHANGED
|
@@ -29,7 +29,7 @@ from lfx.schema.message import Message
|
|
|
29
29
|
class FileComponent(BaseFileComponent):
|
|
30
30
|
"""File component with optional Docling processing (isolated in a subprocess)."""
|
|
31
31
|
|
|
32
|
-
display_name = "File"
|
|
32
|
+
display_name = "Read File"
|
|
33
33
|
description = "Loads content from one or more files."
|
|
34
34
|
documentation: str = "https://docs.langflow.org/components-data#file"
|
|
35
35
|
icon = "file-text"
|
lfx/components/data/mock_data.py
CHANGED
|
@@ -20,20 +20,17 @@ class MockDataGeneratorComponent(Component):
|
|
|
20
20
|
- Development and debugging of Langflow components
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
display_name = "Mock Data
|
|
24
|
-
description =
|
|
25
|
-
"Generate sample data for testing and development. "
|
|
26
|
-
"Choose from text messages, JSON data, or tabular data formats."
|
|
27
|
-
)
|
|
23
|
+
display_name = "Mock Data"
|
|
24
|
+
description = "Generate mock data for testing and development."
|
|
28
25
|
icon = "database"
|
|
29
26
|
name = "MockDataGenerator"
|
|
30
27
|
|
|
31
28
|
inputs = []
|
|
32
29
|
|
|
33
30
|
outputs = [
|
|
34
|
-
Output(display_name="
|
|
35
|
-
Output(display_name="
|
|
36
|
-
Output(display_name="
|
|
31
|
+
Output(display_name="Result", name="dataframe_output", method="generate_dataframe_output"),
|
|
32
|
+
Output(display_name="Result", name="message_output", method="generate_message_output"),
|
|
33
|
+
Output(display_name="Result", name="data_output", method="generate_data_output"),
|
|
37
34
|
]
|
|
38
35
|
|
|
39
36
|
def build(self) -> DataFrame:
|