Merge remote-tracking branch 'origin/dev' into feature/output_dropdown

This commit is contained in:
anovazzi1 2024-05-24 00:17:41 -03:00
commit bd4f4c8724
318 changed files with 14679 additions and 9306 deletions

View file

@ -131,3 +131,4 @@ dmypy.json
# Pyre type checker
.pyre/
*.db

View file

@ -2,9 +2,9 @@ import platform
import socket
import sys
import time
import warnings
from pathlib import Path
from typing import Optional
import warnings
import click
import httpx
@ -17,8 +17,10 @@ from rich import print as rprint
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
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.utils import initialize_services
@ -432,17 +434,57 @@ def superuser(
# Verify that the superuser was created
from langflow.services.database.models.user.model import User
user: User = session.query(User).filter(User.username == username).first()
user: User = session.exec(select(User).where(User.username == username)).first()
if user is None or not user.is_superuser:
typer.echo("Superuser creation failed.")
return
# Now create the first folder for the user
result = create_default_folder_if_it_doesnt_exist(session, user.id)
if result:
typer.echo("Default folder created successfully.")
else:
raise RuntimeError("Could not create default folder.")
typer.echo("Superuser created successfully.")
else:
typer.echo("Superuser creation failed.")
# command to copy the langflow database from the cache to the current directory
# because now the database is stored per installation
@app.command()
def copy_db():
"""
Copy the database files to the current directory.
This function copies the 'langflow.db' and 'langflow-pre.db' files from the cache directory to the current directory.
If the files exist in the cache directory, they will be copied to the same directory as this script (__main__.py).
Returns:
None
"""
import shutil
from platformdirs import user_cache_dir
cache_dir = Path(user_cache_dir("langflow"))
db_path = cache_dir / "langflow.db"
pre_db_path = cache_dir / "langflow-pre.db"
# It should be copied to the current directory
# this file is __main__.py and it should be in the same directory as the database
destination_folder = Path(__file__).parent
if db_path.exists():
shutil.copy(db_path, destination_folder)
typer.echo(f"Database copied to {destination_folder}")
else:
typer.echo("Database not found in the cache directory.")
if pre_db_path.exists():
shutil.copy(pre_db_path, destination_folder)
typer.echo(f"Pre-release database copied to {destination_folder}")
else:
typer.echo("Pre-release database not found in the cache directory.")
@app.command()
def migration(
test: bool = typer.Option(True, help="Run migrations in test mode."),

View file

@ -0,0 +1,78 @@
"""Add Folder table
Revision ID: 012fb73ac359
Revises: c153816fd85f
Create Date: 2024-05-07 12:52:16.954691
"""
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 = "012fb73ac359"
down_revision: Union[str, None] = "c153816fd85f"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "folder" not in table_names:
op.create_table(
"folder",
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
sa.Column("parent_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["parent_id"],
["folder.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["user.id"],
),
sa.PrimaryKeyConstraint("id"),
)
indexes = inspector.get_indexes("folder")
if "ix_folder_name" not in [index["name"] for index in indexes]:
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:
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"])
batch_op.drop_column("folder")
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "folder_id" in inspector.get_columns("flow"):
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.add_column(sa.Column("folder", sa.VARCHAR(), nullable=True))
batch_op.drop_constraint("flow_folder_id_fkey", type_="foreignkey")
batch_op.drop_column("folder_id")
indexes = inspector.get_indexes("folder")
if "ix_folder_name" in [index["name"] for index in indexes]:
with op.batch_alter_table("folder", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_folder_name"))
if "folder" in table_names:
op.drop_table("folder")
# ### end Alembic commands ###

View file

@ -0,0 +1,43 @@
"""Add missing index
Revision ID: 29fe8f1f806b
Revises: 012fb73ac359
Create Date: 2024-05-21 09:23:48.772367
"""
from typing import Sequence, Union
from alembic import op
from sqlalchemy.engine.reflection import Inspector
revision: str = "29fe8f1f806b"
down_revision: Union[str, None] = "012fb73ac359"
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 = inspector.get_indexes("flow")
with op.batch_alter_table("flow", schema=None) as batch_op:
indexes_names = [index["name"] for index in indexes]
if "ix_flow_folder_id" not in indexes_names:
batch_op.create_index(batch_op.f("ix_flow_folder_id"), ["folder_id"], unique=False)
# ### 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 = inspector.get_indexes("flow")
with op.batch_alter_table("flow", schema=None) as batch_op:
indexes_names = [index["name"] for index in indexes]
if "ix_flow_folder_id" in indexes_names:
batch_op.drop_index(batch_op.f("ix_flow_folder_id"))
# ### end Alembic commands ###

View file

@ -13,6 +13,7 @@ from langflow.api.v1 import (
users_router,
validate_router,
variables_router,
folders_router,
)
router = APIRouter(
@ -29,3 +30,4 @@ router.include_router(login_router)
router.include_router(variables_router)
router.include_router(files_router)
router.include_router(monitor_router)
router.include_router(folders_router)

View file

@ -140,7 +140,10 @@ def get_file_path_value(file_path):
# If the path is not in the cache dir, return empty string
# This is to prevent access to files outside the cache dir
# If the path is not a file, return empty string
if not path.exists() or not str(path).startswith(user_cache_dir("langflow", "langflow")):
if not str(path).startswith(user_cache_dir("langflow", "langflow")):
return ""
if not path.exists():
return ""
return file_path

View file

@ -9,6 +9,7 @@ from langflow.api.v1.store import router as store_router
from langflow.api.v1.users import router as users_router
from langflow.api.v1.validate import router as validate_router
from langflow.api.v1.variable import router as variables_router
from langflow.api.v1.folders import router as folders_router
__all__ = [
"chat_router",
@ -22,4 +23,5 @@ __all__ = [
"variables_router",
"monitor_router",
"files_router",
"folders_router",
]

View file

@ -1,13 +1,12 @@
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from uuid import UUID
from langchain.schema import AgentAction, AgentFinish
from langchain_core.callbacks.base import AsyncCallbackHandler
from loguru import logger
from langflow.api.v1.schemas import ChatResponse, PromptResponse
from langflow.services.deps import get_chat_service, get_socket_service
from langflow.utils.util import remove_ansi_escape_codes
from langchain_core.agents import AgentAction, AgentFinish
if TYPE_CHECKING:
from langflow.services.socket.service import SocketIOService

View file

@ -29,7 +29,7 @@ from langflow.services.deps import get_chat_service, get_session, get_session_se
from langflow.services.monitor.utils import log_vertex_build
if TYPE_CHECKING:
from langflow.graph.vertex.types import ChatVertex
from langflow.graph.vertex.types import InterfaceVertex
from langflow.services.session.service import SessionService
router = APIRouter(tags=["Chat"])
@ -53,7 +53,7 @@ async def try_running_celery_task(vertex, user_id):
@router.post("/build/{flow_id}/vertices", response_model=VerticesOrderResponse)
async def retrieve_vertices_order(
flow_id: str,
flow_id: uuid.UUID,
data: Optional[Annotated[Optional[FlowDataRequest], Body(embed=True)]] = None,
stop_component_id: Optional[str] = None,
start_component_id: Optional[str] = None,
@ -78,12 +78,13 @@ async def retrieve_vertices_order(
HTTPException: If there is an error checking the build status.
"""
try:
flow_id_str = str(flow_id)
# First, we need to check if the flow_id is in the cache
if not data:
graph = await build_and_cache_graph_from_db(flow_id=flow_id, session=session, chat_service=chat_service)
graph = await build_and_cache_graph_from_db(flow_id=flow_id_str, session=session, chat_service=chat_service)
else:
graph = await build_and_cache_graph_from_data(
flow_id=flow_id, graph_data=data.model_dump(), chat_service=chat_service
flow_id=flow_id_str, graph_data=data.model_dump(), chat_service=chat_service
)
graph.validate_stream()
if stop_component_id or start_component_id:
@ -119,7 +120,7 @@ async def retrieve_vertices_order(
@router.post("/build/{flow_id}/vertices/{vertex_id}")
async def build_vertex(
flow_id: str,
flow_id: uuid.UUID,
vertex_id: str,
background_tasks: BackgroundTasks,
inputs: Annotated[Optional[InputValueRequest], Body(embed=True)] = None,
@ -143,27 +144,25 @@ async def build_vertex(
HTTPException: If there is an error building the vertex.
"""
flow_id_str = str(flow_id)
start_time = time.perf_counter()
next_runnable_vertices = []
top_level_vertices = []
try:
start_time = time.perf_counter()
cache = await chat_service.get_cache(flow_id)
cache = await chat_service.get_cache(flow_id_str)
if not cache:
# If there's no cache
logger.warning(f"No cache found for {flow_id}. Building graph starting at {vertex_id}")
logger.warning(f"No cache found for {flow_id_str}. Building graph starting at {vertex_id}")
graph = await build_and_cache_graph_from_db(
flow_id=flow_id, session=next(get_session()), chat_service=chat_service
flow_id=flow_id_str, session=next(get_session()), chat_service=chat_service
)
else:
graph = cache.get("result")
result_data_response = ResultDataResponse(results={})
duration = ""
vertex = graph.get_vertex(vertex_id)
try:
lock = chat_service._cache_locks[flow_id]
set_cache_coro = partial(chat_service.set_cache, flow_id=flow_id)
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,
@ -189,13 +188,13 @@ async def build_vertex(
artifacts = {}
# If there's an error building the vertex
# we need to clear the cache
await chat_service.clear_cache(flow_id)
await chat_service.clear_cache(flow_id_str)
# Log the vertex build
if not vertex.will_stream:
background_tasks.add_task(
log_vertex_build,
flow_id=flow_id,
flow_id=flow_id_str,
vertex_id=vertex_id,
valid=valid,
params=params,
@ -212,7 +211,7 @@ async def build_vertex(
inactivated_vertices = list(graph.inactivated_vertices)
graph.reset_inactivated_vertices()
graph.reset_activated_vertices()
await chat_service.set_cache(flow_id, graph)
await chat_service.set_cache(flow_id_str, graph)
# graph.stop_vertex tells us if the user asked
# to stop the build of the graph at a certain vertex
@ -240,7 +239,7 @@ async def build_vertex(
@router.get("/build/{flow_id}/{vertex_id}/stream", response_class=StreamingResponse)
async def build_vertex_stream(
flow_id: str,
flow_id: uuid.UUID,
vertex_id: str,
session_id: Optional[str] = None,
chat_service: "ChatService" = Depends(get_chat_service),
@ -272,23 +271,24 @@ async def build_vertex_stream(
HTTPException: If an error occurs while building the vertex.
"""
try:
flow_id_str = str(flow_id)
async def stream_vertex():
try:
if not session_id:
cache = await chat_service.get_cache(flow_id)
cache = await chat_service.get_cache(flow_id_str)
if not cache:
# If there's no cache
raise ValueError(f"No cache found for {flow_id}.")
raise ValueError(f"No cache found for {flow_id_str}.")
else:
graph = cache.get("result")
else:
session_data = await session_service.load_session(session_id, flow_id=flow_id)
session_data = await session_service.load_session(session_id, flow_id=flow_id_str)
graph, artifacts = session_data if session_data else (None, None)
if not graph:
raise ValueError(f"No graph found for {flow_id}.")
raise ValueError(f"No graph found for {flow_id_str}.")
vertex: "ChatVertex" = graph.get_vertex(vertex_id)
vertex: "InterfaceVertex" = graph.get_vertex(vertex_id)
if not hasattr(vertex, "stream"):
raise ValueError(f"Vertex {vertex_id} does not support streaming")
if isinstance(vertex._built_result, str) and vertex._built_result:

View file

@ -1,5 +1,6 @@
from http import HTTPStatus
from typing import Annotated, List, Optional, Union
from uuid import UUID
import sqlalchemy as sa
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
@ -18,9 +19,7 @@ from langflow.api.v1.schemas import (
UploadFileResponse,
)
from langflow.graph.graph.base import Graph
from langflow.graph.schema import RunOutputs
from langflow.interface.custom.custom_component import CustomComponent
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.interface.custom.utils import build_custom_component_template
from langflow.processing.process import process_tweaks, run_graph_internal
from langflow.schema.graph import Tweaks
@ -54,7 +53,7 @@ def get_all(
@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
async def simplified_run_flow(
db: Annotated[Session, Depends(get_session)],
flow_id: str,
flow_id: UUID,
input_request: SimplifiedAPIRequest = SimplifiedAPIRequest(),
stream: bool = False,
api_key_user: User = Depends(api_key_security),
@ -111,26 +110,26 @@ async def simplified_run_flow(
session_id = input_request.session_id
try:
task_result: List[RunOutputs] = []
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)
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).where(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} not found")
raise ValueError(f"Flow {flow_id_str} not found")
if flow.data is None:
raise ValueError(f"Flow {flow_id} has no data")
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, user_id=str(api_key_user.id))
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)
]
@ -153,7 +152,7 @@ async def simplified_run_flow(
]
task_result, session_id = await run_graph_internal(
graph=graph,
flow_id=flow_id,
flow_id=flow_id_str,
session_id=input_request.session_id,
inputs=inputs,
outputs=outputs,
@ -166,12 +165,12 @@ async def simplified_run_flow(
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} is not a valid UUID")
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} not found" in str(exc):
logger.error(f"Flow {flow_id} not found")
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")
@ -187,7 +186,7 @@ async def simplified_run_flow(
@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)],
flow_id: str,
flow_id: UUID,
inputs: Optional[List[InputValueRequest]] = [InputValueRequest(components=[], input_value="")],
outputs: Optional[List[str]] = [],
tweaks: Annotated[Optional[Tweaks], Body(embed=True)] = None, # noqa: F821
@ -235,31 +234,33 @@ async def experimental_run_flow(
This endpoint facilitates complex flow executions with customized inputs, outputs, and configurations, catering to diverse application requirements.
"""
try:
flow_id_str = str(flow_id)
if outputs is None:
outputs = []
task_result: List[RunOutputs] = []
artifacts = {}
if session_id:
session_data = await session_service.load_session(session_id, flow_id=flow_id)
session_data = await session_service.load_session(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 {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 = session.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first()
flow = session.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} not found")
raise ValueError(f"Flow {flow_id_str} not found")
if flow.data is None:
raise ValueError(f"Flow {flow_id} has no data")
raise ValueError(f"Flow {flow_id_str} has no data")
graph_data = flow.data
graph_data = process_tweaks(graph_data, tweaks or {})
graph = Graph.from_payload(graph_data, flow_id=flow_id)
graph = Graph.from_payload(graph_data, flow_id=flow_id_str)
task_result, session_id = await run_graph_internal(
graph=graph,
flow_id=flow_id,
flow_id=flow_id_str,
session_id=session_id,
inputs=inputs,
outputs=outputs,
@ -272,12 +273,12 @@ async def experimental_run_flow(
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} is not a valid UUID")
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} not found" in str(exc):
logger.error(f"Flow {flow_id} not found")
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")
@ -357,13 +358,14 @@ async def get_task_status(task_id: str):
)
async def create_upload_file(
file: UploadFile,
flow_id: str,
flow_id: UUID,
):
try:
file_path = save_uploaded_file(file, folder_name=flow_id)
flow_id_str = str(flow_id)
file_path = save_uploaded_file(file, folder_name=flow_id_str)
return UploadFileResponse(
flowId=flow_id,
flowId=flow_id_str,
file_path=file_path,
)
except Exception as exc:
@ -400,23 +402,6 @@ async def custom_component(
return built_frontend_node
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)):
from langflow.interface.custom.utils import build_custom_component_template
try:
reader = DirectoryReader("")
valid, content = reader.process_file(path)
if not valid:
raise ValueError(content)
extractor = CustomComponent(code=content)
frontend_node, _ = build_custom_component_template(extractor, user_id=user.id)
return frontend_node
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.post("/custom_component/update", status_code=HTTPStatus.OK)
async def custom_component_update(
code_request: UpdateCustomComponentRequest,

View file

@ -1,6 +1,7 @@
import hashlib
from http import HTTPStatus
from io import BytesIO
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, UploadFile
from fastapi.responses import StreamingResponse
@ -20,38 +21,41 @@ router = APIRouter(tags=["Files"], prefix="/files")
# then finds it in the database and returns it while
# using the current user as the owner
def get_flow_id(
flow_id: str,
flow_id: UUID,
current_user=Depends(get_current_active_user),
session=Depends(get_session),
):
flow_id_str = str(flow_id)
# AttributeError: 'SelectOfScalar' object has no attribute 'first'
flow = session.get(Flow, flow_id)
flow = session.get(Flow, flow_id_str)
if not flow:
raise HTTPException(status_code=404, detail="Flow not found")
if flow.user_id != current_user.id:
raise HTTPException(status_code=403, detail="You don't have access to this flow")
return flow_id
return flow_id_str
@router.post("/upload/{flow_id}", status_code=HTTPStatus.CREATED)
async def upload_file(
file: UploadFile,
flow_id: str = Depends(get_flow_id),
flow_id: UUID = Depends(get_flow_id),
storage_service: StorageService = Depends(get_storage_service),
):
try:
flow_id_str = str(flow_id)
file_content = await file.read()
file_name = file.filename or hashlib.sha256(file_content).hexdigest()
folder = flow_id
folder = flow_id_str
await storage_service.save_file(flow_id=folder, file_name=file_name, data=file_content)
return UploadFileResponse(flowId=flow_id, file_path=f"{folder}/{file_name}")
return UploadFileResponse(flowId=flow_id_str, file_path=f"{folder}/{file_name}")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/download/{flow_id}/{file_name}")
async def download_file(file_name: str, flow_id: str, storage_service: StorageService = Depends(get_storage_service)):
async def download_file(file_name: str, flow_id: UUID, storage_service: StorageService = Depends(get_storage_service)):
try:
flow_id_str = str(flow_id)
extension = file_name.split(".")[-1]
if not extension:
@ -62,7 +66,7 @@ async def download_file(file_name: str, flow_id: str, storage_service: StorageSe
if not content_type:
raise HTTPException(status_code=500, detail=f"Content type not found for extension {extension}")
file_content = await storage_service.get_file(flow_id=flow_id, file_name=file_name)
file_content = await storage_service.get_file(flow_id=flow_id_str, file_name=file_name)
headers = {
"Content-Disposition": f"attachment; filename={file_name} filename*=UTF-8''{file_name}",
"Content-Type": "application/octet-stream",
@ -74,9 +78,10 @@ async def download_file(file_name: str, flow_id: str, storage_service: StorageSe
@router.get("/images/{flow_id}/{file_name}")
async def download_image(file_name: str, flow_id: str, storage_service: StorageService = Depends(get_storage_service)):
async def download_image(file_name: str, flow_id: UUID, storage_service: StorageService = Depends(get_storage_service)):
try:
extension = file_name.split(".")[-1]
flow_id_str = str(flow_id)
if not extension:
raise HTTPException(status_code=500, detail=f"Extension not found for file {file_name}")
@ -88,7 +93,7 @@ async def download_image(file_name: str, flow_id: str, storage_service: StorageS
elif not content_type.startswith("image"):
raise HTTPException(status_code=500, detail=f"Content type {content_type} is not an image")
file_content = await storage_service.get_file(flow_id=flow_id, file_name=file_name)
file_content = await storage_service.get_file(flow_id=flow_id_str, file_name=file_name)
return StreamingResponse(BytesIO(file_content), media_type=content_type)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@ -96,10 +101,11 @@ async def download_image(file_name: str, flow_id: str, storage_service: StorageS
@router.get("/list/{flow_id}")
async def list_files(
flow_id: str = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service)
flow_id: UUID = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service)
):
try:
files = await storage_service.list_files(flow_id=flow_id)
flow_id_str = str(flow_id)
files = await storage_service.list_files(flow_id=flow_id_str)
return {"files": files}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@ -107,10 +113,11 @@ async def list_files(
@router.delete("/delete/{flow_id}/{file_name}")
async def delete_file(
file_name: str, flow_id: str = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service)
file_name: str, flow_id: UUID = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service)
):
try:
await storage_service.delete_file(flow_id=flow_id, file_name=file_name)
flow_id_str = str(flow_id)
await storage_service.delete_file(flow_id=flow_id_str, file_name=file_name)
return {"message": f"File {file_name} deleted successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View file

@ -6,13 +6,15 @@ import orjson
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.encoders import jsonable_encoder
from loguru import logger
from sqlmodel import Session, select
from sqlmodel import Session, col, select
from langflow.api.utils import remove_api_keys, validate_is_component
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
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.folder.constants import DEFAULT_FOLDER_NAME
from langflow.services.database.models.folder.model import Folder
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session, get_settings_service
from langflow.services.settings.service import SettingsService
@ -35,6 +37,11 @@ def create_flow(
db_flow = Flow.model_validate(flow, from_attributes=True)
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)
@ -64,12 +71,9 @@ def read_flows(
flow_ids = [flow.id for flow in flows]
# with the session get the flows that DO NOT have a user_id
try:
example_flows = session.exec(
select(Flow).where(
Flow.user_id == None, # noqa
Flow.folder == STARTER_FOLDER_NAME,
)
).all()
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
@ -129,6 +133,10 @@ def update_flow(
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)
@ -208,3 +216,31 @@ async def download_file(
"""Download all flows as a file."""
flows = read_flows(current_user=current_user, session=session, settings_service=settings_service)
return FlowListRead(flows=flows)
@router.post("/multiple_delete/")
async def delete_multiple_flows(
flow_ids: FlowListIds, user: User = Depends(get_current_active_user), db: Session = Depends(get_session)
):
"""
Delete multiple flows by their IDs.
Args:
flow_ids (List[str]): The list of flow IDs to delete.
user (User, optional): The user making the request. Defaults to the current active user.
Returns:
dict: A dictionary containing the number of flows deleted.
"""
try:
deleted_flows = db.exec(
select(Flow).where(col(Flow.id).in_(flow_ids.flow_ids)).where(Flow.user_id == user.id)
).all()
for flow in deleted_flows:
db.delete(flow)
db.commit()
return {"deleted": len(deleted_flows)}
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc

View file

@ -0,0 +1,239 @@
from typing import List
from uuid import UUID
import orjson
from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status
from sqlalchemy import or_, update
from sqlmodel import Session, select
from langflow.api.v1.flows import create_flows
from langflow.api.v1.schemas import FlowListCreate, FlowListReadWithFolderName
from langflow.services.auth.utils import get_current_active_user
from langflow.services.database.models.flow.model import Flow, FlowCreate, FlowRead
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
from langflow.services.database.models.folder.model import (
Folder,
FolderCreate,
FolderRead,
FolderReadWithFlows,
FolderUpdate,
)
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session
router = APIRouter(prefix="/folders", tags=["Folders"])
@router.post("/", response_model=FolderRead, status_code=201)
def create_folder(
*,
session: Session = Depends(get_session),
folder: FolderCreate,
current_user: User = Depends(get_current_active_user),
):
try:
new_folder = Folder.model_validate(folder, from_attributes=True)
new_folder.user_id = current_user.id
folder_results = session.exec(
select(Folder).where(
Folder.name.like(f"{new_folder.name}%"), # type: ignore
Folder.user_id == current_user.id,
)
)
existing_folder_names = [folder.name for folder in folder_results]
if existing_folder_names:
new_folder.name = f"{new_folder.name} ({len(existing_folder_names) + 1})"
session.add(new_folder)
session.commit()
session.refresh(new_folder)
if folder.components_list:
update_statement_components = (
update(Flow).where(Flow.id.in_(folder.components_list)).values(folder_id=new_folder.id) # type: ignore
)
session.exec(update_statement_components) # type: ignore
session.commit()
if folder.flows_list:
update_statement_flows = update(Flow).where(Flow.id.in_(folder.flows_list)).values(folder_id=new_folder.id) # type: ignore
session.exec(update_statement_flows) # type: ignore
session.commit()
return new_folder
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/", response_model=List[FolderRead], status_code=200)
def read_folders(
*,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_active_user),
):
try:
folders = session.exec(
select(Folder).where(
or_(Folder.user_id == current_user.id, Folder.user_id == None) # type: ignore # noqa: E711
)
).all()
return folders
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{folder_id}", response_model=FolderReadWithFlows, status_code=200)
def read_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
try:
folder = session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first()
if not folder:
raise HTTPException(status_code=404, detail="Folder not found")
return folder
except Exception as e:
if "No result found" in str(e):
raise HTTPException(status_code=404, detail="Folder not found")
raise HTTPException(status_code=500, detail=str(e))
@router.patch("/{folder_id}", response_model=FolderRead, status_code=200)
def update_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
folder: FolderUpdate, # Assuming FolderUpdate is a Pydantic model defining updatable fields
current_user: User = Depends(get_current_active_user),
):
try:
existing_folder = session.exec(
select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)
).first()
if not existing_folder:
raise HTTPException(status_code=404, detail="Folder not found")
folder_data = folder.model_dump(exclude_unset=True)
for key, value in folder_data.items():
if key != "components" and key != "flows":
setattr(existing_folder, key, value)
session.add(existing_folder)
session.commit()
session.refresh(existing_folder)
concat_folder_components = folder.components + folder.flows
flows_ids = session.exec(select(Flow.id).where(Flow.folder_id == existing_folder.id)).all()
excluded_flows = list(set(flows_ids) - set(concat_folder_components))
my_collection_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
if my_collection_folder:
update_statement_my_collection = (
update(Flow).where(Flow.id.in_(excluded_flows)).values(folder_id=my_collection_folder.id) # type: ignore
)
session.exec(update_statement_my_collection) # type: ignore
session.commit()
if concat_folder_components:
update_statement_components = (
update(Flow).where(Flow.id.in_(concat_folder_components)).values(folder_id=existing_folder.id) # type: ignore
)
session.exec(update_statement_components) # type: ignore
session.commit()
return existing_folder
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{folder_id}", status_code=204)
def delete_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
try:
folder = session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first()
if not folder:
raise HTTPException(status_code=404, detail="Folder not found")
session.delete(folder)
session.commit()
flows = session.exec(select(Flow).where(Flow.folder_id == folder_id, Folder.user_id == current_user.id)).all()
for flow in flows:
session.delete(flow)
session.commit()
return Response(status_code=status.HTTP_204_NO_CONTENT)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/download/{folder_id}", response_model=FlowListReadWithFolderName, status_code=200)
async def download_file(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
"""Download all flows from folder."""
try:
folder = session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first()
return folder
except Exception as e:
if "No result found" in str(e):
raise HTTPException(status_code=404, detail="Folder not found")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/upload/", response_model=List[FlowRead], status_code=201)
async def upload_file(
*,
session: Session = Depends(get_session),
file: UploadFile = File(...),
current_user: User = Depends(get_current_active_user),
):
"""Upload flows from a file."""
contents = await file.read()
data = orjson.loads(contents)
if not data:
raise HTTPException(status_code=400, detail="No flows found in the file")
folder_results = session.exec(
select(Folder).where(
Folder.name == data["folder_name"],
Folder.user_id == current_user.id,
)
)
existing_folder_names = [folder.name for folder in folder_results]
if existing_folder_names:
data["folder_name"] = f"{data['folder_name']} ({len(existing_folder_names) + 1})"
folder = FolderCreate(name=data["folder_name"], description=data["folder_description"])
new_folder = Folder.model_validate(folder, from_attributes=True)
new_folder.id = None
new_folder.user_id = current_user.id
session.add(new_folder)
session.commit()
session.refresh(new_folder)
del data["folder_name"]
del data["folder_description"]
if "flows" in data:
flow_list = FlowListCreate(flows=[FlowCreate(**flow) for flow in data["flows"]])
else:
raise HTTPException(status_code=400, detail="No flows found in the data")
# Now we set the user_id for all flows
for flow in flow_list.flows:
flow.user_id = current_user.id
flow.folder_id = new_folder.id
return create_flows(session=session, flow_list=flow_list, current_user=current_user)

View file

@ -1,5 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import Session
from langflow.api.v1.schemas import Token
from langflow.services.auth.utils import (
authenticate_user,
@ -7,14 +9,10 @@ from langflow.services.auth.utils import (
create_user_longterm_token,
create_user_tokens,
)
from langflow.services.deps import (
get_session,
get_settings_service,
get_variable_service,
)
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
from langflow.services.deps import get_session, get_settings_service, get_variable_service
from langflow.services.settings.manager import SettingsService
from langflow.services.variable.service import VariableService
from sqlmodel import Session
router = APIRouter(tags=["Login"])
@ -58,6 +56,8 @@ async def login_to_get_access_token(
expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
)
variable_service.initialize_user_variables(user.id, db)
# Create default folder for user if it doesn't exist
create_default_folder_if_it_doesnt_exist(db, user.id)
return tokens
else:
raise HTTPException(
@ -86,6 +86,7 @@ async def auto_login(
expires=None, # Set to None to make it a session cookie
)
variable_service.initialize_user_variables(user_id, db)
create_default_folder_if_it_doesnt_exist(db, user_id)
return tokens
raise HTTPException(
@ -139,4 +140,3 @@ async def logout(response: Response):
response.delete_cookie("refresh_token_lf")
response.delete_cookie("access_token_lf")
return {"message": "Logout successful"}
return {"message": "Logout successful"}

View file

@ -1,10 +1,13 @@
from typing import Optional
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.schema import VertexBuildMapModel
from langflow.services.monitor.schema import (
MessageModelResponse,
TransactionModelResponse,
VertexBuildMapModel,
)
from langflow.services.monitor.service import MonitorService
router = APIRouter(prefix="/monitor", tags=["Monitor"])
@ -40,8 +43,9 @@ async def delete_vertex_builds(
raise HTTPException(status_code=500, detail=str(e))
@router.get("/messages")
@router.get("/messages", response_model=List[MessageModelResponse])
async def get_messages(
flow_id: Optional[str] = Query(None),
session_id: Optional[str] = Query(None),
sender: Optional[str] = Query(None),
sender_name: Optional[str] = Query(None),
@ -49,25 +53,32 @@ async def get_messages(
monitor_service: MonitorService = Depends(get_monitor_service),
):
try:
return monitor_service.get_messages(
df = monitor_service.get_messages(
flow_id=flow_id,
sender=sender,
sender_name=sender_name,
session_id=session_id,
order_by=order_by,
)
dicts = df.to_dict(orient="records")
return [MessageModelResponse(**d) for d in dicts]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/transactions")
@router.get("/transactions", response_model=List[TransactionModelResponse])
async def get_transactions(
source: Optional[str] = Query(None),
target: Optional[str] = Query(None),
status: Optional[str] = Query(None),
order_by: Optional[str] = Query("timestamp"),
flow_id: Optional[str] = Query(None),
monitor_service: MonitorService = Depends(get_monitor_service),
):
try:
return monitor_service.get_transactions(source=source, target=target, status=status, order_by=order_by)
dicts = monitor_service.get_transactions(
source=source, target=target, status=status, order_by=order_by, flow_id=flow_id
)
return [TransactionModelResponse(**d) for d in dicts]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View file

@ -139,10 +139,20 @@ class FlowListCreate(BaseModel):
flows: List[FlowCreate]
class FlowListIds(BaseModel):
flow_ids: List[str]
class FlowListRead(BaseModel):
flows: List[FlowRead]
class FlowListReadWithFolderName(BaseModel):
flows: List[FlowRead]
name: str
description: str
class InitResponse(BaseModel):
flowId: str

View file

@ -13,6 +13,7 @@ from langflow.services.auth.utils import (
get_password_hash,
verify_password,
)
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
from langflow.services.database.models.user import User, UserCreate, UserRead, UserUpdate
from langflow.services.database.models.user.crud import get_user_by_id, update_user
from langflow.services.deps import get_session, get_settings_service
@ -36,6 +37,9 @@ def add_user(
session.add(new_user)
session.commit()
session.refresh(new_user)
folder = create_default_folder_if_it_doesnt_exist(session, new_user.id)
if not folder:
raise HTTPException(status_code=500, detail="Error creating default folder")
except IntegrityError as e:
session.rollback()
raise HTTPException(status_code=400, detail="This username is unavailable.") from e

View file

@ -49,12 +49,12 @@ class ChatComponent(CustomComponent):
sender: Optional[str] = None,
sender_name: Optional[str] = None,
) -> list[Record]:
records = store_message(
message,
session_id=session_id,
sender=sender,
sender_name=sender_name,
flow_id=self.graph.flow_id,
)
self.status = records

View file

@ -1,7 +1,5 @@
from typing import Optional
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

View file

@ -1,10 +1,10 @@
from fastapi import HTTPException
from langchain.prompts import PromptTemplate
from loguru import logger
from langflow.api.v1.base import INVALID_NAMES, check_input_variables
from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.template.field.prompt import DefaultPromptField
from langchain_core.prompts import PromptTemplate
def validate_prompt(prompt_template: str, silent_errors: bool = False) -> list[str]:

View file

@ -1,3 +1,6 @@
from copy import deepcopy
from langchain_core.documents import Document
from langflow.schema import Record
@ -27,19 +30,20 @@ def dict_values_to_string(d: dict) -> dict:
dict: The dictionary with values converted to strings.
"""
# Do something similar to the above
for key, value in d.items():
d_copy = deepcopy(d)
for key, value in d_copy.items():
# it could be a list of records or documents or strings
if isinstance(value, list):
for i, item in enumerate(value):
if isinstance(item, Record):
d[key][i] = record_to_string(item)
d_copy[key][i] = record_to_string(item)
elif isinstance(item, Document):
d[key][i] = document_to_string(item)
d_copy[key][i] = document_to_string(item)
elif isinstance(value, Record):
d[key] = record_to_string(value)
d_copy[key] = record_to_string(value)
elif isinstance(value, Document):
d[key] = document_to_string(value)
return d
d_copy[key] = document_to_string(value)
return d_copy
def document_to_string(document: Document) -> str:

View file

@ -1,10 +1,11 @@
from langchain.agents import AgentExecutor, create_json_agent
from langchain.agents import AgentExecutor
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
class JsonAgentComponent(CustomComponent):

View file

@ -4,14 +4,14 @@ 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.prompts import SystemMessagePromptTemplate
from langchain.prompts.chat import MessagesPlaceholder
from langchain.schema.memory import BaseMemory
from langchain.tools import Tool
from langchain_community.chat_models import ChatOpenAI
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):
@ -57,9 +57,14 @@ class ConversationalAgent(CustomComponent):
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=openai_api_key,
api_key=api_key,
base_url=openai_api_base,
max_tokens=max_token_limit,
temperature=temperature,

View file

@ -1,9 +1,8 @@
from typing import Optional
from langchain.embeddings.base import Embeddings
from langchain_community.embeddings import BedrockEmbeddings
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.embeddings import Embeddings
class AmazonBedrockEmeddingsComponent(CustomComponent):

View file

@ -1,7 +1,7 @@
from langchain.embeddings.base import Embeddings
from langchain_community.embeddings import AzureOpenAIEmbeddings
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.embeddings import Embeddings
from langchain_openai import AzureOpenAIEmbeddings
from pydantic.v1 import SecretStr
class AzureOpenAIEmbeddingsComponent(CustomComponent):
@ -52,12 +52,16 @@ class AzureOpenAIEmbeddingsComponent(CustomComponent):
api_version: str,
api_key: str,
) -> Embeddings:
if api_key:
azure_api_key = SecretStr(api_key)
else:
azure_api_key = None
try:
embeddings = AzureOpenAIEmbeddings(
azure_endpoint=azure_endpoint,
azure_deployment=azure_deployment,
api_version=api_version,
api_key=api_key,
api_key=azure_api_key,
)
except Exception as e:

View file

@ -1,9 +1,9 @@
from typing import List, Optional
from pydantic.v1 import SecretStr
from langchain_mistralai.embeddings import MistralAIEmbeddings
from langflow.interface.custom.custom_component import CustomComponent
from langflow.field_typing import Embeddings, NestedDict
from langflow.field_typing import Embeddings
class MistralAIEmbeddingsComponent(CustomComponent):
display_name = "MistralAI Embeddings"
@ -37,11 +37,7 @@ class MistralAIEmbeddingsComponent(CustomComponent):
"advanced": True,
"value": 120,
},
"endpoint": {
"display_name": "API Endpoint",
"advanced": True,
"value": "https://api.mistral.ai/v1/"
}
"endpoint": {"display_name": "API Endpoint", "advanced": True, "value": "https://api.mistral.ai/v1/"},
}
def build(
@ -51,7 +47,7 @@ class MistralAIEmbeddingsComponent(CustomComponent):
max_concurrent_requests: int = 64,
max_retries: int = 5,
timeout: int = 120,
endpoint: str = "https://api.mistral.ai/v1/"
endpoint: str = "https://api.mistral.ai/v1/",
) -> Embeddings:
if mistral_api_key:
api_key = SecretStr(mistral_api_key)
@ -64,6 +60,5 @@ class MistralAIEmbeddingsComponent(CustomComponent):
endpoint=endpoint,
max_concurrent_requests=max_concurrent_requests,
max_retries=max_retries,
timeout=timeout
timeout=timeout,
)

View file

@ -1,9 +1,8 @@
from typing import Optional
from langchain.embeddings.base import Embeddings
from langchain_community.embeddings import OllamaEmbeddings
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.embeddings import Embeddings
class OllamaEmbeddingsComponent(CustomComponent):

View file

@ -3,11 +3,12 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record
from langflow.field_typing import Text
class PassComponent(CustomComponent):
display_name = "Pass"
description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction."
field_order = ["ignored_input", "forwarded_input"]
def build_config(self) -> dict:
return {
"ignored_input": {
@ -19,7 +20,7 @@ class PassComponent(CustomComponent):
"display_name": "Input",
"info": "This input is forwarded by the component.",
"input_types": ["Text", "Record"],
}
},
}
def build(self, ignored_input: Text, forwarded_input: Text) -> Union[Text, Record]:

View file

@ -32,7 +32,6 @@ class StoreMessageComponent(CustomComponent):
session_id: Optional[str] = None,
message: str = "",
) -> List[Record]:
store_message(
sender=sender,
sender_name=sender_name,

View file

@ -35,7 +35,7 @@ class SubFlowComponent(CustomComponent):
build_config["flow_name"]["options"] = self.get_flow_names()
# Clean up the build config
for key in list(build_config.keys()):
if key not in self.field_order + ["code", "_type"]:
if key not in self.field_order + ["code", "_type", "get_final_results_only"]:
del build_config[key]
if field_value is not None and field_name == "flow_name":
try:
@ -85,20 +85,29 @@ class SubFlowComponent(CustomComponent):
"display_name": "Tweaks",
"info": "Tweaks to apply to the flow.",
},
"get_final_results_only": {
"display_name": "Get Final Results Only",
"info": "If False, the output will contain all outputs from the flow.",
"advanced": True,
},
}
def build_records_from_result_data(self, result_data: ResultData) -> List[Record]:
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()
record = Record(data={"result": result_data.model_dump(), "message": message_dict.get("message", "")})
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, **kwargs) -> List[Record]:
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(
tweaks=tweaks,
@ -112,7 +121,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))
records.extend(self.build_records_from_result_data(output, get_final_results_only))
self.status = records
logger.debug(records)

View file

@ -4,6 +4,7 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record
from langflow.field_typing import Text
class TextOperatorComponent(CustomComponent):
display_name = "Text Operator"
description = "Compares two text inputs based on a specified condition such as equality or inequality, with optional case sensitivity."
@ -21,14 +22,7 @@ class TextOperatorComponent(CustomComponent):
"operator": {
"display_name": "Operator",
"info": "The operator to apply for comparing the texts.",
"options": [
"equals",
"not equals",
"contains",
"starts with",
"ends with",
"exists"
],
"options": ["equals", "not equals", "contains", "starts with", "ends with", "exists"],
},
"case_sensitive": {
"display_name": "Case Sensitive",
@ -51,11 +45,8 @@ class TextOperatorComponent(CustomComponent):
case_sensitive: bool = False,
true_output: Optional[Text] = "",
) -> Union[Text, Record]:
if not input_text or not match_text:
raise ValueError(
"Both 'input_text' and 'match_text' must be provided and non-empty."
)
raise ValueError("Both 'input_text' and 'match_text' must be provided and non-empty.")
if not case_sensitive:
input_text = input_text.lower()
@ -82,4 +73,4 @@ class TextOperatorComponent(CustomComponent):
self.status = "Comparison failed, stopping execution."
self.stop()
return output_record
return output_record

View file

@ -0,0 +1,91 @@
from typing import Optional, cast
from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory
from langflow.base.memory.memory import BaseMemoryComponent
from langflow.field_typing import Text
from langflow.schema.schema import Record
class AstraDBMessageReaderComponent(BaseMemoryComponent):
display_name = "Astra DB Message Reader"
description = "Retrieves stored chat messages from Astra DB."
def build_config(self):
return {
"session_id": {
"display_name": "Session ID",
"info": "Session ID of the chat history.",
"input_types": ["Text"],
},
"collection_name": {
"display_name": "Collection Name",
"info": "Collection name for Astra DB.",
"input_types": ["Text"],
},
"token": {
"display_name": "Astra DB Application Token",
"info": "Token for the Astra DB instance.",
"password": True,
},
"api_endpoint": {
"display_name": "Astra DB API Endpoint",
"info": "API Endpoint for the Astra DB instance.",
"password": True,
},
"namespace": {
"display_name": "Namespace",
"info": "Namespace for the Astra DB instance.",
"input_types": ["Text"],
"advanced": True,
},
}
def get_messages(self, **kwargs) -> list[Record]:
"""
Retrieves messages from the AstraDBChatMessageHistory memory.
Args:
memory (AstraDBChatMessageHistory): The AstraDBChatMessageHistory instance to retrieve messages from.
Returns:
list[Record]: A list of Record objects representing the search results.
"""
memory: AstraDBChatMessageHistory = cast(AstraDBChatMessageHistory, kwargs.get("memory"))
if not memory:
raise ValueError("AstraDBChatMessageHistory instance is required.")
# Get messages from the memory
messages = memory.messages
results = [Record.from_lc_message(message) for message in messages]
return list(results)
def build(
self,
session_id: Text,
collection_name: str,
token: str,
api_endpoint: str,
namespace: Optional[str] = None,
) -> list[Record]:
try:
pass
except ImportError:
raise ImportError(
"Could not import langchain Astra DB integration package. "
"Please install it with `pip install langchain-astradb`."
)
memory = AstraDBChatMessageHistory(
session_id=session_id,
collection_name=collection_name,
token=token,
api_endpoint=api_endpoint,
namespace=namespace,
)
records = self.get_messages(memory=memory)
self.status = records
return records

View file

@ -0,0 +1,117 @@
from typing import Optional
from langflow.base.memory.memory import BaseMemoryComponent
from langflow.field_typing import Text
from langflow.schema.schema import Record
from langchain_core.messages import BaseMessage
from langchain_astradb import AstraDBChatMessageHistory
class AstraDBMessageWriterComponent(BaseMemoryComponent):
display_name = "Astra DB Message Writer"
description = "Writes a message to Astra DB."
def build_config(self):
return {
"input_value": {
"display_name": "Input Record",
"info": "Record to write to Astra DB.",
},
"session_id": {
"display_name": "Session ID",
"info": "Session ID of the chat history.",
"input_types": ["Text"],
},
"collection_name": {
"display_name": "Collection Name",
"info": "Collection name for Astra DB.",
"input_types": ["Text"],
},
"token": {
"display_name": "Astra DB Application Token",
"info": "Token for the Astra DB instance.",
"password": True,
},
"api_endpoint": {
"display_name": "Astra DB API Endpoint",
"info": "API Endpoint for the Astra DB instance.",
"password": True,
},
"namespace": {
"display_name": "Namespace",
"info": "Namespace for the Astra DB instance.",
"input_types": ["Text"],
"advanced": True,
},
}
def add_message(
self,
sender: str,
sender_name: str,
text: Text,
session_id: str,
metadata: Optional[dict] = None,
**kwargs,
):
"""
Adds a message to the AstraDBChatMessageHistory memory.
Args:
sender (Text): The type of the message sender. Valid values are "Machine" or "User".
sender_name (Text): The name of the message sender.
text (Text): The content of the message.
session_id (Text): The session ID associated with the message.
metadata (dict | None, optional): Additional metadata for the message. Defaults to None.
**kwargs: Additional keyword arguments.
Raises:
ValueError: If the AstraDBChatMessageHistory instance is not provided.
"""
memory: AstraDBChatMessageHistory | None = kwargs.pop("memory", None)
if memory is None:
raise ValueError("AstraDBChatMessageHistory instance is required.")
text_list = [
BaseMessage(
content=text,
sender=sender,
sender_name=sender_name,
metadata=metadata,
session_id=session_id,
)
]
memory.add_messages(text_list)
def build(
self,
input_value: Record,
session_id: Text,
collection_name: str,
token: str,
api_endpoint: str,
namespace: Optional[str] = None,
) -> Record:
try:
pass
except ImportError:
raise ImportError(
"Could not import langchain Astra DB integration package. "
"Please install it with `pip install langchain-astradb`."
)
memory = AstraDBChatMessageHistory(
session_id=session_id,
collection_name=collection_name,
token=token,
api_endpoint=api_endpoint,
namespace=namespace,
)
self.add_message(**input_value.data, memory=memory)
self.status = f"Added message to Astra DB memory for session {session_id}"
return input_value

View file

@ -1,10 +1,9 @@
from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_anthropic import ChatAnthropic
from pydantic.v1 import SecretStr
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.language_models import BaseLanguageModel
class ChatAntropicSpecsComponent(CustomComponent):

View file

@ -1,9 +1,9 @@
from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_community.chat_models.azure_openai import AzureChatOpenAI
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
class AzureChatOpenAISpecsComponent(CustomComponent):
@ -84,13 +84,17 @@ class AzureChatOpenAISpecsComponent(CustomComponent):
temperature: float = 0.7,
max_tokens: Optional[int] = 1000,
) -> BaseLanguageModel:
if api_key:
azure_api_key = SecretStr(api_key)
else:
azure_api_key = None
try:
llm = AzureChatOpenAI(
model=model,
azure_endpoint=azure_endpoint,
azure_deployment=azure_deployment,
api_version=api_version,
api_key=api_key,
api_key=azure_api_key,
temperature=temperature,
max_tokens=max_tokens,
)

View file

@ -1,6 +1,8 @@
from typing import Optional
from langchain_community.chat_models.openai import ChatOpenAI
from langchain_openai import ChatOpenAI
from pydantic.v1 import SecretStr
from langflow.base.models.openai_constants import MODEL_NAMES
from langflow.field_typing import BaseLanguageModel, NestedDict
@ -59,11 +61,15 @@ class ChatOpenAIComponent(CustomComponent):
) -> BaseLanguageModel:
if not openai_api_base:
openai_api_base = "https://api.openai.com/v1"
if openai_api_key:
api_key = SecretStr(openai_api_key)
else:
api_key = None
return ChatOpenAI(
max_tokens=max_tokens,
model_kwargs=model_kwargs,
model=model_name,
base_url=openai_api_base,
api_key=openai_api_key,
api_key=api_key,
temperature=temperature,
)

View file

@ -0,0 +1,10 @@
from langflow.custom import CustomComponent
from langflow.schema import Record
class RecordsOutput(CustomComponent):
display_name = "Records Output"
description = "Display Records as a Table"
def build(self, input_value: Record) -> Record:
return input_value

View file

@ -1,9 +1,8 @@
from typing import Optional
from langchain.schema import BaseRetriever
from langchain_community.retrievers import AmazonKendraRetriever
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.retrievers import BaseRetriever
class AmazonKendraRetrieverComponent(CustomComponent):

View file

@ -1,10 +1,9 @@
from typing import Optional
from langchain.schema import BaseRetriever
from langchain_community.retrievers import MetalRetriever
from metal_sdk.metal import Metal # type: ignore
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.retrievers import BaseRetriever
class MetalRetrieverComponent(CustomComponent):

View file

@ -1,13 +1,12 @@
import json
from typing import List
from langchain.base_language import BaseLanguageModel
from langchain.chains.query_constructor.base import AttributeInfo
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.schema import BaseRetriever
from langchain.schema.vectorstore import VectorStore
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.language_models import BaseLanguageModel
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class VectaraSelfQueryRetriverComponent(CustomComponent):

View file

@ -1,10 +1,9 @@
from typing import List
from langchain.text_splitter import CharacterTextSplitter
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langflow.utils.util import unescape_string
from langchain_text_splitters import CharacterTextSplitter
class CharacterTextSplitterComponent(CustomComponent):

View file

@ -1,9 +1,8 @@
from typing import List, Optional
from langchain.text_splitter import Language
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
class LanguageRecursiveTextSplitterComponent(CustomComponent):
@ -61,7 +60,6 @@ class LanguageRecursiveTextSplitterComponent(CustomComponent):
Returns:
list[str]: The chunks of text.
"""
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Make sure chunk_size and chunk_overlap are ints
if isinstance(chunk_size, str):

View file

@ -1,11 +1,10 @@
from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record
from langflow.utils.util import build_loader_repr_from_records, unescape_string
from langchain_text_splitters import RecursiveCharacterTextSplitter
class RecursiveCharacterTextSplitterComponent(CustomComponent):

View file

@ -1,11 +1,9 @@
from typing import List, Union
from langchain.agents import tool
from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.tools import Tool
from metaphor_python import Metaphor # type: ignore
from langflow.interface.custom.custom_component import CustomComponent
from langchain_community.agent_toolkits.base import BaseToolkit
from langchain_core.tools import Tool, tool
class MetaphorToolkit(CustomComponent):

View file

@ -1,7 +1,7 @@
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo
from langchain_community.vectorstores import VectorStore
from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.vectorstores import VectorStore
class VectorStoreInfoComponent(CustomComponent):

View file

@ -1,10 +1,9 @@
import importlib
from langchain.agents import Tool
from langchain_experimental.utilities import PythonREPL
from langflow.base.tools.base import build_status_from_tool
from langflow.custom import CustomComponent
from langchain_core.tools import Tool
class PythonREPLToolComponent(CustomComponent):

View file

@ -0,0 +1,69 @@
from typing import List
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Couchbase import CouchbaseComponent
from langflow.field_typing import Embeddings, Text
from langflow.schema import Record
class CouchbaseSearchComponent(LCVectorStoreComponent):
display_name = "Couchbase Search"
description = "Search a Couchbase Vector Store for similar documents."
documentation = "https://python.langchain.com/docs/integrations/vectorstores/couchbase"
icon = "Couchbase"
field_order = [
"couchbase_connection_string",
"couchbase_username",
"couchbase_password",
"bucket_name",
"scope_name",
"collection_name",
"index_name",
]
def build_config(self):
return {
"input_value": {"display_name": "Input"},
"embedding": {"display_name": "Embedding"},
"couchbase_connection_string": {"display_name": "Couchbase Cluster connection string", "required": True},
"couchbase_username": {"display_name": "Couchbase username", "required": True},
"couchbase_password": {"display_name": "Couchbase password", "password": True, "required": True},
"bucket_name": {"display_name": "Bucket Name", "required": True},
"scope_name": {"display_name": "Scope Name", "required": True},
"collection_name": {"display_name": "Collection Name", "required": True},
"index_name": {"display_name": "Index Name", "required": True},
"number_of_results": {
"display_name": "Number of Results",
"info": "Number of results to return.",
"advanced": True,
},
}
def build( # type: ignore[override]
self,
input_value: Text,
embedding: Embeddings,
number_of_results: int = 4,
bucket_name: str = "",
scope_name: str = "",
collection_name: str = "",
index_name: str = "",
couchbase_connection_string: str = "",
couchbase_username: str = "",
couchbase_password: str = "",
) -> List[Record]:
vector_store = CouchbaseComponent().build(
couchbase_connection_string=couchbase_connection_string,
couchbase_username=couchbase_username,
couchbase_password=couchbase_password,
bucket_name=bucket_name,
scope_name=scope_name,
collection_name=collection_name,
embedding=embedding,
index_name=index_name,
)
if not vector_store:
raise ValueError("Failed to create Couchbase Vector Store")
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type="similarity", k=number_of_results
)

View file

@ -1,11 +1,10 @@
from typing import List, Optional
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Redis import RedisComponent
from langflow.field_typing import Text
from langflow.schema import Record
from langchain_core.embeddings import Embeddings
class RedisSearchComponent(RedisComponent, LCVectorStoreComponent):

View file

@ -1,11 +1,10 @@
from typing import List, Optional
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Weaviate import WeaviateVectorStoreComponent
from langflow.field_typing import Text
from langflow.schema import Record
from langchain_core.embeddings import Embeddings
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):

View file

@ -9,10 +9,12 @@ from .SupabaseVectorStoreSearch import SupabaseSearchComponent
from .VectaraSearch import VectaraSearchComponent
from .WeaviateSearch import WeaviateSearchVectorStore
from .pgvectorSearch import PGVectorSearchComponent
from .Couchbase import CouchbaseSearchComponent # type: ignore
__all__ = [
"AstraDBSearchComponent",
"ChromaSearchComponent",
"CouchbaseSearchComponent",
"FAISSSearchComponent",
"MongoDBAtlasSearchComponent",
"PineconeSearchComponent",

View file

@ -1,11 +1,10 @@
from typing import List
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.pgvector import PGVectorComponent
from langflow.field_typing import Text
from langflow.schema import Record
from langchain_core.embeddings import Embeddings
class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):

View file

@ -1,12 +1,11 @@
from typing import List, Optional, Union
from langchain.schema import BaseRetriever
from langchain_astradb import AstraDBVectorStore
from langchain_astradb.utils.astradb import SetupMode
from langflow.custom import CustomComponent
from langflow.field_typing import Embeddings, VectorStore
from langflow.schema import Record
from langchain_core.retrievers import BaseRetriever
class AstraDBVectorStoreComponent(CustomComponent):

View file

@ -1,13 +1,13 @@
from typing import List, Optional, Union
import chromadb # type: ignore
from langchain.embeddings.base import Embeddings
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.chroma import Chroma
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_core.embeddings import Embeddings
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class ChromaComponent(CustomComponent):

View file

@ -0,0 +1,90 @@
from typing import List, Optional, Union
from langchain_community.vectorstores import CouchbaseVectorStore
from langflow.custom import CustomComponent
from langflow.field_typing import Embeddings, VectorStore
from langflow.schema import Record
from datetime import timedelta
from couchbase.auth import PasswordAuthenticator # type: ignore
from couchbase.cluster import Cluster # type: ignore
from couchbase.options import ClusterOptions # type: ignore
from langchain_core.retrievers import BaseRetriever
class CouchbaseComponent(CustomComponent):
display_name = "Couchbase"
description = "Construct a `Couchbase Vector Search` vector store from raw documents."
documentation = "https://python.langchain.com/docs/integrations/vectorstores/couchbase"
icon = "Couchbase"
field_order = [
"couchbase_connection_string",
"couchbase_username",
"couchbase_password",
"bucket_name",
"scope_name",
"collection_name",
"index_name",
]
def build_config(self):
return {
"inputs": {"display_name": "Input", "input_types": ["Document", "Record"]},
"embedding": {"display_name": "Embedding"},
"couchbase_connection_string": {"display_name": "Couchbase Cluster connection string", "required": True},
"couchbase_username": {"display_name": "Couchbase username", "required": True},
"couchbase_password": {"display_name": "Couchbase password", "password": True, "required": True},
"bucket_name": {"display_name": "Bucket Name", "required": True},
"scope_name": {"display_name": "Scope Name", "required": True},
"collection_name": {"display_name": "Collection Name", "required": True},
"index_name": {"display_name": "Index Name", "required": True},
}
def build(
self,
embedding: Embeddings,
inputs: Optional[List[Record]] = None,
bucket_name: str = "",
scope_name: str = "",
collection_name: str = "",
index_name: str = "",
couchbase_connection_string: str = "",
couchbase_username: str = "",
couchbase_password: str = "",
) -> Union[VectorStore, BaseRetriever]:
try:
auth = PasswordAuthenticator(couchbase_username, couchbase_password)
options = ClusterOptions(auth)
cluster = Cluster(couchbase_connection_string, options)
cluster.wait_until_ready(timedelta(seconds=5))
except Exception as e:
raise ValueError(f"Failed to connect to Couchbase: {e}")
documents = []
for _input in inputs or []:
if isinstance(_input, Record):
documents.append(_input.to_lc_document())
else:
documents.append(_input)
if documents:
vector_store = CouchbaseVectorStore.from_documents(
documents=documents,
cluster=cluster,
bucket_name=bucket_name,
scope_name=scope_name,
collection_name=collection_name,
embedding=embedding,
index_name=index_name,
)
else:
vector_store = CouchbaseVectorStore(
cluster=cluster,
bucket_name=bucket_name,
scope_name=scope_name,
collection_name=collection_name,
embedding=embedding,
index_name=index_name,
)
return vector_store

View file

@ -1,12 +1,11 @@
from typing import List, Text, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.faiss import FAISS
from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class FAISSComponent(CustomComponent):

View file

@ -1,7 +1,4 @@
from typing import List, Optional, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_core.documents import Document
from langchain_pinecone._utilities import DistanceStrategy
from langchain_pinecone.vectorstores import PineconeVectorStore
@ -9,6 +6,8 @@ from langchain_pinecone.vectorstores import PineconeVectorStore
from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class PineconeComponent(CustomComponent):

View file

@ -1,12 +1,11 @@
from typing import Optional, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.qdrant import Qdrant
from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class QdrantComponent(CustomComponent):

View file

@ -1,12 +1,11 @@
from typing import Optional, Union
from langchain.embeddings.base import Embeddings
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.redis import Redis
from langchain_core.retrievers import BaseRetriever
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_core.embeddings import Embeddings
from langchain_core.vectorstores import VectorStore
class RedisComponent(CustomComponent):

View file

@ -1,13 +1,12 @@
from typing import List, Optional, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.supabase import SupabaseVectorStore
from supabase.client import Client, create_client
from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class SupabaseComponent(CustomComponent):

View file

@ -1,13 +1,14 @@
from typing import Optional, Union
import weaviate # type: ignore
from langchain.embeddings.base import Embeddings
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore, Weaviate
from langchain_community.vectorstores import Weaviate
from langchain_core.documents import Document
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_core.embeddings import Embeddings
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class WeaviateVectorStoreComponent(CustomComponent):

View file

@ -9,10 +9,12 @@ from .SupabaseVectorStore import SupabaseComponent
from .Vectara import VectaraComponent
from .Weaviate import WeaviateVectorStoreComponent
from .pgvector import PGVectorComponent
from .Couchbase import CouchbaseComponent
__all__ = [
"AstraDBVectorStoreComponent",
"ChromaComponent",
"CouchbaseComponent",
"FAISSComponent",
"MongoDBAtlasComponent",
"PineconeComponent",

View file

@ -1,12 +1,11 @@
from typing import Optional, Union
from langchain.embeddings.base import Embeddings
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.pgvector import PGVector
from langchain_core.retrievers import BaseRetriever
from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record
from langchain_core.embeddings import Embeddings
from langchain_core.vectorstores import VectorStore
class PGVectorComponent(CustomComponent):

View file

@ -2,17 +2,18 @@ from typing import Callable, Dict, Text, Union
from langchain.agents.agent import AgentExecutor
from langchain.chains.base import Chain
from langchain.document_loaders.base import BaseLoader
from langchain.llms.base import BaseLLM
from langchain.memory.chat_memory import BaseChatMemory
from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
from langchain.schema import BaseOutputParser, BaseRetriever, Document
from langchain.schema.embeddings import Embeddings
from langchain.schema.language_model import BaseLanguageModel
from langchain.schema.memory import BaseMemory
from langchain.text_splitter import TextSplitter
from langchain.tools import Tool
from langchain_community.vectorstores import VectorStore
from langchain_core.document_loaders import BaseLoader
from langchain_core.documents import Document
from langchain_core.embeddings import Embeddings
from langchain_core.language_models import BaseLLM, BaseLanguageModel
from langchain_core.memory import BaseMemory
from langchain_core.output_parsers import BaseOutputParser
from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
from langchain_core.retrievers import BaseRetriever
from langchain_core.tools import Tool
from langchain_core.vectorstores import VectorStore
from langchain_text_splitters import TextSplitter
# Type alias for more complex dicts
NestedDict = Dict[str, Union[str, Dict]]

View file

@ -3,9 +3,7 @@ from typing import TYPE_CHECKING, Any, List, Optional
from loguru import logger
from pydantic import BaseModel, Field
from langflow.graph.edge.utils import build_clean_params
from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.utils import log_message
if TYPE_CHECKING:
@ -143,7 +141,6 @@ class ContractEdge(Edge):
if not self.is_fulfilled:
await self.honor(source, target)
log_transaction(self, source, target, "success")
# If the target vertex is a power component we log messages
if target.vertex_type == "ChatOutput" and (
isinstance(target.params.get(INPUT_FIELD_NAME), str)
@ -157,26 +154,9 @@ class ContractEdge(Edge):
message=target.params.get(INPUT_FIELD_NAME, {}),
session_id=target.params.get("session_id", ""),
artifacts=target.artifacts,
flow_id=target.graph.flow_id,
)
return self.result
def __repr__(self) -> str:
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
def log_transaction(edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None):
try:
monitor_service = get_monitor_service()
clean_params = build_clean_params(target)
data = {
"source": source.vertex_type,
"target": target.vertex_type,
"target_args": clean_params,
"timestamp": monitor_service.get_timestamp(),
"status": status,
"error": error,
}
monitor_service.add_row(table_name="transactions", data=data)
except Exception as e:
logger.error(f"Error logging transaction: {e}")
logger.error(f"Error logging transaction: {e}")

View file

@ -1,19 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
def build_clean_params(target: "Vertex") -> dict:
"""
Cleans the parameters of the target vertex.
"""
# Removes all keys that the values aren't python types like str, int, bool, etc.
params = {
key: value for key, value in target.params.items() if isinstance(value, (str, int, bool, float, list, dict))
}
# if it is a list we need to check if the contents are python types
for key, value in params.items():
if isinstance(value, list):
params[key] = [item for item in value if isinstance(item, (str, int, bool, float, list, dict))]
return params

View file

@ -14,7 +14,7 @@ from langflow.graph.graph.state_manager import GraphStateManager
from langflow.graph.graph.utils import process_flow
from langflow.graph.schema import InterfaceComponentTypes, RunOutputs
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import ChatVertex, FileToolVertex, LLMVertex, StateVertex, ToolkitVertex
from langflow.graph.vertex.types import FileToolVertex, InterfaceVertex, LLMVertex, StateVertex, ToolkitVertex
from langflow.interface.tools.constants import FILE_TOOLS
from langflow.schema import Record
from langflow.schema.schema import INPUT_FIELD_NAME, InputType
@ -242,6 +242,7 @@ class Graph:
outputs: list[str],
stream: bool,
session_id: str,
fallback_to_env_vars: bool,
) -> List[Optional["ResultData"]]:
"""
Runs the graph with the given inputs.
@ -289,7 +290,7 @@ class Graph:
start_component_id = next(
(vertex_id for vertex_id in self._is_input_vertices if "chat" in vertex_id.lower()), None
)
await self.process(start_component_id=start_component_id)
await self.process(start_component_id=start_component_id, fallback_to_env_vars=fallback_to_env_vars)
self.increment_run_count()
except Exception as exc:
logger.exception(exc)
@ -315,6 +316,7 @@ class Graph:
outputs: Optional[list[str]] = None,
session_id: Optional[str] = None,
stream: bool = False,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]:
"""
Run the graph with the given inputs and return the outputs.
@ -340,6 +342,7 @@ class Graph:
outputs=outputs,
session_id=session_id,
stream=stream,
fallback_to_env_vars=fallback_to_env_vars,
)
try:
@ -362,6 +365,7 @@ class Graph:
outputs: Optional[list[str]] = None,
session_id: Optional[str] = None,
stream: bool = False,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]:
"""
Runs the graph with the given inputs.
@ -403,6 +407,7 @@ class Graph:
outputs=outputs or [],
stream=stream,
session_id=session_id or "",
fallback_to_env_vars=fallback_to_env_vars,
)
run_output_object = RunOutputs(inputs=run_inputs, outputs=run_outputs)
logger.debug(f"Run outputs: {run_output_object}")
@ -468,9 +473,9 @@ class Graph:
"""Marks a branch of the graph."""
if visited is None:
visited = set()
visited.add(vertex_id)
if vertex_id in visited:
return
visited.add(vertex_id)
self.mark_vertex(vertex_id, state)
@ -712,6 +717,7 @@ class Graph:
vertex_id: str,
inputs_dict: Optional[Dict[str, str]] = None,
user_id: Optional[str] = None,
fallback_to_env_vars: bool = False,
):
"""
Builds a vertex in the graph.
@ -733,7 +739,7 @@ class Graph:
vertex = self.get_vertex(vertex_id)
try:
if not vertex.frozen or not vertex._built:
await vertex.build(user_id=user_id, inputs=inputs_dict)
await vertex.build(user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars)
if vertex.result is not None:
params = vertex._built_object_repr()
@ -796,7 +802,7 @@ class Graph:
vertices.append(vertex)
return vertices
async def process(self, start_component_id: Optional[str] = None) -> "Graph":
async def process(self, fallback_to_env_vars: bool, start_component_id: Optional[str] = None) -> "Graph":
"""Processes the graph with vertices in each layer run in parallel."""
first_layer = self.sort_vertices(start_component_id=start_component_id)
@ -821,6 +827,7 @@ class Graph:
vertex_id=vertex_id,
user_id=self.user_id,
inputs_dict={},
fallback_to_env_vars=fallback_to_env_vars,
),
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
)
@ -987,8 +994,8 @@ class Graph:
"""Returns the node class based on the node type."""
# First we check for the node_base_type
node_name = node_id.split("-")[0]
if node_name in ["ChatOutput", "ChatInput"]:
return ChatVertex
if node_name in InterfaceComponentTypes:
return InterfaceVertex
elif node_name in ["SharedState", "Notify", "Listen"]:
return StateVertex
elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:

View file

@ -1,3 +1,4 @@
from langflow.graph.schema import CHAT_COMPONENTS
from langflow.graph.vertex import types
from langflow.interface.agents.base import agent_creator
from langflow.interface.custom.base import custom_component_creator
@ -13,8 +14,6 @@ from langflow.interface.tools.base import tool_creator
from langflow.interface.wrappers.base import wrapper_creator
from langflow.utils.lazy_load import LazyLoadDictBase
CHAT_COMPONENTS = ["ChatInput", "ChatOutput", "TextInput", "SessionID"]
class VertexTypesDict(LazyLoadDictBase):
def __init__(self):
@ -47,7 +46,7 @@ class VertexTypesDict(LazyLoadDictBase):
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
**{t: types.ChatVertex for t in CHAT_COMPONENTS},
**{t: types.InterfaceVertex for t in CHAT_COMPONENTS},
}
def get_custom_component_vertex_type(self):

View file

@ -30,6 +30,7 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
ChatOutput = "ChatOutput"
TextInput = "TextInput"
TextOutput = "TextOutput"
RecordsOutput = "RecordsOutput"
def __contains__(cls, item):
try:
@ -40,6 +41,8 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
return True
CHAT_COMPONENTS = [InterfaceComponentTypes.ChatInput, InterfaceComponentTypes.ChatOutput]
RECORDS_COMPONENTS = [InterfaceComponentTypes.RecordsOutput]
INPUT_COMPONENTS = [
InterfaceComponentTypes.ChatInput,
InterfaceComponentTypes.TextInput,

View file

@ -10,7 +10,7 @@ from loguru import logger
from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData
from langflow.graph.utils import UnbuiltObject, UnbuiltResult
from langflow.graph.vertex.utils import generate_result
from langflow.graph.vertex.utils import generate_result, log_transaction
from langflow.interface.initialize import loading
from langflow.interface.listing import lazy_load_dict
from langflow.schema.schema import INPUT_FIELD_NAME
@ -317,7 +317,11 @@ class Vertex:
params[field_name] = full_path
elif field.get("required"):
field_display_name = field.get("display_name")
raise ValueError(f"File path not found for {field_display_name} in component {self.display_name}")
logger.warning(
f"File path not found for {field_display_name} in component {self.display_name}. Setting to None."
)
params[field_name] = None
elif field.get("type") in DIRECT_TYPES and params.get(field_name) is None:
val = field.get("value")
if field.get("type") == "code":
@ -392,13 +396,17 @@ class Vertex:
self.params = self._raw_params.copy()
self.updated_raw_params = True
async def _build(self, user_id=None):
async def _build(
self,
fallback_to_env_vars,
user_id=None,
):
"""
Initiate the build process.
"""
logger.debug(f"Building {self.display_name}")
await self._build_each_vertex_in_params_dict(user_id)
await self._get_and_instantiate_class(user_id)
await self._get_and_instantiate_class(user_id, fallback_to_env_vars)
self._validate_built_object()
self._built = True
@ -434,7 +442,11 @@ class Vertex:
# to the frontend
self.set_artifacts()
artifacts = self.artifacts
messages = self.extract_messages_from_artifacts(artifacts)
if isinstance(artifacts, dict):
messages = self.extract_messages_from_artifacts(artifacts)
else:
messages = []
result_dict = ResultData(
results=result_dict,
artifacts=artifacts,
@ -502,7 +514,7 @@ class Vertex:
if not self._is_vertex(value):
self.params[key][sub_key] = value
else:
result = await value.get_result()
result = await value.get_result(self)
self.params[key][sub_key] = result
def _is_vertex(self, value):
@ -517,9 +529,7 @@ class Vertex:
"""
return all(self._is_vertex(vertex) for vertex in value)
async def get_result(
self,
) -> Any:
async def get_result(self, requester: "Vertex") -> Any:
"""
Retrieves the result of the vertex.
@ -529,9 +539,9 @@ class Vertex:
The result of the vertex.
"""
async with self._lock:
return await self._get_result()
return await self._get_result(requester)
async def _get_result(self) -> Any:
async def _get_result(self, requester: "Vertex") -> Any:
"""
Retrieves the result of the built component.
@ -541,15 +551,19 @@ class Vertex:
The built result if use_result is True, else the built object.
"""
if not self._built:
log_transaction(source=self, target=requester, flow_id=self.graph.flow_id, status="error")
raise ValueError(f"Component {self.display_name} has not been built yet")
return self._built_result if self.use_result else self._built_object
result = self._built_result if self.use_result else self._built_object
log_transaction(source=self, target=requester, flow_id=self.graph.flow_id, status="success")
return result
async def _build_vertex_and_update_params(self, key, vertex: "Vertex"):
"""
Builds a given vertex and updates the params dictionary accordingly.
"""
result = await vertex.get_result()
result = await vertex.get_result(self)
self._handle_func(key, result)
if isinstance(result, list):
self._extend_params_list_with_result(key, result)
@ -565,7 +579,7 @@ class Vertex:
"""
self.params[key] = []
for vertex in vertices:
result = await vertex.get_result()
result = await vertex.get_result(self)
# Weird check to see if the params[key] is a list
# because sometimes it is a Record and breaks the code
if not isinstance(self.params[key], list):
@ -608,7 +622,7 @@ class Vertex:
if isinstance(self.params[key], list):
self.params[key].extend(result)
async def _get_and_instantiate_class(self, user_id=None):
async def _get_and_instantiate_class(self, user_id=None, fallback_to_env_vars=False):
"""
Gets the class from a dictionary and instantiates it with the params.
"""
@ -617,6 +631,7 @@ class Vertex:
try:
result = await loading.instantiate_class(
user_id=user_id,
fallback_to_env_vars=fallback_to_env_vars,
vertex=self,
)
self._update_built_object_and_artifacts(result)

View file

@ -6,14 +6,14 @@ import yaml
from langchain_core.messages import AIMessage
from loguru import logger
from langflow.graph.schema import InterfaceComponentTypes
from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes
from langflow.graph.utils import UnbuiltObject, flatten_list, serialize_field
from langflow.graph.vertex.base import Vertex
from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.schema import Record
from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.monitor.utils import log_vertex_build
from langflow.utils.schemas import ChatOutputResponse
from langflow.utils.schemas import ChatOutputResponse, RecordOutputResponse
from langflow.utils.util import unescape_string
@ -309,7 +309,7 @@ class CustomComponentVertex(Vertex):
return self.artifacts["repr"] or super()._built_object_repr()
class ChatVertex(Vertex):
class InterfaceVertex(Vertex):
def __init__(self, data: Dict, graph):
super().__init__(data, graph=graph, base_type="custom_components", is_task=True)
self.steps = [self._build, self._run]
@ -325,56 +325,131 @@ class ChatVertex(Vertex):
return f"Task {self.task_id} is not running"
if self.artifacts:
# dump as a yaml string
artifacts = {k.title().replace("_", " "): v for k, v in self.artifacts.items() if v is not None}
if isinstance(self.artifacts, dict):
_artifacts = [self.artifacts]
elif hasattr(self.artifacts, "records"):
_artifacts = self.artifacts.records
else:
_artifacts = self.artifacts
artifacts = []
for artifact in _artifacts:
# artifacts = {k.title().replace("_", " "): v for k, v in self.artifacts.items() if v is not None}
artifact = {k.title().replace("_", " "): v for k, v in artifact.items() if v is not None}
artifacts.append(artifact)
yaml_str = yaml.dump(artifacts, default_flow_style=False, allow_unicode=True)
return yaml_str
return super()._built_object_repr()
def _process_chat_component(self):
"""
Process the chat component and return the message.
This method processes the chat component by extracting the necessary parameters
such as sender, sender_name, and message from the `params` dictionary. It then
performs additional operations based on the type of the `_built_object` attribute.
If `_built_object` is an instance of `AIMessage`, it creates a `ChatOutputResponse`
object using the `from_message` method. If `_built_object` is not an instance of
`UnbuiltObject`, it checks the type of `_built_object` and performs specific
operations accordingly. If `_built_object` is a dictionary, it converts it into a
code block. If `_built_object` is an instance of `Record`, it assigns the `text`
attribute to the `message` variable. If `message` is an instance of `AsyncIterator`
or `Iterator`, it builds a stream URL and sets `message` to an empty string. If
`_built_object` is not a string, it converts it to a string. If `message` is a
generator or iterator, it assigns it to the `message` variable. Finally, it creates
a `ChatOutputResponse` object using the extracted parameters and assigns it to the
`artifacts` attribute. If `artifacts` is not None, it calls the `model_dump` method
on it and assigns the result to the `artifacts` attribute. It then returns the
`message` variable.
Returns:
str: The processed message.
"""
artifacts = None
sender = self.params.get("sender", None)
sender_name = self.params.get("sender_name", None)
message = self.params.get(INPUT_FIELD_NAME, None)
if isinstance(message, str):
message = unescape_string(message)
stream_url = None
if isinstance(self._built_object, AIMessage):
artifacts = ChatOutputResponse.from_message(
self._built_object,
sender=sender,
sender_name=sender_name,
)
elif not isinstance(self._built_object, UnbuiltObject):
if isinstance(self._built_object, dict):
# Turn the dict into a pleasing to
# read JSON inside a code block
message = dict_to_codeblock(self._built_object)
elif isinstance(self._built_object, Record):
message = self._built_object.text
elif isinstance(message, (AsyncIterator, Iterator)):
stream_url = self.build_stream_url()
message = ""
elif not isinstance(self._built_object, str):
message = str(self._built_object)
# if the message is a generator or iterator
# it means that it is a stream of messages
else:
message = self._built_object
artifacts = ChatOutputResponse(
message=message,
sender=sender,
sender_name=sender_name,
stream_url=stream_url,
)
self.will_stream = stream_url is not None
if artifacts:
self.artifacts = artifacts.model_dump(exclude_none=True)
return message
def _process_record_component(self):
"""
Process the record component of the vertex.
If the built object is an instance of `Record`, it calls the `model_dump` method
and assigns the result to the `artifacts` attribute.
If the built object is a list, it iterates over each element and checks if it is
an instance of `Record`. If it is, it calls the `model_dump` method and appends
the result to the `artifacts` list. If it is not, it raises a `ValueError` if the
`ignore_errors` parameter is set to `False`, or logs an error message if it is set
to `True`.
Returns:
The built object.
Raises:
ValueError: If an element in the list is not an instance of `Record` and
`ignore_errors` is set to `False`.
"""
if isinstance(self._built_object, Record):
artifacts = [self._built_object.data]
elif isinstance(self._built_object, list):
artifacts = []
ignore_errors = self.params.get("ignore_errors", False)
for record in self._built_object:
if isinstance(record, Record):
artifacts.append(record.data)
elif ignore_errors:
logger.error(f"Record expected, but got {record} of type {type(record)}")
else:
raise ValueError(f"Record expected, but got {record} of type {type(record)}")
self.artifacts = RecordOutputResponse(records=artifacts)
return self._built_object
async def _run(self, *args, **kwargs):
if self.is_interface_component:
if self.vertex_type in ["ChatOutput", "ChatInput"]:
artifacts = None
sender = self.params.get("sender", None)
sender_name = self.params.get("sender_name", None)
message = self.params.get(INPUT_FIELD_NAME, None)
if isinstance(message, str):
message = unescape_string(message)
stream_url = None
if isinstance(self._built_object, AIMessage):
artifacts = ChatOutputResponse.from_message(
self._built_object,
sender=sender,
sender_name=sender_name,
)
elif not isinstance(self._built_object, UnbuiltObject):
if isinstance(self._built_object, dict):
# Turn the dict into a pleasing to
# read JSON inside a code block
message = dict_to_codeblock(self._built_object)
elif isinstance(self._built_object, Record):
message = self._built_object.text
elif isinstance(message, (AsyncIterator, Iterator)):
stream_url = self.build_stream_url()
message = ""
elif not isinstance(self._built_object, str):
message = str(self._built_object)
# if the message is a generator or iterator
# it means that it is a stream of messages
else:
message = self._built_object
artifacts = ChatOutputResponse(
message=message,
sender=sender,
sender_name=sender_name,
stream_url=stream_url,
)
self.will_stream = stream_url is not None
if artifacts:
self.artifacts = artifacts.model_dump(exclude_none=True)
if self.vertex_type in CHAT_COMPONENTS:
message = self._process_chat_component()
elif self.vertex_type in RECORDS_COMPONENTS:
message = self._process_record_component()
if isinstance(self._built_object, (AsyncIterator, Iterator)):
if self.params["return_record"]:
if self.params.get("return_record", False):
self._built_object = Record(text=message, data=self.artifacts)
else:
self._built_object = message

View file

@ -1,11 +1,15 @@
from typing import Any, Optional, Union
from typing import Any, Optional, Union, TYPE_CHECKING
from langchain_core.messages import BaseMessage
from langchain_core.runnables import Runnable
from loguru import logger
from langflow.services.deps import get_monitor_service
from langflow.utils.constants import PYTHON_BASIC_TYPES
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
def is_basic_type(obj):
return type(obj) in PYTHON_BASIC_TYPES
@ -63,3 +67,49 @@ async def generate_result(built_object: Any, inputs: dict, has_external_output:
else:
result = built_object
return result
def build_clean_params(target: "Vertex") -> dict:
"""
Cleans the parameters of the target vertex.
"""
# Removes all keys that the values aren't python types like str, int, bool, etc.
params = {
key: value for key, value in target.params.items() if isinstance(value, (str, int, bool, float, list, dict))
}
# if it is a list we need to check if the contents are python types
for key, value in params.items():
if isinstance(value, list):
params[key] = [item for item in value if isinstance(item, (str, int, bool, float, list, dict))]
return params
def log_transaction(source: "Vertex", target: "Vertex", flow_id, status, error=None):
"""
Logs a transaction between two vertices.
Args:
source (Vertex): The source vertex of the transaction.
target (Vertex): The target vertex of the transaction.
status: The status of the transaction.
error (Optional): Any error associated with the transaction.
Raises:
Exception: If there is an error while logging the transaction.
"""
try:
monitor_service = get_monitor_service()
clean_params = build_clean_params(target)
data = {
"source": source.vertex_type,
"target": target.vertex_type,
"target_args": clean_params,
"timestamp": monitor_service.get_timestamp(),
"status": status,
"error": error,
"flow_id": flow_id,
}
monitor_service.add_row(table_name="transactions", data=data)
except Exception as e:
logger.error(f"Error logging transaction: {e}")

View file

@ -11,10 +11,11 @@ from sqlmodel import select
from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES
from langflow.interface.types import get_all_components
from langflow.services.database.models.flow.model import Flow, FlowCreate
from langflow.services.database.models.folder.model import Folder, FolderCreate
from langflow.services.deps import get_settings_service, session_scope
STARTER_FOLDER_NAME = "Starter Projects"
STARTER_FOLDER_DESCRIPTION = "Starter projects to help you get started in Langflow."
# In the folder ./starter_projects we have a few JSON files that represent
# starter projects. We want to load these into the database so that users
@ -158,6 +159,7 @@ def create_new_project(
project_data,
project_icon,
project_icon_bg_color,
new_folder_id
):
logger.debug(f"Creating starter project {project_name}")
new_project = FlowCreate(
@ -168,33 +170,41 @@ def create_new_project(
data=project_data,
is_component=project_is_component,
updated_at=updated_at_datetime,
folder=STARTER_FOLDER_NAME,
folder_id=new_folder_id,
)
db_flow = Flow.model_validate(new_project, from_attributes=True)
session.add(db_flow)
def get_all_flows_similar_to_project(session, project_name):
flows = session.exec(
select(Flow).where(
Flow.name == project_name,
Flow.folder == STARTER_FOLDER_NAME,
)
).all()
def get_all_flows_similar_to_project(session, folder_id):
flows = session.exec(select(Folder).where(Folder.id == folder_id)).first().flows
return flows
def delete_start_projects(session):
flows = session.exec(
select(Flow).where(
Flow.folder == STARTER_FOLDER_NAME,
)
).all()
def delete_start_projects(session, folder_id):
flows = session.exec(select(Folder).where(Folder.id == folder_id)).first().flows
for flow in flows:
session.delete(flow)
session.commit()
def folder_exists(session, folder_name):
folder = session.exec(select(Folder).where(Folder.name == folder_name)).first()
return folder is not None
def create_starter_folder(session):
if not folder_exists(session, STARTER_FOLDER_NAME):
new_folder = FolderCreate(name=STARTER_FOLDER_NAME, description=STARTER_FOLDER_DESCRIPTION)
db_folder = Folder.model_validate(new_folder, from_attributes=True)
session.add(db_folder)
session.commit()
session.refresh(db_folder)
return db_folder
else:
return session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
def create_or_update_starter_projects():
components_paths = get_settings_service().settings.COMPONENTS_PATH
try:
@ -203,8 +213,9 @@ def create_or_update_starter_projects():
logger.exception(f"Error loading components: {e}")
raise e
with session_scope() as session:
new_folder = create_starter_folder(session)
starter_projects = load_starter_projects()
delete_start_projects(session)
delete_start_projects(session, new_folder.id)
for project_path, project in starter_projects:
(
project_name,
@ -224,7 +235,7 @@ def create_or_update_starter_projects():
update_project_file(project_path, project, updated_project_data)
if project_name and project_data:
for existing_project in get_all_flows_similar_to_project(session, project_name):
for existing_project in get_all_flows_similar_to_project(session, new_folder.id):
session.delete(existing_project)
create_new_project(
@ -236,4 +247,5 @@ def create_or_update_starter_projects():
project_data,
project_icon,
project_icon_bg_color,
new_folder.id
)

View file

@ -262,7 +262,7 @@
"load_from_db": true,
"title_case": false,
"input_types": ["Text"],
"value": ""
"value": "OPENAI_API_KEY"
},
"stream": {
"type": "bool",
@ -716,9 +716,9 @@
"edges": [
{
"source": "OpenAIModel-k39HS",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-k39HSœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153}",
"target": "ChatOutput-njtka",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-njtkaœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-njtka\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -736,13 +736,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-k39HS{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-k39HSœ}-ChatOutput-njtka{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-njtkaœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-OpenAIModel-k39HS{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153}-ChatOutput-njtka{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-njtka\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "Prompt-uxBqP",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-uxBqPœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153}",
"target": "OpenAIModel-k39HS",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-k39HSœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -760,13 +760,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-uxBqP{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-uxBqPœ}-OpenAIModel-k39HS{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-k39HSœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-Prompt-uxBqP{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153}-OpenAIModel-k39HS{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "ChatInput-P3fgL",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-P3fgLœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-P3fgL\u0153}",
"target": "Prompt-uxBqP",
"targetHandle": "{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-uxBqPœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153user_input\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "user_input",
@ -784,7 +784,7 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-P3fgL{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-P3fgLœ}-Prompt-uxBqP{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-uxBqPœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-ChatInput-P3fgL{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-P3fgL\u0153}-Prompt-uxBqP{\u0153fieldName\u0153:\u0153user_input\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}
],
"viewport": {

View file

@ -566,7 +566,7 @@
"load_from_db": false,
"title_case": false,
"input_types": ["Text"],
"value": ""
"value": "OPENAI_API_KEY"
},
"stream": {
"type": "bool",
@ -854,9 +854,9 @@
{
"source": "URL-HYPkR",
"target": "Prompt-Rse03",
"sourceHandle": "{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-HYPkRœ}",
"targetHandle": "{œfieldNameœ:œreference_2œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"id": "reactflow__edge-URL-HYPkR{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-HYPkRœ}-Prompt-Rse03{œfieldNameœ:œreference_2œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-HYPkR\u0153}",
"targetHandle": "{\u0153fieldName\u0153:\u0153reference_2\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"id": "reactflow__edge-URL-HYPkR{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-HYPkR\u0153}-Prompt-Rse03{\u0153fieldName\u0153:\u0153reference_2\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "reference_2",
@ -878,9 +878,9 @@
},
{
"source": "OpenAIModel-gi29P",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-gi29Pœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-gi29P\u0153}",
"target": "ChatOutput-JPlxl",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-JPlxlœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-JPlxl\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -898,13 +898,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-gi29P{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-gi29Pœ}-ChatOutput-JPlxl{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-JPlxlœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-OpenAIModel-gi29P{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-gi29P\u0153}-ChatOutput-JPlxl{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-JPlxl\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "URL-2cX90",
"sourceHandle": "{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-2cX90œ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-2cX90\u0153}",
"target": "Prompt-Rse03",
"targetHandle": "{œfieldNameœ:œreference_1œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153reference_1\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "reference_1",
@ -922,13 +922,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-URL-2cX90{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-2cX90œ}-Prompt-Rse03{œfieldNameœ:œreference_1œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-URL-2cX90{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-2cX90\u0153}-Prompt-Rse03{\u0153fieldName\u0153:\u0153reference_1\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "TextInput-og8Or",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œTextInputœ,œidœ:œTextInput-og8Orœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153TextInput\u0153,\u0153id\u0153:\u0153TextInput-og8Or\u0153}",
"target": "Prompt-Rse03",
"targetHandle": "{œfieldNameœ:œinstructionsœ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153instructions\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "instructions",
@ -946,13 +946,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-TextInput-og8Or{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œTextInputœ,œidœ:œTextInput-og8Orœ}-Prompt-Rse03{œfieldNameœ:œinstructionsœ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-TextInput-og8Or{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153TextInput\u0153,\u0153id\u0153:\u0153TextInput-og8Or\u0153}-Prompt-Rse03{\u0153fieldName\u0153:\u0153instructions\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "Prompt-Rse03",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-Rse03œ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153}",
"target": "OpenAIModel-gi29P",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-gi29Pœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-gi29P\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -970,7 +970,7 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-Rse03{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-Rse03œ}-OpenAIModel-gi29P{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-gi29Pœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"id": "reactflow__edge-Prompt-Rse03{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153}-OpenAIModel-gi29P{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-gi29P\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"selected": false
}
],

View file

@ -711,7 +711,7 @@
"load_from_db": false,
"title_case": false,
"input_types": ["Text"],
"value": ""
"value": "OPENAI_API_KEY"
},
"stream": {
"type": "bool",
@ -825,9 +825,9 @@
"edges": [
{
"source": "ChatInput-MsSJ9",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œRecordœ,œTextœ,œobjectœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-MsSJ9œ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Record\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-MsSJ9\u0153}",
"target": "Prompt-tHwPf",
"targetHandle": "{œfieldNameœ:œQuestionœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153Question\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "Question",
@ -845,13 +845,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-MsSJ9{œbaseClassesœ:[œstrœ,œRecordœ,œTextœ,œobjectœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-MsSJ9œ}-Prompt-tHwPf{œfieldNameœ:œQuestionœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-ChatInput-MsSJ9{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Record\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-MsSJ9\u0153}-Prompt-tHwPf{\u0153fieldName\u0153:\u0153Question\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "File-6TEsD",
"sourceHandle": "{œbaseClassesœ:[œRecordœ],œdataTypeœ:œFileœ,œidœ:œFile-6TEsDœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153File\u0153,\u0153id\u0153:\u0153File-6TEsD\u0153}",
"target": "Prompt-tHwPf",
"targetHandle": "{œfieldNameœ:œDocumentœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153Document\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "Document",
@ -869,13 +869,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-File-6TEsD{œbaseClassesœ:[œRecordœ],œdataTypeœ:œFileœ,œidœ:œFile-6TEsDœ}-Prompt-tHwPf{œfieldNameœ:œDocumentœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-File-6TEsD{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153File\u0153,\u0153id\u0153:\u0153File-6TEsD\u0153}-Prompt-tHwPf{\u0153fieldName\u0153:\u0153Document\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "Prompt-tHwPf",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-tHwPfœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153}",
"target": "OpenAIModel-Bt067",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-Bt067œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -893,13 +893,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-tHwPf{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-tHwPfœ}-OpenAIModel-Bt067{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-Bt067œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-Prompt-tHwPf{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153}-OpenAIModel-Bt067{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "OpenAIModel-Bt067",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-Bt067œ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153}",
"target": "ChatOutput-F5Awj",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-F5Awjœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-F5Awj\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -917,7 +917,7 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-Bt067{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-Bt067œ}-ChatOutput-F5Awj{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-F5Awjœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-OpenAIModel-Bt067{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153}-ChatOutput-F5Awj{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-F5Awj\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}
],
"viewport": {

View file

@ -1,6 +1,6 @@
{
"id": "08d5cccf-d098-4367-b14b-1078429c9ed9",
"icon": "🤖",
"icon": "\ud83e\udd16",
"icon_bg_color": "#FFD700",
"data": {
"nodes": [
@ -792,7 +792,7 @@
"load_from_db": false,
"title_case": false,
"input_types": ["Text"],
"value": ""
"value": "OPENAI_API_KEY"
},
"stream": {
"type": "bool",
@ -1003,9 +1003,9 @@
"edges": [
{
"source": "MemoryComponent-cdA1J",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}",
"target": "Prompt-ODkUx",
"targetHandle": "{œfieldNameœ:œcontextœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153context\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "context",
@ -1023,14 +1023,14 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-MemoryComponent-cdA1J{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}-Prompt-ODkUx{œfieldNameœ:œcontextœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"id": "reactflow__edge-MemoryComponent-cdA1J{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}-Prompt-ODkUx{\u0153fieldName\u0153:\u0153context\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"selected": false
},
{
"source": "ChatInput-t7F8v",
"sourceHandle": "{œbaseClassesœ:[œTextœ,œobjectœ,œRecordœ,œstrœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-t7F8vœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-t7F8v\u0153}",
"target": "Prompt-ODkUx",
"targetHandle": "{œfieldNameœ:œuser_messageœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153user_message\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "user_message",
@ -1048,14 +1048,14 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-t7F8v{œbaseClassesœ:[œTextœ,œobjectœ,œRecordœ,œstrœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-t7F8vœ}-Prompt-ODkUx{œfieldNameœ:œuser_messageœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"id": "reactflow__edge-ChatInput-t7F8v{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-t7F8v\u0153}-Prompt-ODkUx{\u0153fieldName\u0153:\u0153user_message\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"selected": false
},
{
"source": "Prompt-ODkUx",
"sourceHandle": "{œbaseClassesœ:[œTextœ,œstrœ,œobjectœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-ODkUxœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153str\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153}",
"target": "OpenAIModel-9RykF",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-9RykFœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1073,13 +1073,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-ODkUx{œbaseClassesœ:[œTextœ,œstrœ,œobjectœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-ODkUxœ}-OpenAIModel-9RykF{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-9RykFœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-Prompt-ODkUx{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153str\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153}-OpenAIModel-9RykF{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "OpenAIModel-9RykF",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œobjectœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-9RykFœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153object\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153}",
"target": "ChatOutput-P1jEe",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-P1jEeœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-P1jEe\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1097,13 +1097,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-9RykF{œbaseClassesœ:[œstrœ,œobjectœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-9RykFœ}-ChatOutput-P1jEe{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-P1jEeœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-OpenAIModel-9RykF{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153object\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153}-ChatOutput-P1jEe{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-P1jEe\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "MemoryComponent-cdA1J",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}",
"target": "TextOutput-vrs6T",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-vrs6Tœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-vrs6T\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1121,7 +1121,7 @@
"stroke": "#555"
},
"className": "stroke-foreground stroke-connection",
"id": "reactflow__edge-MemoryComponent-cdA1J{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}-TextOutput-vrs6T{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-vrs6Tœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-MemoryComponent-cdA1J{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}-TextOutput-vrs6T{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-vrs6T\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}
],
"viewport": {

View file

@ -911,7 +911,7 @@
"load_from_db": false,
"title_case": false,
"input_types": ["Text"],
"value": ""
"value": "OPENAI_API_KEY"
},
"stream": {
"type": "bool",
@ -1382,9 +1382,9 @@
"edges": [
{
"source": "TextInput-sptaH",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œTextInputœ,œidœ:œTextInput-sptaHœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153TextInput\u0153,\u0153id\u0153:\u0153TextInput-sptaH\u0153}",
"target": "Prompt-amqBu",
"targetHandle": "{œfieldNameœ:œdocumentœ,œidœ:œPrompt-amqBuœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153document\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "document",
@ -1402,13 +1402,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-TextInput-sptaH{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œTextInputœ,œidœ:œTextInput-sptaHœ}-Prompt-amqBu{œfieldNameœ:œdocumentœ,œidœ:œPrompt-amqBuœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-TextInput-sptaH{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153TextInput\u0153,\u0153id\u0153:\u0153TextInput-sptaH\u0153}-Prompt-amqBu{\u0153fieldName\u0153:\u0153document\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "Prompt-amqBu",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-amqBuœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153}",
"target": "TextOutput-2MS4a",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-2MS4aœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-2MS4a\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1426,13 +1426,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-amqBu{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-amqBuœ}-TextOutput-2MS4a{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-2MS4aœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-Prompt-amqBu{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153}-TextOutput-2MS4a{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-2MS4a\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "Prompt-amqBu",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-amqBuœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153}",
"target": "OpenAIModel-uYXZJ",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-uYXZJœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1450,13 +1450,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-amqBu{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-amqBuœ}-OpenAIModel-uYXZJ{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-uYXZJœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-Prompt-amqBu{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153}-OpenAIModel-uYXZJ{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "OpenAIModel-uYXZJ",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-uYXZJœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153}",
"target": "Prompt-gTNiz",
"targetHandle": "{œfieldNameœ:œsummaryœ,œidœ:œPrompt-gTNizœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153summary\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "summary",
@ -1474,13 +1474,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-uYXZJ{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-uYXZJœ}-Prompt-gTNiz{œfieldNameœ:œsummaryœ,œidœ:œPrompt-gTNizœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-OpenAIModel-uYXZJ{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153}-Prompt-gTNiz{\u0153fieldName\u0153:\u0153summary\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "OpenAIModel-uYXZJ",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-uYXZJœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153}",
"target": "ChatOutput-EJkG3",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-EJkG3œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-EJkG3\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1498,13 +1498,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-uYXZJ{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-uYXZJœ}-ChatOutput-EJkG3{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-EJkG3œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-OpenAIModel-uYXZJ{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153}-ChatOutput-EJkG3{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-EJkG3\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "Prompt-gTNiz",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-gTNizœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153}",
"target": "TextOutput-MUDOR",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-MUDORœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-MUDOR\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1522,13 +1522,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-gTNiz{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-gTNizœ}-TextOutput-MUDOR{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-MUDORœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-Prompt-gTNiz{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153}-TextOutput-MUDOR{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-MUDOR\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "Prompt-gTNiz",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-gTNizœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153}",
"target": "OpenAIModel-XawYB",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-XawYBœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-XawYB\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1546,13 +1546,13 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-gTNiz{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-gTNizœ}-OpenAIModel-XawYB{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-XawYBœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-Prompt-gTNiz{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153}-OpenAIModel-XawYB{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-XawYB\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
},
{
"source": "OpenAIModel-XawYB",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-XawYBœ}",
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-XawYB\u0153}",
"target": "ChatOutput-DNmvg",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-DNmvgœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-DNmvg\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": {
"targetHandle": {
"fieldName": "input_value",
@ -1570,7 +1570,7 @@
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-XawYB{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-XawYBœ}-ChatOutput-DNmvg{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-DNmvgœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
"id": "reactflow__edge-OpenAIModel-XawYB{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-XawYB\u0153}-ChatOutput-DNmvg{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-DNmvg\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}
],
"viewport": {

File diff suppressed because one or more lines are too long

View file

@ -5,7 +5,6 @@ from langchain.agents.agent_toolkits import VectorStoreInfo, VectorStoreRouterTo
from langchain.agents.agent_toolkits.vectorstore.prompt import PREFIX as VECTORSTORE_PREFIX
from langchain.agents.agent_toolkits.vectorstore.prompt import ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.base_language import BaseLanguageModel
from langchain.chains.llm import LLMChain
from langchain_community.utilities import SQLDatabase
from langchain.tools.sql_database.prompt import QUERY_CHECKER
@ -18,6 +17,14 @@ from langchain_experimental.agents.agent_toolkits.pandas.prompt import SUFFIX_WI
from langchain_experimental.tools.python.tool import PythonAstREPLTool
from langflow.interface.base import CustomAgentExecutor
from langchain_community.tools import (
InfoSQLDatabaseTool,
ListSQLDatabaseTool,
QuerySQLCheckerTool,
QuerySQLDataBaseTool,
)
from langchain_core.language_models import BaseLanguageModel
from langchain_core.prompts import PromptTemplate
class JsonAgent(CustomAgentExecutor):
@ -165,17 +172,6 @@ class SQLAgent(CustomAgentExecutor):
db = SQLDatabase.from_uri(database_uri)
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
# The right code should be this, but there is a problem with tools = toolkit.get_tools()
# related to `OPENAI_API_KEY`
# return create_sql_agent(llm=llm, toolkit=toolkit, verbose=True)
from langchain.prompts import PromptTemplate
from langchain.tools.sql_database.tool import (
InfoSQLDatabaseTool,
ListSQLDatabaseTool,
QuerySQLCheckerTool,
QuerySQLDataBaseTool,
)
llmchain = LLMChain(
llm=llm,
prompt=PromptTemplate(template=QUERY_CHECKER, input_variables=["query", "dialect"]),

View file

@ -1,9 +1,9 @@
from langchain.chains.llm import LLMChain
from langchain.agents import AgentExecutor, ZeroShotAgent
from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.base_language import BaseLanguageModel
from langchain_community.agent_toolkits import JsonToolkit
from langchain_core.language_models import BaseLanguageModel
class MalfoyAgent(AgentExecutor):

View file

@ -1,14 +1,13 @@
from typing import Dict, Optional, Type, Union
from langchain.base_language import BaseLanguageModel
from langchain.chains import ConversationChain
from langchain.chains.question_answering import load_qa_chain
from langchain.memory.buffer import ConversationBufferMemory
from langchain.schema import BaseMemory
from pydantic.v1 import Field, root_validator
from langflow.interface.base import CustomChain
from langflow.interface.utils import extract_input_variables_from_prompt
from langchain_core.language_models import BaseLanguageModel
from langchain_core.memory import BaseMemory
DEFAULT_SUFFIX = """"
Current conversation:

View file

@ -3,11 +3,13 @@ from typing import Any
from langchain import llms, memory, text_splitter
from langchain_community import agent_toolkits, document_loaders, embeddings
from langchain_community.chat_models import AzureChatOpenAI, ChatAnthropic, ChatOpenAI, ChatVertexAI
from langchain_community.chat_models import ChatVertexAI
from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.chains.custom import CUSTOM_CHAINS
from langflow.interface.importing.utils import import_class
from langchain_anthropic import ChatAnthropic
from langchain_openai import AzureChatOpenAI, ChatOpenAI
# LLMs
llm_type_to_cls_dict = {}

View file

@ -4,13 +4,13 @@ import importlib
from typing import Any, Type
from langchain.agents import Agent
from langchain.base_language import BaseLanguageModel
from langchain.chains.base import Chain
from langchain.prompts import PromptTemplate
from langchain.tools import BaseTool
from langchain_core.language_models.chat_models import BaseChatModel
from langflow.interface.wrappers.base import wrapper_creator
from langchain_core.language_models import BaseLanguageModel
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import BaseTool
def import_module(module_path: str) -> Any:

View file

@ -1,15 +1,12 @@
import inspect
import json
import os
from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type
import orjson
from langchain.agents import agent as agent_module
from langchain.agents.agent import AgentExecutor
from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.agents.tools import BaseTool
from langchain.chains.base import Chain
from langchain.document_loaders.base import BaseLoader
from langchain_community.vectorstores import VectorStore
from langchain_core.documents import Document
from loguru import logger
from pydantic import ValidationError
@ -26,6 +23,11 @@ from langflow.interface.wrappers.base import wrapper_creator
from langflow.schema.schema import Record
from langflow.utils import validate
from langflow.utils.util import unescape_string
from langchain_community.agent_toolkits.base import BaseToolkit
from langchain_core.document_loaders import BaseLoader
from langchain_core.tools import BaseTool
from langchain_core.vectorstores import VectorStore
from langchain_text_splitters import Language
if TYPE_CHECKING:
from langflow.custom import CustomComponent
@ -34,6 +36,7 @@ if TYPE_CHECKING:
async def instantiate_class(
vertex: "Vertex",
fallback_to_env_vars,
user_id=None,
) -> Any:
"""Instantiate class from module type and key, and params"""
@ -56,7 +59,7 @@ async def instantiate_class(
if not base_type:
raise ValueError("No base type provided for vertex")
if base_type == "custom_components":
return await instantiate_custom_component(params, user_id, vertex)
return await instantiate_custom_component(params, user_id, vertex, fallback_to_env_vars=fallback_to_env_vars)
class_object = import_by_type(_type=base_type, name=vertex_type)
return await instantiate_based_on_type(
class_object=class_object,
@ -65,6 +68,7 @@ async def instantiate_class(
params=params,
user_id=user_id,
vertex=vertex,
fallback_to_env_vars=fallback_to_env_vars,
)
@ -92,14 +96,7 @@ def convert_kwargs(params):
return params
async def instantiate_based_on_type(
class_object,
base_type,
node_type,
params,
user_id,
vertex,
):
async def instantiate_based_on_type(class_object, base_type, node_type, params, user_id, vertex, fallback_to_env_vars):
if base_type == "agents":
return instantiate_agent(node_type, class_object, params)
elif base_type == "prompts":
@ -131,33 +128,49 @@ async def instantiate_based_on_type(
elif base_type == "memory":
return instantiate_memory(node_type, class_object, params)
elif base_type == "custom_components":
return await instantiate_custom_component(
params,
user_id,
vertex,
)
return await instantiate_custom_component(params, user_id, vertex, fallback_to_env_vars=fallback_to_env_vars)
elif base_type == "wrappers":
return instantiate_wrapper(node_type, class_object, params)
else:
return class_object(**params)
def update_params_with_load_from_db_fields(custom_component: "CustomComponent", params, load_from_db_fields):
def update_params_with_load_from_db_fields(
custom_component: "CustomComponent", params, load_from_db_fields, fallback_to_env_vars=False
):
# For each field in load_from_db_fields, we will check if it's in the params
# and if it is, we will get the value from the custom_component.keys(name)
# and update the params with the value
for field in load_from_db_fields:
if field in params:
try:
key = custom_component.variables(params[field])
params[field] = key if key else params[field]
key = None
try:
key = custom_component.variables(params[field])
except ValueError as e:
# check if "User id is not set" is in the error message
if "User id is not set" in str(e) and not fallback_to_env_vars:
raise e
logger.debug(str(e))
if fallback_to_env_vars and key is None:
var = os.getenv(params[field])
if var is None:
raise ValueError(f"Environment variable {params[field]} is not set.")
key = var
logger.info(f"Using environment variable {params[field]} for {field}")
if key is None:
logger.warning(f"Could not get value for {field}. Setting it to None.")
params[field] = key
except Exception as exc:
logger.error(f"Failed to get value for {field} from custom component. Error: {exc}")
pass
logger.error(f"Failed to get value for {field} from custom component. Setting it to None. Error: {exc}")
params[field] = None
return params
async def instantiate_custom_component(params, user_id, vertex):
async def instantiate_custom_component(params, user_id, vertex, fallback_to_env_vars: bool = False):
params_copy = params.copy()
class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
custom_component: "CustomComponent" = class_object(
@ -166,7 +179,9 @@ async def instantiate_custom_component(params, user_id, vertex):
vertex=vertex,
selected_output_type=vertex.selected_output_type,
)
params_copy = update_params_with_load_from_db_fields(custom_component, params_copy, vertex.load_from_db_fields)
params_copy = update_params_with_load_from_db_fields(
custom_component, params_copy, vertex.load_from_db_fields, fallback_to_env_vars
)
if "retriever" in params_copy and hasattr(params_copy["retriever"], "as_retriever"):
params_copy["retriever"] = params_copy["retriever"].as_retriever()
@ -416,8 +431,6 @@ def instantiate_textsplitter(
params["separators"] = [unescape_string(separator) for separator in params["separators"]]
text_splitter = class_object(**params)
else:
from langchain.text_splitter import Language
language = params.pop("separator_type", None)
params["language"] = Language(language)
params.pop("separators", None)

View file

@ -4,9 +4,10 @@ from typing import Any, Dict, List
import orjson
from langchain.agents import ZeroShotAgent
from langchain.schema import BaseOutputParser, Document
from langflow.services.database.models.base import orjson_dumps
from langchain_core.documents import Document
from langchain_core.output_parsers import BaseOutputParser
def handle_node_type(node_type, class_object, params: Dict):

View file

@ -5,14 +5,13 @@ import orjson
from langchain_community.vectorstores import (
FAISS,
Chroma,
ElasticsearchStore,
MongoDBAtlasVectorSearch,
Pinecone,
Qdrant,
SupabaseVectorStore,
Weaviate,
)
from langchain_core.documents import Document
from langchain_pinecone import Pinecone
def docs_in_params(params: dict) -> bool:

View file

@ -1,9 +1,8 @@
from typing import Dict, List, Optional, Type
from langchain.prompts import PromptTemplate
from pydantic.v1 import root_validator
from langflow.interface.utils import extract_input_variables_from_prompt
from langchain_core.prompts import PromptTemplate
# Steps to create a BaseCustomPrompt:
# 1. Create a prompt template that endes with:

View file

@ -1,10 +1,10 @@
from langchain import tools
from langchain.agents import Tool
from langchain.agents.load_tools import _BASE_TOOLS, _EXTRA_LLM_TOOLS, _EXTRA_OPTIONAL_TOOLS, _LLM_TOOLS
from langchain_community.tools.json.tool import JsonSpec
from langflow.interface.importing.utils import import_class
from langflow.interface.tools.custom import PythonFunctionTool
from langchain_core.tools import Tool
FILE_TOOLS = {"JsonSpec": JsonSpec}
CUSTOM_TOOLS = {

View file

@ -1,10 +1,9 @@
from typing import Callable, Optional
from langchain.agents.tools import Tool
from pydantic.v1 import BaseModel, validator
from langflow.interface.custom.utils import get_function
from langflow.utils import validate
from langchain_core.tools import Tool
class Function(BaseModel):

View file

@ -2,8 +2,7 @@ import ast
import inspect
import textwrap
from typing import Dict, Union
from langchain.agents.tools import Tool
from langchain_core.tools import Tool
def get_func_tool_params(func, **kwargs) -> Union[Dict, None]:

View file

@ -7,12 +7,12 @@ from typing import Dict
import yaml
from docstring_parser import parse
from langchain.base_language import BaseLanguageModel
from langflow.services.chat.config import ChatConfig
from langflow.services.deps import get_settings_service
from langflow.utils.util import format_dict, get_base_classes, get_default_factory
from loguru import logger
from PIL.Image import Image
from langchain_core.language_models import BaseLanguageModel
def load_file_into_dict(file_path: str) -> dict:

View file

@ -1,5 +1,5 @@
import warnings
from typing import Optional, Union
from typing import List, Optional, Union
from loguru import logger
@ -60,7 +60,7 @@ def get_messages(
return records
def add_messages(records: Union[list[Record], Record]):
def add_messages(records: Union[list[Record], Record], flow_id: Optional[str] = None):
"""
Add a message to the monitor service.
"""
@ -76,7 +76,8 @@ def add_messages(records: Union[list[Record], Record]):
messages: list[MessageModel] = []
for record in records:
messages.append(MessageModel.from_record(record))
record.timestamp = monitor_service.get_timestamp()
messages.append(MessageModel.from_record(record, flow_id=flow_id))
for message in messages:
try:
@ -107,8 +108,24 @@ def store_message(
session_id: Optional[str] = None,
sender: Optional[str] = None,
sender_name: Optional[str] = None,
) -> list[Record]:
flow_id: Optional[str] = None,
) -> List[Record]:
"""
Stores a message in the memory.
Args:
message (Union[str, Record]): The message to be stored. It can be either a string or a Record object.
session_id (Optional[str]): The session ID associated with the message.
sender (Optional[str]): The sender ID associated with the message.
sender_name (Optional[str]): The name of the sender associated with the message.
flow_id (Optional[str]): The flow ID associated with the message. When running from the CustomComponent you can access this using `self.graph.flow_id`.
Returns:
List[Record]: A list of records containing the stored message.
Raises:
ValueError: If any of the required parameters (session_id, sender, sender_name) is not provided.
"""
if not message:
warnings.warn("No message provided.")
return []
@ -135,4 +152,4 @@ def store_message(
},
)
return add_messages([record])
return add_messages([record], flow_id=flow_id)

View file

@ -1,11 +1,11 @@
from typing import TYPE_CHECKING, List, Union
from langchain.agents.agent import AgentExecutor
from langchain.callbacks.base import BaseCallbackHandler
from loguru import logger
from langflow.processing.process import fix_memory_inputs, format_actions
from langflow.services.deps import get_plugins_service
from langchain_core.callbacks import BaseCallbackHandler
if TYPE_CHECKING:
from langfuse.callback import CallbackHandler # type: ignore

View file

@ -82,6 +82,7 @@ def run_flow_from_json(
env_file: Optional[str] = None,
cache: Optional[str] = None,
disable_logs: Optional[bool] = True,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]:
"""
Run a flow from a JSON file or dictionary.
@ -98,6 +99,7 @@ def run_flow_from_json(
env_file (Optional[str], optional): The environment file to load. Defaults to None.
cache (Optional[str], optional): The cache directory to use. Defaults to None.
disable_logs (Optional[bool], optional): Whether to disable logs. Defaults to True.
fallback_to_env_vars (bool, optional): Whether Global Variables should fallback to environment variables if not found. Defaults to False.
Returns:
List[RunOutputs]: A list of RunOutputs objects representing the results of running the flow.
@ -127,5 +129,6 @@ def run_flow_from_json(
input_type=input_type,
output_type=output_type,
output_component=output_component,
fallback_to_env_vars=fallback_to_env_vars,
)
return result

View file

@ -2,7 +2,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
from langchain.agents import AgentExecutor
from langchain.schema import AgentAction
from loguru import logger
from pydantic import BaseModel
@ -13,6 +12,7 @@ from langflow.interface.run import get_memory_key, update_memory_keys
from langflow.schema.graph import InputValue, Tweaks
from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.session.service import SessionService
from langchain_core.agents import AgentAction
if TYPE_CHECKING:
@ -175,6 +175,7 @@ def run_graph(
input_value: str,
input_type: str,
output_type: str,
fallback_to_env_vars: bool = False,
output_component: Optional[str] = None,
) -> List[RunOutputs]:
"""
@ -218,6 +219,7 @@ def run_graph(
outputs or [],
stream=False,
session_id="",
fallback_to_env_vars=fallback_to_env_vars,
)
return run_outputs

View file

@ -1,10 +1,10 @@
import copy
import json
from typing import Literal, Optional, cast
from langchain_core.documents import Document
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
from pydantic import BaseModel, model_validator
from langchain_core.messages import HumanMessage, AIMessage
class Record(BaseModel):
@ -163,23 +163,15 @@ class Record(BaseModel):
# Create a new Record object with a deep copy of the data dictionary
return Record(data=copy.deepcopy(self.data, memo), text_key=self.text_key, default_value=self.default_value)
def __str__(self) -> str:
"""
Returns a string representation of the Record, including text and data.
"""
# Assuming a method to dump model data as JSON string exists.
# If it doesn't, you might need to implement it or use json.dumps() directly.
# build the string considering all keys in the data dictionary
prefix = "Record("
suffix = ")"
text = f"text_key={self.text_key}, "
text += ", ".join([f"{k}={v}" for k, v in self.data.items()])
return prefix + text + suffix
# check which attributes the Record has by checking the keys in the data dictionary
def __dir__(self):
return super().__dir__() + list(self.data.keys())
def __str__(self) -> str:
# return a JSON string representation of the Record atributes
return json.dumps(self.data)
INPUT_FIELD_NAME = "input_value"

View file

@ -1,6 +1,7 @@
from .api_key import ApiKey
from .flow import Flow
from .folder import Folder
from .user import User
from .variable import Variable
__all__ = ["Flow", "User", "ApiKey", "Variable"]
__all__ = ["Flow", "User", "ApiKey", "Variable", "Folder"]

View file

@ -13,6 +13,7 @@ from sqlmodel import JSON, Column, Field, Relationship, SQLModel
from langflow.schema.schema import Record
if TYPE_CHECKING:
from langflow.services.database.models.folder import Folder
from langflow.services.database.models.user import User
@ -24,7 +25,7 @@ class FlowBase(SQLModel):
data: Optional[Dict] = Field(default=None, nullable=True)
is_component: Optional[bool] = Field(default=False, nullable=True)
updated_at: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc), nullable=True)
folder: Optional[str] = Field(default=None, nullable=True)
folder_id: Optional[UUID] = Field(default=None, nullable=True)
@field_validator("icon_bg_color")
def validate_icon_bg_color(cls, v):
@ -112,6 +113,8 @@ class Flow(FlowBase, table=True):
data: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
user_id: Optional[UUID] = Field(index=True, foreign_key="user.id", nullable=True)
user: "User" = Relationship(back_populates="flows")
folder_id: Optional[UUID] = Field(default=None, foreign_key="folder.id", nullable=True, index=True)
folder: Optional["Folder"] = Relationship(back_populates="flows")
def to_record(self):
serialized = self.model_dump()
@ -128,14 +131,17 @@ class Flow(FlowBase, table=True):
class FlowCreate(FlowBase):
user_id: Optional[UUID] = None
folder_id: Optional[UUID] = None
class FlowRead(FlowBase):
id: UUID
user_id: Optional[UUID] = Field()
folder_id: Optional[UUID] = Field()
class FlowUpdate(SQLModel):
name: Optional[str] = None
description: Optional[str] = None
data: Optional[Dict] = None
folder_id: Optional[UUID] = None

View file

@ -0,0 +1,3 @@
from .model import Folder, FolderCreate, FolderRead, FolderUpdate
__all__ = ["Folder", "FolderCreate", "FolderRead", "FolderUpdate"]

View file

@ -0,0 +1,2 @@
DEFAULT_FOLDER_DESCRIPTION = "Manage your personal projects. Download and upload entire collections."
DEFAULT_FOLDER_NAME = "My Projects"

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