lfx-nightly 0.2.0.dev41__py3-none-any.whl → 0.3.0.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lfx/__main__.py +137 -6
- lfx/_assets/component_index.json +1 -1
- lfx/base/agents/agent.py +10 -6
- lfx/base/agents/altk_base_agent.py +5 -3
- lfx/base/agents/altk_tool_wrappers.py +1 -1
- lfx/base/agents/events.py +1 -1
- lfx/base/agents/utils.py +4 -0
- lfx/base/composio/composio_base.py +78 -41
- lfx/base/data/cloud_storage_utils.py +156 -0
- lfx/base/data/docling_utils.py +130 -55
- lfx/base/datastax/astradb_base.py +75 -64
- lfx/base/embeddings/embeddings_class.py +113 -0
- lfx/base/models/__init__.py +11 -1
- lfx/base/models/google_generative_ai_constants.py +33 -9
- lfx/base/models/model_metadata.py +6 -0
- lfx/base/models/ollama_constants.py +196 -30
- lfx/base/models/openai_constants.py +37 -10
- lfx/base/models/unified_models.py +1123 -0
- lfx/base/models/watsonx_constants.py +43 -4
- lfx/base/prompts/api_utils.py +40 -5
- lfx/base/tools/component_tool.py +2 -9
- lfx/cli/__init__.py +10 -2
- lfx/cli/commands.py +3 -0
- lfx/cli/run.py +65 -409
- lfx/cli/script_loader.py +18 -7
- lfx/cli/validation.py +6 -3
- lfx/components/__init__.py +0 -3
- lfx/components/composio/github_composio.py +1 -1
- lfx/components/cuga/cuga_agent.py +39 -27
- lfx/components/data_source/api_request.py +4 -2
- lfx/components/datastax/astradb_assistant_manager.py +4 -2
- lfx/components/docling/__init__.py +45 -11
- lfx/components/docling/docling_inline.py +39 -49
- lfx/components/docling/docling_remote.py +1 -0
- lfx/components/elastic/opensearch_multimodal.py +1733 -0
- lfx/components/files_and_knowledge/file.py +384 -36
- lfx/components/files_and_knowledge/ingestion.py +8 -0
- lfx/components/files_and_knowledge/retrieval.py +10 -0
- lfx/components/files_and_knowledge/save_file.py +91 -88
- lfx/components/langchain_utilities/ibm_granite_handler.py +211 -0
- lfx/components/langchain_utilities/tool_calling.py +37 -6
- lfx/components/llm_operations/batch_run.py +64 -18
- lfx/components/llm_operations/lambda_filter.py +213 -101
- lfx/components/llm_operations/llm_conditional_router.py +39 -7
- lfx/components/llm_operations/structured_output.py +38 -12
- lfx/components/models/__init__.py +16 -74
- lfx/components/models_and_agents/agent.py +51 -203
- lfx/components/models_and_agents/embedding_model.py +171 -255
- lfx/components/models_and_agents/language_model.py +54 -318
- lfx/components/models_and_agents/mcp_component.py +96 -10
- lfx/components/models_and_agents/prompt.py +105 -18
- lfx/components/ollama/ollama_embeddings.py +111 -29
- lfx/components/openai/openai_chat_model.py +1 -1
- lfx/components/processing/text_operations.py +580 -0
- lfx/components/vllm/__init__.py +37 -0
- lfx/components/vllm/vllm.py +141 -0
- lfx/components/vllm/vllm_embeddings.py +110 -0
- lfx/custom/custom_component/component.py +65 -10
- lfx/custom/custom_component/custom_component.py +8 -6
- lfx/events/observability/__init__.py +0 -0
- lfx/events/observability/lifecycle_events.py +111 -0
- lfx/field_typing/__init__.py +57 -58
- lfx/graph/graph/base.py +40 -1
- lfx/graph/utils.py +109 -30
- lfx/graph/vertex/base.py +75 -23
- lfx/graph/vertex/vertex_types.py +0 -5
- lfx/inputs/__init__.py +2 -0
- lfx/inputs/input_mixin.py +55 -0
- lfx/inputs/inputs.py +120 -0
- lfx/interface/components.py +24 -7
- lfx/interface/initialize/loading.py +42 -12
- lfx/io/__init__.py +2 -0
- lfx/run/__init__.py +5 -0
- lfx/run/base.py +464 -0
- lfx/schema/__init__.py +50 -0
- lfx/schema/data.py +1 -1
- lfx/schema/image.py +26 -7
- lfx/schema/message.py +104 -11
- lfx/schema/workflow.py +171 -0
- lfx/services/deps.py +12 -0
- lfx/services/interfaces.py +43 -1
- lfx/services/mcp_composer/service.py +7 -1
- lfx/services/schema.py +1 -0
- lfx/services/settings/auth.py +95 -4
- lfx/services/settings/base.py +11 -1
- lfx/services/settings/constants.py +2 -0
- lfx/services/settings/utils.py +82 -0
- lfx/services/storage/local.py +13 -8
- lfx/services/transaction/__init__.py +5 -0
- lfx/services/transaction/service.py +35 -0
- lfx/tests/unit/components/__init__.py +0 -0
- lfx/utils/constants.py +2 -0
- lfx/utils/mustache_security.py +79 -0
- lfx/utils/validate_cloud.py +81 -3
- {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/METADATA +7 -2
- {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/RECORD +98 -80
- {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/WHEEL +0 -0
- {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/entry_points.txt +0 -0
lfx/inputs/inputs.py
CHANGED
|
@@ -22,6 +22,7 @@ from .input_mixin import (
|
|
|
22
22
|
LinkMixin,
|
|
23
23
|
ListableInputMixin,
|
|
24
24
|
MetadataTraceMixin,
|
|
25
|
+
ModelInputMixin,
|
|
25
26
|
MultilineMixin,
|
|
26
27
|
QueryMixin,
|
|
27
28
|
RangeMixin,
|
|
@@ -119,10 +120,127 @@ class PromptInput(BaseInputMixin, ListableInputMixin, InputTraceMixin, ToolModeM
|
|
|
119
120
|
field_type: SerializableFieldTypes = FieldTypes.PROMPT
|
|
120
121
|
|
|
121
122
|
|
|
123
|
+
class MustachePromptInput(PromptInput):
|
|
124
|
+
field_type: SerializableFieldTypes = FieldTypes.MUSTACHE_PROMPT
|
|
125
|
+
|
|
126
|
+
|
|
122
127
|
class CodeInput(BaseInputMixin, ListableInputMixin, InputTraceMixin, ToolModeMixin):
|
|
123
128
|
field_type: SerializableFieldTypes = FieldTypes.CODE
|
|
124
129
|
|
|
125
130
|
|
|
131
|
+
class ModelInput(BaseInputMixin, ModelInputMixin, ListableInputMixin, InputTraceMixin, ToolModeMixin):
|
|
132
|
+
"""Represents a model input field with optional LanguageModel connection support.
|
|
133
|
+
|
|
134
|
+
By default:
|
|
135
|
+
- input_types=[] (no handle shown)
|
|
136
|
+
- external_options with "Connect other models" button
|
|
137
|
+
- refresh_button=True
|
|
138
|
+
|
|
139
|
+
When "Connect other models" is selected (value="connect_other_models"):
|
|
140
|
+
- input_types is set to ["LanguageModel"] to show the connection handle
|
|
141
|
+
|
|
142
|
+
Value format:
|
|
143
|
+
- Can be a list of dicts: [{'name': 'gpt-4o', 'provider': 'OpenAI', ...}]
|
|
144
|
+
- Can be a simple list of strings: ['gpt-4o', 'gpt-4o-mini'] (auto-converted)
|
|
145
|
+
- Can be a single string: 'gpt-4o' (auto-converted to list)
|
|
146
|
+
- Can be "connect_other_models" string to enable connection mode
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
field_type: SerializableFieldTypes = FieldTypes.MODEL
|
|
150
|
+
placeholder: str | None = "Setup Provider"
|
|
151
|
+
input_types: list[str] = Field(default_factory=list) # Empty by default, no handle shown
|
|
152
|
+
refresh_button: bool | None = True
|
|
153
|
+
external_options: dict = Field(
|
|
154
|
+
default_factory=lambda: {
|
|
155
|
+
"fields": {
|
|
156
|
+
"data": {
|
|
157
|
+
"node": {
|
|
158
|
+
"name": "connect_other_models",
|
|
159
|
+
"display_name": "Connect other models",
|
|
160
|
+
"icon": "CornerDownLeft",
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
@field_validator("value", mode="before")
|
|
168
|
+
@classmethod
|
|
169
|
+
def normalize_value(cls, v):
|
|
170
|
+
"""Convert simple string or list of strings to list of dicts format.
|
|
171
|
+
|
|
172
|
+
Allows passing:
|
|
173
|
+
- 'gpt-4o' -> [{'name': 'gpt-4o', ...}]
|
|
174
|
+
- ['gpt-4o', 'claude-3'] -> [{'name': 'gpt-4o', ...}, {'name': 'claude-3', ...}]
|
|
175
|
+
- [{'name': 'gpt-4o'}] -> [{'name': 'gpt-4o'}] (unchanged)
|
|
176
|
+
- 'connect_other_models' -> 'connect_other_models' (special value, keep as string)
|
|
177
|
+
"""
|
|
178
|
+
# Handle empty or None values
|
|
179
|
+
if v is None or v == "":
|
|
180
|
+
return v
|
|
181
|
+
|
|
182
|
+
# Special case: keep "connect_other_models" as a string to enable connection mode
|
|
183
|
+
if v == "connect_other_models":
|
|
184
|
+
return v
|
|
185
|
+
|
|
186
|
+
# If it's not a list or string, return as-is (could be a BaseLanguageModel)
|
|
187
|
+
if not isinstance(v, list | str):
|
|
188
|
+
return v
|
|
189
|
+
|
|
190
|
+
# If it's a list and already in dict format, return as-is
|
|
191
|
+
if isinstance(v, list) and all(isinstance(item, dict) for item in v):
|
|
192
|
+
return v
|
|
193
|
+
|
|
194
|
+
# If it's a string or list of strings, convert to dict format
|
|
195
|
+
if isinstance(v, str) or (isinstance(v, list) and all(isinstance(item, str) for item in v)):
|
|
196
|
+
# Avoid circular import by importing the module directly (not through package __init__)
|
|
197
|
+
try:
|
|
198
|
+
from lfx.base.models.unified_models import normalize_model_names_to_dicts
|
|
199
|
+
|
|
200
|
+
return normalize_model_names_to_dicts(v)
|
|
201
|
+
except Exception: # noqa: BLE001
|
|
202
|
+
# Fallback if import or normalization fails
|
|
203
|
+
# This can happen during module initialization or in test environments
|
|
204
|
+
if isinstance(v, str):
|
|
205
|
+
return [{"name": v}]
|
|
206
|
+
return [{"name": item} for item in v]
|
|
207
|
+
|
|
208
|
+
# Return as-is for all other cases
|
|
209
|
+
return v
|
|
210
|
+
|
|
211
|
+
@model_validator(mode="after")
|
|
212
|
+
def set_defaults(self):
|
|
213
|
+
"""Handle connection mode and set defaults.
|
|
214
|
+
|
|
215
|
+
When value is "connect_other_models", set input_types to ["LanguageModel"]
|
|
216
|
+
to enable the connection handle. Otherwise, keep input_types empty.
|
|
217
|
+
"""
|
|
218
|
+
# Check if we're in connection mode (user selected "Connect other models")
|
|
219
|
+
if self.value == "connect_other_models" and not self.input_types:
|
|
220
|
+
# Enable connection handle by setting input_types
|
|
221
|
+
# Use object.__setattr__ to avoid triggering validation recursion
|
|
222
|
+
object.__setattr__(self, "input_types", ["LanguageModel"])
|
|
223
|
+
|
|
224
|
+
# Set external_options if not explicitly provided
|
|
225
|
+
if self.external_options is None or len(self.external_options) == 0:
|
|
226
|
+
object.__setattr__(
|
|
227
|
+
self,
|
|
228
|
+
"external_options",
|
|
229
|
+
{
|
|
230
|
+
"fields": {
|
|
231
|
+
"data": {
|
|
232
|
+
"node": {
|
|
233
|
+
"name": "connect_other_models",
|
|
234
|
+
"display_name": "Connect other models",
|
|
235
|
+
"icon": "CornerDownLeft",
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
)
|
|
241
|
+
return self
|
|
242
|
+
|
|
243
|
+
|
|
126
244
|
# Applying mixins to a specific input type
|
|
127
245
|
class StrInput(
|
|
128
246
|
BaseInputMixin,
|
|
@@ -687,11 +805,13 @@ InputTypes: TypeAlias = (
|
|
|
687
805
|
| HandleInput
|
|
688
806
|
| IntInput
|
|
689
807
|
| McpInput
|
|
808
|
+
| ModelInput
|
|
690
809
|
| MultilineInput
|
|
691
810
|
| MultilineSecretInput
|
|
692
811
|
| NestedDictInput
|
|
693
812
|
| ToolsInput
|
|
694
813
|
| PromptInput
|
|
814
|
+
| MustachePromptInput
|
|
695
815
|
| CodeInput
|
|
696
816
|
| SecretStrInput
|
|
697
817
|
| StrInput
|
lfx/interface/components.py
CHANGED
|
@@ -14,11 +14,16 @@ import orjson
|
|
|
14
14
|
from lfx.constants import BASE_COMPONENTS_PATH
|
|
15
15
|
from lfx.custom.utils import abuild_custom_components, create_component_template
|
|
16
16
|
from lfx.log.logger import logger
|
|
17
|
+
from lfx.utils.validate_cloud import (
|
|
18
|
+
filter_disabled_components_from_dict,
|
|
19
|
+
is_component_disabled_in_astra_cloud,
|
|
20
|
+
)
|
|
17
21
|
|
|
18
22
|
if TYPE_CHECKING:
|
|
19
23
|
from lfx.services.settings.service import SettingsService
|
|
20
24
|
|
|
21
25
|
MIN_MODULE_PARTS = 2
|
|
26
|
+
MIN_MODULE_PARTS_WITH_FILENAME = 4 # Minimum parts needed to have a module filename (lfx.components.type.filename)
|
|
22
27
|
EXPECTED_RESULT_LENGTH = 2 # Expected length of the tuple returned by _process_single_module
|
|
23
28
|
|
|
24
29
|
|
|
@@ -284,6 +289,8 @@ async def _load_from_index_or_cache(
|
|
|
284
289
|
if top_level not in modules_dict:
|
|
285
290
|
modules_dict[top_level] = {}
|
|
286
291
|
modules_dict[top_level].update(components)
|
|
292
|
+
# Filter disabled components for Astra cloud
|
|
293
|
+
modules_dict = filter_disabled_components_from_dict(modules_dict)
|
|
287
294
|
await logger.adebug(f"Loaded {len(modules_dict)} component categories from index")
|
|
288
295
|
return modules_dict, "builtin"
|
|
289
296
|
|
|
@@ -303,6 +310,8 @@ async def _load_from_index_or_cache(
|
|
|
303
310
|
if top_level not in modules_dict:
|
|
304
311
|
modules_dict[top_level] = {}
|
|
305
312
|
modules_dict[top_level].update(components)
|
|
313
|
+
# Filter disabled components for Astra cloud
|
|
314
|
+
modules_dict = filter_disabled_components_from_dict(modules_dict)
|
|
306
315
|
await logger.adebug(f"Loaded {len(modules_dict)} component categories from cache")
|
|
307
316
|
return modules_dict, "cache"
|
|
308
317
|
|
|
@@ -335,11 +344,19 @@ async def _load_components_dynamically(
|
|
|
335
344
|
if "deactivated" in modname:
|
|
336
345
|
continue
|
|
337
346
|
|
|
338
|
-
#
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
347
|
+
# Parse module name once for all checks
|
|
348
|
+
parts = modname.split(".")
|
|
349
|
+
if len(parts) > MIN_MODULE_PARTS:
|
|
350
|
+
component_type = parts[2]
|
|
351
|
+
|
|
352
|
+
# Skip disabled components when ASTRA_CLOUD_DISABLE_COMPONENT is true
|
|
353
|
+
if len(parts) >= MIN_MODULE_PARTS_WITH_FILENAME:
|
|
354
|
+
module_filename = parts[3]
|
|
355
|
+
if is_component_disabled_in_astra_cloud(component_type.lower(), module_filename):
|
|
356
|
+
continue
|
|
357
|
+
|
|
358
|
+
# If specific modules requested, filter by top-level module name
|
|
359
|
+
if target_modules and component_type.lower() not in target_modules:
|
|
343
360
|
continue
|
|
344
361
|
|
|
345
362
|
module_names.append(modname)
|
|
@@ -549,7 +566,7 @@ def _process_single_module(modname: str) -> tuple[str, dict] | None:
|
|
|
549
566
|
return (top_level, module_components)
|
|
550
567
|
|
|
551
568
|
|
|
552
|
-
async def _determine_loading_strategy(settings_service: "SettingsService") -> dict:
|
|
569
|
+
async def _determine_loading_strategy(settings_service: "SettingsService") -> dict[str, Any]:
|
|
553
570
|
"""Determines and executes the appropriate component loading strategy.
|
|
554
571
|
|
|
555
572
|
Args:
|
|
@@ -577,7 +594,7 @@ async def _determine_loading_strategy(settings_service: "SettingsService") -> di
|
|
|
577
594
|
f"Built {component_count} custom components from {settings_service.settings.components_path}"
|
|
578
595
|
)
|
|
579
596
|
|
|
580
|
-
return component_cache.all_types_dict
|
|
597
|
+
return component_cache.all_types_dict or {}
|
|
581
598
|
|
|
582
599
|
|
|
583
600
|
async def get_and_cache_all_types_dict(
|
|
@@ -111,15 +111,26 @@ def convert_kwargs(params):
|
|
|
111
111
|
return params
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
def load_from_env_vars(params, load_from_db_fields):
|
|
114
|
+
def load_from_env_vars(params, load_from_db_fields, context=None):
|
|
115
115
|
for field in load_from_db_fields:
|
|
116
116
|
if field not in params or not params[field]:
|
|
117
117
|
continue
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
variable_name = params[field]
|
|
119
|
+
key = None
|
|
120
|
+
|
|
121
|
+
# Check request_variables in context
|
|
122
|
+
if context and "request_variables" in context:
|
|
123
|
+
request_variables = context["request_variables"]
|
|
124
|
+
if variable_name in request_variables:
|
|
125
|
+
key = request_variables[variable_name]
|
|
126
|
+
logger.debug(f"Found context override for variable '{variable_name}'")
|
|
127
|
+
|
|
128
|
+
if key is None:
|
|
129
|
+
key = os.getenv(variable_name)
|
|
130
|
+
if key:
|
|
131
|
+
logger.info(f"Using environment variable {variable_name} for {field}")
|
|
132
|
+
else:
|
|
133
|
+
logger.error(f"Environment variable {variable_name} is not set.")
|
|
123
134
|
params[field] = key if key is not None else None
|
|
124
135
|
if key is None:
|
|
125
136
|
logger.warning(f"Could not get value for {field}. Setting it to None.")
|
|
@@ -142,6 +153,11 @@ async def update_table_params_with_load_from_db_fields(
|
|
|
142
153
|
if not table_data or not load_from_db_columns:
|
|
143
154
|
return params
|
|
144
155
|
|
|
156
|
+
# Extract context once for use throughout the function
|
|
157
|
+
context = None
|
|
158
|
+
if hasattr(custom_component, "graph") and hasattr(custom_component.graph, "context"):
|
|
159
|
+
context = custom_component.graph.context
|
|
160
|
+
|
|
145
161
|
async with session_scope() as session:
|
|
146
162
|
settings_service = get_settings_service()
|
|
147
163
|
is_noop_session = isinstance(session, NoopSession) or (
|
|
@@ -170,11 +186,22 @@ async def update_table_params_with_load_from_db_fields(
|
|
|
170
186
|
try:
|
|
171
187
|
if is_noop_session:
|
|
172
188
|
# Fallback to environment variables
|
|
173
|
-
key =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
key = None
|
|
190
|
+
# Check request_variables first
|
|
191
|
+
if context and "request_variables" in context:
|
|
192
|
+
request_variables = context["request_variables"]
|
|
193
|
+
if variable_name in request_variables:
|
|
194
|
+
key = request_variables[variable_name]
|
|
195
|
+
logger.debug(f"Found context override for variable '{variable_name}'")
|
|
196
|
+
|
|
197
|
+
if key is None:
|
|
198
|
+
key = os.getenv(variable_name)
|
|
199
|
+
if key:
|
|
200
|
+
logger.info(
|
|
201
|
+
f"Using environment variable {variable_name} for table column {column_name}"
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
logger.error(f"Environment variable {variable_name} is not set.")
|
|
178
205
|
else:
|
|
179
206
|
# Load from database
|
|
180
207
|
key = await custom_component.get_variable(
|
|
@@ -222,7 +249,10 @@ async def update_params_with_load_from_db_fields(
|
|
|
222
249
|
)
|
|
223
250
|
if is_noop_session:
|
|
224
251
|
logger.debug("Loading variables from environment variables because database is not available.")
|
|
225
|
-
|
|
252
|
+
context = None
|
|
253
|
+
if hasattr(custom_component, "graph") and hasattr(custom_component.graph, "context"):
|
|
254
|
+
context = custom_component.graph.context
|
|
255
|
+
return load_from_env_vars(params, load_from_db_fields, context=context)
|
|
226
256
|
for field in load_from_db_fields:
|
|
227
257
|
# Check if this is a table field (using our naming convention)
|
|
228
258
|
if field.startswith("table:"):
|
lfx/io/__init__.py
CHANGED
|
@@ -14,6 +14,7 @@ from lfx.inputs import (
|
|
|
14
14
|
McpInput,
|
|
15
15
|
MessageInput,
|
|
16
16
|
MessageTextInput,
|
|
17
|
+
ModelInput,
|
|
17
18
|
MultilineInput,
|
|
18
19
|
MultilineSecretInput,
|
|
19
20
|
MultiselectInput,
|
|
@@ -47,6 +48,7 @@ __all__ = [
|
|
|
47
48
|
"McpInput",
|
|
48
49
|
"MessageInput",
|
|
49
50
|
"MessageTextInput",
|
|
51
|
+
"ModelInput",
|
|
50
52
|
"MultilineInput",
|
|
51
53
|
"MultilineSecretInput",
|
|
52
54
|
"MultiselectInput",
|