diff --git a/src/backend/base/langflow/base/vectorstores/model.py b/src/backend/base/langflow/base/vectorstores/model.py index c17ded6a5..e7d6317c2 100644 --- a/src/backend/base/langflow/base/vectorstores/model.py +++ b/src/backend/base/langflow/base/vectorstores/model.py @@ -53,15 +53,15 @@ class LCVectorStoreComponent(Component): trace_type = "retriever" inputs = [ + DataInput( + name="ingest_data", + display_name="Ingest Data", + ), MultilineInput( name="search_query", display_name="Search Query", tool_mode=True, ), - DataInput( - name="ingest_data", - display_name="Ingest Data", - ), ] outputs = [ diff --git a/src/backend/base/langflow/components/vectorstores/astradb.py b/src/backend/base/langflow/components/vectorstores/astradb.py index 3a32a29b6..d5a6d1a93 100644 --- a/src/backend/base/langflow/components/vectorstores/astradb.py +++ b/src/backend/base/langflow/components/vectorstores/astradb.py @@ -1,16 +1,15 @@ import os from collections import defaultdict +from dataclasses import dataclass, field -from astrapy import AstraDBAdmin, DataAPIClient -from astrapy.admin import parse_api_endpoint -from langchain_astradb import AstraDBVectorStore +from astrapy import AstraDBAdmin, DataAPIClient, Database +from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store from langflow.helpers import docs_to_data -from langflow.inputs import DictInput, FloatInput, MessageTextInput, NestedDictInput +from langflow.inputs import FloatInput, NestedDictInput from langflow.io import ( BoolInput, - DataInput, DropdownInput, HandleInput, IntInput, @@ -30,21 +29,82 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): _cached_vector_store: AstraDBVectorStore | None = None - base_inputs = LCVectorStoreComponent.inputs - if "search_query" not in [input_.name for input_ in base_inputs]: - base_inputs.append( - MessageTextInput( - name="search_query", - display_name="Search Query", - tool_mode=True, - ) + @dataclass + class NewDatabaseInput: + functionality: str = "create" + fields: dict[str, dict] = field( + default_factory=lambda: { + "data": { + "node": { + "description": "Create a new database in Astra DB.", + "display_name": "Create New Database", + "field_order": ["new_database_name", "cloud_provider", "region"], + "template": { + "new_database_name": StrInput( + name="new_database_name", + display_name="New Database Name", + info="Name of the new database to create in Astra DB.", + required=True, + ), + "cloud_provider": DropdownInput( + name="cloud_provider", + display_name="Cloud Provider", + info="Cloud provider for the new database.", + options=["Amazon Web Services", "Google Cloud Platform", "Microsoft Azure"], + required=True, + ), + "region": DropdownInput( + name="region", + display_name="Region", + info="Region for the new database.", + options=[], + required=True, + ), + }, + }, + } + } ) - if "ingest_data" not in [input_.name for input_ in base_inputs]: - base_inputs.append( - DataInput( - name="ingest_data", - display_name="Ingest Data", - ) + + @dataclass + class NewCollectionInput: + functionality: str = "create" + fields: dict[str, dict] = field( + default_factory=lambda: { + "data": { + "node": { + "description": "Create a new collection in Astra DB.", + "display_name": "Create New Collection", + "field_order": [ + "new_collection_name", + "embedding_generation_provider", + "embedding_generation_model", + ], + "template": { + "new_collection_name": StrInput( + name="new_collection_name", + display_name="New Collection Name", + info="Name of the new collection to create in Astra DB.", + required=True, + ), + "embedding_generation_provider": DropdownInput( + name="embedding_generation_provider", + display_name="Embedding Generation Provider", + info="Provider to use for generating embeddings.", + options=[], + required=True, + ), + "embedding_generation_model": DropdownInput( + name="embedding_generation_model", + display_name="Embedding Generation Model", + info="Model to use for generating embeddings.", + options=[], + required=True, + ), + }, + }, + } + } ) inputs = [ @@ -54,18 +114,37 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): info="Authentication token for accessing Astra DB.", value="ASTRA_DB_APPLICATION_TOKEN", required=True, - advanced=os.getenv("ASTRA_ENHANCED", "false").lower() == "true", real_time_refresh=True, + input_types=[], + ), + StrInput( + name="environment", + display_name="Environment", + info="The environment for the Astra DB API Endpoint.", + advanced=True, + ), + StrInput( + name="api_endpoint", + display_name="API Endpoint", + info="The API endpoint for the Astra DB instance.", + advanced=True, ), DropdownInput( - name="api_endpoint", + name="database_name", display_name="Database", - info="The Astra DB Database to use.", + info="Select a database in Astra DB.", required=True, refresh_button=True, real_time_refresh=True, - options=["Default database"], - value="Default database", + # dialog_inputs=asdict(NewDatabaseInput()), + options=[], + options_metadata=[ + { + "collections": 0, + } + ], + value="", + combobox=True, ), DropdownInput( name="collection_name", @@ -74,15 +153,17 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): required=True, refresh_button=True, real_time_refresh=True, - options=["+ Create new collection"], - value="+ Create new collection", - ), - StrInput( - name="collection_name_new", - display_name="Collection Name", - info="Name of the new collection to create.", - advanced=os.getenv("LANGFLOW_HOST") is not None, - required=os.getenv("LANGFLOW_HOST") is None, + # dialog_inputs=asdict(NewCollectionInput()), + options=[], + options_metadata=[ + { + "provider": None, + "model": None, + "records": 0, + "icon": "", + } + ], + value="", ), StrInput( name="keyspace", @@ -90,21 +171,14 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): info="Optional keyspace within Astra DB to use for the collection.", advanced=True, ), - DropdownInput( - name="embedding_choice", - display_name="Embedding Model or Astra Vectorize", - info="Determines whether to use Astra Vectorize for the collection.", - options=["Embedding Model", "Astra Vectorize"], - real_time_refresh=True, - value="Embedding Model", - ), HandleInput( name="embedding_model", display_name="Embedding Model", input_types=["Embeddings"], info="Allows an embedding model configuration.", + required=True, ), - *base_inputs, + *LCVectorStoreComponent.inputs, IntInput( name="number_of_results", display_name="Number of Search Results", @@ -162,33 +236,190 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): ), ] - def del_fields(self, build_config, field_list): - for field in field_list: - if field in build_config: - del build_config[field] + @classmethod + def map_cloud_providers(cls): + return { + "Amazon Web Services": { + "id": "aws", + "regions": ["us-east-2", "ap-south-1", "eu-west-1"], + }, + "Google Cloud Platform": { + "id": "gcp", + "regions": ["us-east1"], + }, + "Microsoft Azure": { + "id": "azure", + "regions": ["westus3"], + }, + } - return build_config + @classmethod + def create_database_api( + cls, + token: str, + new_database_name: str, + cloud_provider: str, + region: str, + ): + client = DataAPIClient(token=token) - def insert_in_dict(self, build_config, field_name, new_parameters): - # Insert the new key-value pair after the found key - for new_field_name, new_parameter in new_parameters.items(): - # Get all the items as a list of tuples (key, value) - items = list(build_config.items()) + # Get the admin object + admin_client = client.get_admin(token=token) - # Find the index of the key to insert after - idx = len(items) - for i, (key, _) in enumerate(items): - if key == field_name: - idx = i + 1 - break + # Call the create database function + return admin_client.create_database( + name=new_database_name, + cloud_provider=cloud_provider, + region=region, + ) - items.insert(idx, (new_field_name, new_parameter)) + @classmethod + def create_collection_api( + cls, + token: str, + database_name: str, + new_collection_name: str, + dimension: int | None = None, + embedding_generation_provider: str | None = None, + embedding_generation_model: str | None = None, + ): + client = DataAPIClient(token=token) + api_endpoint = cls.get_api_endpoint_static(token=token, database_name=database_name) - # Clear the original dictionary and update with the modified items - build_config.clear() - build_config.update(items) + # Get the database object + database = client.get_database(api_endpoint=api_endpoint, token=token) - return build_config + # Build vectorize options, if needed + vectorize_options = None + if not dimension: + vectorize_options = CollectionVectorServiceOptions( + provider=embedding_generation_provider, + model_name=embedding_generation_model, + authentication=None, + parameters=None, + ) + + # Create the collection + return database.create_collection( + name=new_collection_name, + dimension=dimension, + service=vectorize_options, + ) + + @classmethod + def get_database_list_static(cls, token: str, environment: str | None = None): + client = DataAPIClient(token=token, environment=environment) + + # Get the admin object + admin_client = client.get_admin(token=token) + + # Get the list of databases + db_list = list(admin_client.list_databases()) + + # Generate the api endpoint for each database + return { + db.info.name: { + "api_endpoint": (api_endpoint := f"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com"), + "collections": len( + list( + client.get_database( + api_endpoint=api_endpoint, token=token, keyspace=db.info.keyspace + ).list_collection_names(keyspace=db.info.keyspace) + ) + ), + } + for db in db_list + } + + def get_database_list(self): + return self.get_database_list_static(token=self.token, environment=self.environment) + + @classmethod + def get_api_endpoint_static( + cls, + token: str, + environment: str | None = None, + api_endpoint: str | None = None, + database_name: str | None = None, + ): + # Check if an api endpoint is provided + if api_endpoint: + return api_endpoint + + # Check if the database_name is like a url + if database_name and database_name.startswith("https://"): + return database_name + + # If the database is not set, nothing we can do. + if not database_name: + return None + + # Otherwise, get the URL from the database list + return cls.get_database_list_static(token=token, environment=environment).get(database_name).get("api_endpoint") + + def get_api_endpoint(self): + return self.get_api_endpoint_static( + token=self.token, + environment=self.environment, + api_endpoint=self.api_endpoint, + database_name=self.database_name, + ) + + def get_keyspace(self): + keyspace = self.keyspace + + if keyspace: + return keyspace.strip() + + return None + + def get_database_object(self): + try: + client = DataAPIClient(token=self.token, environment=self.environment) + + return client.get_database( + api_endpoint=self.get_api_endpoint(), + token=self.token, + keyspace=self.get_keyspace(), + ) + except Exception as e: # noqa: BLE001 + self.log(f"Error getting database: {e}") + + return None + + def collection_exists(self): + try: + client = DataAPIClient(token=self.token, environment=self.environment) + database = client.get_database( + api_endpoint=self.get_api_endpoint(), + token=self.token, + keyspace=self.get_keyspace(), + ) + + return self.collection_name in list(database.list_collection_names(keyspace=self.get_keyspace())) + except Exception as e: # noqa: BLE001 + self.log(f"Error getting collection status: {e}") + + return False + + def collection_data(self, collection_name: str, database: Database | None = None): + try: + if not database: + client = DataAPIClient(token=self.token, environment=self.environment) + + database = client.get_database( + api_endpoint=self.get_api_endpoint(), + token=self.token, + keyspace=self.get_keyspace(), + ) + + collection = database.get_collection(collection_name, keyspace=self.get_keyspace()) + + return collection.estimated_document_count() + except Exception as e: # noqa: BLE001 + self.log(f"Error checking collection data: {e}") + + return None def get_vectorize_providers(self): try: @@ -196,7 +427,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): # Get the admin object admin = AstraDBAdmin(token=self.token) - db_admin = admin.get_database_admin(self.get_api_endpoint()) + db_admin = admin.get_database_admin(api_endpoint=self.get_api_endpoint()) # Get the list of embedding providers embedding_providers = db_admin.find_embedding_providers().as_dict() @@ -207,6 +438,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): display_name = provider_data["displayName"] models = [model["name"] for model in provider_data["models"]] + # TODO: https://astra.datastax.com/api/v2/graphql vectorize_providers_mapping[display_name] = [provider_key, models] # Sort the resulting dictionary @@ -216,319 +448,142 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): return {} - def get_database_list(self): - # Get the admin object - db_admin = AstraDBAdmin(token=self.token) - db_list = list(db_admin.list_databases()) - - # Generate the api endpoint for each database - return {db.info.name: f"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com" for db in db_list} - - def get_api_endpoint(self): - # Get the database name (or endpoint) - database = self.api_endpoint - - # If the database is not set, get the first database in the list - if not database or database == "Default database": - database, _ = next(iter(self.get_database_list().items())) - - # If the database is a URL, return it - if database.startswith("https://"): - return database - - # Otherwise, get the URL from the database list - return self.get_database_list().get(database) - - def get_database(self): - try: - client = DataAPIClient(token=self.token) - - return client.get_database( - api_endpoint=self.get_api_endpoint(), - token=self.token, - ) - except Exception as e: # noqa: BLE001 - self.log(f"Error getting database: {e}") - - return None - def _initialize_database_options(self): - if not self.token: - return ["Default database"] try: - databases = ["Default database", *list(self.get_database_list().keys())] + return [ + {"name": name, "collections": info["collections"]} for name, info in self.get_database_list().items() + ] except Exception as e: # noqa: BLE001 self.log(f"Error fetching databases: {e}") - return ["Default database"] - - return databases + return [] def _initialize_collection_options(self): - database = self.get_database() + database = self.get_database_object() if database is None: - return ["+ Create new collection"] + return [] try: - collections = [collection.name for collection in database.list_collections(keyspace=self.keyspace or None)] + collection_list = list(database.list_collections(keyspace=self.get_keyspace())) + + return [ + { + "name": col.name, + "records": self.collection_data(collection_name=col.name, database=database), + "provider": ( + col.options.vector.service.provider + if col.options.vector and col.options.vector.service + else None + ), + "icon": "", + "model": ( + col.options.vector.service.model_name + if col.options.vector and col.options.vector.service + else None + ), + } + for col in collection_list + ] except Exception as e: # noqa: BLE001 self.log(f"Error fetching collections: {e}") - return ["+ Create new collection"] - - return [*collections, "+ Create new collection"] - - def get_collection_choice(self): - collection_name = self.collection_name - if collection_name == "+ Create new collection": - return self.collection_name_new - - return collection_name - - def get_collection_options(self): - # Only get the options if the collection exists - database = self.get_database() - if database is None: - return None - - collection_name = self.get_collection_choice() - - try: - collection = database.get_collection(collection_name, keyspace=self.keyspace or None) - collection_options = collection.options() - except Exception as _: # noqa: BLE001 - return None - - return collection_options.vector + return [] def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None): - # Always attempt to update the database list - if field_name in {"token", "api_endpoint", "collection_name"}: - # Update the database selector - build_config["api_endpoint"]["options"] = self._initialize_database_options() + if not self.token or not self.token.startswith("AstraCS:"): + build_config["database_name"]["info"] = "Add a Valid Token to Select a Database" + else: + build_config["database_name"]["info"] = "Select a Database from Astra DB" - # Set the default API endpoint if not set - if build_config["api_endpoint"]["value"] == "Default database": - build_config["api_endpoint"]["value"] = build_config["api_endpoint"]["options"][0] + # Refresh the database name options + if field_name in ["token", "environment"] or not build_config["database_name"]["options"]: + # Reset the list of collections + build_config["collection_name"]["options"] = [] + build_config["collection_name"]["options_metadata"] = [] + build_config["database_name"]["value"] = [] - # Update the collection selector - build_config["collection_name"]["options"] = self._initialize_collection_options() + # Get the list of databases + database_options = self._initialize_database_options() + build_config["database_name"]["options"] = [db["name"] for db in database_options] + build_config["database_name"]["options_metadata"] = [ + {k: v for k, v in db.items() if k not in ["name"]} for db in database_options + ] - # Update the choice of embedding model based on collection name + # Get list of regions for a given cloud provider + """ + cloud_provider = ( + build_config["database_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"]["cloud_provider"][ + "value" + ] + or "Amazon Web Services" + ) + build_config["database_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"]["region"][ + "options" + ] = self.map_cloud_providers()[cloud_provider]["regions"] + """ + + return build_config + + # Refresh the collection name options + if field_name in ["database_name", "api_endpoint"] or not build_config["collection_name"]["options"]: + build_config["collection_name"]["value"] = None + + # Reset the list of collections + collection_options = self._initialize_collection_options() + build_config["collection_name"]["options"] = [col["name"] for col in collection_options] + build_config["collection_name"]["options_metadata"] = [ + {k: v for k, v in col.items() if k not in ["name"]} for col in collection_options + ] + + return build_config + + # Hide embedding model option if opriona_metadata provider is not null if field_name == "collection_name": - # Detect if it is a new collection - is_new_collection = field_value == "+ Create new collection" + # Find location of the name in the options list + index_of_name = build_config["collection_name"]["options"].index(field_value) + value_of_provider = build_config["collection_name"]["options_metadata"][index_of_name]["provider"] - # Set the advanced and required fields based on the collection choice - build_config["embedding_choice"].update( - { - "advanced": not is_new_collection, - "value": "Embedding Model" if is_new_collection else build_config["embedding_choice"].get("value"), - } - ) - - # Set the advanced field for the embedding model - build_config["embedding_model"]["advanced"] = not is_new_collection - - # Set the advanced and required fields for the new collection name - build_config["collection_name_new"].update( - { - "advanced": not is_new_collection, - "required": is_new_collection, - "value": "" if not is_new_collection else build_config["collection_name_new"].get("value"), - } - ) - - # Get the collection options for the selected collection - collection_options = self.get_collection_options() - - # If the collection options are available (DB exists), show the advanced options - if collection_options: - build_config["embedding_choice"]["advanced"] = True - - if collection_options.service: - # Remove unnecessary fields when a service is set - self.del_fields( - build_config, - [ - "embedding_provider", - "model", - "z_01_model_parameters", - "z_02_api_key_name", - "z_03_provider_api_key", - "z_04_authentication", - ], - ) - - # Update the providers mapping - updates = { - "embedding_model": {"advanced": True}, - "embedding_choice": {"value": "Astra Vectorize"}, - } - else: - # Update the providers mapping - updates = { - "embedding_model": {"advanced": False}, - "embedding_provider": {"advanced": False}, - "embedding_choice": {"value": "Embedding Model"}, - } - - # Apply updates to the build_config - for key, value in updates.items(): - build_config[key].update(value) - - elif field_name == "embedding_choice": - if field_value == "Astra Vectorize": + if value_of_provider: build_config["embedding_model"]["advanced"] = True - - # Update the providers mapping - vectorize_providers = self.get_vectorize_providers() - - new_parameter = DropdownInput( - name="embedding_provider", - display_name="Embedding Provider", - options=vectorize_providers.keys(), - value="", - required=True, - real_time_refresh=True, - ).to_dict() - - self.insert_in_dict(build_config, "embedding_choice", {"embedding_provider": new_parameter}) + build_config["embedding_model"]["required"] = False else: build_config["embedding_model"]["advanced"] = False + build_config["embedding_model"]["required"] = True - self.del_fields( - build_config, - [ - "embedding_provider", - "model", - "z_01_model_parameters", - "z_02_api_key_name", - "z_03_provider_api_key", - "z_04_authentication", - ], - ) + # For the final step, get the list of vectorize providers + """ + vectorize_providers = self.get_vectorize_providers() + if not vectorize_providers: + return build_config - elif field_name == "embedding_provider": - self.del_fields( - build_config, - ["model", "z_01_model_parameters", "z_02_api_key_name", "z_03_provider_api_key", "z_04_authentication"], - ) + # Allow the user to see the embedding provider options + provider_options = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][ + "embedding_generation_provider" + ]["options"] + if not provider_options: + # If the collection is set, allow user to see embedding options + build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][ + "embedding_generation_provider" + ]["options"] = ["Bring your own", "Nvidia", *[key for key in vectorize_providers if key != "Nvidia"]] - # Update the providers mapping - vectorize_providers = self.get_vectorize_providers() - model_options = vectorize_providers[field_value][1] + # And allow the user to see the models based on a selected provider + model_options = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][ + "embedding_generation_model" + ]["options"] + if not model_options: + embedding_provider = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][ + "embedding_generation_provider" + ]["value"] - new_parameter = DropdownInput( - name="model", - display_name="Model", - info="The embedding model to use for the selected provider. Each provider has a different set of " - "models available (full list at " - "https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\n\n" - f"{', '.join(model_options)}", - options=model_options, - value=None, - required=True, - real_time_refresh=True, - ).to_dict() - - self.insert_in_dict(build_config, "embedding_provider", {"model": new_parameter}) - - elif field_name == "model": - self.del_fields( - build_config, - ["z_01_model_parameters", "z_02_api_key_name", "z_03_provider_api_key", "z_04_authentication"], - ) - - new_parameter_1 = DictInput( - name="z_01_model_parameters", - display_name="Model Parameters", - list=True, - ).to_dict() - - new_parameter_2 = MessageTextInput( - name="z_02_api_key_name", - display_name="API Key Name", - info="The name of the embeddings provider API key stored on Astra. " - "If set, it will override the 'ProviderKey' in the authentication parameters.", - ).to_dict() - - new_parameter_3 = SecretStrInput( - load_from_db=False, - name="z_03_provider_api_key", - display_name="Provider API Key", - info="An alternative to the Astra Authentication that passes an API key for the provider " - "with each request to Astra DB. " - "This may be used when Vectorize is configured for the collection, " - "but no corresponding provider secret is stored within Astra's key management system.", - ).to_dict() - - new_parameter_4 = DictInput( - name="z_04_authentication", - display_name="Authentication Parameters", - list=True, - ).to_dict() - - self.insert_in_dict( - build_config, - "model", - { - "z_01_model_parameters": new_parameter_1, - "z_02_api_key_name": new_parameter_2, - "z_03_provider_api_key": new_parameter_3, - "z_04_authentication": new_parameter_4, - }, - ) + build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][ + "embedding_generation_model" + ]["options"] = vectorize_providers.get(embedding_provider, [[], []])[1] + """ return build_config - def build_vectorize_options(self, **kwargs): - for attribute in [ - "embedding_provider", - "model", - "z_01_model_parameters", - "z_02_api_key_name", - "z_03_provider_api_key", - "z_04_authentication", - ]: - if not hasattr(self, attribute): - setattr(self, attribute, None) - - # Fetch values from kwargs if any self.* attributes are None - provider_mapping = self.get_vectorize_providers() - provider_value = provider_mapping.get(self.embedding_provider, [None])[0] or kwargs.get("embedding_provider") - model_name = self.model or kwargs.get("model") - authentication = {**(self.z_04_authentication or {}), **kwargs.get("z_04_authentication", {})} - parameters = self.z_01_model_parameters or kwargs.get("z_01_model_parameters", {}) - - # Set the API key name if provided - api_key_name = self.z_02_api_key_name or kwargs.get("z_02_api_key_name") - provider_key = self.z_03_provider_api_key or kwargs.get("z_03_provider_api_key") - if api_key_name: - authentication["providerKey"] = api_key_name - if authentication: - provider_key = None - authentication["providerKey"] = authentication["providerKey"].split(".")[0] - - # Set authentication and parameters to None if no values are provided - if not authentication: - authentication = None - if not parameters: - parameters = None - - return { - # must match astrapy.info.CollectionVectorServiceOptions - "collection_vector_service_options": { - "provider": provider_value, - "modelName": model_name, - "authentication": authentication, - "parameters": parameters, - }, - "collection_embedding_api_key": provider_key, - } - @check_cached_vector_store - def build_vector_store(self, vectorize_options=None): + def build_vector_store(self): try: from langchain_astradb import AstraDBVectorStore except ImportError as e: @@ -538,36 +593,9 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): ) raise ImportError(msg) from e - # Initialize parameters based on the collection name - is_new_collection = self.get_collection_options() is None - - # Get the embedding model - embedding_params = {"embedding": self.embedding_model} if self.embedding_choice == "Embedding Model" else {} - - # Use the embedding model if the choice is set to "Embedding Model" - if self.embedding_choice == "Astra Vectorize" and is_new_collection: - from astrapy.info import CollectionVectorServiceOptions - - # Build the vectorize options dictionary - dict_options = vectorize_options or self.build_vectorize_options( - embedding_provider=getattr(self, "embedding_provider", None) or None, - model=getattr(self, "model", None) or None, - z_01_model_parameters=getattr(self, "z_01_model_parameters", None) or None, - z_02_api_key_name=getattr(self, "z_02_api_key_name", None) or None, - z_03_provider_api_key=getattr(self, "z_03_provider_api_key", None) or None, - z_04_authentication=getattr(self, "z_04_authentication", {}) or {}, - ) - - # Set the embedding dictionary - embedding_params = { - "collection_vector_service_options": CollectionVectorServiceOptions.from_dict( - dict_options.get("collection_vector_service_options") - ), - "collection_embedding_api_key": dict_options.get("collection_embedding_api_key"), - } - - # Get the running environment for Langflow - environment = parse_api_endpoint(self.get_api_endpoint()).environment if self.get_api_endpoint() else None + # Get the embedding model and additional params + embedding_params = {"embedding": self.embedding_model} if self.embedding_model else {} + additional_params = self.astradb_vectorstore_kwargs or {} # Get Langflow version and platform information __version__ = get_version_info()["version"] @@ -577,9 +605,16 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): # Bundle up the auto-detect parameters autodetect_params = { - # TODO: May want to expose this option - "autodetect_collection": not is_new_collection, - "content_field": self.content_field or None, + "autodetect_collection": self.collection_exists(), # TODO: May want to expose this option + "content_field": ( + self.content_field + if self.content_field and embedding_params + else ( + "page_content" + if embedding_params and self.collection_data(collection_name=self.collection_name) == 0 + else None + ) + ), "ignore_invalid_documents": self.ignore_invalid_documents, } @@ -589,15 +624,15 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): # Astra DB Authentication Parameters token=self.token, api_endpoint=self.get_api_endpoint(), - namespace=self.keyspace or None, - collection_name=self.get_collection_choice(), - environment=environment, + namespace=self.get_keyspace(), + collection_name=self.collection_name, + environment=self.environment, # Astra DB Usage Tracking Parameters ext_callers=[(f"{langflow_prefix}langflow", __version__)], # Astra DB Vector Store Parameters - **autodetect_params or {}, - **embedding_params or {}, - **self.astradb_vectorstore_kwargs or {}, + **autodetect_params, + **embedding_params, + **additional_params, ) except Exception as e: msg = f"Error initializing AstraDBVectorStore: {e}" @@ -619,8 +654,8 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): if documents and self.deletion_field: self.log(f"Deleting documents where {self.deletion_field}") try: - database = self.get_database() - collection = database.get_collection(self.get_collection_choice(), keyspace=self.keyspace or None) + database = self.get_database_object() + collection = database.get_collection(self.collection_name, keyspace=self.get_keyspace()) delete_values = list({doc.metadata[self.deletion_field] for doc in documents}) self.log(f"Deleting documents where {self.deletion_field} matches {delete_values}.") collection.delete_many({f"metadata.{self.deletion_field}": {"$in": delete_values}}) @@ -639,11 +674,12 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): self.log("No documents to add to the Vector Store.") def _map_search_type(self) -> str: - if self.search_type == "Similarity with score threshold": - return "similarity_score_threshold" - if self.search_type == "MMR (Max Marginal Relevance)": - return "mmr" - return "similarity" + search_type_mapping = { + "Similarity with score threshold": "similarity_score_threshold", + "MMR (Max Marginal Relevance)": "mmr", + } + + return search_type_mapping.get(self.search_type, "similarity") def _build_search_args(self): query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None @@ -700,10 +736,12 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): data = docs_to_data(docs) self.log(f"Converted documents to data: {len(data)}") self.status = data + return data def get_retriever_kwargs(self): search_args = self._build_search_args() + return { "search_type": self._map_search_type(), "search_kwargs": search_args, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json index 0e9c1235a..4956b8b47 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json @@ -7,7 +7,7 @@ "data": { "sourceHandle": { "dataType": "ParseData", - "id": "ParseData-1lOsZ", + "id": "ParseData-UH7tX", "name": "text", "output_types": [ "Message" @@ -15,7 +15,7 @@ }, "targetHandle": { "fieldName": "context", - "id": "Prompt-o2SL4", + "id": "Prompt-sF0Mn", "inputTypes": [ "Message", "Text" @@ -23,11 +23,11 @@ "type": "str" } }, - "id": "reactflow__edge-ParseData-1lOsZ{œdataTypeœ:œParseDataœ,œidœ:œParseData-1lOsZœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-o2SL4{œfieldNameœ:œcontextœ,œidœ:œPrompt-o2SL4œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "ParseData-1lOsZ", - "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-1lOsZœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-o2SL4", - "targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-o2SL4œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ParseData-UH7tX{œdataTypeœ:œParseDataœ,œidœ:œParseData-UH7tXœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-sF0Mn{œfieldNameœ:œcontextœ,œidœ:œPrompt-sF0Mnœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "ParseData-UH7tX", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-UH7tXœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-sF0Mn", + "targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-sF0Mnœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -35,7 +35,7 @@ "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-o2SL4", + "id": "Prompt-sF0Mn", "name": "prompt", "output_types": [ "Message" @@ -43,18 +43,18 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "OpenAIModel-yjcwf", + "id": "OpenAIModel-RkTA1", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Prompt-o2SL4{œdataTypeœ:œPromptœ,œidœ:œPrompt-o2SL4œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-yjcwf{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-yjcwfœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "Prompt-o2SL4", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-o2SL4œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "OpenAIModel-yjcwf", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-yjcwfœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-Prompt-sF0Mn{œdataTypeœ:œPromptœ,œidœ:œPrompt-sF0Mnœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-RkTA1{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-RkTA1œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "Prompt-sF0Mn", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-sF0Mnœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "OpenAIModel-RkTA1", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-RkTA1œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -62,7 +62,7 @@ "data": { "sourceHandle": { "dataType": "OpenAIModel", - "id": "OpenAIModel-yjcwf", + "id": "OpenAIModel-RkTA1", "name": "text_output", "output_types": [ "Message" @@ -70,18 +70,18 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-p7okC", + "id": "ChatOutput-0SENC", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-OpenAIModel-yjcwf{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-yjcwfœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-p7okC{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-p7okCœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "OpenAIModel-yjcwf", - "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-yjcwfœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-p7okC", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-p7okCœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-OpenAIModel-RkTA1{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-RkTA1œ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-0SENC{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-0SENCœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "OpenAIModel-RkTA1", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-RkTA1œ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-0SENC", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-0SENCœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -89,7 +89,7 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-uBsVA", + "id": "ChatInput-J1eN6", "name": "message", "output_types": [ "Message" @@ -97,7 +97,7 @@ }, "targetHandle": { "fieldName": "question", - "id": "Prompt-o2SL4", + "id": "Prompt-sF0Mn", "inputTypes": [ "Message", "Text" @@ -105,11 +105,11 @@ "type": "str" } }, - "id": "reactflow__edge-ChatInput-uBsVA{œdataTypeœ:œChatInputœ,œidœ:œChatInput-uBsVAœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-o2SL4{œfieldNameœ:œquestionœ,œidœ:œPrompt-o2SL4œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", - "source": "ChatInput-uBsVA", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-uBsVAœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-o2SL4", - "targetHandle": "{œfieldNameœ: œquestionœ, œidœ: œPrompt-o2SL4œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" + "id": "reactflow__edge-ChatInput-J1eN6{œdataTypeœ:œChatInputœ,œidœ:œChatInput-J1eN6œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-sF0Mn{œfieldNameœ:œquestionœ,œidœ:œPrompt-sF0Mnœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}", + "source": "ChatInput-J1eN6", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-J1eN6œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-sF0Mn", + "targetHandle": "{œfieldNameœ: œquestionœ, œidœ: œPrompt-sF0Mnœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -117,7 +117,7 @@ "data": { "sourceHandle": { "dataType": "File", - "id": "File-jTMwG", + "id": "File-6bzAm", "name": "data", "output_types": [ "Data" @@ -125,24 +125,24 @@ }, "targetHandle": { "fieldName": "data_inputs", - "id": "SplitText-bByhd", + "id": "SplitText-Sayuz", "inputTypes": [ "Data" ], "type": "other" } }, - "id": "reactflow__edge-File-jTMwG{œdataTypeœ:œFileœ,œidœ:œFile-jTMwGœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-SplitText-bByhd{œfieldNameœ:œdata_inputsœ,œidœ:œSplitText-bByhdœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "File-jTMwG", - "sourceHandle": "{œdataTypeœ: œFileœ, œidœ: œFile-jTMwGœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", - "target": "SplitText-bByhd", - "targetHandle": "{œfieldNameœ: œdata_inputsœ, œidœ: œSplitText-bByhdœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + "id": "reactflow__edge-File-6bzAm{œdataTypeœ:œFileœ,œidœ:œFile-6bzAmœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-SplitText-Sayuz{œfieldNameœ:œdata_inputsœ,œidœ:œSplitText-Sayuzœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "File-6bzAm", + "sourceHandle": "{œdataTypeœ: œFileœ, œidœ: œFile-6bzAmœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", + "target": "SplitText-Sayuz", + "targetHandle": "{œfieldNameœ: œdata_inputsœ, œidœ: œSplitText-Sayuzœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" }, { "data": { "sourceHandle": { "dataType": "OpenAIEmbeddings", - "id": "OpenAIEmbeddings-b229k", + "id": "OpenAIEmbeddings-2DOEZ", "name": "embeddings", "output_types": [ "Embeddings" @@ -150,24 +150,24 @@ }, "targetHandle": { "fieldName": "embedding_model", - "id": "AstraDB-0959q", + "id": "AstraDB-O0rq2", "inputTypes": [ "Embeddings" ], "type": "other" } }, - "id": "reactflow__edge-OpenAIEmbeddings-b229k{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-b229kœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDB-0959q{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDB-0959qœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}", - "source": "OpenAIEmbeddings-b229k", - "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-b229kœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}", - "target": "AstraDB-0959q", - "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDB-0959qœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}" + "id": "xy-edge__OpenAIEmbeddings-2DOEZ{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-2DOEZœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDB-O0rq2{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDB-O0rq2œ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}", + "source": "OpenAIEmbeddings-2DOEZ", + "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-2DOEZœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}", + "target": "AstraDB-O0rq2", + "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDB-O0rq2œ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}" }, { "data": { "sourceHandle": { "dataType": "SplitText", - "id": "SplitText-bByhd", + "id": "SplitText-Sayuz", "name": "chunks", "output_types": [ "Data" @@ -175,49 +175,24 @@ }, "targetHandle": { "fieldName": "ingest_data", - "id": "AstraDB-0959q", + "id": "AstraDB-O0rq2", "inputTypes": [ "Data" ], "type": "other" } }, - "id": "reactflow__edge-SplitText-bByhd{œdataTypeœ:œSplitTextœ,œidœ:œSplitText-bByhdœ,œnameœ:œchunksœ,œoutput_typesœ:[œDataœ]}-AstraDB-0959q{œfieldNameœ:œingest_dataœ,œidœ:œAstraDB-0959qœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "SplitText-bByhd", - "sourceHandle": "{œdataTypeœ: œSplitTextœ, œidœ: œSplitText-bByhdœ, œnameœ: œchunksœ, œoutput_typesœ: [œDataœ]}", - "target": "AstraDB-0959q", - "targetHandle": "{œfieldNameœ: œingest_dataœ, œidœ: œAstraDB-0959qœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" - }, - { - "data": { - "sourceHandle": { - "dataType": "OpenAIEmbeddings", - "id": "OpenAIEmbeddings-qVkNT", - "name": "embeddings", - "output_types": [ - "Embeddings" - ] - }, - "targetHandle": { - "fieldName": "embedding_model", - "id": "AstraDB-3Vxgl", - "inputTypes": [ - "Embeddings" - ], - "type": "other" - } - }, - "id": "reactflow__edge-OpenAIEmbeddings-qVkNT{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-qVkNTœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDB-3Vxgl{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDB-3Vxglœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}", - "source": "OpenAIEmbeddings-qVkNT", - "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-qVkNTœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}", - "target": "AstraDB-3Vxgl", - "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDB-3Vxglœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}" + "id": "xy-edge__SplitText-Sayuz{œdataTypeœ:œSplitTextœ,œidœ:œSplitText-Sayuzœ,œnameœ:œchunksœ,œoutput_typesœ:[œDataœ]}-AstraDB-O0rq2{œfieldNameœ:œingest_dataœ,œidœ:œAstraDB-O0rq2œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "SplitText-Sayuz", + "sourceHandle": "{œdataTypeœ: œSplitTextœ, œidœ: œSplitText-Sayuzœ, œnameœ: œchunksœ, œoutput_typesœ: [œDataœ]}", + "target": "AstraDB-O0rq2", + "targetHandle": "{œfieldNameœ: œingest_dataœ, œidœ: œAstraDB-O0rq2œ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" }, { "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-uBsVA", + "id": "ChatInput-J1eN6", "name": "message", "output_types": [ "Message" @@ -225,24 +200,24 @@ }, "targetHandle": { "fieldName": "search_query", - "id": "AstraDB-3Vxgl", + "id": "AstraDB-KN0Mp", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-ChatInput-uBsVA{œdataTypeœ:œChatInputœ,œidœ:œChatInput-uBsVAœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-AstraDB-3Vxgl{œfieldNameœ:œsearch_queryœ,œidœ:œAstraDB-3Vxglœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", - "source": "ChatInput-uBsVA", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-uBsVAœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "AstraDB-3Vxgl", - "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œAstraDB-3Vxglœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "id": "xy-edge__ChatInput-J1eN6{œdataTypeœ:œChatInputœ,œidœ:œChatInput-J1eN6œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-AstraDB-KN0Mp{œfieldNameœ:œsearch_queryœ,œidœ:œAstraDB-KN0Mpœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ChatInput-J1eN6", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-J1eN6œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "AstraDB-KN0Mp", + "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œAstraDB-KN0Mpœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "data": { "sourceHandle": { "dataType": "AstraDB", - "id": "AstraDB-3Vxgl", + "id": "AstraDB-KN0Mp", "name": "search_results", "output_types": [ "Data" @@ -250,18 +225,43 @@ }, "targetHandle": { "fieldName": "data", - "id": "ParseData-1lOsZ", + "id": "ParseData-UH7tX", "inputTypes": [ "Data" ], "type": "other" } }, - "id": "reactflow__edge-AstraDB-3Vxgl{œdataTypeœ:œAstraDBœ,œidœ:œAstraDB-3Vxglœ,œnameœ:œsearch_resultsœ,œoutput_typesœ:[œDataœ]}-ParseData-1lOsZ{œfieldNameœ:œdataœ,œidœ:œParseData-1lOsZœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", - "source": "AstraDB-3Vxgl", - "sourceHandle": "{œdataTypeœ: œAstraDBœ, œidœ: œAstraDB-3Vxglœ, œnameœ: œsearch_resultsœ, œoutput_typesœ: [œDataœ]}", - "target": "ParseData-1lOsZ", - "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-1lOsZœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + "id": "xy-edge__AstraDB-KN0Mp{œdataTypeœ:œAstraDBœ,œidœ:œAstraDB-KN0Mpœ,œnameœ:œsearch_resultsœ,œoutput_typesœ:[œDataœ]}-ParseData-UH7tX{œfieldNameœ:œdataœ,œidœ:œParseData-UH7tXœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "AstraDB-KN0Mp", + "sourceHandle": "{œdataTypeœ: œAstraDBœ, œidœ: œAstraDB-KN0Mpœ, œnameœ: œsearch_resultsœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-UH7tX", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-UH7tXœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + }, + { + "data": { + "sourceHandle": { + "dataType": "OpenAIEmbeddings", + "id": "OpenAIEmbeddings-bgQ2k", + "name": "embeddings", + "output_types": [ + "Embeddings" + ] + }, + "targetHandle": { + "fieldName": "embedding_model", + "id": "AstraDB-KN0Mp", + "inputTypes": [ + "Embeddings" + ], + "type": "other" + } + }, + "id": "xy-edge__OpenAIEmbeddings-bgQ2k{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-bgQ2kœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDB-KN0Mp{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDB-KN0Mpœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}", + "source": "OpenAIEmbeddings-bgQ2k", + "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-bgQ2kœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}", + "target": "AstraDB-KN0Mp", + "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDB-KN0Mpœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}" } ], "nodes": [ @@ -269,7 +269,7 @@ "data": { "description": "Get chat inputs from the Playground.", "display_name": "Chat Input", - "id": "ChatInput-uBsVA", + "id": "ChatInput-J1eN6", "node": { "base_classes": [ "Message" @@ -532,7 +532,11 @@ }, "dragging": false, "height": 234, - "id": "ChatInput-uBsVA", + "id": "ChatInput-J1eN6", + "measured": { + "height": 234, + "width": 320 + }, "position": { "x": 743.9745420290319, "y": 463.6977510207854 @@ -549,7 +553,7 @@ "data": { "description": "Convert Data into plain text following a specified template.", "display_name": "Parse Data", - "id": "ParseData-1lOsZ", + "id": "ParseData-UH7tX", "node": { "base_classes": [ "Message" @@ -681,7 +685,11 @@ }, "dragging": false, "height": 350, - "id": "ParseData-1lOsZ", + "id": "ParseData-UH7tX", + "measured": { + "height": 350, + "width": 320 + }, "position": { "x": 1606.0595305373527, "y": 751.4473696960695 @@ -698,7 +706,7 @@ "data": { "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", - "id": "Prompt-o2SL4", + "id": "Prompt-sF0Mn", "node": { "base_classes": [ "Message" @@ -856,7 +864,11 @@ }, "dragging": false, "height": 433, - "id": "Prompt-o2SL4", + "id": "Prompt-sF0Mn", + "measured": { + "height": 433, + "width": 320 + }, "position": { "x": 1977.9097981422992, "y": 640.5656416923846 @@ -873,7 +885,7 @@ "data": { "description": "Split text into chunks based on specified criteria.", "display_name": "Split Text", - "id": "SplitText-bByhd", + "id": "SplitText-Sayuz", "node": { "base_classes": [ "Data" @@ -1018,7 +1030,11 @@ }, "dragging": false, "height": 475, - "id": "SplitText-bByhd", + "id": "SplitText-Sayuz", + "measured": { + "height": 475, + "width": 320 + }, "position": { "x": 1683.4543896546102, "y": 1350.7871623588553 @@ -1033,7 +1049,7 @@ }, { "data": { - "id": "note-7AuKD", + "id": "note-zVbcF", "node": { "description": "## 🐕 2. Retriever Flow\n\nThis flow answers your questions with contextual data retrieved from your vector database.\n\nOpen the **Playground** and ask, \n\n```\nWhat is this document about?\n```\n", "display_name": "", @@ -1046,7 +1062,11 @@ }, "dragging": false, "height": 324, - "id": "note-7AuKD", + "id": "note-zVbcF", + "measured": { + "height": 324, + "width": 325 + }, "position": { "x": 374.388314931542, "y": 486.18094072679895 @@ -1066,7 +1086,7 @@ }, { "data": { - "id": "note-5KK6I", + "id": "note-v1fqj", "node": { "description": "## 📖 README\n\nLoad your data into a vector database with the 📚 **Load Data** flow, and then use your data as chat context with the 🐕 **Retriever** flow.\n\n**🚨 Add your OpenAI API key as a global variable to easily add it to all of the OpenAI components in this flow.** \n\n**Quick start**\n1. Run the 📚 **Load Data** flow.\n2. Run the 🐕 **Retriever** flow.\n\n**Next steps** \n\n- Experiment by changing the prompt and the loaded data to see how the bot's responses change. \n\nFor more info, see the [Langflow docs](https://docs.langflow.org/starter-projects-vector-store-rag).", "display_name": "Read Me", @@ -1079,7 +1099,11 @@ }, "dragging": false, "height": 527, - "id": "note-5KK6I", + "id": "note-v1fqj", + "measured": { + "height": 527, + "width": 325 + }, "position": { "x": 94.28986613312418, "y": 907.6428043837066 @@ -1099,7 +1123,7 @@ }, { "data": { - "id": "OpenAIModel-yjcwf", + "id": "OpenAIModel-RkTA1", "node": { "base_classes": [ "LanguageModel", @@ -1396,7 +1420,11 @@ }, "dragging": false, "height": 674, - "id": "OpenAIModel-yjcwf", + "id": "OpenAIModel-RkTA1", + "measured": { + "height": 674, + "width": 320 + }, "position": { "x": 2360.1432368563187, "y": 571.6712358167248 @@ -1413,7 +1441,7 @@ "data": { "description": "Display a chat message in the Playground.", "display_name": "Chat Output", - "id": "ChatOutput-p7okC", + "id": "ChatOutput-0SENC", "node": { "base_classes": [ "Message" @@ -1674,7 +1702,11 @@ }, "dragging": false, "height": 234, - "id": "ChatOutput-p7okC", + "id": "ChatOutput-0SENC", + "measured": { + "height": 234, + "width": 320 + }, "position": { "x": 2734.385670401691, "y": 810.6079786425926 @@ -1689,7 +1721,7 @@ }, { "data": { - "id": "OpenAIEmbeddings-qVkNT", + "id": "OpenAIEmbeddings-bgQ2k", "node": { "base_classes": [ "Embeddings" @@ -2168,7 +2200,11 @@ }, "dragging": false, "height": 320, - "id": "OpenAIEmbeddings-qVkNT", + "id": "OpenAIEmbeddings-bgQ2k", + "measured": { + "height": 320, + "width": 320 + }, "position": { "x": 825.435626932521, "y": 739.6327999745448 @@ -2183,7 +2219,7 @@ }, { "data": { - "id": "note-dI9D3", + "id": "note-qal12", "node": { "description": "## 📚 1. Load Data Flow\n\nRun this first! Load data from a local file and embed it into the vector database.\n\nSelect a Database and a Collection, or create new ones. \n\nClick ▶️ **Run component** on the **Astra DB** component to load your data.\n\n* If you're using OSS Langflow, add your Astra DB Application Token to the Astra DB component.\n\n#### Next steps:\n Experiment by changing the prompt and the contextual data to see how the retrieval flow's responses change.", "display_name": "", @@ -2196,7 +2232,11 @@ }, "dragging": false, "height": 50, - "id": "note-dI9D3", + "id": "note-qal12", + "measured": { + "height": 50, + "width": 325 + }, "position": { "x": 955.3277857006676, "y": 1552.171191793604 @@ -2215,7 +2255,7 @@ }, { "data": { - "id": "OpenAIEmbeddings-b229k", + "id": "OpenAIEmbeddings-2DOEZ", "node": { "base_classes": [ "Embeddings" @@ -2694,7 +2734,11 @@ }, "dragging": false, "height": 320, - "id": "OpenAIEmbeddings-b229k", + "id": "OpenAIEmbeddings-2DOEZ", + "measured": { + "height": 320, + "width": 320 + }, "position": { "x": 1690.9220896443658, "y": 1866.483269483266 @@ -2709,7 +2753,7 @@ }, { "data": { - "id": "File-jTMwG", + "id": "File-6bzAm", "node": { "base_classes": [ "Data" @@ -2883,7 +2927,7 @@ "bz2", "gz" ], - "file_path": "", + "file_path": "4c491583-732f-4c0a-9fe2-77753034170c/2025-01-07_11-26-26_1706.03762v7.pdf", "info": "Supported file extensions: txt, md, mdx, csv, json, yaml, yml, xml, html, htm, pdf, docx, py, sh, sql, js, ts, tsx; optionally bundled in file extensions: zip, tar, tgz, bz2, gz", "list": false, "name": "path", @@ -2934,7 +2978,11 @@ }, "dragging": false, "height": 367, - "id": "File-jTMwG", + "id": "File-6bzAm", + "measured": { + "height": 367, + "width": 320 + }, "position": { "x": 1318.9043936921921, "y": 1484.0151419511485 @@ -2949,7 +2997,7 @@ }, { "data": { - "id": "note-V6ieF", + "id": "note-Y8Yfz", "node": { "description": "### 💡 Add your OpenAI API key here 👇", "display_name": "", @@ -2962,7 +3010,11 @@ }, "dragging": false, "height": 324, - "id": "note-V6ieF", + "id": "note-Y8Yfz", + "measured": { + "height": 324, + "width": 324 + }, "position": { "x": 1692.2322233423606, "y": 1821.9077961087607 @@ -2977,7 +3029,7 @@ }, { "data": { - "id": "note-2HhzZ", + "id": "note-f6zyT", "node": { "description": "### 💡 Add your OpenAI API key here 👇", "display_name": "", @@ -2990,7 +3042,11 @@ }, "dragging": false, "height": 324, - "id": "note-2HhzZ", + "id": "note-f6zyT", + "measured": { + "height": 324, + "width": 324 + }, "position": { "x": 824.1003268813427, "y": 698.6951695764802 @@ -3005,7 +3061,7 @@ }, { "data": { - "id": "note-eiHeg", + "id": "note-AmpWa", "node": { "description": "### 💡 Add your OpenAI API key here 👇", "display_name": "", @@ -3018,7 +3074,11 @@ }, "dragging": false, "height": 324, - "id": "note-eiHeg", + "id": "note-AmpWa", + "measured": { + "height": 324, + "width": 324 + }, "position": { "x": 2350.297636215281, "y": 525.0687902842766 @@ -3033,10 +3093,11 @@ }, { "data": { - "id": "AstraDB-0959q", + "id": "AstraDB-O0rq2", "node": { "base_classes": [ - "Data" + "Data", + "DataFrame" ], "beta": false, "conditional_paths": [], @@ -3047,19 +3108,20 @@ "edited": false, "field_order": [ "token", + "environment", "api_endpoint", + "database_name", "collection_name", - "collection_name_new", "keyspace", - "embedding_choice", "embedding_model", - "search_query", "ingest_data", + "search_query", "number_of_results", "search_type", "search_score_threshold", "advanced_search_filter", "content_field", + "deletion_field", "ignore_invalid_documents", "astradb_vectorstore_kwargs" ], @@ -3067,6 +3129,7 @@ "icon": "AstraDB", "legacy": false, "metadata": {}, + "minimized": false, "output_types": [], "outputs": [ { @@ -3076,9 +3139,9 @@ "method": "search_documents", "name": "search_results", "required_inputs": [ - "api_endpoint", "collection_name", - "collection_name_new", + "database_name", + "embedding_model", "token" ], "selected": "Data", @@ -3111,37 +3174,36 @@ "dynamic": false, "info": "Optional dictionary of filters to apply to the search query.", "list": false, + "list_add_label": "Add More", "name": "advanced_search_filter", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_input": true, "trace_as_metadata": true, "type": "NestedDict", "value": {} }, "api_endpoint": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "display_name": "Database", + "_input_type": "StrInput", + "advanced": true, + "display_name": "API Endpoint", "dynamic": false, - "info": "The Astra DB Database to use.", + "info": "The API endpoint for the Astra DB instance.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, "name": "api_endpoint", - "options": [ - "Default database" - ], "placeholder": "", - "real_time_refresh": true, - "refresh_button": true, - "required": true, + "required": false, "show": true, "title_case": false, "tool_mode": false, "trace_as_metadata": true, "type": "str", - "value": "Default database" + "value": "" }, "astradb_vectorstore_kwargs": { "_input_type": "NestedDictInput", @@ -3150,11 +3212,13 @@ "dynamic": false, "info": "Optional dictionary of additional parameters for the AstraDBVectorStore.", "list": false, + "list_add_label": "Add More", "name": "astradb_vectorstore_kwargs", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_input": true, "trace_as_metadata": true, "type": "NestedDict", @@ -3176,19 +3240,19 @@ "show": true, "title_case": false, "type": "code", - "value": "import os\nfrom collections import defaultdict\n\nfrom astrapy import AstraDBAdmin, DataAPIClient\nfrom astrapy.admin import parse_api_endpoint\nfrom langchain_astradb import AstraDBVectorStore\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import DictInput, FloatInput, MessageTextInput, NestedDictInput\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\nfrom langflow.utils.version import get_version_info\n\n\nclass AstraDBVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.datastax.com/en/langflow/astra-components.html\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n _cached_vector_store: AstraDBVectorStore | None = None\n\n base_inputs = LCVectorStoreComponent.inputs\n if \"search_query\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n tool_mode=True,\n )\n )\n if \"ingest_data\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n DataInput(\n name=\"ingest_data\",\n display_name=\"Ingest Data\",\n )\n )\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"api_endpoint\",\n display_name=\"Database\",\n info=\"The Astra DB Database to use.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"Default database\"],\n value=\"Default database\",\n ),\n DropdownInput(\n name=\"collection_name\",\n display_name=\"Collection\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"+ Create new collection\"],\n value=\"+ Create new collection\",\n ),\n StrInput(\n name=\"collection_name_new\",\n display_name=\"Collection Name\",\n info=\"Name of the new collection to create.\",\n advanced=os.getenv(\"LANGFLOW_HOST\") is not None,\n required=os.getenv(\"LANGFLOW_HOST\") is None,\n ),\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"embedding_choice\",\n display_name=\"Embedding Model or Astra Vectorize\",\n info=\"Determines whether to use Astra Vectorize for the collection.\",\n options=[\"Embedding Model\", \"Astra Vectorize\"],\n real_time_refresh=True,\n value=\"Embedding Model\",\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n *base_inputs,\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n StrInput(\n name=\"deletion_field\",\n display_name=\"Deletion Based On Field\",\n info=\"When this parameter is provided, documents in the target collection with \"\n \"metadata field values matching the input metadata field value will be deleted \"\n \"before new data is loaded.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n ]\n\n def del_fields(self, build_config, field_list):\n for field in field_list:\n if field in build_config:\n del build_config[field]\n\n return build_config\n\n def insert_in_dict(self, build_config, field_name, new_parameters):\n # Insert the new key-value pair after the found key\n for new_field_name, new_parameter in new_parameters.items():\n # Get all the items as a list of tuples (key, value)\n items = list(build_config.items())\n\n # Find the index of the key to insert after\n idx = len(items)\n for i, (key, _) in enumerate(items):\n if key == field_name:\n idx = i + 1\n break\n\n items.insert(idx, (new_field_name, new_parameter))\n\n # Clear the original dictionary and update with the modified items\n build_config.clear()\n build_config.update(items)\n\n return build_config\n\n def get_vectorize_providers(self):\n try:\n self.log(\"Dynamically updating list of Vectorize providers.\")\n\n # Get the admin object\n admin = AstraDBAdmin(token=self.token)\n db_admin = admin.get_database_admin(self.get_api_endpoint())\n\n # Get the list of embedding providers\n embedding_providers = db_admin.find_embedding_providers().as_dict()\n\n vectorize_providers_mapping = {}\n # Map the provider display name to the provider key and models\n for provider_key, provider_data in embedding_providers[\"embeddingProviders\"].items():\n display_name = provider_data[\"displayName\"]\n models = [model[\"name\"] for model in provider_data[\"models\"]]\n\n vectorize_providers_mapping[display_name] = [provider_key, models]\n\n # Sort the resulting dictionary\n return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching Vectorize providers: {e}\")\n\n return {}\n\n def get_database_list(self):\n # Get the admin object\n db_admin = AstraDBAdmin(token=self.token)\n db_list = list(db_admin.list_databases())\n\n # Generate the api endpoint for each database\n return {db.info.name: f\"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com\" for db in db_list}\n\n def get_api_endpoint(self):\n # Get the database name (or endpoint)\n database = self.api_endpoint\n\n # If the database is not set, get the first database in the list\n if not database or database == \"Default database\":\n database, _ = next(iter(self.get_database_list().items()))\n\n # If the database is a URL, return it\n if database.startswith(\"https://\"):\n return database\n\n # Otherwise, get the URL from the database list\n return self.get_database_list().get(database)\n\n def get_database(self):\n try:\n client = DataAPIClient(token=self.token)\n\n return client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n )\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting database: {e}\")\n\n return None\n\n def _initialize_database_options(self):\n if not self.token:\n return [\"Default database\"]\n try:\n databases = [\"Default database\", *list(self.get_database_list().keys())]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching databases: {e}\")\n\n return [\"Default database\"]\n\n return databases\n\n def _initialize_collection_options(self):\n database = self.get_database()\n if database is None:\n return [\"+ Create new collection\"]\n\n try:\n collections = [collection.name for collection in database.list_collections(keyspace=self.keyspace or None)]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching collections: {e}\")\n\n return [\"+ Create new collection\"]\n\n return [*collections, \"+ Create new collection\"]\n\n def get_collection_choice(self):\n collection_name = self.collection_name\n if collection_name == \"+ Create new collection\":\n return self.collection_name_new\n\n return collection_name\n\n def get_collection_options(self):\n # Only get the options if the collection exists\n database = self.get_database()\n if database is None:\n return None\n\n collection_name = self.get_collection_choice()\n\n try:\n collection = database.get_collection(collection_name, keyspace=self.keyspace or None)\n collection_options = collection.options()\n except Exception as _: # noqa: BLE001\n return None\n\n return collection_options.vector\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n # Always attempt to update the database list\n if field_name in {\"token\", \"api_endpoint\", \"collection_name\"}:\n # Update the database selector\n build_config[\"api_endpoint\"][\"options\"] = self._initialize_database_options()\n\n # Set the default API endpoint if not set\n if build_config[\"api_endpoint\"][\"value\"] == \"Default database\":\n build_config[\"api_endpoint\"][\"value\"] = build_config[\"api_endpoint\"][\"options\"][0]\n\n # Update the collection selector\n build_config[\"collection_name\"][\"options\"] = self._initialize_collection_options()\n\n # Update the choice of embedding model based on collection name\n if field_name == \"collection_name\":\n # Detect if it is a new collection\n is_new_collection = field_value == \"+ Create new collection\"\n\n # Set the advanced and required fields based on the collection choice\n build_config[\"embedding_choice\"].update(\n {\n \"advanced\": not is_new_collection,\n \"value\": \"Embedding Model\" if is_new_collection else build_config[\"embedding_choice\"].get(\"value\"),\n }\n )\n\n # Set the advanced field for the embedding model\n build_config[\"embedding_model\"][\"advanced\"] = not is_new_collection\n\n # Set the advanced and required fields for the new collection name\n build_config[\"collection_name_new\"].update(\n {\n \"advanced\": not is_new_collection,\n \"required\": is_new_collection,\n \"value\": \"\" if not is_new_collection else build_config[\"collection_name_new\"].get(\"value\"),\n }\n )\n\n # Get the collection options for the selected collection\n collection_options = self.get_collection_options()\n\n # If the collection options are available (DB exists), show the advanced options\n if collection_options:\n build_config[\"embedding_choice\"][\"advanced\"] = True\n\n if collection_options.service:\n # Remove unnecessary fields when a service is set\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": True},\n \"embedding_choice\": {\"value\": \"Astra Vectorize\"},\n }\n else:\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": False},\n \"embedding_provider\": {\"advanced\": False},\n \"embedding_choice\": {\"value\": \"Embedding Model\"},\n }\n\n # Apply updates to the build_config\n for key, value in updates.items():\n build_config[key].update(value)\n\n elif field_name == \"embedding_choice\":\n if field_value == \"Astra Vectorize\":\n build_config[\"embedding_model\"][\"advanced\"] = True\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n\n new_parameter = DropdownInput(\n name=\"embedding_provider\",\n display_name=\"Embedding Provider\",\n options=vectorize_providers.keys(),\n value=\"\",\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_choice\", {\"embedding_provider\": new_parameter})\n else:\n build_config[\"embedding_model\"][\"advanced\"] = False\n\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n elif field_name == \"embedding_provider\":\n self.del_fields(\n build_config,\n [\"model\", \"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n model_options = vectorize_providers[field_value][1]\n\n new_parameter = DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n info=\"The embedding model to use for the selected provider. Each provider has a different set of \"\n \"models available (full list at \"\n \"https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\\n\\n\"\n f\"{', '.join(model_options)}\",\n options=model_options,\n value=None,\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_provider\", {\"model\": new_parameter})\n\n elif field_name == \"model\":\n self.del_fields(\n build_config,\n [\"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n new_parameter_1 = DictInput(\n name=\"z_01_model_parameters\",\n display_name=\"Model Parameters\",\n list=True,\n ).to_dict()\n\n new_parameter_2 = MessageTextInput(\n name=\"z_02_api_key_name\",\n display_name=\"API Key Name\",\n info=\"The name of the embeddings provider API key stored on Astra. \"\n \"If set, it will override the 'ProviderKey' in the authentication parameters.\",\n ).to_dict()\n\n new_parameter_3 = SecretStrInput(\n load_from_db=False,\n name=\"z_03_provider_api_key\",\n display_name=\"Provider API Key\",\n info=\"An alternative to the Astra Authentication that passes an API key for the provider \"\n \"with each request to Astra DB. \"\n \"This may be used when Vectorize is configured for the collection, \"\n \"but no corresponding provider secret is stored within Astra's key management system.\",\n ).to_dict()\n\n new_parameter_4 = DictInput(\n name=\"z_04_authentication\",\n display_name=\"Authentication Parameters\",\n list=True,\n ).to_dict()\n\n self.insert_in_dict(\n build_config,\n \"model\",\n {\n \"z_01_model_parameters\": new_parameter_1,\n \"z_02_api_key_name\": new_parameter_2,\n \"z_03_provider_api_key\": new_parameter_3,\n \"z_04_authentication\": new_parameter_4,\n },\n )\n\n return build_config\n\n def build_vectorize_options(self, **kwargs):\n for attribute in [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ]:\n if not hasattr(self, attribute):\n setattr(self, attribute, None)\n\n # Fetch values from kwargs if any self.* attributes are None\n provider_mapping = self.get_vectorize_providers()\n provider_value = provider_mapping.get(self.embedding_provider, [None])[0] or kwargs.get(\"embedding_provider\")\n model_name = self.model or kwargs.get(\"model\")\n authentication = {**(self.z_04_authentication or {}), **kwargs.get(\"z_04_authentication\", {})}\n parameters = self.z_01_model_parameters or kwargs.get(\"z_01_model_parameters\", {})\n\n # Set the API key name if provided\n api_key_name = self.z_02_api_key_name or kwargs.get(\"z_02_api_key_name\")\n provider_key = self.z_03_provider_api_key or kwargs.get(\"z_03_provider_api_key\")\n if api_key_name:\n authentication[\"providerKey\"] = api_key_name\n if authentication:\n provider_key = None\n authentication[\"providerKey\"] = authentication[\"providerKey\"].split(\".\")[0]\n\n # Set authentication and parameters to None if no values are provided\n if not authentication:\n authentication = None\n if not parameters:\n parameters = None\n\n return {\n # must match astrapy.info.CollectionVectorServiceOptions\n \"collection_vector_service_options\": {\n \"provider\": provider_value,\n \"modelName\": model_name,\n \"authentication\": authentication,\n \"parameters\": parameters,\n },\n \"collection_embedding_api_key\": provider_key,\n }\n\n @check_cached_vector_store\n def build_vector_store(self, vectorize_options=None):\n try:\n from langchain_astradb import AstraDBVectorStore\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Initialize parameters based on the collection name\n is_new_collection = self.get_collection_options() is None\n\n # Get the embedding model\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_choice == \"Embedding Model\" else {}\n\n # Use the embedding model if the choice is set to \"Embedding Model\"\n if self.embedding_choice == \"Astra Vectorize\" and is_new_collection:\n from astrapy.info import CollectionVectorServiceOptions\n\n # Build the vectorize options dictionary\n dict_options = vectorize_options or self.build_vectorize_options(\n embedding_provider=getattr(self, \"embedding_provider\", None) or None,\n model=getattr(self, \"model\", None) or None,\n z_01_model_parameters=getattr(self, \"z_01_model_parameters\", None) or None,\n z_02_api_key_name=getattr(self, \"z_02_api_key_name\", None) or None,\n z_03_provider_api_key=getattr(self, \"z_03_provider_api_key\", None) or None,\n z_04_authentication=getattr(self, \"z_04_authentication\", {}) or {},\n )\n\n # Set the embedding dictionary\n embedding_params = {\n \"collection_vector_service_options\": CollectionVectorServiceOptions.from_dict(\n dict_options.get(\"collection_vector_service_options\")\n ),\n \"collection_embedding_api_key\": dict_options.get(\"collection_embedding_api_key\"),\n }\n\n # Get the running environment for Langflow\n environment = parse_api_endpoint(self.get_api_endpoint()).environment if self.get_api_endpoint() else None\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n if os.getenv(\"LANGFLOW_HOST\") is not None:\n langflow_prefix = \"ds-\"\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n # TODO: May want to expose this option\n \"autodetect_collection\": not is_new_collection,\n \"content_field\": self.content_field or None,\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.keyspace or None,\n collection_name=self.get_collection_choice(),\n environment=environment,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params or {},\n **embedding_params or {},\n **self.astradb_vectorstore_kwargs or {},\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents and self.deletion_field:\n self.log(f\"Deleting documents where {self.deletion_field}\")\n try:\n database = self.get_database()\n collection = database.get_collection(self.get_collection_choice(), keyspace=self.keyspace or None)\n delete_values = list({doc.metadata[self.deletion_field] for doc in documents})\n self.log(f\"Deleting documents where {self.deletion_field} matches {delete_values}.\")\n collection.delete_many({f\"metadata.{self.deletion_field}\": {\"$in\": delete_values}})\n except Exception as e:\n msg = f\"Error deleting documents from AstraDBVectorStore based on '{self.deletion_field}': {e}\"\n raise ValueError(msg) from e\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n return \"similarity\"\n\n def _build_search_args(self):\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n try:\n search_args = self._build_search_args()\n except Exception as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except Exception as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n" + "value": "import os\nfrom collections import defaultdict\nfrom dataclasses import dataclass, field\n\nfrom astrapy import AstraDBAdmin, DataAPIClient, Database\nfrom langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import FloatInput, NestedDictInput\nfrom langflow.io import (\n BoolInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\nfrom langflow.utils.version import get_version_info\n\n\nclass AstraDBVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.datastax.com/en/langflow/astra-components.html\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n _cached_vector_store: AstraDBVectorStore | None = None\n\n @dataclass\n class NewDatabaseInput:\n functionality: str = \"create\"\n fields: dict[str, dict] = field(\n default_factory=lambda: {\n \"data\": {\n \"node\": {\n \"description\": \"Create a new database in Astra DB.\",\n \"display_name\": \"Create New Database\",\n \"field_order\": [\"new_database_name\", \"cloud_provider\", \"region\"],\n \"template\": {\n \"new_database_name\": StrInput(\n name=\"new_database_name\",\n display_name=\"New Database Name\",\n info=\"Name of the new database to create in Astra DB.\",\n required=True,\n ),\n \"cloud_provider\": DropdownInput(\n name=\"cloud_provider\",\n display_name=\"Cloud Provider\",\n info=\"Cloud provider for the new database.\",\n options=[\"Amazon Web Services\", \"Google Cloud Platform\", \"Microsoft Azure\"],\n required=True,\n ),\n \"region\": DropdownInput(\n name=\"region\",\n display_name=\"Region\",\n info=\"Region for the new database.\",\n options=[],\n required=True,\n ),\n },\n },\n }\n }\n )\n\n @dataclass\n class NewCollectionInput:\n functionality: str = \"create\"\n fields: dict[str, dict] = field(\n default_factory=lambda: {\n \"data\": {\n \"node\": {\n \"description\": \"Create a new collection in Astra DB.\",\n \"display_name\": \"Create New Collection\",\n \"field_order\": [\n \"new_collection_name\",\n \"embedding_generation_provider\",\n \"embedding_generation_model\",\n ],\n \"template\": {\n \"new_collection_name\": StrInput(\n name=\"new_collection_name\",\n display_name=\"New Collection Name\",\n info=\"Name of the new collection to create in Astra DB.\",\n required=True,\n ),\n \"embedding_generation_provider\": DropdownInput(\n name=\"embedding_generation_provider\",\n display_name=\"Embedding Generation Provider\",\n info=\"Provider to use for generating embeddings.\",\n options=[],\n required=True,\n ),\n \"embedding_generation_model\": DropdownInput(\n name=\"embedding_generation_model\",\n display_name=\"Embedding Generation Model\",\n info=\"Model to use for generating embeddings.\",\n options=[],\n required=True,\n ),\n },\n },\n }\n }\n )\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n real_time_refresh=True,\n input_types=[],\n ),\n StrInput(\n name=\"environment\",\n display_name=\"Environment\",\n info=\"The environment for the Astra DB API Endpoint.\",\n advanced=True,\n ),\n StrInput(\n name=\"api_endpoint\",\n display_name=\"API Endpoint\",\n info=\"The API endpoint for the Astra DB instance.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"database_name\",\n display_name=\"Database\",\n info=\"Select a database in Astra DB.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n # dialog_inputs=asdict(NewDatabaseInput()),\n options=[],\n options_metadata=[\n {\n \"collections\": 0,\n }\n ],\n value=\"\",\n combobox=True,\n ),\n DropdownInput(\n name=\"collection_name\",\n display_name=\"Collection\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n # dialog_inputs=asdict(NewCollectionInput()),\n options=[],\n options_metadata=[\n {\n \"provider\": None,\n \"model\": None,\n \"records\": 0,\n \"icon\": \"\",\n }\n ],\n value=\"\",\n ),\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n required=True,\n ),\n *LCVectorStoreComponent.inputs,\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n StrInput(\n name=\"deletion_field\",\n display_name=\"Deletion Based On Field\",\n info=\"When this parameter is provided, documents in the target collection with \"\n \"metadata field values matching the input metadata field value will be deleted \"\n \"before new data is loaded.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n ]\n\n @classmethod\n def map_cloud_providers(cls):\n return {\n \"Amazon Web Services\": {\n \"id\": \"aws\",\n \"regions\": [\"us-east-2\", \"ap-south-1\", \"eu-west-1\"],\n },\n \"Google Cloud Platform\": {\n \"id\": \"gcp\",\n \"regions\": [\"us-east1\"],\n },\n \"Microsoft Azure\": {\n \"id\": \"azure\",\n \"regions\": [\"westus3\"],\n },\n }\n\n @classmethod\n def create_database_api(\n cls,\n token: str,\n new_database_name: str,\n cloud_provider: str,\n region: str,\n ):\n client = DataAPIClient(token=token)\n\n # Get the admin object\n admin_client = client.get_admin(token=token)\n\n # Call the create database function\n return admin_client.create_database(\n name=new_database_name,\n cloud_provider=cloud_provider,\n region=region,\n )\n\n @classmethod\n def create_collection_api(\n cls,\n token: str,\n database_name: str,\n new_collection_name: str,\n dimension: int | None = None,\n embedding_generation_provider: str | None = None,\n embedding_generation_model: str | None = None,\n ):\n client = DataAPIClient(token=token)\n api_endpoint = cls.get_api_endpoint_static(token=token, database_name=database_name)\n\n # Get the database object\n database = client.get_database(api_endpoint=api_endpoint, token=token)\n\n # Build vectorize options, if needed\n vectorize_options = None\n if not dimension:\n vectorize_options = CollectionVectorServiceOptions(\n provider=embedding_generation_provider,\n model_name=embedding_generation_model,\n authentication=None,\n parameters=None,\n )\n\n # Create the collection\n return database.create_collection(\n name=new_collection_name,\n dimension=dimension,\n service=vectorize_options,\n )\n\n @classmethod\n def get_database_list_static(cls, token: str, environment: str | None = None):\n client = DataAPIClient(token=token, environment=environment)\n\n # Get the admin object\n admin_client = client.get_admin(token=token)\n\n # Get the list of databases\n db_list = list(admin_client.list_databases())\n\n # Generate the api endpoint for each database\n return {\n db.info.name: {\n \"api_endpoint\": (api_endpoint := f\"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com\"),\n \"collections\": len(\n list(\n client.get_database(\n api_endpoint=api_endpoint, token=token, keyspace=db.info.keyspace\n ).list_collection_names(keyspace=db.info.keyspace)\n )\n ),\n }\n for db in db_list\n }\n\n def get_database_list(self):\n return self.get_database_list_static(token=self.token, environment=self.environment)\n\n @classmethod\n def get_api_endpoint_static(\n cls,\n token: str,\n environment: str | None = None,\n api_endpoint: str | None = None,\n database_name: str | None = None,\n ):\n # Check if an api endpoint is provided\n if api_endpoint:\n return api_endpoint\n\n # Check if the database_name is like a url\n if database_name and database_name.startswith(\"https://\"):\n return database_name\n\n # If the database is not set, nothing we can do.\n if not database_name:\n return None\n\n # Otherwise, get the URL from the database list\n return cls.get_database_list_static(token=token, environment=environment).get(database_name).get(\"api_endpoint\")\n\n def get_api_endpoint(self):\n return self.get_api_endpoint_static(\n token=self.token,\n environment=self.environment,\n api_endpoint=self.api_endpoint,\n database_name=self.database_name,\n )\n\n def get_keyspace(self):\n keyspace = self.keyspace\n\n if keyspace:\n return keyspace.strip()\n\n return None\n\n def get_database_object(self):\n try:\n client = DataAPIClient(token=self.token, environment=self.environment)\n\n return client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n keyspace=self.get_keyspace(),\n )\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting database: {e}\")\n\n return None\n\n def collection_exists(self):\n try:\n client = DataAPIClient(token=self.token, environment=self.environment)\n database = client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n keyspace=self.get_keyspace(),\n )\n\n return self.collection_name in list(database.list_collection_names(keyspace=self.get_keyspace()))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting collection status: {e}\")\n\n return False\n\n def collection_data(self, collection_name: str, database: Database | None = None):\n try:\n if not database:\n client = DataAPIClient(token=self.token, environment=self.environment)\n\n database = client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n keyspace=self.get_keyspace(),\n )\n\n collection = database.get_collection(collection_name, keyspace=self.get_keyspace())\n\n return collection.estimated_document_count()\n except Exception as e: # noqa: BLE001\n self.log(f\"Error checking collection data: {e}\")\n\n return None\n\n def get_vectorize_providers(self):\n try:\n self.log(\"Dynamically updating list of Vectorize providers.\")\n\n # Get the admin object\n admin = AstraDBAdmin(token=self.token)\n db_admin = admin.get_database_admin(api_endpoint=self.get_api_endpoint())\n\n # Get the list of embedding providers\n embedding_providers = db_admin.find_embedding_providers().as_dict()\n\n vectorize_providers_mapping = {}\n # Map the provider display name to the provider key and models\n for provider_key, provider_data in embedding_providers[\"embeddingProviders\"].items():\n display_name = provider_data[\"displayName\"]\n models = [model[\"name\"] for model in provider_data[\"models\"]]\n\n # TODO: https://astra.datastax.com/api/v2/graphql\n vectorize_providers_mapping[display_name] = [provider_key, models]\n\n # Sort the resulting dictionary\n return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching Vectorize providers: {e}\")\n\n return {}\n\n def _initialize_database_options(self):\n try:\n return [\n {\"name\": name, \"collections\": info[\"collections\"]} for name, info in self.get_database_list().items()\n ]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching databases: {e}\")\n\n return []\n\n def _initialize_collection_options(self):\n database = self.get_database_object()\n if database is None:\n return []\n\n try:\n collection_list = list(database.list_collections(keyspace=self.get_keyspace()))\n\n return [\n {\n \"name\": col.name,\n \"records\": self.collection_data(collection_name=col.name, database=database),\n \"provider\": (\n col.options.vector.service.provider\n if col.options.vector and col.options.vector.service\n else None\n ),\n \"icon\": \"\",\n \"model\": (\n col.options.vector.service.model_name\n if col.options.vector and col.options.vector.service\n else None\n ),\n }\n for col in collection_list\n ]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching collections: {e}\")\n\n return []\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n if not self.token or not self.token.startswith(\"AstraCS:\"):\n build_config[\"database_name\"][\"info\"] = \"Add a Valid Token to Select a Database\"\n else:\n build_config[\"database_name\"][\"info\"] = \"Select a Database from Astra DB\"\n\n # Refresh the database name options\n if field_name in [\"token\", \"environment\"] or not build_config[\"database_name\"][\"options\"]:\n # Reset the list of collections\n build_config[\"collection_name\"][\"options\"] = []\n build_config[\"collection_name\"][\"options_metadata\"] = []\n build_config[\"database_name\"][\"value\"] = []\n\n # Get the list of databases\n database_options = self._initialize_database_options()\n build_config[\"database_name\"][\"options\"] = [db[\"name\"] for db in database_options]\n build_config[\"database_name\"][\"options_metadata\"] = [\n {k: v for k, v in db.items() if k not in [\"name\"]} for db in database_options\n ]\n\n # Get list of regions for a given cloud provider\n \"\"\"\n cloud_provider = (\n build_config[\"database_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\"cloud_provider\"][\n \"value\"\n ]\n or \"Amazon Web Services\"\n )\n build_config[\"database_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\"region\"][\n \"options\"\n ] = self.map_cloud_providers()[cloud_provider][\"regions\"]\n \"\"\"\n\n return build_config\n\n # Refresh the collection name options\n if field_name in [\"database_name\", \"api_endpoint\"] or not build_config[\"collection_name\"][\"options\"]:\n build_config[\"collection_name\"][\"value\"] = None\n\n # Reset the list of collections\n collection_options = self._initialize_collection_options()\n build_config[\"collection_name\"][\"options\"] = [col[\"name\"] for col in collection_options]\n build_config[\"collection_name\"][\"options_metadata\"] = [\n {k: v for k, v in col.items() if k not in [\"name\"]} for col in collection_options\n ]\n\n return build_config\n\n # Hide embedding model option if opriona_metadata provider is not null\n if field_name == \"collection_name\":\n # Find location of the name in the options list\n index_of_name = build_config[\"collection_name\"][\"options\"].index(field_value)\n value_of_provider = build_config[\"collection_name\"][\"options_metadata\"][index_of_name][\"provider\"]\n\n if value_of_provider:\n build_config[\"embedding_model\"][\"advanced\"] = True\n build_config[\"embedding_model\"][\"required\"] = False\n else:\n build_config[\"embedding_model\"][\"advanced\"] = False\n build_config[\"embedding_model\"][\"required\"] = True\n\n # For the final step, get the list of vectorize providers\n \"\"\"\n vectorize_providers = self.get_vectorize_providers()\n if not vectorize_providers:\n return build_config\n\n # Allow the user to see the embedding provider options\n provider_options = build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_provider\"\n ][\"options\"]\n if not provider_options:\n # If the collection is set, allow user to see embedding options\n build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_provider\"\n ][\"options\"] = [\"Bring your own\", \"Nvidia\", *[key for key in vectorize_providers if key != \"Nvidia\"]]\n\n # And allow the user to see the models based on a selected provider\n model_options = build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_model\"\n ][\"options\"]\n if not model_options:\n embedding_provider = build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_provider\"\n ][\"value\"]\n\n build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_model\"\n ][\"options\"] = vectorize_providers.get(embedding_provider, [[], []])[1]\n \"\"\"\n\n return build_config\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBVectorStore\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Get the embedding model and additional params\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_model else {}\n additional_params = self.astradb_vectorstore_kwargs or {}\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n if os.getenv(\"LANGFLOW_HOST\") is not None:\n langflow_prefix = \"ds-\"\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n \"autodetect_collection\": self.collection_exists(), # TODO: May want to expose this option\n \"content_field\": (\n self.content_field\n if self.content_field and embedding_params\n else (\n \"page_content\"\n if embedding_params and self.collection_data(collection_name=self.collection_name) == 0\n else None\n )\n ),\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.get_keyspace(),\n collection_name=self.collection_name,\n environment=self.environment,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params,\n **embedding_params,\n **additional_params,\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents and self.deletion_field:\n self.log(f\"Deleting documents where {self.deletion_field}\")\n try:\n database = self.get_database_object()\n collection = database.get_collection(self.collection_name, keyspace=self.get_keyspace())\n delete_values = list({doc.metadata[self.deletion_field] for doc in documents})\n self.log(f\"Deleting documents where {self.deletion_field} matches {delete_values}.\")\n collection.delete_many({f\"metadata.{self.deletion_field}\": {\"$in\": delete_values}})\n except Exception as e:\n msg = f\"Error deleting documents from AstraDBVectorStore based on '{self.deletion_field}': {e}\"\n raise ValueError(msg) from e\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n search_type_mapping = {\n \"Similarity with score threshold\": \"similarity_score_threshold\",\n \"MMR (Max Marginal Relevance)\": \"mmr\",\n }\n\n return search_type_mapping.get(self.search_type, \"similarity\")\n\n def _build_search_args(self):\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n try:\n search_args = self._build_search_args()\n except Exception as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except Exception as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n" }, "collection_name": { "_input_type": "DropdownInput", "advanced": false, "combobox": false, + "dialog_inputs": {}, "display_name": "Collection", "dynamic": false, "info": "The name of the collection within Astra DB where the vectors will be stored.", "name": "collection_name", - "options": [ - "+ Create new collection" - ], + "options": [], + "options_metadata": [], "placeholder": "", "real_time_refresh": true, "refresh_button": true, @@ -3198,25 +3262,6 @@ "tool_mode": false, "trace_as_metadata": true, "type": "str", - "value": "+ Create new collection" - }, - "collection_name_new": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Collection Name", - "dynamic": false, - "info": "Name of the new collection to create.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "collection_name_new", - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", "value": "" }, "content_field": { @@ -3226,16 +3271,40 @@ "dynamic": false, "info": "Field to use as the text content field for the vector store.", "list": false, + "list_add_label": "Add More", "load_from_db": false, "name": "content_field", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "str", "value": "" }, + "database_name": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": true, + "dialog_inputs": {}, + "display_name": "Database", + "dynamic": false, + "info": "Select a database in Astra DB.", + "name": "database_name", + "options": [], + "options_metadata": [], + "placeholder": "", + "real_time_refresh": true, + "refresh_button": true, + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": [] + }, "deletion_field": { "_input_type": "StrInput", "advanced": true, @@ -3243,6 +3312,7 @@ "dynamic": false, "info": "When this parameter is provided, documents in the target collection with metadata field values matching the input metadata field value will be deleted before new data is loaded.", "list": false, + "list_add_label": "Add More", "load_from_db": false, "name": "deletion_field", "placeholder": "", @@ -3254,28 +3324,6 @@ "type": "str", "value": "" }, - "embedding_choice": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "display_name": "Embedding Model or Astra Vectorize", - "dynamic": false, - "info": "Determines whether to use Astra Vectorize for the collection.", - "name": "embedding_choice", - "options": [ - "Embedding Model", - "Astra Vectorize" - ], - "placeholder": "", - "real_time_refresh": true, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "Embedding Model" - }, "embedding_model": { "_input_type": "HandleInput", "advanced": false, @@ -3286,15 +3334,35 @@ "Embeddings" ], "list": false, + "list_add_label": "Add More", "name": "embedding_model", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_metadata": true, "type": "other", "value": "" }, + "environment": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "Environment", + "dynamic": false, + "info": "The environment for the Astra DB API Endpoint.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "environment", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "ignore_invalid_documents": { "_input_type": "BoolInput", "advanced": true, @@ -3302,11 +3370,13 @@ "dynamic": false, "info": "Boolean flag to determine whether to ignore invalid documents at runtime.", "list": false, + "list_add_label": "Add More", "name": "ignore_invalid_documents", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "bool", "value": false @@ -3321,6 +3391,7 @@ "Data" ], "list": false, + "list_add_label": "Add More", "name": "ingest_data", "placeholder": "", "required": false, @@ -3339,12 +3410,14 @@ "dynamic": false, "info": "Optional keyspace within Astra DB to use for the collection.", "list": false, + "list_add_label": "Add More", "load_from_db": false, "name": "keyspace", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "str", "value": "" @@ -3356,11 +3429,13 @@ "dynamic": false, "info": "Number of search results to return.", "list": false, + "list_add_label": "Add More", "name": "number_of_results", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "int", "value": 4 @@ -3375,6 +3450,7 @@ "Message" ], "list": false, + "list_add_label": "Add More", "load_from_db": false, "multiline": true, "name": "search_query", @@ -3395,11 +3471,13 @@ "dynamic": false, "info": "Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')", "list": false, + "list_add_label": "Add More", "name": "search_score_threshold", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "float", "value": 0 @@ -3408,6 +3486,7 @@ "_input_type": "DropdownInput", "advanced": true, "combobox": false, + "dialog_inputs": {}, "display_name": "Search Type", "dynamic": false, "info": "Search type to use", @@ -3417,6 +3496,7 @@ "Similarity with score threshold", "MMR (Max Marginal Relevance)" ], + "options_metadata": [], "placeholder": "", "required": false, "show": true, @@ -3432,9 +3512,7 @@ "display_name": "Astra DB Application Token", "dynamic": false, "info": "Authentication token for accessing Astra DB.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "token", "password": true, @@ -3449,29 +3527,29 @@ }, "tool_mode": false }, + "showNode": true, "type": "AstraDB" }, "dragging": false, - "height": 763, - "id": "AstraDB-0959q", - "position": { - "x": 2048.063432724921, - "y": 1382.2469953470875 + "id": "AstraDB-O0rq2", + "measured": { + "height": 614, + "width": 320 }, - "positionAbsolute": { - "x": 2048.063432724921, - "y": 1382.2469953470875 + "position": { + "x": 2053.72248912555, + "y": 1456.27751087445 }, "selected": false, - "type": "genericNode", - "width": 320 + "type": "genericNode" }, { "data": { - "id": "AstraDB-3Vxgl", + "id": "AstraDB-KN0Mp", "node": { "base_classes": [ - "Data" + "Data", + "DataFrame" ], "beta": false, "conditional_paths": [], @@ -3482,19 +3560,20 @@ "edited": false, "field_order": [ "token", + "environment", "api_endpoint", + "database_name", "collection_name", - "collection_name_new", "keyspace", - "embedding_choice", "embedding_model", - "search_query", "ingest_data", + "search_query", "number_of_results", "search_type", "search_score_threshold", "advanced_search_filter", "content_field", + "deletion_field", "ignore_invalid_documents", "astradb_vectorstore_kwargs" ], @@ -3502,6 +3581,7 @@ "icon": "AstraDB", "legacy": false, "metadata": {}, + "minimized": false, "output_types": [], "outputs": [ { @@ -3511,9 +3591,9 @@ "method": "search_documents", "name": "search_results", "required_inputs": [ - "api_endpoint", "collection_name", - "collection_name_new", + "database_name", + "embedding_model", "token" ], "selected": "Data", @@ -3546,37 +3626,36 @@ "dynamic": false, "info": "Optional dictionary of filters to apply to the search query.", "list": false, + "list_add_label": "Add More", "name": "advanced_search_filter", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_input": true, "trace_as_metadata": true, "type": "NestedDict", "value": {} }, "api_endpoint": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "display_name": "Database", + "_input_type": "StrInput", + "advanced": true, + "display_name": "API Endpoint", "dynamic": false, - "info": "The Astra DB Database to use.", + "info": "The API endpoint for the Astra DB instance.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, "name": "api_endpoint", - "options": [ - "Default database" - ], "placeholder": "", - "real_time_refresh": true, - "refresh_button": true, - "required": true, + "required": false, "show": true, "title_case": false, "tool_mode": false, "trace_as_metadata": true, "type": "str", - "value": "Default database" + "value": "" }, "astradb_vectorstore_kwargs": { "_input_type": "NestedDictInput", @@ -3585,11 +3664,13 @@ "dynamic": false, "info": "Optional dictionary of additional parameters for the AstraDBVectorStore.", "list": false, + "list_add_label": "Add More", "name": "astradb_vectorstore_kwargs", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_input": true, "trace_as_metadata": true, "type": "NestedDict", @@ -3611,19 +3692,19 @@ "show": true, "title_case": false, "type": "code", - "value": "import os\nfrom collections import defaultdict\n\nfrom astrapy import AstraDBAdmin, DataAPIClient\nfrom astrapy.admin import parse_api_endpoint\nfrom langchain_astradb import AstraDBVectorStore\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import DictInput, FloatInput, MessageTextInput, NestedDictInput\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\nfrom langflow.utils.version import get_version_info\n\n\nclass AstraDBVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.datastax.com/en/langflow/astra-components.html\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n _cached_vector_store: AstraDBVectorStore | None = None\n\n base_inputs = LCVectorStoreComponent.inputs\n if \"search_query\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n tool_mode=True,\n )\n )\n if \"ingest_data\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n DataInput(\n name=\"ingest_data\",\n display_name=\"Ingest Data\",\n )\n )\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"api_endpoint\",\n display_name=\"Database\",\n info=\"The Astra DB Database to use.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"Default database\"],\n value=\"Default database\",\n ),\n DropdownInput(\n name=\"collection_name\",\n display_name=\"Collection\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"+ Create new collection\"],\n value=\"+ Create new collection\",\n ),\n StrInput(\n name=\"collection_name_new\",\n display_name=\"Collection Name\",\n info=\"Name of the new collection to create.\",\n advanced=os.getenv(\"LANGFLOW_HOST\") is not None,\n required=os.getenv(\"LANGFLOW_HOST\") is None,\n ),\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"embedding_choice\",\n display_name=\"Embedding Model or Astra Vectorize\",\n info=\"Determines whether to use Astra Vectorize for the collection.\",\n options=[\"Embedding Model\", \"Astra Vectorize\"],\n real_time_refresh=True,\n value=\"Embedding Model\",\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n *base_inputs,\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n StrInput(\n name=\"deletion_field\",\n display_name=\"Deletion Based On Field\",\n info=\"When this parameter is provided, documents in the target collection with \"\n \"metadata field values matching the input metadata field value will be deleted \"\n \"before new data is loaded.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n ]\n\n def del_fields(self, build_config, field_list):\n for field in field_list:\n if field in build_config:\n del build_config[field]\n\n return build_config\n\n def insert_in_dict(self, build_config, field_name, new_parameters):\n # Insert the new key-value pair after the found key\n for new_field_name, new_parameter in new_parameters.items():\n # Get all the items as a list of tuples (key, value)\n items = list(build_config.items())\n\n # Find the index of the key to insert after\n idx = len(items)\n for i, (key, _) in enumerate(items):\n if key == field_name:\n idx = i + 1\n break\n\n items.insert(idx, (new_field_name, new_parameter))\n\n # Clear the original dictionary and update with the modified items\n build_config.clear()\n build_config.update(items)\n\n return build_config\n\n def get_vectorize_providers(self):\n try:\n self.log(\"Dynamically updating list of Vectorize providers.\")\n\n # Get the admin object\n admin = AstraDBAdmin(token=self.token)\n db_admin = admin.get_database_admin(self.get_api_endpoint())\n\n # Get the list of embedding providers\n embedding_providers = db_admin.find_embedding_providers().as_dict()\n\n vectorize_providers_mapping = {}\n # Map the provider display name to the provider key and models\n for provider_key, provider_data in embedding_providers[\"embeddingProviders\"].items():\n display_name = provider_data[\"displayName\"]\n models = [model[\"name\"] for model in provider_data[\"models\"]]\n\n vectorize_providers_mapping[display_name] = [provider_key, models]\n\n # Sort the resulting dictionary\n return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching Vectorize providers: {e}\")\n\n return {}\n\n def get_database_list(self):\n # Get the admin object\n db_admin = AstraDBAdmin(token=self.token)\n db_list = list(db_admin.list_databases())\n\n # Generate the api endpoint for each database\n return {db.info.name: f\"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com\" for db in db_list}\n\n def get_api_endpoint(self):\n # Get the database name (or endpoint)\n database = self.api_endpoint\n\n # If the database is not set, get the first database in the list\n if not database or database == \"Default database\":\n database, _ = next(iter(self.get_database_list().items()))\n\n # If the database is a URL, return it\n if database.startswith(\"https://\"):\n return database\n\n # Otherwise, get the URL from the database list\n return self.get_database_list().get(database)\n\n def get_database(self):\n try:\n client = DataAPIClient(token=self.token)\n\n return client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n )\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting database: {e}\")\n\n return None\n\n def _initialize_database_options(self):\n if not self.token:\n return [\"Default database\"]\n try:\n databases = [\"Default database\", *list(self.get_database_list().keys())]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching databases: {e}\")\n\n return [\"Default database\"]\n\n return databases\n\n def _initialize_collection_options(self):\n database = self.get_database()\n if database is None:\n return [\"+ Create new collection\"]\n\n try:\n collections = [collection.name for collection in database.list_collections(keyspace=self.keyspace or None)]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching collections: {e}\")\n\n return [\"+ Create new collection\"]\n\n return [*collections, \"+ Create new collection\"]\n\n def get_collection_choice(self):\n collection_name = self.collection_name\n if collection_name == \"+ Create new collection\":\n return self.collection_name_new\n\n return collection_name\n\n def get_collection_options(self):\n # Only get the options if the collection exists\n database = self.get_database()\n if database is None:\n return None\n\n collection_name = self.get_collection_choice()\n\n try:\n collection = database.get_collection(collection_name, keyspace=self.keyspace or None)\n collection_options = collection.options()\n except Exception as _: # noqa: BLE001\n return None\n\n return collection_options.vector\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n # Always attempt to update the database list\n if field_name in {\"token\", \"api_endpoint\", \"collection_name\"}:\n # Update the database selector\n build_config[\"api_endpoint\"][\"options\"] = self._initialize_database_options()\n\n # Set the default API endpoint if not set\n if build_config[\"api_endpoint\"][\"value\"] == \"Default database\":\n build_config[\"api_endpoint\"][\"value\"] = build_config[\"api_endpoint\"][\"options\"][0]\n\n # Update the collection selector\n build_config[\"collection_name\"][\"options\"] = self._initialize_collection_options()\n\n # Update the choice of embedding model based on collection name\n if field_name == \"collection_name\":\n # Detect if it is a new collection\n is_new_collection = field_value == \"+ Create new collection\"\n\n # Set the advanced and required fields based on the collection choice\n build_config[\"embedding_choice\"].update(\n {\n \"advanced\": not is_new_collection,\n \"value\": \"Embedding Model\" if is_new_collection else build_config[\"embedding_choice\"].get(\"value\"),\n }\n )\n\n # Set the advanced field for the embedding model\n build_config[\"embedding_model\"][\"advanced\"] = not is_new_collection\n\n # Set the advanced and required fields for the new collection name\n build_config[\"collection_name_new\"].update(\n {\n \"advanced\": not is_new_collection,\n \"required\": is_new_collection,\n \"value\": \"\" if not is_new_collection else build_config[\"collection_name_new\"].get(\"value\"),\n }\n )\n\n # Get the collection options for the selected collection\n collection_options = self.get_collection_options()\n\n # If the collection options are available (DB exists), show the advanced options\n if collection_options:\n build_config[\"embedding_choice\"][\"advanced\"] = True\n\n if collection_options.service:\n # Remove unnecessary fields when a service is set\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": True},\n \"embedding_choice\": {\"value\": \"Astra Vectorize\"},\n }\n else:\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": False},\n \"embedding_provider\": {\"advanced\": False},\n \"embedding_choice\": {\"value\": \"Embedding Model\"},\n }\n\n # Apply updates to the build_config\n for key, value in updates.items():\n build_config[key].update(value)\n\n elif field_name == \"embedding_choice\":\n if field_value == \"Astra Vectorize\":\n build_config[\"embedding_model\"][\"advanced\"] = True\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n\n new_parameter = DropdownInput(\n name=\"embedding_provider\",\n display_name=\"Embedding Provider\",\n options=vectorize_providers.keys(),\n value=\"\",\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_choice\", {\"embedding_provider\": new_parameter})\n else:\n build_config[\"embedding_model\"][\"advanced\"] = False\n\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n elif field_name == \"embedding_provider\":\n self.del_fields(\n build_config,\n [\"model\", \"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n model_options = vectorize_providers[field_value][1]\n\n new_parameter = DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n info=\"The embedding model to use for the selected provider. Each provider has a different set of \"\n \"models available (full list at \"\n \"https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\\n\\n\"\n f\"{', '.join(model_options)}\",\n options=model_options,\n value=None,\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_provider\", {\"model\": new_parameter})\n\n elif field_name == \"model\":\n self.del_fields(\n build_config,\n [\"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n new_parameter_1 = DictInput(\n name=\"z_01_model_parameters\",\n display_name=\"Model Parameters\",\n list=True,\n ).to_dict()\n\n new_parameter_2 = MessageTextInput(\n name=\"z_02_api_key_name\",\n display_name=\"API Key Name\",\n info=\"The name of the embeddings provider API key stored on Astra. \"\n \"If set, it will override the 'ProviderKey' in the authentication parameters.\",\n ).to_dict()\n\n new_parameter_3 = SecretStrInput(\n load_from_db=False,\n name=\"z_03_provider_api_key\",\n display_name=\"Provider API Key\",\n info=\"An alternative to the Astra Authentication that passes an API key for the provider \"\n \"with each request to Astra DB. \"\n \"This may be used when Vectorize is configured for the collection, \"\n \"but no corresponding provider secret is stored within Astra's key management system.\",\n ).to_dict()\n\n new_parameter_4 = DictInput(\n name=\"z_04_authentication\",\n display_name=\"Authentication Parameters\",\n list=True,\n ).to_dict()\n\n self.insert_in_dict(\n build_config,\n \"model\",\n {\n \"z_01_model_parameters\": new_parameter_1,\n \"z_02_api_key_name\": new_parameter_2,\n \"z_03_provider_api_key\": new_parameter_3,\n \"z_04_authentication\": new_parameter_4,\n },\n )\n\n return build_config\n\n def build_vectorize_options(self, **kwargs):\n for attribute in [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ]:\n if not hasattr(self, attribute):\n setattr(self, attribute, None)\n\n # Fetch values from kwargs if any self.* attributes are None\n provider_mapping = self.get_vectorize_providers()\n provider_value = provider_mapping.get(self.embedding_provider, [None])[0] or kwargs.get(\"embedding_provider\")\n model_name = self.model or kwargs.get(\"model\")\n authentication = {**(self.z_04_authentication or {}), **kwargs.get(\"z_04_authentication\", {})}\n parameters = self.z_01_model_parameters or kwargs.get(\"z_01_model_parameters\", {})\n\n # Set the API key name if provided\n api_key_name = self.z_02_api_key_name or kwargs.get(\"z_02_api_key_name\")\n provider_key = self.z_03_provider_api_key or kwargs.get(\"z_03_provider_api_key\")\n if api_key_name:\n authentication[\"providerKey\"] = api_key_name\n if authentication:\n provider_key = None\n authentication[\"providerKey\"] = authentication[\"providerKey\"].split(\".\")[0]\n\n # Set authentication and parameters to None if no values are provided\n if not authentication:\n authentication = None\n if not parameters:\n parameters = None\n\n return {\n # must match astrapy.info.CollectionVectorServiceOptions\n \"collection_vector_service_options\": {\n \"provider\": provider_value,\n \"modelName\": model_name,\n \"authentication\": authentication,\n \"parameters\": parameters,\n },\n \"collection_embedding_api_key\": provider_key,\n }\n\n @check_cached_vector_store\n def build_vector_store(self, vectorize_options=None):\n try:\n from langchain_astradb import AstraDBVectorStore\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Initialize parameters based on the collection name\n is_new_collection = self.get_collection_options() is None\n\n # Get the embedding model\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_choice == \"Embedding Model\" else {}\n\n # Use the embedding model if the choice is set to \"Embedding Model\"\n if self.embedding_choice == \"Astra Vectorize\" and is_new_collection:\n from astrapy.info import CollectionVectorServiceOptions\n\n # Build the vectorize options dictionary\n dict_options = vectorize_options or self.build_vectorize_options(\n embedding_provider=getattr(self, \"embedding_provider\", None) or None,\n model=getattr(self, \"model\", None) or None,\n z_01_model_parameters=getattr(self, \"z_01_model_parameters\", None) or None,\n z_02_api_key_name=getattr(self, \"z_02_api_key_name\", None) or None,\n z_03_provider_api_key=getattr(self, \"z_03_provider_api_key\", None) or None,\n z_04_authentication=getattr(self, \"z_04_authentication\", {}) or {},\n )\n\n # Set the embedding dictionary\n embedding_params = {\n \"collection_vector_service_options\": CollectionVectorServiceOptions.from_dict(\n dict_options.get(\"collection_vector_service_options\")\n ),\n \"collection_embedding_api_key\": dict_options.get(\"collection_embedding_api_key\"),\n }\n\n # Get the running environment for Langflow\n environment = parse_api_endpoint(self.get_api_endpoint()).environment if self.get_api_endpoint() else None\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n if os.getenv(\"LANGFLOW_HOST\") is not None:\n langflow_prefix = \"ds-\"\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n # TODO: May want to expose this option\n \"autodetect_collection\": not is_new_collection,\n \"content_field\": self.content_field or None,\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.keyspace or None,\n collection_name=self.get_collection_choice(),\n environment=environment,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params or {},\n **embedding_params or {},\n **self.astradb_vectorstore_kwargs or {},\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents and self.deletion_field:\n self.log(f\"Deleting documents where {self.deletion_field}\")\n try:\n database = self.get_database()\n collection = database.get_collection(self.get_collection_choice(), keyspace=self.keyspace or None)\n delete_values = list({doc.metadata[self.deletion_field] for doc in documents})\n self.log(f\"Deleting documents where {self.deletion_field} matches {delete_values}.\")\n collection.delete_many({f\"metadata.{self.deletion_field}\": {\"$in\": delete_values}})\n except Exception as e:\n msg = f\"Error deleting documents from AstraDBVectorStore based on '{self.deletion_field}': {e}\"\n raise ValueError(msg) from e\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n return \"similarity\"\n\n def _build_search_args(self):\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n try:\n search_args = self._build_search_args()\n except Exception as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except Exception as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n" + "value": "import os\nfrom collections import defaultdict\nfrom dataclasses import dataclass, field\n\nfrom astrapy import AstraDBAdmin, DataAPIClient, Database\nfrom langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import FloatInput, NestedDictInput\nfrom langflow.io import (\n BoolInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\nfrom langflow.utils.version import get_version_info\n\n\nclass AstraDBVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.datastax.com/en/langflow/astra-components.html\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n _cached_vector_store: AstraDBVectorStore | None = None\n\n @dataclass\n class NewDatabaseInput:\n functionality: str = \"create\"\n fields: dict[str, dict] = field(\n default_factory=lambda: {\n \"data\": {\n \"node\": {\n \"description\": \"Create a new database in Astra DB.\",\n \"display_name\": \"Create New Database\",\n \"field_order\": [\"new_database_name\", \"cloud_provider\", \"region\"],\n \"template\": {\n \"new_database_name\": StrInput(\n name=\"new_database_name\",\n display_name=\"New Database Name\",\n info=\"Name of the new database to create in Astra DB.\",\n required=True,\n ),\n \"cloud_provider\": DropdownInput(\n name=\"cloud_provider\",\n display_name=\"Cloud Provider\",\n info=\"Cloud provider for the new database.\",\n options=[\"Amazon Web Services\", \"Google Cloud Platform\", \"Microsoft Azure\"],\n required=True,\n ),\n \"region\": DropdownInput(\n name=\"region\",\n display_name=\"Region\",\n info=\"Region for the new database.\",\n options=[],\n required=True,\n ),\n },\n },\n }\n }\n )\n\n @dataclass\n class NewCollectionInput:\n functionality: str = \"create\"\n fields: dict[str, dict] = field(\n default_factory=lambda: {\n \"data\": {\n \"node\": {\n \"description\": \"Create a new collection in Astra DB.\",\n \"display_name\": \"Create New Collection\",\n \"field_order\": [\n \"new_collection_name\",\n \"embedding_generation_provider\",\n \"embedding_generation_model\",\n ],\n \"template\": {\n \"new_collection_name\": StrInput(\n name=\"new_collection_name\",\n display_name=\"New Collection Name\",\n info=\"Name of the new collection to create in Astra DB.\",\n required=True,\n ),\n \"embedding_generation_provider\": DropdownInput(\n name=\"embedding_generation_provider\",\n display_name=\"Embedding Generation Provider\",\n info=\"Provider to use for generating embeddings.\",\n options=[],\n required=True,\n ),\n \"embedding_generation_model\": DropdownInput(\n name=\"embedding_generation_model\",\n display_name=\"Embedding Generation Model\",\n info=\"Model to use for generating embeddings.\",\n options=[],\n required=True,\n ),\n },\n },\n }\n }\n )\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n real_time_refresh=True,\n input_types=[],\n ),\n StrInput(\n name=\"environment\",\n display_name=\"Environment\",\n info=\"The environment for the Astra DB API Endpoint.\",\n advanced=True,\n ),\n StrInput(\n name=\"api_endpoint\",\n display_name=\"API Endpoint\",\n info=\"The API endpoint for the Astra DB instance.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"database_name\",\n display_name=\"Database\",\n info=\"Select a database in Astra DB.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n # dialog_inputs=asdict(NewDatabaseInput()),\n options=[],\n options_metadata=[\n {\n \"collections\": 0,\n }\n ],\n value=\"\",\n combobox=True,\n ),\n DropdownInput(\n name=\"collection_name\",\n display_name=\"Collection\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n # dialog_inputs=asdict(NewCollectionInput()),\n options=[],\n options_metadata=[\n {\n \"provider\": None,\n \"model\": None,\n \"records\": 0,\n \"icon\": \"\",\n }\n ],\n value=\"\",\n ),\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n required=True,\n ),\n *LCVectorStoreComponent.inputs,\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n StrInput(\n name=\"deletion_field\",\n display_name=\"Deletion Based On Field\",\n info=\"When this parameter is provided, documents in the target collection with \"\n \"metadata field values matching the input metadata field value will be deleted \"\n \"before new data is loaded.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n ]\n\n @classmethod\n def map_cloud_providers(cls):\n return {\n \"Amazon Web Services\": {\n \"id\": \"aws\",\n \"regions\": [\"us-east-2\", \"ap-south-1\", \"eu-west-1\"],\n },\n \"Google Cloud Platform\": {\n \"id\": \"gcp\",\n \"regions\": [\"us-east1\"],\n },\n \"Microsoft Azure\": {\n \"id\": \"azure\",\n \"regions\": [\"westus3\"],\n },\n }\n\n @classmethod\n def create_database_api(\n cls,\n token: str,\n new_database_name: str,\n cloud_provider: str,\n region: str,\n ):\n client = DataAPIClient(token=token)\n\n # Get the admin object\n admin_client = client.get_admin(token=token)\n\n # Call the create database function\n return admin_client.create_database(\n name=new_database_name,\n cloud_provider=cloud_provider,\n region=region,\n )\n\n @classmethod\n def create_collection_api(\n cls,\n token: str,\n database_name: str,\n new_collection_name: str,\n dimension: int | None = None,\n embedding_generation_provider: str | None = None,\n embedding_generation_model: str | None = None,\n ):\n client = DataAPIClient(token=token)\n api_endpoint = cls.get_api_endpoint_static(token=token, database_name=database_name)\n\n # Get the database object\n database = client.get_database(api_endpoint=api_endpoint, token=token)\n\n # Build vectorize options, if needed\n vectorize_options = None\n if not dimension:\n vectorize_options = CollectionVectorServiceOptions(\n provider=embedding_generation_provider,\n model_name=embedding_generation_model,\n authentication=None,\n parameters=None,\n )\n\n # Create the collection\n return database.create_collection(\n name=new_collection_name,\n dimension=dimension,\n service=vectorize_options,\n )\n\n @classmethod\n def get_database_list_static(cls, token: str, environment: str | None = None):\n client = DataAPIClient(token=token, environment=environment)\n\n # Get the admin object\n admin_client = client.get_admin(token=token)\n\n # Get the list of databases\n db_list = list(admin_client.list_databases())\n\n # Generate the api endpoint for each database\n return {\n db.info.name: {\n \"api_endpoint\": (api_endpoint := f\"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com\"),\n \"collections\": len(\n list(\n client.get_database(\n api_endpoint=api_endpoint, token=token, keyspace=db.info.keyspace\n ).list_collection_names(keyspace=db.info.keyspace)\n )\n ),\n }\n for db in db_list\n }\n\n def get_database_list(self):\n return self.get_database_list_static(token=self.token, environment=self.environment)\n\n @classmethod\n def get_api_endpoint_static(\n cls,\n token: str,\n environment: str | None = None,\n api_endpoint: str | None = None,\n database_name: str | None = None,\n ):\n # Check if an api endpoint is provided\n if api_endpoint:\n return api_endpoint\n\n # Check if the database_name is like a url\n if database_name and database_name.startswith(\"https://\"):\n return database_name\n\n # If the database is not set, nothing we can do.\n if not database_name:\n return None\n\n # Otherwise, get the URL from the database list\n return cls.get_database_list_static(token=token, environment=environment).get(database_name).get(\"api_endpoint\")\n\n def get_api_endpoint(self):\n return self.get_api_endpoint_static(\n token=self.token,\n environment=self.environment,\n api_endpoint=self.api_endpoint,\n database_name=self.database_name,\n )\n\n def get_keyspace(self):\n keyspace = self.keyspace\n\n if keyspace:\n return keyspace.strip()\n\n return None\n\n def get_database_object(self):\n try:\n client = DataAPIClient(token=self.token, environment=self.environment)\n\n return client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n keyspace=self.get_keyspace(),\n )\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting database: {e}\")\n\n return None\n\n def collection_exists(self):\n try:\n client = DataAPIClient(token=self.token, environment=self.environment)\n database = client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n keyspace=self.get_keyspace(),\n )\n\n return self.collection_name in list(database.list_collection_names(keyspace=self.get_keyspace()))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting collection status: {e}\")\n\n return False\n\n def collection_data(self, collection_name: str, database: Database | None = None):\n try:\n if not database:\n client = DataAPIClient(token=self.token, environment=self.environment)\n\n database = client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n keyspace=self.get_keyspace(),\n )\n\n collection = database.get_collection(collection_name, keyspace=self.get_keyspace())\n\n return collection.estimated_document_count()\n except Exception as e: # noqa: BLE001\n self.log(f\"Error checking collection data: {e}\")\n\n return None\n\n def get_vectorize_providers(self):\n try:\n self.log(\"Dynamically updating list of Vectorize providers.\")\n\n # Get the admin object\n admin = AstraDBAdmin(token=self.token)\n db_admin = admin.get_database_admin(api_endpoint=self.get_api_endpoint())\n\n # Get the list of embedding providers\n embedding_providers = db_admin.find_embedding_providers().as_dict()\n\n vectorize_providers_mapping = {}\n # Map the provider display name to the provider key and models\n for provider_key, provider_data in embedding_providers[\"embeddingProviders\"].items():\n display_name = provider_data[\"displayName\"]\n models = [model[\"name\"] for model in provider_data[\"models\"]]\n\n # TODO: https://astra.datastax.com/api/v2/graphql\n vectorize_providers_mapping[display_name] = [provider_key, models]\n\n # Sort the resulting dictionary\n return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching Vectorize providers: {e}\")\n\n return {}\n\n def _initialize_database_options(self):\n try:\n return [\n {\"name\": name, \"collections\": info[\"collections\"]} for name, info in self.get_database_list().items()\n ]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching databases: {e}\")\n\n return []\n\n def _initialize_collection_options(self):\n database = self.get_database_object()\n if database is None:\n return []\n\n try:\n collection_list = list(database.list_collections(keyspace=self.get_keyspace()))\n\n return [\n {\n \"name\": col.name,\n \"records\": self.collection_data(collection_name=col.name, database=database),\n \"provider\": (\n col.options.vector.service.provider\n if col.options.vector and col.options.vector.service\n else None\n ),\n \"icon\": \"\",\n \"model\": (\n col.options.vector.service.model_name\n if col.options.vector and col.options.vector.service\n else None\n ),\n }\n for col in collection_list\n ]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching collections: {e}\")\n\n return []\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n if not self.token or not self.token.startswith(\"AstraCS:\"):\n build_config[\"database_name\"][\"info\"] = \"Add a Valid Token to Select a Database\"\n else:\n build_config[\"database_name\"][\"info\"] = \"Select a Database from Astra DB\"\n\n # Refresh the database name options\n if field_name in [\"token\", \"environment\"] or not build_config[\"database_name\"][\"options\"]:\n # Reset the list of collections\n build_config[\"collection_name\"][\"options\"] = []\n build_config[\"collection_name\"][\"options_metadata\"] = []\n build_config[\"database_name\"][\"value\"] = []\n\n # Get the list of databases\n database_options = self._initialize_database_options()\n build_config[\"database_name\"][\"options\"] = [db[\"name\"] for db in database_options]\n build_config[\"database_name\"][\"options_metadata\"] = [\n {k: v for k, v in db.items() if k not in [\"name\"]} for db in database_options\n ]\n\n # Get list of regions for a given cloud provider\n \"\"\"\n cloud_provider = (\n build_config[\"database_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\"cloud_provider\"][\n \"value\"\n ]\n or \"Amazon Web Services\"\n )\n build_config[\"database_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\"region\"][\n \"options\"\n ] = self.map_cloud_providers()[cloud_provider][\"regions\"]\n \"\"\"\n\n return build_config\n\n # Refresh the collection name options\n if field_name in [\"database_name\", \"api_endpoint\"] or not build_config[\"collection_name\"][\"options\"]:\n build_config[\"collection_name\"][\"value\"] = None\n\n # Reset the list of collections\n collection_options = self._initialize_collection_options()\n build_config[\"collection_name\"][\"options\"] = [col[\"name\"] for col in collection_options]\n build_config[\"collection_name\"][\"options_metadata\"] = [\n {k: v for k, v in col.items() if k not in [\"name\"]} for col in collection_options\n ]\n\n return build_config\n\n # Hide embedding model option if opriona_metadata provider is not null\n if field_name == \"collection_name\":\n # Find location of the name in the options list\n index_of_name = build_config[\"collection_name\"][\"options\"].index(field_value)\n value_of_provider = build_config[\"collection_name\"][\"options_metadata\"][index_of_name][\"provider\"]\n\n if value_of_provider:\n build_config[\"embedding_model\"][\"advanced\"] = True\n build_config[\"embedding_model\"][\"required\"] = False\n else:\n build_config[\"embedding_model\"][\"advanced\"] = False\n build_config[\"embedding_model\"][\"required\"] = True\n\n # For the final step, get the list of vectorize providers\n \"\"\"\n vectorize_providers = self.get_vectorize_providers()\n if not vectorize_providers:\n return build_config\n\n # Allow the user to see the embedding provider options\n provider_options = build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_provider\"\n ][\"options\"]\n if not provider_options:\n # If the collection is set, allow user to see embedding options\n build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_provider\"\n ][\"options\"] = [\"Bring your own\", \"Nvidia\", *[key for key in vectorize_providers if key != \"Nvidia\"]]\n\n # And allow the user to see the models based on a selected provider\n model_options = build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_model\"\n ][\"options\"]\n if not model_options:\n embedding_provider = build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_provider\"\n ][\"value\"]\n\n build_config[\"collection_name\"][\"dialog_inputs\"][\"fields\"][\"data\"][\"node\"][\"template\"][\n \"embedding_generation_model\"\n ][\"options\"] = vectorize_providers.get(embedding_provider, [[], []])[1]\n \"\"\"\n\n return build_config\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBVectorStore\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Get the embedding model and additional params\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_model else {}\n additional_params = self.astradb_vectorstore_kwargs or {}\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n if os.getenv(\"LANGFLOW_HOST\") is not None:\n langflow_prefix = \"ds-\"\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n \"autodetect_collection\": self.collection_exists(), # TODO: May want to expose this option\n \"content_field\": (\n self.content_field\n if self.content_field and embedding_params\n else (\n \"page_content\"\n if embedding_params and self.collection_data(collection_name=self.collection_name) == 0\n else None\n )\n ),\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.get_keyspace(),\n collection_name=self.collection_name,\n environment=self.environment,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params,\n **embedding_params,\n **additional_params,\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents and self.deletion_field:\n self.log(f\"Deleting documents where {self.deletion_field}\")\n try:\n database = self.get_database_object()\n collection = database.get_collection(self.collection_name, keyspace=self.get_keyspace())\n delete_values = list({doc.metadata[self.deletion_field] for doc in documents})\n self.log(f\"Deleting documents where {self.deletion_field} matches {delete_values}.\")\n collection.delete_many({f\"metadata.{self.deletion_field}\": {\"$in\": delete_values}})\n except Exception as e:\n msg = f\"Error deleting documents from AstraDBVectorStore based on '{self.deletion_field}': {e}\"\n raise ValueError(msg) from e\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n search_type_mapping = {\n \"Similarity with score threshold\": \"similarity_score_threshold\",\n \"MMR (Max Marginal Relevance)\": \"mmr\",\n }\n\n return search_type_mapping.get(self.search_type, \"similarity\")\n\n def _build_search_args(self):\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n try:\n search_args = self._build_search_args()\n except Exception as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except Exception as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n" }, "collection_name": { "_input_type": "DropdownInput", "advanced": false, "combobox": false, + "dialog_inputs": {}, "display_name": "Collection", "dynamic": false, "info": "The name of the collection within Astra DB where the vectors will be stored.", "name": "collection_name", - "options": [ - "+ Create new collection" - ], + "options": [], + "options_metadata": [], "placeholder": "", "real_time_refresh": true, "refresh_button": true, @@ -3633,25 +3714,6 @@ "tool_mode": false, "trace_as_metadata": true, "type": "str", - "value": "+ Create new collection" - }, - "collection_name_new": { - "_input_type": "StrInput", - "advanced": false, - "display_name": "Collection Name", - "dynamic": false, - "info": "Name of the new collection to create.", - "list": false, - "list_add_label": "Add More", - "load_from_db": false, - "name": "collection_name_new", - "placeholder": "", - "required": true, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", "value": "" }, "content_field": { @@ -3661,16 +3723,40 @@ "dynamic": false, "info": "Field to use as the text content field for the vector store.", "list": false, + "list_add_label": "Add More", "load_from_db": false, "name": "content_field", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "str", "value": "" }, + "database_name": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": true, + "dialog_inputs": {}, + "display_name": "Database", + "dynamic": false, + "info": "Select a database in Astra DB.", + "name": "database_name", + "options": [], + "options_metadata": [], + "placeholder": "", + "real_time_refresh": true, + "refresh_button": true, + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": [] + }, "deletion_field": { "_input_type": "StrInput", "advanced": true, @@ -3678,6 +3764,7 @@ "dynamic": false, "info": "When this parameter is provided, documents in the target collection with metadata field values matching the input metadata field value will be deleted before new data is loaded.", "list": false, + "list_add_label": "Add More", "load_from_db": false, "name": "deletion_field", "placeholder": "", @@ -3689,28 +3776,6 @@ "type": "str", "value": "" }, - "embedding_choice": { - "_input_type": "DropdownInput", - "advanced": false, - "combobox": false, - "display_name": "Embedding Model or Astra Vectorize", - "dynamic": false, - "info": "Determines whether to use Astra Vectorize for the collection.", - "name": "embedding_choice", - "options": [ - "Embedding Model", - "Astra Vectorize" - ], - "placeholder": "", - "real_time_refresh": true, - "required": false, - "show": true, - "title_case": false, - "tool_mode": false, - "trace_as_metadata": true, - "type": "str", - "value": "Embedding Model" - }, "embedding_model": { "_input_type": "HandleInput", "advanced": false, @@ -3721,15 +3786,35 @@ "Embeddings" ], "list": false, + "list_add_label": "Add More", "name": "embedding_model", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_metadata": true, "type": "other", "value": "" }, + "environment": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "Environment", + "dynamic": false, + "info": "The environment for the Astra DB API Endpoint.", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "environment", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "ignore_invalid_documents": { "_input_type": "BoolInput", "advanced": true, @@ -3737,11 +3822,13 @@ "dynamic": false, "info": "Boolean flag to determine whether to ignore invalid documents at runtime.", "list": false, + "list_add_label": "Add More", "name": "ignore_invalid_documents", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "bool", "value": false @@ -3756,6 +3843,7 @@ "Data" ], "list": false, + "list_add_label": "Add More", "name": "ingest_data", "placeholder": "", "required": false, @@ -3774,12 +3862,14 @@ "dynamic": false, "info": "Optional keyspace within Astra DB to use for the collection.", "list": false, + "list_add_label": "Add More", "load_from_db": false, "name": "keyspace", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "str", "value": "" @@ -3791,11 +3881,13 @@ "dynamic": false, "info": "Number of search results to return.", "list": false, + "list_add_label": "Add More", "name": "number_of_results", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "int", "value": 4 @@ -3810,6 +3902,7 @@ "Message" ], "list": false, + "list_add_label": "Add More", "load_from_db": false, "multiline": true, "name": "search_query", @@ -3830,11 +3923,13 @@ "dynamic": false, "info": "Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')", "list": false, + "list_add_label": "Add More", "name": "search_score_threshold", "placeholder": "", "required": false, "show": true, "title_case": false, + "tool_mode": false, "trace_as_metadata": true, "type": "float", "value": 0 @@ -3843,6 +3938,7 @@ "_input_type": "DropdownInput", "advanced": true, "combobox": false, + "dialog_inputs": {}, "display_name": "Search Type", "dynamic": false, "info": "Search type to use", @@ -3852,6 +3948,7 @@ "Similarity with score threshold", "MMR (Max Marginal Relevance)" ], + "options_metadata": [], "placeholder": "", "required": false, "show": true, @@ -3867,9 +3964,7 @@ "display_name": "Astra DB Application Token", "dynamic": false, "info": "Authentication token for accessing Astra DB.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "token", "password": true, @@ -3884,33 +3979,32 @@ }, "tool_mode": false }, + "showNode": true, "type": "AstraDB" }, "dragging": false, - "height": 763, - "id": "AstraDB-3Vxgl", + "id": "AstraDB-KN0Mp", + "measured": { + "height": 614, + "width": 320 + }, "position": { - "x": 1212.6260540264493, - "y": 479.1345217261139 + "x": 1211.81052392379, + "y": 596.2478494450424 }, - "positionAbsolute": { - "x": 1212.6260540264493, - "y": 479.1345217261139 - }, - "selected": false, - "type": "genericNode", - "width": 320 + "selected": true, + "type": "genericNode" } ], "viewport": { - "x": -19.356798387149297, - "y": -230.4171794261714, - "zoom": 0.5951923522724938 + "x": 112.11000558627495, + "y": -163.1714705321857, + "zoom": 0.43217536168398524 } }, "description": "Load your data for chat context with Retrieval Augmented Generation.", "endpoint_name": null, - "id": "8f0934bc-f9ed-436a-b5ef-2c90755ab4d7", + "id": "823f3806-1a62-4561-adbf-897d056b49bb", "is_component": false, "last_tested_version": "1.1.1", "name": "Vector Store RAG", diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index ee256ddf8..4ccb8928d 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -80,6 +80,7 @@ class BaseInputMixin(BaseModel, validate_assignment=True): # type: ignore[call- refresh_button: bool | None = None """Specifies if the field should have a refresh button. Defaults to False.""" + refresh_button_text: str | None = None """Specifies the text for the refresh button. Defaults to None.""" @@ -158,8 +159,12 @@ class RangeMixin(BaseModel): class DropDownMixin(BaseModel): options: list[str] | None = None """List of options for the field. Only used when is_list=True. Default is an empty list.""" + options_metadata: list[dict[str, Any]] | None = None + """List of dictionaries with metadata for each option.""" combobox: CoalesceBool = False """Variable that defines if the user can insert custom values in the dropdown.""" + dialog_inputs: dict[str, Any] | None = None + """Dictionary of dialog inputs for the field. Default is an empty object.""" class MultilineMixin(BaseModel): diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index 1604dc212..48e0c0649 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -424,11 +424,16 @@ class DropdownInput(BaseInputMixin, DropDownMixin, MetadataTraceMixin, ToolModeM field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.TEXT. options (Optional[Union[list[str], Callable]]): List of options for the field. Default is None. + options_metadata (Optional[list[dict[str, str]]): List of dictionaries with metadata for each option. + Default is None. + combobox (CoalesceBool): Variable that defines if the user can insert custom values in the dropdown. """ field_type: SerializableFieldTypes = FieldTypes.TEXT options: list[str] = Field(default_factory=list) + options_metadata: list[dict[str, Any]] = Field(default_factory=list) combobox: CoalesceBool = False + dialog_inputs: dict[str, Any] = Field(default_factory=dict) class MultiselectInput(BaseInputMixin, ListableInputMixin, DropDownMixin, MetadataTraceMixin, ToolModeMixin): diff --git a/src/backend/tests/integration/components/astra/test_astra_component.py b/src/backend/tests/integration/components/astra/test_astra_component.py index 9c768bda2..b69c0db27 100644 --- a/src/backend/tests/integration/components/astra/test_astra_component.py +++ b/src/backend/tests/integration/components/astra/test_astra_component.py @@ -1,7 +1,7 @@ import os import pytest -from astrapy.db import AstraDB +from astrapy import DataAPIClient from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions from langchain_core.documents import Document from langflow.components.embeddings import OpenAIEmbeddingsComponent @@ -30,14 +30,21 @@ ALL_COLLECTIONS = [ @pytest.fixture def astradb_client(): - client = AstraDB(api_endpoint=get_astradb_api_endpoint(), token=get_astradb_application_token()) - yield client + api_client = DataAPIClient(token=get_astradb_application_token()) + client = api_client.get_database(get_astradb_api_endpoint()) + + yield client # Provide the client to the test functions + + # Cleanup: Drop all collections after tests for collection in ALL_COLLECTIONS: - client.delete_collection(collection) + try: # noqa: SIM105 + client.drop_collection(collection) + except Exception: # noqa: BLE001, S110 + pass @pytest.mark.api_key_required -async def test_base(astradb_client: AstraDB): +async def test_base(astradb_client: DataAPIClient): application_token = get_astradb_application_token() api_endpoint = get_astradb_api_endpoint() @@ -56,7 +63,7 @@ async def test_base(astradb_client: AstraDB): ) assert results["search_results"] == [] - assert astradb_client.collection(BASIC_COLLECTION) + assert astradb_client.get_collection(BASIC_COLLECTION) @pytest.mark.api_key_required @@ -92,8 +99,8 @@ def test_astra_vectorize(): store = None try: + # Get the vectorize options options = {"provider": "nvidia", "modelName": "NV-Embed-QA"} - options_comp = {"embedding_provider": "nvidia", "model": "NV-Embed-QA"} store = AstraDBVectorStore( collection_name=VECTORIZE_COLLECTION, @@ -106,7 +113,6 @@ def test_astra_vectorize(): records = [Data.from_document(d) for d in documents] component = AstraDBVectorStoreComponent() - vectorize_options = component.build_vectorize_options(**options_comp) component.build( token=application_token, @@ -115,9 +121,8 @@ def test_astra_vectorize(): ingest_data=records, search_query="test", number_of_results=2, - pre_delete_collection=True, ) - vector_store = component.build_vector_store(vectorize_options) + vector_store = component.build_vector_store() records = component.search_documents(vector_store=vector_store) assert len(records) == 2 @@ -141,14 +146,6 @@ def test_astra_vectorize_with_provider_api_key(): "authentication": {"providerKey": "openai"}, } - options_comp = { - "embedding_provider": "openai", - "model": "text-embedding-3-small", - "z_01_model_parameters": {}, - "z_03_provider_api_key": "openai", - "z_04_authentication": {}, - } - store = AstraDBVectorStore( collection_name=VECTORIZE_COLLECTION_OPENAI, api_endpoint=api_endpoint, @@ -160,7 +157,6 @@ def test_astra_vectorize_with_provider_api_key(): records = [Data.from_document(d) for d in documents] component = AstraDBVectorStoreComponent() - vectorize_options = component.build_vectorize_options(**options_comp) component.build( token=application_token, @@ -169,10 +165,9 @@ def test_astra_vectorize_with_provider_api_key(): ingest_data=records, search_query="test", number_of_results=2, - pre_delete_collection=True, ) - vector_store = component.build_vector_store(vectorize_options) + vector_store = component.build_vector_store() records = component.search_documents(vector_store=vector_store) assert len(records) == 2 @@ -195,12 +190,6 @@ def test_astra_vectorize_passes_authentication(): "parameters": {}, "authentication": {"providerKey": "openai"}, } - options_comp = { - "embedding_provider": "openai", - "model": "text-embedding-3-small", - "z_01_model_parameters": {}, - "z_04_authentication": {"providerKey": "openai"}, - } store = AstraDBVectorStore( collection_name=VECTORIZE_COLLECTION_OPENAI_WITH_AUTH, @@ -213,7 +202,6 @@ def test_astra_vectorize_passes_authentication(): records = [Data.from_document(d) for d in documents] component = AstraDBVectorStoreComponent() - vectorize_options = component.build_vectorize_options(**options_comp) component.build( token=application_token, @@ -222,10 +210,9 @@ def test_astra_vectorize_passes_authentication(): ingest_data=records, search_query="test", number_of_results=2, - pre_delete_collection=True, ) - vector_store = component.build_vector_store(vectorize_options) + vector_store = component.build_vector_store() records = component.search_documents(vector_store=vector_store) assert len(records) == 2 diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index fb4aae4e8..35e5e0702 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -4040,9 +4040,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz", - "integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.0.tgz", + "integrity": "sha512-qFcFto9figFLz2g25DxJ1WWL9+c91fTxnGuwhToCl8BaqDsDYMl/kOnBXAyAqkkzAWimYMSWNPWEjt+ADAHuoQ==", "cpu": [ "arm" ], @@ -4052,9 +4052,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz", - "integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.0.tgz", + "integrity": "sha512-vqrQdusvVl7dthqNjWCL043qelBK+gv9v3ZiqdxgaJvmZyIAAXMjeGVSqZynKq69T7062T5VrVTuikKSAAVP6A==", "cpu": [ "arm64" ], @@ -4064,9 +4064,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz", - "integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.0.tgz", + "integrity": "sha512-617pd92LhdA9+wpixnzsyhVft3szYiN16aNUMzVkf2N+yAk8UXY226Bfp36LvxYTUt7MO/ycqGFjQgJ0wlMaWQ==", "cpu": [ "arm64" ], @@ -4076,9 +4076,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz", - "integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.0.tgz", + "integrity": "sha512-Y3b4oDoaEhCypg8ajPqigKDcpi5ZZovemQl9Edpem0uNv6UUjXv7iySBpGIUTSs2ovWOzYpfw9EbFJXF/fJHWw==", "cpu": [ "x64" ], @@ -4088,9 +4088,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz", - "integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.0.tgz", + "integrity": "sha512-3REQJ4f90sFIBfa0BUokiCdrV/E4uIjhkWe1bMgCkhFXbf4D8YN6C4zwJL881GM818qVYE9BO3dGwjKhpo2ABA==", "cpu": [ "arm64" ], @@ -4100,9 +4100,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz", - "integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.0.tgz", + "integrity": "sha512-ZtY3Y8icbe3Cc+uQicsXG5L+CRGUfLZjW6j2gn5ikpltt3Whqjfo5mkyZ86UiuHF9Q3ZsaQeW7YswlHnN+lAcg==", "cpu": [ "x64" ], @@ -4112,9 +4112,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz", - "integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.0.tgz", + "integrity": "sha512-bsPGGzfiHXMhQGuFGpmo2PyTwcrh2otL6ycSZAFTESviUoBOuxF7iBbAL5IJXc/69peXl5rAtbewBFeASZ9O0g==", "cpu": [ "arm" ], @@ -4124,9 +4124,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz", - "integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.0.tgz", + "integrity": "sha512-kvyIECEhs2DrrdfQf++maCWJIQ974EI4txlz1nNSBaCdtf7i5Xf1AQCEJWOC5rEBisdaMFFnOWNLYt7KpFqy5A==", "cpu": [ "arm" ], @@ -4136,9 +4136,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz", - "integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.0.tgz", + "integrity": "sha512-CFE7zDNrokaotXu+shwIrmWrFxllg79vciH4E/zeK7NitVuWEaXRzS0mFfFvyhZfn8WfVOG/1E9u8/DFEgK7WQ==", "cpu": [ "arm64" ], @@ -4148,9 +4148,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz", - "integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.0.tgz", + "integrity": "sha512-MctNTBlvMcIBP0t8lV/NXiUwFg9oK5F79CxLU+a3xgrdJjfBLVIEHSAjQ9+ipofN2GKaMLnFFXLltg1HEEPaGQ==", "cpu": [ "arm64" ], @@ -4160,9 +4160,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz", - "integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.0.tgz", + "integrity": "sha512-fBpoYwLEPivL3q368+gwn4qnYnr7GVwM6NnMo8rJ4wb0p/Y5lg88vQRRP077gf+tc25akuqd+1Sxbn9meODhwA==", "cpu": [ "loong64" ], @@ -4172,9 +4172,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz", - "integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.0.tgz", + "integrity": "sha512-1hiHPV6dUaqIMXrIjN+vgJqtfkLpqHS1Xsg0oUfUVD98xGp1wX89PIXgDF2DWra1nxAd8dfE0Dk59MyeKaBVAw==", "cpu": [ "ppc64" ], @@ -4184,9 +4184,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz", - "integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.0.tgz", + "integrity": "sha512-U0xcC80SMpEbvvLw92emHrNjlS3OXjAM0aVzlWfar6PR0ODWCTQtKeeB+tlAPGfZQXicv1SpWwRz9Hyzq3Jx3g==", "cpu": [ "riscv64" ], @@ -4196,9 +4196,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz", - "integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.0.tgz", + "integrity": "sha512-VU/P/IODrNPasgZDLIFJmMiLGez+BN11DQWfTVlViJVabyF3JaeaJkP6teI8760f18BMGCQOW9gOmuzFaI1pUw==", "cpu": [ "s390x" ], @@ -4208,9 +4208,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz", - "integrity": "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.0.tgz", + "integrity": "sha512-laQVRvdbKmjXuFA3ZiZj7+U24FcmoPlXEi2OyLfbpY2MW1oxLt9Au8q9eHd0x6Pw/Kw4oe9gwVXWwIf2PVqblg==", "cpu": [ "x64" ], @@ -4220,9 +4220,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.1.tgz", - "integrity": "sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.0.tgz", + "integrity": "sha512-3wzKzduS7jzxqcOvy/ocU/gMR3/QrHEFLge5CD7Si9fyHuoXcidyYZ6jyx8OPYmCcGm3uKTUl+9jUSAY74Ln5A==", "cpu": [ "x64" ], @@ -4232,9 +4232,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz", - "integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.0.tgz", + "integrity": "sha512-jROwnI1+wPyuv696rAFHp5+6RFhXGGwgmgSfzE8e4xfit6oLRg7GyMArVUoM3ChS045OwWr9aTnU+2c1UdBMyw==", "cpu": [ "arm64" ], @@ -4244,9 +4244,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz", - "integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.0.tgz", + "integrity": "sha512-duzweyup5WELhcXx5H1jokpr13i3BV9b48FMiikYAwk/MT1LrMYYk2TzenBd0jj4ivQIt58JWSxc19y4SvLP4g==", "cpu": [ "ia32" ], @@ -4256,9 +4256,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz", - "integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.0.tgz", + "integrity": "sha512-DYvxS0M07PvgvavMIybCOBYheyrqlui6ZQBHJs6GqduVzHSZ06TPPvlfvnYstjODHQ8UUXFwt5YE+h0jFI8kwg==", "cpu": [ "x64" ], @@ -4585,9 +4585,9 @@ } }, "node_modules/@swc/core": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz", - "integrity": "sha512-rQ4dS6GAdmtzKiCRt3LFVxl37FaY1cgL9kSUTnhQ2xc3fmHOd7jdJK/V4pSZMG1ruGTd0bsi34O2R0Olg9Zo/w==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.4.tgz", + "integrity": "sha512-ut3zfiTLORMxhr6y/GBxkHmzcGuVpwJYX4qyXWuBKkpw/0g0S5iO1/wW7RnLnZbAi8wS/n0atRZoaZlXWBkeJg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -4602,16 +4602,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.10.1", - "@swc/core-darwin-x64": "1.10.1", - "@swc/core-linux-arm-gnueabihf": "1.10.1", - "@swc/core-linux-arm64-gnu": "1.10.1", - "@swc/core-linux-arm64-musl": "1.10.1", - "@swc/core-linux-x64-gnu": "1.10.1", - "@swc/core-linux-x64-musl": "1.10.1", - "@swc/core-win32-arm64-msvc": "1.10.1", - "@swc/core-win32-ia32-msvc": "1.10.1", - "@swc/core-win32-x64-msvc": "1.10.1" + "@swc/core-darwin-arm64": "1.10.4", + "@swc/core-darwin-x64": "1.10.4", + "@swc/core-linux-arm-gnueabihf": "1.10.4", + "@swc/core-linux-arm64-gnu": "1.10.4", + "@swc/core-linux-arm64-musl": "1.10.4", + "@swc/core-linux-x64-gnu": "1.10.4", + "@swc/core-linux-x64-musl": "1.10.4", + "@swc/core-win32-arm64-msvc": "1.10.4", + "@swc/core-win32-ia32-msvc": "1.10.4", + "@swc/core-win32-x64-msvc": "1.10.4" }, "peerDependencies": { "@swc/helpers": "*" @@ -4623,9 +4623,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.1.tgz", - "integrity": "sha512-NyELPp8EsVZtxH/mEqvzSyWpfPJ1lugpTQcSlMduZLj1EASLO4sC8wt8hmL1aizRlsbjCX+r0PyL+l0xQ64/6Q==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.4.tgz", + "integrity": "sha512-sV/eurLhkjn/197y48bxKP19oqcLydSel42Qsy2zepBltqUx+/zZ8+/IS0Bi7kaWVFxerbW1IPB09uq8Zuvm3g==", "cpu": [ "arm64" ], @@ -4639,9 +4639,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.1.tgz", - "integrity": "sha512-L4BNt1fdQ5ZZhAk5qoDfUnXRabDOXKnXBxMDJ+PWLSxOGBbWE6aJTnu4zbGjJvtot0KM46m2LPAPY8ttknqaZA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.4.tgz", + "integrity": "sha512-gjYNU6vrAUO4+FuovEo9ofnVosTFXkF0VDuo1MKPItz6e2pxc2ale4FGzLw0Nf7JB1sX4a8h06CN16/pLJ8Q2w==", "cpu": [ "x64" ], @@ -4655,9 +4655,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.1.tgz", - "integrity": "sha512-Y1u9OqCHgvVp2tYQAJ7hcU9qO5brDMIrA5R31rwWQIAKDkJKtv3IlTHF0hrbWk1wPR0ZdngkQSJZple7G+Grvw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.4.tgz", + "integrity": "sha512-zd7fXH5w8s+Sfvn2oO464KDWl+ZX1MJiVmE4Pdk46N3PEaNwE0koTfgx2vQRqRG4vBBobzVvzICC3618WcefOA==", "cpu": [ "arm" ], @@ -4671,9 +4671,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.1.tgz", - "integrity": "sha512-tNQHO/UKdtnqjc7o04iRXng1wTUXPgVd8Y6LI4qIbHVoVPwksZydISjMcilKNLKIwOoUQAkxyJ16SlOAeADzhQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.4.tgz", + "integrity": "sha512-+UGfoHDxsMZgFD3tABKLeEZHqLNOkxStu+qCG7atGBhS4Slri6h6zijVvf4yI5X3kbXdvc44XV/hrP/Klnui2A==", "cpu": [ "arm64" ], @@ -4687,9 +4687,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.1.tgz", - "integrity": "sha512-x0L2Pd9weQ6n8dI1z1Isq00VHFvpBClwQJvrt3NHzmR+1wCT/gcYl1tp9P5xHh3ldM8Cn4UjWCw+7PaUgg8FcQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.4.tgz", + "integrity": "sha512-cDDj2/uYsOH0pgAnDkovLZvKJpFmBMyXkxEG6Q4yw99HbzO6QzZ5HDGWGWVq/6dLgYKlnnmpjZCPPQIu01mXEg==", "cpu": [ "arm64" ], @@ -4703,9 +4703,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.1.tgz", - "integrity": "sha512-yyYEwQcObV3AUsC79rSzN9z6kiWxKAVJ6Ntwq2N9YoZqSPYph+4/Am5fM1xEQYf/kb99csj0FgOelomJSobxQA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.4.tgz", + "integrity": "sha512-qJXh9D6Kf5xSdGWPINpLGixAbB5JX8JcbEJpRamhlDBoOcQC79dYfOMEIxWPhTS1DGLyFakAx2FX/b2VmQmj0g==", "cpu": [ "x64" ], @@ -4719,9 +4719,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.1.tgz", - "integrity": "sha512-tcaS43Ydd7Fk7sW5ROpaf2Kq1zR+sI5K0RM+0qYLYYurvsJruj3GhBCaiN3gkzd8m/8wkqNqtVklWaQYSDsyqA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.4.tgz", + "integrity": "sha512-A76lIAeyQnHCVt0RL/pG+0er8Qk9+acGJqSZOZm67Ve3B0oqMd871kPtaHBM0BW3OZAhoILgfHW3Op9Q3mx3Cw==", "cpu": [ "x64" ], @@ -4735,9 +4735,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.1.tgz", - "integrity": "sha512-D3Qo1voA7AkbOzQ2UGuKNHfYGKL6eejN8VWOoQYtGHHQi1p5KK/Q7V1ku55oxXBsj79Ny5FRMqiRJpVGad7bjQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.4.tgz", + "integrity": "sha512-e6j5kBu4fIY7fFxFxnZI0MlEovRvp50Lg59Fw+DVbtqHk3C85dckcy5xKP+UoXeuEmFceauQDczUcGs19SRGSQ==", "cpu": [ "arm64" ], @@ -4751,9 +4751,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.1.tgz", - "integrity": "sha512-WalYdFoU3454Og+sDKHM1MrjvxUGwA2oralknXkXL8S0I/8RkWZOB++p3pLaGbTvOO++T+6znFbQdR8KRaa7DA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.4.tgz", + "integrity": "sha512-RSYHfdKgNXV/amY5Tqk1EWVsyQnhlsM//jeqMLw5Fy9rfxP592W9UTumNikNRPdjI8wKKzNMXDb1U29tQjN0dg==", "cpu": [ "ia32" ], @@ -4767,9 +4767,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.1.tgz", - "integrity": "sha512-JWobfQDbTnoqaIwPKQ3DVSywihVXlQMbDuwik/dDWlj33A8oEHcjPOGs4OqcA3RHv24i+lfCQpM3Mn4FAMfacA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.4.tgz", + "integrity": "sha512-1ujYpaqfqNPYdwKBlvJnOqcl+Syn3UrQ4XE0Txz6zMYgyh6cdU6a3pxqLqIUSJ12MtXRA9ZUhEz1ekU3LfLWXw==", "cpu": [ "x64" ], @@ -4876,20 +4876,20 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.62.9", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.9.tgz", - "integrity": "sha512-lwePd8hNYhyQ4nM/iRQ+Wz2cDtspGeZZHFZmCzHJ7mfKXt+9S301fULiY2IR2byJYY6Z03T427E5PoVfMexHjw==", + "version": "5.62.15", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.15.tgz", + "integrity": "sha512-wT20X14CxcWY8YLJ/1pnsXn/y1Q2uRJZYWW93PWRtZt+3/JlGZyiyTcO4pGnqycnP7CokCROAyatsraosqZsDA==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-query": { - "version": "5.62.9", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.9.tgz", - "integrity": "sha512-jFUH9pOsOyN2ugGIR8tjz2+bRC0PEp25LvpNvYIgiEegP5Xbfi9phuNOMnjFugyg3GM0WI/HfUEE7eM2vmcbxg==", + "version": "5.62.15", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.15.tgz", + "integrity": "sha512-Ny3xxsOWmEQCFyHiV3CF7t6+QAV+LpBEREiXyllKR4+tStyd8smOAa98ZHmEx0ZNy36M31K8enifB5wTSYAKJw==", "dependencies": { - "@tanstack/query-core": "5.62.9" + "@tanstack/query-core": "5.62.15" }, "funding": { "type": "github", @@ -5284,9 +5284,9 @@ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==" }, "node_modules/@types/d3-shape": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", - "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", "dependencies": { "@types/d3-path": "*" } @@ -5474,9 +5474,9 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "20.17.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", - "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", + "version": "20.17.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz", + "integrity": "sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==", "dependencies": { "undici-types": "~6.19.2" } @@ -5767,9 +5767,9 @@ } }, "node_modules/ace-builds": { - "version": "1.37.1", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.37.1.tgz", - "integrity": "sha512-6/jxFucA1z1C3hgLlVkTE5/znZ+iYvD301vfwtybiMc3k76IDykliCD0xh/eYZMJUfsJtaOQHZ2AJO5ey0PHWw==" + "version": "1.37.2", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.37.2.tgz", + "integrity": "sha512-jkJccO2a75aC6HWIPcRBhMA99IRbvBDoy1lXvjr3QfHYa0rZtaC6ZY2RjlcEFrcFdaNAOHe8jMaei5p1rbDKGw==" }, "node_modules/acorn": { "version": "8.14.0", @@ -6141,9 +6141,9 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/bare-events": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", - "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.2.tgz", + "integrity": "sha512-KSdMqLj1ZERZMP1PTmnLK7SqJu9z9/SbwUUPZly2puMtfVcytC+jl6mb/9XYiqq0PXcx1rNDS+Qvl1g54Lho6A==", "dev": true, "optional": true }, @@ -6458,9 +6458,9 @@ ] }, "node_modules/canvas": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.0.0.tgz", - "integrity": "sha512-NtcIBY88FjymQy+g2g5qnuP5IslrbWCQ3A6rSr1PeuYxVRapRZ3BZCrDyAakvI6CuDYidgZaf55ygulFVwROdg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.0.1.tgz", + "integrity": "sha512-PcpVF4f8RubAeN/jCQQ/UymDKzOiLmRPph8fOTzDnlsUihkO/AUlxuhaa7wGRc3vMcCbV1fzuvyu5cWZlIcn1w==", "hasInstallScript": true, "optional": true, "dependencies": { @@ -7392,17 +7392,17 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/effect": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.12.0.tgz", - "integrity": "sha512-b/u9s3b9HfTo0qygVouegP0hkbiuxRIeaCe1ppf8P88hPyl6lKCbErtn7Az4jG7LuU7f0Wgm4c8WXbMcL2j8+g==", + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.12.1.tgz", + "integrity": "sha512-aAZdh56Yp1ehOFYeMcHHctTtxfqm6kkOdZFTXK6Zf0QoaKKc1hPG6ocjrKOc0axE8JbG4eZw351ogNLrM4vo9w==", "dependencies": { "fast-check": "^3.23.1" } }, "node_modules/electron-to-chromium": { - "version": "1.5.75", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz", - "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==" + "version": "1.5.76", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", + "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==" }, "node_modules/elkjs": { "version": "0.9.3", @@ -7907,15 +7907,15 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -8269,12 +8269,12 @@ } }, "node_modules/framer-motion": { - "version": "11.15.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.15.0.tgz", - "integrity": "sha512-MLk8IvZntxOMg7lDBLw2qgTHHv664bYoYmnFTmE0Gm/FW67aOJk0WM3ctMcG+Xhcv+vh5uyyXwxvxhSeJzSe+w==", + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.16.0.tgz", + "integrity": "sha512-oL2AWqLQuw0+CNEUa0sz3mWC/n3i147CckvpQn8bLRs30b+HxTxlRi0YR2FpHHhAbWV7DKjNdHU42KHLfBWh/g==", "dependencies": { - "motion-dom": "^11.14.3", - "motion-utils": "^11.14.3", + "motion-dom": "^11.16.0", + "motion-utils": "^11.16.0", "tslib": "^2.4.0" }, "peerDependencies": { @@ -8792,9 +8792,9 @@ } }, "node_modules/hono": { - "version": "4.6.14", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.14.tgz", - "integrity": "sha512-j4VkyUp2xazGJ8eCCLN1Vm/bxdvm/j5ZuU9AIjLu9vapn2M44p9L3Ktr9Vnb2RN2QtcR/wVjZVMlT5k7GJQgPw==", + "version": "4.6.16", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.16.tgz", + "integrity": "sha512-iE6xOPwDYlfnZFwk6BfIMMIH4WZm3pPhz6rc1uJM/OPew0pjG5K6p8WTLaMBY1/szF/T0TaEjprMpwn16BA0NQ==", "engines": { "node": ">=16.9.0" } @@ -9540,9 +9540,9 @@ } }, "node_modules/katex": { - "version": "0.16.18", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.18.tgz", - "integrity": "sha512-LRuk0rPdXrecAFwQucYjMiIs0JFefk6N1q/04mlw14aVIVgxq1FO0MA9RiIIGVaKOB5GIP5GH4aBBNraZERmaQ==", + "version": "0.16.19", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.19.tgz", + "integrity": "sha512-3IA6DYVhxhBabjSLTNO9S4+OliA3Qvb8pBQXMfC4WxXJgLwZgnfDl0BmB4z6nBMdznBsZ+CGM8DrGZ5hcguDZg==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -11478,14 +11478,17 @@ } }, "node_modules/motion-dom": { - "version": "11.14.3", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.3.tgz", - "integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==" + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.16.0.tgz", + "integrity": "sha512-4bmEwajSdrljzDAYpu6ceEdtI4J5PH25fmN8YSx7Qxk6OMrC10CXM0D5y+VO/pFZjhmCvm2bGf7Rus482kwhzA==", + "dependencies": { + "motion-utils": "^11.16.0" + } }, "node_modules/motion-utils": { - "version": "11.14.3", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.14.3.tgz", - "integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==" + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.16.0.tgz", + "integrity": "sha512-ngdWPjg31rD4WGXFi0eZ00DQQqKKu04QExyv/ymlC+3k+WIgYVFbt6gS5JsFPbJODTF/r8XiE/X+SsoT9c0ocw==" }, "node_modules/mri": { "version": "1.2.0", @@ -12210,9 +12213,9 @@ } }, "node_modules/postcss-load-config/node_modules/yaml": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", - "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", "bin": { "yaml": "bin.mjs" }, @@ -13603,9 +13606,9 @@ } }, "node_modules/rollup": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.1.tgz", - "integrity": "sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.0.tgz", + "integrity": "sha512-sDnr1pcjTgUT69qBksNF1N1anwfbyYG6TBQ22b03bII8EdiUQ7J0TlozVaTMjT/eEJAO49e1ndV7t+UZfL1+vA==", "dependencies": { "@types/estree": "1.0.6" }, @@ -13617,25 +13620,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.29.1", - "@rollup/rollup-android-arm64": "4.29.1", - "@rollup/rollup-darwin-arm64": "4.29.1", - "@rollup/rollup-darwin-x64": "4.29.1", - "@rollup/rollup-freebsd-arm64": "4.29.1", - "@rollup/rollup-freebsd-x64": "4.29.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.29.1", - "@rollup/rollup-linux-arm-musleabihf": "4.29.1", - "@rollup/rollup-linux-arm64-gnu": "4.29.1", - "@rollup/rollup-linux-arm64-musl": "4.29.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.29.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.29.1", - "@rollup/rollup-linux-riscv64-gnu": "4.29.1", - "@rollup/rollup-linux-s390x-gnu": "4.29.1", - "@rollup/rollup-linux-x64-gnu": "4.29.1", - "@rollup/rollup-linux-x64-musl": "4.29.1", - "@rollup/rollup-win32-arm64-msvc": "4.29.1", - "@rollup/rollup-win32-ia32-msvc": "4.29.1", - "@rollup/rollup-win32-x64-msvc": "4.29.1", + "@rollup/rollup-android-arm-eabi": "4.30.0", + "@rollup/rollup-android-arm64": "4.30.0", + "@rollup/rollup-darwin-arm64": "4.30.0", + "@rollup/rollup-darwin-x64": "4.30.0", + "@rollup/rollup-freebsd-arm64": "4.30.0", + "@rollup/rollup-freebsd-x64": "4.30.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.30.0", + "@rollup/rollup-linux-arm-musleabihf": "4.30.0", + "@rollup/rollup-linux-arm64-gnu": "4.30.0", + "@rollup/rollup-linux-arm64-musl": "4.30.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.30.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.30.0", + "@rollup/rollup-linux-riscv64-gnu": "4.30.0", + "@rollup/rollup-linux-s390x-gnu": "4.30.0", + "@rollup/rollup-linux-x64-gnu": "4.30.0", + "@rollup/rollup-linux-x64-musl": "4.30.0", + "@rollup/rollup-win32-arm64-msvc": "4.30.0", + "@rollup/rollup-win32-ia32-msvc": "4.30.0", + "@rollup/rollup-win32-x64-msvc": "4.30.0", "fsevents": "~2.3.2" } }, diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeDialogComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeDialogComponent/index.tsx new file mode 100644 index 000000000..5b7020b75 --- /dev/null +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeDialogComponent/index.tsx @@ -0,0 +1,116 @@ +import { ParameterRenderComponent } from "@/components/core/parameterRenderComponent"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { getCustomParameterTitle } from "@/customization/components/custom-parameter"; +import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value"; +import useFlowStore from "@/stores/flowStore"; +import { InputFieldType } from "@/types/api"; +import { cloneDeep } from "lodash"; + +export const NodeDialog = ({ + open, + onClose, + dialogInputs, + nodeId, +}: { + open: boolean; + onClose: () => void; + dialogInputs: any; + nodeId: string; +}) => { + const nodes = useFlowStore((state) => state.nodes); + const setNode = useFlowStore((state) => state.setNode); + + const handleNewValue = (value: string, key: string) => { + let rawValue = value; + + if (typeof value === "object" && value) { + rawValue = (value as { value: string }).value; + } + + const template = cloneDeep(dialogInputs?.fields?.data?.node?.template); + template[key].value = value; + + const newNode = cloneDeep(nodes.find((node) => node.id === nodeId)); + if (newNode) { + const template = newNode.data.node.template; + const databaseFields = template.database_name.dialog_inputs.fields; + const nodeTemplate = databaseFields.data.node.template; + + nodeTemplate[key].value = rawValue; + } + setNode(nodeId, newNode!); + }; + + return ( + + + + +
+ + {dialogInputs.fields?.data?.node?.display_name} + +
+
+ +
+ {dialogInputs.fields?.data?.node?.description} +
+
+
+ +
+ {Object.entries(dialogInputs?.fields?.data?.node?.template ?? {}).map( + ([key, value]) => ( +
+
+ {getCustomParameterTitle({ + title: + dialogInputs?.fields?.data?.node?.template[key] + .display_name ?? "", + nodeId, + isFlexView: false, + })} +
+ + handleNewValue(value, key) + } + name={key} + nodeId={nodeId} + templateData={value as Partial} + templateValue={ + dialogInputs?.fields?.data?.node?.template[key].value + } + editNode={false} + handleNodeClass={() => {}} + nodeClass={dialogInputs.fields?.data?.node} + disabled={false} + placeholder="" + isToolMode={false} + /> +
+ ), + )} +
+ + + + + +
+
+ ); +}; + +export default NodeDialog; diff --git a/src/frontend/src/components/core/dropdownComponent/index.tsx b/src/frontend/src/components/core/dropdownComponent/index.tsx index 9302e9c62..20c7887c9 100644 --- a/src/frontend/src/components/core/dropdownComponent/index.tsx +++ b/src/frontend/src/components/core/dropdownComponent/index.tsx @@ -1,9 +1,13 @@ +import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value"; +import NodeDialog from "@/CustomNodes/GenericNode/components/NodeDialogComponent"; +import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template"; +import useAlertStore from "@/stores/alertStore"; import { PopoverAnchor } from "@radix-ui/react-popover"; import Fuse from "fuse.js"; import { cloneDeep } from "lodash"; import { ChangeEvent, useEffect, useRef, useState } from "react"; import { DropDownComponent } from "../../../types/components"; -import { cn, formatPlaceholderName } from "../../../utils/utils"; +import { cn, formatName, formatPlaceholderName } from "../../../utils/utils"; import { default as ForwardedIconComponent } from "../../common/genericIconComponent"; import ShadTooltip from "../../common/shadTooltipComponent"; import { Button } from "../../ui/button"; @@ -13,6 +17,7 @@ import { CommandGroup, CommandItem, CommandList, + CommandSeparator, } from "../../ui/command"; import { Popover, @@ -20,24 +25,32 @@ import { PopoverContentWithoutPortal, PopoverTrigger, } from "../../ui/popover"; +import { BaseInputProps } from "../parameterRenderComponent/types"; export default function Dropdown({ disabled, isLoading, value, options, + optionsMetaData, combobox, onSelect, editNode = false, id = "", children, name, -}: DropDownComponent): JSX.Element { + dialogInputs, + ...baseInputProps +}: BaseInputProps & DropDownComponent): JSX.Element { + const nodeId = baseInputProps?.nodeId; + const placeholderName = name ? formatPlaceholderName(name) : "Choose an option..."; + const { firstWord } = formatName(name); const [open, setOpen] = useState(children ? true : false); + const [openDialog, setOpenDialog] = useState(false); const refButton = useRef(null); @@ -58,6 +71,28 @@ export default function Dropdown({ setCustomValue(value); }; + const { nodeClass, handleNodeClass } = baseInputProps; + + const postTemplateValue = usePostTemplateValue({ + parameterId: name || "", + nodeId: id, + node: nodeClass!, + }); + + const { isPending } = postTemplateValue; + + const setErrorData = useAlertStore((state) => state.setErrorData); + + const handleRefreshButtonPress = () => { + mutateTemplate( + value, + nodeClass!, + handleNodeClass, + postTemplateValue, + setErrorData, + ); + }; + useEffect(() => { if (disabled && value !== "") { onSelect("", undefined, true); @@ -77,7 +112,12 @@ export default function Dropdown({ const renderTriggerButton = () => ( + + + + + setOpenDialog(false)} + nodeId={nodeId!} + /> + + ); + const renderOptionsList = () => ( No values found. {filteredOptions?.map((option, index) => ( -
- { - onSelect(currentValue); - setOpen(false); - }} - className="items-center overflow-hidden truncate hover:cursor-pointer" - data-testid={`${option}-${index}-option`} - > - {customValue === option && ( - Text:  - )} - {option} - - -
+ +
+ { + onSelect(currentValue); + setOpen(false); + }} + className="items-center" + data-testid={`${option}-${index}-option`} + > +
+ {optionsMetaData?.[index]?.icon ? ( + + ) : null} +
0, + "w-full pl-2": !optionsMetaData?.[index]?.icon, + })} + > +
{option}
+ {optionsMetaData && optionsMetaData?.length > 0 ? ( +
+ {Object.entries(optionsMetaData?.[index] || {}) + .filter( + ([key, value]) => value !== null && key !== "icon", + ) + .map(([key, value], i, arr) => ( +
+ {i > 0 && ( + + )} +
{`${String(value)} ${key}`}
+
+ ))} +
+ ) : ( +
+ +
+ )} +
+
+
+
+
))}
+ + {dialogInputs && dialogInputs?.fields && renderCustomOptionDialog()}
); @@ -175,17 +326,11 @@ export default function Dropdown({ ); - if (Object.keys(options).length === 0 && !combobox) { - return isLoading ? ( + if (Object.keys(options).length === 0 && !combobox && isLoading) { + return (
Loading...
- ) : ( -
- - No parameters are available for display. - -
); } diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/dropdownComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/dropdownComponent/index.tsx index 3b35aacbd..404889138 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/dropdownComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/dropdownComponent/index.tsx @@ -10,20 +10,28 @@ export default function DropdownComponent({ combobox, options, name, + dialogInputs, + optionsMetaData, + ...baseInputProps }: InputProps) { const onChange = (value: any, dbValue?: boolean, skipSnapshot?: boolean) => { handleOnNewValue({ value, load_from_db: dbValue }, { skipSnapshot }); }; + return ( ); } diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/inputComponent/components/popover/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/inputComponent/components/popover/index.tsx index 4f9916bca..de9aa0f37 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/inputComponent/components/popover/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/inputComponent/components/popover/index.tsx @@ -177,8 +177,6 @@ const CustomInputPopover = ({ const [isFocused, setIsFocused] = useState(false); const memoizedOptions = useMemo(() => new Set(options), [options]); - console.log(nodeStyle); - const PopoverContentInput = editNode ? PopoverContent : PopoverContentWithoutPortal; diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/refreshParameterComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/refreshParameterComponent/index.tsx index ed11de425..024daab88 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/refreshParameterComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/refreshParameterComponent/index.tsx @@ -45,7 +45,8 @@ export function RefreshParameterComponent({ const isFlexView = FLEX_VIEW_TYPES.includes(templateData.type ?? ""); return ( - (children || templateData.refresh_button) && ( + (children || + (templateData.refresh_button && !templateData.dialog_inputs)) && (
{children} - {templateData.refresh_button && ( -
- -
- )} + {templateData.refresh_button && + !templateData.dialog_inputs?.fields?.data?.node?.template && ( +
+ +
+ )}
) ); diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/strRenderComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/strRenderComponent/index.tsx index b50ff6cab..3978353c6 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/strRenderComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/strRenderComponent/index.tsx @@ -45,7 +45,9 @@ export function StrRenderComponent({ return ( diff --git a/src/frontend/src/components/core/parameterRenderComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/index.tsx index fa4972560..7c0a9640f 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/index.tsx @@ -33,7 +33,9 @@ export function ParameterRenderComponent({ placeholder, isToolMode, }: { - handleOnNewValue: handleOnNewValueType; + handleOnNewValue: + | handleOnNewValueType + | ((value: string, key: string) => void); name: string; nodeId: string; templateData: Partial; @@ -57,13 +59,14 @@ export function ParameterRenderComponent({ id, value: templateValue, editNode, - handleOnNewValue, + handleOnNewValue: handleOnNewValue as handleOnNewValueType, disabled, nodeClass, handleNodeClass, readonly: templateData.readonly, placeholder, isToolMode, + nodeId, }; if (TEXT_FIELD_TYPES.includes(templateData.type ?? "")) { diff --git a/src/frontend/src/components/core/parameterRenderComponent/types.ts b/src/frontend/src/components/core/parameterRenderComponent/types.ts index b0391b6b4..920726de9 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/types.ts +++ b/src/frontend/src/components/core/parameterRenderComponent/types.ts @@ -16,6 +16,7 @@ export type BaseInputProps = { placeholder?: string; isToolMode?: boolean; metadata?: any; + nodeId?: string; }; // Generic type for composing input props @@ -81,6 +82,8 @@ export type DropDownComponentType = { combobox?: boolean; options: string[]; name: string; + dialogInputs?: any; + optionsMetaData?: any[]; }; export type TextAreaComponentType = { diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 562578678..01950fb90 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -54,11 +54,13 @@ export type DropDownComponent = { value: string; combobox?: boolean; options: string[]; + optionsMetaData?: any[]; onSelect: (value: string, dbValue?: boolean, snapshot?: boolean) => void; editNode?: boolean; id?: string; children?: ReactNode; name?: string; + dialogInputs?: any; }; export type ParameterComponentType = { selected?: boolean; diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 502808215..c8ec29b2f 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -728,6 +728,19 @@ export const formatPlaceholderName = (name) => { return `Select ${prefix} ${formattedName}`; }; +export const formatName = (name) => { + const formattedName = name + .split("_") + .map((word: string) => word.toLowerCase()) + .join(" "); + + const firstWord = + formattedName.split(" ")[0].charAt(0).toUpperCase() + + formattedName.split(" ")[0].slice(1); + + return { formattedName, firstWord }; +}; + export const isStringArray = (value: unknown): value is string[] => { return ( Array.isArray(value) && value.every((item) => typeof item === "string") diff --git a/uv.lock b/uv.lock index d7c875351..4c15677da 100644 --- a/uv.lock +++ b/uv.lock @@ -6629,12 +6629,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/1b/d0b013bf7d1af7cf0a6a4fce13f5fe5813ab225313755367b36e714a63f8/pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93", size = 2254397 }, { url = "https://files.pythonhosted.org/packages/14/71/4cbd3870d3e926c34706f705d6793159ac49d9a213e3ababcdade5864663/pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764", size = 1775641 }, { url = "https://files.pythonhosted.org/packages/43/1d/81d59d228381576b92ecede5cd7239762c14001a828bdba30d64896e9778/pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53", size = 1812863 }, - { url = "https://files.pythonhosted.org/packages/25/b3/09ff7072e6d96c9939c24cf51d3c389d7c345bf675420355c22402f71b68/pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca", size = 1691593 }, - { url = "https://files.pythonhosted.org/packages/a8/91/38e43628148f68ba9b68dedbc323cf409e537fd11264031961fd7c744034/pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd", size = 1765997 }, - { url = "https://files.pythonhosted.org/packages/08/16/ae464d4ac338c1dd41f89c41f9488e54f7d2a3acf93bb920bb193b99f8e3/pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8", size = 1615855 }, - { url = "https://files.pythonhosted.org/packages/1e/8c/b0cee957eee1950ce7655006b26a8894cee1dc4b8747ae913684352786eb/pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6", size = 1650018 }, - { url = "https://files.pythonhosted.org/packages/93/4d/d7138068089b99f6b0368622e60f97a577c936d75f533552a82613060c58/pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0", size = 1687977 }, - { url = "https://files.pythonhosted.org/packages/96/02/90ae1ac9f28be4df0ed88c127bf4acc1b102b40053e172759d4d1c54d937/pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6", size = 1788273 }, ] [[package]]