Merge branch 'zustand/io/migration' of personal:logspace-ai/langflow into zustand/io/migration

This commit is contained in:
anovazzi1 2024-02-16 13:36:38 -03:00
commit 0f22175d6a
49 changed files with 1606 additions and 1241 deletions

11
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# Set update schedule for GitHub Actions
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "monthly"

View file

@ -16,10 +16,10 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Cache Docker layers - name: Cache Docker layers
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: /tmp/.buildx-cache path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }} key: ${{ runner.os }}-buildx-${{ github.sha }}

View file

@ -30,11 +30,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -48,7 +48,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell. # ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@ -61,6 +61,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View file

@ -12,8 +12,8 @@ jobs:
name: Deploy to GitHub Pages name: Deploy to GitHub Pages
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
cache: npm cache: npm

View file

@ -16,13 +16,14 @@ jobs:
python-version: python-version:
- "3.9" - "3.9"
- "3.10" - "3.10"
- "3.11"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install poetry - name: Install poetry
run: | run: |
pipx install poetry==$POETRY_VERSION pipx install poetry==$POETRY_VERSION
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
cache: poetry cache: poetry

View file

@ -18,11 +18,11 @@ jobs:
if: ${{ (github.event.pull_request.merged == true) && contains(github.event.pull_request.labels.*.name, 'pre-release') }} if: ${{ (github.event.pull_request.merged == true) && contains(github.event.pull_request.labels.*.name, 'pre-release') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install poetry - name: Install poetry
run: pipx install poetry==$POETRY_VERSION run: pipx install poetry==$POETRY_VERSION
- name: Set up Python 3.10 - name: Set up Python 3.10
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: "3.10" python-version: "3.10"
cache: "poetry" cache: "poetry"

View file

@ -17,11 +17,11 @@ jobs:
if: ${{ (github.event.pull_request.merged == true) && contains(github.event.pull_request.labels.*.name, 'Release') }} if: ${{ (github.event.pull_request.merged == true) && contains(github.event.pull_request.labels.*.name, 'Release') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install poetry - name: Install poetry
run: pipx install poetry==$POETRY_VERSION run: pipx install poetry==$POETRY_VERSION
- name: Set up Python 3.10 - name: Set up Python 3.10
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: "3.10" python-version: "3.10"
cache: "poetry" cache: "poetry"

View file

@ -16,14 +16,15 @@ jobs:
matrix: matrix:
python-version: python-version:
- "3.10" - "3.10"
- "3.11"
env: env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install poetry - name: Install poetry
run: pipx install poetry==$POETRY_VERSION run: pipx install poetry==$POETRY_VERSION
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
cache: "poetry" cache: "poetry"

View file

@ -22,6 +22,8 @@ services:
dockerfile: ./cdk.Dockerfile dockerfile: ./cdk.Dockerfile
args: args:
- BACKEND_URL=http://backend:7860 - BACKEND_URL=http://backend:7860
depends_on:
- backend
environment: environment:
- VITE_PROXY_TARGET=http://backend:7860 - VITE_PROXY_TARGET=http://backend:7860
ports: ports:

2076
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "0.6.7a1" version = "0.6.7a2"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"] authors = ["Logspace <contact@logspace.ai>"]
maintainers = [ maintainers = [
@ -25,18 +25,20 @@ documentation = "https://docs.langflow.org"
langflow = "langflow.__main__:main" langflow = "langflow.__main__:main"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.9,<3.11"
python = ">=3.9,<3.12"
duckdb = "^0.9.2"
fastapi = "^0.109.0" fastapi = "^0.109.0"
uvicorn = "^0.27.0" uvicorn = "^0.27.0"
beautifulsoup4 = "^4.12.2" beautifulsoup4 = "^4.12.2"
google-search-results = "^2.4.1" google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0" google-api-python-client = "^2.118.0"
typer = "^0.9.0" typer = "^0.9.0"
gunicorn = "^21.2.0" gunicorn = "^21.2.0"
langchain = "~0.1.0" langchain = "~0.1.0"
duckdb = "^0.9.2" openai = "^1.12.0"
openai = "^1.11.0" pandas = "2.2.0"
pandas = "2.0.3"
chromadb = "^0.4.0" chromadb = "^0.4.0"
huggingface-hub = { version = "^0.20.0", extras = ["inference"] } huggingface-hub = { version = "^0.20.0", extras = ["inference"] }
rich = "^13.7.0" rich = "^13.7.0"
@ -50,15 +52,14 @@ fake-useragent = "^1.4.0"
docstring-parser = "^0.15" docstring-parser = "^0.15"
psycopg2-binary = "^2.9.6" psycopg2-binary = "^2.9.6"
pyarrow = "^14.0.0" pyarrow = "^14.0.0"
tiktoken = "~0.5.0" tiktoken = "~0.6.0"
wikipedia = "^1.4.0" wikipedia = "^1.4.0"
qdrant-client = "^1.7.0" qdrant-client = "^1.7.0"
weaviate-client = "*" weaviate-client = "*"
jina = "*"
sentence-transformers = { version = "^2.3.1", optional = true } sentence-transformers = { version = "^2.3.1", optional = true }
ctransformers = { version = "^0.2.10", optional = true } ctransformers = { version = "^0.2.10", optional = true }
cohere = "^4.45.0" cohere = "^4.47.0"
python-multipart = "^0.0.6" python-multipart = "^0.0.7"
sqlmodel = "^0.0.14" sqlmodel = "^0.0.14"
faiss-cpu = "^1.7.4" faiss-cpu = "^1.7.4"
anthropic = "^0.15.0" anthropic = "^0.15.0"
@ -67,17 +68,17 @@ multiprocess = "^0.70.14"
cachetools = "^5.3.1" cachetools = "^5.3.1"
types-cachetools = "^5.3.0.5" types-cachetools = "^5.3.0.5"
platformdirs = "^4.2.0" platformdirs = "^4.2.0"
pinecone-client = "^2.2.2" pinecone-client = "^3.0.3"
pymongo = "^4.6.0" pymongo = "^4.6.0"
supabase = "^2.3.0" supabase = "^2.3.0"
certifi = "^2023.11.17" certifi = "^2023.11.17"
google-cloud-aiplatform = "^1.36.0" google-cloud-aiplatform = "^1.42.0"
psycopg = "^3.1.9" psycopg = "^3.1.9"
psycopg-binary = "^3.1.9" psycopg-binary = "^3.1.9"
fastavro = "^1.8.0" fastavro = "^1.8.0"
langchain-experimental = "*" langchain-experimental = "*"
celery = { extras = ["redis"], version = "^5.3.6", optional = true } celery = { extras = ["redis"], version = "^5.3.6", optional = true }
redis = { version = "^4.6.0", optional = true } redis = { version = "^5.0.1", optional = true }
flower = { version = "^2.0.0", optional = true } flower = { version = "^2.0.0", optional = true }
alembic = "^1.13.0" alembic = "^1.13.0"
passlib = "^1.7.4" passlib = "^1.7.4"
@ -90,46 +91,46 @@ zep-python = "*"
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" } pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
loguru = "^0.7.1" loguru = "^0.7.1"
langfuse = "^2.9.0" langfuse = "^2.9.0"
pillow = "^10.0.0" pillow = "^10.2.0"
metal-sdk = "^2.4.0" metal-sdk = "^2.5.0"
markupsafe = "^2.1.3" markupsafe = "^2.1.3"
extract-msg = "^0.45.0" extract-msg = "^0.47.0"
# jq is not available for windows # jq is not available for windows
jq = { version = "^1.6.0", markers = "sys_platform != 'win32'" } jq = { version = "^1.6.0", markers = "sys_platform != 'win32'" }
boto3 = "^1.34.0" boto3 = "^1.34.0"
numexpr = "^2.8.6" numexpr = "^2.8.6"
qianfan = "0.2.0" qianfan = "0.3.0"
pgvector = "^0.2.3" pgvector = "^0.2.3"
pyautogen = "^0.2.0" pyautogen = "^0.2.0"
langchain-google-genai = "^0.0.6" langchain-google-genai = "^0.0.6"
elasticsearch = "^8.11.1" elasticsearch = "^8.12.0"
pytube = "^15.0.0" pytube = "^15.0.0"
python-socketio = "^5.11.0" python-socketio = "^5.11.0"
llama-index = "^0.9.44" llama-index = "0.9.48"
langchain-openai = "^0.0.5" langchain-openai = "^0.0.6"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
pytest-asyncio = "^0.23.1" pytest-asyncio = "^0.23.1"
types-redis = "^4.6.0.5" types-redis = "^4.6.0.5"
ipykernel = "^6.27.0" ipykernel = "^6.29.0"
mypy = "^1.8.0" mypy = "^1.8.0"
ruff = "^0.1.5" ruff = "^0.2.1"
httpx = "*" httpx = "*"
pytest = "^7.4.2" pytest = "^8.0.0"
types-requests = "^2.31.0" types-requests = "^2.31.0"
requests = "^2.31.0" requests = "^2.31.0"
pytest-cov = "^4.1.0" pytest-cov = "^4.1.0"
pandas-stubs = "^2.0.0.230412" pandas-stubs = "^2.1.4.231227"
types-pillow = "^9.5.0.2" types-pillow = "^10.2.0.20240213"
types-pyyaml = "^6.0.12.8" types-pyyaml = "^6.0.12.8"
types-python-jose = "^3.3.4.8" types-python-jose = "^3.3.4.8"
types-passlib = "^1.7.7.13" types-passlib = "^1.7.7.13"
locust = "^2.19.1" locust = "^2.23.1"
pytest-mock = "^3.12.0" pytest-mock = "^3.12.0"
pytest-xdist = "^3.5.0" pytest-xdist = "^3.5.0"
types-pywin32 = "^306.0.0.4" types-pywin32 = "^306.0.0.4"
types-google-cloud-ndb = "^2.2.0.0" types-google-cloud-ndb = "^2.2.0.0"
pytest-sugar = "^0.9.7" pytest-sugar = "^1.0.0"
pytest-instafail = "^0.5.0" pytest-instafail = "^0.5.0"

View file

@ -2,8 +2,9 @@ import asyncio
from typing import TYPE_CHECKING, Any, Dict, List, Optional from typing import TYPE_CHECKING, Any, Dict, List, Optional
from uuid import UUID from uuid import UUID
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from langchain.schema import AgentAction, AgentFinish from langchain.schema import AgentAction, AgentFinish
from langchain_core.callbacks.base import (AsyncCallbackHandler,
BaseCallbackHandler)
from langflow.api.v1.schemas import ChatResponse, PromptResponse from langflow.api.v1.schemas import ChatResponse, PromptResponse
from langflow.services.deps import get_chat_service from langflow.services.deps import get_chat_service
from langflow.utils.util import remove_ansi_escape_codes from langflow.utils.util import remove_ansi_escape_codes

View file

@ -1,10 +1,8 @@
from langflow import CustomComponent from typing import Callable, Union
from langchain.chains import LLMCheckerChain from langchain.chains import LLMCheckerChain
from typing import Union, Callable from langflow import CustomComponent
from langflow.field_typing import ( from langflow.field_typing import BaseLanguageModel, Chain
BaseLanguageModel,
Chain,
)
class LLMCheckerChainComponent(CustomComponent): class LLMCheckerChainComponent(CustomComponent):
@ -21,4 +19,4 @@ class LLMCheckerChainComponent(CustomComponent):
self, self,
llm: BaseLanguageModel, llm: BaseLanguageModel,
) -> Union[Chain, Callable]: ) -> Union[Chain, Callable]:
return LLMCheckerChain(llm=llm) return LLMCheckerChain.from_llm(llm=llm)

View file

@ -45,4 +45,6 @@ class RetrievalQAComponent(CustomComponent):
self.status = runnable self.status = runnable
result = runnable.invoke({input_key: inputs}) result = runnable.invoke({input_key: inputs})
result = result.content if hasattr(result, "content") else result result = result.content if hasattr(result, "content") else result
# Result is a dict with keys "query", "result" and "source_documents"
# for now we just return the result
return result.get("result") return result.get("result")

View file

@ -1,6 +1,8 @@
from langflow import CustomComponent from typing import Any, Dict, List
from langchain.docstore.document import Document from langchain.docstore.document import Document
from typing import Optional, Dict, Any from langchain.document_loaders.directory import DirectoryLoader
from langflow import CustomComponent
class DirectoryLoaderComponent(CustomComponent): class DirectoryLoaderComponent(CustomComponent):
@ -23,20 +25,18 @@ class DirectoryLoaderComponent(CustomComponent):
self, self,
glob: str, glob: str,
path: str, path: str,
load_hidden: Optional[bool] = False, max_concurrency: int = 2,
max_concurrency: Optional[int] = 10, load_hidden: bool = False,
metadata: Optional[dict] = {}, recursive: bool = True,
recursive: Optional[bool] = True, silent_errors: bool = False,
silent_errors: Optional[bool] = False, use_multithreading: bool = True,
use_multithreading: Optional[bool] = True, ) -> List[Document]:
) -> Document: return DirectoryLoader(
return Document(
glob=glob, glob=glob,
path=path, path=path,
load_hidden=load_hidden, load_hidden=load_hidden,
max_concurrency=max_concurrency, max_concurrency=max_concurrency,
metadata=metadata,
recursive=recursive, recursive=recursive,
silent_errors=silent_errors, silent_errors=silent_errors,
use_multithreading=use_multithreading, use_multithreading=use_multithreading,
) ).load()

View file

@ -0,0 +1,42 @@
from typing import Dict, Optional
from langchain_community.embeddings.huggingface import HuggingFaceInferenceAPIEmbeddings
from langflow import CustomComponent
from pydantic.v1.types import SecretStr
class HuggingFaceInferenceAPIEmbeddingsComponent(CustomComponent):
display_name = "HuggingFaceInferenceAPIEmbeddings"
description = "HuggingFace sentence_transformers embedding models, API version."
documentation = "https://github.com/huggingface/text-embeddings-inference"
def build_config(self):
return {
"api_key": {"display_name": "API Key", "password": True, "advanced": True},
"api_url": {"display_name": "API URL", "advanced": True},
"model_name": {"display_name": "Model Name"},
"cache_folder": {"display_name": "Cache Folder", "advanced": True},
"encode_kwargs": {"display_name": "Encode Kwargs", "advanced": True, "field_type": "dict"},
"model_kwargs": {"display_name": "Model Kwargs", "field_type": "dict", "advanced": True},
"multi_process": {"display_name": "Multi Process", "advanced": True},
}
def build(
self,
api_key: Optional[str] = "",
api_url: str = "http://localhost:8080",
model_name: str = "BAAI/bge-large-en-v1.5",
cache_folder: Optional[str] = None,
encode_kwargs: Optional[Dict] = {},
model_kwargs: Optional[Dict] = {},
multi_process: bool = False,
) -> HuggingFaceInferenceAPIEmbeddings:
if api_key:
secret_api_key = SecretStr(api_key)
else:
raise ValueError("API Key is required")
return HuggingFaceInferenceAPIEmbeddings(
api_key=secret_api_key,
api_url=api_url,
model_name=model_name,
)

View file

@ -1,9 +1,9 @@
from typing import Any, Callable, Dict, List, Optional, Union from typing import Any, Callable, Dict, List, Optional, Union
from langchain_openai.embeddings.base import OpenAIEmbeddings from langchain_openai.embeddings.base import OpenAIEmbeddings
from langflow import CustomComponent from langflow import CustomComponent
from langflow.field_typing import NestedDict from langflow.field_typing import NestedDict
from pydantic.v1.types import SecretStr
class OpenAIEmbeddingsComponent(CustomComponent): class OpenAIEmbeddingsComponent(CustomComponent):
@ -67,7 +67,7 @@ class OpenAIEmbeddingsComponent(CustomComponent):
}, },
"skip_empty": {"display_name": "Skip Empty", "advanced": True}, "skip_empty": {"display_name": "Skip Empty", "advanced": True},
"tiktoken_model_name": {"display_name": "TikToken Model Name"}, "tiktoken_model_name": {"display_name": "TikToken Model Name"},
"tikToken_enable": {"display_name": "TikToken Enable"}, "tikToken_enable": {"display_name": "TikToken Enable", "advanced": True},
} }
def build( def build(
@ -92,14 +92,17 @@ class OpenAIEmbeddingsComponent(CustomComponent):
request_timeout: Optional[float] = None, request_timeout: Optional[float] = None,
show_progress_bar: bool = False, show_progress_bar: bool = False,
skip_empty: bool = False, skip_empty: bool = False,
tikToken_enable: bool = True, tiktoken_enable: bool = True,
tiktoken_model_name: Optional[str] = None, tiktoken_model_name: Optional[str] = None,
) -> Union[OpenAIEmbeddings, Callable]: ) -> Union[OpenAIEmbeddings, Callable]:
# This is to avoid errors with Vector Stores (e.g Chroma) # This is to avoid errors with Vector Stores (e.g Chroma)
if disallowed_special == ["all"]: if disallowed_special == ["all"]:
disallowed_special = "all" disallowed_special = "all" # type: ignore
api_key = SecretStr(openai_api_key) if openai_api_key else None
return OpenAIEmbeddings( return OpenAIEmbeddings(
tiktoken_enabled=tikToken_enable, tiktoken_enabled=tiktoken_enable,
default_headers=default_headers, default_headers=default_headers,
default_query=default_query, default_query=default_query,
allowed_special=set(allowed_special), allowed_special=set(allowed_special),
@ -112,7 +115,7 @@ class OpenAIEmbeddingsComponent(CustomComponent):
model=model, model=model,
model_kwargs=model_kwargs, model_kwargs=model_kwargs,
base_url=openai_api_base, base_url=openai_api_base,
api_key=openai_api_key, api_key=api_key,
openai_api_type=openai_api_type, openai_api_type=openai_api_type,
api_version=openai_api_version, api_version=openai_api_version,
organization=openai_organization, organization=openai_organization,

View file

@ -1,4 +1,4 @@
from pydantic import SecretStr from pydantic.v1.types import SecretStr
from langflow import CustomComponent from langflow import CustomComponent
from typing import Optional, Union, Callable from typing import Optional, Union, Callable
from langflow.field_typing import BaseLanguageModel from langflow.field_typing import BaseLanguageModel

View file

@ -1,9 +1,9 @@
from typing import Optional from typing import Optional
from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore
from langflow import CustomComponent from langflow import CustomComponent
from langflow.field_typing import BaseLanguageModel, RangeSpec, TemplateField from langflow.field_typing import BaseLanguageModel, RangeSpec, TemplateField
from pydantic.v1.types import SecretStr
class GoogleGenerativeAIComponent(CustomComponent): class GoogleGenerativeAIComponent(CustomComponent):
@ -63,10 +63,10 @@ class GoogleGenerativeAIComponent(CustomComponent):
) -> BaseLanguageModel: ) -> BaseLanguageModel:
return ChatGoogleGenerativeAI( return ChatGoogleGenerativeAI(
model=model, model=model,
max_output_tokens=max_output_tokens or None, max_output_tokens=max_output_tokens or None, # type: ignore
temperature=temperature, temperature=temperature,
top_k=top_k or None, top_k=top_k or None,
top_p=top_p or None, top_p=top_p or None, # type: ignore
n=n or 1, n=n or 1,
google_api_key=google_api_key, google_api_key=SecretStr(google_api_key),
) )

View file

@ -24,6 +24,8 @@ class CharacterTextSplitterComponent(CustomComponent):
chunk_size: int = 1000, chunk_size: int = 1000,
separator: str = "\n", separator: str = "\n",
) -> List[Document]: ) -> List[Document]:
# separator may come escaped from the frontend
separator = separator.encode().decode("unicode_escape")
docs = CharacterTextSplitter( docs = CharacterTextSplitter(
chunk_overlap=chunk_overlap, chunk_overlap=chunk_overlap,
chunk_size=chunk_size, chunk_size=chunk_size,

View file

@ -1,8 +1,7 @@
from langchain_community.agent_toolkits.openapi.toolkit import BaseToolkit, OpenAPIToolkit
from langchain_community.utilities.requests import TextRequestsWrapper
from langflow import CustomComponent from langflow import CustomComponent
from langflow.field_typing import AgentExecutor from langflow.field_typing import AgentExecutor
from typing import Callable
from langchain_community.utilities.requests import TextRequestsWrapper
from langchain_community.agent_toolkits.openapi.toolkit import OpenAPIToolkit
class OpenAPIToolkitComponent(CustomComponent): class OpenAPIToolkitComponent(CustomComponent):
@ -19,5 +18,5 @@ class OpenAPIToolkitComponent(CustomComponent):
self, self,
json_agent: AgentExecutor, json_agent: AgentExecutor,
requests_wrapper: TextRequestsWrapper, requests_wrapper: TextRequestsWrapper,
) -> Callable: ) -> BaseToolkit:
return OpenAPIToolkit(json_agent=json_agent, requests_wrapper=requests_wrapper) return OpenAPIToolkit(json_agent=json_agent, requests_wrapper=requests_wrapper)

View file

@ -1,6 +1,7 @@
from langflow import CustomComponent from typing import Callable, Union
from typing import Union, Callable
from langchain_community.utilities.google_search import GoogleSearchAPIWrapper from langchain_community.utilities.google_search import GoogleSearchAPIWrapper
from langflow import CustomComponent
class GoogleSearchAPIWrapperComponent(CustomComponent): class GoogleSearchAPIWrapperComponent(CustomComponent):
@ -18,4 +19,4 @@ class GoogleSearchAPIWrapperComponent(CustomComponent):
google_api_key: str, google_api_key: str,
google_cse_id: str, google_cse_id: str,
) -> Union[GoogleSearchAPIWrapper, Callable]: ) -> Union[GoogleSearchAPIWrapper, Callable]:
return GoogleSearchAPIWrapper(google_api_key=google_api_key, google_cse_id=google_cse_id) return GoogleSearchAPIWrapper(google_api_key=google_api_key, google_cse_id=google_cse_id) # type: ignore

View file

@ -1,9 +1,9 @@
from langflow import CustomComponent from typing import Dict
from typing import Dict, Optional
# Assuming the existence of GoogleSerperAPIWrapper class in the serper module # Assuming the existence of GoogleSerperAPIWrapper class in the serper module
# If this class does not exist, you would need to create it or import the appropriate class from another module # If this class does not exist, you would need to create it or import the appropriate class from another module
from langchain_community.utilities.google_serper import GoogleSerperAPIWrapper from langchain_community.utilities.google_serper import GoogleSerperAPIWrapper
from langflow import CustomComponent
class GoogleSerperAPIWrapperComponent(CustomComponent): class GoogleSerperAPIWrapperComponent(CustomComponent):
@ -42,6 +42,5 @@ class GoogleSerperAPIWrapperComponent(CustomComponent):
def build( def build(
self, self,
serper_api_key: str, serper_api_key: str,
result_key_for_type: Optional[Dict[str, str]] = None,
) -> GoogleSerperAPIWrapper: ) -> GoogleSerperAPIWrapper:
return GoogleSerperAPIWrapper(result_key_for_type=result_key_for_type, serper_api_key=serper_api_key) return GoogleSerperAPIWrapper(serper_api_key=serper_api_key)

View file

@ -17,6 +17,7 @@ class ChromaComponent(CustomComponent):
description: str = "Implementation of Vector Store using Chroma" description: str = "Implementation of Vector Store using Chroma"
documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma" documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma"
beta: bool = True beta: bool = True
icon = "Chroma"
def build_config(self): def build_config(self):
""" """
@ -28,7 +29,7 @@ class ChromaComponent(CustomComponent):
return { return {
"collection_name": {"display_name": "Collection Name", "value": "langflow"}, "collection_name": {"display_name": "Collection Name", "value": "langflow"},
"persist": {"display_name": "Persist"}, "persist": {"display_name": "Persist"},
"persist_directory": {"display_name": "Persist Directory"}, "index_directory": {"display_name": "Persist Directory"},
"code": {"advanced": True, "display_name": "Code"}, "code": {"advanced": True, "display_name": "Code"},
"documents": {"display_name": "Documents", "is_list": True}, "documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"}, "embedding": {"display_name": "Embedding"},
@ -54,7 +55,7 @@ class ChromaComponent(CustomComponent):
persist: bool, persist: bool,
embedding: Embeddings, embedding: Embeddings,
chroma_server_ssl_enabled: bool, chroma_server_ssl_enabled: bool,
persist_directory: Optional[str] = None, index_directory: Optional[str] = None,
documents: Optional[List[Document]] = None, documents: Optional[List[Document]] = None,
chroma_server_cors_allow_origins: Optional[str] = None, chroma_server_cors_allow_origins: Optional[str] = None,
chroma_server_host: Optional[str] = None, chroma_server_host: Optional[str] = None,
@ -66,7 +67,7 @@ class ChromaComponent(CustomComponent):
Args: Args:
- collection_name (str): The name of the collection. - collection_name (str): The name of the collection.
- persist_directory (Optional[str]): The directory to persist the Vector Store to. - index_directory (Optional[str]): The directory to persist the Vector Store to.
- chroma_server_ssl_enabled (bool): Whether to enable SSL for the Chroma server. - chroma_server_ssl_enabled (bool): Whether to enable SSL for the Chroma server.
- persist (bool): Whether to persist the Vector Store or not. - persist (bool): Whether to persist the Vector Store or not.
- embedding (Optional[Embeddings]): The embeddings to use for the Vector Store. - embedding (Optional[Embeddings]): The embeddings to use for the Vector Store.
@ -85,7 +86,8 @@ class ChromaComponent(CustomComponent):
if chroma_server_host is not None: if chroma_server_host is not None:
chroma_settings = chromadb.config.Settings( chroma_settings = chromadb.config.Settings(
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None, chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
or None,
chroma_server_host=chroma_server_host, chroma_server_host=chroma_server_host,
chroma_server_port=chroma_server_port or None, chroma_server_port=chroma_server_port or None,
chroma_server_grpc_port=chroma_server_grpc_port or None, chroma_server_grpc_port=chroma_server_grpc_port or None,
@ -93,15 +95,25 @@ class ChromaComponent(CustomComponent):
) )
# If documents, then we need to create a Chroma instance using .from_documents # If documents, then we need to create a Chroma instance using .from_documents
# Check index_directory and expand it if it is a relative path
index_directory = self.resolve_path(index_directory)
if documents is not None and embedding is not None: if documents is not None and embedding is not None:
if len(documents) == 0: if len(documents) == 0:
raise ValueError("If documents are provided, there must be at least one document.") raise ValueError(
return Chroma.from_documents( "If documents are provided, there must be at least one document."
)
chroma = Chroma.from_documents(
documents=documents, # type: ignore documents=documents, # type: ignore
persist_directory=persist_directory if persist else None, persist_directory=index_directory if persist else None,
collection_name=collection_name, collection_name=collection_name,
embedding=embedding, embedding=embedding,
client_settings=chroma_settings, client_settings=chroma_settings,
) )
else:
return Chroma(persist_directory=persist_directory, client_settings=chroma_settings) chroma = Chroma(
persist_directory=index_directory, client_settings=chroma_settings
)
return chroma

View file

@ -3,7 +3,7 @@ from typing import List, Optional
import chromadb # type: ignore import chromadb # type: ignore
from langchain_community.vectorstores.chroma import Chroma from langchain_community.vectorstores.chroma import Chroma
from langflow import CustomComponent from langflow import CustomComponent
from langflow.field_typing import Document, Embeddings, Text from langflow.field_typing import Embeddings, Text
from langflow.schema import Record, docs_to_records from langflow.schema import Record, docs_to_records
@ -15,6 +15,7 @@ class ChromaSearchComponent(CustomComponent):
display_name: str = "Chroma Search" display_name: str = "Chroma Search"
description: str = "Search a Chroma collection for similar documents." description: str = "Search a Chroma collection for similar documents."
beta: bool = True beta: bool = True
icon = "Chroma"
def build_config(self): def build_config(self):
""" """
@ -25,13 +26,19 @@ class ChromaSearchComponent(CustomComponent):
""" """
return { return {
"inputs": {"display_name": "Input"}, "inputs": {"display_name": "Input"},
"search_type": {"display_name": "Search Type", "options": ["Similarity", "MMR"]}, "search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"collection_name": {"display_name": "Collection Name", "value": "langflow"}, "collection_name": {"display_name": "Collection Name", "value": "langflow"},
"persist": {"display_name": "Persist"}, # "persist": {"display_name": "Persist"},
"persist_directory": {"display_name": "Persist Directory"}, "index_directory": {"display_name": "Index Directory"},
"code": {"show": False, "display_name": "Code"}, "code": {"show": False, "display_name": "Code"},
"documents": {"display_name": "Documents", "is_list": True}, "documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"}, "embedding": {
"display_name": "Embedding",
"info": "Embedding model to vectorize inputs (make sure to use same as index)",
},
"chroma_server_cors_allow_origins": { "chroma_server_cors_allow_origins": {
"display_name": "Server CORS Allow Origins", "display_name": "Server CORS Allow Origins",
"advanced": True, "advanced": True,
@ -53,11 +60,9 @@ class ChromaSearchComponent(CustomComponent):
inputs: Text, inputs: Text,
search_type: str, search_type: str,
collection_name: str, collection_name: str,
persist: bool,
embedding: Embeddings, embedding: Embeddings,
chroma_server_ssl_enabled: bool, chroma_server_ssl_enabled: bool,
persist_directory: Optional[str] = None, index_directory: Optional[str] = None,
documents: Optional[List[Document]] = None,
chroma_server_cors_allow_origins: Optional[str] = None, chroma_server_cors_allow_origins: Optional[str] = None,
chroma_server_host: Optional[str] = None, chroma_server_host: Optional[str] = None,
chroma_server_port: Optional[int] = None, chroma_server_port: Optional[int] = None,
@ -87,26 +92,20 @@ class ChromaSearchComponent(CustomComponent):
if chroma_server_host is not None: if chroma_server_host is not None:
chroma_settings = chromadb.config.Settings( chroma_settings = chromadb.config.Settings(
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None, chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
or None,
chroma_server_host=chroma_server_host, chroma_server_host=chroma_server_host,
chroma_server_port=chroma_server_port or None, chroma_server_port=chroma_server_port or None,
chroma_server_grpc_port=chroma_server_grpc_port or None, chroma_server_grpc_port=chroma_server_grpc_port or None,
chroma_server_ssl_enabled=chroma_server_ssl_enabled, chroma_server_ssl_enabled=chroma_server_ssl_enabled,
) )
# If documents, then we need to create a Chroma instance using .from_documents chroma = Chroma(
if documents is not None and embedding is not None: embedding_function=embedding,
if len(documents) == 0: collection_name=collection_name,
raise ValueError("If documents are provided, there must be at least one document.") persist_directory=index_directory,
chroma = Chroma.from_documents( client_settings=chroma_settings,
documents=documents, # type: ignore )
persist_directory=persist_directory if persist else None,
collection_name=collection_name,
embedding=embedding,
client_settings=chroma_settings,
)
else:
chroma = Chroma(persist_directory=persist_directory, client_settings=chroma_settings)
# Validate the inputs # Validate the inputs
docs = [] docs = []

View file

@ -5,7 +5,6 @@ import pinecone # type: ignore
from langchain.schema import BaseRetriever from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.pinecone import Pinecone from langchain_community.vectorstores.pinecone import Pinecone
from langflow import CustomComponent from langflow import CustomComponent
from langflow.field_typing import Document, Embeddings from langflow.field_typing import Document, Embeddings
@ -31,11 +30,11 @@ class PineconeComponent(CustomComponent):
embedding: Embeddings, embedding: Embeddings,
pinecone_env: str, pinecone_env: str,
documents: List[Document], documents: List[Document],
text_key: str = "text",
pool_threads: int = 4,
index_name: Optional[str] = None, index_name: Optional[str] = None,
pinecone_api_key: Optional[str] = None, pinecone_api_key: Optional[str] = None,
text_key: Optional[str] = "text",
namespace: Optional[str] = "default", namespace: Optional[str] = "default",
pool_threads: Optional[int] = None,
) -> Union[VectorStore, Pinecone, BaseRetriever]: ) -> Union[VectorStore, Pinecone, BaseRetriever]:
if pinecone_api_key is None or pinecone_env is None: if pinecone_api_key is None or pinecone_env is None:
raise ValueError("Pinecone API Key and Environment are required.") raise ValueError("Pinecone API Key and Environment are required.")
@ -43,6 +42,8 @@ class PineconeComponent(CustomComponent):
raise ValueError("Pinecone API Key is required.") raise ValueError("Pinecone API Key is required.")
pinecone.init(api_key=pinecone_api_key, environment=pinecone_env) # type: ignore pinecone.init(api_key=pinecone_api_key, environment=pinecone_env) # type: ignore
if not index_name:
raise ValueError("Index Name is required.")
if documents: if documents:
return Pinecone.from_documents( return Pinecone.from_documents(
documents=documents, documents=documents,

View file

@ -1,4 +1,4 @@
from typing import List, Optional, Union from typing import Optional, Union
from langchain.schema import BaseRetriever from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore from langchain_community.vectorstores import VectorStore
@ -15,7 +15,7 @@ class QdrantComponent(CustomComponent):
return { return {
"documents": {"display_name": "Documents"}, "documents": {"display_name": "Documents"},
"embedding": {"display_name": "Embedding"}, "embedding": {"display_name": "Embedding"},
"api_key": {"display_name": "API Key", "password": True}, "api_key": {"display_name": "API Key", "password": True, "advanced": True},
"collection_name": {"display_name": "Collection Name"}, "collection_name": {"display_name": "Collection Name"},
"content_payload_key": {"display_name": "Content Payload Key", "advanced": True}, "content_payload_key": {"display_name": "Content Payload Key", "advanced": True},
"distance_func": {"display_name": "Distance Function", "advanced": True}, "distance_func": {"display_name": "Distance Function", "advanced": True},
@ -36,41 +36,68 @@ class QdrantComponent(CustomComponent):
def build( def build(
self, self,
embedding: Embeddings, embedding: Embeddings,
documents: List[Document], collection_name: str,
documents: Optional[Document] = None,
api_key: Optional[str] = None, api_key: Optional[str] = None,
collection_name: Optional[str] = None,
content_payload_key: str = "page_content", content_payload_key: str = "page_content",
distance_func: str = "Cosine", distance_func: str = "Cosine",
grpc_port: Optional[int] = 6334, grpc_port: int = 6334,
host: Optional[str] = None,
https: bool = False, https: bool = False,
location: str = ":memory:", host: Optional[str] = None,
location: Optional[str] = None,
metadata_payload_key: str = "metadata", metadata_payload_key: str = "metadata",
path: Optional[str] = None, path: Optional[str] = None,
port: Optional[int] = 6333, port: Optional[int] = 6333,
prefer_grpc: bool = False, prefer_grpc: bool = False,
prefix: Optional[str] = None, prefix: Optional[str] = None,
search_kwargs: Optional[NestedDict] = None, search_kwargs: Optional[NestedDict] = None,
timeout: Optional[float] = None, timeout: Optional[int] = None,
url: Optional[str] = None, url: Optional[str] = None,
) -> Union[VectorStore, Qdrant, BaseRetriever]: ) -> Union[VectorStore, Qdrant, BaseRetriever]:
return Qdrant.from_documents( if documents is None:
documents=documents, from qdrant_client import QdrantClient
embedding=embedding,
api_key=api_key, client = QdrantClient(
collection_name=collection_name, location=location,
content_payload_key=content_payload_key, url=host,
distance_func=distance_func, port=port,
grpc_port=grpc_port, grpc_port=grpc_port,
host=host, https=https,
https=https, prefix=prefix,
location=location, timeout=timeout,
metadata_payload_key=metadata_payload_key, prefer_grpc=prefer_grpc,
path=path, metadata_payload_key=metadata_payload_key,
port=port, content_payload_key=content_payload_key,
prefer_grpc=prefer_grpc, api_key=api_key,
prefix=prefix, collection_name=collection_name,
search_kwargs=search_kwargs, host=host,
timeout=timeout, path=path,
url=url, )
) vs = Qdrant(
client=client,
collection_name=collection_name,
embeddings=embedding,
)
return vs
else:
vs = Qdrant.from_documents(
documents=documents, # type: ignore
embedding=embedding,
api_key=api_key,
collection_name=collection_name,
content_payload_key=content_payload_key,
distance_func=distance_func,
grpc_port=grpc_port,
host=host,
https=https,
location=location,
metadata_payload_key=metadata_payload_key,
path=path,
port=port,
prefer_grpc=prefer_grpc,
prefix=prefix,
search_kwargs=search_kwargs,
timeout=timeout,
url=url,
)
return vs

View file

@ -5,7 +5,6 @@ from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.redis import Redis from langchain_community.vectorstores.redis import Redis
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever from langchain_core.retrievers import BaseRetriever
from langflow import CustomComponent from langflow import CustomComponent
@ -31,6 +30,7 @@ class RedisComponent(CustomComponent):
"code": {"show": False, "display_name": "Code"}, "code": {"show": False, "display_name": "Code"},
"documents": {"display_name": "Documents", "is_list": True}, "documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"}, "embedding": {"display_name": "Embedding"},
"schema": {"display_name": "Schema", "file_types": [".yaml"]},
"redis_server_url": { "redis_server_url": {
"display_name": "Redis Server Connection String", "display_name": "Redis Server Connection String",
"advanced": False, "advanced": False,
@ -43,6 +43,7 @@ class RedisComponent(CustomComponent):
embedding: Embeddings, embedding: Embeddings,
redis_server_url: str, redis_server_url: str,
redis_index_name: str, redis_index_name: str,
schema: Optional[str] = None,
documents: Optional[Document] = None, documents: Optional[Document] = None,
) -> Union[VectorStore, BaseRetriever]: ) -> Union[VectorStore, BaseRetriever]:
""" """
@ -58,10 +59,12 @@ class RedisComponent(CustomComponent):
- VectorStore: The Vector Store object. - VectorStore: The Vector Store object.
""" """
if documents is None: if documents is None:
if schema is None:
raise ValueError("If no documents are provided, a schema must be provided.")
redis_vs = Redis.from_existing_index( redis_vs = Redis.from_existing_index(
embedding=embedding, embedding=embedding,
index_name=redis_index_name, index_name=redis_index_name,
schema=None, schema=schema,
key_prefix=None, key_prefix=None,
redis_url=redis_server_url, redis_url=redis_server_url,
) )

View file

@ -6,7 +6,6 @@ from typing import List, Optional, Union
from langchain_community.embeddings import FakeEmbeddings from langchain_community.embeddings import FakeEmbeddings
from langchain_community.vectorstores.vectara import Vectara from langchain_community.vectorstores.vectara import Vectara
from langchain_core.vectorstores import VectorStore from langchain_core.vectorstores import VectorStore
from langflow import CustomComponent from langflow import CustomComponent
from langflow.field_typing import BaseRetriever, Document from langflow.field_typing import BaseRetriever, Document
@ -46,7 +45,7 @@ class VectaraComponent(CustomComponent):
if documents is not None: if documents is not None:
return Vectara.from_documents( return Vectara.from_documents(
documents=documents, documents=documents, # type: ignore
embedding=FakeEmbeddings(size=768), embedding=FakeEmbeddings(size=768),
vectara_customer_id=vectara_customer_id, vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id, vectara_corpus_id=vectara_corpus_id,

View file

@ -5,7 +5,6 @@ from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.pgvector import PGVector from langchain_community.vectorstores.pgvector import PGVector
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever from langchain_core.retrievers import BaseRetriever
from langflow import CustomComponent from langflow import CustomComponent
@ -63,13 +62,13 @@ class PGVectorComponent(CustomComponent):
collection_name=collection_name, collection_name=collection_name,
connection_string=pg_server_url, connection_string=pg_server_url,
) )
else:
vector_store = PGVector.from_documents( vector_store = PGVector.from_documents(
embedding=embedding, embedding=embedding,
documents=documents, documents=documents, # type: ignore
collection_name=collection_name, collection_name=collection_name,
connection_string=pg_server_url, connection_string=pg_server_url,
) )
except Exception as e: except Exception as e:
raise RuntimeError(f"Failed to build PGVector: {e}") raise RuntimeError(f"Failed to build PGVector: {e}")
return vector_store return vector_store

View file

@ -6,7 +6,12 @@ from langflow.graph.edge.base import ContractEdge
from langflow.graph.graph.constants import lazy_load_vertex_dict from langflow.graph.graph.constants import lazy_load_vertex_dict
from langflow.graph.graph.utils import process_flow from langflow.graph.graph.utils import process_flow
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import ChatVertex, FileToolVertex, LLMVertex, ToolkitVertex from langflow.graph.vertex.types import (
ChatVertex,
FileToolVertex,
LLMVertex,
ToolkitVertex,
)
from langflow.interface.tools.constants import FILE_TOOLS from langflow.interface.tools.constants import FILE_TOOLS
from langflow.utils import payload from langflow.utils import payload
from loguru import logger from loguru import logger
@ -127,7 +132,9 @@ class Graph:
return return
for vertex in self.vertices: for vertex in self.vertices:
if not self._validate_vertex(vertex): if not self._validate_vertex(vertex):
raise ValueError(f"{vertex.vertex_type} is not connected to any other components") raise ValueError(
f"{vertex.vertex_type} is not connected to any other components"
)
def _validate_vertex(self, vertex: Vertex) -> bool: def _validate_vertex(self, vertex: Vertex) -> bool:
"""Validates a vertex.""" """Validates a vertex."""
@ -140,7 +147,11 @@ class Graph:
def get_vertex_edges(self, vertex_id: str) -> List[ContractEdge]: def get_vertex_edges(self, vertex_id: str) -> List[ContractEdge]:
"""Returns a list of edges for a given vertex.""" """Returns a list of edges for a given vertex."""
return [edge for edge in self.edges if edge.source_id == vertex_id or edge.target_id == vertex_id] return [
edge
for edge in self.edges
if edge.source_id == vertex_id or edge.target_id == vertex_id
]
def get_vertices_with_target(self, vertex_id: str) -> List[Vertex]: def get_vertices_with_target(self, vertex_id: str) -> List[Vertex]:
"""Returns the vertices connected to a vertex.""" """Returns the vertices connected to a vertex."""
@ -178,7 +189,9 @@ class Graph:
def dfs(vertex): def dfs(vertex):
if state[vertex] == 1: if state[vertex] == 1:
# We have a cycle # We have a cycle
raise ValueError("Graph contains a cycle, cannot perform topological sort") raise ValueError(
"Graph contains a cycle, cannot perform topological sort"
)
if state[vertex] == 0: if state[vertex] == 0:
state[vertex] = 1 state[vertex] = 1
for edge in vertex.edges: for edge in vertex.edges:
@ -237,7 +250,9 @@ class Graph:
edges.append(ContractEdge(source, target, edge)) edges.append(ContractEdge(source, target, edge))
return edges return edges
def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]: def _get_vertex_class(
self, node_type: str, node_base_type: str, node_id: str
) -> Type[Vertex]:
"""Returns the node class based on the node type.""" """Returns the node class based on the node type."""
# First we check for the node_base_type # First we check for the node_base_type
node_name = node_id.split("-")[0] node_name = node_id.split("-")[0]
@ -267,14 +282,18 @@ class Graph:
vertex_type: str = vertex_data["type"] # type: ignore vertex_type: str = vertex_data["type"] # type: ignore
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"]) VertexClass = self._get_vertex_class(
vertex_type, vertex_base_type, vertex_data["id"]
)
vertex_instance = VertexClass(vertex, graph=self) vertex_instance = VertexClass(vertex, graph=self)
vertex_instance.set_top_level(self.top_level_vertices) vertex_instance.set_top_level(self.top_level_vertices)
vertices.append(vertex_instance) vertices.append(vertex_instance)
return vertices return vertices
def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]: def get_children_by_vertex_type(
self, vertex: Vertex, vertex_type: str
) -> List[Vertex]:
"""Returns the children of a vertex based on the vertex type.""" """Returns the children of a vertex based on the vertex type."""
children = [] children = []
vertex_types = [vertex.data["type"]] vertex_types = [vertex.data["type"]]
@ -286,7 +305,9 @@ class Graph:
def __repr__(self): def __repr__(self):
vertex_ids = [vertex.id for vertex in self.vertices] vertex_ids = [vertex.id for vertex in self.vertices]
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]) edges_repr = "\n".join(
[f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]
)
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}" return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
def layered_topological_sort(self): def layered_topological_sort(self):
@ -299,7 +320,9 @@ class Graph:
in_degree[edge.target_id] += 1 in_degree[edge.target_id] += 1
# Queue for vertices with no incoming edges # Queue for vertices with no incoming edges
queue = deque(vertex.id for vertex in self.vertices if in_degree[vertex.id] == 0) queue = deque(
vertex.id for vertex in self.vertices if in_degree[vertex.id] == 0
)
layers = [] layers = []
current_layer = 0 current_layer = 0
@ -314,9 +337,40 @@ class Graph:
if in_degree[neighbor] == 0: if in_degree[neighbor] == 0:
queue.append(neighbor) queue.append(neighbor)
current_layer += 1 # Next layer current_layer += 1 # Next layer
new_layers = self.refine_layers(graph, layers)
return new_layers
return layers def refine_layers(self, graph, initial_layers):
return layers # Map each vertex to its current layer
return layers vertex_to_layer = {}
return layers for layer_index, layer in enumerate(initial_layers):
return layers for vertex in layer:
vertex_to_layer[vertex] = layer_index
# Build the adjacency list for reverse lookup (dependencies)
refined_layers = [[] for _ in initial_layers] # Start with empty layers
new_layer_index_map = defaultdict(
int
) # Map each vertex to its highest dependency layer
for vertex_id, deps in graph.items():
for dep in deps:
new_layer_index_map[vertex_id] = (
max(new_layer_index_map[vertex_id], vertex_to_layer[dep]) - 1
)
for layer_index, layer in enumerate(initial_layers):
for vertex_id in layer:
# Place the vertex in the highest possible layer where its dependencies are met
new_layer_index = new_layer_index_map[vertex_id]
if new_layer_index > layer_index:
refined_layers[new_layer_index].append(vertex_id)
vertex_to_layer[vertex_id] = new_layer_index
else:
refined_layers[layer_index].append(vertex_id)
# Remove empty layers if any
refined_layers = [layer for layer in refined_layers if layer]
return refined_layers

View file

@ -5,7 +5,6 @@ from typing import Any, ClassVar, Optional
import emoji import emoji
from cachetools import TTLCache, cachedmethod from cachetools import TTLCache, cachedmethod
from fastapi import HTTPException from fastapi import HTTPException
from langflow.interface.custom.code_parser import CodeParser from langflow.interface.custom.code_parser import CodeParser
from langflow.interface.custom.eval import eval_custom_component_code from langflow.interface.custom.eval import eval_custom_component_code
from langflow.utils import validate from langflow.utils import validate
@ -21,7 +20,9 @@ class ComponentFunctionEntrypointNameNullError(HTTPException):
class Component: class Component:
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided." ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided." ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = (
"The name of the entrypoint function must be provided."
)
code: Optional[str] = None code: Optional[str] = None
_function_entrypoint_name: str = "build" _function_entrypoint_name: str = "build"
@ -36,10 +37,6 @@ class Component:
else: else:
setattr(self, key, value) setattr(self, key, value)
# Validate the emoji at the icon field
if self.icon:
self.icon = self.validate_icon(self.icon)
def __setattr__(self, key, value): def __setattr__(self, key, value):
if key == "_user_id" and hasattr(self, "_user_id"): if key == "_user_id" and hasattr(self, "_user_id"):
warnings.warn("user_id is immutable and cannot be changed.") warnings.warn("user_id is immutable and cannot be changed.")
@ -68,8 +65,8 @@ class Component:
return validate.create_function(self.code, self._function_entrypoint_name) return validate.create_function(self.code, self._function_entrypoint_name)
def getattr_return_str(self, component, value): def getattr_return_str(self, value):
value = getattr(component, value)
return str(value) if value else "" return str(value) if value else ""
def build_template_config(self) -> dict: def build_template_config(self) -> dict:
@ -89,19 +86,22 @@ class Component:
for attribute, func in attributes_func_mapping.items(): for attribute, func in attributes_func_mapping.items():
if hasattr(component_instance, attribute): if hasattr(component_instance, attribute):
template_config[attribute] = func(component=component_instance, value=attribute) value = getattr(component_instance, attribute)
if value is not None:
template_config[attribute] = func(value=value)
return template_config return template_config
def validate_icon(self, value: str, *args, **kwargs): def validate_icon(self, value: str, *args, **kwargs):
# we are going to use the emoji library to validate the emoji # we are going to use the emoji library to validate the emoji
# emojis can be defined using the :emoji_name: syntax # emojis can be defined using the :emoji_name: syntax
if not value.startswith(":") or not value.endswith(":"): if not value.startswith(":") or not value.endswith(":"):
raise ValueError("Invalid emoji. Please use the :emoji_name: syntax.") warnings.warn("Invalid emoji. Please use the :emoji_name: syntax.")
return value
emoji_value = emoji.emojize(value, variant="emoji_type") emoji_value = emoji.emojize(value, variant="emoji_type")
if value == emoji_value: if value == emoji_value:
raise ValueError(f"Invalid emoji. {value} is not a valid emoji.") warnings.warn(f"Invalid emoji. {value} is not a valid emoji.")
return value
return emoji_value return emoji_value
def build(self, *args: Any, **kwargs: Any) -> Any: def build(self, *args: Any, **kwargs: Any) -> Any:

View file

@ -1,11 +1,11 @@
import operator import operator
from pathlib import Path
from typing import Any, Callable, ClassVar, List, Optional, Union from typing import Any, Callable, ClassVar, List, Optional, Union
from uuid import UUID from uuid import UUID
import yaml import yaml
from cachetools import TTLCache, cachedmethod from cachetools import TTLCache, cachedmethod
from fastapi import HTTPException from fastapi import HTTPException
from langflow.interface.custom.code_parser.utils import ( from langflow.interface.custom.code_parser.utils import (
extract_inner_type_from_generic_alias, extract_inner_type_from_generic_alias,
extract_union_types_from_generic_alias, extract_union_types_from_generic_alias,
@ -13,7 +13,11 @@ from langflow.interface.custom.code_parser.utils import (
from langflow.interface.custom.custom_component.component import Component from langflow.interface.custom.custom_component.component import Component
from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow import Flow
from langflow.services.database.utils import session_getter from langflow.services.database.utils import session_getter
from langflow.services.deps import get_credential_service, get_db_service, get_storage_service from langflow.services.deps import (
get_credential_service,
get_db_service,
get_storage_service,
)
from langflow.services.storage.service import StorageService from langflow.services.storage.service import StorageService
from langflow.utils import validate from langflow.utils import validate
@ -42,6 +46,16 @@ class CustomComponent(Component):
self.cache = TTLCache(maxsize=1024, ttl=60) self.cache = TTLCache(maxsize=1024, ttl=60)
super().__init__(**data) super().__init__(**data)
@staticmethod
def resolve_path(path: str) -> str:
"""Resolves the path to an absolute path."""
path_object = Path(path)
if path_object.parts[0] == "~":
path_object = path_object.expanduser()
elif path_object.is_relative_to("."):
path_object = path_object.resolve()
return str(path_object)
def get_full_path(self, path: str) -> str: def get_full_path(self, path: str) -> str:
storage_svc: "StorageService" = get_storage_service() storage_svc: "StorageService" = get_storage_service()
@ -78,7 +92,8 @@ class CustomComponent(Component):
detail={ detail={
"error": "Type hint Error", "error": "Type hint Error",
"traceback": ( "traceback": (
"Prompt type is not supported in the build method." " Try using PromptTemplate instead." "Prompt type is not supported in the build method."
" Try using PromptTemplate instead."
), ),
}, },
) )
@ -92,14 +107,20 @@ class CustomComponent(Component):
if not self.code: if not self.code:
return {} return {}
component_classes = [cls for cls in self.tree["classes"] if self.code_class_base_inheritance in cls["bases"]] component_classes = [
cls
for cls in self.tree["classes"]
if self.code_class_base_inheritance in cls["bases"]
]
if not component_classes: if not component_classes:
return {} return {}
# Assume the first Component class is the one we're interested in # Assume the first Component class is the one we're interested in
component_class = component_classes[0] component_class = component_classes[0]
build_methods = [ build_methods = [
method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name method
for method in component_class["methods"]
if method["name"] == self.function_entrypoint_name
] ]
return build_methods[0] if build_methods else {} return build_methods[0] if build_methods else {}
@ -112,7 +133,10 @@ class CustomComponent(Component):
return_type = build_method["return_type"] return_type = build_method["return_type"]
# If list or List is in the return type, then we remove it and return the inner type # If list or List is in the return type, then we remove it and return the inner type
if hasattr(return_type, "__origin__") and return_type.__origin__ in [list, List]: if hasattr(return_type, "__origin__") and return_type.__origin__ in [
list,
List,
]:
return_type = extract_inner_type_from_generic_alias(return_type) return_type = extract_inner_type_from_generic_alias(return_type)
# If the return type is not a Union, then we just return it as a list # If the return type is not a Union, then we just return it as a list
@ -153,7 +177,9 @@ class CustomComponent(Component):
# Retrieve and decrypt the credential by name for the current user # Retrieve and decrypt the credential by name for the current user
db_service = get_db_service() db_service = get_db_service()
with session_getter(db_service) as session: with session_getter(db_service) as session:
return credential_service.get_credential(user_id=self._user_id or "", name=name, session=session) return credential_service.get_credential(
user_id=self._user_id or "", name=name, session=session
)
return get_credential return get_credential
@ -163,7 +189,9 @@ class CustomComponent(Component):
credential_service = get_credential_service() credential_service = get_credential_service()
db_service = get_db_service() db_service = get_db_service()
with session_getter(db_service) as session: with session_getter(db_service) as session:
return credential_service.list_credentials(user_id=self._user_id, session=session) return credential_service.list_credentials(
user_id=self._user_id, session=session
)
def index(self, value: int = 0): def index(self, value: int = 0):
"""Returns a function that returns the value at the given index in the iterable.""" """Returns a function that returns the value at the given index in the iterable."""
@ -214,7 +242,11 @@ class CustomComponent(Component):
if flow_id: if flow_id:
flow = session.query(Flow).get(flow_id) flow = session.query(Flow).get(flow_id)
elif flow_name: elif flow_name:
flow = (session.query(Flow).filter(Flow.name == flow_name).filter(Flow.user_id == self.user_id)).first() flow = (
session.query(Flow)
.filter(Flow.name == flow_name)
.filter(Flow.user_id == self.user_id)
).first()
else: else:
raise ValueError("Either flow_name or flow_id must be provided") raise ValueError("Either flow_name or flow_id must be provided")

View file

@ -7,8 +7,6 @@ from typing import Any, Dict, List, Optional, Union
from uuid import UUID from uuid import UUID
from fastapi import HTTPException from fastapi import HTTPException
from loguru import logger
from langflow.field_typing.range_spec import RangeSpec from langflow.field_typing.range_spec import RangeSpec
from langflow.interface.custom.code_parser.utils import extract_inner_type from langflow.interface.custom.code_parser.utils import extract_inner_type
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
@ -22,6 +20,7 @@ from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode
from langflow.utils import validate from langflow.utils import validate
from langflow.utils.util import get_base_classes from langflow.utils.util import get_base_classes
from loguru import logger
def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]): def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
@ -151,9 +150,6 @@ def add_extra_fields(frontend_node, field_config, function_args):
if not function_args: if not function_args:
return return
# sort function_args which is a list of dicts
function_args.sort(key=lambda x: x["name"])
for extra_field in function_args: for extra_field in function_args:
if "name" not in extra_field or extra_field["name"] == "self": if "name" not in extra_field or extra_field["name"] == "self":
continue continue

View file

@ -132,12 +132,12 @@ async def instantiate_custom_component(node_type, class_object, params, user_id)
if is_async: if is_async:
# Await the build method directly if it's async # Await the build method directly if it's async
built_object = await custom_component.build(**params_copy) build_result = await custom_component.build(**params_copy)
else: else:
# Call the build method directly if it's sync # Call the build method directly if it's sync
built_object = custom_component.build(**params_copy) build_result = custom_component.build(**params_copy)
return built_object, {"repr": custom_component.custom_repr()} return build_result, {"repr": custom_component.custom_repr()}
def instantiate_wrapper(node_type, class_object, params): def instantiate_wrapper(node_type, class_object, params):

View file

@ -2,10 +2,11 @@ from typing import TYPE_CHECKING, List, Union
from langchain.agents.agent import AgentExecutor from langchain.agents.agent import AgentExecutor
from langchain.callbacks.base import BaseCallbackHandler from langchain.callbacks.base import BaseCallbackHandler
from loguru import logger
from langflow.api.v1.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler from langflow.api.v1.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler
from langflow.processing.process import fix_memory_inputs, format_actions from langflow.processing.process import fix_memory_inputs, format_actions
from langflow.services.deps import get_plugins_service from langflow.services.deps import get_plugins_service
from loguru import logger
if TYPE_CHECKING: if TYPE_CHECKING:
from langfuse.callback import CallbackHandler # type: ignore from langfuse.callback import CallbackHandler # type: ignore
@ -28,13 +29,12 @@ def setup_callbacks(sync, trace_id, **kwargs):
def get_langfuse_callback(trace_id): def get_langfuse_callback(trace_id):
from langflow.services.deps import get_plugins_service from langflow.services.deps import get_plugins_service
from langfuse.callback import CreateTrace
logger.debug("Initializing langfuse callback") logger.debug("Initializing langfuse callback")
if langfuse := get_plugins_service().get("langfuse"): if langfuse := get_plugins_service().get("langfuse"):
logger.debug("Langfuse credentials found") logger.debug("Langfuse credentials found")
try: try:
trace = langfuse.trace(CreateTrace(name="langflow-" + trace_id, id=trace_id)) trace = langfuse.trace(name="langflow-" + trace_id, id=trace_id)
return trace.getNewHandler() return trace.getNewHandler()
except Exception as exc: except Exception as exc:
logger.error(f"Error initializing langfuse callback: {exc}") logger.error(f"Error initializing langfuse callback: {exc}")

View file

@ -64,14 +64,13 @@ class LangfusePlugin(CallbackPlugin):
def get_callback(self, _id: Optional[str] = None): def get_callback(self, _id: Optional[str] = None):
if _id is None: if _id is None:
_id = "default" _id = "default"
from langfuse.callback import CreateTrace # type: ignore
logger.debug("Initializing langfuse callback") logger.debug("Initializing langfuse callback")
try: try:
langfuse_instance = self.get() langfuse_instance = self.get()
if langfuse_instance is not None and hasattr(langfuse_instance, "trace"): if langfuse_instance is not None and hasattr(langfuse_instance, "trace"):
trace = langfuse_instance.trace(CreateTrace(name="langflow-" + _id, id=_id)) trace = langfuse_instance.trace(name="langflow-" + _id, id=_id)
if trace: if trace:
return trace.getNewHandler() return trace.getNewHandler()

View file

@ -45,7 +45,7 @@
"dompurify": "^3.0.5", "dompurify": "^3.0.5",
"esbuild": "^0.17.19", "esbuild": "^0.17.19",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.233.0", "lucide-react": "^0.331.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
@ -99,7 +99,7 @@
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^4.5.1" "vite": "^4.5.2"
} }
}, },
"node_modules/@adobe/css-tools": { "node_modules/@adobe/css-tools": {
@ -7220,9 +7220,9 @@
} }
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "0.233.0", "version": "0.331.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.233.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.331.0.tgz",
"integrity": "sha512-r0jMHF0vPDq2wBbZ0B3rtIcBjDyWDKpHu+vAjD2OHn2WLUr3HN5IHovtO0EMgQXuSI7YrMZbjsEZWC2uBHr8nQ==", "integrity": "sha512-CHFJ0ve9vaZ7bB2VRAl27SlX1ELh6pfNC0jS96qGpPEEzLkLDGq4pDBFU8RhOoRMqsjXqTzLm9U6bZ1OcIHq7Q==",
"peerDependencies": { "peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
} }

View file

@ -40,7 +40,7 @@
"dompurify": "^3.0.5", "dompurify": "^3.0.5",
"esbuild": "^0.17.19", "esbuild": "^0.17.19",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.233.0", "lucide-react": "^0.331.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
@ -121,6 +121,6 @@
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^4.5.1" "vite": "^4.5.2"
} }
} }

View file

@ -30,6 +30,7 @@ export default function App() {
); );
const loading = useAlertStore((state) => state.loading); const loading = useAlertStore((state) => state.loading);
const [fetchError, setFetchError] = useState(false); const [fetchError, setFetchError] = useState(false);
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const removeAlert = (id: string) => { const removeAlert = (id: string) => {
removeFromTempNotificationList(id); removeFromTempNotificationList(id);
@ -86,7 +87,7 @@ export default function App() {
description={FETCH_ERROR_DESCRIPION} description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE} message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent> ></FetchErrorComponent>
) : loading ? ( ) : isLoading ? (
<div className="loading-page-panel"> <div className="loading-page-panel">
<LoadingComponent remSize={50} /> <LoadingComponent remSize={50} />
</div> </div>

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { NodeToolbar } from "reactflow"; import { NodeToolbar } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import Tooltip from "../../components/TooltipComponent"; import Tooltip from "../../components/TooltipComponent";
@ -112,6 +112,39 @@ export default function GenericNode({
const nameEditable = data.node?.flow || data.type === "CustomComponent"; const nameEditable = data.node?.flow || data.type === "CustomComponent";
const emojiRegex = /\p{Emoji}/u;
const isEmoji = emojiRegex.test(data?.node?.icon!);
const iconNodeRender = useCallback(() => {
const iconElement = data?.node?.icon;
const iconColor = nodeColors[types[data.type]];
const iconName =
iconElement || (data.node?.flow ? "group_components" : name);
const iconClassName = `generic-node-icon ${
!showNode ? "absolute inset-x-6 h-12 w-12" : ""
}`;
if (iconElement && isEmoji) {
return nodeIconFragment(iconElement);
} else {
return checkNodeIconFragment(iconColor, iconName, iconClassName);
}
}, [data, isEmoji, name, showNode]);
const nodeIconFragment = (icon) => {
return <span className="text-lg">{icon}</span>;
};
const checkNodeIconFragment = (iconColor, iconName, iconClassName) => {
return (
<IconComponent
name={iconName}
className={iconClassName}
iconColor={iconColor}
/>
);
};
return ( return (
<> <>
<NodeToolbar> <NodeToolbar>
@ -164,19 +197,7 @@ export default function GenericNode({
(!showNode && "justify-center") (!showNode && "justify-center")
} }
> >
{data?.node?.icon ? ( {iconNodeRender()}
<span className="text-lg">{data?.node?.icon}</span>
) : (
<IconComponent
name={data.node?.flow ? "group_components" : name}
className={
"generic-node-icon " +
(!showNode ? "absolute inset-x-6 h-12 w-12" : "")
}
iconColor={`${nodeColors[types[data.type]]}`}
/>
)}
{showNode && ( {showNode && (
<div className="generic-node-tooltip-div"> <div className="generic-node-tooltip-div">
{nameEditable && inputName ? ( {nameEditable && inputName ? (
@ -370,7 +391,7 @@ export default function GenericNode({
<span className="flex"> <span className="flex">
Build{" "} Build{" "}
<IconComponent <IconComponent
name="Zap" name="Play"
className=" h-5 fill-build-trigger stroke-build-trigger stroke-1" className=" h-5 fill-build-trigger stroke-build-trigger stroke-1"
/>{" "} />{" "}
flow to validate status. flow to validate status.
@ -390,7 +411,7 @@ export default function GenericNode({
> >
<div className="generic-node-status-position flex items-center justify-center"> <div className="generic-node-status-position flex items-center justify-center">
<IconComponent <IconComponent
name="Zap" name="Play"
className={classNames( className={classNames(
validationStatus && validationStatus.valid validationStatus && validationStatus.valid
? "green-status" ? "green-status"
@ -399,7 +420,7 @@ export default function GenericNode({
)} )}
/> />
<IconComponent <IconComponent
name="Zap" name="Play"
className={classNames( className={classNames(
validationStatus && !validationStatus.valid validationStatus && !validationStatus.valid
? "red-status" ? "red-status"
@ -408,7 +429,7 @@ export default function GenericNode({
)} )}
/> />
<IconComponent <IconComponent
name="Zap" name="Play"
className={classNames( className={classNames(
!validationStatus || isBuilding !validationStatus || isBuilding
? "yellow-status" ? "yellow-status"

View file

@ -12,7 +12,7 @@ const buttonVariants = cva(
destructive: destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90", "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: outline:
"border border-input hover:bg-accent hover:text-accent-foreground", "border border-input hover:bg-input hover:text-accent-foreground",
primary: primary:
"border bg-background text-secondary-foreground hover:bg-secondary-foreground/5 dark:hover:bg-background/10 hover:shadow-sm", "border bg-background text-secondary-foreground hover:bg-secondary-foreground/5 dark:hover:bg-background/10 hover:shadow-sm",
secondary: secondary:

View file

@ -94,12 +94,6 @@ export default function ComponentsComponent({
setPageSize(10); setPageSize(10);
} }
useEffect(() => {
setTimeout(() => {
setLoadingScreen(false);
}, 600);
}, []);
return ( return (
<CardsWrapComponent <CardsWrapComponent
onFileDrop={onFileDrop} onFileDrop={onFileDrop}
@ -107,7 +101,7 @@ export default function ComponentsComponent({
> >
<div className="flex h-full w-full flex-col justify-between"> <div className="flex h-full w-full flex-col justify-between">
<div className="flex w-full flex-col gap-4"> <div className="flex w-full flex-col gap-4">
{!loadingScreen && data.length === 0 ? ( {!isLoading && data.length === 0 ? (
<div className="mt-6 flex w-full items-center justify-center text-center"> <div className="mt-6 flex w-full items-center justify-center text-center">
<div className="flex-max-width h-full flex-col"> <div className="flex-max-width h-full flex-col">
<div className="flex w-full flex-col gap-4"> <div className="flex w-full flex-col gap-4">
@ -136,7 +130,7 @@ export default function ComponentsComponent({
</div> </div>
) : ( ) : (
<div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-2"> <div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-2">
{loadingScreen === false && data?.length > 0 ? ( {isLoading === false && data?.length > 0 ? (
data?.map((item, idx) => ( data?.map((item, idx) => (
<CollectionCardComponent <CollectionCardComponent
onDelete={() => { onDelete={() => {
@ -185,7 +179,7 @@ export default function ComponentsComponent({
</div> </div>
)} )}
</div> </div>
{!loadingScreen && data.length > 0 && ( {!isLoading && data.length > 0 && (
<div className="relative py-6"> <div className="relative py-6">
<PaginatorComponent <PaginatorComponent
storeComponent={true} storeComponent={true}

View file

@ -62,10 +62,10 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
if (dbData) { if (dbData) {
const { data, flows } = processFlows(dbData, false); const { data, flows } = processFlows(dbData, false);
get().setFlows(flows); get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({ useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data }, data: { ...state.data, ["saved_components"]: data },
})); }));
set({ isLoading: false });
resolve(); resolve();
} }
}) })

View file

@ -4,6 +4,7 @@ import { APIDataType } from "../types/api";
import { TypesStoreType } from "../types/zustand/types"; import { TypesStoreType } from "../types/zustand/types";
import { templatesGenerator, typesGenerator } from "../utils/reactflowUtils"; import { templatesGenerator, typesGenerator } from "../utils/reactflowUtils";
import useAlertStore from "./alertStore"; import useAlertStore from "./alertStore";
import useFlowsManagerStore from "./flowsManagerStore";
export const useTypesStore = create<TypesStoreType>((set, get) => ({ export const useTypesStore = create<TypesStoreType>((set, get) => ({
types: {}, types: {},
@ -11,6 +12,8 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
data: {}, data: {},
getTypes: () => { getTypes: () => {
return new Promise<void>(async (resolve, reject) => { return new Promise<void>(async (resolve, reject) => {
const setLoading = useFlowsManagerStore.getState().setIsLoading;
setLoading(true);
getAll() getAll()
.then((response) => { .then((response) => {
const data = response.data; const data = response.data;
@ -20,6 +23,7 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
data: { ...old.data, ...data }, data: { ...old.data, ...data },
templates: templatesGenerator(data), templates: templatesGenerator(data),
})); }));
setLoading(false)
resolve(); resolve();
}) })
.catch((error) => { .catch((error) => {

View file

@ -26,10 +26,11 @@ export async function buildVertices({
for (let i = 0; i < verticesOrder.length; i += 1) { for (let i = 0; i < verticesOrder.length; i += 1) {
const innerArray = verticesOrder[i]; const innerArray = verticesOrder[i];
const idIndex = innerArray.indexOf(nodeId); const idIndex = innerArray.indexOf(nodeId);
if (idIndex !== -1) { if (idIndex !== -1) {
// If the targetId is found in the inner array, cut the array before the id // If there's a nodeId, we want to run just that component and not the entire layer
vertices.push(innerArray.slice(0, idIndex + 1)); // because a layer contains dependencies for the next layer
// and we are stopping at the layer that contains the nodeId
vertices.push([innerArray[idIndex]]);
break; // Stop searching after finding the first occurrence break; // Stop searching after finding the first occurrence
} }
// If the targetId is not found, include the entire inner array // If the targetId is not found, include the entire inner array
@ -38,7 +39,7 @@ export async function buildVertices({
} else { } else {
vertices = verticesOrder; vertices = verticesOrder;
} }
console.log("Vertices: ", vertices);
const buildResults: Array<boolean> = []; const buildResults: Array<boolean> = [];
for (let i = 0; i < vertices.length; i += 1) { for (let i = 0; i < vertices.length; i += 1) {
await Promise.all( await Promise.all(

View file

@ -34,6 +34,7 @@ import {
FileSearch, FileSearch,
FileSearch2, FileSearch2,
FileText, FileText,
Cable,
FileUp, FileUp,
Fingerprint, Fingerprint,
FolderPlus, FolderPlus,
@ -70,6 +71,7 @@ import {
Paperclip, Paperclip,
Pencil, Pencil,
Pin, Pin,
Play,
Plus, Plus,
Redo, Redo,
RefreshCcw, RefreshCcw,
@ -237,6 +239,7 @@ export const nodeNames: { [char: string]: string } = {
}; };
export const nodeIconsLucide: iconsType = { export const nodeIconsLucide: iconsType = {
Play,
Vectara: VectaraIcon, Vectara: VectaraIcon,
ArrowUpToLine: ArrowUpToLine, ArrowUpToLine: ArrowUpToLine,
Chroma: ChromaIcon, Chroma: ChromaIcon,
@ -394,7 +397,7 @@ export const nodeIconsLucide: iconsType = {
Combine, Combine,
TerminalIcon, TerminalIcon,
Repeat, Repeat,
io: ArrowDownUp, io: Cable,
ScreenShare, ScreenShare,
Code, Code,
}; };

View file

@ -139,12 +139,16 @@ export function groupByFamily(
}))) })))
); );
}; };
console.log(flow);
if (flow) { if (flow) {
// se existir o flow
for (const node of flow) { for (const node of flow) {
// para cada node do flow
if (node!.data!.node!.flow) break; // não faz nada se o node for um group
const nodeData = node.data; const nodeData = node.data;
const foundNode = checkedNodes.get(nodeData.type); const foundNode = checkedNodes.get(nodeData.type); // verifica se o tipo do node já foi checado
checkedNodes.set(nodeData.type, { checkedNodes.set(nodeData.type, {
hasBaseClassInTemplate: hasBaseClassInTemplate:
foundNode?.hasBaseClassInTemplate || foundNode?.hasBaseClassInTemplate ||
@ -153,7 +157,7 @@ export function groupByFamily(
foundNode?.hasBaseClassInBaseClasses || foundNode?.hasBaseClassInBaseClasses ||
nodeData.node!.base_classes.some((baseClass) => nodeData.node!.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass) baseClassesSet.has(baseClass)
), ), //seta como anterior ou verifica se o node tem base class
displayName: nodeData.node?.display_name, displayName: nodeData.node?.display_name,
}); });
} }

View file

@ -611,35 +611,36 @@ def test_async_task_processing(distributed_client, flow, created_api_key):
assert "Gabriel" in task_status_json["result"]["text"], task_status_json["result"] assert "Gabriel" in task_status_json["result"]["text"], task_status_json["result"]
# ! Deactivating this until updating the test
# Test function without loop # Test function without loop
@pytest.mark.async_test # @pytest.mark.async_test
def test_async_task_processing_vector_store(client, added_vector_store, created_api_key): # def test_async_task_processing_vector_store(client, added_vector_store, created_api_key):
headers = {"x-api-key": created_api_key.api_key} # headers = {"x-api-key": created_api_key.api_key}
post_data = {"inputs": {"input": "How do I upload examples?"}} # post_data = {"inputs": {"input": "How do I upload examples?"}}
# Run the /api/v1/process/{flow_id} endpoint with sync=False # # Run the /api/v1/process/{flow_id} endpoint with sync=False
response = client.post( # response = client.post(
f"api/v1/process/{added_vector_store.get('id')}", # f"api/v1/process/{added_vector_store.get('id')}",
headers=headers, # headers=headers,
json={**post_data, "sync": False}, # json={**post_data, "sync": False},
) # )
assert response.status_code == 200, response.json() # assert response.status_code == 200, response.json()
assert "result" in response.json() # assert "result" in response.json()
assert "FAILURE" not in response.json()["result"] # assert "FAILURE" not in response.json()["result"]
# Extract the task ID from the response # # Extract the task ID from the response
task = response.json().get("task") # task = response.json().get("task")
task_id = task.get("id") # task_id = task.get("id")
task_href = task.get("href") # task_href = task.get("href")
assert task_id is not None # assert task_id is not None
assert task_href is not None # assert task_href is not None
assert task_href == f"api/v1/task/{task_id}" # assert task_href == f"api/v1/task/{task_id}"
# Polling the task status using the helper function # # Polling the task status using the helper function
task_status_json = poll_task_status(client, headers, task_href) # task_status_json = poll_task_status(client, headers, task_href)
assert task_status_json is not None, "Task did not complete in time" # assert task_status_json is not None, "Task did not complete in time"
# Validate that the task completed successfully and the result is as expected # # Validate that the task completed successfully and the result is as expected
assert "result" in task_status_json, task_status_json # assert "result" in task_status_json, task_status_json
assert "output" in task_status_json["result"], task_status_json["result"] # assert "output" in task_status_json["result"], task_status_json["result"]
assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"] # assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"]