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 +130 -30
- langflow/components/data/kb_retrieval.py +1 -1
- langflow/components/processing/save_file.py +1 -1
- langflow/initial_setup/starter_projects/Knowledge Retrieval.json +37 -35
- langflow/initial_setup/starter_projects/News Aggregator.json +4 -4
- langflow/services/database/models/user/crud.py +7 -0
- langflow/services/settings/auth.py +14 -1
- langflow/services/utils.py +8 -3
- {langflow_base_nightly-0.5.0.dev31.dist-info → langflow_base_nightly-0.5.0.dev33.dist-info}/METADATA +1 -1
- {langflow_base_nightly-0.5.0.dev31.dist-info → langflow_base_nightly-0.5.0.dev33.dist-info}/RECORD +12 -12
- {langflow_base_nightly-0.5.0.dev31.dist-info → langflow_base_nightly-0.5.0.dev33.dist-info}/WHEEL +0 -0
- {langflow_base_nightly-0.5.0.dev31.dist-info → langflow_base_nightly-0.5.0.dev33.dist-info}/entry_points.txt +0 -0
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.
|
|
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(
|
|
636
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -53,7 +53,7 @@ class SaveToFileComponent(Component):
|
|
|
53
53
|
),
|
|
54
54
|
]
|
|
55
55
|
|
|
56
|
-
outputs = [Output(display_name="File Path", name="
|
|
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
|
-
"
|
|
5
|
+
"animated": false,
|
|
6
6
|
"data": {
|
|
7
7
|
"sourceHandle": {
|
|
8
8
|
"dataType": "TextInput",
|
|
9
|
-
"id": "TextInput-
|
|
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-
|
|
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-
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
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
|
-
"
|
|
32
|
+
"animated": false,
|
|
32
33
|
"data": {
|
|
33
34
|
"sourceHandle": {
|
|
34
35
|
"dataType": "KBRetrieval",
|
|
35
|
-
"id": "KBRetrieval-
|
|
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-
|
|
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-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
532
|
+
"last_updated": "2025-08-14T17:19:22.182Z",
|
|
531
533
|
"legacy": false,
|
|
532
534
|
"metadata": {
|
|
533
|
-
"code_hash": "
|
|
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":
|
|
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-
|
|
683
|
+
"id": "KBRetrieval-zz3I0",
|
|
682
684
|
"measured": {
|
|
683
|
-
"height":
|
|
685
|
+
"height": 329,
|
|
684
686
|
"width": 320
|
|
685
687
|
},
|
|
686
688
|
"position": {
|
|
687
|
-
"x":
|
|
688
|
-
"y": -
|
|
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":
|
|
696
|
-
"y":
|
|
697
|
-
"zoom": 0.
|
|
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": "
|
|
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": "
|
|
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": "
|
|
1224
|
-
"selected": "
|
|
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=\"
|
|
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 =
|
|
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
|
langflow/services/utils.py
CHANGED
|
@@ -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
|
|
79
|
-
|
|
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
|
|
{langflow_base_nightly-0.5.0.dev31.dist-info → langflow_base_nightly-0.5.0.dev33.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langflow-base-nightly
|
|
3
|
-
Version: 0.5.0.
|
|
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
|
{langflow_base_nightly-0.5.0.dev31.dist-info → langflow_base_nightly-0.5.0.dev33.dist-info}/RECORD
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
langflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
langflow/__main__.py,sha256=
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
1115
|
-
langflow_base_nightly-0.5.0.
|
|
1116
|
-
langflow_base_nightly-0.5.0.
|
|
1117
|
-
langflow_base_nightly-0.5.0.
|
|
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,,
|
{langflow_base_nightly-0.5.0.dev31.dist-info → langflow_base_nightly-0.5.0.dev33.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|