Merge remote-tracking branch 'origin/dev' into node-shortcuts-refactor

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-04-12 11:56:01 -03:00
commit 23bcdcec28
216 changed files with 11018 additions and 10942 deletions

View file

@ -9,11 +9,6 @@ import click
import httpx
import typer
from dotenv import load_dotenv
from langflow.main import setup_app
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service, get_settings_service
from langflow.services.utils import initialize_services, initialize_settings_service
from langflow.utils.logger import configure, logger
from multiprocess import Process, cpu_count # type: ignore
from packaging import version as pkg_version
from rich import box
@ -22,6 +17,13 @@ from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from langflow.main import setup_app
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service
from langflow.services.utils import initialize_services
from langflow.utils.logger import configure, logger
from langflow.utils.util import update_settings
console = Console()
app = typer.Typer(no_args_is_help=True)
@ -67,44 +69,10 @@ def set_var_for_macos_issue():
logger.debug("Set OBJC_DISABLE_INITIALIZE_FORK_SAFETY to YES to avoid error")
def update_settings(
config: str,
cache: Optional[str] = None,
dev: bool = False,
remove_api_keys: bool = False,
components_path: Optional[Path] = None,
store: bool = True,
):
"""Update the settings from a config file."""
# Check for database_url in the environment variables
initialize_settings_service()
settings_service = get_settings_service()
if config:
logger.debug(f"Loading settings from {config}")
settings_service.settings.update_from_yaml(config, dev=dev)
if remove_api_keys:
logger.debug(f"Setting remove_api_keys to {remove_api_keys}")
settings_service.settings.update_settings(REMOVE_API_KEYS=remove_api_keys)
if cache:
logger.debug(f"Setting cache to {cache}")
settings_service.settings.update_settings(CACHE=cache)
if components_path:
logger.debug(f"Adding component path {components_path}")
settings_service.settings.update_settings(COMPONENTS_PATH=components_path)
if not store:
logger.debug("Setting store to False")
settings_service.settings.update_settings(STORE=False)
@app.command()
def run(
host: str = typer.Option(
"127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"
),
workers: int = typer.Option(
1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"
),
host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"),
workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"),
timeout: int = typer.Option(300, help="Worker timeout in seconds."),
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
components_path: Optional[Path] = typer.Option(
@ -112,19 +80,11 @@ def run(
help="Path to the directory containing custom components.",
envvar="LANGFLOW_COMPONENTS_PATH",
),
config: str = typer.Option(
Path(__file__).parent / "config.yaml", help="Path to the configuration file."
),
config: str = typer.Option(Path(__file__).parent / "config.yaml", help="Path to the configuration file."),
# .env file param
env_file: Path = typer.Option(
None, help="Path to the .env file containing environment variables."
),
log_level: str = typer.Option(
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
),
log_file: Path = typer.Option(
"logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"
),
env_file: Path = typer.Option(None, help="Path to the .env file containing environment variables."),
log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"),
cache: Optional[str] = typer.Option(
envvar="LANGFLOW_LANGCHAIN_CACHE",
help="Type of cache to use. (InMemoryCache, SQLiteCache)",
@ -192,17 +152,21 @@ def run(
# Define an env variable to know if we are just testing the server
if "pytest" in sys.modules:
return
if platform.system() in ["Windows"]:
# Run using uvicorn on MacOS and Windows
# Windows doesn't support gunicorn
# MacOS requires an env variable to be set to use gunicorn
run_on_windows(host, port, log_level, options, app)
else:
# Run using gunicorn on Linux
run_on_mac_or_linux(host, port, log_level, options, app, open_browser)
if open_browser:
click.launch(f"http://{host}:{port}")
try:
if platform.system() in ["Windows"]:
# Run using uvicorn on MacOS and Windows
# Windows doesn't support gunicorn
# MacOS requires an env variable to be set to use gunicorn
process = run_on_windows(host, port, log_level, options, app)
else:
# Run using gunicorn on Linux
process = run_on_mac_or_linux(host, port, log_level, options, app)
if open_browser:
click.launch(f"http://{host}:{port}")
if process:
process.join()
except KeyboardInterrupt:
pass
def wait_for_server_ready(host, port):
@ -218,13 +182,12 @@ def wait_for_server_ready(host, port):
def run_on_mac_or_linux(host, port, log_level, options, app):
webapp_process = Process(
target=run_langflow, args=(host, port, log_level, options, app)
)
webapp_process = Process(target=run_langflow, args=(host, port, log_level, options, app))
webapp_process.start()
wait_for_server_ready(host, port)
print_banner(host, port)
return webapp_process
def run_on_windows(host, port, log_level, options, app):
@ -233,6 +196,7 @@ def run_on_windows(host, port, log_level, options, app):
"""
print_banner(host, port)
run_langflow(host, port, log_level, options, app)
return None
def is_port_in_use(port, host="localhost"):
@ -316,9 +280,7 @@ def build_new_version_notice(current_version: str, package_name: str):
f"A new pre-release version of {package_name} is available: {latest_version}",
)
else:
latest_version = httpx.get(f"https://pypi.org/pypi/{package_name}/json").json()[
"info"
]["version"]
latest_version = httpx.get(f"https://pypi.org/pypi/{package_name}/json").json()["info"]["version"]
if not version_is_prerelease(latest_version):
return (
False,
@ -331,7 +293,7 @@ def is_prerelease(version: str) -> bool:
return "a" in version or "b" in version or "rc" in version
def fetch_latest_version(package_name: str, include_prerelease: bool) -> str:
def fetch_latest_version(package_name: str, include_prerelease: bool) -> Optional[str]:
response = httpx.get(f"https://pypi.org/pypi/{package_name}/json")
versions = response.json()["releases"].keys()
valid_versions = [v for v in versions if include_prerelease or not is_prerelease(v)]
@ -342,9 +304,7 @@ def fetch_latest_version(package_name: str, include_prerelease: bool) -> str:
def build_version_notice(current_version: str, package_name: str) -> str:
latest_version = fetch_latest_version(package_name, is_prerelease(current_version))
if latest_version and pkg_version.parse(current_version) < pkg_version.parse(
latest_version
):
if latest_version and pkg_version.parse(current_version) < pkg_version.parse(latest_version):
release_type = "pre-release" if is_prerelease(latest_version) else "version"
return f"A new {release_type} of {package_name} is available: {latest_version}"
return ""
@ -375,7 +335,7 @@ def print_banner(host: str, port: int):
package_name = ""
try:
from langflow.version import __version__ as langflow_version
from langflow.version import __version__ as langflow_version # type: ignore
is_pre_release |= is_prerelease(langflow_version) # Update pre-release status
notice = build_version_notice(langflow_version, "langflow")
@ -393,9 +353,7 @@ def print_banner(host: str, port: int):
from importlib import metadata
langflow_base_version = metadata.version("langflow-base")
is_pre_release |= is_prerelease(
langflow_base_version
) # Update pre-release status
is_pre_release |= is_prerelease(langflow_base_version) # Update pre-release status
notice = build_version_notice(langflow_base_version, "langflow-base")
notice = stylize_text(notice, "langflow-base", is_pre_release)
if notice:
@ -414,12 +372,10 @@ def print_banner(host: str, port: int):
notices.append(f"Run '{pip_command}' to update.")
styled_notices = [f"[bold]{notice}[/bold]" for notice in notices if notice]
styled_package_name = stylize_text(
package_name, package_name, any("pre-release" in notice for notice in notices)
)
styled_package_name = stylize_text(package_name, package_name, any("pre-release" in notice for notice in notices))
title = f"[bold]Welcome to :chains: {styled_package_name}[/bold]\n"
info_text = "Collaborate, and contribute at our [bold][link=https://github.com/logspace-ai/langflow]GitHub Repo[/link][/bold] :rocket:"
info_text = "Collaborate, and contribute at our [bold][link=https://github.com/langflow-ai/langflow]GitHub Repo[/link][/bold] :rocket:"
access_link = f"Access [link=http://{host}:{port}]http://{host}:{port}[/link]"
panel_content = "\n\n".join([title, *styled_notices, info_text, access_link])
@ -459,12 +415,8 @@ def run_langflow(host, port, log_level, options, app):
@app.command()
def superuser(
username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
password: str = typer.Option(
..., prompt=True, hide_input=True, help="Password for the superuser."
),
log_level: str = typer.Option(
"error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
),
password: str = typer.Option(..., prompt=True, hide_input=True, help="Password for the superuser."),
log_level: str = typer.Option("error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
):
"""
Create a superuser.
@ -491,11 +443,23 @@ def superuser(
@app.command()
def migration(test: bool = typer.Option(True, help="Run migrations in test mode.")):
def migration(
test: bool = typer.Option(True, help="Run migrations in test mode."),
fix: bool = typer.Option(
False,
help="Fix migrations. This is a destructive operation, and should only be used if you know what you are doing.",
),
):
"""
Run or test migrations.
"""
initialize_services()
if fix:
if not typer.confirm(
"This will delete all data necessary to fix migrations. Are you sure you want to continue?"
):
raise typer.Abort()
initialize_services(fix_migration=fix)
db_service = get_db_service()
if not test:
db_service.run_migrations()

View file

@ -0,0 +1,99 @@
"""Change datetime type
Revision ID: 79e675cb6752
Revises: e3bc869fa272
Create Date: 2024-04-11 19:23:10.697335
"""
from calendar import c
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
from sqlalchemy.engine.reflection import Inspector
# revision identifiers, used by Alembic.
revision: str = "79e675cb6752"
down_revision: Union[str, None] = "e3bc869fa272"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "apikey" in table_names:
columns = inspector.get_columns("apikey")
created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
if created_at_column is not None and created_at_column["type"] == postgresql.TIMESTAMP():
with op.batch_alter_table("apikey", schema=None) as batch_op:
batch_op.alter_column(
"created_at",
existing_type=postgresql.TIMESTAMP(),
type_=sa.DateTime(timezone=True),
existing_nullable=False,
)
if "variable" in table_names:
columns = inspector.get_columns("variable")
created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
updated_at_column = next((column for column in columns if column["name"] == "updated_at"), None)
with op.batch_alter_table("variable", schema=None) as batch_op:
if created_at_column is not None and created_at_column["type"] == postgresql.TIMESTAMP():
batch_op.alter_column(
"created_at",
existing_type=postgresql.TIMESTAMP(),
type_=sa.DateTime(timezone=True),
existing_nullable=True,
)
if updated_at_column is not None and updated_at_column["type"] == postgresql.TIMESTAMP():
batch_op.alter_column(
"updated_at",
existing_type=postgresql.TIMESTAMP(),
type_=sa.DateTime(timezone=True),
existing_nullable=True,
)
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "variable" in table_names:
columns = inspector.get_columns("variable")
created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
updated_at_column = next((column for column in columns if column["name"] == "updated_at"), None)
with op.batch_alter_table("variable", schema=None) as batch_op:
if updated_at_column is not None and updated_at_column["type"] == sa.DateTime(timezone=True):
batch_op.alter_column(
"updated_at",
existing_type=sa.DateTime(timezone=True),
type_=postgresql.TIMESTAMP(),
existing_nullable=True,
)
if created_at_column is not None and created_at_column["type"] == sa.DateTime(timezone=True):
batch_op.alter_column(
"created_at",
existing_type=sa.DateTime(timezone=True),
type_=postgresql.TIMESTAMP(),
existing_nullable=True,
)
if "apikey" in table_names:
columns = inspector.get_columns("apikey")
created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
if created_at_column is not None and created_at_column["type"] == sa.DateTime(timezone=True):
with op.batch_alter_table("apikey", schema=None) as batch_op:
batch_op.alter_column(
"created_at",
existing_type=sa.DateTime(timezone=True),
type_=postgresql.TIMESTAMP(),
existing_nullable=False,
)
# ### end Alembic commands ###

View file

@ -0,0 +1,62 @@
"""Fix nullable
Revision ID: e3bc869fa272
Revises: 1a110b568907
Create Date: 2024-04-10 19:17:22.820455
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy.engine.reflection import Inspector
# revision identifiers, used by Alembic.
revision: str = "e3bc869fa272"
down_revision: Union[str, None] = "1a110b568907"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "variable" not in table_names:
return
column_names = [column["name"] for column in inspector.get_columns("variable")]
with op.batch_alter_table("variable", schema=None) as batch_op:
if "created_at" in column_names:
batch_op.alter_column(
"created_at",
existing_type=sa.TIMESTAMP(timezone=True),
nullable=True,
# existing_server_default expects str | bool | Identity | Computed | None
# sa.text("now()") is not a valid value for existing_server_default
existing_server_default=False,
)
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "variable" not in table_names:
return
with op.batch_alter_table("variable", schema=None) as batch_op:
if "created_at" in inspector.get_columns("variable"):
batch_op.alter_column(
"created_at",
existing_type=sa.TIMESTAMP(timezone=True),
nullable=False,
existing_server_default=False,
)
# ### end Alembic commands ###

View file

@ -14,7 +14,6 @@ from langflow.api.v1.schemas import (
RunResponse,
SimplifiedAPIRequest,
TaskStatusResponse,
Tweaks,
UpdateCustomComponentRequest,
UploadFileResponse,
)
@ -24,6 +23,7 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.interface.custom.utils import build_custom_component_template
from langflow.processing.process import process_tweaks, run_graph_internal
from langflow.schema.graph import Tweaks
from langflow.services.auth.utils import api_key_security, get_current_active_user
from langflow.services.cache.utils import save_uploaded_file
from langflow.services.database.models.flow import Flow
@ -373,7 +373,7 @@ async def create_upload_file(
@router.get("/version")
def get_version():
try:
from langflow.version import __version__
from langflow.version import __version__ # type: ignore
version = __version__
package = "Langflow"

View file

@ -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"}

View file

@ -4,10 +4,11 @@ from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, RootModel, field_validator, model_serializer
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_serializer
from langflow.graph.schema import RunOutputs
from langflow.schema import dotdict
from langflow.schema.graph import Tweaks
from langflow.schema.schema import InputType, OutputType
from langflow.services.database.models.api_key.model import ApiKeyRead
from langflow.services.database.models.base import orjson_dumps
@ -283,36 +284,6 @@ class InputValueRequest(BaseModel):
)
class Tweaks(RootModel):
root: dict[str, Union[str, dict[str, str]]] = Field(
description="A dictionary of tweaks to adjust the flow's execution. Allows customizing flow behavior dynamically. All tweaks are overridden by the input values.",
)
model_config = {
"json_schema_extra": {
"examples": [
{
"parameter_name": "value",
"Component Name": {"parameter_name": "value"},
"component_id": {"parameter_name": "value"},
}
]
}
}
# This should behave like a dict
def __getitem__(self, key):
return self.root[key]
def __setitem__(self, key, value):
self.root[key] = value
def __delitem__(self, key):
del self.root[key]
def items(self):
return self.root.items()
class SimplifiedAPIRequest(BaseModel):
input_value: Optional[str] = Field(default="", description="The input value")
input_type: Optional[InputType] = Field(default="chat", description="The input type")

View file

@ -4,7 +4,7 @@ from fastapi import APIRouter, HTTPException
from loguru import logger
from langflow.api.v1.base import Code, CodeValidationResponse, PromptValidationResponse, ValidatePromptRequest
from langflow.base.prompts.utils import (
from langflow.base.prompts.api_utils import (
add_new_variables_to_template,
get_old_custom_fields,
remove_old_variables_from_template,

View file

@ -24,4 +24,5 @@ FIELD_FORMAT_ATTRIBUTES = [
"real_time_refresh",
"refresh_button",
"refresh_button_text",
"options",
]

View file

@ -10,7 +10,26 @@ from langflow.schema.schema import Record
# Types of files that can be read simply by file.read()
# and have 100% to be completely readable
TEXT_FILE_TYPES = ["txt", "md", "mdx", "csv", "json", "yaml", "yml", "xml", "html", "htm", "pdf", "docx"]
TEXT_FILE_TYPES = [
"txt",
"md",
"mdx",
"csv",
"json",
"yaml",
"yml",
"xml",
"html",
"htm",
"pdf",
"docx",
"py",
"sh",
"sql",
"js",
"ts",
"tsx",
]
def is_hidden(path: Path) -> bool:

View file

@ -79,7 +79,7 @@ class ChatComponent(CustomComponent):
records = add_messages([record])
return records[0]
def build(
def build_with_record(
self,
sender: Optional[str] = "User",
sender_name: Optional[str] = "User",
@ -116,3 +116,41 @@ class ChatComponent(CustomComponent):
if session_id and isinstance(result, (Record, str)):
self.store_message(result, session_id, sender, sender_name)
return result
def build_no_record(
self,
sender: Optional[str] = "User",
sender_name: Optional[str] = "User",
input_value: Optional[str] = None,
session_id: Optional[str] = None,
return_record: Optional[bool] = False,
record_template: str = "Text: {text}\nData: {data}",
) -> Union[Text, Record]:
input_value_record: Optional[Record] = None
if return_record:
if isinstance(input_value, Record):
# Update the data of the record
input_value.data["sender"] = sender
input_value.data["sender_name"] = sender_name
input_value.data["session_id"] = session_id
else:
input_value_record = Record(
text=input_value,
data={
"sender": sender,
"sender_name": sender_name,
"session_id": session_id,
},
)
elif isinstance(input_value, Record):
input_value = records_to_text(template=record_template, records=input_value)
if not input_value:
input_value = ""
if return_record and input_value_record:
result: Union[Text, Record] = input_value_record
else:
result = input_value
self.status = result
if session_id and isinstance(result, (Record, str)):
self.store_message(result, session_id, sender, sender_name)
return result

View file

@ -0,0 +1,83 @@
from fastapi import HTTPException
from langchain.prompts import PromptTemplate
from loguru import logger
from langflow.api.v1.base import INVALID_NAMES, check_input_variables
from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.template.field.prompt import DefaultPromptField
def validate_prompt(prompt_template: str, silent_errors: bool = False) -> list[str]:
input_variables = extract_input_variables_from_prompt(prompt_template)
# Check if there are invalid characters in the input_variables
input_variables = check_input_variables(input_variables)
if any(var in INVALID_NAMES for var in input_variables):
raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
try:
PromptTemplate(template=prompt_template, input_variables=input_variables)
except Exception as exc:
logger.error(f"Invalid prompt: {exc}")
if not silent_errors:
raise ValueError(f"Invalid prompt: {exc}") from exc
return input_variables
def get_old_custom_fields(custom_fields, name):
try:
if len(custom_fields) == 1 and name == "":
# If there is only one custom field and the name is empty string
# then we are dealing with the first prompt request after the node was created
name = list(custom_fields.keys())[0]
old_custom_fields = custom_fields[name]
if not old_custom_fields:
old_custom_fields = []
old_custom_fields = old_custom_fields.copy()
except KeyError:
old_custom_fields = []
custom_fields[name] = []
return old_custom_fields
def add_new_variables_to_template(input_variables, custom_fields, template, name):
for variable in input_variables:
try:
template_field = DefaultPromptField(name=variable, display_name=variable)
if variable in template:
# Set the new field with the old value
template_field.value = template[variable]["value"]
template[variable] = template_field.to_dict()
# Check if variable is not already in the list before appending
if variable not in custom_fields[name]:
custom_fields[name].append(variable)
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc
def remove_old_variables_from_template(old_custom_fields, input_variables, custom_fields, template, name):
for variable in old_custom_fields:
if variable not in input_variables:
try:
# Remove the variable from custom_fields associated with the given name
if variable in custom_fields[name]:
custom_fields[name].remove(variable)
# Remove the variable from the template
template.pop(variable, None)
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc
def update_input_variables_field(input_variables, template):
if "input_variables" in template:
template["input_variables"]["value"] = input_variables

View file

@ -1,12 +1,19 @@
from fastapi import HTTPException
from langchain.prompts import PromptTemplate
from langchain_core.documents import Document
from loguru import logger
from langflow.api.v1.base import INVALID_NAMES, check_input_variables
from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.schema import Record
from langflow.template.field.prompt import DefaultPromptField
def record_to_string(record: Record) -> str:
"""
Convert a record to a string.
Args:
record (Record): The record to convert.
Returns:
str: The record as a string.
"""
return record.get_text()
def dict_values_to_string(d: dict) -> dict:
@ -35,19 +42,6 @@ def dict_values_to_string(d: dict) -> dict:
return d
def record_to_string(record: Record) -> str:
"""
Convert a record to a string.
Args:
record (Record): The record to convert.
Returns:
str: The record as a string.
"""
return record.get_text()
def document_to_string(document: Document) -> str:
"""
Convert a document to a string.
@ -59,79 +53,3 @@ def document_to_string(document: Document) -> str:
str: The document as a string.
"""
return document.page_content
def validate_prompt(prompt_template: str, silent_errors: bool = False) -> list[str]:
input_variables = extract_input_variables_from_prompt(prompt_template)
# Check if there are invalid characters in the input_variables
input_variables = check_input_variables(input_variables)
if any(var in INVALID_NAMES for var in input_variables):
raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
try:
PromptTemplate(template=prompt_template, input_variables=input_variables)
except Exception as exc:
logger.error(f"Invalid prompt: {exc}")
if not silent_errors:
raise ValueError(f"Invalid prompt: {exc}") from exc
return input_variables
def get_old_custom_fields(custom_fields, name):
try:
if len(custom_fields) == 1 and name == "":
# If there is only one custom field and the name is empty string
# then we are dealing with the first prompt request after the node was created
name = list(custom_fields.keys())[0]
old_custom_fields = custom_fields[name]
if not old_custom_fields:
old_custom_fields = []
old_custom_fields = old_custom_fields.copy()
except KeyError:
old_custom_fields = []
custom_fields[name] = []
return old_custom_fields
def add_new_variables_to_template(input_variables, custom_fields, template, name):
for variable in input_variables:
try:
template_field = DefaultPromptField(name=variable, display_name=variable)
if variable in template:
# Set the new field with the old value
template_field.value = template[variable]["value"]
template[variable] = template_field.to_dict()
# Check if variable is not already in the list before appending
if variable not in custom_fields[name]:
custom_fields[name].append(variable)
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc
def remove_old_variables_from_template(old_custom_fields, input_variables, custom_fields, template, name):
for variable in old_custom_fields:
if variable not in input_variables:
try:
# Remove the variable from custom_fields associated with the given name
if variable in custom_fields[name]:
custom_fields[name].remove(variable)
# Remove the variable from the template
template.pop(variable, None)
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc
def update_input_variables_field(input_variables, template):
if "input_variables" in template:
template["input_variables"]["value"] = input_variables

View file

@ -0,0 +1,23 @@
from langflow.field_typing import Tool
def build_status_from_tool(tool: Tool):
"""
Builds a status string representation of a tool.
Args:
tool (Tool): The tool object to build the status for.
Returns:
str: The status string representation of the tool, including its name, description, and arguments (if any).
"""
description_repr = repr(tool.description).strip("'")
args_str = "\n".join(
[
f"- {arg_name}: {arg_data['description']}"
for arg_name, arg_data in tool.args.items()
if "description" in arg_data
]
)
status = f"Name: {tool.name}\nDescription: {description_repr}"
return status + (f"\nArguments:\n{args_str}" if args_str else "")

View file

@ -4,7 +4,7 @@ from langchain.agents import create_xml_agent
from langchain_core.prompts import PromptTemplate
from langflow.base.agents.agent import LCAgentComponent
from langflow.field_typing import BaseLLM, BaseMemory, Text, Tool
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text, Tool
class XMLAgentComponent(LCAgentComponent):
@ -66,7 +66,7 @@ class XMLAgentComponent(LCAgentComponent):
async def build(
self,
input_value: str,
llm: BaseLLM,
llm: BaseLanguageModel,
tools: List[Tool],
prompt: str,
memory: Optional[BaseMemory] = None,

View file

@ -1,9 +1,10 @@
from typing import Optional
from langchain.chains import LLMChain
from langchain.chains.llm import LLMChain
from langflow.field_typing import BaseLanguageModel, BaseMemory, BasePromptTemplate, Text
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.prompts import PromptTemplate
class LLMChainComponent(CustomComponent):
@ -19,10 +20,11 @@ class LLMChainComponent(CustomComponent):
def build(
self,
prompt: BasePromptTemplate,
template: Text,
llm: BaseLanguageModel,
memory: Optional[BaseMemory] = None,
) -> Text:
prompt = PromptTemplate.from_template(template)
runnable = LLMChain(prompt=prompt, llm=llm, memory=memory)
result_dict = runnable.invoke({})
output_key = runnable.output_key

View file

@ -3,7 +3,6 @@ import json
from typing import List, Optional
import httpx
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record
@ -47,8 +46,8 @@ class APIRequest(CustomComponent):
client: httpx.AsyncClient,
method: str,
url: str,
headers: Optional[Record] = None,
body: Optional[Record] = None,
headers: Optional[dict] = None,
body: Optional[dict] = None,
timeout: int = 5,
) -> Record:
method = method.upper()

View file

@ -1,6 +1,7 @@
from typing import Any, Dict, List, Optional
from langchain_openai.embeddings.base import OpenAIEmbeddings
from pydantic.v1 import SecretStr
from langflow.field_typing import Embeddings, NestedDict
from langflow.interface.custom.custom_component import CustomComponent
@ -113,6 +114,10 @@ class OpenAIEmbeddingsComponent(CustomComponent):
# This is to avoid errors with Vector Stores (e.g Chroma)
if disallowed_special == ["all"]:
disallowed_special = "all" # type: ignore
if openai_api_key:
api_key = SecretStr(openai_api_key)
else:
api_key = None
return OpenAIEmbeddings(
tiktoken_enabled=tiktoken_enable,
@ -128,7 +133,7 @@ class OpenAIEmbeddingsComponent(CustomComponent):
model=model,
model_kwargs=model_kwargs,
base_url=openai_api_base,
api_key=openai_api_key,
api_key=api_key,
openai_api_type=openai_api_type,
api_version=openai_api_version,
organization=openai_organization,

View file

@ -1,14 +1,14 @@
from typing import Any, List, Optional
from asyncer import syncify
from langchain_core.tools import StructuredTool
from loguru import logger
from langflow.custom import CustomComponent
from langflow.field_typing import Tool
from langflow.graph.graph.base import Graph
from langflow.helpers.flow import build_function_and_schema
from langflow.schema.dotdict import dotdict
from langflow.schema.schema import Record
from loguru import logger
class FlowToolComponent(CustomComponent):
@ -74,6 +74,7 @@ class FlowToolComponent(CustomComponent):
graph = Graph.from_payload(flow_record.data["data"])
dynamic_flow_function, schema = build_function_and_schema(flow_record, graph)
tool = StructuredTool.from_function(
func=syncify(dynamic_flow_function, raise_sync_error=False), # type: ignore
coroutine=dynamic_flow_function,
name=name,
description=description,

View file

@ -61,6 +61,6 @@ class MemoryComponent(CustomComponent):
limit=n_messages,
order=order,
)
messages_str = records_to_text(template=record_template, records=messages)
messages_str = records_to_text(template=record_template or "", records=messages)
self.status = messages_str
return messages_str

View file

@ -1,4 +1,4 @@
from typing import Optional
from typing import Optional, Union
from langchain.text_splitter import CharacterTextSplitter, RecursiveCharacterTextSplitter
from langchain_core.documents import Document
@ -51,6 +51,8 @@ class SplitTextComponent(CustomComponent):
chunk_overlap: Optional[int] = 200,
recursive: bool = False,
) -> list[Record]:
if separators is None:
separators = []
separators = [unescape_string(x) for x in separators]
# Make sure chunk_size and chunk_overlap are ints
@ -58,7 +60,7 @@ class SplitTextComponent(CustomComponent):
chunk_size = int(chunk_size)
if isinstance(chunk_overlap, str):
chunk_overlap = int(chunk_overlap)
splitter: Optional[Union[CharacterTextSplitter, RecursiveCharacterTextSplitter]] = None
if recursive:
splitter = RecursiveCharacterTextSplitter(
separators=separators,

View file

@ -28,7 +28,7 @@ class ChatInput(ChatComponent):
session_id: Optional[str] = None,
return_record: Optional[bool] = False,
) -> Union[Text, Record]:
return super().build(
return super().build_no_record(
sender=sender,
sender_name=sender_name,
input_value=input_value,

View file

@ -26,7 +26,7 @@ class TextInput(TextComponent):
def build(
self,
input_value: Optional[str] = "",
input_value: Optional[Text] = "",
record_template: Optional[str] = "",
) -> Text:
return super().build(input_value=input_value, record_template=record_template)

View file

@ -1,6 +1,5 @@
from typing import Optional
from langchain.llms.base import BaseLLM
from langflow.field_typing import BaseLanguageModel
from langchain_community.llms.bedrock import Bedrock
from langflow.interface.custom.custom_component import CustomComponent
@ -46,7 +45,7 @@ class AmazonBedrockComponent(CustomComponent):
endpoint_url: Optional[str] = None,
streaming: bool = False,
cache: Optional[bool] = None,
) -> BaseLLM:
) -> BaseLanguageModel:
try:
output = Bedrock(
credentials_profile_name=credentials_profile_name,

View file

@ -1,14 +1,14 @@
from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_community.chat_models.anthropic import ChatAnthropic
from langchain_anthropic import ChatAnthropic
from pydantic.v1 import SecretStr
from langflow.interface.custom.custom_component import CustomComponent
class AnthropicLLM(CustomComponent):
display_name: str = "AnthropicLLM"
class ChatAntropicSpecsComponent(CustomComponent):
display_name: str = "Anthropic"
description: str = "Anthropic Chat&Completion large language models."
icon = "Anthropic"

View file

@ -1,49 +0,0 @@
from typing import Optional
from langchain_community.llms.anthropic import Anthropic
from pydantic.v1 import SecretStr
from langflow.field_typing import BaseLanguageModel, NestedDict
from langflow.interface.custom.custom_component import CustomComponent
class AnthropicComponent(CustomComponent):
display_name = "Anthropic"
description = "Anthropic large language models."
icon = "Anthropic"
def build_config(self):
return {
"anthropic_api_key": {
"display_name": "Anthropic API Key",
"type": str,
"password": True,
},
"anthropic_api_url": {
"display_name": "Anthropic API URL",
"type": str,
},
"model_kwargs": {
"display_name": "Model Kwargs",
"field_type": "NestedDict",
"advanced": True,
},
"temperature": {
"display_name": "Temperature",
"field_type": "float",
},
}
def build(
self,
anthropic_api_key: str,
anthropic_api_url: str,
model_kwargs: NestedDict = {},
temperature: Optional[float] = None,
) -> BaseLanguageModel:
return Anthropic(
anthropic_api_key=SecretStr(anthropic_api_key),
anthropic_api_url=anthropic_api_url,
model_kwargs=model_kwargs,
temperature=temperature,
)

View file

@ -1,9 +1,9 @@
from typing import Optional
from langchain.llms.base import BaseLLM
from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
from pydantic.v1 import SecretStr
from langflow.field_typing import BaseLanguageModel
from langflow.interface.custom.custom_component import CustomComponent
@ -79,7 +79,7 @@ class QianfanChatEndpointComponent(CustomComponent):
temperature: Optional[float] = None,
penalty_score: Optional[float] = None,
endpoint: Optional[str] = None,
) -> BaseLLM:
) -> BaseLanguageModel:
try:
output = QianfanChatEndpoint( # type: ignore
model=model,

View file

@ -1,9 +1,9 @@
from typing import Optional
from langchain.llms.baidu_qianfan_endpoint import QianfanLLMEndpoint
from langchain.llms.base import BaseLLM
from langflow.interface.custom.custom_component import CustomComponent
from langflow.field_typing import BaseLanguageModel
class QianfanLLMEndpointComponent(CustomComponent):
@ -78,7 +78,7 @@ class QianfanLLMEndpointComponent(CustomComponent):
temperature: Optional[float] = None,
penalty_score: Optional[float] = None,
endpoint: Optional[str] = None,
) -> BaseLLM:
) -> BaseLanguageModel:
try:
output = QianfanLLMEndpoint( # type: ignore
model=model,

View file

@ -1,67 +1,89 @@
from typing import Callable, Optional, Union
from typing import Optional
from langchain_community.chat_models.anthropic import ChatAnthropic
from langchain_anthropic import ChatAnthropic
from pydantic.v1.types import SecretStr
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel
class ChatAnthropicComponent(CustomComponent):
display_name = "ChatAnthropic"
description = "`Anthropic` chat large language models."
documentation = "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/anthropic"
class AnthropicLLM(CustomComponent):
display_name: str = "Anthropic"
description: str = "Generate text using Anthropic Chat&Completion LLMs."
icon = "Anthropic"
field_order = [
"model",
"anthropic_api_key",
"max_tokens",
"temperature",
"anthropic_api_url",
]
def build_config(self):
return {
"model": {
"display_name": "Model Name",
"options": [
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-haiku-20240307",
"claude-2.1",
"claude-2.0",
"claude-instant-1.2",
"claude-instant-1",
],
"info": "Name of the model to use.",
"required": True,
"value": "claude-3-opus-20240229",
},
"anthropic_api_key": {
"display_name": "Anthropic API Key",
"field_type": "str",
"required": True,
"password": True,
},
"model_kwargs": {
"display_name": "Model Kwargs",
"field_type": "dict",
"advanced": True,
},
"model_name": {
"display_name": "Model Name",
"field_type": "str",
"advanced": False,
"required": False,
"options": ["claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307"],
},
"temperature": {
"display_name": "Temperature",
"field_type": "float",
"info": "Your Anthropic API key.",
},
"max_tokens": {
"display_name": "Max Tokens",
"field_type": "int",
"advanced": False,
"required": False,
"advanced": True,
"value": 256,
},
"top_k": {"display_name": "Top K", "field_type": "int", "advanced": True},
"top_p": {"display_name": "Top P", "field_type": "float", "advanced": True},
"temperature": {
"display_name": "Temperature",
"field_type": "float",
"value": 0.1,
},
"anthropic_api_url": {
"display_name": "Anthropic API URL",
"advanced": True,
"info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
},
"code": {"show": False},
}
def build(
self,
anthropic_api_key: str,
model_kwargs: dict = {},
model_name: str = "claude-3-opus-20240229",
model: str,
anthropic_api_key: Optional[str] = None,
max_tokens: Optional[int] = None,
temperature: Optional[float] = None,
max_tokens: Optional[int] = 1024,
top_k: Optional[int] = None,
top_p: Optional[float] = None,
) -> Union[BaseLanguageModel, Callable]:
return ChatAnthropic(
anthropic_api_key=SecretStr(anthropic_api_key),
model_kwargs=model_kwargs,
model_name=model_name,
temperature=temperature,
max_tokens=max_tokens, # type: ignore
top_k=top_k,
top_p=top_p,
)
anthropic_api_url: Optional[str] = None,
) -> BaseLanguageModel:
# Set default API endpoint if not provided
if not anthropic_api_url:
anthropic_api_url = "https://api.anthropic.com"
try:
output = ChatAnthropic(
model_name=model,
anthropic_api_key=(SecretStr(anthropic_api_key) if anthropic_api_key else None),
max_tokens_to_sample=max_tokens, # type: ignore
temperature=temperature,
anthropic_api_url=anthropic_api_url,
)
except Exception as e:
raise ValueError("Could not connect to Anthropic API.") from e
return output

View file

@ -1,4 +1,4 @@
from typing import Any, Callable, Dict, Optional, Union
from typing import Any, Dict, Optional
from langchain_community.chat_models.litellm import ChatLiteLLM, ChatLiteLLMException
from langflow.field_typing import BaseLanguageModel

View file

@ -1,9 +1,9 @@
from typing import Optional, Union
from typing import Optional
from langchain.llms import BaseLLM
from langflow.field_typing import BaseLanguageModel
from langchain_community.chat_models.openai import ChatOpenAI
from langflow.field_typing import BaseLanguageModel, NestedDict
from langflow.field_typing import NestedDict
from langflow.interface.custom.custom_component import CustomComponent
@ -68,7 +68,7 @@ class ChatOpenAIComponent(CustomComponent):
openai_api_base: Optional[str] = None,
openai_api_key: Optional[str] = None,
temperature: float = 0.7,
) -> Union[BaseLanguageModel, BaseLLM]:
) -> BaseLanguageModel:
if not openai_api_base:
openai_api_base = "https://api.openai.com/v1"
return ChatOpenAI(

View file

@ -1,6 +1,5 @@
from typing import List, Optional, Union
from typing import List, Optional
from langchain.llms import BaseLLM
from langchain_community.chat_models.vertexai import ChatVertexAI
from langchain_core.messages.base import BaseMessage
@ -74,7 +73,7 @@ class ChatVertexAIComponent(CustomComponent):
top_k: int = 40,
top_p: float = 0.95,
verbose: bool = False,
) -> Union[BaseLanguageModel, BaseLLM]:
) -> BaseLanguageModel:
return ChatVertexAI(
credentials=credentials,
examples=examples,

View file

@ -1,6 +1,6 @@
from typing import Optional
from langchain.llms.base import BaseLLM
from langflow.field_typing import BaseLanguageModel
from langchain.llms.huggingface_endpoint import HuggingFaceEndpoint
from langflow.interface.custom.custom_component import CustomComponent
@ -32,7 +32,7 @@ class HuggingFaceEndpointsComponent(CustomComponent):
task: str = "text2text-generation",
huggingfacehub_api_token: Optional[str] = None,
model_kwargs: Optional[dict] = None,
) -> BaseLLM:
) -> BaseLanguageModel:
try:
output = HuggingFaceEndpoint( # type: ignore
endpoint_url=endpoint_url,

View file

@ -1,6 +1,6 @@
from typing import List, Optional
from langchain.llms.base import BaseLLM
from langflow.field_typing import BaseLanguageModel
from langchain_community.llms.ollama import Ollama
from langflow.interface.custom.custom_component import CustomComponent
@ -118,7 +118,7 @@ class OllamaLLM(CustomComponent):
tfs_z: Optional[float] = None,
top_k: Optional[int] = None,
top_p: Optional[int] = None,
) -> BaseLLM:
) -> BaseLanguageModel:
if not base_url:
base_url = "http://localhost:11434"

View file

@ -1,6 +1,6 @@
from typing import Callable, Dict, Optional, Union
from typing import Dict, Optional
from langchain.llms import BaseLLM
from langflow.field_typing import BaseLanguageModel
from langchain_community.llms.vertexai import VertexAI
from langflow.interface.custom.custom_component import CustomComponent
@ -129,7 +129,7 @@ class VertexAIComponent(CustomComponent):
top_p: float = 0.95,
tuned_model_name: Optional[str] = None,
verbose: bool = False,
) -> Union[BaseLLM, Callable]:
) -> BaseLanguageModel:
return VertexAI(
credentials=credentials,
location=location,

View file

@ -1,10 +1,7 @@
from .AmazonBedrockSpecs import AmazonBedrockComponent
from .AnthropicLLMSpecs import AnthropicLLM
from .AnthropicSpecs import AnthropicComponent
from .AzureChatOpenAISpecs import AzureChatOpenAISpecsComponent
from .BaiduQianfanChatEndpointsSpecs import QianfanChatEndpointComponent
from .BaiduQianfanLLMEndpointsSpecs import QianfanLLMEndpointComponent
from .ChatAnthropicSpecs import ChatAnthropicComponent
from .ChatAnthropicSpecs import AnthropicLLM
from .ChatLiteLLMSpecs import ChatLiteLLMComponent
from .ChatOllamaEndpointSpecs import ChatOllamaComponent
from .ChatOpenAISpecs import ChatOpenAIComponent
@ -17,12 +14,11 @@ from .VertexAISpecs import VertexAIComponent
__all__ = [
"AmazonBedrockComponent",
"AnthropicLLM",
"AnthropicComponent",
"ChatAntropicSpecsComponent",
"AzureChatOpenAISpecsComponent",
"QianfanChatEndpointComponent",
"QianfanLLMEndpointComponent",
"ChatAnthropicComponent",
"AnthropicLLM",
"ChatLiteLLMComponent",
"ChatOllamaComponent",
"ChatOpenAIComponent",

View file

@ -2,6 +2,7 @@ from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_openai import AzureChatOpenAI
from pydantic.v1 import SecretStr
from langflow.base.constants import STREAM_INFO_TEXT
from langflow.base.models.model import LCModelComponent
@ -105,13 +106,17 @@ class AzureChatOpenAIComponent(LCModelComponent):
max_tokens: Optional[int] = 1000,
stream: bool = False,
) -> BaseLanguageModel:
if api_key:
secret_api_key = SecretStr(api_key)
else:
secret_api_key = None
try:
output = AzureChatOpenAI(
model=model,
azure_endpoint=azure_endpoint,
azure_deployment=azure_deployment,
api_version=api_version,
api_key=api_key,
api_key=secret_api_key,
temperature=temperature,
max_tokens=max_tokens,
)

View file

@ -1,6 +1,7 @@
from typing import Optional
from langchain_openai import ChatOpenAI
from pydantic.v1 import SecretStr
from langflow.base.constants import STREAM_INFO_TEXT
from langflow.base.models.model import LCModelComponent
@ -94,12 +95,17 @@ class OpenAIModelComponent(LCModelComponent):
) -> Text:
if not openai_api_base:
openai_api_base = "https://api.openai.com/v1"
if openai_api_key:
api_key = SecretStr(openai_api_key)
else:
api_key = None
output = ChatOpenAI(
max_tokens=max_tokens,
model_kwargs=model_kwargs,
model=model_name,
base_url=openai_api_base,
api_key=openai_api_key,
api_key=api_key,
temperature=temperature,
)

View file

@ -19,11 +19,11 @@ class ChatOutput(ChatComponent):
return_record: Optional[bool] = False,
record_template: Optional[str] = "{text}",
) -> Union[Text, Record]:
return super().build(
return super().build_with_record(
sender=sender,
sender_name=sender_name,
input_value=input_value,
session_id=session_id,
return_record=return_record,
record_template=record_template,
record_template=record_template or "",
)

View file

@ -24,5 +24,5 @@ class TextOutput(TextComponent):
},
}
def build(self, input_value: Optional[Text] = "", record_template: str = "") -> Text:
def build(self, input_value: Optional[Text] = "", record_template: Optional[str] = "") -> Text:
return super().build(input_value=input_value, record_template=record_template)

View file

@ -1,8 +1,8 @@
from typing import Callable, Optional, Union
from typing import Optional
from langchain.retrievers import MultiQueryRetriever
from langflow.field_typing import BaseLLM, BaseRetriever, PromptTemplate
from langflow.field_typing import BaseRetriever, PromptTemplate, BaseLanguageModel
from langflow.interface.custom.custom_component import CustomComponent
@ -39,11 +39,11 @@ class MultiQueryRetrieverComponent(CustomComponent):
def build(
self,
llm: BaseLLM,
llm: BaseLanguageModel,
retriever: BaseRetriever,
prompt: Optional[PromptTemplate] = None,
parser_key: str = "lines",
) -> Union[Callable, MultiQueryRetriever]:
) -> MultiQueryRetriever:
if not prompt:
return MultiQueryRetriever.from_llm(llm=llm, retriever=retriever, parser_key=parser_key)
else:

View file

@ -1,5 +1,3 @@
from typing import Callable, Union
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo
from langchain_community.vectorstores import VectorStore
@ -22,5 +20,5 @@ class VectorStoreInfoComponent(CustomComponent):
vectorstore: VectorStore,
description: str,
name: str,
) -> Union[VectorStoreInfo, Callable]:
) -> VectorStoreInfo:
return VectorStoreInfo(vectorstore=vectorstore, description=description, name=name)

View file

@ -0,0 +1,68 @@
import importlib
from langchain.agents import Tool
from langchain_experimental.utilities import PythonREPL
from langflow.base.tools.base import build_status_from_tool
from langflow.custom import CustomComponent
class PythonREPLToolComponent(CustomComponent):
display_name = "Python REPL Tool"
description = "A tool for running Python code in a REPL environment."
def build_config(self):
return {
"name": {"display_name": "Name", "info": "The name of the tool."},
"description": {"display_name": "Description", "info": "A description of the tool."},
"global_imports": {
"display_name": "Global Imports",
"info": "A list of modules to import globally, e.g. ['math', 'numpy'].",
},
}
def get_globals(self, globals: list[str]) -> dict:
"""
Retrieves the global variables from the specified modules.
Args:
globals (list[str]): A list of module names.
Returns:
dict: A dictionary containing the global variables from the specified modules.
"""
global_dict = {}
for module in globals:
try:
imported_module = importlib.import_module(module)
global_dict[imported_module.__name__] = imported_module
except ImportError:
print(f"Could not import module {module}")
return global_dict
def build(
self,
name: str = "python_repl",
description: str = "A Python shell. Use this to execute python commands. Input should be a valid python command. If you want to see the output of a value, you should print it out with `print(...)`.",
global_imports: list[str] = ["math"],
) -> Tool:
"""
Builds a Python REPL tool.
Args:
name (str, optional): The name of the tool. Defaults to "python_repl".
description (str, optional): The description of the tool. Defaults to "A Python shell. Use this to execute python commands. Input should be a valid python command. If you want to see the output of a value, you should print it out with `print(...)`. ".
global_imports (list[str], optional): A list of global imports to be available in the Python REPL. Defaults to ["math"].
Returns:
Tool: The built Python REPL tool.
"""
_globals = self.get_globals(global_imports)
python_repl = PythonREPL(_globals=_globals)
tool = Tool(
name=name,
description=description,
func=python_repl.run,
)
self.status = build_status_from_tool(tool)
return tool

View file

@ -1,5 +1,7 @@
from .PythonREPLTool import PythonREPLToolComponent
from .RetrieverTool import RetrieverToolComponent
from .SearchAPITool import SearchApiToolComponent
from .SearchApi import SearchApi
from .SearchAPITool import SearchApiToolComponent
__all__ = ["RetrieverToolComponent", "SearchApiToolComponent", "SearchApi"]
__all__ = ["RetrieverToolComponent", "SearchApiToolComponent", "SearchApi", "PythonREPLToolComponent"]

View file

@ -3,6 +3,7 @@ from typing import List, Optional
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Pinecone import PineconeComponent
from langflow.field_typing import Embeddings, Text
from langflow.field_typing.constants import NestedDict
from langflow.schema import Record
@ -32,7 +33,6 @@ class PineconeSearchComponent(PineconeComponent, LCVectorStoreComponent):
"default": "",
"required": True,
},
"search_kwargs": {"display_name": "Search Kwargs", "default": "{}"},
"pool_threads": {
"display_name": "Pool Threads",
"default": 1,
@ -57,6 +57,7 @@ class PineconeSearchComponent(PineconeComponent, LCVectorStoreComponent):
pinecone_api_key: Optional[str] = None,
namespace: Optional[str] = "default",
search_type: str = "similarity",
search_kwargs: Optional[NestedDict] = None,
) -> List[Record]: # type: ignore[override]
vector_store = super().build(
embedding=embedding,
@ -70,7 +71,13 @@ class PineconeSearchComponent(PineconeComponent, LCVectorStoreComponent):
)
if not vector_store:
raise ValueError("Failed to load the Pinecone index.")
if search_kwargs is None:
search_kwargs = {}
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type=search_type, k=number_of_results
vector_store=vector_store,
input_value=input_value,
search_type=search_type,
k=number_of_results,
**search_kwargs,
)

View file

@ -86,13 +86,18 @@ class QdrantSearchComponent(QdrantComponent, LCVectorStoreComponent):
port=port,
prefer_grpc=prefer_grpc,
prefix=prefix,
search_kwargs=search_kwargs,
timeout=timeout,
url=url,
)
if not vector_store:
raise ValueError("Failed to load the Qdrant index.")
if search_kwargs is None:
search_kwargs = {}
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type=search_type, k=number_of_results
vector_store=vector_store,
input_value=input_value,
search_type=search_type,
k=number_of_results,
**search_kwargs,
)

View file

@ -105,7 +105,7 @@ class AstraDBVectorStoreComponent(CustomComponent):
bulk_insert_batch_concurrency: Optional[int] = None,
bulk_insert_overwrite_concurrency: Optional[int] = None,
bulk_delete_concurrency: Optional[int] = None,
setup_mode: str = "Async",
setup_mode: str = "Sync",
pre_delete_collection: bool = False,
metadata_indexing_include: Optional[List[str]] = None,
metadata_indexing_exclude: Optional[List[str]] = None,

View file

@ -1,7 +1,7 @@
from typing import List, Optional
from langchain_community.vectorstores.mongodb_atlas import MongoDBAtlasVectorSearch
from langflow.field_typing import Embeddings, NestedDict
from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
@ -19,7 +19,6 @@ class MongoDBAtlasComponent(CustomComponent):
"db_name": {"display_name": "Database Name"},
"index_name": {"display_name": "Index Name"},
"mongodb_atlas_cluster_uri": {"display_name": "MongoDB Atlas Cluster URI"},
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
}
def build(
@ -30,9 +29,7 @@ class MongoDBAtlasComponent(CustomComponent):
db_name: str = "",
index_name: str = "",
mongodb_atlas_cluster_uri: str = "",
search_kwargs: Optional[NestedDict] = None,
) -> MongoDBAtlasVectorSearch:
search_kwargs = search_kwargs or {}
try:
from pymongo import MongoClient
except ImportError:
@ -56,7 +53,6 @@ class MongoDBAtlasComponent(CustomComponent):
db_name=db_name,
index_name=index_name,
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
search_kwargs=search_kwargs,
)
else:
vector_store = MongoDBAtlasVectorSearch(

View file

@ -33,7 +33,6 @@ class PineconeComponent(CustomComponent):
"default": "",
"required": True,
},
"search_kwargs": {"display_name": "Search Kwargs", "default": "{}"},
"pool_threads": {
"display_name": "Pool Threads",
"default": 1,

View file

@ -4,7 +4,7 @@ from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.qdrant import Qdrant
from langflow.field_typing import Embeddings, NestedDict
from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
@ -37,7 +37,6 @@ class QdrantComponent(CustomComponent):
"port": {"display_name": "Port", "advanced": True},
"prefer_grpc": {"display_name": "Prefer gRPC", "advanced": True},
"prefix": {"display_name": "Prefix", "advanced": True},
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
"timeout": {"display_name": "Timeout", "advanced": True},
"url": {"display_name": "URL", "advanced": True},
}
@ -59,7 +58,6 @@ class QdrantComponent(CustomComponent):
port: Optional[int] = 6333,
prefer_grpc: bool = False,
prefix: Optional[str] = None,
search_kwargs: Optional[NestedDict] = None,
timeout: Optional[int] = None,
url: Optional[str] = None,
) -> Union[VectorStore, Qdrant, BaseRetriever]:
@ -111,7 +109,6 @@ class QdrantComponent(CustomComponent):
port=port,
prefer_grpc=prefer_grpc,
prefix=prefix,
search_kwargs=search_kwargs,
timeout=timeout,
url=url,
)

View file

@ -5,7 +5,7 @@ from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.supabase import SupabaseVectorStore
from supabase.client import Client, create_client
from langflow.field_typing import Embeddings, NestedDict
from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
@ -19,7 +19,6 @@ class SupabaseComponent(CustomComponent):
"inputs": {"display_name": "Input", "input_types": ["Document", "Record"]},
"embedding": {"display_name": "Embedding"},
"query_name": {"display_name": "Query Name"},
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
"supabase_service_key": {"display_name": "Supabase Service Key"},
"supabase_url": {"display_name": "Supabase URL"},
"table_name": {"display_name": "Table Name", "advanced": True},
@ -30,7 +29,6 @@ class SupabaseComponent(CustomComponent):
embedding: Embeddings,
inputs: Optional[List[Record]] = None,
query_name: str = "",
search_kwargs: NestedDict = {},
supabase_service_key: str = "",
supabase_url: str = "",
table_name: str = "",
@ -46,7 +44,6 @@ class SupabaseComponent(CustomComponent):
documents=documents,
embedding=embedding,
query_name=query_name,
search_kwargs=search_kwargs,
client=supabase,
table_name=table_name,
)

View file

@ -249,7 +249,10 @@ class Graph:
vertex.update_raw_params({"session_id": session_id})
# Process the graph
try:
await self.process()
start_component_id = next(
(vertex_id for vertex_id in self._is_input_vertices if "chat" in vertex_id.lower()), None
)
await self.process(start_component_id=start_component_id)
self.increment_run_count()
except Exception as exc:
logger.exception(exc)
@ -293,18 +296,27 @@ class Graph:
# run the async function in a sync way
# this could be used in a FastAPI endpoint
# so we should take care of the event loop
loop = asyncio.get_event_loop()
return loop.run_until_complete(
self.arun(
inputs=inputs,
inputs_components=input_components,
types=types,
outputs=outputs,
session_id=session_id,
stream=stream,
)
coro = self.arun(
inputs=inputs,
inputs_components=input_components,
types=types,
outputs=outputs,
session_id=session_id,
stream=stream,
)
try:
# Attempt to get the running event loop; if none, an exception is raised
loop = asyncio.get_running_loop()
if loop.is_closed():
raise RuntimeError("The running event loop is closed.")
except RuntimeError:
# If there's no running event loop or it's closed, use asyncio.run
return asyncio.run(coro)
# If there's an existing, open event loop, use it to run the async function
return loop.run_until_complete(coro)
async def arun(
self,
inputs: list[Dict[str, str]],
@ -345,7 +357,7 @@ class Graph:
if types is None:
types = []
for _ in range(len(inputs) - len(types)):
types.append("any")
types.append("chat") # default to chat
for run_inputs, components, input_type in zip(inputs, inputs_components, types):
run_outputs = await self._run(
inputs=run_inputs,
@ -733,8 +745,10 @@ class Graph:
vertices.append(vertex)
return vertices
async def process(self) -> "Graph":
async def process(self, start_component_id: Optional[str] = None) -> "Graph":
"""Processes the graph with vertices in each layer run in parallel."""
self.sort_vertices(start_component_id=start_component_id)
vertices_layers = self.sorted_vertices_layers
vertex_task_run_count: Dict[str, int] = {}
for layer_index, layer in enumerate(vertices_layers):
@ -904,7 +918,7 @@ class Graph:
return ChatVertex
elif node_name in ["ShouldRunNext"]:
return RoutingVertex
elif node_name in ["SharedState", "Notify", "GetNotified"]:
elif node_name in ["SharedState", "Notify", "Listen"]:
return StateVertex
elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:
return lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_base_type]
@ -1116,6 +1130,34 @@ class Graph:
return vertices_layers
def sort_layer_by_dependency(self, vertices_layers: List[List[str]]) -> List[List[str]]:
"""Sorts the vertices in each layer by dependency, ensuring no vertex depends on a subsequent vertex."""
sorted_layers = []
for layer in vertices_layers:
sorted_layer = self._sort_single_layer_by_dependency(layer)
sorted_layers.append(sorted_layer)
return sorted_layers
def _sort_single_layer_by_dependency(self, layer: List[str]) -> List[str]:
"""Sorts a single layer by dependency using a stable sorting method."""
# Build a map of each vertex to its index in the layer for quick lookup.
index_map = {vertex: index for index, vertex in enumerate(layer)}
# Create a sorted copy of the layer based on dependency order.
sorted_layer = sorted(layer, key=lambda vertex: self._max_dependency_index(vertex, index_map), reverse=True)
return sorted_layer
def _max_dependency_index(self, vertex_id: str, index_map: Dict[str, int]) -> int:
"""Finds the highest index a given vertex's dependencies occupy in the same layer."""
vertex = self.get_vertex(vertex_id)
max_index = -1
for successor in vertex.successors: # Assuming vertex.successors is a list of successor vertex identifiers.
if successor.id in index_map:
max_index = max(max_index, index_map[successor.id])
return max_index
def sort_vertices(
self,
stop_component_id: Optional[str] = None,
@ -1137,6 +1179,9 @@ class Graph:
vertices_layers = self.layered_topological_sort(vertices)
vertices_layers = self.sort_by_avg_build_time(vertices_layers)
# vertices_layers = self.sort_chat_inputs_first(vertices_layers)
# Now we should sort each layer in a way that we make sure
# vertex V does not depend on vertex V+1
vertices_layers = self.sort_layer_by_dependency(vertices_layers)
self.increment_run_count()
self._sorted_vertices_layers = vertices_layers
first_layer = vertices_layers[0]

View file

@ -1,15 +1,22 @@
from typing import TYPE_CHECKING, Callable
from langflow.services.deps import get_state_service
from loguru import logger
from langflow.services.deps import get_settings_service, get_state_service
if TYPE_CHECKING:
from langflow.services.state.service import StateService
class GraphStateManager:
def __init__(self):
self.state_service: "StateService" = get_state_service()
try:
self.state_service: "StateService" = get_state_service()
except Exception as e:
logger.debug(f"Error getting state service. Defaulting to InMemoryStateService: {e}")
from langflow.services.state.service import InMemoryStateService
self.state_service = InMemoryStateService(get_settings_service())
def append_state(self, key, new_state, run_id: str):
self.state_service.append_state(key, new_state, run_id)

View file

@ -1,10 +1,10 @@
import ast
import asyncio
import inspect
import os
import types
from enum import Enum
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional
import os
from loguru import logger
@ -315,7 +315,8 @@ class Vertex:
raise e
params[field_name] = full_path
elif field.get("required"):
raise ValueError(f"File path not found for {self.display_name}")
field_display_name = field.get("display_name")
raise ValueError(f"File path not found for {field_display_name} in component {self.display_name}")
elif field.get("type") in DIRECT_TYPES and params.get(field_name) is None:
val = field.get("value")
if field.get("type") == "code":

View file

@ -1,11 +1,10 @@
from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Optional, Tuple, Type, Union, cast
from pydantic.v1 import BaseModel, Field, create_model
from sqlmodel import select
from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional, Tuple, Type, Union, cast
from langflow.schema.schema import INPUT_FIELD_NAME, Record
from langflow.services.database.models.flow.model import Flow
from langflow.services.deps import session_scope
from pydantic.v1 import BaseModel, Field, create_model
from sqlmodel import select
if TYPE_CHECKING:
from langflow.graph.graph.base import Graph
@ -86,7 +85,7 @@ async def run_flow(
return await graph.arun(inputs_list, inputs_components=inputs_components, types=types)
def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Coroutine:
def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Callable[..., Awaitable[Any]]:
"""
Generate a dynamic flow function based on the given inputs and flow ID.
@ -145,7 +144,9 @@ async def flow_function({func_args}):
return local_scope["flow_function"]
def build_function_and_schema(flow_record: Record, graph: "Graph") -> Tuple[Callable, Type[BaseModel]]:
def build_function_and_schema(
flow_record: Record, graph: "Graph"
) -> Tuple[Callable[..., Awaitable[Any]], Type[BaseModel]]:
"""
Builds a dynamic function and schema for a given flow.

View file

@ -84,7 +84,7 @@ def log_node_changes(node_changes_log):
logger.debug("\n".join(formatted_messages))
def load_starter_projects():
def load_starter_projects() -> list[tuple[Path, dict]]:
starter_projects = []
folder = Path(__file__).parent / "starter_projects"
for file in folder.glob("*.json"):

View file

@ -45,7 +45,9 @@
"name": "template",
"display_name": "Template",
"advanced": false,
"input_types": ["Text"],
"input_types": [
"Text"
],
"dynamic": false,
"info": "",
"load_from_db": false,
@ -136,14 +138,24 @@
"is_input": null,
"is_output": null,
"is_composition": null,
"base_classes": ["object", "Text", "str"],
"base_classes": [
"object",
"Text",
"str"
],
"name": "",
"display_name": "Prompt",
"documentation": "",
"custom_fields": {
"template": ["reference_1", "reference_2", "instructions"]
"template": [
"reference_1",
"reference_2",
"instructions"
]
},
"output_types": ["Text"],
"output_types": [
"Text"
],
"full_path": null,
"field_formatters": {},
"frozen": false,
@ -210,7 +222,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"],
"input_types": [
"Text"
],
"value": [
"https://www.promptingguide.ai/techniques/prompt_chaining"
]
@ -219,13 +233,17 @@
},
"description": "Fetch content from one or more URLs.",
"icon": "layout-template",
"base_classes": ["Record"],
"base_classes": [
"Record"
],
"display_name": "URL",
"documentation": "",
"custom_fields": {
"urls": null
},
"output_types": ["Record"],
"output_types": [
"Record"
],
"field_formatters": {},
"frozen": false,
"field_order": [],
@ -260,7 +278,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Interaction Panel.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template,\n )\n",
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Interaction Panel.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build_with_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template or \"\",\n )\n",
"fileTypes": [],
"file_path": "",
"password": false,
@ -284,7 +302,9 @@
"name": "input_value",
"display_name": "Message",
"advanced": false,
"input_types": ["Text"],
"input_types": [
"Text"
],
"dynamic": false,
"info": "",
"load_from_db": false,
@ -308,7 +328,9 @@
"info": "In case of Message being a Record, this template will be used to convert it to text.",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"return_record": {
"type": "bool",
@ -340,7 +362,10 @@
"fileTypes": [],
"file_path": "",
"password": false,
"options": ["Machine", "User"],
"options": [
"Machine",
"User"
],
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
@ -348,7 +373,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"sender_name": {
"type": "str",
@ -368,7 +395,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"session_id": {
"type": "str",
@ -387,13 +416,20 @@
"info": "If provided, the message will be stored in the memory.",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"_type": "CustomComponent"
},
"description": "Display a chat message in the Interaction Panel.",
"icon": "ChatOutput",
"base_classes": ["Text", "Record", "object", "str"],
"base_classes": [
"Text",
"Record",
"object",
"str"
],
"display_name": "Chat Output",
"documentation": "",
"custom_fields": {
@ -404,7 +440,10 @@
"return_record": null,
"record_template": null
},
"output_types": ["Text", "Record"],
"output_types": [
"Text",
"Record"
],
"field_formatters": {},
"frozen": false,
"field_order": [],
@ -444,7 +483,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"code": {
"type": "code",
@ -453,7 +494,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-3.5-turbo\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n \"value\": \"gpt-4-turbo-preview\",\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float,\n model_name: str,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=openai_api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-3.5-turbo\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n \"value\": \"gpt-4-turbo-preview\",\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float,\n model_name: str,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
"fileTypes": [],
"file_path": "",
"password": false,
@ -529,7 +570,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"openai_api_base": {
"type": "str",
@ -548,7 +591,9 @@
"info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\n\nYou can change this to use other APIs like JinaChat, LocalAI and Prem.",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"openai_api_key": {
"type": "str",
@ -567,7 +612,9 @@
"info": "The OpenAI API Key to use for the OpenAI model.",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"],
"input_types": [
"Text"
],
"value": ""
},
"stream": {
@ -606,7 +653,9 @@
"info": "System message to pass to the model.",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"temperature": {
"type": "float",
@ -637,7 +686,11 @@
},
"description": "Generates text using OpenAI LLMs.",
"icon": "OpenAI",
"base_classes": ["str", "Text", "object"],
"base_classes": [
"str",
"Text",
"object"
],
"display_name": "OpenAI",
"documentation": "",
"custom_fields": {
@ -651,7 +704,9 @@
"stream": null,
"system_message": null
},
"output_types": ["Text"],
"output_types": [
"Text"
],
"field_formatters": {},
"frozen": false,
"field_order": [
@ -724,20 +779,28 @@
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"],
"value": ["https://www.promptingguide.ai/introduction/basics"]
"input_types": [
"Text"
],
"value": [
"https://www.promptingguide.ai/introduction/basics"
]
},
"_type": "CustomComponent"
},
"description": "Fetch content from one or more URLs.",
"icon": "layout-template",
"base_classes": ["Record"],
"base_classes": [
"Record"
],
"display_name": "URL",
"documentation": "",
"custom_fields": {
"urls": null
},
"output_types": ["Record"],
"output_types": [
"Record"
],
"field_formatters": {},
"frozen": false,
"field_order": [],
@ -797,7 +860,10 @@
"name": "input_value",
"display_name": "Value",
"advanced": false,
"input_types": ["Record", "Text"],
"input_types": [
"Record",
"Text"
],
"dynamic": false,
"info": "Text or Record to be passed as input.",
"load_from_db": false,
@ -821,20 +887,28 @@
"info": "Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.",
"load_from_db": false,
"title_case": false,
"input_types": ["Text"]
"input_types": [
"Text"
]
},
"_type": "CustomComponent"
},
"description": "Get text inputs from the Interaction Panel.",
"icon": "type",
"base_classes": ["object", "Text", "str"],
"base_classes": [
"object",
"Text",
"str"
],
"display_name": "Instructions",
"documentation": "",
"custom_fields": {
"input_value": null,
"record_template": null
},
"output_types": ["Text"],
"output_types": [
"Text"
],
"field_formatters": {},
"frozen": false,
"field_order": [],
@ -863,11 +937,18 @@
"targetHandle": {
"fieldName": "reference_2",
"id": "Prompt-Rse03",
"inputTypes": ["Document", "BaseOutputParser", "Record", "Text"],
"inputTypes": [
"Document",
"BaseOutputParser",
"Record",
"Text"
],
"type": "str"
},
"sourceHandle": {
"baseClasses": ["Record"],
"baseClasses": [
"Record"
],
"dataType": "URL",
"id": "URL-HYPkR"
}
@ -887,11 +968,17 @@
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-JPlxl",
"inputTypes": ["Text"],
"inputTypes": [
"Text"
],
"type": "str"
},
"sourceHandle": {
"baseClasses": ["str", "Text", "object"],
"baseClasses": [
"str",
"Text",
"object"
],
"dataType": "OpenAIModel",
"id": "OpenAIModel-gi29P"
}
@ -911,11 +998,18 @@
"targetHandle": {
"fieldName": "reference_1",
"id": "Prompt-Rse03",
"inputTypes": ["Document", "BaseOutputParser", "Record", "Text"],
"inputTypes": [
"Document",
"BaseOutputParser",
"Record",
"Text"
],
"type": "str"
},
"sourceHandle": {
"baseClasses": ["Record"],
"baseClasses": [
"Record"
],
"dataType": "URL",
"id": "URL-2cX90"
}
@ -935,11 +1029,20 @@
"targetHandle": {
"fieldName": "instructions",
"id": "Prompt-Rse03",
"inputTypes": ["Document", "BaseOutputParser", "Record", "Text"],
"inputTypes": [
"Document",
"BaseOutputParser",
"Record",
"Text"
],
"type": "str"
},
"sourceHandle": {
"baseClasses": ["object", "Text", "str"],
"baseClasses": [
"object",
"Text",
"str"
],
"dataType": "TextInput",
"id": "TextInput-og8Or"
}
@ -959,11 +1062,17 @@
"targetHandle": {
"fieldName": "input_value",
"id": "OpenAIModel-gi29P",
"inputTypes": ["Text"],
"inputTypes": [
"Text"
],
"type": "str"
},
"sourceHandle": {
"baseClasses": ["object", "Text", "str"],
"baseClasses": [
"object",
"Text",
"str"
],
"dataType": "Prompt",
"id": "Prompt-Rse03"
}
@ -986,4 +1095,4 @@
"name": "Blog Writer",
"last_tested_version": "1.0.0a0",
"is_component": false
}
}

View file

@ -256,7 +256,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Interaction Panel.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template,\n )\n",
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Interaction Panel.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build_with_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template or \"\",\n )\n",
"fileTypes": [],
"file_path": "",
"password": false,
@ -452,7 +452,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Interaction Panel.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template,\n )\n",
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Interaction Panel.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build_with_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template or \"\",\n )\n",
"fileTypes": [],
"file_path": "",
"password": false,
@ -647,7 +647,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional\n\nfrom langflow.base.io.text import TextComponent\nfrom langflow.field_typing import Text\n\n\nclass TextInput(TextComponent):\n display_name = \"Text Input\"\n description = \"Get text inputs from the Interaction Panel.\"\n icon = \"type\"\n\n def build_config(self):\n return {\n \"input_value\": {\n \"display_name\": \"Value\",\n \"input_types\": [\"Record\", \"Text\"],\n \"info\": \"Text or Record to be passed as input.\",\n },\n \"record_template\": {\n \"display_name\": \"Record Template\",\n \"multiline\": True,\n \"info\": \"Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Optional[str] = \"\",\n record_template: Optional[str] = \"\",\n ) -> Text:\n return super().build(input_value=input_value, record_template=record_template)\n",
"value": "from typing import Optional\n\nfrom langflow.base.io.text import TextComponent\nfrom langflow.field_typing import Text\n\n\nclass TextInput(TextComponent):\n display_name = \"Text Input\"\n description = \"Get text inputs from the Interaction Panel.\"\n icon = \"type\"\n\n def build_config(self):\n return {\n \"input_value\": {\n \"display_name\": \"Value\",\n \"input_types\": [\"Record\", \"Text\"],\n \"info\": \"Text or Record to be passed as input.\",\n },\n \"record_template\": {\n \"display_name\": \"Record Template\",\n \"multiline\": True,\n \"info\": \"Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Optional[Text] = \"\",\n record_template: Optional[str] = \"\",\n ) -> Text:\n return super().build(input_value=input_value, record_template=record_template)\n",
"fileTypes": [],
"file_path": "",
"password": false,
@ -884,7 +884,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-3.5-turbo\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n \"value\": \"gpt-4-turbo-preview\",\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float,\n model_name: str,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=openai_api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-3.5-turbo\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n \"value\": \"gpt-4-turbo-preview\",\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float,\n model_name: str,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
"fileTypes": [],
"file_path": "",
"password": false,
@ -1270,7 +1270,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-3.5-turbo\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n \"value\": \"gpt-4-turbo-preview\",\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float,\n model_name: str,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=openai_api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-3.5-turbo\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n \"value\": \"gpt-4-turbo-preview\",\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float,\n model_name: str,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
"fileTypes": [],
"file_path": "",
"password": false,

File diff suppressed because one or more lines are too long

View file

@ -1,14 +1,14 @@
from typing import ClassVar, Dict, List, Optional
from langchain.agents import types
from loguru import logger
from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.utils import build_template_from_class
from langflow.legacy_custom.customs import get_custom_nodes
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.agents import AgentFrontendNode
from langflow.utils.util import build_template_from_class, build_template_from_method
from langflow.utils.util import build_template_from_method
from loguru import logger
class AgentCreator(LangChainTypeCreator):

View file

@ -2,14 +2,14 @@ from typing import Any, ClassVar, Dict, List, Optional, Type
from langchain import chains
from langchain_experimental.sql import SQLDatabaseChain
from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.interface.utils import build_template_from_class
from langflow.legacy_custom.customs import get_custom_nodes
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.chains import ChainFrontendNode
from langflow.utils.util import build_template_from_class, build_template_from_method
from langflow.utils.util import build_template_from_method
from loguru import logger
# Assuming necessary imports for Field, Template, and FrontendNode classes

View file

@ -4,9 +4,9 @@ from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
from langflow.interface.utils import build_template_from_class
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode
from langflow.utils.util import build_template_from_class
class DocumentLoaderCreator(LangChainTypeCreator):

View file

@ -4,10 +4,10 @@ from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import embedding_type_to_cls_dict
from langflow.interface.utils import build_template_from_class
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
from langflow.utils.util import build_template_from_class
class EmbeddingCreator(LangChainTypeCreator):

View file

@ -4,9 +4,9 @@ from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import llm_type_to_cls_dict
from langflow.interface.utils import build_template_from_class
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.llms import LLMFrontendNode
from langflow.utils.util import build_template_from_class
class LLMCreator(LangChainTypeCreator):

View file

@ -1,14 +1,14 @@
from typing import ClassVar, Dict, List, Optional, Type
from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import memory_type_to_cls_dict
from langflow.interface.utils import build_template_from_class
from langflow.legacy_custom.customs import get_custom_nodes
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.memories import MemoryFrontendNode
from langflow.utils.util import build_template_from_class, build_template_from_method
from langflow.utils.util import build_template_from_method
from loguru import logger
class MemoryCreator(LangChainTypeCreator):

View file

@ -1,13 +1,13 @@
from typing import ClassVar, Dict, List, Optional, Type
from langchain import output_parsers
from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.interface.utils import build_template_from_class
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
from langflow.utils.util import build_template_from_class, build_template_from_method
from langflow.utils.util import build_template_from_method
from loguru import logger
class OutputParserCreator(LangChainTypeCreator):

View file

@ -2,13 +2,12 @@ from typing import Dict, List, Optional, Type
from langchain import prompts
from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.interface.utils import build_template_from_class
from langflow.legacy_custom.customs import get_custom_nodes
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.prompts import PromptFrontendNode
from langflow.utils.util import build_template_from_class
class PromptCreator(LangChainTypeCreator):

View file

@ -1,13 +1,13 @@
from typing import Any, ClassVar, Dict, List, Optional, Type
from langchain_community import retrievers
from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.interface.utils import build_template_from_class
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
from langflow.utils.util import build_template_from_class, build_template_from_method
from langflow.utils.util import build_template_from_method
from loguru import logger
class RetrieverCreator(LangChainTypeCreator):

View file

@ -5,17 +5,6 @@ from loguru import logger
from langflow.graph import Graph
async def build_sorted_vertices(data_graph, flow_id: str) -> Tuple[Graph, Dict]:
"""
Build langchain object from data_graph.
"""
logger.debug("Building langchain object")
graph = Graph.from_payload(data_graph, flow_id=flow_id)
return graph, {}
def get_memory_key(langchain_object):
"""
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.

View file

@ -4,9 +4,9 @@ from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
from langflow.interface.utils import build_template_from_class
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode
from langflow.utils.util import build_template_from_class
class TextSplitterCreator(LangChainTypeCreator):

View file

@ -3,11 +3,10 @@ from typing import Callable, Dict, List, Optional
from langchain.agents import agent_toolkits
from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class, import_module
from langflow.interface.utils import build_template_from_class
from langflow.services.deps import get_settings_service
from langflow.utils.util import build_template_from_class
class ToolkitCreator(LangChainTypeCreator):

View file

@ -11,7 +11,7 @@ from langflow.template.field.base import TemplateField
from langflow.template.template.base import Template
from langflow.utils import util
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
from langflow.interface.utils import build_template_from_class
TOOL_INPUTS = {
"str": TemplateField(

View file

@ -2,13 +2,12 @@ from typing import Dict, List, Optional, Type
from langchain_community import utilities
from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.interface.utils import build_template_from_class
from langflow.legacy_custom.customs import get_custom_nodes
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.utilities import UtilitiesFrontendNode
from langflow.utils.util import build_template_from_class
class UtilityCreator(LangChainTypeCreator):

View file

@ -3,14 +3,16 @@ import json
import os
import re
from io import BytesIO
from typing import Dict
import yaml
from docstring_parser import parse
from langchain.base_language import BaseLanguageModel
from loguru import logger
from PIL.Image import Image
from langflow.services.chat.config import ChatConfig
from langflow.services.deps import get_settings_service
from langflow.utils.util import format_dict, get_base_classes, get_default_factory
from loguru import logger
from PIL.Image import Image
def load_file_into_dict(file_path: str) -> dict:
@ -100,7 +102,6 @@ def setup_llm_caching():
def set_langchain_cache(settings):
from langchain.globals import set_llm_cache
from langflow.interface.importing.utils import import_class
if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"):
@ -114,3 +115,51 @@ def set_langchain_cache(settings):
logger.warning(f"Could not import {cache_type}. ")
else:
logger.info("No LLM cache set.")
def build_template_from_class(name: str, type_to_cls_dict: Dict, add_function: bool = False):
classes = [item.__name__ for item in type_to_cls_dict.values()]
# Raise error if name is not in chains
if name not in classes:
raise ValueError(f"{name} not found.")
for _type, v in type_to_cls_dict.items():
if v.__name__ == name:
_class = v
# Get the docstring
docs = parse(_class.__doc__)
variables = {"_type": _type}
if "__fields__" in _class.__dict__:
for class_field_items, value in _class.__fields__.items():
if class_field_items in ["callback_manager"]:
continue
variables[class_field_items] = {}
for name_, value_ in value.__repr_args__():
if name_ == "default_factory":
try:
variables[class_field_items]["default"] = get_default_factory(
module=_class.__base__.__module__,
function=value_,
)
except Exception:
variables[class_field_items]["default"] = None
elif name_ not in ["name"]:
variables[class_field_items][name_] = value_
variables[class_field_items]["placeholder"] = (
docs.params[class_field_items] if class_field_items in docs.params else ""
)
base_classes = get_base_classes(_class)
# Adding function to base classes to allow
# the output to be a function
if add_function:
base_classes.append("Callable")
return {
"template": format_dict(variables, name),
"description": docs.short_description or "",
"base_classes": base_classes,
}

View file

@ -1,10 +1,9 @@
from typing import Dict, List, Optional
from langchain_community.utilities import requests
from loguru import logger
from langflow.interface.base import LangChainTypeCreator
from langflow.utils.util import build_template_from_class
from langflow.interface.utils import build_template_from_class
from loguru import logger
class WrapperCreator(LangChainTypeCreator):

View file

@ -10,6 +10,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from loguru import logger
from rich import print as rprint
from langflow.api import router
from langflow.initial_setup.setup import create_or_update_starter_projects
@ -28,6 +29,8 @@ def get_lifespan(fix_migration=False, socketio_server=None):
LangfuseInstance.update()
create_or_update_starter_projects()
yield
# Shutdown message
rprint("[bold red]Shutting down Langflow...[/bold red]")
teardown_services()
return lifespan

View file

@ -2,21 +2,55 @@ import json
from pathlib import Path
from typing import List, Optional, Union
from dotenv import load_dotenv
from langflow.graph import Graph
from langflow.graph.schema import RunOutputs
from langflow.processing.process import process_tweaks, run_graph
from langflow.utils.logger import configure
from langflow.utils.util import update_settings
def load_flow_from_json(flow: Union[Path, str, dict], tweaks: Optional[dict] = None) -> Graph:
def load_flow_from_json(
flow: Union[Path, str, dict],
tweaks: Optional[dict] = None,
log_level: Optional[str] = None,
log_file: Optional[str] = None,
env_file: Optional[str] = None,
cache: Optional[str] = None,
disable_logs: Optional[bool] = True,
) -> Graph:
"""
Load flow from a JSON file or a JSON object.
Load a flow graph from a JSON file or a JSON object.
Args:
flow (Union[Path, str, dict]): The flow to load. It can be a file path (str or Path object)
or a JSON object (dict).
tweaks (Optional[dict]): Optional tweaks to apply to the loaded flow graph.
log_level (Optional[str]): Optional log level to configure for the flow processing.
log_file (Optional[str]): Optional log file to configure for the flow processing.
env_file (Optional[str]): Optional .env file to override environment variables.
cache (Optional[str]): Optional cache path to update the flow settings.
disable_logs (Optional[bool], default=True): Optional flag to disable logs during flow processing.
If log_level or log_file are set, disable_logs is not used.
Returns:
Graph: The loaded flow graph as a Graph object.
Raises:
TypeError: If the input is neither a file path (str or Path object) nor a JSON object (dict).
:param flow: JSON file path or JSON object
:param tweaks: Optional tweaks to be processed
:param build: If True, build the graph, otherwise return the graph object
:return: Langchain object or Graph object depending on the build parameter
"""
# If input is a file path, load JSON from the file
log_file_path = Path(log_file) if log_file else None
configure(log_level=log_level, log_file=log_file_path, disable=disable_logs)
# override env variables with .env file
if env_file:
load_dotenv(env_file, override=True)
# Update settings with cache and components path
update_settings(cache=cache)
if isinstance(flow, (str, Path)):
with open(flow, "r", encoding="utf-8") as f:
flow_graph = json.load(f)
@ -41,22 +75,44 @@ def run_flow_from_json(
input_type: str = "chat",
output_type: str = "chat",
output_component: Optional[str] = None,
log_level: Optional[str] = None,
log_file: Optional[str] = None,
env_file: Optional[str] = None,
cache: Optional[str] = None,
disable_logs: Optional[bool] = True,
) -> List[RunOutputs]:
"""
Runs a JSON flow by loading it from a file or dictionary and executing it with the given input value.
Run a flow from a JSON file or dictionary.
Args:
flow (Union[Path, str, dict]): The path to the JSON file, or the JSON dictionary representing the flow.
flow (Union[Path, str, dict]): The path to the JSON file or the JSON dictionary representing the flow.
input_value (str): The input value to be processed by the flow.
tweaks (Optional[dict], optional): Optional tweaks to be applied to the flow. Defaults to None.
input_type (str, optional): The type of the input value. Defaults to "chat".
output_type (str, optional): The type of the output value. Defaults to "chat".
output_component (Optional[str], optional): The specific output component to retrieve. Defaults to None.
output_component (Optional[str], optional): The specific component to output. Defaults to None.
log_level (Optional[str], optional): The log level to use. Defaults to None.
log_file (Optional[str], optional): The log file to write logs to. Defaults to None.
env_file (Optional[str], optional): The environment file to load. Defaults to None.
cache (Optional[str], optional): The cache directory to use. Defaults to None.
disable_logs (Optional[bool], optional): Whether to disable logs. Defaults to True.
Returns:
None: The result of running the flow.
List[RunOutputs]: A list of RunOutputs objects representing the results of running the flow.
"""
graph = load_flow_from_json(flow, tweaks)
# Set all streaming to false
if tweaks is None:
tweaks = {}
tweaks["stream"] = False
graph = load_flow_from_json(
flow=flow,
tweaks=tweaks,
log_level=log_level,
log_file=log_file,
env_file=env_file,
cache=cache,
disable_logs=disable_logs,
)
result = run_graph(
graph=graph,
input_value=input_value,

View file

@ -9,11 +9,12 @@ from langflow.graph.graph.base import Graph
from langflow.graph.schema import RunOutputs
from langflow.graph.vertex.base import Vertex
from langflow.interface.run import get_memory_key, update_memory_keys
from langflow.schema.graph import InputValue, Tweaks
from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.session.service import SessionService
if TYPE_CHECKING:
from langflow.api.v1.schemas import InputValueRequest, Tweaks
from langflow.api.v1.schemas import InputValueRequest
def fix_memory_inputs(langchain_object):
@ -188,7 +189,7 @@ def run_graph(
List[RunOutputs]: A list of RunOutputs objects representing the outputs of the graph.
"""
inputs = [InputValueRequest(components=[], input_value=input_value, type=input_type)]
inputs = [InputValue(components=[], input_value=input_value, type=input_type)]
if output_component:
outputs = [output_component]
else:
@ -245,7 +246,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
logger.warning(f"Node {node.get('id')} does not have a tweak named {tweak_name}")
continue
if tweak_name in template_data:
key = tweak_name if tweak_name == "file_path" else "value"
key = "file_path" if template_data[tweak_name]["type"] == "file" else "value"
template_data[tweak_name][key] = tweak_value

View file

@ -0,0 +1,43 @@
from typing import List, Optional, Union
from langflow.schema.schema import InputType
from pydantic import BaseModel, Field, RootModel
class InputValue(BaseModel):
components: Optional[List[str]] = []
input_value: Optional[str] = None
type: Optional[InputType] = Field(
"any",
description="Defines on which components the input value should be applied. 'any' applies to all input components.",
)
class Tweaks(RootModel):
root: dict[str, Union[str, dict[str, str]]] = Field(
description="A dictionary of tweaks to adjust the flow's execution. Allows customizing flow behavior dynamically. All tweaks are overridden by the input values.",
)
model_config = {
"json_schema_extra": {
"examples": [
{
"parameter_name": "value",
"Component Name": {"parameter_name": "value"},
"component_id": {"parameter_name": "value"},
}
]
}
}
# This should behave like a dict
def __getitem__(self, key):
return self.root[key]
def __setitem__(self, key, value):
self.root[key] = value
def __delitem__(self, key):
del self.root[key]
def items(self):
return self.root.items()

View file

@ -1,6 +1,8 @@
import asyncio
import logging
import signal
from gunicorn import glogging
from gunicorn import glogging # type: ignore
from gunicorn.app.base import BaseApplication # type: ignore
from uvicorn.workers import UvicornWorker
@ -10,6 +12,21 @@ from langflow.utils.logger import InterceptHandler # type: ignore
class LangflowUvicornWorker(UvicornWorker):
CONFIG_KWARGS = {"loop": "asyncio"}
def _install_sigint_handler(self) -> None:
"""Install a SIGQUIT handler on workers.
- https://github.com/encode/uvicorn/issues/1116
- https://github.com/benoitc/gunicorn/issues/2604
"""
loop = asyncio.get_running_loop()
loop.add_signal_handler(signal.SIGINT, self.handle_exit, signal.SIGINT, None)
async def _serve(self) -> None:
# We do this to not log the "Worker (pid:XXXXX) was sent SIGINT"
self._install_sigint_handler()
await super()._serve()
class Logger(glogging.Logger):
"""Implements and overrides the gunicorn logging interface.
@ -35,11 +52,7 @@ class LangflowApplication(BaseApplication):
super().__init__()
def load_config(self):
config = {
key: value
for key, value in self.options.items()
if key in self.cfg.settings and value is not None
}
config = {key: value for key, value in self.options.items() if key in self.cfg.settings and value is not None}
for key, value in config.items():
self.cfg.set(key.lower(), value)

View file

@ -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
@ -207,7 +211,7 @@ def create_super_user(
return super_user
def create_user_longterm_token(db: Session = Depends(get_session)) -> dict:
def create_user_longterm_token(db: Session = Depends(get_session)) -> tuple[UUID, dict]:
settings_service = get_settings_service()
username = settings_service.auth_settings.SUPERUSER
password = settings_service.auth_settings.SUPERUSER_PASSWORD
@ -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",

View file

@ -1,9 +1,9 @@
from datetime import datetime
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Optional
from uuid import UUID, uuid4
from pydantic import validator
from sqlmodel import Field, Relationship, SQLModel
from pydantic import field_validator, validator
from sqlmodel import Field, Relationship, SQLModel, Column, func, DateTime
if TYPE_CHECKING:
from langflow.services.database.models.user import User
@ -11,7 +11,9 @@ if TYPE_CHECKING:
class ApiKeyBase(SQLModel):
name: Optional[str] = Field(index=True, nullable=True, default=None)
created_at: datetime = Field(default_factory=datetime.utcnow)
created_at: datetime = Field(
default=None, sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
)
last_used_at: Optional[datetime] = Field(default=None, nullable=True)
total_uses: int = Field(default=0)
is_active: bool = Field(default=True)
@ -33,6 +35,10 @@ class ApiKeyCreate(ApiKeyBase):
api_key: Optional[str] = None
user_id: Optional[UUID] = None
@field_validator("created_at", mode="before")
def set_created_at(cls, v):
return v or datetime.now(timezone.utc)
class UnmaskedApiKeyRead(ApiKeyBase):
id: UUID

View file

@ -1,14 +1,15 @@
# Path: src/backend/langflow/services/database/models/flow/model.py
import warnings
from datetime import datetime
from typing import TYPE_CHECKING, Dict, Optional
from uuid import UUID, uuid4
import emoji
from emoji import purely_emoji # type: ignore
from pydantic import field_serializer, field_validator
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
from langflow.interface.custom.attributes import validate_icon
from langflow.schema.schema import Record
if TYPE_CHECKING:
@ -45,12 +46,25 @@ class FlowBase(SQLModel):
# emoji pattern in Python
if v is None:
return v
# we are going to use the emoji library to validate the emoji
# emojis can be defined using the :emoji_name: syntax
emoji = validate_icon(v)
if not v.startswith(":") and not v.endswith(":"):
return v
elif not v.startswith(":") or not v.endswith(":"):
# emoji should have both starting and ending colons
# so if one of them is missing, we will raise
raise ValueError(f"Invalid emoji. {v} is not a valid emoji.")
if purely_emoji(emoji):
emoji_value = emoji.emojize(v, variant="emoji_type")
if v == emoji_value:
warnings.warn(f"Invalid emoji. {v} is not a valid emoji.")
icon = v
icon = emoji_value
if purely_emoji(icon):
# this is indeed an emoji
return emoji
return icon
# otherwise it should be a valid lucide icon
if v is not None and not isinstance(v, str):
raise ValueError("Icon must be a string")

View file

@ -2,8 +2,7 @@ from datetime import datetime, timezone
from typing import TYPE_CHECKING, Optional
from uuid import UUID, uuid4
from sqlmodel import Field, Relationship, SQLModel
from sqlmodel import Column, DateTime, Field, Relationship, SQLModel, func
if TYPE_CHECKING:
from langflow.services.database.models.user.model import User
@ -26,15 +25,25 @@ class Variable(VariableBase, table=True):
description="Unique ID for the variable",
)
# name is unique per user
created_at: datetime = Field(default_factory=utc_now, description="Creation time of the variable")
updated_at: Optional[datetime] = Field(None, description="Last update time of the variable")
created_at: datetime = Field(
default=None,
sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=True),
description="Creation time of the variable",
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(DateTime(timezone=True), nullable=True),
description="Last update time of the variable",
)
# foreign key to user table
user_id: UUID = Field(description="User ID associated with this variable", foreign_key="user.id")
user: "User" = Relationship(back_populates="variables")
class VariableCreate(VariableBase):
type: Optional[str] = Field(None, description="Type of the variable")
created_at: Optional[datetime] = Field(default_factory=utc_now, description="Creation time of the variable")
updated_at: Optional[datetime] = Field(default_factory=utc_now, description="Creation time of the variable")
class VariableRead(SQLModel):

View file

@ -6,16 +6,17 @@ from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import command, util
from alembic.config import Config
from loguru import logger
from sqlalchemy import inspect
from sqlalchemy.exc import OperationalError
from sqlmodel import Session, SQLModel, create_engine, select, text
from langflow.services.base import Service
from langflow.services.database import models # noqa
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults
from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
from loguru import logger
from sqlalchemy import inspect
from sqlalchemy.exc import OperationalError
from sqlmodel import Session, SQLModel, create_engine, select, text
if TYPE_CHECKING:
from sqlalchemy.engine import Engine
@ -36,10 +37,7 @@ class DatabaseService(Service):
def _create_engine(self) -> "Engine":
"""Create the engine for the database."""
settings_service = get_settings_service()
if (
settings_service.settings.DATABASE_URL
and settings_service.settings.DATABASE_URL.startswith("sqlite")
):
if settings_service.settings.DATABASE_URL and settings_service.settings.DATABASE_URL.startswith("sqlite"):
connect_args = {"check_same_thread": False}
else:
connect_args = {}
@ -51,9 +49,7 @@ class DatabaseService(Service):
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None: # If an exception has been raised
logger.error(
f"Session rollback because of exception: {exc_type.__name__} {exc_value}"
)
logger.error(f"Session rollback because of exception: {exc_type.__name__} {exc_value}")
self._session.rollback()
else:
self._session.commit()
@ -70,9 +66,7 @@ class DatabaseService(Service):
settings_service = get_settings_service()
if settings_service.auth_settings.AUTO_LOGIN:
with Session(self.engine) as session:
flows = session.exec(
select(models.Flow).where(models.Flow.user_id is None)
).all()
flows = session.exec(select(models.Flow).where(models.Flow.user_id is None)).all()
if flows:
logger.debug("Migrating flows to default superuser")
username = settings_service.auth_settings.SUPERUSER
@ -102,9 +96,7 @@ class DatabaseService(Service):
expected_columns = list(model.model_fields.keys())
try:
available_columns = [
col["name"] for col in inspector.get_columns(table)
]
available_columns = [col["name"] for col in inspector.get_columns(table)]
except sa.exc.NoSuchTableError:
logger.debug(f"Missing table: {table}")
return False
@ -168,9 +160,7 @@ class DatabaseService(Service):
buffer.write(f"{datetime.now().isoformat()}: Checking migrations\n")
command.check(alembic_cfg)
except Exception as exc:
if isinstance(
exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)
):
if isinstance(exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)):
command.upgrade(alembic_cfg, "head")
time.sleep(3)
@ -206,11 +196,11 @@ class DatabaseService(Service):
# This method is used for testing purposes only
# We will check that all models are in the database
# and that the database is up to date with all columns
sql_models = [models.Flow, models.User, models.ApiKey]
return [
TableResults(sql_model.__tablename__, self.check_table(sql_model))
for sql_model in sql_models
# get all models that are subclasses of SQLModel
sql_models = [
model for model in models.__dict__.values() if isinstance(model, type) and issubclass(model, SQLModel)
]
return [TableResults(sql_model.__tablename__, self.check_table(sql_model)) for sql_model in sql_models]
def check_table(self, model):
results = []
@ -219,9 +209,7 @@ class DatabaseService(Service):
expected_columns = list(model.__fields__.keys())
available_columns = []
try:
available_columns = [
col["name"] for col in inspector.get_columns(table_name)
]
available_columns = [col["name"] for col in inspector.get_columns(table_name)]
results.append(Result(name=table_name, type="table", success=True))
except sa.exc.NoSuchTableError:
logger.error(f"Missing table: {table_name}")
@ -252,9 +240,7 @@ class DatabaseService(Service):
try:
table.create(self.engine, checkfirst=True)
except OperationalError as oe:
logger.warning(
f"Table {table} already exists, skipping. Exception: {oe}"
)
logger.warning(f"Table {table} already exists, skipping. Exception: {oe}")
except Exception as exc:
logger.error(f"Error creating table {table}: {exc}")
raise RuntimeError(f"Error creating table {table}") from exc
@ -266,9 +252,7 @@ class DatabaseService(Service):
if table not in table_names:
logger.error("Something went wrong creating the database and tables.")
logger.error("Please check your database settings.")
raise RuntimeError(
"Something went wrong creating the database and tables."
)
raise RuntimeError("Something went wrong creating the database and tables.")
logger.debug("Database and tables created successfully")

View file

@ -1,7 +1,7 @@
from contextlib import contextmanager
from typing import TYPE_CHECKING, Generator
from langflow.services import ServiceType, service_manager
from langflow.services.schema import ServiceType
if TYPE_CHECKING:
from sqlmodel import Session
@ -21,7 +21,7 @@ if TYPE_CHECKING:
from langflow.services.variable.service import VariableService
def get_service(service_type: ServiceType):
def get_service(service_type: ServiceType, default=None):
"""
Retrieves the service instance for the given service type.
@ -32,7 +32,13 @@ def get_service(service_type: ServiceType):
Any: The service instance.
"""
return service_manager.get(service_type) # type: ignore
from langflow.services.manager import service_manager
if not service_manager.factories:
#! This is a workaround to ensure that the service manager is initialized
#! Not optimal, but it works for now
service_manager.register_factories()
return service_manager.get(service_type, default) # type: ignore
def get_state_service() -> "StateService":
@ -42,7 +48,9 @@ def get_state_service() -> "StateService":
Returns:
The StateService instance.
"""
return service_manager.get(ServiceType.STATE_SERVICE) # type: ignore
from langflow.services.state.factory import StateServiceFactory
return get_service(ServiceType.STATE_SERVICE, StateServiceFactory()) # type: ignore
def get_socket_service() -> "SocketIOService":
@ -52,7 +60,7 @@ def get_socket_service() -> "SocketIOService":
Returns:
SocketIOService: The SocketIOService instance.
"""
return service_manager.get(ServiceType.SOCKETIO_SERVICE) # type: ignore
return get_service(ServiceType.SOCKETIO_SERVICE) # type: ignore
def get_storage_service() -> "StorageService":
@ -62,7 +70,9 @@ def get_storage_service() -> "StorageService":
Returns:
The storage service instance.
"""
return service_manager.get(ServiceType.STORAGE_SERVICE) # type: ignore
from langflow.services.storage.factory import StorageServiceFactory
return get_service(ServiceType.STORAGE_SERVICE, default=StorageServiceFactory()) # type: ignore
def get_variable_service() -> "VariableService":
@ -73,7 +83,9 @@ def get_variable_service() -> "VariableService":
The VariableService instance.
"""
return service_manager.get(ServiceType.VARIABLE_SERVICE) # type: ignore
from langflow.services.variable.factory import VariableServiceFactory
return get_service(ServiceType.VARIABLE_SERVICE, VariableServiceFactory()) # type: ignore
def get_plugins_service() -> "PluginService":
@ -83,7 +95,7 @@ def get_plugins_service() -> "PluginService":
Returns:
PluginService: The PluginService instance.
"""
return service_manager.get(ServiceType.PLUGIN_SERVICE) # type: ignore
return get_service(ServiceType.PLUGIN_SERVICE) # type: ignore
def get_settings_service() -> "SettingsService":
@ -98,14 +110,9 @@ def get_settings_service() -> "SettingsService":
Raises:
ValueError: If the service cannot be retrieved or initialized.
"""
try:
return service_manager.get(ServiceType.SETTINGS_SERVICE) # type: ignore
except ValueError:
# initialize settings service
from langflow.services.manager import initialize_settings_service
from langflow.services.settings.factory import SettingsServiceFactory
initialize_settings_service()
return service_manager.get(ServiceType.SETTINGS_SERVICE) # type: ignore
return get_service(ServiceType.SETTINGS_SERVICE, SettingsServiceFactory()) # type: ignore
def get_db_service() -> "DatabaseService":
@ -116,7 +123,9 @@ def get_db_service() -> "DatabaseService":
The DatabaseService instance.
"""
return service_manager.get(ServiceType.DATABASE_SERVICE) # type: ignore
from langflow.services.database.factory import DatabaseServiceFactory
return get_service(ServiceType.DATABASE_SERVICE, DatabaseServiceFactory()) # type: ignore
def get_session() -> Generator["Session", None, None]:
@ -165,7 +174,9 @@ def get_cache_service() -> "CacheService":
Returns:
The cache service instance.
"""
return service_manager.get(ServiceType.CACHE_SERVICE) # type: ignore
from langflow.services.cache.factory import CacheServiceFactory
return get_service(ServiceType.CACHE_SERVICE, CacheServiceFactory()) # type: ignore
def get_session_service() -> "SessionService":
@ -175,7 +186,9 @@ def get_session_service() -> "SessionService":
Returns:
The session service instance.
"""
return service_manager.get(ServiceType.SESSION_SERVICE) # type: ignore
from langflow.services.session.factory import SessionServiceFactory
return get_service(ServiceType.SESSION_SERVICE, SessionServiceFactory()) # type: ignore
def get_monitor_service() -> "MonitorService":
@ -185,7 +198,9 @@ def get_monitor_service() -> "MonitorService":
Returns:
MonitorService: The MonitorService instance.
"""
return service_manager.get(ServiceType.MONITOR_SERVICE) # type: ignore
from langflow.services.monitor.factory import MonitorServiceFactory
return get_service(ServiceType.MONITOR_SERVICE, MonitorServiceFactory()) # type: ignore
def get_task_service() -> "TaskService":
@ -196,7 +211,9 @@ def get_task_service() -> "TaskService":
The TaskService instance.
"""
return service_manager.get(ServiceType.TASK_SERVICE) # type: ignore
from langflow.services.task.factory import TaskServiceFactory
return get_service(ServiceType.TASK_SERVICE, TaskServiceFactory()) # type: ignore
def get_chat_service() -> "ChatService":
@ -206,7 +223,7 @@ def get_chat_service() -> "ChatService":
Returns:
ChatService: The chat service instance.
"""
return service_manager.get(ServiceType.CHAT_SERVICE) # type: ignore
return get_service(ServiceType.CHAT_SERVICE) # type: ignore
def get_store_service() -> "StoreService":
@ -216,4 +233,4 @@ def get_store_service() -> "StoreService":
Returns:
StoreService: The StoreService instance.
"""
return service_manager.get(ServiceType.STORE_SERVICE) # type: ignore
return get_service(ServiceType.STORE_SERVICE) # type: ignore

View file

@ -1,13 +1,20 @@
from typing import TYPE_CHECKING, Dict
import importlib
import inspect
from typing import TYPE_CHECKING, Dict, Optional
from loguru import logger
if TYPE_CHECKING:
from langflow.services.base import Service
from langflow.services.factory import ServiceFactory
from langflow.services.schema import ServiceType
class NoFactoryRegisteredError(Exception):
pass
class ServiceManager:
"""
Manages the creation of different services.
@ -16,6 +23,15 @@ class ServiceManager:
def __init__(self):
self.services: Dict[str, "Service"] = {}
self.factories = {}
self.register_factories()
def register_factories(self):
for factory in self.get_factories():
try:
self.register_factory(factory)
except Exception as exc:
logger.exception(exc)
logger.error(f"Error initializing {factory}: {exc}")
def register_factory(
self,
@ -28,24 +44,28 @@ class ServiceManager:
service_name = service_factory.service_class.name
self.factories[service_name] = service_factory
def get(self, service_name: "ServiceType") -> "Service":
def get(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None) -> "Service":
"""
Get (or create) a service by its name.
"""
if service_name not in self.services:
self._create_service(service_name)
self._create_service(service_name, default)
return self.services[service_name]
def _create_service(self, service_name: "ServiceType"):
def _create_service(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None):
"""
Create a new service given its name, handling dependencies.
"""
logger.debug(f"Create service {service_name}")
self._validate_service_creation(service_name)
self._validate_service_creation(service_name, default)
# Create dependencies first
factory = self.factories.get(service_name)
if factory is None and default is not None:
self.register_factory(default)
factory = default
for dependency in factory.dependencies:
if dependency not in self.services:
self._create_service(dependency)
@ -57,12 +77,12 @@ class ServiceManager:
self.services[service_name] = self.factories[service_name].create(**dependent_services)
self.services[service_name].set_ready()
def _validate_service_creation(self, service_name: "ServiceType"):
def _validate_service_creation(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None):
"""
Validate whether the service can be created.
"""
if service_name not in self.factories:
raise ValueError(f"No factory registered for the service class '{service_name.name}'")
if service_name not in self.factories and default is None:
raise NoFactoryRegisteredError(f"No factory registered for the service class '{service_name.name}'")
def update(self, service_name: "ServiceType"):
"""
@ -88,6 +108,34 @@ class ServiceManager:
self.services = {}
self.factories = {}
@staticmethod
def get_factories():
from langflow.services.factory import ServiceFactory
from langflow.services.schema import ServiceType
service_names = [ServiceType(service_type).value.replace("_service", "") for service_type in ServiceType]
base_module = "langflow.services"
factories = []
for name in service_names:
try:
module_name = f"{base_module}.{name}.factory"
module = importlib.import_module(module_name)
# Find all classes in the module that are subclasses of ServiceFactory
for name, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, ServiceFactory) and obj is not ServiceFactory:
factories.append(obj())
break
except Exception as exc:
logger.exception(exc)
raise RuntimeError(
f"Could not initialize services. Please check your settings. Error in {name}."
) from exc
return factories
service_manager = ServiceManager()
@ -106,9 +154,7 @@ def initialize_session_service():
Initialize the session manager.
"""
from langflow.services.cache import factory as cache_factory
from langflow.services.session import (
factory as session_service_factory,
) # type: ignore
from langflow.services.session import factory as session_service_factory # type: ignore
initialize_settings_service()

View file

@ -19,5 +19,5 @@ class ServiceType(str, Enum):
VARIABLE_SERVICE = "variable_service"
STORAGE_SERVICE = "storage_service"
MONITOR_SERVICE = "monitor_service"
SOCKETIO_SERVICE = "socket_service"
# SOCKETIO_SERVICE = "socket_service"
STATE_SERVICE = "state_service"

View file

@ -1,6 +1,5 @@
from typing import Coroutine, Optional
from langflow.interface.run import build_sorted_vertices
from langflow.services.base import Service
from langflow.services.cache.base import CacheService
from langflow.services.session.utils import compute_dict_hash, session_id_generator
@ -25,8 +24,10 @@ class SessionService(Service):
if data_graph is None:
return (None, None)
# If not cached, build the graph and cache it
graph, artifacts = await build_sorted_vertices(data_graph, flow_id)
from langflow.graph.graph.base import Graph
graph = Graph.from_payload(data_graph, flow_id=flow_id)
artifacts: dict = {}
await self.cache_service.set(key, (graph, artifacts))
return graph, artifacts

View file

@ -3,18 +3,60 @@ 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
# 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.
"""
if field.annotation is None:
return False
try:
if hasattr(field.annotation, "__args__"):
union_args = field.annotation.__args__
else:
union_args = []
return field.annotation.__origin__ == list or any(
arg.__origin__ == list for arg in union_args if hasattr(arg, "__origin__")
)
except AttributeError:
return False
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 = {}
@ -58,15 +100,20 @@ class Settings(BaseSettings):
STORE: Optional[bool] = True
STORE_URL: Optional[str] = "https://api.langflow.store"
DOWNLOAD_WEBHOOK_URL: Optional[
str
] = "https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
DOWNLOAD_WEBHOOK_URL: Optional[str] = (
"https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
)
LIKE_WEBHOOK_URL: Optional[str] = "https://api.langflow.store/flows/trigger/64275852-ec00-45c1-984e-3bff814732da"
STORAGE_TYPE: str = "local"
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:
@ -104,21 +151,50 @@ class Settings(BaseSettings):
# if there is a database in that location
if not values["CONFIG_DIR"]:
raise ValueError("CONFIG_DIR not set, please set it or provide a DATABASE_URL")
from langflow.version import is_pre_release # type: ignore
new_path = f"{values['CONFIG_DIR']}/langflow.db"
if Path("./langflow.db").exists():
pre_db_file_name = "langflow-pre.db"
db_file_name = "langflow.db"
new_pre_path = f"{values['CONFIG_DIR']}/{pre_db_file_name}"
new_path = f"{values['CONFIG_DIR']}/{db_file_name}"
final_path = None
if is_pre_release:
if Path(new_pre_path).exists():
final_path = new_pre_path
elif Path(new_path).exists():
# We need to copy the current db to the new location
logger.debug("Copying existing database to new location")
copy2(new_path, new_pre_path)
logger.debug(f"Copied existing database to {new_pre_path}")
elif Path(f"./{db_file_name}").exists():
logger.debug("Copying existing database to new location")
copy2(f"./{db_file_name}", new_pre_path)
logger.debug(f"Copied existing database to {new_pre_path}")
else:
logger.debug(f"Database already exists at {new_pre_path}, using it")
final_path = new_pre_path
else:
if Path(new_path).exists():
logger.debug(f"Database already exists at {new_path}, using it")
else:
final_path = new_path
elif Path("./{db_file_name}").exists():
try:
logger.debug("Copying existing database to new location")
copy2("./langflow.db", new_path)
copy2("./{db_file_name}", new_path)
logger.debug(f"Copied existing database to {new_path}")
except Exception:
logger.error("Failed to copy database, using default path")
new_path = "./langflow.db"
new_path = "./{db_file_name}"
else:
final_path = new_path
value = f"sqlite:///{new_path}"
if final_path is None:
if is_pre_release:
final_path = new_pre_path
else:
final_path = new_path
value = f"sqlite:///{final_path}"
return value
@ -149,14 +225,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 +277,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:

View file

@ -1,2 +1,21 @@
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",
"SEARCHAPI_API_KEY",
"SERPAPI_API_KEY",
"VECTARA_CUSTOMER_ID",
"VECTARA_CORPUS_ID",
"VECTARA_API_KEY",
]

View file

@ -1,41 +1,13 @@
import importlib
import inspect
from loguru import logger
from sqlmodel import Session, select
from langflow.services.auth.utils import create_super_user, verify_password
from langflow.services.cache.factory import CacheServiceFactory
from langflow.services.database.utils import initialize_database
from langflow.services.factory import ServiceFactory
from langflow.services.manager import service_manager
from langflow.services.schema import ServiceType
from langflow.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
from langflow.services.socket.utils import set_socketio_server
from .deps import get_db_service, get_session, get_settings_service
def get_factories():
service_names = [ServiceType(service_type).value.replace("_service", "") for service_type in ServiceType]
base_module = "langflow.services"
factories = []
for name in service_names:
try:
module_name = f"{base_module}.{name}.factory"
module = importlib.import_module(module_name)
# Find all classes in the module that are subclasses of ServiceFactory
for name, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, ServiceFactory) and obj is not ServiceFactory:
factories.append(obj())
break
except Exception as exc:
logger.exception(exc)
raise RuntimeError(f"Could not initialize services. Please check your settings. Error in {name}.") from exc
return factories
from .deps import get_db_service, get_service, get_session, get_settings_service
def get_or_create_super_user(session: Session, username, password, is_default):
@ -145,6 +117,8 @@ def teardown_services():
except Exception as exc:
logger.exception(exc)
try:
from langflow.services.manager import service_manager
service_manager.teardown()
except Exception as exc:
logger.exception(exc)
@ -156,7 +130,7 @@ def initialize_settings_service():
"""
from langflow.services.settings import factory as settings_factory
service_manager.register_factory(settings_factory.SettingsServiceFactory())
get_service(ServiceType.SETTINGS_SERVICE, settings_factory.SettingsServiceFactory())
def initialize_session_service():
@ -168,11 +142,13 @@ def initialize_session_service():
initialize_settings_service()
service_manager.register_factory(
get_service(
ServiceType.CACHE_SERVICE,
cache_factory.CacheServiceFactory(),
)
service_manager.register_factory(
get_service(
ServiceType.SESSION_SERVICE,
session_service_factory.SessionServiceFactory(),
)
@ -181,27 +157,17 @@ def initialize_services(fix_migration: bool = False, socketio_server=None):
"""
Initialize all the services needed.
"""
for factory in get_factories():
try:
service_manager.register_factory(factory)
except Exception as exc:
logger.exception(exc)
logger.error(f"Error initializing {factory}: {exc}")
# Test cache connection
service_manager.get(ServiceType.CACHE_SERVICE)
get_service(ServiceType.CACHE_SERVICE, default=CacheServiceFactory())
# Setup the superuser
try:
initialize_database(fix_migration=fix_migration)
except Exception as exc:
logger.error(exc)
raise exc
setup_superuser(service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session()))
setup_superuser(get_service(ServiceType.SETTINGS_SERVICE), next(get_session()))
try:
get_db_service().migrate_flows_if_auto_login()
except Exception as exc:
logger.error(f"Error migrating flows: {exc}")
raise RuntimeError("Error migrating flows") from exc
# Initialize the SocketIO service
set_socketio_server(socketio_server)

View file

@ -1,12 +1,14 @@
import os
from typing import TYPE_CHECKING, Optional, Union
from uuid import UUID
from fastapi import Depends
from loguru import logger
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.database.models.variable.model import Variable, VariableCreate
from langflow.services.deps import get_session
if TYPE_CHECKING:
@ -19,7 +21,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 +57,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 +73,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,11 +87,19 @@ 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)
variable_base = VariableCreate(
name=name,
type=_type,
value=auth_utils.encrypt_api_key(value, settings_service=self.settings_service),
)
variable = Variable.model_validate(variable_base, from_attributes=True, update={"user_id": user_id})
session.add(variable)
session.commit()
session.refresh(variable)

View file

@ -63,8 +63,8 @@ class LLMFrontendNode(FrontendNode):
field.info = OPENAI_API_BASE_INFO
def add_extra_base_classes(self) -> None:
if "BaseLLM" not in self.base_classes:
self.base_classes.append("BaseLLM")
if "BaseLanguageModel" not in self.base_classes:
self.base_classes.append("BaseLanguageModel")
@staticmethod
def format_azure_field(field: TemplateField):

View file

@ -25,11 +25,10 @@ def patching(record):
record["extra"]["serialized"] = serialize(record)
def configure(log_level: Optional[str] = None, log_file: Optional[Path] = None):
if (
os.getenv("LANGFLOW_LOG_LEVEL", "").upper() in VALID_LOG_LEVELS
and log_level is None
):
def configure(log_level: Optional[str] = None, log_file: Optional[Path] = None, disable: Optional[bool] = False):
if disable and log_level is None and log_file is None:
logger.disable("langflow")
if os.getenv("LANGFLOW_LOG_LEVEL", "").upper() in VALID_LOG_LEVELS and log_level is None:
log_level = os.getenv("LANGFLOW_LOG_LEVEL")
if log_level is None:
log_level = "ERROR"
@ -77,11 +76,7 @@ def configure(log_level: Optional[str] = None, log_file: Optional[Path] = None):
def setup_uvicorn_logger():
loggers = (
logging.getLogger(name)
for name in logging.root.manager.loggerDict
if name.startswith("uvicorn.")
)
loggers = (logging.getLogger(name) for name in logging.root.manager.loggerDict if name.startswith("uvicorn."))
for uvicorn_logger in loggers:
uvicorn_logger.handlers = []
logging.getLogger("uvicorn").handlers = [InterceptHandler()]
@ -111,6 +106,4 @@ class InterceptHandler(logging.Handler):
frame = frame.f_back
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(
level, record.getMessage()
)
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())

View file

@ -2,13 +2,15 @@ import importlib
import inspect
import re
from functools import wraps
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from docstring_parser import parse
from langflow.schema.schema import Record
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
from langflow.utils import constants
from langflow.utils.logger import logger
def unescape_string(s: str):
@ -66,54 +68,6 @@ def build_template_from_function(name: str, type_to_loader_dict: Dict, add_funct
}
def build_template_from_class(name: str, type_to_cls_dict: Dict, add_function: bool = False):
classes = [item.__name__ for item in type_to_cls_dict.values()]
# Raise error if name is not in chains
if name not in classes:
raise ValueError(f"{name} not found.")
for _type, v in type_to_cls_dict.items():
if v.__name__ == name:
_class = v
# Get the docstring
docs = parse(_class.__doc__)
variables = {"_type": _type}
if "__fields__" in _class.__dict__:
for class_field_items, value in _class.__fields__.items():
if class_field_items in ["callback_manager"]:
continue
variables[class_field_items] = {}
for name_, value_ in value.__repr_args__():
if name_ == "default_factory":
try:
variables[class_field_items]["default"] = get_default_factory(
module=_class.__base__.__module__,
function=value_,
)
except Exception:
variables[class_field_items]["default"] = None
elif name_ not in ["name"]:
variables[class_field_items][name_] = value_
variables[class_field_items]["placeholder"] = (
docs.params[class_field_items] if class_field_items in docs.params else ""
)
base_classes = get_base_classes(_class)
# Adding function to base classes to allow
# the output to be a function
if add_function:
base_classes.append("Callable")
return {
"template": format_dict(variables, name),
"description": docs.short_description or "",
"base_classes": base_classes,
}
def build_template_from_method(
class_name: str,
method_name: str,
@ -462,3 +416,35 @@ def build_loader_repr_from_records(records: List[Record]) -> str:
\nAvg. Record Length (characters): {int(avg_length)}
Records: {records[:3]}..."""
return "0 records"
def update_settings(
config: Optional[str] = None,
cache: Optional[str] = None,
dev: bool = False,
remove_api_keys: bool = False,
components_path: Optional[Path] = None,
store: bool = True,
):
"""Update the settings from a config file."""
from langflow.services.utils import initialize_settings_service
# Check for database_url in the environment variables
initialize_settings_service()
settings_service = get_settings_service()
if config:
logger.debug(f"Loading settings from {config}")
settings_service.settings.update_from_yaml(config, dev=dev)
if remove_api_keys:
logger.debug(f"Setting remove_api_keys to {remove_api_keys}")
settings_service.settings.update_settings(REMOVE_API_KEYS=remove_api_keys)
if cache:
logger.debug(f"Setting cache to {cache}")
settings_service.settings.update_settings(CACHE=cache)
if components_path:
logger.debug(f"Adding component path {components_path}")
settings_service.settings.update_settings(COMPONENTS_PATH=components_path)
if not store:
logger.debug("Setting store to False")
settings_service.settings.update_settings(STORE=False)

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more