langflow-base-nightly 0.5.0.dev31__py3-none-any.whl → 0.5.0.dev33__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
langflow/__main__.py CHANGED
@@ -15,7 +15,9 @@ import click
15
15
  import httpx
16
16
  import typer
17
17
  from dotenv import load_dotenv
18
+ from fastapi import HTTPException
18
19
  from httpx import HTTPError
20
+ from jose import JWTError
19
21
  from multiprocess import cpu_count
20
22
  from multiprocess.context import Process
21
23
  from packaging import version as pkg_version
@@ -29,9 +31,9 @@ from langflow.cli.progress import create_langflow_progress
29
31
  from langflow.initial_setup.setup import get_or_create_default_folder
30
32
  from langflow.logging.logger import configure, logger
31
33
  from langflow.main import setup_app
32
- from langflow.services.database.utils import session_getter
34
+ from langflow.services.auth.utils import check_key, get_current_user_by_jwt
33
35
  from langflow.services.deps import get_db_service, get_settings_service, session_scope
34
- from langflow.services.settings.constants import DEFAULT_SUPERUSER
36
+ from langflow.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
35
37
  from langflow.services.utils import initialize_services
36
38
  from langflow.utils.version import fetch_latest_version, get_version_info
37
39
  from langflow.utils.version import is_pre_release as langflow_is_pre_release
@@ -632,41 +634,138 @@ def print_banner(host: str, port: int, protocol: str) -> None:
632
634
 
633
635
  @app.command()
634
636
  def superuser(
635
- username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
636
- password: str = typer.Option(..., prompt=True, hide_input=True, help="Password for the superuser."),
637
+ username: str = typer.Option(
638
+ None, help="Username for the superuser. Defaults to 'langflow' when AUTO_LOGIN is enabled."
639
+ ),
640
+ password: str = typer.Option(
641
+ None, help="Password for the superuser. Defaults to 'langflow' when AUTO_LOGIN is enabled."
642
+ ),
637
643
  log_level: str = typer.Option("error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
644
+ auth_token: str = typer.Option(
645
+ None, help="Authentication token of existing superuser.", envvar="LANGFLOW_SUPERUSER_TOKEN"
646
+ ),
638
647
  ) -> None:
639
- """Create a superuser."""
648
+ """Create a superuser.
649
+
650
+ When AUTO_LOGIN is enabled, uses default credentials.
651
+ In production mode, requires authentication.
652
+ """
640
653
  configure(log_level=log_level)
641
- db_service = get_db_service()
642
654
 
643
- async def _create_superuser():
644
- await initialize_services()
645
- async with session_getter(db_service) as session:
646
- from langflow.services.auth.utils import create_super_user
647
-
648
- if await create_super_user(db=session, username=username, password=password):
649
- # Verify that the superuser was created
650
- from langflow.services.database.models.user.model import User
651
-
652
- stmt = select(User).where(User.username == username)
653
- user: User = (await session.exec(stmt)).first()
654
- if user is None or not user.is_superuser:
655
- typer.echo("Superuser creation failed.")
656
- return
657
- # Now create the first folder for the user
658
- result = await get_or_create_default_folder(session, user.id)
659
- if result:
660
- typer.echo("Default folder created successfully.")
661
- else:
662
- msg = "Could not create default folder."
663
- raise RuntimeError(msg)
664
- typer.echo("Superuser created successfully.")
655
+ asyncio.run(_create_superuser(username, password, auth_token))
665
656
 
666
- else:
657
+
658
+ async def _create_superuser(username: str, password: str, auth_token: str | None):
659
+ """Create a superuser."""
660
+ await initialize_services()
661
+
662
+ settings_service = get_settings_service()
663
+ # Check if superuser creation via CLI is enabled
664
+ if not settings_service.auth_settings.ENABLE_SUPERUSER_CLI:
665
+ typer.echo("Error: Superuser creation via CLI is disabled.")
666
+ typer.echo("Set LANGFLOW_ENABLE_SUPERUSER_CLI=true to enable this feature.")
667
+ raise typer.Exit(1)
668
+
669
+ if settings_service.auth_settings.AUTO_LOGIN:
670
+ # Force default credentials for AUTO_LOGIN mode
671
+ username = DEFAULT_SUPERUSER
672
+ password = DEFAULT_SUPERUSER_PASSWORD
673
+ else:
674
+ # Production mode - prompt for credentials if not provided
675
+ if not username:
676
+ username = typer.prompt("Username")
677
+ if not password:
678
+ password = typer.prompt("Password", hide_input=True)
679
+
680
+ from langflow.services.database.models.user.crud import get_all_superusers
681
+
682
+ existing_superusers = []
683
+ async with session_scope() as session:
684
+ # Note that the default superuser is created by the initialize_services() function,
685
+ # but leaving this check here in case we change that behavior
686
+ existing_superusers = await get_all_superusers(session)
687
+ is_first_setup = len(existing_superusers) == 0
688
+
689
+ # If AUTO_LOGIN is true, only allow default superuser creation
690
+ if settings_service.auth_settings.AUTO_LOGIN:
691
+ if not is_first_setup:
692
+ typer.echo("Error: Cannot create additional superusers when AUTO_LOGIN is enabled.")
693
+ typer.echo("AUTO_LOGIN mode is for development with only the default superuser.")
694
+ typer.echo("To create additional superusers:")
695
+ typer.echo("1. Set LANGFLOW_AUTO_LOGIN=false")
696
+ typer.echo("2. Run this command again with --auth-token")
697
+ raise typer.Exit(1)
698
+
699
+ typer.echo(f"AUTO_LOGIN enabled. Creating default superuser '{username}'...")
700
+ typer.echo(f"Note: Default credentials are {DEFAULT_SUPERUSER}/{DEFAULT_SUPERUSER_PASSWORD}")
701
+ # AUTO_LOGIN is false - production mode
702
+ elif is_first_setup:
703
+ typer.echo("No superusers found. Creating first superuser...")
704
+ else:
705
+ # Authentication is required in production mode
706
+ if not auth_token:
707
+ typer.echo("Error: Creating a superuser requires authentication.")
708
+ typer.echo("Please provide --auth-token with a valid superuser API key or JWT token.")
709
+ typer.echo("To get a token, use: `uv run langflow api_key`")
710
+ raise typer.Exit(1)
711
+
712
+ # Validate the auth token
713
+ try:
714
+ auth_user = None
715
+ async with session_scope() as session:
716
+ # Try JWT first
717
+ user = None
718
+ try:
719
+ user = await get_current_user_by_jwt(auth_token, session)
720
+ except (JWTError, HTTPException):
721
+ # Try API key
722
+ api_key_result = await check_key(session, auth_token)
723
+ if api_key_result and hasattr(api_key_result, "is_superuser"):
724
+ user = api_key_result
725
+ auth_user = user
726
+
727
+ if not auth_user or not auth_user.is_superuser:
728
+ typer.echo(
729
+ "Error: Invalid token or insufficient privileges. Only superusers can create other superusers."
730
+ )
731
+ raise typer.Exit(1)
732
+ except typer.Exit:
733
+ raise # Re-raise typer.Exit without wrapping
734
+ except Exception as e: # noqa: BLE001
735
+ typer.echo(f"Error: Authentication failed - {e!s}")
736
+ raise typer.Exit(1) from None
737
+
738
+ # Auth complete, create the superuser
739
+ async with session_scope() as session:
740
+ from langflow.services.auth.utils import create_super_user
741
+
742
+ if await create_super_user(db=session, username=username, password=password):
743
+ # Verify that the superuser was created
744
+ from langflow.services.database.models.user.model import User
745
+
746
+ stmt = select(User).where(User.username == username)
747
+ created_user: User = (await session.exec(stmt)).first()
748
+ if created_user is None or not created_user.is_superuser:
667
749
  typer.echo("Superuser creation failed.")
750
+ return
751
+ # Now create the first folder for the user
752
+ result = await get_or_create_default_folder(session, created_user.id)
753
+ if result:
754
+ typer.echo("Default folder created successfully.")
755
+ else:
756
+ msg = "Could not create default folder."
757
+ raise RuntimeError(msg)
668
758
 
669
- asyncio.run(_create_superuser())
759
+ # Log the superuser creation for audit purposes
760
+ logger.warning(
761
+ f"SECURITY AUDIT: New superuser '{username}' created via CLI command"
762
+ + (" by authenticated user" if auth_token else " (first-time setup)")
763
+ )
764
+ typer.echo("Superuser created successfully.")
765
+
766
+ else:
767
+ logger.error(f"SECURITY AUDIT: Failed attempt to create superuser '{username}' via CLI")
768
+ typer.echo("Superuser creation failed.")
670
769
 
671
770
 
672
771
  # command to copy the langflow database from the cache to the current directory
@@ -749,6 +848,7 @@ def api_key(
749
848
  settings_service = get_settings_service()
750
849
  auth_settings = settings_service.auth_settings
751
850
  if not auth_settings.AUTO_LOGIN:
851
+ # TODO: Allow non-auto-login users to create API keys via CLI
752
852
  typer.echo("Auto login is disabled. API keys cannot be created through the CLI.")
753
853
  return None
754
854
 
@@ -66,7 +66,7 @@ class KBRetrievalComponent(Component):
66
66
  display_name="Include Metadata",
67
67
  info="Whether to include all metadata and embeddings in the output. If false, only content is returned.",
68
68
  value=True,
69
- advanced=True,
69
+ advanced=False,
70
70
  ),
71
71
  ]
72
72
 
@@ -53,7 +53,7 @@ class SaveToFileComponent(Component):
53
53
  ),
54
54
  ]
55
55
 
56
- outputs = [Output(display_name="File Path", name="result", method="save_to_file")]
56
+ outputs = [Output(display_name="File Path", name="message", method="save_to_file")]
57
57
 
58
58
  async def save_to_file(self) -> Message:
59
59
  """Save the input to a file and upload it, returning a confirmation message."""
@@ -2,11 +2,11 @@
2
2
  "data": {
3
3
  "edges": [
4
4
  {
5
- "className": "",
5
+ "animated": false,
6
6
  "data": {
7
7
  "sourceHandle": {
8
8
  "dataType": "TextInput",
9
- "id": "TextInput-Z3rM3",
9
+ "id": "TextInput-WyJxO",
10
10
  "name": "text",
11
11
  "output_types": [
12
12
  "Message"
@@ -14,25 +14,26 @@
14
14
  },
15
15
  "targetHandle": {
16
16
  "fieldName": "search_query",
17
- "id": "KBRetrieval-tGoBR",
17
+ "id": "KBRetrieval-zz3I0",
18
18
  "inputTypes": [
19
19
  "Message"
20
20
  ],
21
21
  "type": "str"
22
22
  }
23
23
  },
24
- "id": "xy-edge__TextInput-Z3rM3{œdataTypeœ:œTextInputœ,œidœ:œTextInput-Z3rM3œ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-KBRetrieval-tGoBR{œfieldNameœ:œsearch_queryœ,œidœ:œKBRetrieval-tGoBRœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
25
- "source": "TextInput-Z3rM3",
26
- "sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-Z3rM3œ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
27
- "target": "KBRetrieval-tGoBR",
28
- "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œKBRetrieval-tGoBRœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
24
+ "id": "xy-edge__TextInput-WyJxO{œdataTypeœ:œTextInputœ,œidœ:œTextInput-WyJxOœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-KBRetrieval-zz3I0{œfieldNameœ:œsearch_queryœ,œidœ:œKBRetrieval-zz3I0œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
25
+ "selected": false,
26
+ "source": "TextInput-WyJxO",
27
+ "sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-WyJxOœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
28
+ "target": "KBRetrieval-zz3I0",
29
+ "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œKBRetrieval-zz3I0œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
29
30
  },
30
31
  {
31
- "className": "",
32
+ "animated": false,
32
33
  "data": {
33
34
  "sourceHandle": {
34
35
  "dataType": "KBRetrieval",
35
- "id": "KBRetrieval-tGoBR",
36
+ "id": "KBRetrieval-zz3I0",
36
37
  "name": "chroma_kb_data",
37
38
  "output_types": [
38
39
  "DataFrame"
@@ -40,7 +41,7 @@
40
41
  },
41
42
  "targetHandle": {
42
43
  "fieldName": "input_value",
43
- "id": "ChatOutput-tixOe",
44
+ "id": "ChatOutput-N7nxz",
44
45
  "inputTypes": [
45
46
  "Data",
46
47
  "DataFrame",
@@ -49,17 +50,18 @@
49
50
  "type": "other"
50
51
  }
51
52
  },
52
- "id": "xy-edge__KBRetrieval-tGoBR{œdataTypeœ:œKBRetrievalœ,œidœ:œKBRetrieval-tGoBRœ,œnameœ:œchroma_kb_dataœ,œoutput_typesœ:[œDataFrameœ]}-ChatOutput-tixOe{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-tixOeœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œotherœ}",
53
- "source": "KBRetrieval-tGoBR",
54
- "sourceHandle": "{œdataTypeœ: œKBRetrievalœ, œidœ: œKBRetrieval-tGoBRœ, œnameœ: œchroma_kb_dataœ, œoutput_typesœ: [œDataFrameœ]}",
55
- "target": "ChatOutput-tixOe",
56
- "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-tixOeœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œotherœ}"
53
+ "id": "xy-edge__KBRetrieval-zz3I0{œdataTypeœ:œKBRetrievalœ,œidœ:œKBRetrieval-zz3I0œ,œnameœ:œchroma_kb_dataœ,œoutput_typesœ:[œDataFrameœ]}-ChatOutput-N7nxz{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-N7nxzœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œotherœ}",
54
+ "selected": false,
55
+ "source": "KBRetrieval-zz3I0",
56
+ "sourceHandle": "{œdataTypeœ: œKBRetrievalœ, œidœ: œKBRetrieval-zz3I0œ, œnameœ: œchroma_kb_dataœ, œoutput_typesœ: [œDataFrameœ]}",
57
+ "target": "ChatOutput-N7nxz",
58
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-N7nxzœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œotherœ}"
57
59
  }
58
60
  ],
59
61
  "nodes": [
60
62
  {
61
63
  "data": {
62
- "id": "note-YyBfz",
64
+ "id": "note-f86G8",
63
65
  "node": {
64
66
  "description": "## Knowledge Retrieval\n\nA stand-alone component handles the retrieval of ingested knowledge from existing knowledge bases. To retrieve knowledge:\n\n1. Select your knowledge base from the Knowledge Base dropdown. If you do not see it, choose \"Refresh List\".\n2. (Optional) Enter a Search Query to be performed against the knowledge base.\n\nNote that by default, 5 results are returned, which can be configured by clicking Controls at the top of the component.\n",
65
67
  "display_name": "",
@@ -70,7 +72,7 @@
70
72
  },
71
73
  "dragging": false,
72
74
  "height": 384,
73
- "id": "note-YyBfz",
75
+ "id": "note-f86G8",
74
76
  "measured": {
75
77
  "height": 384,
76
78
  "width": 371
@@ -86,7 +88,7 @@
86
88
  },
87
89
  {
88
90
  "data": {
89
- "id": "TextInput-Z3rM3",
91
+ "id": "TextInput-WyJxO",
90
92
  "node": {
91
93
  "base_classes": [
92
94
  "Message"
@@ -180,7 +182,7 @@
180
182
  "type": "TextInput"
181
183
  },
182
184
  "dragging": false,
183
- "id": "TextInput-Z3rM3",
185
+ "id": "TextInput-WyJxO",
184
186
  "measured": {
185
187
  "height": 204,
186
188
  "width": 320
@@ -194,7 +196,7 @@
194
196
  },
195
197
  {
196
198
  "data": {
197
- "id": "ChatOutput-tixOe",
199
+ "id": "ChatOutput-N7nxz",
198
200
  "node": {
199
201
  "base_classes": [
200
202
  "Message"
@@ -492,7 +494,7 @@
492
494
  "type": "ChatOutput"
493
495
  },
494
496
  "dragging": false,
495
- "id": "ChatOutput-tixOe",
497
+ "id": "ChatOutput-N7nxz",
496
498
  "measured": {
497
499
  "height": 48,
498
500
  "width": 192
@@ -506,7 +508,7 @@
506
508
  },
507
509
  {
508
510
  "data": {
509
- "id": "KBRetrieval-tGoBR",
511
+ "id": "KBRetrieval-zz3I0",
510
512
  "node": {
511
513
  "base_classes": [
512
514
  "DataFrame"
@@ -527,10 +529,10 @@
527
529
  ],
528
530
  "frozen": false,
529
531
  "icon": "database",
530
- "last_updated": "2025-08-13T19:46:57.894Z",
532
+ "last_updated": "2025-08-14T17:19:22.182Z",
531
533
  "legacy": false,
532
534
  "metadata": {
533
- "code_hash": "f82365a0977f",
535
+ "code_hash": "ee2b66958f09",
534
536
  "module": "langflow.components.data.kb_retrieval.KBRetrievalComponent"
535
537
  },
536
538
  "minimized": false,
@@ -587,11 +589,11 @@
587
589
  "show": true,
588
590
  "title_case": false,
589
591
  "type": "code",
590
- "value": "import json\nfrom pathlib import Path\nfrom typing import Any\n\nfrom cryptography.fernet import InvalidToken\nfrom langchain_chroma import Chroma\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.services.auth.utils import decrypt_api_key\nfrom langflow.services.deps import get_settings_service\n\nsettings = get_settings_service().settings\nknowledge_directory = settings.knowledge_bases_dir\nif not knowledge_directory:\n msg = \"Knowledge bases directory is not set in the settings.\"\n raise ValueError(msg)\nKNOWLEDGE_BASES_ROOT_PATH = Path(knowledge_directory).expanduser()\n\n\nclass KBRetrievalComponent(Component):\n display_name = \"Knowledge Retrieval\"\n description = \"Search and retrieve data from knowledge.\"\n icon = \"database\"\n name = \"KBRetrieval\"\n\n inputs = [\n DropdownInput(\n name=\"knowledge_base\",\n display_name=\"Knowledge\",\n info=\"Select the knowledge to load data from.\",\n required=True,\n options=[\n str(d.name) for d in KNOWLEDGE_BASES_ROOT_PATH.iterdir() if not d.name.startswith(\".\") and d.is_dir()\n ]\n if KNOWLEDGE_BASES_ROOT_PATH.exists()\n else [],\n refresh_button=True,\n real_time_refresh=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Embedding Provider API Key\",\n info=\"API key for the embedding provider to generate embeddings.\",\n advanced=True,\n required=False,\n ),\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n info=\"Optional search query to filter knowledge base data.\",\n ),\n IntInput(\n name=\"top_k\",\n display_name=\"Top K Results\",\n info=\"Number of top results to return from the knowledge base.\",\n value=5,\n advanced=True,\n required=False,\n ),\n BoolInput(\n name=\"include_metadata\",\n display_name=\"Include Metadata\",\n info=\"Whether to include all metadata and embeddings in the output. If false, only content is returned.\",\n value=True,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(\n name=\"chroma_kb_data\",\n display_name=\"Results\",\n method=\"get_chroma_kb_data\",\n info=\"Returns the data from the selected knowledge base.\",\n ),\n ]\n\n def _get_knowledge_bases(self) -> list[str]:\n \"\"\"Retrieve a list of available knowledge bases.\n\n Returns:\n A list of knowledge base names.\n \"\"\"\n if not KNOWLEDGE_BASES_ROOT_PATH.exists():\n return []\n\n return [str(d.name) for d in KNOWLEDGE_BASES_ROOT_PATH.iterdir() if not d.name.startswith(\".\") and d.is_dir()]\n\n def update_build_config(self, build_config, field_value, field_name=None): # noqa: ARG002\n if field_name == \"knowledge_base\":\n # Update the knowledge base options dynamically\n build_config[\"knowledge_base\"][\"options\"] = self._get_knowledge_bases()\n\n # If the selected knowledge base is not available, reset it\n if build_config[\"knowledge_base\"][\"value\"] not in build_config[\"knowledge_base\"][\"options\"]:\n build_config[\"knowledge_base\"][\"value\"] = None\n\n return build_config\n\n def _get_kb_metadata(self, kb_path: Path) -> dict:\n \"\"\"Load and process knowledge base metadata.\"\"\"\n metadata: dict[str, Any] = {}\n metadata_file = kb_path / \"embedding_metadata.json\"\n if not metadata_file.exists():\n logger.warning(f\"Embedding metadata file not found at {metadata_file}\")\n return metadata\n\n try:\n with metadata_file.open(\"r\", encoding=\"utf-8\") as f:\n metadata = json.load(f)\n except json.JSONDecodeError:\n logger.error(f\"Error decoding JSON from {metadata_file}\")\n return {}\n\n # Decrypt API key if it exists\n if \"api_key\" in metadata and metadata.get(\"api_key\"):\n settings_service = get_settings_service()\n try:\n decrypted_key = decrypt_api_key(metadata[\"api_key\"], settings_service)\n metadata[\"api_key\"] = decrypted_key\n except (InvalidToken, TypeError, ValueError) as e:\n logger.error(f\"Could not decrypt API key. Please provide it manually. Error: {e}\")\n metadata[\"api_key\"] = None\n return metadata\n\n def _build_embeddings(self, metadata: dict):\n \"\"\"Build embedding model from metadata.\"\"\"\n provider = metadata.get(\"embedding_provider\")\n model = metadata.get(\"embedding_model\")\n api_key = metadata.get(\"api_key\")\n chunk_size = metadata.get(\"chunk_size\")\n\n # If user provided a key in the input, it overrides the stored one.\n if self.api_key and self.api_key.get_secret_value():\n api_key = self.api_key.get_secret_value()\n\n # Handle various providers\n if provider == \"OpenAI\":\n from langchain_openai import OpenAIEmbeddings\n\n if not api_key:\n msg = \"OpenAI API key is required. Provide it in the component's advanced settings.\"\n raise ValueError(msg)\n return OpenAIEmbeddings(\n model=model,\n api_key=api_key,\n chunk_size=chunk_size,\n )\n if provider == \"HuggingFace\":\n from langchain_huggingface import HuggingFaceEmbeddings\n\n return HuggingFaceEmbeddings(\n model=model,\n )\n if provider == \"Cohere\":\n from langchain_cohere import CohereEmbeddings\n\n if not api_key:\n msg = \"Cohere API key is required when using Cohere provider\"\n raise ValueError(msg)\n return CohereEmbeddings(\n model=model,\n cohere_api_key=api_key,\n )\n if provider == \"Custom\":\n # For custom embedding models, we would need additional configuration\n msg = \"Custom embedding models not yet supported\"\n raise NotImplementedError(msg)\n # Add other providers here if they become supported in ingest\n msg = f\"Embedding provider '{provider}' is not supported for retrieval.\"\n raise NotImplementedError(msg)\n\n def get_chroma_kb_data(self) -> DataFrame:\n \"\"\"Retrieve data from the selected knowledge base by reading the Chroma collection.\n\n Returns:\n A DataFrame containing the data rows from the knowledge base.\n \"\"\"\n kb_path = KNOWLEDGE_BASES_ROOT_PATH / self.knowledge_base\n\n metadata = self._get_kb_metadata(kb_path)\n if not metadata:\n msg = f\"Metadata not found for knowledge base: {self.knowledge_base}. Ensure it has been indexed.\"\n raise ValueError(msg)\n\n # Build the embedder for the knowledge base\n embedding_function = self._build_embeddings(metadata)\n\n # Load vector store\n chroma = Chroma(\n persist_directory=str(kb_path),\n embedding_function=embedding_function,\n collection_name=self.knowledge_base,\n )\n\n # If a search query is provided, perform a similarity search\n if self.search_query:\n # Use the search query to perform a similarity search\n logger.info(f\"Performing similarity search with query: {self.search_query}\")\n results = chroma.similarity_search_with_score(\n query=self.search_query or \"\",\n k=self.top_k,\n )\n else:\n results = chroma.similarity_search(\n query=self.search_query or \"\",\n k=self.top_k,\n )\n\n # For each result, make it a tuple to match the expected output format\n results = [(doc, 0) for doc in results] # Assign a dummy score of 0\n\n # If metadata is enabled, get embeddings for the results\n id_to_embedding = {}\n if self.include_metadata and results:\n doc_ids = [doc[0].metadata.get(\"_id\") for doc in results if doc[0].metadata.get(\"_id\")]\n\n # Only proceed if we have valid document IDs\n if doc_ids:\n # Access underlying client to get embeddings\n collection = chroma._client.get_collection(name=self.knowledge_base)\n embeddings_result = collection.get(where={\"_id\": {\"$in\": doc_ids}}, include=[\"embeddings\", \"metadatas\"])\n\n # Create a mapping from document ID to embedding\n for i, metadata in enumerate(embeddings_result.get(\"metadatas\", [])):\n if metadata and \"_id\" in metadata:\n id_to_embedding[metadata[\"_id\"]] = embeddings_result[\"embeddings\"][i]\n\n # Build output data based on include_metadata setting\n data_list = []\n for doc in results:\n if self.include_metadata:\n # Include all metadata, embeddings, and content\n kwargs = {\n \"content\": doc[0].page_content,\n **doc[0].metadata,\n }\n if self.search_query:\n kwargs[\"_score\"] = -1 * doc[1]\n kwargs[\"_embeddings\"] = id_to_embedding.get(doc[0].metadata.get(\"_id\"))\n else:\n # Only include content\n kwargs = {\n \"content\": doc[0].page_content,\n }\n\n data_list.append(Data(**kwargs))\n\n # Return the DataFrame containing the data\n return DataFrame(data=data_list)\n"
592
+ "value": "import json\nfrom pathlib import Path\nfrom typing import Any\n\nfrom cryptography.fernet import InvalidToken\nfrom langchain_chroma import Chroma\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.services.auth.utils import decrypt_api_key\nfrom langflow.services.deps import get_settings_service\n\nsettings = get_settings_service().settings\nknowledge_directory = settings.knowledge_bases_dir\nif not knowledge_directory:\n msg = \"Knowledge bases directory is not set in the settings.\"\n raise ValueError(msg)\nKNOWLEDGE_BASES_ROOT_PATH = Path(knowledge_directory).expanduser()\n\n\nclass KBRetrievalComponent(Component):\n display_name = \"Knowledge Retrieval\"\n description = \"Search and retrieve data from knowledge.\"\n icon = \"database\"\n name = \"KBRetrieval\"\n\n inputs = [\n DropdownInput(\n name=\"knowledge_base\",\n display_name=\"Knowledge\",\n info=\"Select the knowledge to load data from.\",\n required=True,\n options=[\n str(d.name) for d in KNOWLEDGE_BASES_ROOT_PATH.iterdir() if not d.name.startswith(\".\") and d.is_dir()\n ]\n if KNOWLEDGE_BASES_ROOT_PATH.exists()\n else [],\n refresh_button=True,\n real_time_refresh=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Embedding Provider API Key\",\n info=\"API key for the embedding provider to generate embeddings.\",\n advanced=True,\n required=False,\n ),\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n info=\"Optional search query to filter knowledge base data.\",\n ),\n IntInput(\n name=\"top_k\",\n display_name=\"Top K Results\",\n info=\"Number of top results to return from the knowledge base.\",\n value=5,\n advanced=True,\n required=False,\n ),\n BoolInput(\n name=\"include_metadata\",\n display_name=\"Include Metadata\",\n info=\"Whether to include all metadata and embeddings in the output. If false, only content is returned.\",\n value=True,\n advanced=False,\n ),\n ]\n\n outputs = [\n Output(\n name=\"chroma_kb_data\",\n display_name=\"Results\",\n method=\"get_chroma_kb_data\",\n info=\"Returns the data from the selected knowledge base.\",\n ),\n ]\n\n def _get_knowledge_bases(self) -> list[str]:\n \"\"\"Retrieve a list of available knowledge bases.\n\n Returns:\n A list of knowledge base names.\n \"\"\"\n if not KNOWLEDGE_BASES_ROOT_PATH.exists():\n return []\n\n return [str(d.name) for d in KNOWLEDGE_BASES_ROOT_PATH.iterdir() if not d.name.startswith(\".\") and d.is_dir()]\n\n def update_build_config(self, build_config, field_value, field_name=None): # noqa: ARG002\n if field_name == \"knowledge_base\":\n # Update the knowledge base options dynamically\n build_config[\"knowledge_base\"][\"options\"] = self._get_knowledge_bases()\n\n # If the selected knowledge base is not available, reset it\n if build_config[\"knowledge_base\"][\"value\"] not in build_config[\"knowledge_base\"][\"options\"]:\n build_config[\"knowledge_base\"][\"value\"] = None\n\n return build_config\n\n def _get_kb_metadata(self, kb_path: Path) -> dict:\n \"\"\"Load and process knowledge base metadata.\"\"\"\n metadata: dict[str, Any] = {}\n metadata_file = kb_path / \"embedding_metadata.json\"\n if not metadata_file.exists():\n logger.warning(f\"Embedding metadata file not found at {metadata_file}\")\n return metadata\n\n try:\n with metadata_file.open(\"r\", encoding=\"utf-8\") as f:\n metadata = json.load(f)\n except json.JSONDecodeError:\n logger.error(f\"Error decoding JSON from {metadata_file}\")\n return {}\n\n # Decrypt API key if it exists\n if \"api_key\" in metadata and metadata.get(\"api_key\"):\n settings_service = get_settings_service()\n try:\n decrypted_key = decrypt_api_key(metadata[\"api_key\"], settings_service)\n metadata[\"api_key\"] = decrypted_key\n except (InvalidToken, TypeError, ValueError) as e:\n logger.error(f\"Could not decrypt API key. Please provide it manually. Error: {e}\")\n metadata[\"api_key\"] = None\n return metadata\n\n def _build_embeddings(self, metadata: dict):\n \"\"\"Build embedding model from metadata.\"\"\"\n provider = metadata.get(\"embedding_provider\")\n model = metadata.get(\"embedding_model\")\n api_key = metadata.get(\"api_key\")\n chunk_size = metadata.get(\"chunk_size\")\n\n # If user provided a key in the input, it overrides the stored one.\n if self.api_key and self.api_key.get_secret_value():\n api_key = self.api_key.get_secret_value()\n\n # Handle various providers\n if provider == \"OpenAI\":\n from langchain_openai import OpenAIEmbeddings\n\n if not api_key:\n msg = \"OpenAI API key is required. Provide it in the component's advanced settings.\"\n raise ValueError(msg)\n return OpenAIEmbeddings(\n model=model,\n api_key=api_key,\n chunk_size=chunk_size,\n )\n if provider == \"HuggingFace\":\n from langchain_huggingface import HuggingFaceEmbeddings\n\n return HuggingFaceEmbeddings(\n model=model,\n )\n if provider == \"Cohere\":\n from langchain_cohere import CohereEmbeddings\n\n if not api_key:\n msg = \"Cohere API key is required when using Cohere provider\"\n raise ValueError(msg)\n return CohereEmbeddings(\n model=model,\n cohere_api_key=api_key,\n )\n if provider == \"Custom\":\n # For custom embedding models, we would need additional configuration\n msg = \"Custom embedding models not yet supported\"\n raise NotImplementedError(msg)\n # Add other providers here if they become supported in ingest\n msg = f\"Embedding provider '{provider}' is not supported for retrieval.\"\n raise NotImplementedError(msg)\n\n def get_chroma_kb_data(self) -> DataFrame:\n \"\"\"Retrieve data from the selected knowledge base by reading the Chroma collection.\n\n Returns:\n A DataFrame containing the data rows from the knowledge base.\n \"\"\"\n kb_path = KNOWLEDGE_BASES_ROOT_PATH / self.knowledge_base\n\n metadata = self._get_kb_metadata(kb_path)\n if not metadata:\n msg = f\"Metadata not found for knowledge base: {self.knowledge_base}. Ensure it has been indexed.\"\n raise ValueError(msg)\n\n # Build the embedder for the knowledge base\n embedding_function = self._build_embeddings(metadata)\n\n # Load vector store\n chroma = Chroma(\n persist_directory=str(kb_path),\n embedding_function=embedding_function,\n collection_name=self.knowledge_base,\n )\n\n # If a search query is provided, perform a similarity search\n if self.search_query:\n # Use the search query to perform a similarity search\n logger.info(f\"Performing similarity search with query: {self.search_query}\")\n results = chroma.similarity_search_with_score(\n query=self.search_query or \"\",\n k=self.top_k,\n )\n else:\n results = chroma.similarity_search(\n query=self.search_query or \"\",\n k=self.top_k,\n )\n\n # For each result, make it a tuple to match the expected output format\n results = [(doc, 0) for doc in results] # Assign a dummy score of 0\n\n # If metadata is enabled, get embeddings for the results\n id_to_embedding = {}\n if self.include_metadata and results:\n doc_ids = [doc[0].metadata.get(\"_id\") for doc in results if doc[0].metadata.get(\"_id\")]\n\n # Only proceed if we have valid document IDs\n if doc_ids:\n # Access underlying client to get embeddings\n collection = chroma._client.get_collection(name=self.knowledge_base)\n embeddings_result = collection.get(where={\"_id\": {\"$in\": doc_ids}}, include=[\"embeddings\", \"metadatas\"])\n\n # Create a mapping from document ID to embedding\n for i, metadata in enumerate(embeddings_result.get(\"metadatas\", [])):\n if metadata and \"_id\" in metadata:\n id_to_embedding[metadata[\"_id\"]] = embeddings_result[\"embeddings\"][i]\n\n # Build output data based on include_metadata setting\n data_list = []\n for doc in results:\n if self.include_metadata:\n # Include all metadata, embeddings, and content\n kwargs = {\n \"content\": doc[0].page_content,\n **doc[0].metadata,\n }\n if self.search_query:\n kwargs[\"_score\"] = -1 * doc[1]\n kwargs[\"_embeddings\"] = id_to_embedding.get(doc[0].metadata.get(\"_id\"))\n else:\n # Only include content\n kwargs = {\n \"content\": doc[0].page_content,\n }\n\n data_list.append(Data(**kwargs))\n\n # Return the DataFrame containing the data\n return DataFrame(data=data_list)\n"
591
593
  },
592
594
  "include_metadata": {
593
595
  "_input_type": "BoolInput",
594
- "advanced": true,
596
+ "advanced": false,
595
597
  "display_name": "Include Metadata",
596
598
  "dynamic": false,
597
599
  "info": "Whether to include all metadata and embeddings in the output. If false, only content is returned.",
@@ -678,28 +680,28 @@
678
680
  "type": "KBRetrieval"
679
681
  },
680
682
  "dragging": false,
681
- "id": "KBRetrieval-tGoBR",
683
+ "id": "KBRetrieval-zz3I0",
682
684
  "measured": {
683
- "height": 286,
685
+ "height": 329,
684
686
  "width": 320
685
687
  },
686
688
  "position": {
687
- "x": 640.6283193600648,
688
- "y": -313.9694258557284
689
+ "x": 616.6226476085393,
690
+ "y": -343.13068334363356
689
691
  },
690
692
  "selected": false,
691
693
  "type": "genericNode"
692
694
  }
693
695
  ],
694
696
  "viewport": {
695
- "x": 285.0464459586908,
696
- "y": 588.7377652547386,
697
- "zoom": 0.9833370380356916
697
+ "x": 177.06633386268413,
698
+ "y": 482.8027480187026,
699
+ "zoom": 0.8999566725119924
698
700
  }
699
701
  },
700
702
  "description": "An example of performing a vector search against data in a Knowledge Base to retrieve relevant documents.",
701
703
  "endpoint_name": null,
702
- "id": "670745f6-08b1-480e-bdaf-64ba74967cba",
704
+ "id": "5487ee05-73d5-4b12-9b41-bc4c3a2f9326",
703
705
  "is_component": false,
704
706
  "last_tested_version": "1.5.0.post1",
705
707
  "name": "Knowledge Retrieval",
@@ -1208,7 +1208,7 @@
1208
1208
  "legacy": false,
1209
1209
  "lf_version": "1.4.3",
1210
1210
  "metadata": {
1211
- "code_hash": "6f244023207e",
1211
+ "code_hash": "9134859cf24d",
1212
1212
  "module": "langflow.components.processing.save_file.SaveToFileComponent"
1213
1213
  },
1214
1214
  "minimized": false,
@@ -1220,8 +1220,8 @@
1220
1220
  "display_name": "File Path",
1221
1221
  "group_outputs": false,
1222
1222
  "method": "save_to_file",
1223
- "name": "result",
1224
- "selected": "Text",
1223
+ "name": "message",
1224
+ "selected": "Message",
1225
1225
  "tool_mode": true,
1226
1226
  "types": [
1227
1227
  "Message"
@@ -1248,7 +1248,7 @@
1248
1248
  "show": true,
1249
1249
  "title_case": false,
1250
1250
  "type": "code",
1251
- "value": "import json\nfrom collections.abc import AsyncIterator, Iterator\nfrom pathlib import Path\n\nimport orjson\nimport pandas as pd\nfrom fastapi import UploadFile\nfrom fastapi.encoders import jsonable_encoder\n\nfrom langflow.api.v2.files import upload_user_file\nfrom langflow.custom import Component\nfrom langflow.io import DropdownInput, HandleInput, StrInput\nfrom langflow.schema import Data, DataFrame, Message\nfrom langflow.services.auth.utils import create_user_longterm_token\nfrom langflow.services.database.models.user.crud import get_user_by_id\nfrom langflow.services.deps import get_session, get_settings_service, get_storage_service\nfrom langflow.template.field.base import Output\n\n\nclass SaveToFileComponent(Component):\n display_name = \"Save File\"\n description = \"Save data to a local file in the selected format.\"\n documentation: str = \"https://docs.langflow.org/components-processing#save-file\"\n icon = \"save\"\n name = \"SaveToFile\"\n\n # File format options for different types\n DATA_FORMAT_CHOICES = [\"csv\", \"excel\", \"json\", \"markdown\"]\n MESSAGE_FORMAT_CHOICES = [\"txt\", \"json\", \"markdown\"]\n\n inputs = [\n HandleInput(\n name=\"input\",\n display_name=\"Input\",\n info=\"The input to save.\",\n dynamic=True,\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n StrInput(\n name=\"file_name\",\n display_name=\"File Name\",\n info=\"Name file will be saved as (without extension).\",\n required=True,\n ),\n DropdownInput(\n name=\"file_format\",\n display_name=\"File Format\",\n options=list(dict.fromkeys(DATA_FORMAT_CHOICES + MESSAGE_FORMAT_CHOICES)),\n info=\"Select the file format to save the input. If not provided, the default format will be used.\",\n value=\"\",\n advanced=True,\n ),\n ]\n\n outputs = [Output(display_name=\"File Path\", name=\"result\", method=\"save_to_file\")]\n\n async def save_to_file(self) -> Message:\n \"\"\"Save the input to a file and upload it, returning a confirmation message.\"\"\"\n # Validate inputs\n if not self.file_name:\n msg = \"File name must be provided.\"\n raise ValueError(msg)\n if not self._get_input_type():\n msg = \"Input type is not set.\"\n raise ValueError(msg)\n\n # Validate file format based on input type\n file_format = self.file_format or self._get_default_format()\n allowed_formats = (\n self.MESSAGE_FORMAT_CHOICES if self._get_input_type() == \"Message\" else self.DATA_FORMAT_CHOICES\n )\n if file_format not in allowed_formats:\n msg = f\"Invalid file format '{file_format}' for {self._get_input_type()}. Allowed: {allowed_formats}\"\n raise ValueError(msg)\n\n # Prepare file path\n file_path = Path(self.file_name).expanduser()\n if not file_path.parent.exists():\n file_path.parent.mkdir(parents=True, exist_ok=True)\n file_path = self._adjust_file_path_with_format(file_path, file_format)\n\n # Save the input to file based on type\n if self._get_input_type() == \"DataFrame\":\n confirmation = self._save_dataframe(self.input, file_path, file_format)\n elif self._get_input_type() == \"Data\":\n confirmation = self._save_data(self.input, file_path, file_format)\n elif self._get_input_type() == \"Message\":\n confirmation = await self._save_message(self.input, file_path, file_format)\n else:\n msg = f\"Unsupported input type: {self._get_input_type()}\"\n raise ValueError(msg)\n\n # Upload the saved file\n await self._upload_file(file_path)\n\n # Return the final file path and confirmation message\n final_path = Path.cwd() / file_path if not file_path.is_absolute() else file_path\n\n return Message(text=f\"{confirmation} at {final_path}\")\n\n def _get_input_type(self) -> str:\n \"\"\"Determine the input type based on the provided input.\"\"\"\n # Use exact type checking (type() is) instead of isinstance() to avoid inheritance issues.\n # Since Message inherits from Data, isinstance(message, Data) would return True for Message objects,\n # causing Message inputs to be incorrectly identified as Data type.\n if type(self.input) is DataFrame:\n return \"DataFrame\"\n if type(self.input) is Message:\n return \"Message\"\n if type(self.input) is Data:\n return \"Data\"\n msg = f\"Unsupported input type: {type(self.input)}\"\n raise ValueError(msg)\n\n def _get_default_format(self) -> str:\n \"\"\"Return the default file format based on input type.\"\"\"\n if self._get_input_type() == \"DataFrame\":\n return \"csv\"\n if self._get_input_type() == \"Data\":\n return \"json\"\n if self._get_input_type() == \"Message\":\n return \"json\"\n return \"json\" # Fallback\n\n def _adjust_file_path_with_format(self, path: Path, fmt: str) -> Path:\n \"\"\"Adjust the file path to include the correct extension.\"\"\"\n file_extension = path.suffix.lower().lstrip(\".\")\n if fmt == \"excel\":\n return Path(f\"{path}.xlsx\").expanduser() if file_extension not in [\"xlsx\", \"xls\"] else path\n return Path(f\"{path}.{fmt}\").expanduser() if file_extension != fmt else path\n\n async def _upload_file(self, file_path: Path) -> None:\n \"\"\"Upload the saved file using the upload_user_file service.\"\"\"\n if not file_path.exists():\n msg = f\"File not found: {file_path}\"\n raise FileNotFoundError(msg)\n\n with file_path.open(\"rb\") as f:\n async for db in get_session():\n user_id, _ = await create_user_longterm_token(db)\n current_user = await get_user_by_id(db, user_id)\n\n await upload_user_file(\n file=UploadFile(filename=file_path.name, file=f, size=file_path.stat().st_size),\n session=db,\n current_user=current_user,\n storage_service=get_storage_service(),\n settings_service=get_settings_service(),\n )\n\n def _save_dataframe(self, dataframe: DataFrame, path: Path, fmt: str) -> str:\n \"\"\"Save a DataFrame to the specified file format.\"\"\"\n if fmt == \"csv\":\n dataframe.to_csv(path, index=False)\n elif fmt == \"excel\":\n dataframe.to_excel(path, index=False, engine=\"openpyxl\")\n elif fmt == \"json\":\n dataframe.to_json(path, orient=\"records\", indent=2)\n elif fmt == \"markdown\":\n path.write_text(dataframe.to_markdown(index=False), encoding=\"utf-8\")\n else:\n msg = f\"Unsupported DataFrame format: {fmt}\"\n raise ValueError(msg)\n return f\"DataFrame saved successfully as '{path}'\"\n\n def _save_data(self, data: Data, path: Path, fmt: str) -> str:\n \"\"\"Save a Data object to the specified file format.\"\"\"\n if fmt == \"csv\":\n pd.DataFrame(data.data).to_csv(path, index=False)\n elif fmt == \"excel\":\n pd.DataFrame(data.data).to_excel(path, index=False, engine=\"openpyxl\")\n elif fmt == \"json\":\n path.write_text(\n orjson.dumps(jsonable_encoder(data.data), option=orjson.OPT_INDENT_2).decode(\"utf-8\"), encoding=\"utf-8\"\n )\n elif fmt == \"markdown\":\n path.write_text(pd.DataFrame(data.data).to_markdown(index=False), encoding=\"utf-8\")\n else:\n msg = f\"Unsupported Data format: {fmt}\"\n raise ValueError(msg)\n return f\"Data saved successfully as '{path}'\"\n\n async def _save_message(self, message: Message, path: Path, fmt: str) -> str:\n \"\"\"Save a Message to the specified file format, handling async iterators.\"\"\"\n content = \"\"\n if message.text is None:\n content = \"\"\n elif isinstance(message.text, AsyncIterator):\n async for item in message.text:\n content += str(item) + \" \"\n content = content.strip()\n elif isinstance(message.text, Iterator):\n content = \" \".join(str(item) for item in message.text)\n else:\n content = str(message.text)\n\n if fmt == \"txt\":\n path.write_text(content, encoding=\"utf-8\")\n elif fmt == \"json\":\n path.write_text(json.dumps({\"message\": content}, indent=2), encoding=\"utf-8\")\n elif fmt == \"markdown\":\n path.write_text(f\"**Message:**\\n\\n{content}\", encoding=\"utf-8\")\n else:\n msg = f\"Unsupported Message format: {fmt}\"\n raise ValueError(msg)\n return f\"Message saved successfully as '{path}'\"\n"
1251
+ "value": "import json\nfrom collections.abc import AsyncIterator, Iterator\nfrom pathlib import Path\n\nimport orjson\nimport pandas as pd\nfrom fastapi import UploadFile\nfrom fastapi.encoders import jsonable_encoder\n\nfrom langflow.api.v2.files import upload_user_file\nfrom langflow.custom import Component\nfrom langflow.io import DropdownInput, HandleInput, StrInput\nfrom langflow.schema import Data, DataFrame, Message\nfrom langflow.services.auth.utils import create_user_longterm_token\nfrom langflow.services.database.models.user.crud import get_user_by_id\nfrom langflow.services.deps import get_session, get_settings_service, get_storage_service\nfrom langflow.template.field.base import Output\n\n\nclass SaveToFileComponent(Component):\n display_name = \"Save File\"\n description = \"Save data to a local file in the selected format.\"\n documentation: str = \"https://docs.langflow.org/components-processing#save-file\"\n icon = \"save\"\n name = \"SaveToFile\"\n\n # File format options for different types\n DATA_FORMAT_CHOICES = [\"csv\", \"excel\", \"json\", \"markdown\"]\n MESSAGE_FORMAT_CHOICES = [\"txt\", \"json\", \"markdown\"]\n\n inputs = [\n HandleInput(\n name=\"input\",\n display_name=\"Input\",\n info=\"The input to save.\",\n dynamic=True,\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n StrInput(\n name=\"file_name\",\n display_name=\"File Name\",\n info=\"Name file will be saved as (without extension).\",\n required=True,\n ),\n DropdownInput(\n name=\"file_format\",\n display_name=\"File Format\",\n options=list(dict.fromkeys(DATA_FORMAT_CHOICES + MESSAGE_FORMAT_CHOICES)),\n info=\"Select the file format to save the input. If not provided, the default format will be used.\",\n value=\"\",\n advanced=True,\n ),\n ]\n\n outputs = [Output(display_name=\"File Path\", name=\"message\", method=\"save_to_file\")]\n\n async def save_to_file(self) -> Message:\n \"\"\"Save the input to a file and upload it, returning a confirmation message.\"\"\"\n # Validate inputs\n if not self.file_name:\n msg = \"File name must be provided.\"\n raise ValueError(msg)\n if not self._get_input_type():\n msg = \"Input type is not set.\"\n raise ValueError(msg)\n\n # Validate file format based on input type\n file_format = self.file_format or self._get_default_format()\n allowed_formats = (\n self.MESSAGE_FORMAT_CHOICES if self._get_input_type() == \"Message\" else self.DATA_FORMAT_CHOICES\n )\n if file_format not in allowed_formats:\n msg = f\"Invalid file format '{file_format}' for {self._get_input_type()}. Allowed: {allowed_formats}\"\n raise ValueError(msg)\n\n # Prepare file path\n file_path = Path(self.file_name).expanduser()\n if not file_path.parent.exists():\n file_path.parent.mkdir(parents=True, exist_ok=True)\n file_path = self._adjust_file_path_with_format(file_path, file_format)\n\n # Save the input to file based on type\n if self._get_input_type() == \"DataFrame\":\n confirmation = self._save_dataframe(self.input, file_path, file_format)\n elif self._get_input_type() == \"Data\":\n confirmation = self._save_data(self.input, file_path, file_format)\n elif self._get_input_type() == \"Message\":\n confirmation = await self._save_message(self.input, file_path, file_format)\n else:\n msg = f\"Unsupported input type: {self._get_input_type()}\"\n raise ValueError(msg)\n\n # Upload the saved file\n await self._upload_file(file_path)\n\n # Return the final file path and confirmation message\n final_path = Path.cwd() / file_path if not file_path.is_absolute() else file_path\n\n return Message(text=f\"{confirmation} at {final_path}\")\n\n def _get_input_type(self) -> str:\n \"\"\"Determine the input type based on the provided input.\"\"\"\n # Use exact type checking (type() is) instead of isinstance() to avoid inheritance issues.\n # Since Message inherits from Data, isinstance(message, Data) would return True for Message objects,\n # causing Message inputs to be incorrectly identified as Data type.\n if type(self.input) is DataFrame:\n return \"DataFrame\"\n if type(self.input) is Message:\n return \"Message\"\n if type(self.input) is Data:\n return \"Data\"\n msg = f\"Unsupported input type: {type(self.input)}\"\n raise ValueError(msg)\n\n def _get_default_format(self) -> str:\n \"\"\"Return the default file format based on input type.\"\"\"\n if self._get_input_type() == \"DataFrame\":\n return \"csv\"\n if self._get_input_type() == \"Data\":\n return \"json\"\n if self._get_input_type() == \"Message\":\n return \"json\"\n return \"json\" # Fallback\n\n def _adjust_file_path_with_format(self, path: Path, fmt: str) -> Path:\n \"\"\"Adjust the file path to include the correct extension.\"\"\"\n file_extension = path.suffix.lower().lstrip(\".\")\n if fmt == \"excel\":\n return Path(f\"{path}.xlsx\").expanduser() if file_extension not in [\"xlsx\", \"xls\"] else path\n return Path(f\"{path}.{fmt}\").expanduser() if file_extension != fmt else path\n\n async def _upload_file(self, file_path: Path) -> None:\n \"\"\"Upload the saved file using the upload_user_file service.\"\"\"\n if not file_path.exists():\n msg = f\"File not found: {file_path}\"\n raise FileNotFoundError(msg)\n\n with file_path.open(\"rb\") as f:\n async for db in get_session():\n user_id, _ = await create_user_longterm_token(db)\n current_user = await get_user_by_id(db, user_id)\n\n await upload_user_file(\n file=UploadFile(filename=file_path.name, file=f, size=file_path.stat().st_size),\n session=db,\n current_user=current_user,\n storage_service=get_storage_service(),\n settings_service=get_settings_service(),\n )\n\n def _save_dataframe(self, dataframe: DataFrame, path: Path, fmt: str) -> str:\n \"\"\"Save a DataFrame to the specified file format.\"\"\"\n if fmt == \"csv\":\n dataframe.to_csv(path, index=False)\n elif fmt == \"excel\":\n dataframe.to_excel(path, index=False, engine=\"openpyxl\")\n elif fmt == \"json\":\n dataframe.to_json(path, orient=\"records\", indent=2)\n elif fmt == \"markdown\":\n path.write_text(dataframe.to_markdown(index=False), encoding=\"utf-8\")\n else:\n msg = f\"Unsupported DataFrame format: {fmt}\"\n raise ValueError(msg)\n return f\"DataFrame saved successfully as '{path}'\"\n\n def _save_data(self, data: Data, path: Path, fmt: str) -> str:\n \"\"\"Save a Data object to the specified file format.\"\"\"\n if fmt == \"csv\":\n pd.DataFrame(data.data).to_csv(path, index=False)\n elif fmt == \"excel\":\n pd.DataFrame(data.data).to_excel(path, index=False, engine=\"openpyxl\")\n elif fmt == \"json\":\n path.write_text(\n orjson.dumps(jsonable_encoder(data.data), option=orjson.OPT_INDENT_2).decode(\"utf-8\"), encoding=\"utf-8\"\n )\n elif fmt == \"markdown\":\n path.write_text(pd.DataFrame(data.data).to_markdown(index=False), encoding=\"utf-8\")\n else:\n msg = f\"Unsupported Data format: {fmt}\"\n raise ValueError(msg)\n return f\"Data saved successfully as '{path}'\"\n\n async def _save_message(self, message: Message, path: Path, fmt: str) -> str:\n \"\"\"Save a Message to the specified file format, handling async iterators.\"\"\"\n content = \"\"\n if message.text is None:\n content = \"\"\n elif isinstance(message.text, AsyncIterator):\n async for item in message.text:\n content += str(item) + \" \"\n content = content.strip()\n elif isinstance(message.text, Iterator):\n content = \" \".join(str(item) for item in message.text)\n else:\n content = str(message.text)\n\n if fmt == \"txt\":\n path.write_text(content, encoding=\"utf-8\")\n elif fmt == \"json\":\n path.write_text(json.dumps({\"message\": content}, indent=2), encoding=\"utf-8\")\n elif fmt == \"markdown\":\n path.write_text(f\"**Message:**\\n\\n{content}\", encoding=\"utf-8\")\n else:\n msg = f\"Unsupported Message format: {fmt}\"\n raise ValueError(msg)\n return f\"Message saved successfully as '{path}'\"\n"
1252
1252
  },
1253
1253
  "file_format": {
1254
1254
  "_input_type": "DropdownInput",
@@ -60,3 +60,10 @@ async def update_user_last_login_at(user_id: UUID, db: AsyncSession):
60
60
  return await update_user(user, user_data, db)
61
61
  except Exception as e: # noqa: BLE001
62
62
  logger.error(f"Error updating user last login at: {e!s}")
63
+
64
+
65
+ async def get_all_superusers(db: AsyncSession) -> list[User]:
66
+ """Get all superuser accounts from the database."""
67
+ stmt = select(User).where(User.is_superuser == True) # noqa: E712
68
+ result = await db.exec(stmt)
69
+ return list(result.all())
@@ -27,12 +27,25 @@ class AuthSettings(BaseSettings):
27
27
  API_KEY_ALGORITHM: str = "HS256"
28
28
  API_V1_STR: str = "/api/v1"
29
29
 
30
- AUTO_LOGIN: bool = True
30
+ AUTO_LOGIN: bool = Field(
31
+ default=True, # TODO: Set to False in v1.6
32
+ description=(
33
+ "Enable automatic login with default credentials. "
34
+ "SECURITY WARNING: This bypasses authentication and should only be used in development environments. "
35
+ "Set to False in production."
36
+ ),
37
+ )
31
38
  """If True, the application will attempt to log in automatically as a super user."""
32
39
  skip_auth_auto_login: bool = True
33
40
  """If True, the application will skip authentication when AUTO_LOGIN is enabled.
34
41
  This will be removed in v1.6"""
35
42
 
43
+ ENABLE_SUPERUSER_CLI: bool = Field(
44
+ default=True,
45
+ description="Allow creation of superusers via CLI. Set to False in production for security.",
46
+ )
47
+ """If True, allows creation of superusers via the CLI 'langflow superuser' command."""
48
+
36
49
  NEW_USER_IS_ACTIVE: bool = False
37
50
  SUPERUSER: str = DEFAULT_SUPERUSER
38
51
  SUPERUSER_PASSWORD: str = DEFAULT_SUPERUSER_PASSWORD
@@ -68,15 +68,20 @@ async def get_or_create_super_user(session: AsyncSession, username, password, is
68
68
  return await create_super_user(username, password, db=session)
69
69
 
70
70
 
71
- async def setup_superuser(settings_service, session: AsyncSession) -> None:
71
+ async def setup_superuser(settings_service: SettingsService, session: AsyncSession) -> None:
72
72
  if settings_service.auth_settings.AUTO_LOGIN:
73
73
  logger.debug("AUTO_LOGIN is set to True. Creating default superuser.")
74
+ username = DEFAULT_SUPERUSER
75
+ password = DEFAULT_SUPERUSER_PASSWORD
74
76
  else:
75
77
  # Remove the default superuser if it exists
76
78
  await teardown_superuser(settings_service, session)
79
+ username = settings_service.auth_settings.SUPERUSER
80
+ password = settings_service.auth_settings.SUPERUSER_PASSWORD
77
81
 
78
- username = settings_service.auth_settings.SUPERUSER
79
- password = settings_service.auth_settings.SUPERUSER_PASSWORD
82
+ if not username or not password:
83
+ msg = "Username and password must be set"
84
+ raise ValueError(msg)
80
85
 
81
86
  is_default = (username == DEFAULT_SUPERUSER) and (password == DEFAULT_SUPERUSER_PASSWORD)
82
87
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langflow-base-nightly
3
- Version: 0.5.0.dev31
3
+ Version: 0.5.0.dev33
4
4
  Summary: A Python package with a built-in web application
5
5
  Project-URL: Repository, https://github.com/langflow-ai/langflow
6
6
  Project-URL: Documentation, https://docs.langflow.org
@@ -1,5 +1,5 @@
1
1
  langflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- langflow/__main__.py,sha256=80Jc0iW3DjkwRG_oDqHH9RkzJIakyCrPGvl_Zvhzaqc,32283
2
+ langflow/__main__.py,sha256=x1urQ-v-LKMcuPfdxMXVxzY6vF7nZ1gMeQq6coMbl08,36812
3
3
  langflow/alembic.ini,sha256=fbUkg3Y988q24z9FRya85qBQBvKvEI8fQbR4CsWHHsk,3503
4
4
  langflow/langflow_launcher.py,sha256=ruVUD-ZXb-Bi9xtXYu17rXuwBr6OmPMF46mH-hPbfpQ,1984
5
5
  langflow/main.py,sha256=20UgLEpbG__xbT3zqQNvLsP2mYtjZcvNsGaAvUxWD3c,18195
@@ -257,7 +257,7 @@ langflow/components/data/directory.py,sha256=MqSUyq5cL6Xy2CqBREc0hJlcoega3r82ti2
257
257
  langflow/components/data/file.py,sha256=07zPsp7_qUyBpziW7UfQLHoWY70Ps6hRPyKyX3aLLzw,5861
258
258
  langflow/components/data/json_to_data.py,sha256=uN3yyVHo-DOvv0ZwYQx99V-rWddh3A6iDBKW7ga1J4c,3554
259
259
  langflow/components/data/kb_ingest.py,sha256=Ed8Z3lQdjsT-FScEYnJA0N68ReppmP7JZvczViZ12eU,24334
260
- langflow/components/data/kb_retrieval.py,sha256=-CNloJd_aEPZsZ_hhnwQdKoqZ7TKWO9_yTpqX6kJgLQ,9946
260
+ langflow/components/data/kb_retrieval.py,sha256=7itmlY8JTS91P_oju0-sKDV2vZyWUuiQVRhg88I_3s8,9947
261
261
  langflow/components/data/news_search.py,sha256=PpuhSTH_gk1iWjX4X3N0PxIPAAdcrnH0GAtu_d5LSgA,6196
262
262
  langflow/components/data/rss.py,sha256=B_DZvPdnJhnh7qkzPHcp-ERsfqcft6kTNl58G94zJzg,2504
263
263
  langflow/components/data/sql_executor.py,sha256=rfIXj4-w8Jkq10DL9nCRVflbEDZGsw_C6Lrgrhk7UA8,3637
@@ -476,7 +476,7 @@ langflow/components/processing/parser.py,sha256=VWIJUgZQRN-eW8zgEUOECfpmy0nmfRI7
476
476
  langflow/components/processing/prompt.py,sha256=xHilcszTEdewqBufJusnkXWTrRqC8MX9fEEz1n-vgK0,2791
477
477
  langflow/components/processing/python_repl_core.py,sha256=FaNGm6f2ngniE2lueYaoxSn-hZ-yKePdV60y-jc9nfs,3477
478
478
  langflow/components/processing/regex.py,sha256=MQVd8nUwe3engl_JiI-wEn1BvXVm1e0vQOn99gdiOrw,2660
479
- langflow/components/processing/save_file.py,sha256=byRAIyB-KVM4FaL6_ZPhaP5NhT9Rl4Sett9n_xoBBmg,8841
479
+ langflow/components/processing/save_file.py,sha256=kTSFnPJN7em85Dpa6EzqyALSc3A3eaP6MnQMPu_IojU,8842
480
480
  langflow/components/processing/select_data.py,sha256=t1InSoxLIageodImGpkNG1tWAirHKuFrU9QhNdom8PA,1765
481
481
  langflow/components/processing/split_text.py,sha256=2_Lp0jGdEAnuup4ucL-ZexpcarCL78wJAwQLCL2cleE,5323
482
482
  langflow/components/processing/structured_output.py,sha256=rSpvRVLAlxcnEWFJVvExZC-NTmIb6rUieBzYnSMV9po,7991
@@ -867,11 +867,11 @@ langflow/initial_setup/starter_projects/Image Sentiment Analysis.json,sha256=p3y
867
867
  langflow/initial_setup/starter_projects/Instagram Copywriter.json,sha256=iBqKLxara5PJsbztdq9k-M6q0mkd_OExISb71t9Ft6o,169884
868
868
  langflow/initial_setup/starter_projects/Invoice Summarizer.json,sha256=IdWYegxw5qTplYBdBt3Vl_b61bNgeTzPEtX6DVuimSM,95726
869
869
  langflow/initial_setup/starter_projects/Knowledge Ingestion.json,sha256=tqDxS1SO6L8ReXutxVgJSeoV-m_R6slPPsF1uswsMcc,81324
870
- langflow/initial_setup/starter_projects/Knowledge Retrieval.json,sha256=nfFOoBouPGbCxfXXH87A-fUox1gFIKm9utLjBAMAJqY,43432
870
+ langflow/initial_setup/starter_projects/Knowledge Retrieval.json,sha256=abZ7akGNWy_ywoFWTcq0xyT--iRbEBnXh8Xx0Q1BizY,43494
871
871
  langflow/initial_setup/starter_projects/Market Research.json,sha256=i3IZbaXaXwNL_l222sikK4kCbtVjm_JU8xHrs-KTFI0,151362
872
872
  langflow/initial_setup/starter_projects/Meeting Summary.json,sha256=rm58p7Dkxb4vBzyin-Aa1i6XdMT0Au5D5_QuEuuxNDM,195851
873
873
  langflow/initial_setup/starter_projects/Memory Chatbot.json,sha256=d4imk-w2M69O8iCJT-Xbf9dleEf8uaLAsKzqLkMMZWw,85446
874
- langflow/initial_setup/starter_projects/News Aggregator.json,sha256=Lzs07rFOPH2btMNs0TRyPGsF_JRNwLFuFEHPhBIoSaI,113349
874
+ langflow/initial_setup/starter_projects/News Aggregator.json,sha256=vx0oPSjujjvtvq1XexHHs5VqA_thHpP4toPp7bS4R1Y,113354
875
875
  langflow/initial_setup/starter_projects/Nvidia Remix.json,sha256=hv-3BxMnVVh4b8Jk_b80EDRRlxOKEmG0-qYsehX6TzE,315071
876
876
  langflow/initial_setup/starter_projects/Pokédex Agent.json,sha256=xBs9Ih8IRFDTAP64ra2DhO52iQHui7xj-2JMq6YL3kY,111969
877
877
  langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json,sha256=GU8ESXR_Lf6_NdGihyuPyk4JUcO0KhzmL7dQQGr9XAo,123578
@@ -953,7 +953,7 @@ langflow/services/deps.py,sha256=hpY-jjKF2GrDC924BfnCaUpF99sgr_ERin2k8Q_cYaA,785
953
953
  langflow/services/factory.py,sha256=mbPR1IIG5Fot2MV8rrkpouwq72sbq--SFYM83IUMCB8,3023
954
954
  langflow/services/manager.py,sha256=SwTQwWzP9IrAgex0bfdp95IfnVbaMLm7HYS7DtVFO9U,5634
955
955
  langflow/services/schema.py,sha256=dZVRfY58rqCHs0VUdI5F773pgyYTPjVz3Hm2eUA_MxM,811
956
- langflow/services/utils.py,sha256=oFI46ywkr_lE_MdkegbRb6hDv6JYsHArVRLpaUxZ7L4,9593
956
+ langflow/services/utils.py,sha256=w_s6zUIA4p0psKExGDvv4yHh3kSi5vHu84JNvpzXszI,9818
957
957
  langflow/services/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
958
958
  langflow/services/auth/factory.py,sha256=ML6QA2QfHpkbyzKAbJUHoW2cx6T6IvGp6SWGlnOZ_9s,389
959
959
  langflow/services/auth/service.py,sha256=mBlINyRcZZGR5jpWZQ6AOEm0CfXouz0D6UV3z73OM3w,364
@@ -999,7 +999,7 @@ langflow/services/database/models/transactions/__init__.py,sha256=rgYTZHOkYWztmi
999
999
  langflow/services/database/models/transactions/crud.py,sha256=Iv8_HDPLEPj6TZDzzspw6N-Qi6YinlfRWUFngdW5GWU,2807
1000
1000
  langflow/services/database/models/transactions/model.py,sha256=-NZyjAxFYQaNBApOFKat5Jy3TTTnrsIHMg8P0Yn9fjA,2289
1001
1001
  langflow/services/database/models/user/__init__.py,sha256=_em9DxemZQd6omKdWTlVYvDZ0NUix24UWOq5ymZs_-o,137
1002
- langflow/services/database/models/user/crud.py,sha256=1lpjb7BS4p4c6A1X1OWiubmm3rDF0xQxq3ubu35DeDI,2196
1002
+ langflow/services/database/models/user/crud.py,sha256=M6CuXvP8hxiy5HgdKpmAWgWJCZTurR_cBxcnVQ8LiX8,2450
1003
1003
  langflow/services/database/models/user/model.py,sha256=uGFtG4n4fBS-p2aWMh_19BCLAH10MnMBhCX97TSrI9M,3036
1004
1004
  langflow/services/database/models/variable/__init__.py,sha256=9yX_efy4NkT5UvF4souRWZ-jjlidYwqp7cIuweSgOpc,150
1005
1005
  langflow/services/database/models/variable/model.py,sha256=aJROdKeNDDjWGsitmeTG0ZEhiuAVZMdJIrTG4y9dP7g,2820
@@ -1016,7 +1016,7 @@ langflow/services/session/factory.py,sha256=6IzDDO4a3Ui5P3-Crf2yXrJjFpvXET6kXWfq
1016
1016
  langflow/services/session/service.py,sha256=KR82A1sycF2gmb7zeqxn4EZ0_NxZcQN1ggRsergQNpI,2511
1017
1017
  langflow/services/session/utils.py,sha256=YkInqhL019XrgIyovOtkLqFF3jJmlQBSPbKiKOuWGiM,516
1018
1018
  langflow/services/settings/__init__.py,sha256=UISBvOQIqoA3a8opwJrTQp4PSTqpReY6GQ_7O6WuqJQ,65
1019
- langflow/services/settings/auth.py,sha256=jpVRROmFtNp1rqoHYFINzsdnQ_QrglyLgB_uVHhpNVI,4562
1019
+ langflow/services/settings/auth.py,sha256=1WaQ74QemaN8J9A6rdMOiiu-7I380QkLDbfK0yeu9g4,5138
1020
1020
  langflow/services/settings/base.py,sha256=h5YtJ2ISz7bJjq3JOLDFccGxQKMEJ-UQfBRBLKQNFxo,24365
1021
1021
  langflow/services/settings/constants.py,sha256=Uf8HrGOpRE-55IZ7aopsZsEnzBb9rRPi--PFvJfUGqw,878
1022
1022
  langflow/services/settings/factory.py,sha256=Jf0leRvzUBlxZ6BsoCJEDKdH2kWR9Tiv-Dk8Y7cbqUE,595
@@ -1111,7 +1111,7 @@ langflow/utils/util_strings.py,sha256=Blz5lwvE7lml7nKCG9vVJ6me5VNmVtYzFXDVPHPK7v
1111
1111
  langflow/utils/validate.py,sha256=8RnY61LZFCBU1HIlPDCMI3vsXOmK_IFAYBGZIfZJcsU,16362
1112
1112
  langflow/utils/version.py,sha256=OjSj0smls9XnPd4-LpTH9AWyUO_NAn5mncqKkkXl_fw,2840
1113
1113
  langflow/utils/voice_utils.py,sha256=pzU6uuseI2_5mi-yXzFIjMavVRFyuVrpLmR6LqbF7mE,3346
1114
- langflow_base_nightly-0.5.0.dev31.dist-info/METADATA,sha256=oNuRt9Jypx0ynOqLtC2pJk9IQ2yXWLqSkg6W5FI-eZU,4212
1115
- langflow_base_nightly-0.5.0.dev31.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1116
- langflow_base_nightly-0.5.0.dev31.dist-info/entry_points.txt,sha256=JvuLdXSrkeDmDdpb8M-VvFIzb84n4HmqUcIP10_EIF8,57
1117
- langflow_base_nightly-0.5.0.dev31.dist-info/RECORD,,
1114
+ langflow_base_nightly-0.5.0.dev33.dist-info/METADATA,sha256=5XO02Mg1LMhMdnjlctfL1xGbNt7OWYtjo-FhT0OI__U,4212
1115
+ langflow_base_nightly-0.5.0.dev33.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1116
+ langflow_base_nightly-0.5.0.dev33.dist-info/entry_points.txt,sha256=JvuLdXSrkeDmDdpb8M-VvFIzb84n4HmqUcIP10_EIF8,57
1117
+ langflow_base_nightly-0.5.0.dev33.dist-info/RECORD,,