Merge remote-tracking branch 'origin/dev' into fix/minor_bugs
This commit is contained in:
commit
81f61201be
435 changed files with 9714 additions and 13063 deletions
|
|
@ -22,7 +22,8 @@ from sqlmodel import select
|
|||
from langflow.main import setup_app
|
||||
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
|
||||
from langflow.services.database.utils import session_getter
|
||||
from langflow.services.deps import get_db_service
|
||||
from langflow.services.deps import get_db_service, get_settings_service, session_scope
|
||||
from langflow.services.settings.constants import DEFAULT_SUPERUSER
|
||||
from langflow.services.utils import initialize_services
|
||||
from langflow.utils.logger import configure, logger
|
||||
from langflow.utils.util import update_settings
|
||||
|
|
@ -76,14 +77,13 @@ def set_var_for_macos_issue():
|
|||
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"),
|
||||
timeout: int = typer.Option(300, help="Worker timeout in seconds."),
|
||||
timeout: int = typer.Option(300, help="Worker timeout in seconds.", envvar="LANGFLOW_WORKER_TIMEOUT"),
|
||||
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
|
||||
components_path: Optional[Path] = typer.Option(
|
||||
Path(__file__).parent / "components",
|
||||
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."),
|
||||
# .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"),
|
||||
|
|
@ -132,7 +132,6 @@ def run(
|
|||
load_dotenv(env_file, override=True)
|
||||
|
||||
update_settings(
|
||||
config,
|
||||
dev=dev,
|
||||
remove_api_keys=remove_api_keys,
|
||||
cache=cache,
|
||||
|
|
@ -146,6 +145,10 @@ def run(
|
|||
if is_port_in_use(port, host):
|
||||
port = get_free_port(port)
|
||||
|
||||
settings_service = get_settings_service()
|
||||
|
||||
settings_service.set("worker_timeout", timeout)
|
||||
|
||||
options = {
|
||||
"bind": f"{host}:{port}",
|
||||
"workers": get_number_of_workers(workers),
|
||||
|
|
@ -510,6 +513,66 @@ def migration(
|
|||
display_results(results)
|
||||
|
||||
|
||||
@app.command()
|
||||
def api_key(
|
||||
log_level: str = typer.Option("error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
|
||||
):
|
||||
"""
|
||||
Creates an API key for the default superuser if AUTO_LOGIN is enabled.
|
||||
|
||||
Args:
|
||||
log_level (str, optional): Logging level. Defaults to "error".
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
configure(log_level=log_level)
|
||||
initialize_services()
|
||||
settings_service = get_settings_service()
|
||||
auth_settings = settings_service.auth_settings
|
||||
if not auth_settings.AUTO_LOGIN:
|
||||
typer.echo("Auto login is disabled. API keys cannot be created through the CLI.")
|
||||
return
|
||||
with session_scope() as session:
|
||||
from langflow.services.database.models.user.model import User
|
||||
|
||||
superuser = session.exec(select(User).where(User.username == DEFAULT_SUPERUSER)).first()
|
||||
if not superuser:
|
||||
typer.echo("Default superuser not found. This command requires a superuser and AUTO_LOGIN to be enabled.")
|
||||
return
|
||||
from langflow.services.database.models.api_key import ApiKey, ApiKeyCreate
|
||||
from langflow.services.database.models.api_key.crud import create_api_key, delete_api_key
|
||||
|
||||
api_key = session.exec(select(ApiKey).where(ApiKey.user_id == superuser.id)).first()
|
||||
if api_key:
|
||||
delete_api_key(session, api_key.id)
|
||||
|
||||
api_key_create = ApiKeyCreate(name="CLI")
|
||||
unmasked_api_key = create_api_key(session, api_key_create, user_id=superuser.id)
|
||||
session.commit()
|
||||
# Create a banner to display the API key and tell the user it won't be shown again
|
||||
api_key_banner(unmasked_api_key)
|
||||
|
||||
|
||||
def api_key_banner(unmasked_api_key):
|
||||
is_mac = platform.system() == "Darwin"
|
||||
import pyperclip # type: ignore
|
||||
|
||||
pyperclip.copy(unmasked_api_key.api_key)
|
||||
panel = Panel(
|
||||
f"[bold]API Key Created Successfully:[/bold]\n\n"
|
||||
f"[bold blue]{unmasked_api_key.api_key}[/bold blue]\n\n"
|
||||
"This is the only time the API key will be displayed. \n"
|
||||
"Make sure to store it in a secure location. \n\n"
|
||||
f"The API key has been copied to your clipboard. [bold]{['Ctrl','Cmd'][is_mac]} + V[/bold] to paste it.",
|
||||
box=box.ROUNDED,
|
||||
border_style="blue",
|
||||
expand=False,
|
||||
)
|
||||
console = Console()
|
||||
console.print(panel)
|
||||
|
||||
|
||||
def main():
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from alembic import op
|
|||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
from langflow.utils import migration
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
|
|
@ -22,13 +23,9 @@ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|||
|
||||
def upgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = Inspector.from_engine(conn) # type: ignore
|
||||
table_names = inspector.get_table_names()
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = Inspector.from_engine(conn) # type: ignore
|
||||
table_names = inspector.get_table_names()
|
||||
${downgrades if downgrades else "pass"}
|
||||
|
|
|
|||
|
|
@ -48,10 +48,12 @@ def upgrade() -> None:
|
|||
with op.batch_alter_table("folder", schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f("ix_folder_name"), ["name"], unique=False)
|
||||
|
||||
if "folder_id" not in inspector.get_columns("flow"):
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
column_names = [column["name"] for column in inspector.get_columns("flow")]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
if "folder_id" not in column_names:
|
||||
batch_op.add_column(sa.Column("folder_id", sqlmodel.sql.sqltypes.GUID(), nullable=True))
|
||||
batch_op.create_foreign_key("flow_folder_id_fkey", "folder", ["folder_id"], ["id"])
|
||||
if "folder" in column_names:
|
||||
batch_op.drop_column("folder")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -62,11 +64,13 @@ def downgrade() -> None:
|
|||
inspector = Inspector.from_engine(conn) # type: ignore
|
||||
table_names = inspector.get_table_names()
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
if "folder_id" in inspector.get_columns("flow"):
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
column_names = [column["name"] for column in inspector.get_columns("flow")]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
if "folder" not in column_names:
|
||||
batch_op.add_column(sa.Column("folder", sa.VARCHAR(), nullable=True))
|
||||
batch_op.drop_constraint("flow_folder_id_fkey", type_="foreignkey")
|
||||
if "folder_id" in column_names:
|
||||
batch_op.drop_column("folder_id")
|
||||
batch_op.drop_constraint("flow_folder_id_fkey", type_="foreignkey")
|
||||
|
||||
indexes = inspector.get_indexes("folder")
|
||||
if "ix_folder_name" in [index["name"] for index in indexes]:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
"""Add unique constraints per user in folder table
|
||||
|
||||
Revision ID: 1c79524817ed
|
||||
Revises: 3bb0ddf32dfb
|
||||
Create Date: 2024-05-29 23:12:09.146880
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "1c79524817ed"
|
||||
down_revision: Union[str, None] = "3bb0ddf32dfb"
|
||||
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
|
||||
constraints_names = [constraint["name"] for constraint in inspector.get_unique_constraints("folder")]
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("folder", schema=None) as batch_op:
|
||||
if "unique_folder_name" not in constraints_names:
|
||||
batch_op.create_unique_constraint("unique_folder_name", ["user_id", "name"])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = Inspector.from_engine(conn) # type: ignore
|
||||
constraints_names = [constraint["name"] for constraint in inspector.get_unique_constraints("folder")]
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("folder", schema=None) as batch_op:
|
||||
if "unique_folder_name" in constraints_names:
|
||||
batch_op.drop_constraint("unique_folder_name", type_="unique")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
"""Add unique constraints per user in flow table
|
||||
|
||||
Revision ID: 3bb0ddf32dfb
|
||||
Revises: a72f5cf9c2f9
|
||||
Create Date: 2024-05-29 23:08:43.935040
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "3bb0ddf32dfb"
|
||||
down_revision: Union[str, None] = "a72f5cf9c2f9"
|
||||
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
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
indexes_names = [index["name"] for index in inspector.get_indexes("flow")]
|
||||
constraints_names = [constraint["name"] for constraint in inspector.get_unique_constraints("flow")]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
if "ix_flow_endpoint_name" in indexes_names:
|
||||
batch_op.drop_index("ix_flow_endpoint_name")
|
||||
batch_op.create_index(batch_op.f("ix_flow_endpoint_name"), ["endpoint_name"], unique=False)
|
||||
if "unique_flow_endpoint_name" not in constraints_names:
|
||||
batch_op.create_unique_constraint("unique_flow_endpoint_name", ["user_id", "endpoint_name"])
|
||||
if "unique_flow_name" not in constraints_names:
|
||||
batch_op.create_unique_constraint("unique_flow_name", ["user_id", "name"])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = Inspector.from_engine(conn) # type: ignore
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
indexes_names = [index["name"] for index in inspector.get_indexes("flow")]
|
||||
constraints_names = [constraint["name"] for constraint in inspector.get_unique_constraints("flow")]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
if "unique_flow_name" in constraints_names:
|
||||
batch_op.drop_constraint("unique_flow_name", type_="unique")
|
||||
if "unique_flow_endpoint_name" in constraints_names:
|
||||
batch_op.drop_constraint("unique_flow_endpoint_name", type_="unique")
|
||||
if "ix_flow_endpoint_name" in indexes_names:
|
||||
batch_op.drop_index(batch_op.f("ix_flow_endpoint_name"))
|
||||
batch_op.create_index("ix_flow_endpoint_name", ["endpoint_name"], unique=1)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
"""Add webhook columns
|
||||
|
||||
Revision ID: 631faacf5da2
|
||||
Revises: 1c79524817ed
|
||||
Create Date: 2024-04-22 15:14:43.454784
|
||||
|
||||
"""
|
||||
|
||||
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 = "631faacf5da2"
|
||||
down_revision: Union[str, None] = "1c79524817ed"
|
||||
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! ###
|
||||
column_names = [column["name"] for column in inspector.get_columns("flow")]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
if "flow" in table_names and "webhook" not in column_names:
|
||||
batch_op.add_column(sa.Column("webhook", sa.Boolean(), 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! ###
|
||||
column_names = [column["name"] for column in inspector.get_columns("flow")]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
if "flow" in table_names and "webhook" in column_names:
|
||||
batch_op.drop_column("webhook")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -52,9 +52,14 @@ def upgrade() -> None:
|
|||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
try:
|
||||
conn = op.get_bind()
|
||||
inspector = Inspector.from_engine(conn) # type: ignore
|
||||
column_names = [column["name"] for column in inspector.get_columns("flow")]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
batch_op.drop_column("folder")
|
||||
batch_op.drop_column("updated_at")
|
||||
if "folder" in column_names:
|
||||
batch_op.drop_column("folder")
|
||||
if "updated_at" in column_names:
|
||||
batch_op.drop_column("updated_at")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
"""Add endpoint name col
|
||||
|
||||
Revision ID: a72f5cf9c2f9
|
||||
Revises: 29fe8f1f806b
|
||||
Create Date: 2024-05-29 21:44:04.240816
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel
|
||||
from alembic import op
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "a72f5cf9c2f9"
|
||||
down_revision: Union[str, None] = "29fe8f1f806b"
|
||||
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
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
column_names = [column["name"] for column in inspector.get_columns("flow")]
|
||||
indexes = inspector.get_indexes("flow")
|
||||
index_names = [index["name"] for index in indexes]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
if "endpoint_name" not in column_names:
|
||||
batch_op.add_column(sa.Column("endpoint_name", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
|
||||
if "ix_flow_endpoint_name" not in index_names:
|
||||
batch_op.create_index(batch_op.f("ix_flow_endpoint_name"), ["endpoint_name"], unique=True)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
inspector = Inspector.from_engine(conn) # type: ignore
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
column_names = [column["name"] for column in inspector.get_columns("flow")]
|
||||
indexes = inspector.get_indexes("flow")
|
||||
index_names = [index["name"] for index in indexes]
|
||||
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||
if "ix_flow_endpoint_name" in index_names:
|
||||
batch_op.drop_index(batch_op.f("ix_flow_endpoint_name"))
|
||||
if "endpoint_name" in column_names:
|
||||
batch_op.drop_column("endpoint_name")
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -286,7 +286,7 @@ async def get_next_runnable_vertices(
|
|||
for v_id in set(next_runnable_vertices): # Use set to avoid duplicates
|
||||
graph.vertices_to_run.remove(v_id)
|
||||
graph.remove_from_predecessors(v_id)
|
||||
await chat_service.set_cache(flow_id=flow_id, data=graph, lock=lock)
|
||||
await chat_service.set_cache(key=flow_id, data=graph, lock=lock)
|
||||
return next_runnable_vertices
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import time
|
||||
import uuid
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Annotated, Optional
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException
|
||||
|
|
@ -162,7 +161,6 @@ async def build_vertex(
|
|||
vertex = graph.get_vertex(vertex_id)
|
||||
try:
|
||||
lock = chat_service._cache_locks[flow_id_str]
|
||||
set_cache_coro = partial(chat_service.set_cache, flow_id=flow_id_str)
|
||||
(
|
||||
next_runnable_vertices,
|
||||
top_level_vertices,
|
||||
|
|
@ -173,7 +171,7 @@ async def build_vertex(
|
|||
vertex,
|
||||
) = await graph.build_vertex(
|
||||
lock=lock,
|
||||
set_cache_coro=set_cache_coro,
|
||||
chat_service=chat_service,
|
||||
vertex_id=vertex_id,
|
||||
user_id=current_user.id,
|
||||
inputs_dict=inputs.model_dump() if inputs else {},
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
from http import HTTPStatus
|
||||
from typing import Annotated, List, Optional, Union
|
||||
from typing import TYPE_CHECKING, Annotated, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
import sqlalchemy as sa
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException, Request, UploadFile, status
|
||||
from loguru import logger
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from langflow.api.utils import update_frontend_node_with_template_values
|
||||
from langflow.api.v1.schemas import (
|
||||
ConfigResponse,
|
||||
CustomComponentRequest,
|
||||
InputValueRequest,
|
||||
ProcessResponse,
|
||||
|
|
@ -18,20 +19,25 @@ from langflow.api.v1.schemas import (
|
|||
UpdateCustomComponentRequest,
|
||||
UploadFileResponse,
|
||||
)
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.custom.utils import build_custom_component_template
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.interface.custom.utils import build_custom_component_template
|
||||
from langflow.graph.schema import RunOutputs
|
||||
from langflow.helpers.flow import get_flow_by_id_or_endpoint_name
|
||||
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
|
||||
from langflow.services.database.models.flow.utils import get_all_webhook_components_in_flow, get_flow_by_id
|
||||
from langflow.services.database.models.user.model import User
|
||||
from langflow.services.deps import get_session, get_session_service, get_settings_service, get_task_service
|
||||
from langflow.services.session.service import SessionService
|
||||
from langflow.services.task.service import TaskService
|
||||
|
||||
# build router
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.settings.manager import SettingsService
|
||||
|
||||
router = APIRouter(tags=["Base"])
|
||||
|
||||
|
||||
|
|
@ -43,17 +49,82 @@ def get_all(
|
|||
|
||||
logger.debug("Building langchain types dict")
|
||||
try:
|
||||
all_types_dict = get_all_types_dict(settings_service.settings.COMPONENTS_PATH)
|
||||
all_types_dict = get_all_types_dict(settings_service.settings.components_path)
|
||||
return all_types_dict
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
|
||||
async def simple_run_flow(
|
||||
db: Session,
|
||||
flow: Flow,
|
||||
input_request: SimplifiedAPIRequest,
|
||||
session_service: SessionService,
|
||||
stream: bool = False,
|
||||
api_key_user: Optional[User] = None,
|
||||
):
|
||||
try:
|
||||
task_result: List[RunOutputs] = []
|
||||
artifacts = {}
|
||||
user_id = api_key_user.id if api_key_user else None
|
||||
flow_id_str = str(flow.id)
|
||||
if input_request.session_id:
|
||||
session_data = await session_service.load_session(input_request.session_id, flow_id=flow_id_str)
|
||||
graph, artifacts = session_data if session_data else (None, None)
|
||||
if graph is None:
|
||||
raise ValueError(f"Session {input_request.session_id} not found")
|
||||
else:
|
||||
if flow.data is None:
|
||||
raise ValueError(f"Flow {flow_id_str} has no data")
|
||||
graph_data = flow.data
|
||||
graph_data = process_tweaks(graph_data, input_request.tweaks or {})
|
||||
graph = Graph.from_payload(graph_data, flow_id=flow_id_str, user_id=str(user_id))
|
||||
inputs = [
|
||||
InputValueRequest(components=[], input_value=input_request.input_value, type=input_request.input_type)
|
||||
]
|
||||
# outputs is a list of all components that should return output
|
||||
# we need to get them by checking their type
|
||||
# if the output type is debug, we return all outputs
|
||||
# if the output type is any, we return all outputs that are either chat or text
|
||||
# if the output type is chat or text, we return only the outputs that match the type
|
||||
if input_request.output_component:
|
||||
outputs = [input_request.output_component]
|
||||
else:
|
||||
outputs = [
|
||||
vertex.id
|
||||
for vertex in graph.vertices
|
||||
if input_request.output_type == "debug"
|
||||
or (
|
||||
vertex.is_output
|
||||
and (input_request.output_type == "any" or input_request.output_type in vertex.id.lower())
|
||||
)
|
||||
]
|
||||
task_result, session_id = await run_graph_internal(
|
||||
graph=graph,
|
||||
flow_id=flow_id_str,
|
||||
session_id=input_request.session_id,
|
||||
inputs=inputs,
|
||||
outputs=outputs,
|
||||
artifacts=artifacts,
|
||||
session_service=session_service,
|
||||
stream=stream,
|
||||
)
|
||||
|
||||
return RunResponse(outputs=task_result, session_id=session_id)
|
||||
|
||||
except sa.exc.StatementError as exc:
|
||||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||
if "badly formed hexadecimal UUID string" in str(exc):
|
||||
logger.error(f"Flow ID {flow_id_str} is not a valid UUID")
|
||||
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
||||
raise ValueError(str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/run/{flow_id_or_name}", response_model=RunResponse, response_model_exclude_none=True)
|
||||
async def simplified_run_flow(
|
||||
db: Annotated[Session, Depends(get_session)],
|
||||
flow_id: UUID,
|
||||
flow: Annotated[Flow, Depends(get_flow_by_id_or_endpoint_name)],
|
||||
input_request: SimplifiedAPIRequest = SimplifiedAPIRequest(),
|
||||
stream: bool = False,
|
||||
api_key_user: User = Depends(api_key_security),
|
||||
|
|
@ -64,7 +135,7 @@ async def simplified_run_flow(
|
|||
|
||||
### Parameters:
|
||||
- `db` (Session): Database session for executing queries.
|
||||
- `flow_id` (str): Unique identifier of the flow to be executed.
|
||||
- `flow_id_or_name` (str): ID or endpoint name of the flow to run.
|
||||
- `input_request` (SimplifiedAPIRequest): Request object containing input values, types, output selection, tweaks, and session ID.
|
||||
- `api_key_user` (User): User object derived from the provided API key, used for authentication.
|
||||
- `session_service` (SessionService): Service for managing flow sessions, essential for session reuse and caching.
|
||||
|
|
@ -107,73 +178,21 @@ async def simplified_run_flow(
|
|||
|
||||
This endpoint provides a powerful interface for executing flows with enhanced flexibility and efficiency, supporting a wide range of applications by allowing for dynamic input and output configuration along with performance optimizations through session management and caching.
|
||||
"""
|
||||
session_id = input_request.session_id
|
||||
|
||||
try:
|
||||
flow_id_str = str(flow_id)
|
||||
artifacts = {}
|
||||
if input_request.session_id:
|
||||
session_data = await session_service.load_session(input_request.session_id, flow_id=flow_id_str)
|
||||
graph, artifacts = session_data if session_data else (None, None)
|
||||
if graph is None:
|
||||
raise ValueError(f"Session {input_request.session_id} not found")
|
||||
else:
|
||||
# Get the flow that matches the flow_id and belongs to the user
|
||||
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
|
||||
flow = db.exec(select(Flow).where(Flow.id == flow_id_str).where(Flow.user_id == api_key_user.id)).first()
|
||||
if flow is None:
|
||||
raise ValueError(f"Flow {flow_id_str} not found")
|
||||
|
||||
if flow.data is None:
|
||||
raise ValueError(f"Flow {flow_id_str} has no data")
|
||||
graph_data = flow.data
|
||||
|
||||
graph_data = process_tweaks(graph_data, input_request.tweaks or {}, stream=stream)
|
||||
graph = Graph.from_payload(graph_data, flow_id=flow_id_str, user_id=str(api_key_user.id))
|
||||
inputs = [
|
||||
InputValueRequest(components=[], input_value=input_request.input_value, type=input_request.input_type)
|
||||
]
|
||||
# outputs is a list of all components that should return output
|
||||
# we need to get them by checking their type
|
||||
# if the output type is debug, we return all outputs
|
||||
# if the output type is any, we return all outputs that are either chat or text
|
||||
# if the output type is chat or text, we return only the outputs that match the type
|
||||
if input_request.output_component:
|
||||
outputs = [input_request.output_component]
|
||||
else:
|
||||
outputs = [
|
||||
vertex.id
|
||||
for vertex in graph.vertices
|
||||
if input_request.output_type == "debug"
|
||||
or (
|
||||
vertex.is_output
|
||||
and (input_request.output_type == "any" or input_request.output_type in vertex.id.lower())
|
||||
)
|
||||
]
|
||||
task_result, session_id = await run_graph_internal(
|
||||
graph=graph,
|
||||
flow_id=flow_id_str,
|
||||
session_id=input_request.session_id,
|
||||
inputs=inputs,
|
||||
outputs=outputs,
|
||||
artifacts=artifacts,
|
||||
return await simple_run_flow(
|
||||
db=db,
|
||||
flow=flow,
|
||||
input_request=input_request,
|
||||
session_service=session_service,
|
||||
stream=stream,
|
||||
api_key_user=api_key_user,
|
||||
)
|
||||
|
||||
return RunResponse(outputs=task_result, session_id=session_id)
|
||||
except sa.exc.StatementError as exc:
|
||||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||
if "badly formed hexadecimal UUID string" in str(exc):
|
||||
logger.error(f"Flow ID {flow_id_str} is not a valid UUID")
|
||||
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
if f"Flow {flow_id_str} not found" in str(exc):
|
||||
logger.error(f"Flow {flow_id_str} not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
elif f"Session {session_id} not found" in str(exc):
|
||||
logger.error(f"Session {session_id} not found")
|
||||
if "badly formed hexadecimal UUID string" in str(exc):
|
||||
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
|
||||
if "not found" in str(exc):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
else:
|
||||
logger.exception(exc)
|
||||
|
|
@ -183,6 +202,68 @@ async def simplified_run_flow(
|
|||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/webhook/{flow_id}", response_model=dict, status_code=HTTPStatus.ACCEPTED)
|
||||
async def webhook_run_flow(
|
||||
db: Annotated[Session, Depends(get_session)],
|
||||
flow: Annotated[Flow, Depends(get_flow_by_id)],
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
session_service: SessionService = Depends(get_session_service),
|
||||
):
|
||||
"""
|
||||
Run a flow using a webhook request.
|
||||
|
||||
Args:
|
||||
db (Session): The database session.
|
||||
request (Request): The incoming HTTP request.
|
||||
background_tasks (BackgroundTasks): The background tasks manager.
|
||||
session_service (SessionService, optional): The session service. Defaults to Depends(get_session_service).
|
||||
flow (Flow, optional): The flow to be executed. Defaults to Depends(get_flow_by_id).
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the status of the task.
|
||||
|
||||
Raises:
|
||||
HTTPException: If the flow is not found or if there is an error processing the request.
|
||||
"""
|
||||
try:
|
||||
logger.debug("Received webhook request")
|
||||
data = await request.body()
|
||||
if not data:
|
||||
logger.error("Request body is empty")
|
||||
raise ValueError(
|
||||
"Request body is empty. You should provide a JSON payload containing the flow ID.",
|
||||
)
|
||||
|
||||
# get all webhook components in the flow
|
||||
webhook_components = get_all_webhook_components_in_flow(flow.data)
|
||||
tweaks = {}
|
||||
data_dict = await request.json()
|
||||
for component in webhook_components:
|
||||
tweaks[component["id"]] = {"data": data.decode() if isinstance(data, bytes) else data}
|
||||
input_request = SimplifiedAPIRequest(
|
||||
input_value=data_dict.get("input_value", ""),
|
||||
input_type=data_dict.get("input_type", "chat"),
|
||||
output_type=data_dict.get("output_type", "chat"),
|
||||
tweaks=tweaks,
|
||||
session_id=data_dict.get("session_id"),
|
||||
)
|
||||
logger.debug("Starting background task")
|
||||
background_tasks.add_task(
|
||||
simple_run_flow,
|
||||
db=db,
|
||||
flow=flow,
|
||||
input_request=input_request,
|
||||
session_service=session_service,
|
||||
)
|
||||
return {"message": "Task started in the background", "status": "in progress"}
|
||||
except Exception as exc:
|
||||
if "Flow ID is required" in str(exc) or "Request body is empty" in str(exc):
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
logger.exception(exc)
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/run/advanced/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
|
||||
async def experimental_run_flow(
|
||||
session: Annotated[Session, Depends(get_session)],
|
||||
|
|
@ -440,3 +521,15 @@ async def custom_component_update(
|
|||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.get("/config", response_model=ConfigResponse)
|
||||
def get_config():
|
||||
try:
|
||||
from langflow.services.deps import get_settings_service
|
||||
|
||||
settings_service: "SettingsService" = get_settings_service()
|
||||
return settings_service.settings.model_dump()
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from langflow.api.v1.schemas import FlowListCreate, FlowListIds, FlowListRead
|
|||
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
|
||||
from langflow.services.auth.utils import get_current_active_user
|
||||
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
|
||||
from langflow.services.database.models.flow.utils import get_webhook_component_in_flow
|
||||
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
|
||||
from langflow.services.database.models.folder.model import Folder
|
||||
from langflow.services.database.models.user.model import User
|
||||
|
|
@ -38,7 +39,10 @@ def create_flow(
|
|||
db_flow.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
if db_flow.folder_id is None:
|
||||
default_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
|
||||
# Make sure flows always have a folder
|
||||
default_folder = session.exec(
|
||||
select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME, Folder.user_id == current_user.id)
|
||||
).first()
|
||||
if default_folder:
|
||||
db_flow.folder_id = default_folder.id
|
||||
|
||||
|
|
@ -54,8 +58,22 @@ def read_flows(
|
|||
current_user: User = Depends(get_current_active_user),
|
||||
session: Session = Depends(get_session),
|
||||
settings_service: "SettingsService" = Depends(get_settings_service),
|
||||
remove_example_flows: bool = False,
|
||||
):
|
||||
"""Read all flows."""
|
||||
"""
|
||||
Retrieve a list of flows.
|
||||
|
||||
Args:
|
||||
current_user (User): The current authenticated user.
|
||||
session (Session): The database session.
|
||||
settings_service (SettingsService): The settings service.
|
||||
remove_example_flows (bool, optional): Whether to remove example flows. Defaults to False.
|
||||
|
||||
|
||||
Returns:
|
||||
List[Dict]: A list of flows in JSON format.
|
||||
"""
|
||||
|
||||
try:
|
||||
auth_settings = settings_service.auth_settings
|
||||
if auth_settings.AUTO_LOGIN:
|
||||
|
|
@ -70,15 +88,16 @@ def read_flows(
|
|||
flows = validate_is_component(flows) # type: ignore
|
||||
flow_ids = [flow.id for flow in flows]
|
||||
# with the session get the flows that DO NOT have a user_id
|
||||
try:
|
||||
folder = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
|
||||
if not remove_example_flows:
|
||||
try:
|
||||
folder = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
|
||||
|
||||
example_flows = folder.flows if folder else []
|
||||
for example_flow in example_flows:
|
||||
if example_flow.id not in flow_ids:
|
||||
flows.append(example_flow) # type: ignore
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
example_flows = folder.flows if folder else []
|
||||
for example_flow in example_flows:
|
||||
if example_flow.id not in flow_ids:
|
||||
flows.append(example_flow) # type: ignore
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
return [jsonable_encoder(flow) for flow in flows]
|
||||
|
|
@ -117,30 +136,51 @@ def update_flow(
|
|||
settings_service=Depends(get_settings_service),
|
||||
):
|
||||
"""Update a flow."""
|
||||
try:
|
||||
db_flow = read_flow(
|
||||
session=session,
|
||||
flow_id=flow_id,
|
||||
current_user=current_user,
|
||||
settings_service=settings_service,
|
||||
)
|
||||
if not db_flow:
|
||||
raise HTTPException(status_code=404, detail="Flow not found")
|
||||
flow_data = flow.model_dump(exclude_unset=True)
|
||||
if settings_service.settings.remove_api_keys:
|
||||
flow_data = remove_api_keys(flow_data)
|
||||
for key, value in flow_data.items():
|
||||
if value is not None:
|
||||
setattr(db_flow, key, value)
|
||||
webhook_component = get_webhook_component_in_flow(db_flow.data)
|
||||
db_flow.webhook = webhook_component is not None
|
||||
db_flow.updated_at = datetime.now(timezone.utc)
|
||||
if db_flow.folder_id is None:
|
||||
default_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
|
||||
if default_folder:
|
||||
db_flow.folder_id = default_folder.id
|
||||
session.add(db_flow)
|
||||
session.commit()
|
||||
session.refresh(db_flow)
|
||||
return db_flow
|
||||
except Exception as e:
|
||||
# If it is a validation error, return the error message
|
||||
if hasattr(e, "errors"):
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
elif "UNIQUE constraint failed" in str(e):
|
||||
# Get the name of the column that failed
|
||||
columns = str(e).split("UNIQUE constraint failed: ")[1].split(".")[1].split("\n")[0]
|
||||
# UNIQUE constraint failed: flow.user_id, flow.name
|
||||
# or UNIQUE constraint failed: flow.name
|
||||
# if the column has id in it, we want the other column
|
||||
column = columns.split(",")[1] if "id" in columns.split(",")[0] else columns.split(",")[0]
|
||||
|
||||
db_flow = read_flow(
|
||||
session=session,
|
||||
flow_id=flow_id,
|
||||
current_user=current_user,
|
||||
settings_service=settings_service,
|
||||
)
|
||||
if not db_flow:
|
||||
raise HTTPException(status_code=404, detail="Flow not found")
|
||||
flow_data = flow.model_dump(exclude_unset=True)
|
||||
if settings_service.settings.REMOVE_API_KEYS:
|
||||
flow_data = remove_api_keys(flow_data)
|
||||
for key, value in flow_data.items():
|
||||
if value is not None:
|
||||
setattr(db_flow, key, value)
|
||||
db_flow.updated_at = datetime.now(timezone.utc)
|
||||
if db_flow.folder_id is None:
|
||||
default_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
|
||||
if default_folder:
|
||||
db_flow.folder_id = default_folder.id
|
||||
session.add(db_flow)
|
||||
session.commit()
|
||||
session.refresh(db_flow)
|
||||
return db_flow
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"{column.capitalize().replace('_', ' ')} must be unique"
|
||||
) from e
|
||||
elif isinstance(e, HTTPException):
|
||||
raise e
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.delete("/{flow_id}", status_code=200)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from typing import List
|
||||
from uuid import UUID
|
||||
|
||||
import orjson
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status
|
||||
|
|
@ -88,7 +87,7 @@ def read_folders(
|
|||
def read_folder(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
folder_id: UUID,
|
||||
folder_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
try:
|
||||
|
|
@ -106,7 +105,7 @@ def read_folder(
|
|||
def update_folder(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
folder_id: UUID,
|
||||
folder_id: str,
|
||||
folder: FolderUpdate, # Assuming FolderUpdate is a Pydantic model defining updatable fields
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
|
|
@ -155,7 +154,7 @@ def update_folder(
|
|||
def delete_folder(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
folder_id: UUID,
|
||||
folder_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
try:
|
||||
|
|
@ -177,7 +176,7 @@ def delete_folder(
|
|||
async def download_file(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
folder_id: UUID,
|
||||
folder_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""Download all flows from folder."""
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ async def login_to_get_access_token(
|
|||
samesite=auth_settings.REFRESH_SAME_SITE,
|
||||
secure=auth_settings.REFRESH_SECURE,
|
||||
expires=auth_settings.REFRESH_TOKEN_EXPIRE_SECONDS,
|
||||
domain=auth_settings.COOKIE_DOMAIN,
|
||||
)
|
||||
response.set_cookie(
|
||||
"access_token_lf",
|
||||
|
|
@ -54,6 +55,7 @@ async def login_to_get_access_token(
|
|||
samesite=auth_settings.ACCESS_SAME_SITE,
|
||||
secure=auth_settings.ACCESS_SECURE,
|
||||
expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||
domain=auth_settings.COOKIE_DOMAIN,
|
||||
)
|
||||
variable_service.initialize_user_variables(user.id, db)
|
||||
# Create default folder for user if it doesn't exist
|
||||
|
|
@ -71,8 +73,7 @@ async def login_to_get_access_token(
|
|||
async def auto_login(
|
||||
response: Response,
|
||||
db: Session = Depends(get_session),
|
||||
settings_service=Depends(get_settings_service),
|
||||
variable_service: VariableService = Depends(get_variable_service),
|
||||
settings_service=Depends(get_settings_service)
|
||||
):
|
||||
auth_settings = settings_service.auth_settings
|
||||
if settings_service.auth_settings.AUTO_LOGIN:
|
||||
|
|
@ -84,9 +85,9 @@ async def auto_login(
|
|||
samesite=auth_settings.ACCESS_SAME_SITE,
|
||||
secure=auth_settings.ACCESS_SECURE,
|
||||
expires=None, # Set to None to make it a session cookie
|
||||
domain=auth_settings.COOKIE_DOMAIN,
|
||||
)
|
||||
variable_service.initialize_user_variables(user_id, db)
|
||||
create_default_folder_if_it_doesnt_exist(db, user_id)
|
||||
|
||||
return tokens
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -117,6 +118,7 @@ async def refresh_token(
|
|||
samesite=auth_settings.REFRESH_SAME_SITE,
|
||||
secure=auth_settings.REFRESH_SECURE,
|
||||
expires=auth_settings.REFRESH_TOKEN_EXPIRE_SECONDS,
|
||||
domain=auth_settings.COOKIE_DOMAIN,
|
||||
)
|
||||
response.set_cookie(
|
||||
"access_token_lf",
|
||||
|
|
@ -125,6 +127,7 @@ async def refresh_token(
|
|||
samesite=auth_settings.ACCESS_SAME_SITE,
|
||||
secure=auth_settings.ACCESS_SECURE,
|
||||
expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||
domain=auth_settings.COOKIE_DOMAIN,
|
||||
)
|
||||
return tokens
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -248,6 +248,7 @@ class ResultDataResponse(BaseModel):
|
|||
artifacts: Optional[Any] = Field(default_factory=dict)
|
||||
timedelta: Optional[float] = None
|
||||
duration: Optional[str] = None
|
||||
used_frozen_result: Optional[bool] = False
|
||||
|
||||
|
||||
class VertexBuildResponse(BaseModel):
|
||||
|
|
@ -316,3 +317,7 @@ class FlowDataRequest(BaseModel):
|
|||
nodes: List[dict]
|
||||
edges: List[dict]
|
||||
viewport: Optional[dict] = None
|
||||
|
||||
|
||||
class ConfigResponse(BaseModel):
|
||||
frontend_timeout: int
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ def check_if_store_is_enabled(
|
|||
settings_service=Depends(get_settings_service),
|
||||
):
|
||||
return {
|
||||
"enabled": settings_service.settings.STORE,
|
||||
"enabled": settings_service.settings.store,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
89
src/backend/base/langflow/base/curl/parse.py
Normal file
89
src/backend/base/langflow/base/curl/parse.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
This file contains a fix for the implementation of the `uncurl` library, which is available at https://github.com/spulec/uncurl.git.
|
||||
|
||||
The `uncurl` library provides a way to parse and convert cURL commands into Python requests. However, there are some issues with the original implementation that this file aims to fix.
|
||||
|
||||
The `parse_context` function in this file takes a cURL command as input and returns a `ParsedContext` object, which contains the parsed information from the cURL command, such as the HTTP method, URL, headers, cookies, etc.
|
||||
|
||||
The `normalize_newlines` function is a helper function that replaces the line continuation character ("\") followed by a newline with a space.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
import shlex
|
||||
from collections import OrderedDict, namedtuple
|
||||
from http.cookies import SimpleCookie
|
||||
|
||||
from uncurl.api import parser # type: ignore
|
||||
|
||||
parser.add_argument("-x", "--proxy", default={})
|
||||
parser.add_argument("-U", "--proxy-user", default="")
|
||||
|
||||
ParsedContext = namedtuple("ParsedContext", ["method", "url", "data", "headers", "cookies", "verify", "auth", "proxy"])
|
||||
|
||||
|
||||
def normalize_newlines(multiline_text):
|
||||
return multiline_text.replace(" \\\n", " ")
|
||||
|
||||
|
||||
def parse_context(curl_command):
|
||||
method = "get"
|
||||
|
||||
tokens = shlex.split(normalize_newlines(curl_command))
|
||||
tokens = [token for token in tokens if token and token != " "]
|
||||
parsed_args = parser.parse_args(tokens)
|
||||
|
||||
post_data = parsed_args.data or parsed_args.data_binary
|
||||
if post_data:
|
||||
method = "post"
|
||||
|
||||
if parsed_args.X:
|
||||
method = parsed_args.X.lower()
|
||||
|
||||
cookie_dict = OrderedDict()
|
||||
quoted_headers = OrderedDict()
|
||||
|
||||
for curl_header in parsed_args.header:
|
||||
if curl_header.startswith(":"):
|
||||
occurrence = [m.start() for m in re.finditer(":", curl_header)]
|
||||
header_key, header_value = curl_header[: occurrence[1]], curl_header[occurrence[1] + 1 :]
|
||||
else:
|
||||
header_key, header_value = curl_header.split(":", 1)
|
||||
|
||||
if header_key.lower().strip("$") == "cookie":
|
||||
cookie = SimpleCookie(bytes(header_value, "ascii").decode("unicode-escape"))
|
||||
for key in cookie:
|
||||
cookie_dict[key] = cookie[key].value
|
||||
else:
|
||||
quoted_headers[header_key] = header_value.strip()
|
||||
|
||||
# add auth
|
||||
user = parsed_args.user
|
||||
if parsed_args.user:
|
||||
user = tuple(user.split(":"))
|
||||
|
||||
# add proxy and its authentication if it's available.
|
||||
proxies = parsed_args.proxy
|
||||
# proxy_auth = parsed_args.proxy_user
|
||||
if parsed_args.proxy and parsed_args.proxy_user:
|
||||
proxies = {
|
||||
"http": "http://{}@{}/".format(parsed_args.proxy_user, parsed_args.proxy),
|
||||
"https": "http://{}@{}/".format(parsed_args.proxy_user, parsed_args.proxy),
|
||||
}
|
||||
elif parsed_args.proxy:
|
||||
proxies = {
|
||||
"http": "http://{}/".format(parsed_args.proxy),
|
||||
"https": "http://{}/".format(parsed_args.proxy),
|
||||
}
|
||||
|
||||
return ParsedContext(
|
||||
method=method,
|
||||
url=parsed_args.url,
|
||||
data=post_data,
|
||||
headers=quoted_headers,
|
||||
cookies=cookie_dict,
|
||||
verify=parsed_args.insecure,
|
||||
auth=user,
|
||||
proxy=proxies,
|
||||
)
|
||||
67
src/backend/base/langflow/base/flow_processing/utils.py
Normal file
67
src/backend/base/langflow/base/flow_processing/utils.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
from typing import List
|
||||
|
||||
from langflow.graph.schema import ResultData, RunOutputs
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
def build_records_from_run_outputs(run_outputs: RunOutputs) -> List[Record]:
|
||||
"""
|
||||
Build a list of records from the given RunOutputs.
|
||||
|
||||
Args:
|
||||
run_outputs (RunOutputs): The RunOutputs object containing the output data.
|
||||
|
||||
Returns:
|
||||
List[Record]: A list of records built from the RunOutputs.
|
||||
|
||||
"""
|
||||
if not run_outputs:
|
||||
return []
|
||||
records = []
|
||||
for result_data in run_outputs.outputs:
|
||||
if result_data:
|
||||
records.extend(build_records_from_result_data(result_data))
|
||||
return records
|
||||
|
||||
|
||||
def build_records_from_result_data(result_data: ResultData, get_final_results_only: bool = True) -> List[Record]:
|
||||
"""
|
||||
Build a list of records from the given ResultData.
|
||||
|
||||
Args:
|
||||
result_data (ResultData): The ResultData object containing the result data.
|
||||
get_final_results_only (bool, optional): Whether to include only final results. Defaults to True.
|
||||
|
||||
Returns:
|
||||
List[Record]: A list of records built from the ResultData.
|
||||
|
||||
"""
|
||||
messages = result_data.messages
|
||||
if not messages:
|
||||
return []
|
||||
records = []
|
||||
for message in messages:
|
||||
message_dict = message if isinstance(message, dict) else message.model_dump()
|
||||
if get_final_results_only:
|
||||
result_data_dict = result_data.model_dump()
|
||||
results = result_data_dict.get("results", {})
|
||||
inner_result = results.get("result", {})
|
||||
record = Record(data={"result": inner_result, "message": message_dict}, text_key="result")
|
||||
records.append(record)
|
||||
return records
|
||||
|
||||
|
||||
def format_flow_output_records(records: List[Record]) -> str:
|
||||
"""
|
||||
Format the flow output records into a string.
|
||||
|
||||
Args:
|
||||
records (List[Record]): The list of records to format.
|
||||
|
||||
Returns:
|
||||
str: The formatted flow output records.
|
||||
|
||||
"""
|
||||
result = "Flow run output:\n"
|
||||
results = "\n".join([record.result for record in records if record.data["message"]])
|
||||
return result + results
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
from typing import Optional, Union
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.helpers.record import records_to_text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.memory import store_message
|
||||
from langflow.schema import Record
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from typing import Optional
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.helpers.record import records_to_text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Optional
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
117
src/backend/base/langflow/base/tools/flow_tool.py
Normal file
117
src/backend/base/langflow/base/tools/flow_tool.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
from typing import Any, List, Optional, Type
|
||||
|
||||
from asyncer import syncify
|
||||
from langchain.tools import BaseTool
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langchain_core.tools import ToolException
|
||||
from pydantic.v1 import BaseModel
|
||||
|
||||
from langflow.base.flow_processing.utils import build_records_from_result_data, format_flow_output_records
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.helpers.flow import build_schema_from_inputs, get_arg_names, get_flow_inputs, run_flow
|
||||
|
||||
|
||||
class FlowTool(BaseTool):
|
||||
name: str
|
||||
description: str
|
||||
graph: Optional[Graph] = None
|
||||
flow_id: Optional[str] = None
|
||||
user_id: Optional[str] = None
|
||||
inputs: List["Vertex"] = []
|
||||
get_final_results_only: bool = True
|
||||
|
||||
@property
|
||||
def args(self) -> dict:
|
||||
schema = self.get_input_schema()
|
||||
return schema.schema()["properties"]
|
||||
|
||||
def get_input_schema(self, config: Optional[RunnableConfig] = None) -> Type[BaseModel]:
|
||||
"""The tool's input schema."""
|
||||
if self.args_schema is not None:
|
||||
return self.args_schema
|
||||
elif self.graph is not None:
|
||||
return build_schema_from_inputs(self.name, get_flow_inputs(self.graph))
|
||||
else:
|
||||
raise ToolException("No input schema available.")
|
||||
|
||||
def _run(
|
||||
self,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""Use the tool."""
|
||||
args_names = get_arg_names(self.inputs)
|
||||
if len(args_names) == len(args):
|
||||
kwargs = {arg["arg_name"]: arg_value for arg, arg_value in zip(args_names, args)}
|
||||
elif len(args_names) != len(args) and len(args) != 0:
|
||||
raise ToolException(
|
||||
"Number of arguments does not match the number of inputs. Pass keyword arguments instead."
|
||||
)
|
||||
tweaks = {arg["component_name"]: kwargs[arg["arg_name"]] for arg in args_names}
|
||||
|
||||
run_outputs = syncify(run_flow, raise_sync_error=False)(
|
||||
tweaks={key: {"input_value": value} for key, value in tweaks.items()},
|
||||
flow_id=self.flow_id,
|
||||
user_id=self.user_id,
|
||||
)
|
||||
if not run_outputs:
|
||||
return "No output"
|
||||
run_output = run_outputs[0]
|
||||
|
||||
records = []
|
||||
if run_output is not None:
|
||||
for output in run_output.outputs:
|
||||
if output:
|
||||
records.extend(
|
||||
build_records_from_result_data(output, get_final_results_only=self.get_final_results_only)
|
||||
)
|
||||
return format_flow_output_records(records)
|
||||
|
||||
def validate_inputs(self, args_names: List[dict[str, str]], args: Any, kwargs: Any):
|
||||
"""Validate the inputs."""
|
||||
|
||||
if len(args) > 0 and len(args) != len(args_names):
|
||||
raise ToolException(
|
||||
"Number of positional arguments does not match the number of inputs. Pass keyword arguments instead."
|
||||
)
|
||||
|
||||
if len(args) == len(args_names):
|
||||
kwargs = {arg_name["arg_name"]: arg_value for arg_name, arg_value in zip(args_names, args)}
|
||||
|
||||
missing_args = [arg["arg_name"] for arg in args_names if arg["arg_name"] not in kwargs]
|
||||
if missing_args:
|
||||
raise ToolException(f"Missing required arguments: {', '.join(missing_args)}")
|
||||
|
||||
return kwargs
|
||||
|
||||
def build_tweaks_dict(self, args, kwargs):
|
||||
args_names = get_arg_names(self.inputs)
|
||||
kwargs = self.validate_inputs(args_names=args_names, args=args, kwargs=kwargs)
|
||||
tweaks = {arg["component_name"]: kwargs[arg["arg_name"]] for arg in args_names}
|
||||
return tweaks
|
||||
|
||||
async def _arun(
|
||||
self,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""Use the tool asynchronously."""
|
||||
tweaks = self.build_tweaks_dict(args, kwargs)
|
||||
run_outputs = await run_flow(
|
||||
tweaks={key: {"input_value": value} for key, value in tweaks.items()},
|
||||
flow_id=self.flow_id,
|
||||
user_id=self.user_id,
|
||||
)
|
||||
if not run_outputs:
|
||||
return "No output"
|
||||
run_output = run_outputs[0]
|
||||
|
||||
records = []
|
||||
if run_output is not None:
|
||||
for output in run_output.outputs:
|
||||
if output:
|
||||
records.extend(
|
||||
build_records_from_result_data(output, get_final_results_only=self.get_final_results_only)
|
||||
)
|
||||
return format_flow_output_records(records)
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
from typing import Callable, List, Optional, Union
|
||||
|
||||
from langchain.agents import AgentExecutor, AgentType, initialize_agent, types
|
||||
|
||||
from langflow.field_typing import BaseChatMemory, BaseLanguageModel, Tool
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class AgentInitializerComponent(CustomComponent):
|
||||
display_name: str = "Agent Initializer"
|
||||
description: str = "Initialize a Langchain Agent."
|
||||
documentation: str = "https://python.langchain.com/docs/modules/agents/agent_types/"
|
||||
|
||||
def build_config(self):
|
||||
agents = list(types.AGENT_TO_CLASS.keys())
|
||||
# field_type and required are optional
|
||||
return {
|
||||
"agent": {"options": agents, "value": agents[0], "display_name": "Agent Type"},
|
||||
"max_iterations": {"display_name": "Max Iterations", "value": 10},
|
||||
"memory": {"display_name": "Memory"},
|
||||
"tools": {"display_name": "Tools"},
|
||||
"llm": {"display_name": "Language Model"},
|
||||
"code": {"advanced": True},
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
agent: str,
|
||||
llm: BaseLanguageModel,
|
||||
tools: List[Tool],
|
||||
max_iterations: int,
|
||||
memory: Optional[BaseChatMemory] = None,
|
||||
) -> Union[AgentExecutor, Callable]:
|
||||
agent = AgentType(agent)
|
||||
if memory:
|
||||
return initialize_agent(
|
||||
tools=tools,
|
||||
llm=llm,
|
||||
agent=agent,
|
||||
memory=memory,
|
||||
return_intermediate_steps=True,
|
||||
handle_parsing_errors=True,
|
||||
max_iterations=max_iterations,
|
||||
)
|
||||
return initialize_agent(
|
||||
tools=tools,
|
||||
llm=llm,
|
||||
agent=agent,
|
||||
return_intermediate_steps=True,
|
||||
handle_parsing_errors=True,
|
||||
max_iterations=max_iterations,
|
||||
)
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
from langchain.agents import AgentExecutor
|
||||
from langchain_community.agent_toolkits import create_json_agent
|
||||
from langchain_community.agent_toolkits.json.toolkit import JsonToolkit
|
||||
|
||||
from langflow.field_typing import (
|
||||
BaseLanguageModel,
|
||||
)
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_community.agent_toolkits import create_json_agent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
|
||||
|
||||
class JsonAgentComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from langchain.agents.agent import AgentExecutor
|
||||
from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import _get_default_system_message
|
||||
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
|
||||
from langchain.memory.token_buffer import ConversationTokenBufferMemory
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from langflow.field_typing.range_spec import RangeSpec
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from pydantic.v1 import SecretStr
|
||||
from langchain_core.memory import BaseMemory
|
||||
from langchain_core.prompts import MessagesPlaceholder, SystemMessagePromptTemplate
|
||||
from langchain_core.tools import Tool
|
||||
|
||||
|
||||
class ConversationalAgent(CustomComponent):
|
||||
display_name: str = "OpenAI Conversational Agent"
|
||||
description: str = "Conversational Agent that can use OpenAI's function calling API"
|
||||
icon = "OpenAI"
|
||||
|
||||
def build_config(self):
|
||||
openai_function_models = [
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo-1106",
|
||||
]
|
||||
return {
|
||||
"tools": {"display_name": "Tools"},
|
||||
"memory": {"display_name": "Memory"},
|
||||
"system_message": {"display_name": "System Message"},
|
||||
"max_token_limit": {"display_name": "Max Token Limit"},
|
||||
"model_name": {
|
||||
"display_name": "Model Name",
|
||||
"options": openai_function_models,
|
||||
"value": openai_function_models[0],
|
||||
},
|
||||
"code": {"show": False},
|
||||
"temperature": {
|
||||
"display_name": "Temperature",
|
||||
"value": 0.2,
|
||||
"rangeSpec": RangeSpec(min=0, max=2, step=0.1),
|
||||
},
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
model_name: str,
|
||||
openai_api_key: str,
|
||||
tools: List[Tool],
|
||||
openai_api_base: Optional[str] = None,
|
||||
memory: Optional[BaseMemory] = None,
|
||||
system_message: Optional[SystemMessagePromptTemplate] = None,
|
||||
max_token_limit: int = 2000,
|
||||
temperature: float = 0.9,
|
||||
) -> AgentExecutor:
|
||||
if openai_api_key:
|
||||
api_key = SecretStr(openai_api_key)
|
||||
else:
|
||||
api_key = None
|
||||
|
||||
llm = ChatOpenAI(
|
||||
model=model_name,
|
||||
api_key=api_key,
|
||||
base_url=openai_api_base,
|
||||
max_tokens=max_token_limit,
|
||||
temperature=temperature,
|
||||
)
|
||||
if not memory:
|
||||
memory_key = "chat_history"
|
||||
memory = ConversationTokenBufferMemory(
|
||||
memory_key=memory_key,
|
||||
return_messages=True,
|
||||
output_key="output",
|
||||
llm=llm,
|
||||
max_token_limit=max_token_limit,
|
||||
)
|
||||
else:
|
||||
memory_key = memory.memory_key # type: ignore
|
||||
|
||||
_system_message = system_message or _get_default_system_message()
|
||||
prompt = OpenAIFunctionsAgent.create_prompt(
|
||||
system_message=_system_message, # type: ignore
|
||||
extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)],
|
||||
)
|
||||
agent = OpenAIFunctionsAgent(
|
||||
llm=llm,
|
||||
tools=tools,
|
||||
prompt=prompt, # type: ignore
|
||||
)
|
||||
return AgentExecutor(
|
||||
agent=agent,
|
||||
tools=tools, # type: ignore
|
||||
memory=memory,
|
||||
verbose=True,
|
||||
return_intermediate_steps=True,
|
||||
handle_parsing_errors=True,
|
||||
)
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
from typing import Callable, Union
|
||||
|
||||
from langchain.agents import AgentExecutor
|
||||
from langchain_community.utilities import SQLDatabase
|
||||
from langchain_community.agent_toolkits import SQLDatabaseToolkit
|
||||
from langchain_community.agent_toolkits.sql.base import create_sql_agent
|
||||
from langchain_community.utilities import SQLDatabase
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class SQLAgentComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from typing import Callable, Union
|
|||
from langchain.agents import AgentExecutor, create_vectorstore_agent
|
||||
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreToolkit
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class VectorStoreAgentComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from langchain.agents import create_vectorstore_router_agent
|
|||
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreRouterToolkit
|
||||
from langchain_core.language_models.base import BaseLanguageModel
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class VectorStoreRouterAgentComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
from .AgentInitializer import AgentInitializerComponent
|
||||
from .CSVAgent import CSVAgentComponent
|
||||
from .JsonAgent import JsonAgentComponent
|
||||
from .OpenAIConversationalAgent import ConversationalAgent
|
||||
from .SQLAgent import SQLAgentComponent
|
||||
from .VectorStoreAgent import VectorStoreAgentComponent
|
||||
from .VectorStoreRouterAgent import VectorStoreRouterAgentComponent
|
||||
from .XMLAgent import XMLAgentComponent
|
||||
|
||||
__all__ = [
|
||||
"AgentInitializerComponent",
|
||||
"CSVAgentComponent",
|
||||
"JsonAgentComponent",
|
||||
"ConversationalAgent",
|
||||
"SQLAgentComponent",
|
||||
"VectorStoreAgentComponent",
|
||||
"VectorStoreRouterAgentComponent",
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from typing import Optional
|
|||
|
||||
from langchain.chains import ConversationChain
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class ConversationChainComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain.chains.llm import LLMChain
|
||||
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.prompts import PromptTemplate
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
|
||||
|
||||
|
||||
class LLMChainComponent(CustomComponent):
|
||||
display_name = "LLMChain"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from langchain.chains import LLMCheckerChain
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class LLMCheckerChainComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from typing import Optional
|
|||
|
||||
from langchain.chains import LLMChain, LLMMathChain
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class LLMMathChainComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from typing import Optional
|
|||
from langchain.chains.retrieval_qa.base import RetrievalQA
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, BaseRetriever, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from typing import Optional
|
|||
from langchain.chains import RetrievalQAWithSourcesChain
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, BaseRetriever, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class RetrievalQAWithSourcesChainComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from langchain_community.utilities.sql_database import SQLDatabase
|
|||
from langchain_core.prompts import PromptTemplate
|
||||
from langchain_core.runnables import Runnable
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class SQLGeneratorComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import asyncio
|
||||
import json
|
||||
from typing import List, Optional
|
||||
from typing import Any, List, Optional
|
||||
|
||||
import httpx
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from loguru import logger
|
||||
|
||||
from langflow.base.curl.parse import parse_context
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import NestedDict
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.dotdict import dotdict
|
||||
|
||||
|
||||
class APIRequest(CustomComponent):
|
||||
|
|
@ -16,10 +21,15 @@ class APIRequest(CustomComponent):
|
|||
|
||||
field_config = {
|
||||
"urls": {"display_name": "URLs", "info": "URLs to make requests to."},
|
||||
"curl": {
|
||||
"display_name": "Curl",
|
||||
"info": "Paste a curl command to populate the fields.",
|
||||
"refresh_button": True,
|
||||
"refresh_button_text": "",
|
||||
},
|
||||
"method": {
|
||||
"display_name": "Method",
|
||||
"info": "The HTTP method to use.",
|
||||
"field_type": "str",
|
||||
"options": ["GET", "POST", "PATCH", "PUT"],
|
||||
"value": "GET",
|
||||
},
|
||||
|
|
@ -35,12 +45,33 @@ class APIRequest(CustomComponent):
|
|||
},
|
||||
"timeout": {
|
||||
"display_name": "Timeout",
|
||||
"field_type": "int",
|
||||
"info": "The timeout to use for the request.",
|
||||
"value": 5,
|
||||
},
|
||||
}
|
||||
|
||||
def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:
|
||||
try:
|
||||
parsed = parse_context(curl)
|
||||
build_config["urls"]["value"] = [parsed.url]
|
||||
build_config["method"]["value"] = parsed.method.upper()
|
||||
build_config["headers"]["value"] = dict(parsed.headers)
|
||||
|
||||
try:
|
||||
json_data = json.loads(parsed.data)
|
||||
build_config["body"]["value"] = json_data
|
||||
except json.JSONDecodeError as e:
|
||||
print(e)
|
||||
except Exception as exc:
|
||||
logger.error(f"Error parsing curl: {exc}")
|
||||
raise ValueError(f"Error parsing curl: {exc}")
|
||||
return build_config
|
||||
|
||||
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
|
||||
if field_name == "curl" and field_value is not None:
|
||||
build_config = self.parse_curl(field_value, build_config)
|
||||
return build_config
|
||||
|
||||
async def make_request(
|
||||
self,
|
||||
client: httpx.AsyncClient,
|
||||
|
|
@ -93,21 +124,25 @@ class APIRequest(CustomComponent):
|
|||
self,
|
||||
method: str,
|
||||
urls: List[str],
|
||||
headers: Optional[Record] = None,
|
||||
body: Optional[Record] = None,
|
||||
curl: Optional[str] = None,
|
||||
headers: Optional[NestedDict] = {},
|
||||
body: Optional[NestedDict] = {},
|
||||
timeout: int = 5,
|
||||
) -> List[Record]:
|
||||
if headers is None:
|
||||
headers_dict = {}
|
||||
else:
|
||||
elif isinstance(headers, Record):
|
||||
headers_dict = headers.data
|
||||
else:
|
||||
headers_dict = headers
|
||||
|
||||
bodies = []
|
||||
if body:
|
||||
if isinstance(body, list):
|
||||
bodies = [b.data for b in body]
|
||||
if not isinstance(body, list):
|
||||
bodies = [body]
|
||||
else:
|
||||
bodies = [body.data]
|
||||
bodies = body
|
||||
bodies = [b.data if isinstance(b, Record) else b for b in bodies] # type: ignore
|
||||
|
||||
if len(urls) != len(bodies):
|
||||
# add bodies with None
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from langflow.base.data.utils import parallel_load_records, parse_text_file_to_record, retrieve_file_paths
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from pathlib import Path
|
|||
from typing import Any, Dict
|
||||
|
||||
from langflow.base.data.utils import TEXT_FILE_TYPES, parse_text_file_to_record
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Any, Dict
|
|||
|
||||
from langchain_community.document_loaders.web_base import WebBaseLoader
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
39
src/backend/base/langflow/components/data/Webhook.py
Normal file
39
src/backend/base/langflow/components/data/Webhook.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import json
|
||||
import uuid
|
||||
from typing import Any, Optional
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.dotdict import dotdict
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
class WebhookComponent(CustomComponent):
|
||||
display_name = "Webhook Input"
|
||||
description = "Defines a webhook input for the flow."
|
||||
|
||||
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
|
||||
if field_name == "webhook_id":
|
||||
build_config["webhook_id"]["value"] = uuid.uuid4().hex
|
||||
return build_config
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
"data": {
|
||||
"display_name": "Data",
|
||||
"info": "Use this field to quickly test the webhook component by providing a JSON payload.",
|
||||
"multiline": True,
|
||||
}
|
||||
}
|
||||
|
||||
def build(self, data: Optional[str] = "") -> Record:
|
||||
message = ""
|
||||
try:
|
||||
body = json.loads(data or "{}")
|
||||
except json.JSONDecodeError:
|
||||
body = {"payload": data}
|
||||
message = f"Invalid JSON payload. Please check the format.\n\n{data}"
|
||||
record = Record(data=body)
|
||||
if not message:
|
||||
message = json.dumps(body, indent=2)
|
||||
self.status = message
|
||||
return record
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
from .APIRequest import APIRequest
|
||||
from .Directory import DirectoryComponent
|
||||
from .File import FileComponent
|
||||
from .Webhook import WebhookComponent
|
||||
|
||||
from .URL import URLComponent
|
||||
|
||||
__all__ = ["APIRequest", "DirectoryComponent", "FileComponent", "URLComponent"]
|
||||
__all__ = ["APIRequest", "DirectoryComponent", "FileComponent", "URLComponent", "WebhookComponent"]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from typing import Optional
|
||||
from langchain_community.embeddings import BedrockEmbeddings
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_community.embeddings import BedrockEmbeddings
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class AmazonBedrockEmeddingsComponent(CustomComponent):
|
||||
display_name: str = "Amazon Bedrock Embeddings"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.embeddings import Embeddings
|
||||
from langchain_openai import AzureOpenAIEmbeddings
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class AzureOpenAIEmbeddingsComponent(CustomComponent):
|
||||
display_name: str = "Azure OpenAI Embeddings"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Dict, Optional
|
|||
|
||||
from langchain_community.embeddings.huggingface import HuggingFaceEmbeddings
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class HuggingFaceEmbeddingsComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from typing import Dict, Optional
|
|||
from langchain_community.embeddings.huggingface import HuggingFaceInferenceAPIEmbeddings
|
||||
from pydantic.v1.types import SecretStr
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class HuggingFaceInferenceAPIEmbeddingsComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from langchain_mistralai.embeddings import MistralAIEmbeddings
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langchain_mistralai.embeddings import MistralAIEmbeddings
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from typing import Optional
|
||||
from langchain_community.embeddings import OllamaEmbeddings
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_community.embeddings import OllamaEmbeddings
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class OllamaEmbeddingsComponent(CustomComponent):
|
||||
display_name: str = "Ollama Embeddings"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from typing import Any, Dict, List, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from langchain_openai.embeddings.base import OpenAIEmbeddings
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings, NestedDict
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class OpenAIEmbeddingsComponent(CustomComponent):
|
||||
|
|
@ -94,7 +94,6 @@ class OpenAIEmbeddingsComponent(CustomComponent):
|
|||
allowed_special: List[str] = [],
|
||||
disallowed_special: List[str] = ["all"],
|
||||
chunk_size: int = 1000,
|
||||
client: Optional[Any] = None,
|
||||
deployment: str = "text-embedding-ada-002",
|
||||
embedding_ctx_length: int = 8191,
|
||||
max_retries: int = 6,
|
||||
|
|
@ -126,7 +125,6 @@ class OpenAIEmbeddingsComponent(CustomComponent):
|
|||
allowed_special=set(allowed_special),
|
||||
disallowed_special="all",
|
||||
chunk_size=chunk_size,
|
||||
client=client,
|
||||
deployment=deployment,
|
||||
embedding_ctx_length=embedding_ctx_length,
|
||||
max_retries=max_retries,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import List, Optional
|
|||
|
||||
from langchain_google_vertexai import VertexAIEmbeddings
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class VertexAIEmbeddingsComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.memory import delete_messages, get_messages
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.base.tools.flow_tool import FlowTool
|
||||
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.helpers.flow import get_flow_inputs
|
||||
from langflow.schema.dotdict import dotdict
|
||||
from langflow.schema.schema import Record
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class FlowToolComponent(CustomComponent):
|
||||
|
|
@ -68,18 +68,20 @@ class FlowToolComponent(CustomComponent):
|
|||
}
|
||||
|
||||
async def build(self, flow_name: str, name: str, description: str, return_direct: bool = False) -> Tool:
|
||||
FlowTool.update_forward_refs()
|
||||
flow_record = self.get_flow(flow_name)
|
||||
if not flow_record:
|
||||
raise ValueError("Flow not found.")
|
||||
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,
|
||||
inputs = get_flow_inputs(graph)
|
||||
tool = FlowTool(
|
||||
name=name,
|
||||
description=description,
|
||||
graph=graph,
|
||||
return_direct=return_direct,
|
||||
args_schema=schema,
|
||||
inputs=inputs,
|
||||
flow_id=str(flow_record.id),
|
||||
user_id=str(self._user_id),
|
||||
)
|
||||
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()])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import List
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Optional
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from typing import Union
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class PassComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from typing import Callable
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.custom.utils import get_function
|
||||
from langflow.field_typing import Code
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.interface.custom.utils import get_function
|
||||
|
||||
|
||||
class PythonFunctionComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from typing import Any, List, Optional
|
||||
|
||||
from langflow.base.flow_processing.utils import build_records_from_run_outputs
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import NestedDict, Text
|
||||
from langflow.graph.schema import ResultData
|
||||
from langflow.graph.schema import RunOutputs
|
||||
from langflow.schema import Record, dotdict
|
||||
|
||||
|
||||
|
|
@ -39,28 +40,17 @@ class RunFlowComponent(CustomComponent):
|
|||
},
|
||||
}
|
||||
|
||||
def build_records_from_result_data(self, result_data: ResultData) -> List[Record]:
|
||||
messages = result_data.messages
|
||||
if not messages:
|
||||
return []
|
||||
records = []
|
||||
for message in messages:
|
||||
message_dict = message if isinstance(message, dict) else message.model_dump()
|
||||
record = Record(text=message_dict.get("text", ""), data={"result": result_data})
|
||||
records.append(record)
|
||||
return records
|
||||
|
||||
async def build(self, input_value: Text, flow_name: str, tweaks: NestedDict) -> List[Record]:
|
||||
results: List[Optional[ResultData]] = await self.run_flow(
|
||||
results: List[Optional[RunOutputs]] = await self.run_flow(
|
||||
inputs={"input_value": input_value}, flow_name=flow_name, tweaks=tweaks
|
||||
)
|
||||
if isinstance(results, list):
|
||||
records = []
|
||||
for result in results:
|
||||
if result:
|
||||
records.extend(self.build_records_from_result_data(result))
|
||||
records.extend(build_records_from_run_outputs(result))
|
||||
else:
|
||||
records = self.build_records_from_result_data(results)
|
||||
records = build_records_from_run_outputs()(results)
|
||||
|
||||
self.status = records
|
||||
return records
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from langchain_core.runnables import Runnable
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class RunnableExecComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool
|
||||
from langchain_community.utilities import SQLDatabase
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class SQLExecutorComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Optional
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema import Record
|
||||
from langflow.utils.util import unescape_string
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ class SplitTextComponent(CustomComponent):
|
|||
chunks = [chunk[:truncate_size] for chunk in chunks]
|
||||
|
||||
for chunk in chunks:
|
||||
outputs.append(Record(text=chunk, data={"parent": text}))
|
||||
outputs.append(Record(data={"parent": text, "text": chunk}))
|
||||
|
||||
self.status = outputs
|
||||
return outputs
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.memory import get_messages, store_message
|
||||
from langflow.schema import Record
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ from typing import Any, List, Optional
|
|||
|
||||
from loguru import logger
|
||||
|
||||
from langflow.base.flow_processing.utils import build_records_from_result_data
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.graph.schema import ResultData, RunOutputs
|
||||
from langflow.graph.schema import RunOutputs
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.helpers.flow import get_flow_inputs
|
||||
from langflow.schema import Record
|
||||
|
|
@ -92,21 +93,6 @@ class SubFlowComponent(CustomComponent):
|
|||
},
|
||||
}
|
||||
|
||||
def build_records_from_result_data(self, result_data: ResultData, get_final_results_only: bool) -> List[Record]:
|
||||
messages = result_data.messages
|
||||
if not messages:
|
||||
return []
|
||||
records = []
|
||||
for message in messages:
|
||||
message_dict = message if isinstance(message, dict) else message.model_dump()
|
||||
if get_final_results_only:
|
||||
result_data_dict = result_data.model_dump()
|
||||
results = result_data_dict.get("results", {})
|
||||
inner_result = results.get("result", {})
|
||||
record = Record(data={"result": inner_result, "message": message_dict}, text_key="result")
|
||||
records.append(record)
|
||||
return records
|
||||
|
||||
async def build(self, flow_name: str, get_final_results_only: bool = True, **kwargs) -> List[Record]:
|
||||
tweaks = {key: {"input_value": value} for key, value in kwargs.items()}
|
||||
run_outputs: List[Optional[RunOutputs]] = await self.run_flow(
|
||||
|
|
@ -121,7 +107,7 @@ class SubFlowComponent(CustomComponent):
|
|||
if run_output is not None:
|
||||
for output in run_output.outputs:
|
||||
if output:
|
||||
records.extend(self.build_records_from_result_data(output, get_final_results_only))
|
||||
records.extend(build_records_from_result_data(output, get_final_results_only))
|
||||
|
||||
self.status = records
|
||||
logger.debug(records)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from typing import Optional, Union
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema import Record
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class TextOperatorComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# from langflow.field_typing import Data
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class Component(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import List
|
|||
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import uuid
|
||||
from typing import Any, Optional
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class UUIDGeneratorComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.memory import get_messages
|
||||
from langflow.schema import Record
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.helpers.record import records_to_text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from langchain_core.prompts import PromptTemplate
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Prompt, TemplateField, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class PromptComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# We need to make sure this class is importable from the context where this code will be running.
|
||||
from langchain_community.utilities.bing_search import BingSearchAPIWrapper
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class BingSearchAPIWrapperComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Callable, Union
|
|||
|
||||
from langchain_community.utilities.google_search import GoogleSearchAPIWrapper
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class GoogleSearchAPIWrapperComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from typing import Dict
|
|||
# If this class does not exist, you would need to create it or import the appropriate class from another module
|
||||
from langchain_community.utilities.google_serper import GoogleSerperAPIWrapper
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class GoogleSerperAPIWrapperComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.services.database.models.base import orjson_dumps
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from langchain_experimental.sql.base import SQLDatabase
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class SQLDatabaseComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Dict, Optional
|
|||
|
||||
from langchain_community.utilities.searx_search import SearxSearchWrapper
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class SearxSearchWrapperComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Callable, Union
|
|||
|
||||
from langchain_community.utilities.serpapi import SerpAPIWrapper
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class SerpAPIWrapperComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Callable, Union
|
|||
|
||||
from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
# Assuming WikipediaAPIWrapper is a class that needs to be imported.
|
||||
# The import statement is not included as it is not provided in the JSON
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Callable, Union
|
|||
|
||||
from langchain_community.utilities.wolfram_alpha import WolframAlphaAPIWrapper
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
# Since all the fields in the JSON have show=False, we will only create a basic component
|
||||
# without any configurable fields.
|
||||
|
|
|
|||
|
|
@ -116,19 +116,18 @@ class ZepMessageReaderComponent(BaseMemoryComponent):
|
|||
url: Optional[Text] = None,
|
||||
api_key: Optional[Text] = None,
|
||||
query: Optional[Text] = None,
|
||||
search_scope: SearchScope = SearchScope.messages,
|
||||
search_type: SearchType = SearchType.similarity,
|
||||
search_scope: str = SearchScope.messages,
|
||||
search_type: str = SearchType.similarity,
|
||||
limit: Optional[int] = None,
|
||||
) -> list[Record]:
|
||||
try:
|
||||
from zep_python import ZepClient
|
||||
from zep_python.langchain import ZepChatMessageHistory
|
||||
|
||||
# Monkeypatch API_BASE_PATH to
|
||||
# avoid 404
|
||||
# This is a workaround for the local Zep instance
|
||||
# cloud Zep works with v2
|
||||
import zep_python.zep_client
|
||||
from zep_python import ZepClient
|
||||
from zep_python.langchain import ZepChatMessageHistory
|
||||
|
||||
zep_python.zep_client.API_BASE_PATH = api_base_path
|
||||
except ImportError:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from typing import Optional
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
|
||||
from langchain_community.llms.bedrock import Bedrock
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
|
||||
|
||||
class AmazonBedrockComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.language_models import BaseLanguageModel
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.language_models import BaseLanguageModel
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class ChatAntropicSpecsComponent(CustomComponent):
|
||||
|
|
@ -34,8 +35,8 @@ class ChatAntropicSpecsComponent(CustomComponent):
|
|||
},
|
||||
"max_tokens": {
|
||||
"display_name": "Max Tokens",
|
||||
"field_type": "int",
|
||||
"value": 256,
|
||||
"advanced": True,
|
||||
"info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
|
||||
},
|
||||
"temperature": {
|
||||
"display_name": "Temperature",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from typing import Optional
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.language_models import BaseLanguageModel
|
||||
from langchain_openai import AzureChatOpenAI
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class AzureChatOpenAISpecsComponent(CustomComponent):
|
||||
display_name: str = "AzureChatOpenAI"
|
||||
|
|
@ -65,11 +66,8 @@ class AzureChatOpenAISpecsComponent(CustomComponent):
|
|||
},
|
||||
"max_tokens": {
|
||||
"display_name": "Max Tokens",
|
||||
"value": 1000,
|
||||
"required": False,
|
||||
"field_type": "int",
|
||||
"advanced": True,
|
||||
"info": "Maximum number of tokens to generate.",
|
||||
"info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
|
||||
},
|
||||
"code": {"show": False},
|
||||
}
|
||||
|
|
@ -96,7 +94,7 @@ class AzureChatOpenAISpecsComponent(CustomComponent):
|
|||
api_version=api_version,
|
||||
api_key=azure_api_key,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
max_tokens=max_tokens or None,
|
||||
)
|
||||
except Exception as e:
|
||||
raise ValueError("Could not connect to AzureOpenAI API.") from e
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
|
||||
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class QianfanChatEndpointComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Optional
|
|||
|
||||
from langchain_community.llms.baidu_qianfan_endpoint import QianfanLLMEndpoint
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,9 +46,8 @@ class AnthropicLLM(CustomComponent):
|
|||
},
|
||||
"max_tokens": {
|
||||
"display_name": "Max Tokens",
|
||||
"field_type": "int",
|
||||
"advanced": True,
|
||||
"value": 256,
|
||||
"info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
|
||||
},
|
||||
"temperature": {
|
||||
"display_name": "Temperature",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from typing import Any, Dict, Optional
|
||||
|
||||
from langchain_community.chat_models.litellm import ChatLiteLLM, ChatLiteLLMException
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class ChatLiteLLMComponent(CustomComponent):
|
||||
|
|
@ -81,12 +82,9 @@ class ChatLiteLLMComponent(CustomComponent):
|
|||
"default": 1,
|
||||
},
|
||||
"max_tokens": {
|
||||
"display_name": "Max tokens",
|
||||
"field_type": "int",
|
||||
"advanced": False,
|
||||
"required": False,
|
||||
"default": 256,
|
||||
"info": "The maximum number of tokens to generate for each chat completion.",
|
||||
"display_name": "Max Tokens",
|
||||
"advanced": True,
|
||||
"info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
|
||||
},
|
||||
"max_retries": {
|
||||
"display_name": "Max retries",
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class MistralAIModelComponent(CustomComponent):
|
|||
output = ChatMistralAI(
|
||||
model_name=model,
|
||||
api_key=(SecretStr(mistral_api_key) if mistral_api_key else None),
|
||||
max_tokens=max_tokens,
|
||||
max_tokens=max_tokens or None,
|
||||
temperature=temperature,
|
||||
endpoint=mistral_api_base,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from typing import Any, Dict, List, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# from langchain_community.chat_models import ChatOllama
|
||||
from langchain_community.chat_models import ChatOllama
|
||||
from langchain_core.language_models.chat_models import BaseChatModel
|
||||
|
||||
# from langchain.chat_models import ChatOllama
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
# from langchain.callbacks.manager import CallbackManager
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ class ChatOllamaComponent(CustomComponent):
|
|||
num_ctx: Optional[int] = None,
|
||||
num_gpu: Optional[int] = None,
|
||||
format: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
metadata: Optional[Dict] = None,
|
||||
num_thread: Optional[int] = None,
|
||||
repeat_penalty: Optional[float] = None,
|
||||
stop: Optional[List[str]] = None,
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ from typing import Optional
|
|||
from langchain_openai import ChatOpenAI
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
|
||||
from langflow.base.models.openai_constants import MODEL_NAMES
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, NestedDict
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class ChatOpenAIComponent(CustomComponent):
|
||||
|
|
@ -18,8 +17,8 @@ class ChatOpenAIComponent(CustomComponent):
|
|||
return {
|
||||
"max_tokens": {
|
||||
"display_name": "Max Tokens",
|
||||
"advanced": False,
|
||||
"required": False,
|
||||
"advanced": True,
|
||||
"info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
|
||||
},
|
||||
"model_kwargs": {
|
||||
"display_name": "Model Kwargs",
|
||||
|
|
@ -52,7 +51,7 @@ class ChatOpenAIComponent(CustomComponent):
|
|||
|
||||
def build(
|
||||
self,
|
||||
max_tokens: Optional[int] = 256,
|
||||
max_tokens: Optional[int] = 0,
|
||||
model_kwargs: NestedDict = {},
|
||||
model_name: str = "gpt-4o",
|
||||
openai_api_base: Optional[str] = None,
|
||||
|
|
@ -66,7 +65,7 @@ class ChatOpenAIComponent(CustomComponent):
|
|||
else:
|
||||
api_key = None
|
||||
return ChatOpenAI(
|
||||
max_tokens=max_tokens,
|
||||
max_tokens=max_tokens or None,
|
||||
model_kwargs=model_kwargs,
|
||||
model=model_name,
|
||||
base_url=openai_api_base,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from langchain_community.chat_models.vertexai import ChatVertexAI
|
||||
from langchain_core.messages.base import BaseMessage
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class ChatVertexAIComponent(CustomComponent):
|
||||
|
|
@ -65,7 +64,6 @@ class ChatVertexAIComponent(CustomComponent):
|
|||
self,
|
||||
credentials: Optional[str],
|
||||
project: str,
|
||||
examples: Optional[List[BaseMessage]] = [],
|
||||
location: str = "us-central1",
|
||||
max_output_tokens: int = 128,
|
||||
model_name: str = "chat-bison",
|
||||
|
|
@ -76,7 +74,6 @@ class ChatVertexAIComponent(CustomComponent):
|
|||
) -> BaseLanguageModel:
|
||||
return ChatVertexAI(
|
||||
credentials=credentials,
|
||||
examples=examples,
|
||||
location=location,
|
||||
max_output_tokens=max_output_tokens,
|
||||
model_name=model_name,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
from langchain_community.llms.cohere import Cohere
|
||||
from langchain_core.language_models.base import BaseLanguageModel
|
||||
from typing import Optional
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_cohere import ChatCohere
|
||||
from langchain_core.language_models.base import BaseLanguageModel
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
|
||||
|
||||
class CohereComponent(CustomComponent):
|
||||
|
|
@ -13,14 +16,22 @@ class CohereComponent(CustomComponent):
|
|||
def build_config(self):
|
||||
return {
|
||||
"cohere_api_key": {"display_name": "Cohere API Key", "type": "password", "password": True},
|
||||
"max_tokens": {"display_name": "Max Tokens", "default": 256, "type": "int", "show": True},
|
||||
"max_tokens": {
|
||||
"display_name": "Max Tokens",
|
||||
"advanced": True,
|
||||
"info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
|
||||
},
|
||||
"temperature": {"display_name": "Temperature", "default": 0.75, "type": "float", "show": True},
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
cohere_api_key: str,
|
||||
max_tokens: int = 256,
|
||||
max_tokens: Optional[int] = 256,
|
||||
temperature: float = 0.75,
|
||||
) -> BaseLanguageModel:
|
||||
return Cohere(cohere_api_key=cohere_api_key, max_tokens=max_tokens, temperature=temperature) # type: ignore
|
||||
if cohere_api_key:
|
||||
api_key = SecretStr(cohere_api_key)
|
||||
else:
|
||||
api_key = None
|
||||
return ChatCohere(cohere_api_key=api_key, max_tokens=max_tokens or None, temperature=temperature) # type: ignore
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from typing import Optional
|
|||
from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore
|
||||
from pydantic.v1.types import SecretStr
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, RangeSpec
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
|
||||
class GoogleGenerativeAIComponent(CustomComponent):
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue