lfx-nightly 0.1.12.dev28__py3-none-any.whl → 0.1.12.dev30__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 +15 -14
- lfx/base/mcp/util.py +118 -12
- lfx/base/models/anthropic_constants.py +2 -0
- lfx/components/amazon/amazon_bedrock_converse.py +4 -5
- lfx/components/amazon/amazon_bedrock_model.py +4 -2
- lfx/components/data/__init__.py +6 -0
- lfx/components/data/news_search.py +166 -0
- lfx/components/data/rss.py +71 -0
- lfx/components/ollama/ollama.py +8 -28
- lfx/components/ollama/ollama_embeddings.py +10 -13
- lfx/custom/custom_component/component.py +5 -1
- lfx/schema/json_schema.py +48 -3
- lfx/utils/util.py +11 -2
- {lfx_nightly-0.1.12.dev28.dist-info → lfx_nightly-0.1.12.dev30.dist-info}/METADATA +31 -31
- {lfx_nightly-0.1.12.dev28.dist-info → lfx_nightly-0.1.12.dev30.dist-info}/RECORD +17 -15
- {lfx_nightly-0.1.12.dev28.dist-info → lfx_nightly-0.1.12.dev30.dist-info}/WHEEL +0 -0
- {lfx_nightly-0.1.12.dev28.dist-info → lfx_nightly-0.1.12.dev30.dist-info}/entry_points.txt +0 -0
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
13
|
-
"
|
|
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(),
|
lfx/components/data/__init__.py
CHANGED
|
@@ -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)
|
lfx/components/ollama/ollama.py
CHANGED
|
@@ -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":
|
|
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
|
-
|
|
227
|
-
|
|
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
|
|
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=
|
|
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,
|
|
57
|
-
if field_name in {"base_url", "model_name"} and not await self.is_valid_ollama_url(
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
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.
|
|
3
|
+
Version: 0.1.12.dev30
|
|
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
|
|
8
|
-
Requires-Dist: aiofiles
|
|
9
|
-
Requires-Dist: asyncer
|
|
10
|
-
Requires-Dist: cachetools
|
|
11
|
-
Requires-Dist: chardet
|
|
12
|
-
Requires-Dist: defusedxml
|
|
13
|
-
Requires-Dist: docstring-parser
|
|
14
|
-
Requires-Dist: emoji
|
|
15
|
-
Requires-Dist: fastapi
|
|
16
|
-
Requires-Dist: httpx[http2]
|
|
17
|
-
Requires-Dist: json-repair
|
|
18
|
-
Requires-Dist: langchain-core
|
|
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
|
|
21
|
-
Requires-Dist: nanoid
|
|
22
|
-
Requires-Dist: networkx
|
|
23
|
-
Requires-Dist: orjson
|
|
24
|
-
Requires-Dist: pandas
|
|
25
|
-
Requires-Dist: passlib
|
|
26
|
-
Requires-Dist: pillow
|
|
27
|
-
Requires-Dist: platformdirs
|
|
28
|
-
Requires-Dist: pydantic-settings
|
|
29
|
-
Requires-Dist: pydantic
|
|
30
|
-
Requires-Dist: python-dotenv
|
|
31
|
-
Requires-Dist: rich
|
|
32
|
-
Requires-Dist: structlog
|
|
33
|
-
Requires-Dist: tomli
|
|
34
|
-
Requires-Dist: typer
|
|
35
|
-
Requires-Dist: typing-extensions
|
|
36
|
-
Requires-Dist: uvicorn
|
|
37
|
-
Requires-Dist: validators
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
410
|
-
lfx/components/ollama/ollama_embeddings.py,sha256=
|
|
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=
|
|
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=
|
|
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
|
|
@@ -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=
|
|
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.
|
|
721
|
-
lfx_nightly-0.1.12.
|
|
722
|
-
lfx_nightly-0.1.12.
|
|
723
|
-
lfx_nightly-0.1.12.
|
|
722
|
+
lfx_nightly-0.1.12.dev30.dist-info/METADATA,sha256=GQgcX1Gmt1RURin7nW6gYOxag3L9eOi2m-9JczQHMfw,8290
|
|
723
|
+
lfx_nightly-0.1.12.dev30.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
724
|
+
lfx_nightly-0.1.12.dev30.dist-info/entry_points.txt,sha256=1724p3RHDQRT2CKx_QRzEIa7sFuSVO0Ux70YfXfoMT4,42
|
|
725
|
+
lfx_nightly-0.1.12.dev30.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|