VectorStores, DocumentLoaders, TextSplitters, Embeddings and other additions (#157)

This release adds many bug fixes and exposes many other new nodes.
This commit is contained in:
Ibis Prevedello 2023-04-13 23:17:38 -03:00 • committed by GitHub
commit 4aa9bd659d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 3195 additions and 389 deletions

5
.gitignore vendored
View file

@ -9,6 +9,11 @@ lerna-debug.log*
# Mac # Mac
.DS_Store .DS_Store
# VSCode
.vscode
.chroma
.ruff_cache
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

View file

@ -49,7 +49,7 @@ flow("Hey, have you heard of LangFlow?")
## 👋 Contributing ## 👋 Contributing
We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our contributing guidelines and help make LangFlow more accessible. We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make LangFlow more accessible.
[![Star History Chart](https://api.star-history.com/svg?repos=logspace-ai/langflow&type=Timeline)](https://star-history.com/#logspace-ai/langflow&Date) [![Star History Chart](https://api.star-history.com/svg?repos=logspace-ai/langflow&type=Timeline)](https://star-history.com/#logspace-ai/langflow&Date)

View file

@ -3,7 +3,7 @@ FROM python:3.10-slim
WORKDIR /app WORKDIR /app
# Install Poetry # Install Poetry
RUN apt-get update && apt-get install gcc curl -y RUN apt-get update && apt-get install gcc g++ curl build-essential postgresql-server-dev-all -y
RUN curl -sSL https://install.python-poetry.org | python3 - RUN curl -sSL https://install.python-poetry.org | python3 -
# # Add Poetry to PATH # # Add Poetry to PATH
ENV PATH="${PATH}:/root/.local/bin" ENV PATH="${PATH}:/root/.local/bin"

2136
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.0.55" version = "0.0.56"
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 = [
@ -34,8 +34,18 @@ openai = "^0.27.2"
types-pyyaml = "^6.0.12.8" types-pyyaml = "^6.0.12.8"
dill = "^0.3.6" dill = "^0.3.6"
pandas = "^1.5.3" pandas = "^1.5.3"
chromadb = "^0.3.21"
huggingface-hub = "^0.13.3" huggingface-hub = "^0.13.3"
rich = "^13.3.3" rich = "^13.3.3"
llama-cpp-python = "0.1.23"
networkx = "^3.1"
unstructured = "^0.5.11"
pypdf = "^3.7.1"
lxml = "^4.9.2"
pysrt = "^1.1.2"
fake-useragent = "^1.1.3"
docstring-parser = "^0.15"
psycopg2 = "^2.9.6"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^23.1.0" black = "^23.1.0"

View file

@ -3,7 +3,7 @@ from typing import Any, Dict
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from langflow.interface.run import process_graph from langflow.interface.run import process_graph_cached
from langflow.interface.types import build_langchain_types_dict from langflow.interface.types import build_langchain_types_dict
# build router # build router
@ -19,7 +19,7 @@ def get_all():
@router.post("/predict") @router.post("/predict")
def get_load(data: Dict[str, Any]): def get_load(data: Dict[str, Any]):
try: try:
return process_graph(data) return process_graph_cached(data)
except Exception as e: except Exception as e:
# Log stack trace # Log stack trace
logger.exception(e) logger.exception(e)

View file

@ -1,18 +1,62 @@
import base64
import contextlib import contextlib
import functools
import hashlib import hashlib
import json import json
import os import os
import tempfile import tempfile
from collections import OrderedDict
from pathlib import Path from pathlib import Path
import dill # type: ignore import dill # type: ignore
def create_cache_folder(func):
def wrapper(*args, **kwargs):
# Get the destination folder
cache_path = Path(tempfile.gettempdir()) / PREFIX
# Create the destination folder if it doesn't exist
os.makedirs(cache_path, exist_ok=True)
return func(*args, **kwargs)
return wrapper
def memoize_dict(maxsize=128):
cache = OrderedDict()
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
hashed = compute_dict_hash(args[0])
key = (func.__name__, hashed, frozenset(kwargs.items()))
if key not in cache:
result = func(*args, **kwargs)
cache[key] = result
if len(cache) > maxsize:
cache.popitem(last=False)
else:
result = cache[key]
return result
def clear_cache():
cache.clear()
wrapper.clear_cache = clear_cache
return wrapper
return decorator
PREFIX = "langflow_cache" PREFIX = "langflow_cache"
@create_cache_folder
def clear_old_cache_files(max_cache_size: int = 3): def clear_old_cache_files(max_cache_size: int = 3):
cache_dir = Path(tempfile.gettempdir()) cache_dir = Path(tempfile.gettempdir()) / PREFIX
cache_files = list(cache_dir.glob(f"{PREFIX}_*.dill")) cache_files = list(cache_dir.glob("*.dill"))
if len(cache_files) > max_cache_size: if len(cache_files) > max_cache_size:
cache_files_sorted_by_mtime = sorted( cache_files_sorted_by_mtime = sorted(
@ -24,6 +68,13 @@ def clear_old_cache_files(max_cache_size: int = 3):
os.remove(cache_file) os.remove(cache_file)
def compute_dict_hash(graph_data):
graph_data = filter_json(graph_data)
cleaned_graph_json = json.dumps(graph_data, sort_keys=True)
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest()
def filter_json(json_data): def filter_json(json_data):
filtered_data = json_data.copy() filtered_data = json_data.copy()
@ -48,15 +99,40 @@ def filter_json(json_data):
return filtered_data return filtered_data
def compute_hash(graph_data): @create_cache_folder
graph_data = filter_json(graph_data) def save_binary_file(content: str, file_name: str, accepted_types: list[str]) -> str:
"""
Save a binary file to the specified folder.
cleaned_graph_json = json.dumps(graph_data, sort_keys=True) Args:
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest() content: The content of the file as a bytes object.
file_name: The name of the file, including its extension.
Returns:
The path to the saved file.
"""
if not any(file_name.endswith(suffix) for suffix in accepted_types):
raise ValueError(f"File {file_name} is not accepted")
# Get the destination folder
cache_path = Path(tempfile.gettempdir()) / PREFIX
data = content.split(",")[1]
decoded_bytes = base64.b64decode(data)
# Create the full file path
file_path = os.path.join(cache_path, file_name)
# Save the binary content to the file
with open(file_path, "wb") as file:
file.write(decoded_bytes)
return file_path
@create_cache_folder
def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool): def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool):
cache_path = Path(tempfile.gettempdir()) / f"{PREFIX}_{hash_val}.dill" cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill"
with cache_path.open("wb") as cache_file: with cache_path.open("wb") as cache_file:
dill.dump(chat_data, cache_file) dill.dump(chat_data, cache_file)
@ -64,8 +140,9 @@ def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool):
clear_old_cache_files() clear_old_cache_files()
@create_cache_folder
def load_cache(hash_val): def load_cache(hash_val):
cache_path = Path(tempfile.gettempdir()) / f"{PREFIX}_{hash_val}.dill" cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill"
if cache_path.exists(): if cache_path.exists():
with cache_path.open("rb") as cache_file: with cache_path.open("rb") as cache_file:
return dill.load(cache_file) return dill.load(cache_file)

View file

@ -12,6 +12,9 @@ agents:
- JsonAgent - JsonAgent
- CSVAgent - CSVAgent
- initialize_agent - initialize_agent
- VectorStoreAgent
- VectorStoreRouterAgent
- SQLAgent
prompts: prompts:
- PromptTemplate - PromptTemplate
@ -27,6 +30,7 @@ llms:
# - AzureOpenAI # - AzureOpenAI
- ChatOpenAI - ChatOpenAI
- HuggingFaceHub - HuggingFaceHub
- LlamaCpp
tools: tools:
- Search - Search
@ -43,16 +47,48 @@ wrappers:
toolkits: toolkits:
- OpenAPIToolkit - OpenAPIToolkit
- JsonToolkit - JsonToolkit
- VectorStoreInfo
- VectorStoreRouterToolkit
memories: memories:
- ConversationBufferMemory - ConversationBufferMemory
- ConversationSummaryMemory - ConversationSummaryMemory
- ConversationKGMemory - ConversationKGMemory
embeddings: [] embeddings:
- OpenAIEmbeddings
vectorstores: [] vectorstores:
- Chroma
documentloaders: [] documentloaders:
- AirbyteJSONLoader
- CoNLLULoader
- CSVLoader
- UnstructuredEmailLoader
- EverNoteLoader
- FacebookChatLoader
- GutenbergLoader
- BSHTMLLoader
- UnstructuredHTMLLoader
# - UnstructuredImageLoader # Issue with Python 3.11 (https://github.com/Unstructured-IO/unstructured-inference/issues/83)
- UnstructuredMarkdownLoader
- PyPDFLoader
- UnstructuredPowerPointLoader
- SRTLoader
- TelegramChatLoader
- TextLoader
- UnstructuredWordDocumentLoader
- WebBaseLoader
- AZLyricsLoader
- CollegeConfidentialLoader
- HNLoader
- IFixitLoader
- IMSDbLoader
- GitbookLoader
- ReadTheDocsLoader
textsplitters:
- CharacterTextSplitter
dev: false dev: false

View file

@ -8,6 +8,9 @@ CUSTOM_NODES = {
"JsonAgent": nodes.JsonAgentNode(), "JsonAgent": nodes.JsonAgentNode(),
"CSVAgent": nodes.CSVAgentNode(), "CSVAgent": nodes.CSVAgentNode(),
"initialize_agent": nodes.InitializeAgentNode(), "initialize_agent": nodes.InitializeAgentNode(),
"VectorStoreAgent": nodes.VectorStoreAgentNode(),
"VectorStoreRouterAgent": nodes.VectorStoreRouterAgentNode(),
"SQLAgent": nodes.SQLAgentNode(),
}, },
} }

View file

@ -3,13 +3,14 @@
# - Defer prompts building to the last moment or when they have all the tools # - Defer prompts building to the last moment or when they have all the tools
# - Build each inner agent first, then build the outer agent # - Build each inner agent first, then build the outer agent
import contextlib
import types import types
import warnings import warnings
from copy import deepcopy from copy import deepcopy
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from langflow.cache import utils as cache_utils
from langflow.graph.constants import DIRECT_TYPES from langflow.graph.constants import DIRECT_TYPES
from langflow.graph.utils import load_file
from langflow.interface import loading from langflow.interface import loading
from langflow.interface.listing import ALL_TYPES_DICT from langflow.interface.listing import ALL_TYPES_DICT
from langflow.utils.logger import logger from langflow.utils.logger import logger
@ -88,11 +89,12 @@ class Node:
file_name = value.get("value") file_name = value.get("value")
content = value.get("content") content = value.get("content")
type_to_load = value.get("suffixes") type_to_load = value.get("suffixes")
loaded_dict = load_file(file_name, content, type_to_load) file_path = cache_utils.save_binary_file(
params[key] = loaded_dict content=content, file_name=file_name, accepted_types=type_to_load
)
params[key] = file_path
# We should check if the type is in something not
# the opposite
elif value.get("type") not in DIRECT_TYPES: elif value.get("type") not in DIRECT_TYPES:
# Get the edge that connects to this node # Get the edge that connects to this node
edges = [ edges = [
@ -126,6 +128,9 @@ class Node:
new_value = value.get("value") new_value = value.get("value")
if new_value is None: if new_value is None:
warnings.warn(f"Value for {key} in {self.node_type} is None. ") warnings.warn(f"Value for {key} in {self.node_type} is None. ")
if value.get("type") == "int":
with contextlib.suppress(TypeError, ValueError):
new_value = int(new_value) # type: ignore
params[key] = new_value params[key] = new_value
# Add _type to params # Add _type to params
@ -160,6 +165,7 @@ class Node:
result = result.run # type: ignore result = result.run # type: ignore
elif hasattr(result, "get_function"): elif hasattr(result, "get_function"):
result = result.get_function() # type: ignore result = result.get_function() # type: ignore
self.params[key] = result self.params[key] = result
elif isinstance(value, list) and all( elif isinstance(value, list) and all(
isinstance(node, Node) for node in value isinstance(node, Node) for node in value
@ -189,6 +195,15 @@ class Node:
def build(self, force: bool = False) -> Any: def build(self, force: bool = False) -> Any:
if not self._built or force: if not self._built or force:
self._build() self._build()
#! Deepcopy is breaking for vectorstores
if self.base_type in [
"vectorstores",
"VectorStoreRouterAgent",
"VectorStoreAgent",
"VectorStoreInfo",
] or self.node_type in ["VectorStoreInfo", "VectorStoreRouterToolkit"]:
return self._built_object
return deepcopy(self._built_object) return deepcopy(self._built_object)
def add_edge(self, edge: "Edge") -> None: def add_edge(self, edge: "Edge") -> None:

View file

@ -4,22 +4,30 @@ from langflow.graph.base import Edge, Node
from langflow.graph.nodes import ( from langflow.graph.nodes import (
AgentNode, AgentNode,
ChainNode, ChainNode,
DocumentLoaderNode,
EmbeddingNode,
FileToolNode, FileToolNode,
LLMNode, LLMNode,
MemoryNode, MemoryNode,
PromptNode, PromptNode,
TextSplitterNode,
ToolkitNode, ToolkitNode,
ToolNode, ToolNode,
VectorStoreNode,
WrapperNode, WrapperNode,
) )
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.llms.base import llm_creator from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator from langflow.interface.prompts.base import prompt_creator
from langflow.interface.text_splitters.base import textsplitter_creator
from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator from langflow.interface.tools.base import tool_creator
from langflow.interface.tools.constants import FILE_TOOLS from langflow.interface.tools.constants import FILE_TOOLS
from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.utils import payload from langflow.utils import payload
@ -117,6 +125,10 @@ class Graph:
**{t: WrapperNode for t in wrapper_creator.to_list()}, **{t: WrapperNode for t in wrapper_creator.to_list()},
**{t: LLMNode for t in llm_creator.to_list()}, **{t: LLMNode for t in llm_creator.to_list()},
**{t: MemoryNode for t in memory_creator.to_list()}, **{t: MemoryNode for t in memory_creator.to_list()},
**{t: EmbeddingNode for t in embedding_creator.to_list()},
**{t: VectorStoreNode for t in vectorstore_creator.to_list()},
**{t: DocumentLoaderNode for t in documentloader_creator.to_list()},
**{t: TextSplitterNode for t in textsplitter_creator.to_list()},
} }
if node_type in FILE_TOOLS: if node_type in FILE_TOOLS:

View file

@ -32,6 +32,10 @@ class AgentNode(Node):
chain_node.build(tools=self.tools) chain_node.build(tools=self.tools)
self._build() self._build()
#! Cannot deepcopy VectorStore, VectorStoreRouter, or SQL agents
if self.node_type in ["VectorStoreAgent", "VectorStoreRouterAgent", "SQLAgent"]:
return self._built_object
return deepcopy(self._built_object) return deepcopy(self._built_object)
@ -39,11 +43,6 @@ class ToolNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="tools") super().__init__(data, base_type="tools")
def build(self, force: bool = False) -> Any:
if not self._built or force:
self._build()
return deepcopy(self._built_object)
class PromptNode(Node): class PromptNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
@ -109,32 +108,16 @@ class LLMNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="llms") super().__init__(data, base_type="llms")
def build(self, force: bool = False) -> Any:
if not self._built or force:
self._build()
return deepcopy(self._built_object)
class ToolkitNode(Node): class ToolkitNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="toolkits") super().__init__(data, base_type="toolkits")
def build(self, force: bool = False) -> Any:
if not self._built or force:
self._build()
return deepcopy(self._built_object)
class FileToolNode(ToolNode): class FileToolNode(ToolNode):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data) super().__init__(data)
def build(self, force: bool = False) -> Any:
if not self._built or force:
self._build()
return deepcopy(self._built_object)
class WrapperNode(Node): class WrapperNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
@ -148,11 +131,26 @@ class WrapperNode(Node):
return deepcopy(self._built_object) return deepcopy(self._built_object)
class DocumentLoaderNode(Node):
def __init__(self, data: Dict):
super().__init__(data, base_type="documentloaders")
class EmbeddingNode(Node):
def __init__(self, data: Dict):
super().__init__(data, base_type="embeddings")
class VectorStoreNode(Node):
def __init__(self, data: Dict):
super().__init__(data, base_type="vectorstores")
class MemoryNode(Node): class MemoryNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="memory") super().__init__(data, base_type="memory")
def build(self, force: bool = False) -> Any:
if not self._built or force: class TextSplitterNode(Node):
self._build() def __init__(self, data: Dict):
return deepcopy(self._built_object) super().__init__(data, base_type="textsplitters")

View file

@ -1,45 +1,4 @@
import base64
import csv
import io
import json
import re import re
from typing import Any
import yaml
def load_file(file_name, file_content, accepted_types) -> Any:
"""Load a file from a string."""
# Check if the file is accepted
if not any(file_name.endswith(suffix) for suffix in accepted_types):
raise ValueError(f"File {file_name} is not accepted")
# Get the suffix
suffix = file_name.split(".")[-1]
# file_content == 'data:application/x-yaml;base64,b3BlbmFwaTogIjMuMC4wIg...'
data = file_content.split(",")[1]
decoded_bytes = base64.b64decode(data)
# Convert the bytes object to a string
decoded_string = decoded_bytes.decode("utf-8")
if suffix == "json":
# Return the json content
return json.loads(decoded_string)
elif suffix in ["yaml", "yml"]:
# Return the yaml content
loaded_yaml = yaml.load(decoded_string, Loader=yaml.FullLoader)
try:
from langchain.agents.agent_toolkits.openapi.spec import reduce_openapi_spec # type: ignore
return reduce_openapi_spec(loaded_yaml)
except ImportError:
return loaded_yaml
elif suffix == "csv":
# Load the csv content
csv_reader = csv.DictReader(io.StringIO(decoded_string))
return list(csv_reader)
else:
raise ValueError(f"File {file_name} is not accepted")
def validate_prompt(prompt: str): def validate_prompt(prompt: str):

View file

@ -6,6 +6,7 @@ from langflow.custom.customs import get_custom_nodes
from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.settings import settings from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
@ -31,6 +32,9 @@ class AgentCreator(LangChainTypeCreator):
) )
except ValueError as exc: except ValueError as exc:
raise ValueError("Agent not found") from exc raise ValueError("Agent not found") from exc
except AttributeError as exc:
logger.error(f"Agent {name} not loaded: {exc}")
return None
# Now this is a generator # Now this is a generator
def to_list(self) -> List[str]: def to_list(self) -> List[str]:

View file

@ -1,16 +1,37 @@
from typing import Any, List, Optional from typing import Any, List, Optional
from langchain import LLMChain from langchain import LLMChain
from langchain.agents import AgentExecutor, Tool, ZeroShotAgent, initialize_agent from langchain.agents import (
AgentExecutor,
Tool,
ZeroShotAgent,
initialize_agent,
)
from langchain.agents.agent_toolkits import (
SQLDatabaseToolkit,
VectorStoreInfo,
VectorStoreRouterToolkit,
VectorStoreToolkit,
)
from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit
from langchain.agents.agent_toolkits.pandas.prompt import PREFIX as PANDAS_PREFIX from langchain.agents.agent_toolkits.pandas.prompt import PREFIX as PANDAS_PREFIX
from langchain.agents.agent_toolkits.pandas.prompt import SUFFIX as PANDAS_SUFFIX from langchain.agents.agent_toolkits.pandas.prompt import SUFFIX as PANDAS_SUFFIX
from langchain.agents.agent_toolkits.sql.prompt import SQL_PREFIX, SQL_SUFFIX
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.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS
from langchain.llms.base import BaseLLM from langchain.llms.base import BaseLLM
from langchain.memory.chat_memory import BaseChatMemory from langchain.memory.chat_memory import BaseChatMemory
from langchain.schema import BaseLanguageModel from langchain.schema import BaseLanguageModel
from langchain.sql_database import SQLDatabase
from langchain.tools.python.tool import PythonAstREPLTool from langchain.tools.python.tool import PythonAstREPLTool
from langchain.tools.sql_database.prompt import QUERY_CHECKER
class JsonAgent(AgentExecutor): class JsonAgent(AgentExecutor):
@ -66,7 +87,7 @@ class CSVAgent(AgentExecutor):
@classmethod @classmethod
def from_toolkit_and_llm( def from_toolkit_and_llm(
cls, cls,
path: dict, path: str,
llm: BaseLanguageModel, llm: BaseLanguageModel,
pandas_kwargs: Optional[dict] = None, pandas_kwargs: Optional[dict] = None,
**kwargs: Any **kwargs: Any
@ -74,7 +95,7 @@ class CSVAgent(AgentExecutor):
import pandas as pd # type: ignore import pandas as pd # type: ignore
_kwargs = pandas_kwargs or {} _kwargs = pandas_kwargs or {}
df = pd.DataFrame.from_dict(path, **_kwargs) df = pd.read_csv(path, **_kwargs)
tools = [PythonAstREPLTool(locals={"df": df})] # type: ignore tools = [PythonAstREPLTool(locals={"df": df})] # type: ignore
prompt = ZeroShotAgent.create_prompt( prompt = ZeroShotAgent.create_prompt(
@ -97,6 +118,153 @@ class CSVAgent(AgentExecutor):
return super().run(*args, **kwargs) return super().run(*args, **kwargs)
class VectorStoreAgent(AgentExecutor):
"""Vector Store agent"""
@staticmethod
def function_name():
return "VectorStoreAgent"
@classmethod
def initialize(cls, *args, **kwargs):
return cls.from_toolkit_and_llm(*args, **kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@classmethod
def from_toolkit_and_llm(
cls, llm: BaseLLM, vectorstoreinfo: VectorStoreInfo, **kwargs: Any
):
"""Construct a vectorstore agent from an LLM and tools."""
toolkit = VectorStoreToolkit(vectorstore_info=vectorstoreinfo, llm=llm)
tools = toolkit.get_tools()
prompt = ZeroShotAgent.create_prompt(tools, prefix=VECTORSTORE_PREFIX)
llm_chain = LLMChain(
llm=llm,
prompt=prompt,
)
tool_names = [tool.name for tool in tools]
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs)
return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True
)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)
class SQLAgent(AgentExecutor):
"""SQL agent"""
@staticmethod
def function_name():
return "SQLAgent"
@classmethod
def initialize(cls, *args, **kwargs):
return cls.from_toolkit_and_llm(*args, **kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@classmethod
def from_toolkit_and_llm(cls, llm: BaseLLM, database_uri: str, **kwargs: Any):
"""Construct a sql agent from an LLM and tools."""
db = SQLDatabase.from_uri(database_uri)
toolkit = SQLDatabaseToolkit(db=db)
# 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,
QueryCheckerTool,
QuerySQLDataBaseTool,
)
llmchain = LLMChain(
llm=llm,
prompt=PromptTemplate(
template=QUERY_CHECKER, input_variables=["query", "dialect"]
),
)
tools = [
QuerySQLDataBaseTool(db=db), # type: ignore
InfoSQLDatabaseTool(db=db), # type: ignore
ListSQLDatabaseTool(db=db), # type: ignore
QueryCheckerTool(db=db, llm_chain=llmchain), # type: ignore
]
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
prompt = ZeroShotAgent.create_prompt(
tools=tools, # type: ignore
prefix=prefix,
suffix=SQL_SUFFIX,
format_instructions=SQL_FORMAT_INSTRUCTIONS,
)
llm_chain = LLMChain(
llm=llm,
prompt=prompt,
)
tool_names = [tool.name for tool in tools] # type: ignore
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs)
return AgentExecutor.from_agent_and_tools(
agent=agent,
tools=tools, # type: ignore
verbose=True,
max_iterations=15,
early_stopping_method="force",
)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)
class VectorStoreRouterAgent(AgentExecutor):
"""Vector Store Router Agent"""
@staticmethod
def function_name():
return "VectorStoreRouterAgent"
@classmethod
def initialize(cls, *args, **kwargs):
return cls.from_toolkit_and_llm(*args, **kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@classmethod
def from_toolkit_and_llm(
cls,
llm: BaseLanguageModel,
vectorstoreroutertoolkit: VectorStoreRouterToolkit,
**kwargs: Any
):
"""Construct a vector store router agent from an LLM and tools."""
tools = vectorstoreroutertoolkit.get_tools()
prompt = ZeroShotAgent.create_prompt(tools, prefix=VECTORSTORE_ROUTER_PREFIX)
llm_chain = LLMChain(
llm=llm,
prompt=prompt,
)
tool_names = [tool.name for tool in tools]
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs)
return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True
)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)
class InitializeAgent(AgentExecutor): class InitializeAgent(AgentExecutor):
"""Implementation of initialize_agent function""" """Implementation of initialize_agent function"""
@ -128,4 +296,7 @@ CUSTOM_AGENTS = {
"JsonAgent": JsonAgent, "JsonAgent": JsonAgent,
"CSVAgent": CSVAgent, "CSVAgent": CSVAgent,
"initialize_agent": InitializeAgent, "initialize_agent": InitializeAgent,
"VectorStoreAgent": VectorStoreAgent,
"VectorStoreRouterAgent": VectorStoreRouterAgent,
"SQLAgent": SQLAgent,
} }

View file

@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Type, Union
from pydantic import BaseModel from pydantic import BaseModel
from langflow.template.base import FrontendNode, Template, TemplateField from langflow.template.base import FrontendNode, Template, TemplateField
from langflow.utils.logger import logger
# Assuming necessary imports for Field, Template, and FrontendNode classes # Assuming necessary imports for Field, Template, and FrontendNode classes
@ -39,14 +40,18 @@ class LangChainTypeCreator(BaseModel, ABC):
# frontend_node.to_dict() returns a dict with the following structure: # frontend_node.to_dict() returns a dict with the following structure:
# {name: {template: {fields}, description: str}} # {name: {template: {fields}, description: str}}
# so we should update the result dict # so we should update the result dict
result[self.type_name].update(self.frontend_node(name).to_dict()) node = self.frontend_node(name)
if node is not None:
node = node.to_dict()
result[self.type_name].update(node)
return result return result
def frontend_node(self, name) -> FrontendNode: def frontend_node(self, name) -> Union[FrontendNode, None]:
signature = self.get_signature(name) signature = self.get_signature(name)
if signature is None: if signature is None:
raise ValueError(f"{name} not found") logger.error(f"Node {name} not loaded")
return None
if isinstance(signature, FrontendNode): if isinstance(signature, FrontendNode):
return signature return signature
fields = [ fields = [

View file

@ -5,6 +5,7 @@ from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import chain_type_to_cls_dict from langflow.interface.custom_lists import chain_type_to_cls_dict
from langflow.settings import settings from langflow.settings import settings
from langflow.template.nodes import ChainFrontendNode from langflow.template.nodes import ChainFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
# Assuming necessary imports for Field, Template, and FrontendNode classes # Assuming necessary imports for Field, Template, and FrontendNode classes
@ -39,6 +40,9 @@ class ChainCreator(LangChainTypeCreator):
return build_template_from_class(name, self.type_to_loader_dict) return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc: except ValueError as exc:
raise ValueError("Chain not found") from exc raise ValueError("Chain not found") from exc
except AttributeError as exc:
logger.error(f"Chain {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]: def to_list(self) -> List[str]:
custom_chains = list(get_custom_nodes("chains").keys()) custom_chains = list(get_custom_nodes("chains").keys())

View file

@ -1,6 +1,6 @@
import inspect
from typing import Any from typing import Any
## LLM
from langchain import ( from langchain import (
chains, chains,
document_loaders, document_loaders,
@ -8,6 +8,7 @@ from langchain import (
llms, llms,
memory, memory,
requests, requests,
text_splitter,
vectorstores, vectorstores,
) )
from langchain.agents import agent_toolkits from langchain.agents import agent_toolkits
@ -15,16 +16,17 @@ from langchain.chat_models import ChatOpenAI
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
## LLM ## LLMs
llm_type_to_cls_dict = llms.type_to_cls_dict llm_type_to_cls_dict = llms.type_to_cls_dict
llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore
## Chain ## Chains
chain_type_to_cls_dict: dict[str, Any] = { chain_type_to_cls_dict: dict[str, Any] = {
chain_name: import_class(f"langchain.chains.{chain_name}") chain_name: import_class(f"langchain.chains.{chain_name}")
for chain_name in chains.__all__ for chain_name in chains.__all__
} }
## Toolkits
toolkit_type_to_loader_dict: dict[str, Any] = { toolkit_type_to_loader_dict: dict[str, Any] = {
toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}") toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}")
# if toolkit_name is lower case it is a loader # if toolkit_name is lower case it is a loader
@ -69,3 +71,8 @@ documentloaders_type_to_cls_dict: dict[str, Any] = {
) )
for documentloader_name in document_loaders.__all__ for documentloader_name in document_loaders.__all__
} }
## Text Splitters
textsplitter_type_to_cls_dict: dict[str, Any] = dict(
inspect.getmembers(text_splitter, inspect.isclass)
)

View file

@ -1,31 +0,0 @@
from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.util import build_template_from_class
class DocumentLoaderCreator(LangChainTypeCreator):
type_name: str = "documentloader"
@property
def type_to_loader_dict(self) -> Dict:
return documentloaders_type_to_cls_dict
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a document loader."""
try:
return build_template_from_class(name, documentloaders_type_to_cls_dict)
except ValueError as exc:
raise ValueError(f"Documment Loader {name} not found") from exc
def to_list(self) -> List[str]:
return [
documentloader.__name__
for documentloader in self.type_to_loader_dict.values()
if documentloader.__name__ in settings.documentloaders or settings.dev
]
documentloader_creator = DocumentLoaderCreator()

View file

@ -0,0 +1,146 @@
from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
def build_file_path_template(
suffixes: list, fileTypes: list, name: str = "file_path"
) -> Dict:
"""Build a file path template for a document loader."""
return {
"type": "file",
"required": True,
"show": True,
"name": name,
"value": "",
"suffixes": suffixes,
"fileTypes": fileTypes,
}
class DocumentLoaderCreator(LangChainTypeCreator):
type_name: str = "documentloaders"
@property
def type_to_loader_dict(self) -> Dict:
return documentloaders_type_to_cls_dict
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a document loader."""
try:
signature = build_template_from_class(
name, documentloaders_type_to_cls_dict
)
file_path_templates = {
"AirbyteJSONLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"CoNLLULoader": build_file_path_template(
suffixes=[".csv"], fileTypes=["csv"]
),
"CSVLoader": build_file_path_template(
suffixes=[".csv"], fileTypes=["csv"]
),
"UnstructuredEmailLoader": build_file_path_template(
suffixes=[".eml"], fileTypes=["eml"]
),
"EverNoteLoader": build_file_path_template(
suffixes=[".xml"], fileTypes=["xml"]
),
"FacebookChatLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"GutenbergLoader": build_file_path_template(
suffixes=[".txt"], fileTypes=["txt"]
),
"BSHTMLLoader": build_file_path_template(
suffixes=[".html"], fileTypes=["html"]
),
"UnstructuredHTMLLoader": build_file_path_template(
suffixes=[".html"], fileTypes=["html"]
),
"UnstructuredImageLoader": build_file_path_template(
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
),
"UnstructuredMarkdownLoader": build_file_path_template(
suffixes=[".md"], fileTypes=["md"]
),
"PyPDFLoader": build_file_path_template(
suffixes=[".pdf"], fileTypes=["pdf"]
),
"UnstructuredPowerPointLoader": build_file_path_template(
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
),
"SRTLoader": build_file_path_template(
suffixes=[".srt"], fileTypes=["srt"]
),
"TelegramChatLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"TextLoader": build_file_path_template(
suffixes=[".txt"], fileTypes=["txt"]
),
"UnstructuredWordDocumentLoader": build_file_path_template(
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
),
}
if name in file_path_templates:
signature["template"]["file_path"] = file_path_templates[name]
elif name in {
"WebBaseLoader",
"AZLyricsLoader",
"CollegeConfidentialLoader",
"HNLoader",
"IFixitLoader",
"IMSDbLoader",
}:
signature["template"]["web_path"] = {
"type": "str",
"required": True,
"show": True,
"name": "web_path",
"value": "",
"display_name": "Web Page",
}
elif name in {"GitbookLoader"}:
signature["template"]["web_page"] = {
"type": "str",
"required": True,
"show": True,
"name": "web_page",
"value": "",
"display_name": "Web Page",
}
elif name in {"ReadTheDocsLoader"}:
signature["template"]["path"] = {
"type": "str",
"required": True,
"show": True,
"name": "path",
"value": "",
"display_name": "Web Page",
}
return signature
except ValueError as exc:
raise ValueError(f"Documment Loader {name} not found") from exc
except AttributeError as exc:
logger.error(f"Documment Loader {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [
documentloader.__name__
for documentloader in self.type_to_loader_dict.values()
if documentloader.__name__ in settings.documentloaders or settings.dev
]
documentloader_creator = DocumentLoaderCreator()

View file

@ -3,6 +3,7 @@ from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import embedding_type_to_cls_dict from langflow.interface.custom_lists import embedding_type_to_cls_dict
from langflow.settings import settings from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
@ -20,6 +21,10 @@ class EmbeddingCreator(LangChainTypeCreator):
except ValueError as exc: except ValueError as exc:
raise ValueError(f"Embedding {name} not found") from exc raise ValueError(f"Embedding {name} not found") from exc
except AttributeError as exc:
logger.error(f"Embedding {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]: def to_list(self) -> List[str]:
return [ return [
embedding.__name__ embedding.__name__

View file

@ -40,6 +40,10 @@ def import_by_type(_type: str, name: str) -> Any:
"toolkits": import_toolkit, "toolkits": import_toolkit,
"wrappers": import_wrapper, "wrappers": import_wrapper,
"memory": import_memory, "memory": import_memory,
"embeddings": import_embedding,
"vectorstores": import_vectorstore,
"documentloaders": import_documentloader,
"textsplitters": import_textsplitter,
} }
if _type == "llms": if _type == "llms":
key = "chat" if "chat" in name.lower() else "llm" key = "chat" if "chat" in name.lower() else "llm"
@ -113,3 +117,24 @@ def import_chain(chain: str) -> Type[Chain]:
if chain in CUSTOM_CHAINS: if chain in CUSTOM_CHAINS:
return CUSTOM_CHAINS[chain] return CUSTOM_CHAINS[chain]
return import_class(f"langchain.chains.{chain}") return import_class(f"langchain.chains.{chain}")
def import_embedding(embedding: str) -> Any:
"""Import embedding from embedding name"""
return import_class(f"langchain.embeddings.{embedding}")
def import_vectorstore(vectorstore: str) -> Any:
"""Import vectorstore from vectorstore name"""
return import_class(f"langchain.vectorstores.{vectorstore}")
def import_documentloader(documentloader: str) -> Any:
"""Import documentloader from documentloader name"""
return import_class(f"langchain.document_loaders.{documentloader}")
def import_textsplitter(textsplitter: str) -> Any:
"""Import textsplitter from textsplitter name"""
return import_class(f"langchain.text_splitter.{textsplitter}")

View file

@ -1,10 +1,14 @@
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.llms.base import llm_creator from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator from langflow.interface.prompts.base import prompt_creator
from langflow.interface.text_splitters.base import textsplitter_creator
from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator from langflow.interface.tools.base import tool_creator
from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
@ -18,6 +22,10 @@ def get_type_dict():
"memory": memory_creator.to_list(), "memory": memory_creator.to_list(),
"toolkits": toolkits_creator.to_list(), "toolkits": toolkits_creator.to_list(),
"wrappers": wrapper_creator.to_list(), "wrappers": wrapper_creator.to_list(),
"documentLoaders": documentloader_creator.to_list(),
"vectorStore": vectorstore_creator.to_list(),
"embeddings": embedding_creator.to_list(),
"textSplitters": textsplitter_creator.to_list(),
} }

View file

@ -1,14 +1,20 @@
from typing import Dict, List, Optional from typing import Dict, List, Optional, Type
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import llm_type_to_cls_dict from langflow.interface.custom_lists import llm_type_to_cls_dict
from langflow.settings import settings from langflow.settings import settings
from langflow.template.nodes import LLMFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
class LLMCreator(LangChainTypeCreator): class LLMCreator(LangChainTypeCreator):
type_name: str = "llms" type_name: str = "llms"
@property
def frontend_node_class(self) -> Type[LLMFrontendNode]:
return LLMFrontendNode
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
if self.type_dict is None: if self.type_dict is None:
@ -22,6 +28,10 @@ class LLMCreator(LangChainTypeCreator):
except ValueError as exc: except ValueError as exc:
raise ValueError("LLM not found") from exc raise ValueError("LLM not found") from exc
except AttributeError as exc:
logger.error(f"LLM {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]: def to_list(self) -> List[str]:
return [ return [
llm.__name__ llm.__name__

View file

@ -22,6 +22,7 @@ from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.importing.utils import import_by_type from langflow.interface.importing.utils import import_by_type
from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.types import get_type_list from langflow.interface.types import get_type_list
from langflow.interface.utils import load_file_into_dict
from langflow.utils import util, validate from langflow.utils import util, validate
@ -36,29 +37,44 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
if base_type == "agents": if base_type == "agents":
# We need to initialize it differently # We need to initialize it differently
return load_agent_executor(class_object, params) return load_agent_executor(class_object, params)
elif node_type == "ZeroShotPrompt": elif base_type == "prompts":
if "tools" not in params: if node_type == "ZeroShotPrompt":
params["tools"] = [] if "tools" not in params:
return ZeroShotAgent.create_prompt(**params) params["tools"] = []
return ZeroShotAgent.create_prompt(**params)
elif node_type == "PythonFunction": elif base_type == "tools":
# If the node_type is "PythonFunction" if node_type == "JsonSpec":
# we need to get the function from the params params["dict_"] = load_file_into_dict(params.pop("path"))
# which will be a str containing a python function return class_object(**params)
# and then we need to compile it and return the function elif node_type == "PythonFunction":
# as the instance # If the node_type is "PythonFunction"
function_string = params["code"] # we need to get the function from the params
if isinstance(function_string, str): # which will be a str containing a python function
return validate.eval_function(function_string) # and then we need to compile it and return the function
raise ValueError("Function should be a string") # as the instance
function_string = params["code"]
if isinstance(function_string, str):
return validate.eval_function(function_string)
raise ValueError("Function should be a string")
elif base_type == "toolkits": elif base_type == "toolkits":
loaded_toolkit = class_object(**params) loaded_toolkit = class_object(**params)
# Check if node_type has a loader # Check if node_type has a loader
if toolkits_creator.has_create_function(node_type): if toolkits_creator.has_create_function(node_type):
return load_toolkits_executor(node_type, loaded_toolkit, params) return load_toolkits_executor(node_type, loaded_toolkit, params)
return loaded_toolkit return loaded_toolkit
else: elif base_type == "embeddings":
params.pop("model")
return class_object(**params) return class_object(**params)
elif base_type == "vectorstores":
return class_object.from_documents(**params)
elif base_type == "documentloaders":
return class_object(**params).load()
elif base_type == "textsplitters":
documents = params.pop("documents")
text_splitter = class_object(**params)
return text_splitter.split_documents(documents)
return class_object(**params)
def load_flow_from_json(path: str): def load_flow_from_json(path: str):

View file

@ -5,6 +5,7 @@ from langflow.interface.custom_lists import memory_type_to_cls_dict
from langflow.settings import settings from langflow.settings import settings
from langflow.template.base import FrontendNode from langflow.template.base import FrontendNode
from langflow.template.nodes import MemoryFrontendNode from langflow.template.nodes import MemoryFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
@ -28,6 +29,9 @@ class MemoryCreator(LangChainTypeCreator):
return build_template_from_class(name, memory_type_to_cls_dict) return build_template_from_class(name, memory_type_to_cls_dict)
except ValueError as exc: except ValueError as exc:
raise ValueError("Memory not found") from exc raise ValueError("Memory not found") from exc
except AttributeError as exc:
logger.error(f"Memory {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]: def to_list(self) -> List[str]:
return [ return [

View file

@ -7,6 +7,7 @@ from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.settings import settings from langflow.settings import settings
from langflow.template.nodes import PromptFrontendNode from langflow.template.nodes import PromptFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
@ -43,7 +44,11 @@ class PromptCreator(LangChainTypeCreator):
return get_custom_nodes(self.type_name)[name] return get_custom_nodes(self.type_name)[name]
return build_template_from_class(name, self.type_to_loader_dict) return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc: except ValueError as exc:
raise ValueError("Prompt not found") from exc # raise ValueError("Prompt not found") from exc
logger.error(f"Prompt {name} not found: {exc}")
except AttributeError as exc:
logger.error(f"Prompt {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]: def to_list(self) -> List[str]:
custom_prompts = get_custom_nodes("prompts") custom_prompts = get_custom_nodes("prompts")

View file

@ -2,7 +2,7 @@ import contextlib
import io import io
from typing import Any, Dict from typing import Any, Dict
from langflow.cache.utils import compute_hash, load_cache, save_cache from langflow.cache.utils import compute_dict_hash, load_cache, memoize_dict
from langflow.graph.graph import Graph from langflow.graph.graph import Graph
from langflow.interface import loading from langflow.interface import loading
from langflow.utils.logger import logger from langflow.utils.logger import logger
@ -12,7 +12,7 @@ def load_langchain_object(data_graph, is_first_message=False):
""" """
Load langchain object from cache if it exists, otherwise build it. Load langchain object from cache if it exists, otherwise build it.
""" """
computed_hash = compute_hash(data_graph) computed_hash = compute_dict_hash(data_graph)
if is_first_message: if is_first_message:
langchain_object = build_langchain_object(data_graph) langchain_object = build_langchain_object(data_graph)
else: else:
@ -22,6 +22,32 @@ def load_langchain_object(data_graph, is_first_message=False):
return computed_hash, langchain_object return computed_hash, langchain_object
def load_or_build_langchain_object(data_graph, is_first_message=False):
"""
Load langchain object from cache if it exists, otherwise build it.
"""
if is_first_message:
build_langchain_object_with_caching.clear_cache()
return build_langchain_object_with_caching(data_graph)
@memoize_dict(maxsize=1)
def build_langchain_object_with_caching(data_graph):
"""
Build langchain object from data_graph.
"""
logger.debug("Building langchain object")
nodes = data_graph["nodes"]
# Add input variables
# nodes = payload.extract_input_variables(nodes)
# Nodes, edges and root node
edges = data_graph["edges"]
graph = Graph(nodes, edges)
return graph.build()
def build_langchain_object(data_graph): def build_langchain_object(data_graph):
""" """
Build langchain object from data_graph. Build langchain object from data_graph.
@ -67,11 +93,35 @@ def process_graph(data_graph: Dict[str, Any]):
# We have to save it here because if the # We have to save it here because if the
# memory is updated we need to keep the new values # memory is updated we need to keep the new values
logger.debug("Saving langchain object to cache") logger.debug("Saving langchain object to cache")
save_cache(computed_hash, langchain_object, is_first_message) # save_cache(computed_hash, langchain_object, is_first_message)
logger.debug("Saved langchain object to cache") logger.debug("Saved langchain object to cache")
return {"result": str(result), "thought": thought.strip()} return {"result": str(result), "thought": thought.strip()}
def process_graph_cached(data_graph: Dict[str, Any]):
"""
Process graph by extracting input variables and replacing ZeroShotPrompt
with PromptTemplate,then run the graph and return the result and thought.
"""
# Load langchain object
message = data_graph.pop("message", "")
is_first_message = len(data_graph.get("chatHistory", [])) == 0
langchain_object = load_or_build_langchain_object(data_graph, is_first_message)
logger.debug("Loaded langchain object")
if langchain_object is None:
# Raise user facing error
raise ValueError(
"There was an error loading the langchain_object. Please, check all the nodes and try again."
)
# Generate result and thought
logger.debug("Generating result and thought")
result, thought = get_result_and_thought_using_graph(langchain_object, message)
logger.debug("Generated result and thought")
return {"result": str(result), "thought": thought.strip()}
def get_memory_key(langchain_object): def get_memory_key(langchain_object):
""" """
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute. Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.

View file

@ -0,0 +1,3 @@
from langflow.interface.text_splitters.base import TextSplitterCreator
__all__ = ["TextSplitterCreator"]

View file

@ -0,0 +1,71 @@
from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
class TextSplitterCreator(LangChainTypeCreator):
type_name: str = "textsplitters"
@property
def type_to_loader_dict(self) -> Dict:
return textsplitter_type_to_cls_dict
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a text splitter."""
try:
signature = build_template_from_class(name, textsplitter_type_to_cls_dict)
signature["template"]["documents"] = {
"type": "BaseLoader",
"required": True,
"show": True,
"name": "documents",
}
signature["template"]["separator"] = {
"type": "str",
"required": True,
"show": True,
"value": ".",
"name": "separator",
"display_name": "Separator",
}
signature["template"]["chunk_size"] = {
"type": "int",
"required": True,
"show": True,
"value": 4000,
"name": "chunk_size",
"display_name": "Chunk Size",
}
signature["template"]["chunk_overlap"] = {
"type": "int",
"required": True,
"show": True,
"value": 200,
"name": "chunk_overlap",
"display_name": "Chunk Overlap",
}
return signature
except ValueError as exc:
raise ValueError(f"Text Splitter {name} not found") from exc
except AttributeError as exc:
logger.error(f"Text Splitter {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [
textsplitter.__name__
for textsplitter in self.type_to_loader_dict.values()
if textsplitter.__name__ in settings.textsplitters or settings.dev
]
textsplitter_creator = TextSplitterCreator()

View file

@ -5,6 +5,7 @@ from langchain.agents import agent_toolkits
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class, import_module from langflow.interface.importing.utils import import_class, import_module
from langflow.settings import settings from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
@ -44,6 +45,9 @@ class ToolkitCreator(LangChainTypeCreator):
return build_template_from_class(name, self.type_to_loader_dict) return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc: except ValueError as exc:
raise ValueError("Prompt not found") from exc raise ValueError("Prompt not found") from exc
except AttributeError as exc:
logger.error(f"Prompt {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]: def to_list(self) -> List[str]:
return list(self.type_to_loader_dict.keys()) return list(self.type_to_loader_dict.keys())

View file

@ -47,12 +47,14 @@ TOOL_INPUTS = {
value="", value="",
multiline=True, multiline=True,
), ),
"dict_": TemplateField( "path": TemplateField(
field_type="file", field_type="file",
required=True, required=True,
is_list=False, is_list=False,
show=True, show=True,
value="", value="",
suffixes=[".json", ".yaml", ".yml"],
fileTypes=["json", "yaml", "yml"],
), ),
} }
@ -114,6 +116,8 @@ class ToolCreator(LangChainTypeCreator):
return node return node
elif tool_type in FILE_TOOLS: elif tool_type in FILE_TOOLS:
params = all_tools[name]["params"] # type: ignore params = all_tools[name]["params"] # type: ignore
if tool_type == "JsonSpec":
params["path"] = params.pop("dict_") # type: ignore
base_classes += [name] base_classes += [name]
else: else:
params = [] params = []

View file

@ -7,7 +7,7 @@ from langchain.agents.load_tools import (
) )
from langchain.tools.json.tool import JsonSpec from langchain.tools.json.tool import JsonSpec
from langflow.interface.custom.types import PythonFunction from langflow.interface.tools.custom import PythonFunction
FILE_TOOLS = {"JsonSpec": JsonSpec} FILE_TOOLS = {"JsonSpec": JsonSpec}
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction} CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction}

View file

@ -1,13 +1,14 @@
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.documentLoaders.base import documentloader_creator from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.llms.base import llm_creator from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator from langflow.interface.prompts.base import prompt_creator
from langflow.interface.text_splitters.base import textsplitter_creator
from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator from langflow.interface.tools.base import tool_creator
from langflow.interface.vectorStore.base import vectorstore_creator from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
@ -40,6 +41,7 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
embedding_creator, embedding_creator,
vectorstore_creator, vectorstore_creator,
documentloader_creator, documentloader_creator,
textsplitter_creator,
] ]
all_types = {} all_types = {}

View file

@ -0,0 +1,22 @@
import json
import os
import yaml
def load_file_into_dict(file_path: str) -> dict:
if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
file_extension = os.path.splitext(file_path)[1].lower()
if file_extension == ".json":
with open(file_path, "r") as json_file:
data = json.load(json_file)
elif file_extension in [".yaml", ".yml"]:
with open(file_path, "r") as yaml_file:
data = yaml.safe_load(yaml_file)
else:
raise ValueError("Unsupported file type. Please provide a JSON or YAML file.")
return data

View file

@ -1,31 +0,0 @@
from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import vectorstores_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.util import build_template_from_class
class VectorstoreCreator(LangChainTypeCreator):
type_name: str = "vectorstore"
@property
def type_to_loader_dict(self) -> Dict:
return vectorstores_type_to_cls_dict
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of an embedding."""
try:
return build_template_from_class(name, vectorstores_type_to_cls_dict)
except ValueError as exc:
raise ValueError(f"Vector Store {name} not found") from exc
def to_list(self) -> List[str]:
return [
vectorstore
for vectorstore in self.type_to_loader_dict.keys()
if vectorstore in settings.vectorstores or settings.dev
]
vectorstore_creator = VectorstoreCreator()

View file

@ -0,0 +1,55 @@
from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import vectorstores_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
class VectorstoreCreator(LangChainTypeCreator):
type_name: str = "vectorstores"
@property
def type_to_loader_dict(self) -> Dict:
return vectorstores_type_to_cls_dict
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of an embedding."""
try:
signature = build_template_from_class(name, vectorstores_type_to_cls_dict)
# TODO: Use FrontendendNode class to build the signature
signature["template"] = {
"documents": {
"type": "TextSplitter",
"required": True,
"show": True,
"name": "documents",
"display_name": "Text Splitter",
},
"embedding": {
"type": "Embeddings",
"required": True,
"show": True,
"name": "embedding",
"display_name": "Embedding",
},
}
return signature
except ValueError as exc:
raise ValueError(f"Vector Store {name} not found") from exc
except AttributeError as exc:
logger.error(f"Vector Store {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [
vectorstore
for vectorstore in self.type_to_loader_dict.keys()
if vectorstore in settings.vectorstores or settings.dev
]
vectorstore_creator = VectorstoreCreator()

View file

@ -3,6 +3,7 @@ from typing import Dict, List, Optional
from langchain import requests from langchain import requests
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
@ -22,6 +23,9 @@ class WrapperCreator(LangChainTypeCreator):
return build_template_from_class(name, self.type_to_loader_dict) return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc: except ValueError as exc:
raise ValueError("Wrapper not found") from exc raise ValueError("Wrapper not found") from exc
except AttributeError as exc:
logger.error(f"Wrapper {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]: def to_list(self) -> List[str]:
return list(self.type_to_loader_dict.keys()) return list(self.type_to_loader_dict.keys())

View file

@ -17,6 +17,7 @@ class Settings(BaseSettings):
documentloaders: List[str] = [] documentloaders: List[str] = []
wrappers: List[str] = [] wrappers: List[str] = []
toolkits: List[str] = [] toolkits: List[str] = []
textsplitters: List[str] = []
dev: bool = False dev: bool = False
class Config: class Config:
@ -40,6 +41,7 @@ class Settings(BaseSettings):
self.memories = new_settings.memories or [] self.memories = new_settings.memories or []
self.wrappers = new_settings.wrappers or [] self.wrappers = new_settings.wrappers or []
self.toolkits = new_settings.toolkits or [] self.toolkits = new_settings.toolkits or []
self.textsplitters = new_settings.textsplitters or []
self.dev = new_settings.dev or False self.dev = new_settings.dev or False

View file

@ -248,6 +248,93 @@ class CSVAgentNode(FrontendNode):
return super().to_dict() return super().to_dict()
class VectorStoreAgentNode(FrontendNode):
name: str = "VectorStoreAgent"
template: Template = Template(
type_name="vectorstore_agent",
fields=[
TemplateField(
field_type="VectorStoreInfo",
required=True,
show=True,
name="vectorstoreinfo",
display_name="Vector Store Info",
),
TemplateField(
field_type="BaseLanguageModel",
required=True,
show=True,
name="llm",
display_name="LLM",
),
],
)
description: str = """Construct an agent from a Vector Store."""
base_classes: list[str] = ["AgentExecutor"]
def to_dict(self):
return super().to_dict()
class VectorStoreRouterAgentNode(FrontendNode):
name: str = "VectorStoreRouterAgent"
template: Template = Template(
type_name="vectorstorerouter_agent",
fields=[
TemplateField(
field_type="VectorStoreRouterToolkit",
required=True,
show=True,
name="vectorstoreroutertoolkit",
display_name="Vector Store Router Toolkit",
),
TemplateField(
field_type="BaseLanguageModel",
required=True,
show=True,
name="llm",
display_name="LLM",
),
],
)
description: str = """Construct an agent from a Vector Store Router."""
base_classes: list[str] = ["AgentExecutor"]
def to_dict(self):
return super().to_dict()
class SQLAgentNode(FrontendNode):
name: str = "SQLAgent"
template: Template = Template(
type_name="sql_agent",
fields=[
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=False,
value="",
name="database_uri",
),
TemplateField(
field_type="BaseLanguageModel",
required=True,
show=True,
name="llm",
display_name="LLM",
),
],
)
description: str = """Construct an agent from a Vector Store Router."""
base_classes: list[str] = ["AgentExecutor"]
def to_dict(self):
return super().to_dict()
class PromptFrontendNode(FrontendNode): class PromptFrontendNode(FrontendNode):
@staticmethod @staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None: def format_field(field: TemplateField, name: Optional[str] = None) -> None:
@ -275,6 +362,9 @@ class PromptFrontendNode(FrontendNode):
): ):
field.field_type = "BaseMessagePromptTemplate" field.field_type = "BaseMessagePromptTemplate"
# All prompt fields should be password=False
field.password = False
class MemoryFrontendNode(FrontendNode): class MemoryFrontendNode(FrontendNode):
@staticmethod @staticmethod
@ -289,6 +379,7 @@ class MemoryFrontendNode(FrontendNode):
field.field_type = "int" field.field_type = "int"
field.value = 10 field.value = 10
field.display_name = "Memory Size" field.display_name = "Memory Size"
field.password = False
class ChainFrontendNode(FrontendNode): class ChainFrontendNode(FrontendNode):
@ -299,3 +390,39 @@ class ChainFrontendNode(FrontendNode):
if "key" in field.name: if "key" in field.name:
field.password = False field.password = False
field.show = False field.show = False
if field.name in ["input_key", "output_key"]:
field.required = True
field.show = True
# Separated for possible future changes
if field.name == "prompt":
# if no prompt is provided, use the default prompt
field.required = False
field.show = True
class LLMFrontendNode(FrontendNode):
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
display_names_dict = {
"huggingfacehub_api_token": "HuggingFace Hub API Token",
}
FrontendNode.format_field(field, name)
SHOW_FIELDS = ["repo_id", "task", "model_kwargs"]
if field.name in SHOW_FIELDS:
field.show = True
if "api" in field.name and ("key" in field.name or "token" in field.name):
field.password = True
field.show = True
field.required = True
if field.name == "task":
field.required = True
field.show = True
field.is_list = True
field.options = ["text-generation", "text2text-generation"]
if display_name := display_names_dict.get(field.name):
field.display_name = display_name
if field.name == "model_kwargs":
field.field_type = "code"

View file

@ -3,6 +3,8 @@ import inspect
import re import re
from typing import Dict, Optional from typing import Dict, Optional
from docstring_parser import parse # type: ignore
from langflow.template.constants import FORCE_SHOW_FIELDS from langflow.template.constants import FORCE_SHOW_FIELDS
from langflow.utils import constants from langflow.utils import constants
@ -65,7 +67,8 @@ def build_template_from_function(
if v.__annotations__["return"].__name__ == name: if v.__annotations__["return"].__name__ == name:
_class = v.__annotations__["return"] _class = v.__annotations__["return"]
docs = get_class_doc(_class) # Get the docstring
docs = parse(_class.__doc__)
variables = {"_type": _type} variables = {"_type": _type}
for class_field_items, value in _class.__fields__.items(): for class_field_items, value in _class.__fields__.items():
@ -86,8 +89,8 @@ def build_template_from_function(
variables[class_field_items][name_] = value_ variables[class_field_items][name_] = value_
variables[class_field_items]["placeholder"] = ( variables[class_field_items]["placeholder"] = (
docs["Attributes"][class_field_items] docs.params[class_field_items]
if class_field_items in docs["Attributes"] if class_field_items in docs.params
else "" else ""
) )
# Adding function to base classes to allow # Adding function to base classes to allow
@ -98,7 +101,7 @@ def build_template_from_function(
return { return {
"template": format_dict(variables, name), "template": format_dict(variables, name),
"description": docs["Description"], "description": docs.short_description or "",
"base_classes": base_classes, "base_classes": base_classes,
} }
@ -117,7 +120,7 @@ def build_template_from_class(
_class = v _class = v
# Get the docstring # Get the docstring
docs = get_class_doc(_class) docs = parse(_class.__doc__)
variables = {"_type": _type} variables = {"_type": _type}
@ -140,8 +143,8 @@ def build_template_from_class(
variables[class_field_items][name_] = value_ variables[class_field_items][name_] = value_
variables[class_field_items]["placeholder"] = ( variables[class_field_items]["placeholder"] = (
docs["Attributes"][class_field_items] docs.params[class_field_items]
if class_field_items in docs["Attributes"] if class_field_items in docs.params
else "" else ""
) )
base_classes = get_base_classes(_class) base_classes = get_base_classes(_class)
@ -151,7 +154,7 @@ def build_template_from_class(
base_classes.append("function") base_classes.append("function")
return { return {
"template": format_dict(variables, name), "template": format_dict(variables, name),
"description": docs["Description"], "description": docs.short_description or "",
"base_classes": base_classes, "base_classes": base_classes,
} }
@ -188,60 +191,6 @@ def get_default_factory(module: str, function: str):
return None return None
def get_class_doc(class_name):
"""
Extracts information from the docstring of a given class.
Args:
class_name: the class to extract information from
Returns:
A dictionary containing the extracted information, with keys
for 'Description', 'Parameters', 'Attributes', and 'Returns'.
"""
# Template
data = {
"Description": "",
"Parameters": {},
"Attributes": {},
"Example": [],
"Returns": {},
}
# Get the class docstring
docstring = class_name.__doc__
if not docstring:
return data
# Parse the docstring to extract information
lines = docstring.split("\n")
current_section = "Description"
for line in lines:
line = line.strip()
if not line:
continue
if (
line.startswith(tuple(data.keys()))
and len(line.split()) == 1
and line.endswith(":")
):
current_section = line[:-1]
continue
if current_section in ["Description", "Example"]:
data[current_section] += line
else:
param, desc = line.split(":")
data[current_section][param.strip()] = desc.strip()
return data
def format_dict(d, name: Optional[str] = None): def format_dict(d, name: Optional[str] = None):
""" """
Formats a dictionary by removing certain keys and modifying the Formats a dictionary by removing certain keys and modifying the

View file

@ -40,3 +40,8 @@
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@font-face{
font-family: text-security-disc;
src: url("assets/text-security-disc.woff") format("woff");
}

Binary file not shown.

View file

@ -23,7 +23,7 @@ export default function InputComponent({
className={classNames( className={classNames(
"block w-full form-input dark:bg-gray-900 dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm", "block w-full form-input dark:bg-gray-900 dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
disabled ? " bg-gray-200 dark:bg-gray-700" : "", disabled ? " bg-gray-200 dark:bg-gray-700" : "",
password?"password":"" password && myValue.length>0?"password":""
)} )}
placeholder="Type a text" placeholder="Type a text"
onChange={(e) => { onChange={(e) => {

View file

@ -6,9 +6,10 @@ import ReactFlow, {
useEdgesState, useEdgesState,
useNodesState, useNodesState,
useReactFlow, useReactFlow,
ControlButton, updateEdge,
EdgeChange, EdgeChange,
Connection, Connection,
Edge,
} from "reactflow"; } from "reactflow";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
import ExtraSidebar from "./components/extraSidebarComponent"; import ExtraSidebar from "./components/extraSidebarComponent";
@ -20,6 +21,7 @@ import { typesContext } from "../../contexts/typesContext";
import ConnectionLineComponent from "./components/ConnectionLineComponent"; import ConnectionLineComponent from "./components/ConnectionLineComponent";
import { FlowType, NodeType } from "../../types/flow"; import { FlowType, NodeType } from "../../types/flow";
import { APIClassType } from "../../types/api"; import { APIClassType } from "../../types/api";
import { isValidConnection } from "../../utils";
const nodeTypes = { const nodeTypes = {
genericNode: GenericNode, genericNode: GenericNode,
@ -28,7 +30,7 @@ const nodeTypes = {
var _ = require("lodash"); var _ = require("lodash");
export default function FlowPage({ flow }:{flow:FlowType}) { export default function FlowPage({ flow }:{flow:FlowType}) {
let { updateFlow, incrementNodeId, downloadFlow, uploadFlow } = let { updateFlow, incrementNodeId} =
useContext(TabsContext); useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance } = const { types, reactFlowInstance, setReactFlowInstance } =
useContext(typesContext); useContext(typesContext);
@ -43,6 +45,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
flow.data?.edges ?? [] flow.data?.edges ?? []
); );
const { setViewport } = useReactFlow(); const { setViewport } = useReactFlow();
const edgeUpdateSuccessful = useRef(true)
useEffect(() => { useEffect(() => {
if (reactFlowInstance && flow) { if (reactFlowInstance && flow) {
@ -155,6 +158,26 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
setEdges(edges.filter((ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id))); setEdges(edges.filter((ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id)));
} }
const onEdgeUpdateStart = useCallback(() => {
edgeUpdateSuccessful.current = false;
}, []);
const onEdgeUpdate = useCallback((oldEdge:Edge, newConnection:Connection) => {
if(isValidConnection(newConnection,reactFlowInstance)){
edgeUpdateSuccessful.current = true;
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
}, []);
const onEdgeUpdateEnd = useCallback((_, edge) => {
if (!edgeUpdateSuccessful.current) {
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
}
edgeUpdateSuccessful.current = true;
}, []);
return ( return (
<div className="w-full h-full" ref={reactFlowWrapper}> <div className="w-full h-full" ref={reactFlowWrapper}>
{Object.keys(types).length > 0 ? ( {Object.keys(types).length > 0 ? (
@ -171,6 +194,9 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
onLoad={setReactFlowInstance} onLoad={setReactFlowInstance}
onInit={setReactFlowInstance} onInit={setReactFlowInstance}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
connectionLineComponent={ConnectionLineComponent} connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver} onDragOver={onDragOver}
onDrop={onDrop} onDrop={onDrop}

View file

@ -9,7 +9,11 @@ import {
ComputerDesktopIcon, ComputerDesktopIcon,
Bars3CenterLeftIcon, Bars3CenterLeftIcon,
GiftIcon, GiftIcon,
PaperClipIcon,
QuestionMarkCircleIcon, QuestionMarkCircleIcon,
FingerPrintIcon,
ScissorsIcon,
CircleStackIcon
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow"; import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
import { FlowType } from "./types/flow"; import { FlowType } from "./types/flow";
@ -71,11 +75,14 @@ export const nodeColors: {[char: string]: string} = {
chains: "#FE7500", chains: "#FE7500",
agents: "#903BBE", agents: "#903BBE",
tools: "#FF3434", tools: "#FF3434",
memories: "#FF9135", memories: "#F5B85A",
advanced: "#000000", advanced: "#000000",
chat: "#454173", chat: "#454173",
thought:"#272541", thought:"#272541",
docloaders:"#FF9135", embeddings:"#42BAA7",
documentloaders:"#7AAE42",
vectorstores: "#AA8742",
textsplitters: "#B47CB5",
toolkits:"#DB2C2C", toolkits:"#DB2C2C",
wrappers:"#E6277A", wrappers:"#E6277A",
unknown:"#9CA3AF" unknown:"#9CA3AF"
@ -90,9 +97,12 @@ export const nodeNames:{[char: string]: string} = {
memories: "Memories", memories: "Memories",
advanced: "Advanced", advanced: "Advanced",
chat: "Chat", chat: "Chat",
docloaders:"Document Loader", embeddings: "Embeddings",
documentloaders: "Document Loaders",
vectorstores: "Vector Stores",
toolkits:"Toolkits", toolkits:"Toolkits",
wrappers:"Wrappers", wrappers:"Wrappers",
textsplitters: "Text Splitters",
unknown:"Unknown" unknown:"Unknown"
}; };
@ -105,8 +115,11 @@ export const nodeIcons:{[char: string]: React.ForwardRefExoticComponent<React.SV
tools: WrenchIcon, tools: WrenchIcon,
advanced: ComputerDesktopIcon, advanced: ComputerDesktopIcon,
chat: Bars3CenterLeftIcon, chat: Bars3CenterLeftIcon,
docloaders:Bars3CenterLeftIcon, embeddings:FingerPrintIcon,
documentloaders:PaperClipIcon,
vectorstores: CircleStackIcon,
toolkits:WrenchScrewdriverIcon, toolkits:WrenchScrewdriverIcon,
textsplitters:ScissorsIcon,
wrappers:GiftIcon, wrappers:GiftIcon,
unknown:QuestionMarkCircleIcon unknown:QuestionMarkCircleIcon
}; };

View file

@ -34,7 +34,9 @@ module.exports = {
}, },
}, },
'.password':{ '.password':{
"-webkit-text-security":"disc" "-webkit-text-security":"disc",
"font-family": "text-security-disc"
} }
} }
) )

View file

@ -206,7 +206,7 @@
"type": "JsonSpec", "type": "JsonSpec",
"node": { "node": {
"template": { "template": {
"dict_": { "path": {
"required": true, "required": true,
"placeholder": "", "placeholder": "",
"show": true, "show": true,
@ -218,7 +218,7 @@
".yml" ".yml"
], ],
"password": false, "password": false,
"name": "dict_", "name": "path",
"type": "file", "type": "file",
"list": false, "list": false,
"fileTypes": [ "fileTypes": [

View file

@ -49,7 +49,7 @@ def test_cache_creation(basic_data_graph):
) )
save_cache(computed_hash, langchain_object, is_first_message) save_cache(computed_hash, langchain_object, is_first_message)
# Check if the cache file exists # Check if the cache file exists
cache_file = Path(tempfile.gettempdir()) / f"{PREFIX}_{computed_hash}.dill" cache_file = Path(tempfile.gettempdir()) / f"{PREFIX}/{computed_hash}.dill"
assert cache_file.exists() assert cache_file.exists()

View file

@ -53,9 +53,9 @@ def test_conversation_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "input", "value": "input",
"password": False, "password": False,
@ -64,9 +64,9 @@ def test_conversation_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "response", "value": "response",
"password": False, "password": False,
@ -125,9 +125,9 @@ def test_llm_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "text", "value": "text",
"password": False, "password": False,
@ -179,9 +179,9 @@ def test_llm_checker_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "query", "value": "query",
"password": False, "password": False,
@ -190,9 +190,9 @@ def test_llm_checker_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "result", "value": "result",
"password": False, "password": False,
@ -251,9 +251,9 @@ def test_llm_math_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "question", "value": "question",
"password": False, "password": False,
@ -262,9 +262,9 @@ def test_llm_math_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "answer", "value": "answer",
"password": False, "password": False,
@ -338,9 +338,9 @@ def test_series_character_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "input", "value": "input",
"password": False, "password": False,
@ -349,9 +349,9 @@ def test_series_character_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "response", "value": "response",
"password": False, "password": False,
@ -462,7 +462,7 @@ def test_mid_journey_prompt_chain(client: TestClient):
assert template["prompt"] == { assert template["prompt"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": { "value": {
"input_variables": ["history", "input"], "input_variables": ["history", "input"],
@ -489,9 +489,9 @@ def test_mid_journey_prompt_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "response", "value": "response",
"password": False, "password": False,
@ -500,9 +500,9 @@ def test_mid_journey_prompt_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "input", "value": "input",
"password": False, "password": False,
@ -590,7 +590,7 @@ def test_time_travel_guide_chain(client: TestClient):
assert template["prompt"] == { assert template["prompt"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": { "value": {
"input_variables": ["history", "input"], "input_variables": ["history", "input"],
@ -617,9 +617,9 @@ def test_time_travel_guide_chain(client: TestClient):
"list": False, "list": False,
} }
assert template["output_key"] == { assert template["output_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "response", "value": "response",
"password": False, "password": False,
@ -629,9 +629,9 @@ def test_time_travel_guide_chain(client: TestClient):
} }
assert template["input_key"] == { assert template["input_key"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "input", "value": "input",
"password": False, "password": False,

View file

@ -1,6 +1,6 @@
# Test this: # Test this:
import pytest import pytest
from langflow.interface.custom.types import PythonFunction from langflow.interface.tools.custom import PythonFunction
from langflow.utils import constants from langflow.utils import constants

View file

@ -1,7 +1,9 @@
import json import json
from typing import Type, Union
import pytest import pytest
from langchain.agents import AgentExecutor from langchain.agents import AgentExecutor
from langchain.llms.fake import FakeListLLM
from langflow.graph import Edge, Graph, Node from langflow.graph import Edge, Graph, Node
from langflow.graph.nodes import ( from langflow.graph.nodes import (
AgentNode, AgentNode,
@ -13,6 +15,7 @@ from langflow.graph.nodes import (
ToolNode, ToolNode,
WrapperNode, WrapperNode,
) )
from langflow.interface.run import get_result_and_thought_using_graph
from langflow.utils.payload import build_json, get_root_node from langflow.utils.payload import build_json, get_root_node
# Test cases for the graph module # Test cases for the graph module
@ -53,7 +56,7 @@ def openapi_graph():
return get_graph("openapi") return get_graph("openapi")
def get_node_by_type(graph, node_type): def get_node_by_type(graph, node_type: Type[Node]) -> Union[Node, None]:
"""Get a node by type""" """Get a node by type"""
return next((node for node in graph.nodes if isinstance(node, node_type)), None) return next((node for node in graph.nodes if isinstance(node, node_type)), None)
@ -418,3 +421,28 @@ def test_wrapper_node_build(openapi_graph):
built_object = wrapper_node.build() built_object = wrapper_node.build()
assert built_object is not None assert built_object is not None
# Add any further assertions specific to the WrapperNode's build() method # Add any further assertions specific to the WrapperNode's build() method
def test_get_result_and_thought(basic_graph):
"""Test the get_result_and_thought method"""
responses = [
"Final Answer: I am a response",
]
message = "Hello"
# Find the node that is an LLMNode and change the
# _built_object to a FakeListLLM
llm_node = get_node_by_type(basic_graph, LLMNode)
assert llm_node is not None
llm_node._built_object = FakeListLLM(responses=responses)
llm_node._built = True
langchain_object = basic_graph.build()
# assert all nodes are built
assert all(node._built for node in basic_graph.nodes)
# now build again and check if FakeListLLM was used
# Get the result and thought
result, thought = get_result_and_thought_using_graph(langchain_object, message)
# The result should be a str
assert isinstance(result, str)
# The thought should be a Thought
assert isinstance(thought, str)

View file

@ -53,7 +53,7 @@ def test_hugging_face_hub(client: TestClient):
assert template["repo_id"] == { assert template["repo_id"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"value": "gpt2", "value": "gpt2",
"password": False, "password": False,
@ -62,19 +62,20 @@ def test_hugging_face_hub(client: TestClient):
"list": False, "list": False,
} }
assert template["task"] == { assert template["task"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"password": False, "password": False,
"options": ["text-generation", "text2text-generation"],
"name": "task", "name": "task",
"type": "str", "type": "str",
"list": False, "list": True,
} }
assert template["model_kwargs"] == { assert template["model_kwargs"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"password": False, "password": False,
"name": "model_kwargs", "name": "model_kwargs",
@ -82,12 +83,13 @@ def test_hugging_face_hub(client: TestClient):
"list": False, "list": False,
} }
assert template["huggingfacehub_api_token"] == { assert template["huggingfacehub_api_token"] == {
"required": False, "required": True,
"placeholder": "", "placeholder": "",
"show": True, "show": True,
"multiline": False, "multiline": False,
"password": True, "password": True,
"name": "huggingfacehub_api_token", "name": "huggingfacehub_api_token",
"display_name": "HuggingFace Hub API Token",
"type": "str", "type": "str",
"list": False, "list": False,
} }
@ -231,7 +233,7 @@ def test_openai(client: TestClient):
assert template["model_kwargs"] == { assert template["model_kwargs"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"password": False, "password": False,
"name": "model_kwargs", "name": "model_kwargs",
@ -361,7 +363,7 @@ def test_chat_open_ai(client: TestClient):
assert template["model_kwargs"] == { assert template["model_kwargs"] == {
"required": False, "required": False,
"placeholder": "", "placeholder": "",
"show": False, "show": True,
"multiline": False, "multiline": False,
"password": False, "password": False,
"name": "model_kwargs", "name": "model_kwargs",
@ -438,7 +440,7 @@ def test_chat_open_ai(client: TestClient):
assert template["_type"] == "ChatOpenAI" assert template["_type"] == "ChatOpenAI"
assert ( assert (
model["description"] model["description"]
== "Wrapper around OpenAI Chat large language models.To use, you should have the ``openai`` python package installed, and theenvironment variable ``OPENAI_API_KEY`` set with your API key.Any parameters that are valid to be passed to the openai.create call can be passedin, even if not explicitly saved on this class." # noqa E501 == "Wrapper around OpenAI Chat large language models." # noqa E501
) )
assert set(model["base_classes"]) == { assert set(model["base_classes"]) == {
"BaseChatModel", "BaseChatModel",

View file

@ -8,7 +8,6 @@ from langflow.utils.util import (
build_template_from_function, build_template_from_function,
format_dict, format_dict,
get_base_classes, get_base_classes,
get_class_doc,
get_default_factory, get_default_factory,
) )
from pydantic import BaseModel from pydantic import BaseModel
@ -280,12 +279,3 @@ def test_get_default_factory():
default_value = get_default_factory(module_name, function_repr) default_value = get_default_factory(module_name, function_repr)
assert default_value == "default_value" assert default_value == "default_value"
# Test get_class_doc
def test_get_class_doc():
class_doc_parent = get_class_doc(Parent)
class_doc_child = get_class_doc(Child)
assert class_doc_parent["Description"] == "Parent Class"
assert class_doc_child["Description"] == "Child Class"

View file

@ -0,0 +1,12 @@
from fastapi.testclient import TestClient
from langflow.settings import settings
# check that all agents are in settings.agents
# are in json_response["agents"]
def test_vectorstores_settings(client: TestClient):
response = client.get("/all")
assert response.status_code == 200
json_response = response.json()
vectorstores = json_response["vectorstores"]
assert set(vectorstores.keys()) == set(settings.vectorstores)