lfx-nightly 0.1.12.dev29__py3-none-any.whl → 0.1.12.dev31__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.

Potentially problematic release.


This version of lfx-nightly might be problematic. Click here for more details.

lfx/base/agents/events.py CHANGED
@@ -110,22 +110,23 @@ def _extract_output_text(output: str | list) -> str:
110
110
  # This is a workaround to deal with function calling by Anthropic
111
111
  if "partial_json" in item:
112
112
  return ""
113
+ # For any other dict format, return empty string
114
+ return ""
115
+ # For any other single item type (not str or dict), return empty string
116
+ return ""
113
117
 
114
118
  # Handle multiple items - extract text from all text-type items
115
- else:
116
- text_parts = []
117
- for item in output:
118
- if isinstance(item, str):
119
- text_parts.append(item)
120
- elif isinstance(item, dict):
121
- if "text" in item and item["text"] is not None:
122
- text_parts.append(item["text"])
123
- # Skip tool_use, index-only, and partial_json items
124
- elif (
125
- item.get("type") == "tool_use" or "partial_json" in item or ("index" in item and len(item) == 1)
126
- ):
127
- continue
128
- return "".join(text_parts)
119
+ text_parts = []
120
+ for item in output:
121
+ if isinstance(item, str):
122
+ text_parts.append(item)
123
+ elif isinstance(item, dict):
124
+ if "text" in item and item["text"] is not None:
125
+ text_parts.append(item["text"])
126
+ # Skip tool_use, index-only, and partial_json items
127
+ elif item.get("type") == "tool_use" or "partial_json" in item or ("index" in item and len(item) == 1):
128
+ continue
129
+ return "".join(text_parts)
129
130
 
130
131
  # If we get here, the format is unexpected but try to be graceful
131
132
  return ""
lfx/base/mcp/util.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import contextlib
3
3
  import inspect
4
+ import json
4
5
  import os
5
6
  import platform
6
7
  import re
@@ -189,6 +190,55 @@ def sanitize_mcp_name(name: str, max_length: int = 46) -> str:
189
190
  return name
190
191
 
191
192
 
193
+ def _camel_to_snake(name: str) -> str:
194
+ """Convert camelCase to snake_case."""
195
+ import re
196
+
197
+ # Insert an underscore before any uppercase letter that follows a lowercase letter
198
+ s1 = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name)
199
+ return s1.lower()
200
+
201
+
202
+ def _convert_camel_case_to_snake_case(provided_args: dict[str, Any], arg_schema: type[BaseModel]) -> dict[str, Any]:
203
+ """Convert camelCase field names to snake_case if the schema expects snake_case fields."""
204
+ schema_fields = set(arg_schema.model_fields.keys())
205
+ converted_args = {}
206
+
207
+ for key, value in provided_args.items():
208
+ # If the key already exists in schema, use it as-is
209
+ if key in schema_fields:
210
+ converted_args[key] = value
211
+ else:
212
+ # Try converting camelCase to snake_case
213
+ snake_key = _camel_to_snake(key)
214
+ if snake_key in schema_fields:
215
+ converted_args[snake_key] = value
216
+ else:
217
+ # If neither the original nor converted key exists, keep original
218
+ # The validation will catch this error
219
+ converted_args[key] = value
220
+
221
+ return converted_args
222
+
223
+
224
+ def _handle_tool_validation_error(
225
+ e: Exception, tool_name: str, provided_args: dict[str, Any], arg_schema: type[BaseModel]
226
+ ) -> None:
227
+ """Handle validation errors for tool arguments with detailed error messages."""
228
+ # Check if this is a case where the tool was called with no arguments
229
+ if not provided_args and hasattr(arg_schema, "model_fields"):
230
+ required_fields = [name for name, field in arg_schema.model_fields.items() if field.is_required()]
231
+ if required_fields:
232
+ msg = (
233
+ f"Tool '{tool_name}' requires arguments but none were provided. "
234
+ f"Required fields: {', '.join(required_fields)}. "
235
+ f"Please check that the LLM is properly calling the tool with arguments."
236
+ )
237
+ raise ValueError(msg) from e
238
+ msg = f"Invalid input: {e}"
239
+ raise ValueError(msg) from e
240
+
241
+
192
242
  def create_tool_coroutine(tool_name: str, arg_schema: type[BaseModel], client) -> Callable[..., Awaitable]:
193
243
  async def tool_coroutine(*args, **kwargs):
194
244
  # Get field names from the model (preserving order)
@@ -202,12 +252,12 @@ def create_tool_coroutine(tool_name: str, arg_schema: type[BaseModel], client) -
202
252
  provided_args[field_names[i]] = arg
203
253
  # Merge in keyword arguments
204
254
  provided_args.update(kwargs)
255
+ provided_args = _convert_camel_case_to_snake_case(provided_args, arg_schema)
205
256
  # Validate input and fill defaults for missing optional fields
206
257
  try:
207
258
  validated = arg_schema.model_validate(provided_args)
208
- except Exception as e:
209
- msg = f"Invalid input: {e}"
210
- raise ValueError(msg) from e
259
+ except Exception as e: # noqa: BLE001
260
+ _handle_tool_validation_error(e, tool_name, provided_args, arg_schema)
211
261
 
212
262
  try:
213
263
  return await client.run_tool(tool_name, arguments=validated.model_dump())
@@ -230,11 +280,11 @@ def create_tool_func(tool_name: str, arg_schema: type[BaseModel], client) -> Cal
230
280
  raise ValueError(msg)
231
281
  provided_args[field_names[i]] = arg
232
282
  provided_args.update(kwargs)
283
+ provided_args = _convert_camel_case_to_snake_case(provided_args, arg_schema)
233
284
  try:
234
285
  validated = arg_schema.model_validate(provided_args)
235
- except Exception as e:
236
- msg = f"Invalid input: {e}"
237
- raise ValueError(msg) from e
286
+ except Exception as e: # noqa: BLE001
287
+ _handle_tool_validation_error(e, tool_name, provided_args, arg_schema)
238
288
 
239
289
  try:
240
290
  loop = asyncio.get_event_loop()
@@ -391,7 +441,7 @@ class MCPSessionManager:
391
441
  sessions = server_data.get("sessions", {})
392
442
  sessions_to_remove = []
393
443
 
394
- for session_id, session_info in sessions.items():
444
+ for session_id, session_info in list(sessions.items()):
395
445
  if current_time - session_info["last_used"] > SESSION_IDLE_TIMEOUT:
396
446
  sessions_to_remove.append(session_id)
397
447
 
@@ -488,7 +538,7 @@ class MCPSessionManager:
488
538
  sessions = server_data["sessions"]
489
539
 
490
540
  # Try to find a healthy existing session
491
- for session_id, session_info in sessions.items():
541
+ for session_id, session_info in list(sessions.items()):
492
542
  session = session_info["session"]
493
543
  task = session_info["task"]
494
544
 
@@ -1216,9 +1266,6 @@ class MCPSseClient:
1216
1266
  # Get or create persistent session
1217
1267
  session = await self._get_or_create_session()
1218
1268
 
1219
- # Add timeout to prevent hanging
1220
- import asyncio
1221
-
1222
1269
  result = await asyncio.wait_for(
1223
1270
  session.call_tool(tool_name, arguments=arguments),
1224
1271
  timeout=30.0, # 30 second timeout
@@ -1378,7 +1425,65 @@ async def update_tools(
1378
1425
  logger.warning(f"Could not create schema for tool '{tool.name}' from server '{server_name}'")
1379
1426
  continue
1380
1427
 
1381
- tool_obj = StructuredTool(
1428
+ # Create a custom StructuredTool that bypasses schema validation
1429
+ class MCPStructuredTool(StructuredTool):
1430
+ def run(self, tool_input: str | dict, config=None, **kwargs):
1431
+ """Override the main run method to handle parameter conversion before validation."""
1432
+ # Parse tool_input if it's a string
1433
+ if isinstance(tool_input, str):
1434
+ try:
1435
+ parsed_input = json.loads(tool_input)
1436
+ except json.JSONDecodeError:
1437
+ parsed_input = {"input": tool_input}
1438
+ else:
1439
+ parsed_input = tool_input or {}
1440
+
1441
+ # Convert camelCase parameters to snake_case
1442
+ converted_input = self._convert_parameters(parsed_input)
1443
+
1444
+ # Call the parent run method with converted parameters
1445
+ return super().run(converted_input, config=config, **kwargs)
1446
+
1447
+ async def arun(self, tool_input: str | dict, config=None, **kwargs):
1448
+ """Override the main arun method to handle parameter conversion before validation."""
1449
+ # Parse tool_input if it's a string
1450
+ if isinstance(tool_input, str):
1451
+ try:
1452
+ parsed_input = json.loads(tool_input)
1453
+ except json.JSONDecodeError:
1454
+ parsed_input = {"input": tool_input}
1455
+ else:
1456
+ parsed_input = tool_input or {}
1457
+
1458
+ # Convert camelCase parameters to snake_case
1459
+ converted_input = self._convert_parameters(parsed_input)
1460
+
1461
+ # Call the parent arun method with converted parameters
1462
+ return await super().arun(converted_input, config=config, **kwargs)
1463
+
1464
+ def _convert_parameters(self, input_dict):
1465
+ if not input_dict or not isinstance(input_dict, dict):
1466
+ return input_dict
1467
+
1468
+ converted_dict = {}
1469
+ original_fields = set(self.args_schema.model_fields.keys())
1470
+
1471
+ for key, value in input_dict.items():
1472
+ if key in original_fields:
1473
+ # Field exists as-is
1474
+ converted_dict[key] = value
1475
+ else:
1476
+ # Try to convert camelCase to snake_case
1477
+ snake_key = _camel_to_snake(key)
1478
+ if snake_key in original_fields:
1479
+ converted_dict[snake_key] = value
1480
+ else:
1481
+ # Keep original key
1482
+ converted_dict[key] = value
1483
+
1484
+ return converted_dict
1485
+
1486
+ tool_obj = MCPStructuredTool(
1382
1487
  name=tool.name,
1383
1488
  description=tool.description or "",
1384
1489
  args_schema=args_schema,
@@ -1387,6 +1492,7 @@ async def update_tools(
1387
1492
  tags=[tool.name],
1388
1493
  metadata={"server_name": server_name},
1389
1494
  )
1495
+
1390
1496
  tool_list.append(tool_obj)
1391
1497
  tool_cache[tool.name] = tool_obj
1392
1498
  except (ConnectionError, TimeoutError, OSError, ValueError) as e:
@@ -2,6 +2,8 @@ from .model_metadata import create_model_metadata
2
2
 
3
3
  ANTHROPIC_MODELS_DETAILED = [
4
4
  # Tool calling supported models
5
+ create_model_metadata(provider="Anthropic", name="claude-sonnet-4-5-20250929", icon="Anthropic", tool_calling=True),
6
+ create_model_metadata(provider="Anthropic", name="claude-opus-4-1-20250805", icon="Anthropic", tool_calling=True),
5
7
  create_model_metadata(provider="Anthropic", name="claude-opus-4-20250514", icon="Anthropic", tool_calling=True),
6
8
  create_model_metadata(provider="Anthropic", name="claude-sonnet-4-20250514", icon="Anthropic", tool_calling=True),
7
9
  create_model_metadata(provider="Anthropic", name="claude-3-7-sonnet-latest", icon="Anthropic", tool_calling=True),
@@ -1,16 +1,15 @@
1
- from langflow.base.models.aws_constants import AWS_REGIONS, AWS_MODEL_IDs
2
- from langflow.base.models.model import LCModelComponent
3
1
  from langflow.field_typing import LanguageModel
4
2
  from langflow.inputs.inputs import BoolInput, FloatInput, IntInput, MessageTextInput, SecretStrInput
5
3
  from langflow.io import DictInput, DropdownInput
6
4
 
5
+ from lfx.base.models.aws_constants import AWS_REGIONS, AWS_MODEL_IDs
6
+ from lfx.base.models.model import LCModelComponent
7
+
7
8
 
8
9
  class AmazonBedrockConverseComponent(LCModelComponent):
9
10
  display_name: str = "Amazon Bedrock Converse"
10
11
  description: str = (
11
- "Generate text using Amazon Bedrock LLMs with the modern Converse API "
12
- "for improved conversation handling. We recommend the Converse API for users "
13
- "who do not need to use custom models. It can be accessed using ChatBedrockConverse."
12
+ "Generate text using Amazon Bedrock LLMs with the modern Converse API for improved conversation handling."
14
13
  )
15
14
  icon = "Amazon"
16
15
  name = "AmazonBedrockConverseModel"
@@ -9,11 +9,13 @@ class AmazonBedrockComponent(LCModelComponent):
9
9
  display_name: str = "Amazon Bedrock"
10
10
  description: str = (
11
11
  "Generate text using Amazon Bedrock LLMs with the legacy ChatBedrock API. "
12
- "For better compatibility, newer features, and improved conversation handling, "
13
- "we recommend using Amazon Bedrock Converse instead."
12
+ "This component is deprecated. Please use Amazon Bedrock Converse instead "
13
+ "for better compatibility, newer features, and improved conversation handling."
14
14
  )
15
15
  icon = "Amazon"
16
16
  name = "AmazonBedrockModel"
17
+ legacy = True
18
+ replacement = "amazon.AmazonBedrockConverseComponent"
17
19
 
18
20
  inputs = [
19
21
  *LCModelComponent.get_base_inputs(),
@@ -10,6 +10,8 @@ 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
13
15
  from lfx.components.data.sql_executor import SQLComponent
14
16
  from lfx.components.data.url import URLComponent
15
17
  from lfx.components.data.web_search import WebSearchComponent
@@ -25,6 +27,8 @@ _dynamic_imports = {
25
27
  "URLComponent": "url",
26
28
  "WebSearchComponent": "web_search",
27
29
  "WebhookComponent": "webhook",
30
+ "NewsSearchComponent": "news_search",
31
+ "RSSReaderComponent": "rss",
28
32
  }
29
33
 
30
34
  __all__ = [
@@ -33,6 +37,8 @@ __all__ = [
33
37
  "DirectoryComponent",
34
38
  "FileComponent",
35
39
  "JSONToDataComponent",
40
+ "NewsSearchComponent",
41
+ "RSSReaderComponent",
36
42
  "SQLComponent",
37
43
  "URLComponent",
38
44
  "WebSearchComponent",
@@ -0,0 +1,166 @@
1
+ from urllib.parse import quote_plus
2
+
3
+ import pandas as pd
4
+ import requests
5
+ from bs4 import BeautifulSoup
6
+
7
+ from lfx.custom import Component
8
+ from lfx.io import IntInput, MessageTextInput, Output
9
+ from lfx.schema import DataFrame
10
+
11
+
12
+ class NewsSearchComponent(Component):
13
+ display_name = "News Search"
14
+ description = "Searches Google News via RSS. Returns clean article data."
15
+ documentation: str = "https://docs.langflow.org/components-data#news-search"
16
+ icon = "newspaper"
17
+ name = "NewsSearch"
18
+ legacy = True
19
+ replacement = "data.WebSearch"
20
+
21
+ inputs = [
22
+ MessageTextInput(
23
+ name="query",
24
+ display_name="Search Query",
25
+ info="Search keywords for news articles.",
26
+ tool_mode=True,
27
+ required=True,
28
+ ),
29
+ MessageTextInput(
30
+ name="hl",
31
+ display_name="Language (hl)",
32
+ info="Language code, e.g. en-US, fr, de. Default: en-US.",
33
+ tool_mode=False,
34
+ input_types=[],
35
+ required=False,
36
+ advanced=True,
37
+ ),
38
+ MessageTextInput(
39
+ name="gl",
40
+ display_name="Country (gl)",
41
+ info="Country code, e.g. US, FR, DE. Default: US.",
42
+ tool_mode=False,
43
+ input_types=[],
44
+ required=False,
45
+ advanced=True,
46
+ ),
47
+ MessageTextInput(
48
+ name="ceid",
49
+ display_name="Country:Language (ceid)",
50
+ info="e.g. US:en, FR:fr. Default: US:en.",
51
+ tool_mode=False,
52
+ value="US:en",
53
+ input_types=[],
54
+ required=False,
55
+ advanced=True,
56
+ ),
57
+ MessageTextInput(
58
+ name="topic",
59
+ display_name="Topic",
60
+ info="One of: WORLD, NATION, BUSINESS, TECHNOLOGY, ENTERTAINMENT, SCIENCE, SPORTS, HEALTH.",
61
+ tool_mode=False,
62
+ input_types=[],
63
+ required=False,
64
+ advanced=True,
65
+ ),
66
+ MessageTextInput(
67
+ name="location",
68
+ display_name="Location (Geo)",
69
+ info="City, state, or country for location-based news. Leave blank for keyword search.",
70
+ tool_mode=False,
71
+ input_types=[],
72
+ required=False,
73
+ advanced=True,
74
+ ),
75
+ IntInput(
76
+ name="timeout",
77
+ display_name="Timeout",
78
+ info="Timeout for the request in seconds.",
79
+ value=5,
80
+ required=False,
81
+ advanced=True,
82
+ ),
83
+ ]
84
+
85
+ outputs = [Output(name="articles", display_name="News Articles", method="search_news")]
86
+
87
+ def search_news(self) -> DataFrame:
88
+ # Defaults
89
+ hl = getattr(self, "hl", None) or "en-US"
90
+ gl = getattr(self, "gl", None) or "US"
91
+ ceid = getattr(self, "ceid", None) or f"{gl}:{hl.split('-')[0]}"
92
+ topic = getattr(self, "topic", None)
93
+ location = getattr(self, "location", None)
94
+ query = getattr(self, "query", None)
95
+
96
+ # Build base URL
97
+ if topic:
98
+ # Topic-based feed
99
+ base_url = f"https://news.google.com/rss/headlines/section/topic/{quote_plus(topic.upper())}"
100
+ params = f"?hl={hl}&gl={gl}&ceid={ceid}"
101
+ rss_url = base_url + params
102
+ elif location:
103
+ # Location-based feed
104
+ base_url = f"https://news.google.com/rss/headlines/section/geo/{quote_plus(location)}"
105
+ params = f"?hl={hl}&gl={gl}&ceid={ceid}"
106
+ rss_url = base_url + params
107
+ elif query:
108
+ # Keyword search feed
109
+ base_url = "https://news.google.com/rss/search?q="
110
+ query_parts = [query]
111
+ query_encoded = quote_plus(" ".join(query_parts))
112
+ params = f"&hl={hl}&gl={gl}&ceid={ceid}"
113
+ rss_url = f"{base_url}{query_encoded}{params}"
114
+ else:
115
+ self.status = "No search query, topic, or location provided."
116
+ self.log(self.status)
117
+ return DataFrame(
118
+ pd.DataFrame(
119
+ [
120
+ {
121
+ "title": "Error",
122
+ "link": "",
123
+ "published": "",
124
+ "summary": "No search query, topic, or location provided.",
125
+ }
126
+ ]
127
+ )
128
+ )
129
+
130
+ try:
131
+ response = requests.get(rss_url, timeout=self.timeout)
132
+ response.raise_for_status()
133
+ soup = BeautifulSoup(response.content, "xml")
134
+ items = soup.find_all("item")
135
+ except requests.RequestException as e:
136
+ self.status = f"Failed to fetch news: {e}"
137
+ self.log(self.status)
138
+ return DataFrame(pd.DataFrame([{"title": "Error", "link": "", "published": "", "summary": str(e)}]))
139
+ except (AttributeError, ValueError, TypeError) as e:
140
+ self.status = f"Unexpected error: {e!s}"
141
+ self.log(self.status)
142
+ return DataFrame(pd.DataFrame([{"title": "Error", "link": "", "published": "", "summary": str(e)}]))
143
+
144
+ if not items:
145
+ self.status = "No news articles found."
146
+ self.log(self.status)
147
+ return DataFrame(pd.DataFrame([{"title": "No articles found", "link": "", "published": "", "summary": ""}]))
148
+
149
+ articles = []
150
+ for item in items:
151
+ try:
152
+ title = self.clean_html(item.title.text if item.title else "")
153
+ link = item.link.text if item.link else ""
154
+ published = item.pubDate.text if item.pubDate else ""
155
+ summary = self.clean_html(item.description.text if item.description else "")
156
+ articles.append({"title": title, "link": link, "published": published, "summary": summary})
157
+ except (AttributeError, ValueError, TypeError) as e:
158
+ self.log(f"Error parsing article: {e!s}")
159
+ continue
160
+
161
+ df_articles = pd.DataFrame(articles)
162
+ self.log(f"Found {len(df_articles)} articles.")
163
+ return DataFrame(df_articles)
164
+
165
+ def clean_html(self, html_string: str) -> str:
166
+ return BeautifulSoup(html_string, "html.parser").get_text(separator=" ", strip=True)
@@ -0,0 +1,71 @@
1
+ import pandas as pd
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+
5
+ from lfx.custom import Component
6
+ from lfx.io import IntInput, MessageTextInput, Output
7
+ from lfx.log.logger import logger
8
+ from lfx.schema import DataFrame
9
+
10
+
11
+ class RSSReaderComponent(Component):
12
+ display_name = "RSS Reader"
13
+ description = "Fetches and parses an RSS feed."
14
+ documentation: str = "https://docs.langflow.org/components-data#rss-reader"
15
+ icon = "rss"
16
+ name = "RSSReaderSimple"
17
+ legacy = True
18
+ replacement = "data.WebSearch"
19
+
20
+ inputs = [
21
+ MessageTextInput(
22
+ name="rss_url",
23
+ display_name="RSS Feed URL",
24
+ info="URL of the RSS feed to parse.",
25
+ tool_mode=True,
26
+ required=True,
27
+ ),
28
+ IntInput(
29
+ name="timeout",
30
+ display_name="Timeout",
31
+ info="Timeout for the RSS feed request.",
32
+ value=5,
33
+ advanced=True,
34
+ ),
35
+ ]
36
+
37
+ outputs = [Output(name="articles", display_name="Articles", method="read_rss")]
38
+
39
+ def read_rss(self) -> DataFrame:
40
+ try:
41
+ response = requests.get(self.rss_url, timeout=self.timeout)
42
+ response.raise_for_status()
43
+ if not response.content.strip():
44
+ msg = "Empty response received"
45
+ raise ValueError(msg)
46
+ # Check if the response is valid XML
47
+ try:
48
+ BeautifulSoup(response.content, "xml")
49
+ except Exception as e:
50
+ msg = f"Invalid XML response: {e}"
51
+ raise ValueError(msg) from e
52
+ soup = BeautifulSoup(response.content, "xml")
53
+ items = soup.find_all("item")
54
+ except (requests.RequestException, ValueError) as e:
55
+ self.status = f"Failed to fetch RSS: {e}"
56
+ return DataFrame(pd.DataFrame([{"title": "Error", "link": "", "published": "", "summary": str(e)}]))
57
+
58
+ articles = [
59
+ {
60
+ "title": item.title.text if item.title else "",
61
+ "link": item.link.text if item.link else "",
62
+ "published": item.pubDate.text if item.pubDate else "",
63
+ "summary": item.description.text if item.description else "",
64
+ }
65
+ for item in items
66
+ ]
67
+
68
+ # Ensure the DataFrame has the correct columns even if empty
69
+ df_articles = pd.DataFrame(articles, columns=["title", "link", "published", "summary"])
70
+ logger.info(f"Fetched {len(df_articles)} articles.")
71
+ return DataFrame(df_articles)
@@ -6,11 +6,11 @@ import httpx
6
6
  from langchain_ollama import ChatOllama
7
7
 
8
8
  from lfx.base.models.model import LCModelComponent
9
- from lfx.base.models.ollama_constants import URL_LIST
10
9
  from lfx.field_typing import LanguageModel
11
10
  from lfx.field_typing.range_spec import RangeSpec
12
11
  from lfx.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SliderInput
13
12
  from lfx.log.logger import logger
13
+ from lfx.utils.util import transform_localhost_url
14
14
 
15
15
  HTTP_STATUS_OK = 200
16
16
 
@@ -156,9 +156,10 @@ class ChatOllamaComponent(LCModelComponent):
156
156
  mirostat_eta = self.mirostat_eta
157
157
  mirostat_tau = self.mirostat_tau
158
158
 
159
+ transformed_base_url = transform_localhost_url(self.base_url)
159
160
  # Mapping system settings to their corresponding values
160
161
  llm_params = {
161
- "base_url": self.base_url,
162
+ "base_url": transformed_base_url,
162
163
  "model": self.model_name,
163
164
  "mirostat": mirostat_value,
164
165
  "format": self.format,
@@ -199,6 +200,7 @@ class ChatOllamaComponent(LCModelComponent):
199
200
  async def is_valid_ollama_url(self, url: str) -> bool:
200
201
  try:
201
202
  async with httpx.AsyncClient() as client:
203
+ url = transform_localhost_url(url)
202
204
  return (await client.get(urljoin(url, "api/tags"))).status_code == HTTP_STATUS_OK
203
205
  except httpx.RequestError:
204
206
  return False
@@ -222,38 +224,15 @@ class ChatOllamaComponent(LCModelComponent):
222
224
  build_config["mirostat_eta"]["value"] = 0.1
223
225
  build_config["mirostat_tau"]["value"] = 5
224
226
 
225
- if field_name in {"base_url", "model_name"}:
226
- if build_config["base_url"].get("load_from_db", False):
227
- base_url_value = await self.get_variables(build_config["base_url"].get("value", ""), "base_url")
228
- else:
229
- base_url_value = build_config["base_url"].get("value", "")
230
-
231
- if not await self.is_valid_ollama_url(base_url_value):
232
- # Check if any URL in the list is valid
233
- valid_url = ""
234
- check_urls = URL_LIST
235
- if self.base_url:
236
- check_urls = [self.base_url, *URL_LIST]
237
- for url in check_urls:
238
- if await self.is_valid_ollama_url(url):
239
- valid_url = url
240
- break
241
- if valid_url != "":
242
- build_config["base_url"]["value"] = valid_url
243
- else:
244
- msg = "No valid Ollama URL found."
245
- raise ValueError(msg)
227
+ if field_name in {"base_url", "model_name"} and not await self.is_valid_ollama_url(self.base_url):
228
+ msg = "Ollama is not running on the provided base URL. Please start Ollama and try again."
229
+ raise ValueError(msg)
246
230
  if field_name in {"model_name", "base_url", "tool_model_enabled"}:
247
231
  if await self.is_valid_ollama_url(self.base_url):
248
232
  tool_model_enabled = build_config["tool_model_enabled"].get("value", False) or self.tool_model_enabled
249
233
  build_config["model_name"]["options"] = await self.get_models(
250
234
  self.base_url, tool_model_enabled=tool_model_enabled
251
235
  )
252
- elif await self.is_valid_ollama_url(build_config["base_url"].get("value", "")):
253
- tool_model_enabled = build_config["tool_model_enabled"].get("value", False) or self.tool_model_enabled
254
- build_config["model_name"]["options"] = await self.get_models(
255
- build_config["base_url"].get("value", ""), tool_model_enabled=tool_model_enabled
256
- )
257
236
  else:
258
237
  build_config["model_name"]["options"] = []
259
238
  if field_name == "keep_alive_flag":
@@ -287,6 +266,7 @@ class ChatOllamaComponent(LCModelComponent):
287
266
  try:
288
267
  # Normalize the base URL to avoid the repeated "/" at the end
289
268
  base_url = base_url_value.rstrip("/") + "/"
269
+ base_url = transform_localhost_url(base_url)
290
270
 
291
271
  # Ollama REST API to return models
292
272
  tags_url = urljoin(base_url, "api/tags")
@@ -5,9 +5,10 @@ import httpx
5
5
  from langchain_ollama import OllamaEmbeddings
6
6
 
7
7
  from lfx.base.models.model import LCModelComponent
8
- from lfx.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS, URL_LIST
8
+ from lfx.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS
9
9
  from lfx.field_typing import Embeddings
10
10
  from lfx.io import DropdownInput, MessageTextInput, Output
11
+ from lfx.utils.util import transform_localhost_url
11
12
 
12
13
  HTTP_STATUS_OK = 200
13
14
 
@@ -43,8 +44,9 @@ class OllamaEmbeddingsComponent(LCModelComponent):
43
44
  ]
44
45
 
45
46
  def build_embeddings(self) -> Embeddings:
47
+ transformed_base_url = transform_localhost_url(self.base_url)
46
48
  try:
47
- output = OllamaEmbeddings(model=self.model_name, base_url=self.base_url)
49
+ output = OllamaEmbeddings(model=self.model_name, base_url=transformed_base_url)
48
50
  except Exception as e:
49
51
  msg = (
50
52
  "Unable to connect to the Ollama API. ",
@@ -53,20 +55,13 @@ class OllamaEmbeddingsComponent(LCModelComponent):
53
55
  raise ValueError(msg) from e
54
56
  return output
55
57
 
56
- async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):
57
- if field_name in {"base_url", "model_name"} and not await self.is_valid_ollama_url(field_value):
58
- # Check if any URL in the list is valid
59
- valid_url = ""
60
- for url in URL_LIST:
61
- if await self.is_valid_ollama_url(url):
62
- valid_url = url
63
- break
64
- build_config["base_url"]["value"] = valid_url
58
+ async def update_build_config(self, build_config: dict, _field_value: Any, field_name: str | None = None):
59
+ if field_name in {"base_url", "model_name"} and not await self.is_valid_ollama_url(self.base_url):
60
+ msg = "Ollama is not running on the provided base URL. Please start Ollama and try again."
61
+ raise ValueError(msg)
65
62
  if field_name in {"model_name", "base_url", "tool_model_enabled"}:
66
63
  if await self.is_valid_ollama_url(self.base_url):
67
64
  build_config["model_name"]["options"] = await self.get_model(self.base_url)
68
- elif await self.is_valid_ollama_url(build_config["base_url"].get("value", "")):
69
- build_config["model_name"]["options"] = await self.get_model(build_config["base_url"].get("value", ""))
70
65
  else:
71
66
  build_config["model_name"]["options"] = []
72
67
 
@@ -76,6 +71,7 @@ class OllamaEmbeddingsComponent(LCModelComponent):
76
71
  """Get the model names from Ollama."""
77
72
  model_ids = []
78
73
  try:
74
+ base_url_value = transform_localhost_url(base_url_value)
79
75
  url = urljoin(base_url_value, "/api/tags")
80
76
  async with httpx.AsyncClient() as client:
81
77
  response = await client.get(url)
@@ -101,6 +97,7 @@ class OllamaEmbeddingsComponent(LCModelComponent):
101
97
  async def is_valid_ollama_url(self, url: str) -> bool:
102
98
  try:
103
99
  async with httpx.AsyncClient() as client:
100
+ url = transform_localhost_url(url)
104
101
  return (await client.get(f"{url}/api/tags")).status_code == HTTP_STATUS_OK
105
102
  except httpx.RequestError:
106
103
  return False
@@ -1325,7 +1325,11 @@ class Component(CustomComponent):
1325
1325
  - tags: List of tags associated with the tool
1326
1326
  """
1327
1327
  # Get tools from subclass implementation
1328
- tools = await self._get_tools()
1328
+ # Handle both sync and async _get_tools methods
1329
+ if asyncio.iscoroutinefunction(self._get_tools):
1330
+ tools = await self._get_tools()
1331
+ else:
1332
+ tools = self._get_tools()
1329
1333
 
1330
1334
  if hasattr(self, TOOLS_METADATA_INPUT_NAME):
1331
1335
  tools = self._filter_tools_by_status(tools=tools, metadata=self.tools_metadata)
lfx/schema/json_schema.py CHANGED
@@ -2,13 +2,43 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
- from pydantic import BaseModel, Field, create_model
5
+ from pydantic import AliasChoices, BaseModel, Field, create_model
6
6
 
7
7
  from lfx.log.logger import logger
8
8
 
9
9
  NULLABLE_TYPE_LENGTH = 2 # Number of types in a nullable union (the type itself + null)
10
10
 
11
11
 
12
+ def _snake_to_camel(name: str) -> str:
13
+ """Convert snake_case to camelCase, preserving leading/trailing underscores."""
14
+ if not name:
15
+ return name
16
+
17
+ # Handle leading underscores
18
+ leading = ""
19
+ start_idx = 0
20
+ while start_idx < len(name) and name[start_idx] == "_":
21
+ leading += "_"
22
+ start_idx += 1
23
+
24
+ # Handle trailing underscores
25
+ trailing = ""
26
+ end_idx = len(name)
27
+ while end_idx > start_idx and name[end_idx - 1] == "_":
28
+ trailing += "_"
29
+ end_idx -= 1
30
+
31
+ # Convert the middle part
32
+ middle = name[start_idx:end_idx]
33
+ if not middle:
34
+ return name # All underscores
35
+
36
+ components = middle.split("_")
37
+ camel = components[0] + "".join(word.capitalize() for word in components[1:])
38
+
39
+ return leading + camel + trailing
40
+
41
+
12
42
  def create_input_schema_from_json_schema(schema: dict[str, Any]) -> type[BaseModel]:
13
43
  """Dynamically build a Pydantic model from a JSON schema (with $defs).
14
44
 
@@ -118,7 +148,14 @@ def create_input_schema_from_json_schema(schema: dict[str, Any]) -> type[BaseMod
118
148
  else:
119
149
  default = ... # required by Pydantic
120
150
 
121
- fields[prop_name] = (py_type, Field(default, description=prop_schema.get("description")))
151
+ # Add alias for camelCase if field name is snake_case
152
+ field_kwargs = {"description": prop_schema.get("description")}
153
+ if "_" in prop_name:
154
+ camel_case_name = _snake_to_camel(prop_name)
155
+ if camel_case_name != prop_name: # Only add alias if it's different
156
+ field_kwargs["validation_alias"] = AliasChoices(prop_name, camel_case_name)
157
+
158
+ fields[prop_name] = (py_type, Field(default, **field_kwargs))
122
159
 
123
160
  model_cls = create_model(name, **fields)
124
161
  model_cache[name] = model_cls
@@ -136,6 +173,14 @@ def create_input_schema_from_json_schema(schema: dict[str, Any]) -> type[BaseMod
136
173
  default = fdef.get("default", None)
137
174
  else:
138
175
  default = ...
139
- top_fields[fname] = (py_type, Field(default, description=fdef.get("description")))
176
+
177
+ # Add alias for camelCase if field name is snake_case
178
+ field_kwargs = {"description": fdef.get("description")}
179
+ if "_" in fname:
180
+ camel_case_name = _snake_to_camel(fname)
181
+ if camel_case_name != fname: # Only add alias if it's different
182
+ field_kwargs["validation_alias"] = AliasChoices(fname, camel_case_name)
183
+
184
+ top_fields[fname] = (py_type, Field(default, **field_kwargs))
140
185
 
141
186
  return create_model("InputSchema", **top_fields)
@@ -381,6 +381,8 @@ class Settings(BaseSettings):
381
381
 
382
382
  if isinstance(value, str):
383
383
  value = Path(value)
384
+ # Resolve to absolute path to handle relative paths correctly
385
+ value = value.resolve()
384
386
  if not value.exists():
385
387
  value.mkdir(parents=True, exist_ok=True)
386
388
 
lfx/utils/util.py CHANGED
@@ -115,7 +115,7 @@ def get_container_host() -> str | None:
115
115
  return None
116
116
 
117
117
 
118
- def transform_localhost_url(url: str) -> str:
118
+ def transform_localhost_url(url: str | None) -> str | None:
119
119
  """Transform localhost URLs to container-accessible hosts when running in a container.
120
120
 
121
121
  Automatically detects if running inside a container and finds the appropriate host
@@ -125,17 +125,26 @@ def transform_localhost_url(url: str) -> str:
125
125
  - Gateway IP from routing table (fallback)
126
126
 
127
127
  Args:
128
- url: The original URL
128
+ url: The original URL, can be None or empty string
129
129
 
130
130
  Returns:
131
131
  Transformed URL with container-accessible host if applicable, otherwise the original URL.
132
+ Returns None if url is None, empty string if url is empty string.
132
133
 
133
134
  Example:
134
135
  >>> transform_localhost_url("http://localhost:5001")
135
136
  # Returns "http://host.docker.internal:5001" if running in Docker and hostname resolves
136
137
  # Returns "http://172.17.0.1:5001" if running in Docker on Linux (gateway IP fallback)
137
138
  # Returns "http://localhost:5001" if not in a container
139
+ >>> transform_localhost_url(None)
140
+ # Returns None
141
+ >>> transform_localhost_url("")
142
+ # Returns ""
138
143
  """
144
+ # Guard against None and empty string to prevent TypeError
145
+ if not url:
146
+ return url
147
+
139
148
  container_host = get_container_host()
140
149
 
141
150
  if not container_host:
@@ -1,40 +1,40 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lfx-nightly
3
- Version: 0.1.12.dev29
3
+ Version: 0.1.12.dev31
4
4
  Summary: Langflow Executor - A lightweight CLI tool for executing and serving Langflow AI flows
5
5
  Author-email: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
6
6
  Requires-Python: <3.14,>=3.10
7
- Requires-Dist: aiofile>=3.8.0
8
- Requires-Dist: aiofiles>=24.1.0
9
- Requires-Dist: asyncer>=0.0.8
10
- Requires-Dist: cachetools>=5.5.2
11
- Requires-Dist: chardet>=5.2.0
12
- Requires-Dist: defusedxml>=0.7.1
13
- Requires-Dist: docstring-parser>=0.16
14
- Requires-Dist: emoji>=2.14.1
15
- Requires-Dist: fastapi>=0.115.13
16
- Requires-Dist: httpx[http2]>=0.24.0
17
- Requires-Dist: json-repair>=0.30.3
18
- Requires-Dist: langchain-core>=0.3.66
7
+ Requires-Dist: aiofile<4.0.0,>=3.8.0
8
+ Requires-Dist: aiofiles<25.0.0,>=24.1.0
9
+ Requires-Dist: asyncer<1.0.0,>=0.0.8
10
+ Requires-Dist: cachetools<6.0.0,>=5.5.2
11
+ Requires-Dist: chardet<6.0.0,>=5.2.0
12
+ Requires-Dist: defusedxml<1.0.0,>=0.7.1
13
+ Requires-Dist: docstring-parser<1.0.0,>=0.16
14
+ Requires-Dist: emoji<3.0.0,>=2.14.1
15
+ Requires-Dist: fastapi<1.0.0,>=0.115.13
16
+ Requires-Dist: httpx[http2]<1.0.0,>=0.24.0
17
+ Requires-Dist: json-repair<1.0.0,>=0.30.3
18
+ Requires-Dist: langchain-core<1.0.0,>=0.3.66
19
19
  Requires-Dist: langchain~=0.3.23
20
- Requires-Dist: loguru>=0.7.3
21
- Requires-Dist: nanoid>=2.0.0
22
- Requires-Dist: networkx>=3.4.2
23
- Requires-Dist: orjson>=3.10.15
24
- Requires-Dist: pandas>=2.0.0
25
- Requires-Dist: passlib>=1.7.4
26
- Requires-Dist: pillow>=10.0.0
27
- Requires-Dist: platformdirs>=4.3.8
28
- Requires-Dist: pydantic-settings>=2.10.1
29
- Requires-Dist: pydantic>=2.0.0
30
- Requires-Dist: python-dotenv>=1.0.0
31
- Requires-Dist: rich>=13.0.0
32
- Requires-Dist: structlog
33
- Requires-Dist: tomli>=2.2.1
34
- Requires-Dist: typer>=0.16.0
35
- Requires-Dist: typing-extensions>=4.14.0
36
- Requires-Dist: uvicorn>=0.34.3
37
- Requires-Dist: validators>=0.34.0
20
+ Requires-Dist: loguru<1.0.0,>=0.7.3
21
+ Requires-Dist: nanoid<3.0.0,>=2.0.0
22
+ Requires-Dist: networkx<4.0.0,>=3.4.2
23
+ Requires-Dist: orjson<4.0.0,>=3.10.15
24
+ Requires-Dist: pandas<3.0.0,>=2.0.0
25
+ Requires-Dist: passlib<2.0.0,>=1.7.4
26
+ Requires-Dist: pillow<13.0.0,>=10.0.0
27
+ Requires-Dist: platformdirs<5.0.0,>=4.3.8
28
+ Requires-Dist: pydantic-settings<3.0.0,>=2.10.1
29
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
30
+ Requires-Dist: python-dotenv<2.0.0,>=1.0.0
31
+ Requires-Dist: rich<14.0.0,>=13.0.0
32
+ Requires-Dist: structlog<26.0.0,>=25.4.0
33
+ Requires-Dist: tomli<3.0.0,>=2.2.1
34
+ Requires-Dist: typer<1.0.0,>=0.16.0
35
+ Requires-Dist: typing-extensions<5.0.0,>=4.14.0
36
+ Requires-Dist: uvicorn<1.0.0,>=0.34.3
37
+ Requires-Dist: validators<1.0.0,>=0.34.0
38
38
  Description-Content-Type: text/markdown
39
39
 
40
40
  # lfx - Langflow Executor
@@ -12,7 +12,7 @@ lfx/base/agents/callback.py,sha256=mjlT9ukBMVrfjYrHsJowqpY4g9hVGBVBIYhncLWr3tQ,3
12
12
  lfx/base/agents/context.py,sha256=u0wboX1aRR22Ia8gY14WF12RjhE0Rxv9hPBiixT9DtQ,3916
13
13
  lfx/base/agents/default_prompts.py,sha256=tUjfczwt4D5R1KozNOl1uSL2V2rSCZeUMS-cfV4Gwn0,955
14
14
  lfx/base/agents/errors.py,sha256=4QY1AqSWZaOjq-iQRYH_aeCfH_hWECLQkiwybNXz66U,531
15
- lfx/base/agents/events.py,sha256=1SLai0H5pvorojgBL7l_xbtJ7gpkcddd5IWyUw25UTg,14035
15
+ lfx/base/agents/events.py,sha256=oMIrTi6-5AhL_NBY3XSH24Yt1adjDBXgSPeIaedSEIQ,14126
16
16
  lfx/base/agents/utils.py,sha256=OcmtZx4BTFTyq2A3rta3WoJn98UzEYdEXoRLs8-mTVo,6511
17
17
  lfx/base/agents/crewai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  lfx/base/agents/crewai/crew.py,sha256=TN1JyLXMpJc2yPH3tokhFmxKKYoJ4lMvmG19DmpKfeY,7953
@@ -52,13 +52,13 @@ lfx/base/langwatch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
52
52
  lfx/base/langwatch/utils.py,sha256=N7rH3sRwgmNQzG0pKjj4wr_ans_drwtvkx4BQt-B0WA,457
53
53
  lfx/base/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  lfx/base/mcp/constants.py,sha256=-1XnJxejlqm9zs1R91qGtOeX-_F1ZpdHVzCIqUCvhgE,62
55
- lfx/base/mcp/util.py,sha256=WE3zEwkLx-tbNOnUr4kJdFTD4_lopCrV-vn-C86z9e0,59497
55
+ lfx/base/mcp/util.py,sha256=RbsZ8HPuN04aTNaGUgojHCUaMVWos_rhOP554wv4Fg0,64603
56
56
  lfx/base/memory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  lfx/base/memory/memory.py,sha256=kZ-aZoHvRW4PJAgY1LUt5UBj7YXbos_aVBPGjC1EFCY,1835
58
58
  lfx/base/memory/model.py,sha256=2oDORZV_l-DHLx9j9--wYprQUIYKOb8aTJpXmR1qOLw,1330
59
59
  lfx/base/models/__init__.py,sha256=-wlh_C2fhUXr4U_ZQIUzNx1C1z9iaIE77b1EAYX8gsk,68
60
60
  lfx/base/models/aiml_constants.py,sha256=7r_wG6MklHqPJgerDgXXrfL6cW4lJavXjyDaVpnSp1A,1796
61
- lfx/base/models/anthropic_constants.py,sha256=vVxmo_-jOIBgcI3WyjMsj3iq-QN5Ide7o5V2IgnxRQc,2511
61
+ lfx/base/models/anthropic_constants.py,sha256=51_fghjdfBRyLSNj3qa-ogyWmeP518HrdsTnV-C-j-Y,2751
62
62
  lfx/base/models/aws_constants.py,sha256=-Fa7T3wJqBaZhs80ATRgZP6yZ0Nsd1YYdZv9SfqT-Hs,6327
63
63
  lfx/base/models/chat_result.py,sha256=-MypS6_GKXOqWevtk0xwtrsEO4mIgpPAt7-EML5n0vA,2756
64
64
  lfx/base/models/google_generative_ai_constants.py,sha256=EuFd77ZrrSr6YtSKtmEaq0Nfa4y45AbDe_cz_18nReE,2564
@@ -116,9 +116,9 @@ lfx/components/aiml/__init__.py,sha256=DNKB-HMFGFYmsdkON-s8557ttgBXVXADmS-BcuSQi
116
116
  lfx/components/aiml/aiml.py,sha256=23Ineg1ajlCoqXgWgp50I20OnQbaleRNsw1c6IzPu3A,3877
117
117
  lfx/components/aiml/aiml_embeddings.py,sha256=2uNwORuj55mxn2SfLbh7oAIfjuXwHbsnOqRjfMtQRqc,1095
118
118
  lfx/components/amazon/__init__.py,sha256=QRsw-7ytuqZWhNPmlslCQEcpNsAw6dX5FV_8ihUdlPQ,1338
119
- lfx/components/amazon/amazon_bedrock_converse.py,sha256=HjBOpRd6NkMPlRLFqAmIcqrWQgNROkMDuQF6C3QrK1k,8345
119
+ lfx/components/amazon/amazon_bedrock_converse.py,sha256=WPo_5Wsj-pyltXcBOp6xPmtwVzRcJ5JOz5W013Ev9EE,8190
120
120
  lfx/components/amazon/amazon_bedrock_embedding.py,sha256=cNA5_3nwkegWS3BY75IfjOwpo-g8dpg-EgtQJSgVXfg,4261
121
- lfx/components/amazon/amazon_bedrock_model.py,sha256=CCRf6CKJkDJ0zL-9QRXHe9X8_cfZcFi8VvFOIzaVdLg,5020
121
+ lfx/components/amazon/amazon_bedrock_model.py,sha256=ZEccsdrn2bXPSnR8bx5b1H89ZI-tCGitgysKy1oJUfE,5117
122
122
  lfx/components/amazon/s3_bucket_uploader.py,sha256=bkui2vw8ibgg4dSUYZ7yAEbVOjgmB8oOYqH-3ZvTlBQ,7688
123
123
  lfx/components/anthropic/__init__.py,sha256=SGllKMRoX3KQkDpdzsLUYqbfTbhFu9qjpM6bQHL2syQ,965
124
124
  lfx/components/anthropic/anthropic.py,sha256=fIlMWma6rsfQ499vkkLSWFZqwKn5ifvhFQnC_quHkEw,7289
@@ -201,13 +201,15 @@ lfx/components/crewai/sequential_task.py,sha256=sfF7j8xcFri5JCA_AEePYRCq9ll2wnZv
201
201
  lfx/components/crewai/sequential_task_agent.py,sha256=ClSD74LDE1dOIL9i0Dj0OUi3jyGAGUu3NpxZ_U3OjxU,4738
202
202
  lfx/components/custom_component/__init__.py,sha256=3GXaQiQL8mTRYU-tpVPxtRKFkAd2zBQWAsbc-pQKJMA,928
203
203
  lfx/components/custom_component/custom_component.py,sha256=1QpopvpXkzQ3Vg7TzFI2TszRBR5fFDcQNJYra_q_hrA,919
204
- lfx/components/data/__init__.py,sha256=g2vQndQWDTcaJrwi8EaDgW4UJoDtItwhCmDBb2cxEDw,1925
204
+ lfx/components/data/__init__.py,sha256=6e9qEWQ639owIAtZLF01mW2hVMDbg9-Rp6IWpgypzvs,2180
205
205
  lfx/components/data/api_request.py,sha256=gKVtJhEzh1PdawZqQtxBwi0vU3kQ_f25jCd2tj-EeU0,20211
206
206
  lfx/components/data/csv_to_data.py,sha256=NsNe8rZdkqscnWPynbbd3-svrRj3EEWNaJszjW9b1_k,3345
207
207
  lfx/components/data/directory.py,sha256=iqINxxy5w60l753zraB-EDpYny8FR6vaa-AgVkdYsLk,3936
208
208
  lfx/components/data/file.py,sha256=pPlmaEHElAeUfSs60G0cl-goynWg9I3uo7IPeN63UEc,26073
209
209
  lfx/components/data/json_to_data.py,sha256=p6MPyUgDvY9aKTL2_2cUGmkeK7baS-G9rkCvzHAnhw8,3571
210
210
  lfx/components/data/mock_data.py,sha256=0h3OezKb8P5x6XWGzZJ8JmqmB9eK-bhpfLm0GO21MNY,16039
211
+ lfx/components/data/news_search.py,sha256=rN721OzeLaKessjCsTj_GD4MAzpNJRhOrhFpX00rAqU,6234
212
+ lfx/components/data/rss.py,sha256=J98zRNGbAh-CIdGydYBaI1JhKDoMPDhLily01LZPhGI,2540
211
213
  lfx/components/data/save_file.py,sha256=1ZPSQROFRjt1SivSuFr0jtnAMR1Bqf9DJLokYzbGyY8,25190
212
214
  lfx/components/data/sql_executor.py,sha256=sN1lWM65O_pCfZxNAzgjtZmcTPGBLqMud2_7nFv-kpM,3726
213
215
  lfx/components/data/url.py,sha256=zbfTeTBukw3F_mRBMIJrQYF94psEIBuS8dFjcQku5SE,11001
@@ -406,8 +408,8 @@ lfx/components/nvidia/system_assist.py,sha256=G8cgsLQxRBBnUt49_Uzxt7cdTNplVAzUlD
406
408
  lfx/components/olivya/__init__.py,sha256=ilZR88huL3vnQHO27g4jsUkyIYSgN7RPOq8Corbi6xA,67
407
409
  lfx/components/olivya/olivya.py,sha256=PDmsn8dBdSwAZUM2QGTyTwxGWsINCKaYR4yTjE-4lIQ,4192
408
410
  lfx/components/ollama/__init__.py,sha256=fau8QcWs_eHO2MmtQ4coiKj9CzFA9X4hqFf541ekgXk,1068
409
- lfx/components/ollama/ollama.py,sha256=VN47XaOI-hWEnpEZrCbkT3hkT5qH3RvswuY7OVHGRfc,13999
410
- lfx/components/ollama/ollama_embeddings.py,sha256=xBghc1VIAzSZOq7b3-ZV6J6xPgpBzncQDdBm0hpD68c,4098
411
+ lfx/components/ollama/ollama.py,sha256=eWSRR7lypH82Eb6px2JhPlJCfzDz33MCqgFoOXFbofE,13012
412
+ lfx/components/ollama/ollama_embeddings.py,sha256=nvg-JQvue6j7tcrbbPeq1U_-LUj1MKawWbXxnnvJlWM,3976
411
413
  lfx/components/openai/__init__.py,sha256=G4Fgw4pmmDohdIOmzaeSCGijzKjyqFXNJPLwlcUDZ3w,1113
412
414
  lfx/components/openai/openai.py,sha256=imWO1tTJ0tTLqax1v5bNBPCRINTj2f2wN8j5G-a07GI,4505
413
415
  lfx/components/openai/openai_chat_model.py,sha256=hZO79PqSI8ppnrEQFL_NL1issX6E5iCUPCycjX_d0Fs,6969
@@ -561,7 +563,7 @@ lfx/custom/code_parser/__init__.py,sha256=qIwZQdEp1z7ldn0z-GY44wmwRaywN3L6VPoPt6
561
563
  lfx/custom/code_parser/code_parser.py,sha256=QAqsp4QF607319dClK60BsaiwZLV55n0xeGR-DthSoE,14280
562
564
  lfx/custom/custom_component/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
563
565
  lfx/custom/custom_component/base_component.py,sha256=Pxi-qCocrGIwcG0x5fu-7ty1Py71bl_KG9Fku5SeO_M,4053
564
- lfx/custom/custom_component/component.py,sha256=dhsRmVp5i8Q6NUZ2Ac5FReP68OhW8mbYn7MzQlJdxWI,74349
566
+ lfx/custom/custom_component/component.py,sha256=MeYIPYab-68yarGX_IK0Fac8bGGPa1jlW5pv5P6IJ2E,74518
565
567
  lfx/custom/custom_component/component_with_cache.py,sha256=por6CiPL3EHdLp_DvfI7qz1n4tc1KkqMOJNbsxoqVaI,313
566
568
  lfx/custom/custom_component/custom_component.py,sha256=u330P-UbRXKeO-ughl2rCyvEbgdq-igTNFYEoKQLJzI,22306
567
569
  lfx/custom/directory_reader/__init__.py,sha256=eFjlhKjpt2Kha_sJ2EqWofLRbpvfOTjvDSCpdpaTqWk,77
@@ -641,7 +643,7 @@ lfx/schema/dotdict.py,sha256=d6R5jv8V_pxaQUX3QP41ZzTz2wZpnZ0OFsylFf3xL-Q,2756
641
643
  lfx/schema/encoders.py,sha256=7vlWHZnZuDv1UVuP9X7Xn8srP1HZqLygOmkps3EJyY0,332
642
644
  lfx/schema/graph.py,sha256=o7qXhHZT4lEwjJZtlg4k9SNPgmMVZsZsclBbe8v_y6Y,1313
643
645
  lfx/schema/image.py,sha256=WdaOT3bjkJaG28RpgmurtfcnOG7Hr2phZ27YXH25uHA,5970
644
- lfx/schema/json_schema.py,sha256=keN4trNimJzqrLnPvBhq90rc5FTwQH44lLpmLv4BzhA,5316
646
+ lfx/schema/json_schema.py,sha256=UzMRSSAiLewJpf7B0XY4jPnPt0iskf61QUBxPdyiYys,6871
645
647
  lfx/schema/log.py,sha256=xbwSvJKmT1U8kxqIcV8BYgJxtu8Q6ntJKF8cIeksPEo,1943
646
648
  lfx/schema/message.py,sha256=Fg83OjS67qJYCevqt_5JcBu9xXkh1WY6S6DBLAvT62g,18076
647
649
  lfx/schema/openai_responses_schemas.py,sha256=drMCAlliefHfGRojBTMepPwk4DyEGh67naWvMPD10Sw,2596
@@ -674,7 +676,7 @@ lfx/services/mcp_composer/factory.py,sha256=f8Bj0ZR9A_o1c3Kw5JKyR6SbtbCEPNWOy8b0
674
676
  lfx/services/mcp_composer/service.py,sha256=Binv29dXSRscUPOa40714w_NYmebZB3gwBp68KnaSFc,25765
675
677
  lfx/services/settings/__init__.py,sha256=UISBvOQIqoA3a8opwJrTQp4PSTqpReY6GQ_7O6WuqJQ,65
676
678
  lfx/services/settings/auth.py,sha256=_18KZipq0udCJPq-4xCD_juhqSwAEvoCqxOTSYsNv5w,5720
677
- lfx/services/settings/base.py,sha256=z45EHJpkuvN9DwBuOOaShdxpeX_D0moyMAlG2E640l0,26119
679
+ lfx/services/settings/base.py,sha256=8_eiUe90Yi_YKw-abRurGXgbmVmOAbSXEbUDTabXbas,26221
678
680
  lfx/services/settings/constants.py,sha256=ZBJolZ4kx0ZoYp2BDyHkgDFgaXEQAH-ZcLqgunv_MqQ,908
679
681
  lfx/services/settings/factory.py,sha256=NezZ6TE_xP955B9l9pI6ONNyoylrHPfUZN8arvLVRXg,615
680
682
  lfx/services/settings/feature_flags.py,sha256=HGuDGgfOBIDtuEiEVTgoWHxKqX2vuVBRgsqdX_4D9kg,205
@@ -714,10 +716,10 @@ lfx/utils/image.py,sha256=wMWBEI1gW3cFlQcio3mWgfHBaOw1uoAnqNmEacE_8xo,2133
714
716
  lfx/utils/lazy_load.py,sha256=UDtXi8N7NT9r-FRGxsLUfDtGU_X8yqt-RQqgpc9TqAw,394
715
717
  lfx/utils/request_utils.py,sha256=A6vmwpr7f3ZUxHg6Sz2-BdUUsyAwg84-7N_DNoPC8_Q,518
716
718
  lfx/utils/schemas.py,sha256=NbOtVQBrn4d0BAu-0H_eCTZI2CXkKZlRY37XCSmuJwc,3865
717
- lfx/utils/util.py,sha256=ZW7sYJBtIhM6o_GHgHoI-mXB8l2vTNgRgdUfsogFp0g,20419
719
+ lfx/utils/util.py,sha256=DSMcKoWDxrytAe0-36jwqZ9ac_rT8NP09E8JPDHtal0,20761
718
720
  lfx/utils/util_strings.py,sha256=nU_IcdphNaj6bAPbjeL-c1cInQPfTBit8mp5Y57lwQk,1686
719
721
  lfx/utils/version.py,sha256=cHpbO0OJD2JQAvVaTH_6ibYeFbHJV0QDHs_YXXZ-bT8,671
720
- lfx_nightly-0.1.12.dev29.dist-info/METADATA,sha256=jNoQDRrzcTrsTj7qHpYsJD2msbxfN7UaD3ZY0Xt1u3o,8068
721
- lfx_nightly-0.1.12.dev29.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
722
- lfx_nightly-0.1.12.dev29.dist-info/entry_points.txt,sha256=1724p3RHDQRT2CKx_QRzEIa7sFuSVO0Ux70YfXfoMT4,42
723
- lfx_nightly-0.1.12.dev29.dist-info/RECORD,,
722
+ lfx_nightly-0.1.12.dev31.dist-info/METADATA,sha256=8luCG-uZgW6j456bJIhuC0ObvFOsU3dL9OwJ9uetLhw,8290
723
+ lfx_nightly-0.1.12.dev31.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
724
+ lfx_nightly-0.1.12.dev31.dist-info/entry_points.txt,sha256=1724p3RHDQRT2CKx_QRzEIa7sFuSVO0Ux70YfXfoMT4,42
725
+ lfx_nightly-0.1.12.dev31.dist-info/RECORD,,