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 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
- input_dict: dict[str, str | list[BaseMessage]] = {
141
- "input": self.input_value.to_lc_message()
142
- if hasattr(self.input_value, "to_lc_message")
143
- else self.input_value
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
- if hasattr(input_dict["input"], "content") and isinstance(input_dict["input"].content, list):
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 input_dict["input"].content if item.get("type") == "image"]
161
- input_dict["input"].content = [item for item in input_dict["input"].content if item.get("type") != "image"]
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=self.display_name or "Agent",
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
@@ -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 anyio
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
- async def _extract_file_metadata(self, data_item) -> dict:
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 = anyio.Path(file_path)
255
- file_size_stat = await file_path_obj.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
- async def load_files_message(self) -> Message:
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
- metadata = {}
296
-
297
- for data_item in data_list:
298
- parts.append(self._extract_text(data_item))
299
-
300
- # Set metadata from first file only
301
- if not metadata:
302
- metadata = await self._extract_file_metadata(data_item)
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 = json.loads(s)
378
+ result = orjson.loads(s)
370
379
  if isinstance(result, dict):
371
380
  return result
372
- except json.JSONDecodeError:
381
+ except orjson.JSONDecodeError:
373
382
  pass
374
383
 
375
384
  # Fall back to Python literal evaluation
@@ -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",
@@ -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"
@@ -20,20 +20,17 @@ class MockDataGeneratorComponent(Component):
20
20
  - Development and debugging of Langflow components
21
21
  """
22
22
 
23
- display_name = "Mock Data Generator"
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="DataFrame Output", name="dataframe_output", method="generate_dataframe_output"),
35
- Output(display_name="Message Output", name="message_output", method="generate_message_output"),
36
- Output(display_name="Data Output", name="data_output", method="generate_data_output"),
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: