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.
Files changed (98) hide show
  1. lfx/__main__.py +137 -6
  2. lfx/_assets/component_index.json +1 -1
  3. lfx/base/agents/agent.py +10 -6
  4. lfx/base/agents/altk_base_agent.py +5 -3
  5. lfx/base/agents/altk_tool_wrappers.py +1 -1
  6. lfx/base/agents/events.py +1 -1
  7. lfx/base/agents/utils.py +4 -0
  8. lfx/base/composio/composio_base.py +78 -41
  9. lfx/base/data/cloud_storage_utils.py +156 -0
  10. lfx/base/data/docling_utils.py +130 -55
  11. lfx/base/datastax/astradb_base.py +75 -64
  12. lfx/base/embeddings/embeddings_class.py +113 -0
  13. lfx/base/models/__init__.py +11 -1
  14. lfx/base/models/google_generative_ai_constants.py +33 -9
  15. lfx/base/models/model_metadata.py +6 -0
  16. lfx/base/models/ollama_constants.py +196 -30
  17. lfx/base/models/openai_constants.py +37 -10
  18. lfx/base/models/unified_models.py +1123 -0
  19. lfx/base/models/watsonx_constants.py +43 -4
  20. lfx/base/prompts/api_utils.py +40 -5
  21. lfx/base/tools/component_tool.py +2 -9
  22. lfx/cli/__init__.py +10 -2
  23. lfx/cli/commands.py +3 -0
  24. lfx/cli/run.py +65 -409
  25. lfx/cli/script_loader.py +18 -7
  26. lfx/cli/validation.py +6 -3
  27. lfx/components/__init__.py +0 -3
  28. lfx/components/composio/github_composio.py +1 -1
  29. lfx/components/cuga/cuga_agent.py +39 -27
  30. lfx/components/data_source/api_request.py +4 -2
  31. lfx/components/datastax/astradb_assistant_manager.py +4 -2
  32. lfx/components/docling/__init__.py +45 -11
  33. lfx/components/docling/docling_inline.py +39 -49
  34. lfx/components/docling/docling_remote.py +1 -0
  35. lfx/components/elastic/opensearch_multimodal.py +1733 -0
  36. lfx/components/files_and_knowledge/file.py +384 -36
  37. lfx/components/files_and_knowledge/ingestion.py +8 -0
  38. lfx/components/files_and_knowledge/retrieval.py +10 -0
  39. lfx/components/files_and_knowledge/save_file.py +91 -88
  40. lfx/components/langchain_utilities/ibm_granite_handler.py +211 -0
  41. lfx/components/langchain_utilities/tool_calling.py +37 -6
  42. lfx/components/llm_operations/batch_run.py +64 -18
  43. lfx/components/llm_operations/lambda_filter.py +213 -101
  44. lfx/components/llm_operations/llm_conditional_router.py +39 -7
  45. lfx/components/llm_operations/structured_output.py +38 -12
  46. lfx/components/models/__init__.py +16 -74
  47. lfx/components/models_and_agents/agent.py +51 -203
  48. lfx/components/models_and_agents/embedding_model.py +171 -255
  49. lfx/components/models_and_agents/language_model.py +54 -318
  50. lfx/components/models_and_agents/mcp_component.py +96 -10
  51. lfx/components/models_and_agents/prompt.py +105 -18
  52. lfx/components/ollama/ollama_embeddings.py +111 -29
  53. lfx/components/openai/openai_chat_model.py +1 -1
  54. lfx/components/processing/text_operations.py +580 -0
  55. lfx/components/vllm/__init__.py +37 -0
  56. lfx/components/vllm/vllm.py +141 -0
  57. lfx/components/vllm/vllm_embeddings.py +110 -0
  58. lfx/custom/custom_component/component.py +65 -10
  59. lfx/custom/custom_component/custom_component.py +8 -6
  60. lfx/events/observability/__init__.py +0 -0
  61. lfx/events/observability/lifecycle_events.py +111 -0
  62. lfx/field_typing/__init__.py +57 -58
  63. lfx/graph/graph/base.py +40 -1
  64. lfx/graph/utils.py +109 -30
  65. lfx/graph/vertex/base.py +75 -23
  66. lfx/graph/vertex/vertex_types.py +0 -5
  67. lfx/inputs/__init__.py +2 -0
  68. lfx/inputs/input_mixin.py +55 -0
  69. lfx/inputs/inputs.py +120 -0
  70. lfx/interface/components.py +24 -7
  71. lfx/interface/initialize/loading.py +42 -12
  72. lfx/io/__init__.py +2 -0
  73. lfx/run/__init__.py +5 -0
  74. lfx/run/base.py +464 -0
  75. lfx/schema/__init__.py +50 -0
  76. lfx/schema/data.py +1 -1
  77. lfx/schema/image.py +26 -7
  78. lfx/schema/message.py +104 -11
  79. lfx/schema/workflow.py +171 -0
  80. lfx/services/deps.py +12 -0
  81. lfx/services/interfaces.py +43 -1
  82. lfx/services/mcp_composer/service.py +7 -1
  83. lfx/services/schema.py +1 -0
  84. lfx/services/settings/auth.py +95 -4
  85. lfx/services/settings/base.py +11 -1
  86. lfx/services/settings/constants.py +2 -0
  87. lfx/services/settings/utils.py +82 -0
  88. lfx/services/storage/local.py +13 -8
  89. lfx/services/transaction/__init__.py +5 -0
  90. lfx/services/transaction/service.py +35 -0
  91. lfx/tests/unit/components/__init__.py +0 -0
  92. lfx/utils/constants.py +2 -0
  93. lfx/utils/mustache_security.py +79 -0
  94. lfx/utils/validate_cloud.py +81 -3
  95. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/METADATA +7 -2
  96. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/RECORD +98 -80
  97. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.3.0.dev3.dist-info}/WHEEL +0 -0
  98. {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
@@ -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
- # If specific modules requested, filter by top-level module name
339
- if target_modules:
340
- # Extract top-level: "lfx.components.mistral.xyz" -> "mistral"
341
- parts = modname.split(".")
342
- if len(parts) > MIN_MODULE_PARTS and parts[2].lower() not in target_modules:
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
- key = os.getenv(params[field])
119
- if key:
120
- logger.info(f"Using environment variable {params[field]} for {field}")
121
- else:
122
- logger.error(f"Environment variable {params[field]} is not set.")
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 = os.getenv(variable_name)
174
- if key:
175
- logger.info(f"Using environment variable {variable_name} for table column {column_name}")
176
- else:
177
- logger.error(f"Environment variable {variable_name} is not set.")
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
- return load_from_env_vars(params, load_from_db_fields)
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",
lfx/run/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Run module for executing Langflow graphs."""
2
+
3
+ from lfx.run.base import RunError, run_flow
4
+
5
+ __all__ = ["RunError", "run_flow"]