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.
- lfx/_assets/component_index.json +1 -1
- lfx/base/agents/agent.py +1 -1
- lfx/base/agents/altk_tool_wrappers.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 +36 -0
- lfx/base/tools/component_tool.py +2 -9
- lfx/cli/commands.py +3 -0
- lfx/cli/run.py +65 -409
- lfx/cli/script_loader.py +13 -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/docling/__init__.py +45 -11
- lfx/components/docling/docling_inline.py +39 -49
- 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/tool_calling.py +14 -6
- lfx/components/llm_operations/batch_run.py +64 -18
- lfx/components/llm_operations/lambda_filter.py +33 -6
- 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 +58 -9
- lfx/components/ollama/ollama_embeddings.py +2 -1
- lfx/components/openai/openai_chat_model.py +1 -1
- 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/custom_component.py +8 -6
- lfx/graph/graph/base.py +4 -1
- lfx/graph/utils.py +64 -18
- lfx/graph/vertex/base.py +4 -1
- lfx/inputs/__init__.py +2 -0
- lfx/inputs/input_mixin.py +54 -0
- lfx/inputs/inputs.py +115 -0
- lfx/interface/initialize/loading.py +42 -12
- lfx/io/__init__.py +2 -0
- lfx/run/__init__.py +5 -0
- lfx/run/base.py +494 -0
- lfx/schema/data.py +1 -1
- lfx/schema/image.py +26 -7
- lfx/schema/message.py +19 -3
- lfx/services/mcp_composer/service.py +7 -1
- lfx/services/settings/base.py +7 -1
- lfx/services/settings/constants.py +2 -0
- lfx/services/storage/local.py +13 -8
- lfx/utils/constants.py +1 -0
- lfx/utils/validate_cloud.py +14 -3
- {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.2.1.dev7.dist-info}/METADATA +5 -2
- {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.2.1.dev7.dist-info}/RECORD +70 -61
- {lfx_nightly-0.2.0.dev41.dist-info → lfx_nightly-0.2.1.dev7.dist-info}/WHEEL +0 -0
- {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
|
|
545
|
-
msg =
|
|
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
|
|
548
|
-
msg =
|
|
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
|
|
551
|
-
msg =
|
|
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
|
-
#
|
|
555
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
"
|
|
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
|
-
|
|
568
|
-
|
|
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,
|
|
593
|
-
s3_url = f"s3://{
|
|
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
|
-
#
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
("
|
|
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,
|
|
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
|
-
|
|
28
|
+
ModelInput(
|
|
24
29
|
name="model",
|
|
25
30
|
display_name="Language Model",
|
|
26
|
-
info="
|
|
27
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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,
|
|
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
|
-
|
|
33
|
-
name="
|
|
41
|
+
ModelInput(
|
|
42
|
+
name="model",
|
|
34
43
|
display_name="Language Model",
|
|
35
|
-
info="
|
|
36
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
21
|
-
name="
|
|
34
|
+
ModelInput(
|
|
35
|
+
name="model",
|
|
22
36
|
display_name="Language Model",
|
|
23
|
-
info="
|
|
24
|
-
|
|
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 =
|
|
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 =
|
|
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:
|