lfx-nightly 0.2.0.dev41__py3-none-any.whl → 0.2.1.dev7__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 (70) hide show
  1. lfx/_assets/component_index.json +1 -1
  2. lfx/base/agents/agent.py +1 -1
  3. lfx/base/agents/altk_tool_wrappers.py +1 -1
  4. lfx/base/agents/utils.py +4 -0
  5. lfx/base/composio/composio_base.py +78 -41
  6. lfx/base/data/cloud_storage_utils.py +156 -0
  7. lfx/base/data/docling_utils.py +130 -55
  8. lfx/base/datastax/astradb_base.py +75 -64
  9. lfx/base/embeddings/embeddings_class.py +113 -0
  10. lfx/base/models/__init__.py +11 -1
  11. lfx/base/models/google_generative_ai_constants.py +33 -9
  12. lfx/base/models/model_metadata.py +6 -0
  13. lfx/base/models/ollama_constants.py +196 -30
  14. lfx/base/models/openai_constants.py +37 -10
  15. lfx/base/models/unified_models.py +1123 -0
  16. lfx/base/models/watsonx_constants.py +36 -0
  17. lfx/base/tools/component_tool.py +2 -9
  18. lfx/cli/commands.py +3 -0
  19. lfx/cli/run.py +65 -409
  20. lfx/cli/script_loader.py +13 -3
  21. lfx/components/__init__.py +0 -3
  22. lfx/components/composio/github_composio.py +1 -1
  23. lfx/components/cuga/cuga_agent.py +39 -27
  24. lfx/components/data_source/api_request.py +4 -2
  25. lfx/components/docling/__init__.py +45 -11
  26. lfx/components/docling/docling_inline.py +39 -49
  27. lfx/components/elastic/opensearch_multimodal.py +1733 -0
  28. lfx/components/files_and_knowledge/file.py +384 -36
  29. lfx/components/files_and_knowledge/ingestion.py +8 -0
  30. lfx/components/files_and_knowledge/retrieval.py +10 -0
  31. lfx/components/files_and_knowledge/save_file.py +91 -88
  32. lfx/components/langchain_utilities/tool_calling.py +14 -6
  33. lfx/components/llm_operations/batch_run.py +64 -18
  34. lfx/components/llm_operations/lambda_filter.py +33 -6
  35. lfx/components/llm_operations/llm_conditional_router.py +39 -7
  36. lfx/components/llm_operations/structured_output.py +38 -12
  37. lfx/components/models/__init__.py +16 -74
  38. lfx/components/models_and_agents/agent.py +51 -203
  39. lfx/components/models_and_agents/embedding_model.py +171 -255
  40. lfx/components/models_and_agents/language_model.py +54 -318
  41. lfx/components/models_and_agents/mcp_component.py +58 -9
  42. lfx/components/ollama/ollama_embeddings.py +2 -1
  43. lfx/components/openai/openai_chat_model.py +1 -1
  44. lfx/components/vllm/__init__.py +37 -0
  45. lfx/components/vllm/vllm.py +141 -0
  46. lfx/components/vllm/vllm_embeddings.py +110 -0
  47. lfx/custom/custom_component/custom_component.py +8 -6
  48. lfx/graph/graph/base.py +4 -1
  49. lfx/graph/utils.py +64 -18
  50. lfx/graph/vertex/base.py +4 -1
  51. lfx/inputs/__init__.py +2 -0
  52. lfx/inputs/input_mixin.py +54 -0
  53. lfx/inputs/inputs.py +115 -0
  54. lfx/interface/initialize/loading.py +42 -12
  55. lfx/io/__init__.py +2 -0
  56. lfx/run/__init__.py +5 -0
  57. lfx/run/base.py +494 -0
  58. lfx/schema/data.py +1 -1
  59. lfx/schema/image.py +26 -7
  60. lfx/schema/message.py +19 -3
  61. lfx/services/mcp_composer/service.py +7 -1
  62. lfx/services/settings/base.py +7 -1
  63. lfx/services/settings/constants.py +2 -0
  64. lfx/services/storage/local.py +13 -8
  65. lfx/utils/constants.py +1 -0
  66. lfx/utils/validate_cloud.py +14 -3
  67. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.2.1.dev7.dist-info}/METADATA +5 -2
  68. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.2.1.dev7.dist-info}/RECORD +70 -61
  69. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.2.1.dev7.dist-info}/WHEEL +0 -0
  70. {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.2.1.dev7.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from collections.abc import AsyncIterator, Iterator
3
3
  from pathlib import Path
4
+ from typing import Any
4
5
 
5
6
  import orjson
6
7
  import pandas as pd
@@ -13,6 +14,15 @@ from lfx.io import BoolInput, DropdownInput, HandleInput, SecretStrInput, StrInp
13
14
  from lfx.schema import Data, DataFrame, Message
14
15
  from lfx.services.deps import get_settings_service, get_storage_service, session_scope
15
16
  from lfx.template.field.base import Output
17
+ from lfx.utils.validate_cloud import is_astra_cloud_environment
18
+
19
+
20
+ def _get_storage_location_options():
21
+ """Get storage location options, filtering out Local if in Astra cloud environment."""
22
+ all_options = [{"name": "AWS", "icon": "Amazon"}, {"name": "Google Drive", "icon": "google"}]
23
+ if is_astra_cloud_environment():
24
+ return all_options
25
+ return [{"name": "Local", "icon": "hard-drive"}, *all_options]
16
26
 
17
27
 
18
28
  class SaveToFileComponent(Component):
@@ -49,11 +59,7 @@ class SaveToFileComponent(Component):
49
59
  display_name="Storage Location",
50
60
  placeholder="Select Location",
51
61
  info="Choose where to save the file.",
52
- options=[
53
- {"name": "Local", "icon": "hard-drive"},
54
- {"name": "AWS", "icon": "Amazon"},
55
- {"name": "Google Drive", "icon": "google"},
56
- ],
62
+ options=_get_storage_location_options(),
57
63
  real_time_refresh=True,
58
64
  limit=1,
59
65
  ),
@@ -116,6 +122,7 @@ class SaveToFileComponent(Component):
116
122
  info="AWS Access key ID.",
117
123
  show=False,
118
124
  advanced=True,
125
+ required=True,
119
126
  ),
120
127
  SecretStrInput(
121
128
  name="aws_secret_access_key",
@@ -123,6 +130,7 @@ class SaveToFileComponent(Component):
123
130
  info="AWS Secret Key.",
124
131
  show=False,
125
132
  advanced=True,
133
+ required=True,
126
134
  ),
127
135
  StrInput(
128
136
  name="bucket_name",
@@ -130,6 +138,7 @@ class SaveToFileComponent(Component):
130
138
  info="Enter the name of the S3 bucket.",
131
139
  show=False,
132
140
  advanced=True,
141
+ required=True,
133
142
  ),
134
143
  StrInput(
135
144
  name="aws_region",
@@ -152,6 +161,7 @@ class SaveToFileComponent(Component):
152
161
  info="Your Google Cloud Platform service account JSON key as a secret string (complete JSON content).",
153
162
  show=False,
154
163
  advanced=True,
164
+ required=True,
155
165
  ),
156
166
  StrInput(
157
167
  name="folder_id",
@@ -170,6 +180,12 @@ class SaveToFileComponent(Component):
170
180
 
171
181
  def update_build_config(self, build_config, field_value, field_name=None):
172
182
  """Update build configuration to show/hide fields based on storage location selection."""
183
+ # Update options dynamically based on cloud environment
184
+ # This ensures options are refreshed when build_config is updated
185
+ if "storage_location" in build_config:
186
+ updated_options = _get_storage_location_options()
187
+ build_config["storage_location"]["options"] = updated_options
188
+
173
189
  if field_name != "storage_location":
174
190
  return build_config
175
191
 
@@ -224,12 +240,14 @@ class SaveToFileComponent(Component):
224
240
  for f_name in aws_fields:
225
241
  if f_name in build_config:
226
242
  build_config[f_name]["show"] = True
243
+ build_config[f_name]["advanced"] = False
227
244
 
228
245
  elif location == "Google Drive":
229
246
  gdrive_fields = ["gdrive_format", "service_account_key", "folder_id"]
230
247
  for f_name in gdrive_fields:
231
248
  if f_name in build_config:
232
249
  build_config[f_name]["show"] = True
250
+ build_config[f_name]["advanced"] = False
233
251
 
234
252
  return build_config
235
253
 
@@ -249,6 +267,11 @@ class SaveToFileComponent(Component):
249
267
  msg = "Storage location must be selected."
250
268
  raise ValueError(msg)
251
269
 
270
+ # Check if Local storage is disabled in cloud environment
271
+ if storage_location == "Local" and is_astra_cloud_environment():
272
+ msg = "Local storage is not available in cloud environment. Please use AWS or Google Drive."
273
+ raise ValueError(msg)
274
+
252
275
  # Route to appropriate save method based on storage location
253
276
  if storage_location == "Local":
254
277
  return await self._save_to_local()
@@ -540,32 +563,67 @@ class SaveToFileComponent(Component):
540
563
 
541
564
  async def _save_to_aws(self) -> Message:
542
565
  """Save file to AWS S3 using S3 functionality."""
566
+ import os
567
+
568
+ import boto3
569
+
570
+ from lfx.base.data.cloud_storage_utils import create_s3_client, validate_aws_credentials
571
+
572
+ # Get AWS credentials from component inputs or fall back to environment variables
573
+ aws_access_key_id = getattr(self, "aws_access_key_id", None)
574
+ if aws_access_key_id and hasattr(aws_access_key_id, "get_secret_value"):
575
+ aws_access_key_id = aws_access_key_id.get_secret_value()
576
+ if not aws_access_key_id:
577
+ aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID")
578
+
579
+ aws_secret_access_key = getattr(self, "aws_secret_access_key", None)
580
+ if aws_secret_access_key and hasattr(aws_secret_access_key, "get_secret_value"):
581
+ aws_secret_access_key = aws_secret_access_key.get_secret_value()
582
+ if not aws_secret_access_key:
583
+ aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY")
584
+
585
+ bucket_name = getattr(self, "bucket_name", None)
586
+ if not bucket_name:
587
+ # Try to get from storage service settings
588
+ settings = get_settings_service().settings
589
+ bucket_name = settings.object_storage_bucket_name
590
+
543
591
  # Validate AWS credentials
544
- if not getattr(self, "aws_access_key_id", None):
545
- msg = "AWS Access Key ID is required for S3 storage"
592
+ if not aws_access_key_id:
593
+ msg = (
594
+ "AWS Access Key ID is required for S3 storage. Provide it as a component input "
595
+ "or set AWS_ACCESS_KEY_ID environment variable."
596
+ )
546
597
  raise ValueError(msg)
547
- if not getattr(self, "aws_secret_access_key", None):
548
- msg = "AWS Secret Key is required for S3 storage"
598
+ if not aws_secret_access_key:
599
+ msg = (
600
+ "AWS Secret Key is required for S3 storage. Provide it as a component input "
601
+ "or set AWS_SECRET_ACCESS_KEY environment variable."
602
+ )
549
603
  raise ValueError(msg)
550
- if not getattr(self, "bucket_name", None):
551
- msg = "S3 Bucket Name is required for S3 storage"
604
+ if not bucket_name:
605
+ msg = (
606
+ "S3 Bucket Name is required for S3 storage. Provide it as a component input "
607
+ "or set LANGFLOW_OBJECT_STORAGE_BUCKET_NAME environment variable."
608
+ )
552
609
  raise ValueError(msg)
553
610
 
554
- # Use S3 upload functionality
555
- try:
556
- import boto3
557
- except ImportError as e:
558
- msg = "boto3 is not installed. Please install it using `uv pip install boto3`."
559
- raise ImportError(msg) from e
611
+ # Validate AWS credentials
612
+ validate_aws_credentials(self)
560
613
 
561
614
  # Create S3 client
562
- client_config = {
563
- "aws_access_key_id": self.aws_access_key_id,
564
- "aws_secret_access_key": self.aws_secret_access_key,
615
+ s3_client = create_s3_client(self)
616
+ client_config: dict[str, Any] = {
617
+ "aws_access_key_id": str(aws_access_key_id),
618
+ "aws_secret_access_key": str(aws_secret_access_key),
565
619
  }
566
620
 
567
- if hasattr(self, "aws_region") and self.aws_region:
568
- client_config["region_name"] = self.aws_region
621
+ # Get region from component input, environment variable, or settings
622
+ aws_region = getattr(self, "aws_region", None)
623
+ if not aws_region:
624
+ aws_region = os.getenv("AWS_DEFAULT_REGION") or os.getenv("AWS_REGION")
625
+ if aws_region:
626
+ client_config["region_name"] = str(aws_region)
569
627
 
570
628
  s3_client = boto3.client("s3", **client_config)
571
629
 
@@ -589,8 +647,8 @@ class SaveToFileComponent(Component):
589
647
 
590
648
  try:
591
649
  # Upload to S3
592
- s3_client.upload_file(temp_file_path, self.bucket_name, file_path)
593
- s3_url = f"s3://{self.bucket_name}/{file_path}"
650
+ s3_client.upload_file(temp_file_path, bucket_name, file_path)
651
+ s3_url = f"s3://{bucket_name}/{file_path}"
594
652
  return Message(text=f"File successfully uploaded to {s3_url}")
595
653
  finally:
596
654
  # Clean up temp file
@@ -599,6 +657,12 @@ class SaveToFileComponent(Component):
599
657
 
600
658
  async def _save_to_google_drive(self) -> Message:
601
659
  """Save file to Google Drive using Google Drive functionality."""
660
+ import tempfile
661
+
662
+ from googleapiclient.http import MediaFileUpload
663
+
664
+ from lfx.base.data.cloud_storage_utils import create_google_drive_service
665
+
602
666
  # Validate Google Drive credentials
603
667
  if not getattr(self, "service_account_key", None):
604
668
  msg = "GCP Credentials Secret Key is required for Google Drive storage"
@@ -607,71 +671,10 @@ class SaveToFileComponent(Component):
607
671
  msg = "Google Drive Folder ID is required for Google Drive storage"
608
672
  raise ValueError(msg)
609
673
 
610
- # Use Google Drive upload functionality
611
- try:
612
- import json
613
- import tempfile
614
-
615
- from google.oauth2 import service_account
616
- from googleapiclient.discovery import build
617
- from googleapiclient.http import MediaFileUpload
618
- except ImportError as e:
619
- msg = "Google API client libraries are not installed. Please install them."
620
- raise ImportError(msg) from e
621
-
622
- # Parse credentials with multiple fallback strategies
623
- credentials_dict = None
624
- parse_errors = []
625
-
626
- # Strategy 1: Parse as-is with strict=False to allow control characters
627
- try:
628
- credentials_dict = json.loads(self.service_account_key, strict=False)
629
- except json.JSONDecodeError as e:
630
- parse_errors.append(f"Standard parse: {e!s}")
631
-
632
- # Strategy 2: Strip whitespace and try again
633
- if credentials_dict is None:
634
- try:
635
- cleaned_key = self.service_account_key.strip()
636
- credentials_dict = json.loads(cleaned_key, strict=False)
637
- except json.JSONDecodeError as e:
638
- parse_errors.append(f"Stripped parse: {e!s}")
639
-
640
- # Strategy 3: Check if it's double-encoded (JSON string of a JSON string)
641
- if credentials_dict is None:
642
- try:
643
- decoded_once = json.loads(self.service_account_key, strict=False)
644
- if isinstance(decoded_once, str):
645
- credentials_dict = json.loads(decoded_once, strict=False)
646
- else:
647
- credentials_dict = decoded_once
648
- except json.JSONDecodeError as e:
649
- parse_errors.append(f"Double-encoded parse: {e!s}")
650
-
651
- # Strategy 4: Try to fix common issues with newlines in the private_key field
652
- if credentials_dict is None:
653
- try:
654
- # Replace literal \n with actual newlines which is common in pasted JSON
655
- fixed_key = self.service_account_key.replace("\\n", "\n")
656
- credentials_dict = json.loads(fixed_key, strict=False)
657
- except json.JSONDecodeError as e:
658
- parse_errors.append(f"Newline-fixed parse: {e!s}")
659
-
660
- if credentials_dict is None:
661
- error_details = "; ".join(parse_errors)
662
- msg = (
663
- f"Unable to parse service account key JSON. Tried multiple strategies: {error_details}. "
664
- "Please ensure you've copied the entire JSON content from your service account key file. "
665
- "The JSON should start with '{' and contain fields like 'type', 'project_id', 'private_key', etc."
666
- )
667
- raise ValueError(msg)
668
-
669
- # Create Google Drive service with appropriate scopes
670
- # Use drive scope for folder access, file scope is too restrictive for folder verification
671
- credentials = service_account.Credentials.from_service_account_info(
672
- credentials_dict, scopes=["https://www.googleapis.com/auth/drive"]
674
+ # Create Google Drive service with full drive scope (needed for folder operations)
675
+ drive_service, credentials = create_google_drive_service(
676
+ self.service_account_key, scopes=["https://www.googleapis.com/auth/drive"], return_credentials=True
673
677
  )
674
- drive_service = build("drive", "v3", credentials=credentials)
675
678
 
676
679
  # Extract content and format
677
680
  content = self._extract_content_for_upload()
@@ -44,12 +44,20 @@ class ToolCallingAgentComponent(LCToolsAgentComponent):
44
44
  return self.chat_history
45
45
 
46
46
  def create_agent_runnable(self):
47
- messages = [
48
- ("system", "{system_prompt}"),
49
- ("placeholder", "{chat_history}"),
50
- ("human", "{input}"),
51
- ("placeholder", "{agent_scratchpad}"),
52
- ]
47
+ messages = []
48
+
49
+ # Only include system message if system_prompt is provided and not empty
50
+ if hasattr(self, "system_prompt") and self.system_prompt and self.system_prompt.strip():
51
+ messages.append(("system", "{system_prompt}"))
52
+
53
+ messages.extend(
54
+ [
55
+ ("placeholder", "{chat_history}"),
56
+ ("human", "{input}"),
57
+ ("placeholder", "{agent_scratchpad}"),
58
+ ]
59
+ )
60
+
53
61
  prompt = ChatPromptTemplate.from_messages(messages)
54
62
  self.validate_tool_names()
55
63
  try:
@@ -4,8 +4,13 @@ from typing import TYPE_CHECKING, Any, cast
4
4
 
5
5
  import toml # type: ignore[import-untyped]
6
6
 
7
+ from lfx.base.models.unified_models import (
8
+ get_language_model_options,
9
+ get_model_classes,
10
+ update_model_options_in_build_config,
11
+ )
7
12
  from lfx.custom.custom_component.component import Component
8
- from lfx.io import BoolInput, DataFrameInput, HandleInput, MessageTextInput, MultilineInput, Output
13
+ from lfx.io import BoolInput, DataFrameInput, MessageTextInput, ModelInput, MultilineInput, Output, SecretStrInput
9
14
  from lfx.log.logger import logger
10
15
  from lfx.schema.dataframe import DataFrame
11
16
 
@@ -20,13 +25,20 @@ class BatchRunComponent(Component):
20
25
  icon = "List"
21
26
 
22
27
  inputs = [
23
- HandleInput(
28
+ ModelInput(
24
29
  name="model",
25
30
  display_name="Language Model",
26
- info="Connect the 'Language Model' output from your LLM component here.",
27
- input_types=["LanguageModel"],
31
+ info="Select your model provider",
32
+ real_time_refresh=True,
28
33
  required=True,
29
34
  ),
35
+ SecretStrInput(
36
+ name="api_key",
37
+ display_name="API Key",
38
+ info="Model Provider API key",
39
+ real_time_refresh=True,
40
+ advanced=True,
41
+ ),
30
42
  MultilineInput(
31
43
  name="system_message",
32
44
  display_name="Instructions",
@@ -76,6 +88,17 @@ class BatchRunComponent(Component):
76
88
  ),
77
89
  ]
78
90
 
91
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
92
+ """Dynamically update build config with user-filtered model options."""
93
+ return update_model_options_in_build_config(
94
+ component=self,
95
+ build_config=build_config,
96
+ cache_key_prefix="language_model_options",
97
+ get_options_func=get_language_model_options,
98
+ field_name=field_name,
99
+ field_value=field_value,
100
+ )
101
+
79
102
  def _format_row_as_toml(self, row: dict[str, Any]) -> str:
80
103
  """Convert a dictionary (row) into a TOML-formatted string."""
81
104
  formatted_dict = {str(col): {"value": str(val)} for col, val in row.items()}
@@ -111,20 +134,43 @@ class BatchRunComponent(Component):
111
134
  }
112
135
 
113
136
  async def run_batch(self) -> DataFrame:
114
- """Process each row in df[column_name] with the language model asynchronously.
115
-
116
- Returns:
117
- DataFrame: A new DataFrame containing:
118
- - All original columns
119
- - The model's response column (customizable name)
120
- - 'batch_index' column for processing order
121
- - 'metadata' (optional)
122
-
123
- Raises:
124
- ValueError: If the specified column is not found in the DataFrame
125
- TypeError: If the model is not compatible or input types are wrong
126
- """
127
- model: Runnable = self.model
137
+ """Process each row in df[column_name] with the language model asynchronously."""
138
+ # Check if model is already an instance (for testing) or needs to be instantiated
139
+ if isinstance(self.model, list):
140
+ # Extract model configuration
141
+ model_selection = self.model[0]
142
+ model_name = model_selection.get("name")
143
+ provider = model_selection.get("provider")
144
+ metadata = model_selection.get("metadata", {})
145
+
146
+ # Get model class and parameters from metadata
147
+ model_class = get_model_classes().get(metadata.get("model_class"))
148
+ if model_class is None:
149
+ msg = f"No model class defined for {model_name}"
150
+ raise ValueError(msg)
151
+
152
+ api_key_param = metadata.get("api_key_param", "api_key")
153
+ model_name_param = metadata.get("model_name_param", "model")
154
+
155
+ # Get API key from global variables
156
+ from lfx.base.models.unified_models import get_api_key_for_provider
157
+
158
+ api_key = get_api_key_for_provider(self.user_id, provider, self.api_key)
159
+
160
+ if not api_key and provider != "Ollama":
161
+ msg = f"{provider} API key is required. Please configure it globally."
162
+ raise ValueError(msg)
163
+
164
+ # Instantiate the model
165
+ kwargs = {
166
+ model_name_param: model_name,
167
+ api_key_param: api_key,
168
+ }
169
+ model: Runnable = model_class(**kwargs)
170
+ else:
171
+ # Model is already an instance (typically in tests)
172
+ model = self.model
173
+
128
174
  system_msg = self.system_message or ""
129
175
  df: DataFrame = self.df
130
176
  col_name = self.column_name or ""
@@ -4,14 +4,23 @@ import json
4
4
  import re
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
+ from lfx.base.models.unified_models import (
8
+ get_language_model_options,
9
+ get_llm,
10
+ update_model_options_in_build_config,
11
+ )
7
12
  from lfx.custom.custom_component.component import Component
8
- from lfx.io import DataInput, HandleInput, IntInput, MultilineInput, Output
13
+ from lfx.io import DataInput, IntInput, ModelInput, MultilineInput, Output, SecretStrInput
9
14
  from lfx.schema.data import Data
10
15
  from lfx.schema.dataframe import DataFrame
11
16
 
12
17
  if TYPE_CHECKING:
13
18
  from collections.abc import Callable
14
19
 
20
+ # # Compute model options once at module level
21
+ # _MODEL_OPTIONS = get_language_model_options()
22
+ # _PROVIDERS = [provider["provider"] for provider in _MODEL_OPTIONS]
23
+
15
24
 
16
25
  class LambdaFilterComponent(Component):
17
26
  display_name = "Smart Transform"
@@ -29,13 +38,20 @@ class LambdaFilterComponent(Component):
29
38
  is_list=True,
30
39
  required=True,
31
40
  ),
32
- HandleInput(
33
- name="llm",
41
+ ModelInput(
42
+ name="model",
34
43
  display_name="Language Model",
35
- info="Connect the 'Language Model' output from your LLM component here.",
36
- input_types=["LanguageModel"],
44
+ info="Select your model provider",
45
+ real_time_refresh=True,
37
46
  required=True,
38
47
  ),
48
+ SecretStrInput(
49
+ name="api_key",
50
+ display_name="API Key",
51
+ info="Model Provider API key",
52
+ real_time_refresh=True,
53
+ advanced=True,
54
+ ),
39
55
  MultilineInput(
40
56
  name="filter_instruction",
41
57
  display_name="Instructions",
@@ -75,6 +91,17 @@ class LambdaFilterComponent(Component):
75
91
  ),
76
92
  ]
77
93
 
94
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
95
+ """Dynamically update build config with user-filtered model options."""
96
+ return update_model_options_in_build_config(
97
+ component=self,
98
+ build_config=build_config,
99
+ cache_key_prefix="language_model_options",
100
+ get_options_func=get_language_model_options,
101
+ field_name=field_name,
102
+ field_value=field_value,
103
+ )
104
+
78
105
  def get_data_structure(self, data):
79
106
  """Extract the structure of data, replacing values with their types."""
80
107
  if isinstance(data, list):
@@ -129,7 +156,7 @@ class LambdaFilterComponent(Component):
129
156
  dump = json.dumps(data)
130
157
  self.log(str(data))
131
158
 
132
- llm = self.llm
159
+ llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key)
133
160
  instruction = self.filter_instruction
134
161
  sample_size = self.sample_size
135
162
 
@@ -1,7 +1,21 @@
1
1
  from typing import Any
2
2
 
3
+ from lfx.base.models.unified_models import (
4
+ get_language_model_options,
5
+ get_llm,
6
+ update_model_options_in_build_config,
7
+ )
3
8
  from lfx.custom import Component
4
- from lfx.io import BoolInput, HandleInput, MessageInput, MessageTextInput, MultilineInput, Output, TableInput
9
+ from lfx.io import (
10
+ BoolInput,
11
+ MessageInput,
12
+ MessageTextInput,
13
+ ModelInput,
14
+ MultilineInput,
15
+ Output,
16
+ SecretStrInput,
17
+ TableInput,
18
+ )
5
19
  from lfx.schema.message import Message
6
20
  from lfx.schema.table import EditMode
7
21
 
@@ -17,13 +31,20 @@ class SmartRouterComponent(Component):
17
31
  self._matched_category = None
18
32
 
19
33
  inputs = [
20
- HandleInput(
21
- name="llm",
34
+ ModelInput(
35
+ name="model",
22
36
  display_name="Language Model",
23
- info="LLM to use for categorization.",
24
- input_types=["LanguageModel"],
37
+ info="Select your model provider",
38
+ real_time_refresh=True,
25
39
  required=True,
26
40
  ),
41
+ SecretStrInput(
42
+ name="api_key",
43
+ display_name="API Key",
44
+ info="Model Provider API key",
45
+ real_time_refresh=True,
46
+ advanced=True,
47
+ ),
27
48
  MessageTextInput(
28
49
  name="input_text",
29
50
  display_name="Input",
@@ -111,6 +132,17 @@ class SmartRouterComponent(Component):
111
132
 
112
133
  outputs: list[Output] = []
113
134
 
135
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
136
+ """Dynamically update build config with user-filtered model options."""
137
+ return update_model_options_in_build_config(
138
+ component=self,
139
+ build_config=build_config,
140
+ cache_key_prefix="language_model_options",
141
+ get_options_func=get_language_model_options,
142
+ field_name=field_name,
143
+ field_value=field_value,
144
+ )
145
+
114
146
  def update_outputs(self, frontend_node: dict, field_name: str, field_value: Any) -> dict:
115
147
  """Create a dynamic output for each category in the categories table."""
116
148
  if field_name in {"routes", "enable_else_output"}:
@@ -153,7 +185,7 @@ class SmartRouterComponent(Component):
153
185
 
154
186
  # Find the matching category using LLM-based categorization
155
187
  matched_category = None
156
- llm = getattr(self, "llm", None)
188
+ llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key)
157
189
 
158
190
  if llm and categories:
159
191
  # Create prompt for categorization
@@ -315,7 +347,7 @@ class SmartRouterComponent(Component):
315
347
 
316
348
  # Check if any category matches using LLM categorization
317
349
  has_match = False
318
- llm = getattr(self, "llm", None)
350
+ llm = get_llm(model=self.model, user_id=self.user_id, api_key=self.api_key)
319
351
 
320
352
  if llm and categories:
321
353
  try: