diff --git a/.env.example b/.env.example
index 26f6e3a29..a63a9b4b1 100644
--- a/.env.example
+++ b/.env.example
@@ -71,6 +71,10 @@ LANGFLOW_SUPERUSER=
# Example: LANGFLOW_SUPERUSER_PASSWORD=123456
LANGFLOW_SUPERUSER_PASSWORD=
+# Should store environment variables in the database
+# Values: true, false
+LANGFLOW_STORE_ENVIRONMENT_VARIABLES=
+
# STORE_URL
# Example: LANGFLOW_STORE_URL=https://api.langflow.store
# LANGFLOW_STORE_URL=
diff --git a/docs/docs/migration/global-variables.mdx b/docs/docs/migration/global-variables.mdx
index ce6d15a5f..605ea8252 100644
--- a/docs/docs/migration/global-variables.mdx
+++ b/docs/docs/migration/global-variables.mdx
@@ -3,6 +3,15 @@ import Admonition from "@theme/Admonition";
# Global Variables
+## TLDR;
+
+- Global Variables are reusable variables that can be accessed from any Text field in your project.
+- To create a Global Variable, click on the 🌐 button in a Text field and then **+ Add New Variable**.
+- Define the **Name**, **Type**, and **Value** of the variable.
+- Click on **Save Variable** to create the variable.
+- All Credential Global Variables are encrypted and cannot be accessed by anyone but you.
+- Set _`LANGFLOW_STORE_ENVIRONMENT_VARIABLES`_ to _`true`_ in your `.env` file to add all variables in _`LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT`_ to your user's Global Variables.
+
Global Variables are a really useful feature of Langflow.
They allow you to define reusable variables that can be accessed from any Text field in your project.
@@ -48,7 +57,8 @@ The **Value** is the value that the variable will have.
{/* say that all variables are encrypted */}
- All Global Variables are encrypted and cannot be accessed by anyone but you.
+ All Credential Global Variables are encrypted and cannot be accessed by anyone
+ but you.
+ You can set _`LANGFLOW_STORE_ENVIRONMENT_VARIABLES`_ to _`false`_ in your
+ `.env` file to prevent this behavior.
+
+
+You can also set _`LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT`_ to a list of variables that you want to get from the environment.
+
+The default list at the moment is:
+
+- ANTHROPIC_API_KEY
+- ASTRA_DB_API_ENDPOINT
+- ASTRA_DB_APPLICATION_TOKEN
+- AZURE_OPENAI_API_KEY
+- AZURE_OPENAI_API_DEPLOYMENT_NAME
+- AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME
+- AZURE_OPENAI_API_INSTANCE_NAME
+- AZURE_OPENAI_API_VERSION
+- COHERE_API_KEY
+- GOOGLE_API_KEY
+- HUGGINGFACEHUB_API_TOKEN
+- OPENAI_API_KEY
+
+
+ Set _`LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT`_ as a comma-separated list
+ of variables (e.g. _`"VARIABLE1, VARIABLE2"`_) or as a JSON-encoded string
+ (e.g. _`'["VARIABLE1", "VARIABLE2"]'`_).
+
diff --git a/src/backend/base/langflow/api/v1/login.py b/src/backend/base/langflow/api/v1/login.py
index fba61d3b4..8c9fb5bd4 100644
--- a/src/backend/base/langflow/api/v1/login.py
+++ b/src/backend/base/langflow/api/v1/login.py
@@ -1,7 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.security import OAuth2PasswordRequestForm
-from sqlmodel import Session
-
from langflow.api.v1.schemas import Token
from langflow.services.auth.utils import (
authenticate_user,
@@ -9,8 +7,14 @@ from langflow.services.auth.utils import (
create_user_longterm_token,
create_user_tokens,
)
-from langflow.services.deps import get_session, get_settings_service
+from langflow.services.deps import (
+ get_session,
+ get_settings_service,
+ get_variable_service,
+)
from langflow.services.settings.manager import SettingsService
+from langflow.services.variable.service import VariableService
+from sqlmodel import Session
router = APIRouter(tags=["Login"])
@@ -22,6 +26,7 @@ async def login_to_get_access_token(
db: Session = Depends(get_session),
# _: Session = Depends(get_current_active_user)
settings_service=Depends(get_settings_service),
+ variable_service: VariableService = Depends(get_variable_service),
):
auth_settings = settings_service.auth_settings
try:
@@ -52,6 +57,7 @@ async def login_to_get_access_token(
secure=auth_settings.ACCESS_SECURE,
expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
)
+ variable_service.initialize_user_variables(user.id, db)
return tokens
else:
raise HTTPException(
@@ -66,10 +72,11 @@ async def auto_login(
response: Response,
db: Session = Depends(get_session),
settings_service=Depends(get_settings_service),
+ variable_service: VariableService = Depends(get_variable_service),
):
auth_settings = settings_service.auth_settings
if settings_service.auth_settings.AUTO_LOGIN:
- tokens = create_user_longterm_token(db)
+ user_id, tokens = create_user_longterm_token(db)
response.set_cookie(
"access_token_lf",
tokens["access_token"],
@@ -78,6 +85,7 @@ async def auto_login(
secure=auth_settings.ACCESS_SECURE,
expires=None, # Set to None to make it a session cookie
)
+ variable_service.initialize_user_variables(user_id, db)
return tokens
raise HTTPException(
@@ -91,7 +99,9 @@ async def auto_login(
@router.post("/refresh")
async def refresh_token(
- request: Request, response: Response, settings_service: "SettingsService" = Depends(get_settings_service)
+ request: Request,
+ response: Response,
+ settings_service: "SettingsService" = Depends(get_settings_service),
):
auth_settings = settings_service.auth_settings
@@ -129,3 +139,4 @@ async def logout(response: Response):
response.delete_cookie("refresh_token_lf")
response.delete_cookie("access_token_lf")
return {"message": "Logout successful"}
+ return {"message": "Logout successful"}
diff --git a/src/backend/base/langflow/services/auth/utils.py b/src/backend/base/langflow/services/auth/utils.py
index d89330856..4efda89dd 100644
--- a/src/backend/base/langflow/services/auth/utils.py
+++ b/src/backend/base/langflow/services/auth/utils.py
@@ -11,7 +11,11 @@ from starlette.websockets import WebSocket
from langflow.services.database.models.api_key.crud import check_key
from langflow.services.database.models.api_key.model import ApiKey
-from langflow.services.database.models.user.crud import get_user_by_id, get_user_by_username, update_user_last_login_at
+from langflow.services.database.models.user.crud import (
+ get_user_by_id,
+ get_user_by_username,
+ update_user_last_login_at,
+)
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session, get_settings_service
@@ -227,7 +231,7 @@ def create_user_longterm_token(db: Session = Depends(get_session)) -> dict:
# Update: last_login_at
update_user_last_login_at(super_user.id, db)
- return {
+ return super_user.id, {
"access_token": access_token,
"refresh_token": None,
"token_type": "bearer",
diff --git a/src/backend/base/langflow/services/settings/base.py b/src/backend/base/langflow/services/settings/base.py
index 9c7ab21c4..3379dc48c 100644
--- a/src/backend/base/langflow/services/settings/base.py
+++ b/src/backend/base/langflow/services/settings/base.py
@@ -3,18 +3,51 @@ import json
import os
from pathlib import Path
from shutil import copy2
-from typing import List, Optional
+from typing import Any, List, Optional, Tuple, Type
import orjson
import yaml
from loguru import logger
from pydantic import field_validator, validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic.fields import FieldInfo
+from pydantic_settings import BaseSettings, EnvSettingsSource, PydanticBaseSettingsSource, SettingsConfigDict
+
+from langflow.services.settings.constants import VARIABLES_TO_GET_FROM_ENVIRONMENT
+from pydantic_settings import BaseSettings, EnvSettingsSource, PydanticBaseSettingsSource, SettingsConfigDict
# BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components")
BASE_COMPONENTS_PATH = str(Path(__file__).parent.parent.parent / "components")
+def is_list_of_any(field: FieldInfo) -> bool:
+ """
+ Check if the given field is a list or an optional list of any type.
+
+ Args:
+ field (FieldInfo): The field to be checked.
+
+ Returns:
+ bool: True if the field is a list or a list of any type, False otherwise.
+ """
+ return field.annotation.__origin__ == list or any(
+ arg.__origin__ == list for arg in field.annotation.__args__ if hasattr(arg, "__origin__")
+ )
+
+
+class MyCustomSource(EnvSettingsSource):
+ def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any:
+ # allow comma-separated list parsing
+
+ # fieldInfo contains the annotation of the field
+ if is_list_of_any(field):
+ if isinstance(value, str):
+ value = value.split(",")
+ if isinstance(value, list):
+ return value
+
+ return super().prepare_field_value(field_name, field, value, value_is_complex)
+
+
class Settings(BaseSettings):
CHAINS: dict = {}
AGENTS: dict = {}
@@ -67,6 +100,11 @@ class Settings(BaseSettings):
CELERY_ENABLED: bool = False
+ store_environment_variables: bool = True
+ """Whether to store environment variables as Global Variables in the database."""
+ variables_to_get_from_environment: list[str] = VARIABLES_TO_GET_FROM_ENVIRONMENT
+ """List of environment variables to get from the environment and store in the database."""
+
@validator("CONFIG_DIR", pre=True, allow_reuse=True)
def set_langflow_dir(cls, value):
if not value:
@@ -149,14 +187,6 @@ class Settings(BaseSettings):
model_config = SettingsConfigDict(validate_assignment=True, extra="ignore", env_prefix="LANGFLOW_")
- # @model_validator()
- # @classmethod
- # def validate_lists(cls, values):
- # for key, value in values.items():
- # if key != "dev" and not value:
- # values[key] = []
- # return values
-
def update_from_yaml(self, file_path: str, dev: bool = False):
new_settings = load_settings_from_yaml(file_path)
self.CHAINS = new_settings.CHAINS or {}
@@ -209,6 +239,17 @@ class Settings(BaseSettings):
logger.debug(f"Updated {key}")
logger.debug(f"{key}: {getattr(self, key)}")
+ @classmethod
+ def settings_customise_sources(
+ cls,
+ settings_cls: Type[BaseSettings],
+ init_settings: PydanticBaseSettingsSource,
+ env_settings: PydanticBaseSettingsSource,
+ dotenv_settings: PydanticBaseSettingsSource,
+ file_secret_settings: PydanticBaseSettingsSource,
+ ) -> Tuple[PydanticBaseSettingsSource, ...]:
+ return (MyCustomSource(settings_cls),)
+
def save_settings_to_yaml(settings: Settings, file_path: str):
with open(file_path, "w") as f:
diff --git a/src/backend/base/langflow/services/settings/constants.py b/src/backend/base/langflow/services/settings/constants.py
index 6cf7d4823..14de13281 100644
--- a/src/backend/base/langflow/services/settings/constants.py
+++ b/src/backend/base/langflow/services/settings/constants.py
@@ -1,2 +1,16 @@
DEFAULT_SUPERUSER = "langflow"
DEFAULT_SUPERUSER_PASSWORD = "langflow"
+VARIABLES_TO_GET_FROM_ENVIRONMENT = [
+ "OPENAI_API_KEY",
+ "ANTHROPIC_API_KEY",
+ "GOOGLE_API_KEY",
+ "AZURE_OPENAI_API_KEY",
+ "AZURE_OPENAI_API_VERSION",
+ "AZURE_OPENAI_API_INSTANCE_NAME",
+ "AZURE_OPENAI_API_DEPLOYMENT_NAME",
+ "AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME",
+ "ASTRA_DB_APPLICATION_TOKEN",
+ "ASTRA_DB_API_ENDPOINT",
+ "COHERE_API_KEY",
+ "HUGGINGFACEHUB_API_TOKEN",
+]
diff --git a/src/backend/base/langflow/services/variable/service.py b/src/backend/base/langflow/services/variable/service.py
index 23d1cd806..fb2d346fe 100644
--- a/src/backend/base/langflow/services/variable/service.py
+++ b/src/backend/base/langflow/services/variable/service.py
@@ -1,13 +1,14 @@
+import os
from typing import TYPE_CHECKING, Optional, Union
from uuid import UUID
from fastapi import Depends
-from sqlmodel import Session, select
-
from langflow.services.auth import utils as auth_utils
from langflow.services.base import Service
from langflow.services.database.models.variable.model import Variable
from langflow.services.deps import get_session
+from loguru import logger
+from sqlmodel import Session, select
if TYPE_CHECKING:
from langflow.services.settings.service import SettingsService
@@ -19,7 +20,28 @@ class VariableService(Service):
def __init__(self, settings_service: "SettingsService"):
self.settings_service = settings_service
- def get_variable(self, user_id: Union[UUID, str], name: str, session: Session = Depends(get_session)) -> str:
+ def initialize_user_variables(self, user_id: Union[UUID, str], session: Session = Depends(get_session)):
+ # Check for environment variables that should be stored in the database
+ should_or_should_not = "Should" if self.settings_service.settings.store_environment_variables else "Should not"
+ logger.info(f"{should_or_should_not} store environment variables in the database.")
+ if self.settings_service.settings.store_environment_variables:
+ for var in self.settings_service.settings.variables_to_get_from_environment:
+ if var in os.environ:
+ logger.debug(f"Creating {var} variable from environment.")
+ try:
+ self.create_variable(user_id, var, os.environ[var], _type="Credential", session=session)
+ except Exception as e:
+ logger.error(f"Error creating {var} variable: {e}")
+
+ else:
+ logger.info("Skipping environment variable storage.")
+
+ def get_variable(
+ self,
+ user_id: Union[UUID, str],
+ name: str,
+ session: Session = Depends(get_session),
+ ) -> str:
# we get the credential from the database
# credential = session.query(Variable).filter(Variable.user_id == user_id, Variable.name == name).first()
variable = session.exec(select(Variable).where(Variable.user_id == user_id, Variable.name == name)).first()
@@ -34,7 +56,11 @@ class VariableService(Service):
return [variable.name for variable in variables]
def update_variable(
- self, user_id: Union[UUID, str], name: str, value: str, session: Session = Depends(get_session)
+ self,
+ user_id: Union[UUID, str],
+ name: str,
+ value: str,
+ session: Session = Depends(get_session),
):
variable = session.exec(select(Variable).where(Variable.user_id == user_id, Variable.name == name)).first()
if not variable:
@@ -46,7 +72,12 @@ class VariableService(Service):
session.refresh(variable)
return variable
- def delete_variable(self, user_id: Union[UUID, str], name: str, session: Session = Depends(get_session)):
+ def delete_variable(
+ self,
+ user_id: Union[UUID, str],
+ name: str,
+ session: Session = Depends(get_session),
+ ):
variable = session.exec(select(Variable).where(Variable.user_id == user_id, Variable.name == name)).first()
if not variable:
raise ValueError(f"{name} variable not found.")
@@ -55,12 +86,21 @@ class VariableService(Service):
return variable
def create_variable(
- self, user_id: Union[UUID, str], name: str, value: str, session: Session = Depends(get_session)
+ self,
+ user_id: Union[UUID, str],
+ name: str,
+ value: str,
+ _type: str = "Generic",
+ session: Session = Depends(get_session),
):
variable = Variable(
- user_id=user_id, name=name, value=auth_utils.encrypt_api_key(value, settings_service=self.settings_service)
+ user_id=user_id,
+ name=name,
+ type=_type,
+ value=auth_utils.encrypt_api_key(value, settings_service=self.settings_service),
)
session.add(variable)
session.commit()
session.refresh(variable)
return variable
+ return variable