Merge branch 'dev' of https://github.com/logspace-ai/langflow into NodeModal
This commit is contained in:
commit
72bb4e45c5
47 changed files with 1319 additions and 516 deletions
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Browser and Version**
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -9,6 +9,11 @@ lerna-debug.log*
|
|||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# VSCode
|
||||
.vscode
|
||||
.chroma
|
||||
.ruff_cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
|
|
@ -233,5 +238,5 @@ venv.bak/
|
|||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Poetry
|
||||
.testenv/*
|
||||
# Poetry
|
||||
.testenv/*
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ FROM python:3.10-slim
|
|||
WORKDIR /app
|
||||
|
||||
# Install Poetry
|
||||
RUN apt-get update && apt-get install gcc g++ curl build-essential -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 -
|
||||
# # Add Poetry to PATH
|
||||
ENV PATH="${PATH}:/root/.local/bin"
|
||||
|
|
|
|||
895
poetry.lock
generated
895
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "langflow"
|
||||
version = "0.0.55"
|
||||
version = "0.0.57"
|
||||
description = "A Python package with a built-in web application"
|
||||
authors = ["Logspace <contact@logspace.ai>"]
|
||||
maintainers = [
|
||||
|
|
@ -38,6 +38,15 @@ chromadb = "^0.3.21"
|
|||
huggingface-hub = "^0.13.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-binary = "^2.9.6"
|
||||
pyarrow = "^11.0.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^23.1.0"
|
||||
|
|
|
|||
56
src/backend/langflow/cache/utils.py
vendored
56
src/backend/langflow/cache/utils.py
vendored
|
|
@ -1,3 +1,4 @@
|
|||
import base64
|
||||
import contextlib
|
||||
import functools
|
||||
import hashlib
|
||||
|
|
@ -10,6 +11,19 @@ from pathlib import Path
|
|||
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()
|
||||
|
||||
|
|
@ -39,9 +53,10 @@ def memoize_dict(maxsize=128):
|
|||
PREFIX = "langflow_cache"
|
||||
|
||||
|
||||
@create_cache_folder
|
||||
def clear_old_cache_files(max_cache_size: int = 3):
|
||||
cache_dir = Path(tempfile.gettempdir())
|
||||
cache_files = list(cache_dir.glob(f"{PREFIX}_*.dill"))
|
||||
cache_dir = Path(tempfile.gettempdir()) / PREFIX
|
||||
cache_files = list(cache_dir.glob("*.dill"))
|
||||
|
||||
if len(cache_files) > max_cache_size:
|
||||
cache_files_sorted_by_mtime = sorted(
|
||||
|
|
@ -84,8 +99,40 @@ def filter_json(json_data):
|
|||
return filtered_data
|
||||
|
||||
|
||||
@create_cache_folder
|
||||
def save_binary_file(content: str, file_name: str, accepted_types: list[str]) -> str:
|
||||
"""
|
||||
Save a binary file to the specified folder.
|
||||
|
||||
Args:
|
||||
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):
|
||||
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:
|
||||
dill.dump(chat_data, cache_file)
|
||||
|
||||
|
|
@ -93,8 +140,9 @@ def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool):
|
|||
clear_old_cache_files()
|
||||
|
||||
|
||||
@create_cache_folder
|
||||
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():
|
||||
with cache_path.open("rb") as cache_file:
|
||||
return dill.load(cache_file)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ agents:
|
|||
- initialize_agent
|
||||
- VectorStoreAgent
|
||||
- VectorStoreRouterAgent
|
||||
- SQLAgent
|
||||
|
||||
prompts:
|
||||
- PromptTemplate
|
||||
|
|
@ -61,8 +62,31 @@ vectorstores:
|
|||
- Chroma
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ CUSTOM_NODES = {
|
|||
"initialize_agent": nodes.InitializeAgentNode(),
|
||||
"VectorStoreAgent": nodes.VectorStoreAgentNode(),
|
||||
"VectorStoreRouterAgent": nodes.VectorStoreRouterAgentNode(),
|
||||
"SQLAgent": nodes.SQLAgentNode(),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
# - Defer prompts building to the last moment or when they have all the tools
|
||||
# - Build each inner agent first, then build the outer agent
|
||||
|
||||
import contextlib
|
||||
import types
|
||||
import warnings
|
||||
from copy import deepcopy
|
||||
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.utils import load_file
|
||||
from langflow.interface import loading
|
||||
from langflow.interface.listing import ALL_TYPES_DICT
|
||||
from langflow.utils.logger import logger
|
||||
|
|
@ -88,11 +89,12 @@ class Node:
|
|||
file_name = value.get("value")
|
||||
content = value.get("content")
|
||||
type_to_load = value.get("suffixes")
|
||||
loaded_dict = load_file(file_name, content, type_to_load)
|
||||
params[key] = loaded_dict
|
||||
file_path = cache_utils.save_binary_file(
|
||||
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:
|
||||
# Get the edge that connects to this node
|
||||
edges = [
|
||||
|
|
@ -126,6 +128,9 @@ class Node:
|
|||
new_value = value.get("value")
|
||||
if new_value 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
|
||||
|
||||
# Add _type to params
|
||||
|
|
|
|||
|
|
@ -18,16 +18,16 @@ from langflow.graph.nodes import (
|
|||
)
|
||||
from langflow.interface.agents.base import agent_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.llms.base import llm_creator
|
||||
from langflow.interface.memories.base import memory_creator
|
||||
from langflow.interface.prompts.base import prompt_creator
|
||||
from langflow.interface.textSplitters.base import textsplitter_creator
|
||||
from langflow.interface.text_splitters.base import textsplitter_creator
|
||||
from langflow.interface.toolkits.base import toolkits_creator
|
||||
from langflow.interface.tools.base import tool_creator
|
||||
from langflow.interface.tools.constants import FILE_TOOLS
|
||||
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.utils import payload
|
||||
|
||||
|
|
@ -62,7 +62,12 @@ class Graph:
|
|||
if isinstance(node, ToolkitNode):
|
||||
node.params["llm"] = llm_node
|
||||
# remove invalid nodes
|
||||
self.nodes = [node for node in self.nodes if self._validate_node(node)]
|
||||
self.nodes = [
|
||||
node
|
||||
for node in self.nodes
|
||||
if self._validate_node(node)
|
||||
or (len(self.nodes) == 1 and len(self.edges) == 0)
|
||||
]
|
||||
|
||||
def _validate_node(self, node: Node) -> bool:
|
||||
# All nodes that do not have edges are invalid
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ class AgentNode(Node):
|
|||
|
||||
self._build()
|
||||
|
||||
#! Cannot deepcopy VectorStore
|
||||
if self.node_type in ["VectorStoreAgent", "VectorStoreRouterAgent"]:
|
||||
#! Cannot deepcopy VectorStore, VectorStoreRouter, or SQL agents
|
||||
if self.node_type in ["VectorStoreAgent", "VectorStoreRouterAgent", "SQLAgent"]:
|
||||
return self._built_object
|
||||
return deepcopy(self._built_object)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,48 +1,4 @@
|
|||
import base64
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
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)
|
||||
elif suffix == "txt":
|
||||
# Return the text content
|
||||
return decoded_string
|
||||
else:
|
||||
raise ValueError(f"File {file_name} is not accepted")
|
||||
|
||||
|
||||
def validate_prompt(prompt: str):
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from langflow.custom.customs import get_custom_nodes
|
|||
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
||||
from langflow.interface.base import LangChainTypeCreator
|
||||
from langflow.settings import settings
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.util import build_template_from_class
|
||||
|
||||
|
||||
|
|
@ -31,6 +32,9 @@ class AgentCreator(LangChainTypeCreator):
|
|||
)
|
||||
except ValueError as 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
|
||||
def to_list(self) -> List[str]:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
from typing import Any, List, Optional
|
||||
|
||||
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,
|
||||
|
|
@ -11,6 +17,7 @@ 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.pandas.prompt import PREFIX as PANDAS_PREFIX
|
||||
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,
|
||||
)
|
||||
|
|
@ -18,10 +25,13 @@ 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 as SQL_FORMAT_INSTRUCTIONS
|
||||
from langchain.llms.base import BaseLLM
|
||||
from langchain.memory.chat_memory import BaseChatMemory
|
||||
from langchain.schema import BaseLanguageModel
|
||||
from langchain.sql_database import SQLDatabase
|
||||
from langchain.tools.python.tool import PythonAstREPLTool
|
||||
from langchain.tools.sql_database.prompt import QUERY_CHECKER
|
||||
|
||||
|
||||
class JsonAgent(AgentExecutor):
|
||||
|
|
@ -77,7 +87,7 @@ class CSVAgent(AgentExecutor):
|
|||
@classmethod
|
||||
def from_toolkit_and_llm(
|
||||
cls,
|
||||
path: dict,
|
||||
path: str,
|
||||
llm: BaseLanguageModel,
|
||||
pandas_kwargs: Optional[dict] = None,
|
||||
**kwargs: Any
|
||||
|
|
@ -85,7 +95,7 @@ class CSVAgent(AgentExecutor):
|
|||
import pandas as pd # type: ignore
|
||||
|
||||
_kwargs = pandas_kwargs or {}
|
||||
df = pd.DataFrame.from_dict(path, **_kwargs)
|
||||
df = pd.read_csv(path, **_kwargs)
|
||||
|
||||
tools = [PythonAstREPLTool(locals={"df": df})] # type: ignore
|
||||
prompt = ZeroShotAgent.create_prompt(
|
||||
|
|
@ -146,6 +156,76 @@ class VectorStoreAgent(AgentExecutor):
|
|||
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"""
|
||||
|
||||
|
|
@ -218,4 +298,5 @@ CUSTOM_AGENTS = {
|
|||
"initialize_agent": InitializeAgent,
|
||||
"VectorStoreAgent": VectorStoreAgent,
|
||||
"VectorStoreRouterAgent": VectorStoreRouterAgent,
|
||||
"SQLAgent": SQLAgent,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Type, Union
|
|||
from pydantic import BaseModel
|
||||
|
||||
from langflow.template.base import FrontendNode, Template, TemplateField
|
||||
from langflow.utils.logger import logger
|
||||
|
||||
# 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:
|
||||
# {name: {template: {fields}, description: str}}
|
||||
# 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
|
||||
|
||||
def frontend_node(self, name) -> FrontendNode:
|
||||
def frontend_node(self, name) -> Union[FrontendNode, None]:
|
||||
signature = self.get_signature(name)
|
||||
if signature is None:
|
||||
raise ValueError(f"{name} not found")
|
||||
logger.error(f"Node {name} not loaded")
|
||||
return None
|
||||
if isinstance(signature, FrontendNode):
|
||||
return signature
|
||||
fields = [
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from langflow.interface.base import LangChainTypeCreator
|
|||
from langflow.interface.custom_lists import chain_type_to_cls_dict
|
||||
from langflow.settings import settings
|
||||
from langflow.template.nodes import ChainFrontendNode
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.util import build_template_from_class
|
||||
|
||||
# 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)
|
||||
except ValueError as 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]:
|
||||
custom_chains = list(get_custom_nodes("chains").keys())
|
||||
|
|
|
|||
|
|
@ -1,64 +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.interface.documentLoaders.custom import CUSTOM_DOCUMENTLOADERS
|
||||
from langflow.settings import settings
|
||||
from langflow.utils.util import build_template_from_class
|
||||
|
||||
|
||||
class DocumentLoaderCreator(LangChainTypeCreator):
|
||||
type_name: str = "documentloaders"
|
||||
|
||||
@property
|
||||
def type_to_loader_dict(self) -> Dict:
|
||||
types = documentloaders_type_to_cls_dict
|
||||
|
||||
# Drop some types that are reimplemented with the same name
|
||||
types.pop("TextLoader")
|
||||
|
||||
for name, documentloader in CUSTOM_DOCUMENTLOADERS.items():
|
||||
types[name] = documentloader
|
||||
|
||||
return types
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
if name == "TextLoader":
|
||||
signature["template"]["file"] = {
|
||||
"type": "file",
|
||||
"required": True,
|
||||
"show": True,
|
||||
"name": "path",
|
||||
"value": "",
|
||||
"suffixes": [".txt"],
|
||||
"fileTypes": ["txt"],
|
||||
}
|
||||
elif name == "WebBaseLoader":
|
||||
signature["template"]["web_path"] = {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"show": True,
|
||||
"name": "web_path",
|
||||
"value": "",
|
||||
"display_name": "Web Path",
|
||||
}
|
||||
|
||||
return signature
|
||||
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()
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
"""Load text files."""
|
||||
from typing import List
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.document_loaders.base import BaseLoader
|
||||
|
||||
|
||||
class TextLoader(BaseLoader):
|
||||
"""Load Text files."""
|
||||
|
||||
def __init__(self, file: str):
|
||||
"""Initialize with file path."""
|
||||
self.file = file
|
||||
|
||||
def load(self) -> List[Document]:
|
||||
"""Load from file path."""
|
||||
return [Document(page_content=self.file, metadata={"source": "loaded"})]
|
||||
|
||||
|
||||
CUSTOM_DOCUMENTLOADERS = {
|
||||
"TextLoader": TextLoader,
|
||||
}
|
||||
146
src/backend/langflow/interface/document_loaders/base.py
Normal file
146
src/backend/langflow/interface/document_loaders/base.py
Normal 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()
|
||||
|
|
@ -3,6 +3,7 @@ from typing import Dict, List, Optional
|
|||
from langflow.interface.base import LangChainTypeCreator
|
||||
from langflow.interface.custom_lists import embedding_type_to_cls_dict
|
||||
from langflow.settings import settings
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.util import build_template_from_class
|
||||
|
||||
|
||||
|
|
@ -20,6 +21,10 @@ class EmbeddingCreator(LangChainTypeCreator):
|
|||
except ValueError as 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]:
|
||||
return [
|
||||
embedding.__name__
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from langchain.chat_models.base import BaseChatModel
|
|||
from langchain.llms.base import BaseLLM
|
||||
from langchain.tools import BaseTool
|
||||
|
||||
from langflow.interface.documentLoaders.custom import CUSTOM_DOCUMENTLOADERS
|
||||
from langflow.interface.tools.util import get_tool_by_name
|
||||
|
||||
|
||||
|
|
@ -132,8 +131,6 @@ def import_vectorstore(vectorstore: str) -> Any:
|
|||
|
||||
def import_documentloader(documentloader: str) -> Any:
|
||||
"""Import documentloader from documentloader name"""
|
||||
if documentloader in CUSTOM_DOCUMENTLOADERS:
|
||||
return CUSTOM_DOCUMENTLOADERS[documentloader]
|
||||
|
||||
return import_class(f"langchain.document_loaders.{documentloader}")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
from langflow.interface.agents.base import agent_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.llms.base import llm_creator
|
||||
from langflow.interface.memories.base import memory_creator
|
||||
from langflow.interface.prompts.base import prompt_creator
|
||||
from langflow.interface.textSplitters.base import textsplitter_creator
|
||||
from langflow.interface.text_splitters.base import textsplitter_creator
|
||||
from langflow.interface.toolkits.base import toolkits_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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from langflow.interface.base import LangChainTypeCreator
|
|||
from langflow.interface.custom_lists import llm_type_to_cls_dict
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -27,6 +28,10 @@ class LLMCreator(LangChainTypeCreator):
|
|||
except ValueError as 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]:
|
||||
return [
|
||||
llm.__name__
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from langflow.interface.agents.custom import CUSTOM_AGENTS
|
|||
from langflow.interface.importing.utils import import_by_type
|
||||
from langflow.interface.toolkits.base import toolkits_creator
|
||||
from langflow.interface.types import get_type_list
|
||||
from langflow.interface.utils import load_file_into_dict
|
||||
from langflow.utils import util, validate
|
||||
|
||||
|
||||
|
|
@ -36,21 +37,27 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
|||
if base_type == "agents":
|
||||
# We need to initialize it differently
|
||||
return load_agent_executor(class_object, params)
|
||||
elif node_type == "ZeroShotPrompt":
|
||||
if "tools" not in params:
|
||||
params["tools"] = []
|
||||
return ZeroShotAgent.create_prompt(**params)
|
||||
|
||||
elif node_type == "PythonFunction":
|
||||
# If the node_type is "PythonFunction"
|
||||
# we need to get the function from the params
|
||||
# which will be a str containing a python function
|
||||
# and then we need to compile it and return the function
|
||||
# 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 == "prompts":
|
||||
if node_type == "ZeroShotPrompt":
|
||||
if "tools" not in params:
|
||||
params["tools"] = []
|
||||
return ZeroShotAgent.create_prompt(**params)
|
||||
elif base_type == "tools":
|
||||
if node_type == "JsonSpec":
|
||||
params["dict_"] = load_file_into_dict(params.pop("path"))
|
||||
return class_object(**params)
|
||||
elif node_type == "PythonFunction":
|
||||
# If the node_type is "PythonFunction"
|
||||
# we need to get the function from the params
|
||||
# which will be a str containing a python function
|
||||
# and then we need to compile it and return the function
|
||||
# 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 node_type.lower() == "tool":
|
||||
return class_object(**params)
|
||||
elif base_type == "toolkits":
|
||||
loaded_toolkit = class_object(**params)
|
||||
# Check if node_type has a loader
|
||||
|
|
@ -68,8 +75,8 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
|||
documents = params.pop("documents")
|
||||
text_splitter = class_object(**params)
|
||||
return text_splitter.split_documents(documents)
|
||||
else:
|
||||
return class_object(**params)
|
||||
|
||||
return class_object(**params)
|
||||
|
||||
|
||||
def load_flow_from_json(path: str):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from langflow.interface.custom_lists import memory_type_to_cls_dict
|
|||
from langflow.settings import settings
|
||||
from langflow.template.base import FrontendNode
|
||||
from langflow.template.nodes import MemoryFrontendNode
|
||||
from langflow.utils.logger import logger
|
||||
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)
|
||||
except ValueError as 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]:
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from langflow.interface.base import LangChainTypeCreator
|
|||
from langflow.interface.importing.utils import import_class
|
||||
from langflow.settings import settings
|
||||
from langflow.template.nodes import PromptFrontendNode
|
||||
from langflow.utils.logger import logger
|
||||
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 build_template_from_class(name, self.type_to_loader_dict)
|
||||
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]:
|
||||
custom_prompts = get_custom_nodes("prompts")
|
||||
|
|
|
|||
|
|
@ -170,10 +170,12 @@ def fix_memory_inputs(langchain_object):
|
|||
if langchain_object.memory.memory_key in langchain_object.input_variables:
|
||||
return
|
||||
except AttributeError:
|
||||
if (
|
||||
langchain_object.memory.memory_key
|
||||
in langchain_object.prompt.input_variables
|
||||
):
|
||||
input_variables = (
|
||||
langchain_object.prompt.input_variables
|
||||
if hasattr(langchain_object, "prompt")
|
||||
else langchain_object.input_keys
|
||||
)
|
||||
if langchain_object.memory.memory_key in input_variables:
|
||||
return
|
||||
|
||||
possible_new_mem_key = get_memory_key(langchain_object)
|
||||
|
|
@ -191,9 +193,12 @@ def get_result_and_thought_using_graph(langchain_object, message: str):
|
|||
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
||||
memory_key = langchain_object.memory.memory_key
|
||||
|
||||
for key in langchain_object.input_keys:
|
||||
if key not in [memory_key, "chat_history"]:
|
||||
chat_input = {key: message}
|
||||
if hasattr(langchain_object, "input_keys"):
|
||||
for key in langchain_object.input_keys:
|
||||
if key not in [memory_key, "chat_history"]:
|
||||
chat_input = {key: message}
|
||||
else:
|
||||
chat_input = message # type: ignore
|
||||
|
||||
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||
# https://github.com/hwchase17/langchain/issues/2068
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
from langflow.interface.textSplitters.base import TextSplitterCreator
|
||||
|
||||
__all__ = ["TextSplitterCreator"]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from langflow.interface.text_splitters.base import TextSplitterCreator
|
||||
|
||||
__all__ = ["TextSplitterCreator"]
|
||||
|
|
@ -3,6 +3,7 @@ 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
|
||||
|
||||
|
||||
|
|
@ -25,9 +26,39 @@ class TextSplitterCreator(LangChainTypeCreator):
|
|||
"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 [
|
||||
|
|
@ -5,6 +5,7 @@ from langchain.agents import agent_toolkits
|
|||
from langflow.interface.base import LangChainTypeCreator
|
||||
from langflow.interface.importing.utils import import_class, import_module
|
||||
from langflow.settings import settings
|
||||
from langflow.utils.logger import logger
|
||||
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)
|
||||
except ValueError as 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]:
|
||||
return list(self.type_to_loader_dict.keys())
|
||||
|
|
|
|||
|
|
@ -47,12 +47,14 @@ TOOL_INPUTS = {
|
|||
value="",
|
||||
multiline=True,
|
||||
),
|
||||
"dict_": TemplateField(
|
||||
"path": TemplateField(
|
||||
field_type="file",
|
||||
required=True,
|
||||
is_list=False,
|
||||
show=True,
|
||||
value="",
|
||||
suffixes=[".json", ".yaml", ".yml"],
|
||||
fileTypes=["json", "yaml", "yml"],
|
||||
),
|
||||
}
|
||||
|
||||
|
|
@ -104,8 +106,8 @@ class ToolCreator(LangChainTypeCreator):
|
|||
n_dict = {val[0]: val[1] for val in _EXTRA_OPTIONAL_TOOLS.values()} # type: ignore
|
||||
extra_keys = n_dict[all_tools[tool_type]["fcn"]]
|
||||
params = extra_keys
|
||||
elif tool_type == "Tool":
|
||||
params = ["name", "description", "func"]
|
||||
# elif tool_type == "Tool":
|
||||
# params = ["name", "description", "func"]
|
||||
elif tool_type in CUSTOM_TOOLS:
|
||||
# Get custom tool params
|
||||
params = all_tools[name]["params"] # type: ignore
|
||||
|
|
@ -114,6 +116,8 @@ class ToolCreator(LangChainTypeCreator):
|
|||
return node
|
||||
elif tool_type in FILE_TOOLS:
|
||||
params = all_tools[name]["params"] # type: ignore
|
||||
if tool_type == "JsonSpec":
|
||||
params["path"] = params.pop("dict_") # type: ignore
|
||||
base_classes += [name]
|
||||
else:
|
||||
params = []
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
from langflow.interface.agents.base import agent_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.llms.base import llm_creator
|
||||
from langflow.interface.memories.base import memory_creator
|
||||
from langflow.interface.prompts.base import prompt_creator
|
||||
from langflow.interface.textSplitters.base import textsplitter_creator
|
||||
from langflow.interface.text_splitters.base import textsplitter_creator
|
||||
from langflow.interface.toolkits.base import toolkits_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
|
||||
|
||||
|
||||
|
|
|
|||
22
src/backend/langflow/interface/utils.py
Normal file
22
src/backend/langflow/interface/utils.py
Normal 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
|
||||
|
|
@ -3,6 +3,7 @@ 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
|
||||
|
||||
|
||||
|
|
@ -39,6 +40,9 @@ class VectorstoreCreator(LangChainTypeCreator):
|
|||
|
||||
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 [
|
||||
|
|
@ -3,6 +3,7 @@ from typing import Dict, List, Optional
|
|||
from langchain import requests
|
||||
|
||||
from langflow.interface.base import LangChainTypeCreator
|
||||
from langflow.utils.logger import logger
|
||||
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)
|
||||
except ValueError as 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]:
|
||||
return list(self.type_to_loader_dict.keys())
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class PythonFunctionNode(FrontendNode):
|
|||
class ToolNode(FrontendNode):
|
||||
name: str = "Tool"
|
||||
template: Template = Template(
|
||||
type_name="tool",
|
||||
type_name="Tool",
|
||||
fields=[
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
|
|
@ -127,19 +127,27 @@ class ToolNode(FrontendNode):
|
|||
name="description",
|
||||
),
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
name="func",
|
||||
field_type="function",
|
||||
required=True,
|
||||
is_list=False,
|
||||
show=True,
|
||||
multiline=True,
|
||||
),
|
||||
TemplateField(
|
||||
field_type="bool",
|
||||
required=True,
|
||||
placeholder="",
|
||||
is_list=False,
|
||||
show=True,
|
||||
multiline=True,
|
||||
value="",
|
||||
name="func",
|
||||
multiline=False,
|
||||
value=False,
|
||||
name="return_direct",
|
||||
),
|
||||
],
|
||||
)
|
||||
description: str = "Tool to be used in the flow."
|
||||
base_classes: list[str] = ["BaseTool"]
|
||||
base_classes: list[str] = ["Tool"]
|
||||
|
||||
def to_dict(self):
|
||||
return super().to_dict()
|
||||
|
|
@ -304,6 +312,37 @@ class VectorStoreRouterAgentNode(FrontendNode):
|
|||
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):
|
||||
@staticmethod
|
||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||
|
|
@ -348,6 +387,7 @@ class MemoryFrontendNode(FrontendNode):
|
|||
field.field_type = "int"
|
||||
field.value = 10
|
||||
field.display_name = "Memory Size"
|
||||
field.password = False
|
||||
|
||||
|
||||
class ChainFrontendNode(FrontendNode):
|
||||
|
|
@ -358,6 +398,14 @@ class ChainFrontendNode(FrontendNode):
|
|||
if "key" in field.name:
|
||||
field.password = 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):
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ def get_root_node(graph):
|
|||
Returns the root node of the template.
|
||||
"""
|
||||
incoming_edges = {edge.source for edge in graph.edges}
|
||||
|
||||
if not incoming_edges and len(graph.nodes) == 1:
|
||||
return graph.nodes[0]
|
||||
|
||||
return next((node for node in graph.nodes if node not in incoming_edges), None)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import inspect
|
|||
import re
|
||||
from typing import Dict, Optional
|
||||
|
||||
from docstring_parser import parse # type: ignore
|
||||
|
||||
from langflow.template.constants import FORCE_SHOW_FIELDS
|
||||
from langflow.utils import constants
|
||||
|
||||
|
|
@ -65,7 +67,8 @@ def build_template_from_function(
|
|||
if v.__annotations__["return"].__name__ == name:
|
||||
_class = v.__annotations__["return"]
|
||||
|
||||
docs = get_class_doc(_class)
|
||||
# Get the docstring
|
||||
docs = parse(_class.__doc__)
|
||||
|
||||
variables = {"_type": _type}
|
||||
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]["placeholder"] = (
|
||||
docs["Attributes"][class_field_items]
|
||||
if class_field_items in docs["Attributes"]
|
||||
docs.params[class_field_items]
|
||||
if class_field_items in docs.params
|
||||
else ""
|
||||
)
|
||||
# Adding function to base classes to allow
|
||||
|
|
@ -98,7 +101,7 @@ def build_template_from_function(
|
|||
|
||||
return {
|
||||
"template": format_dict(variables, name),
|
||||
"description": docs["Description"],
|
||||
"description": docs.short_description or "",
|
||||
"base_classes": base_classes,
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +120,7 @@ def build_template_from_class(
|
|||
_class = v
|
||||
|
||||
# Get the docstring
|
||||
docs = get_class_doc(_class)
|
||||
docs = parse(_class.__doc__)
|
||||
|
||||
variables = {"_type": _type}
|
||||
|
||||
|
|
@ -140,8 +143,8 @@ def build_template_from_class(
|
|||
variables[class_field_items][name_] = value_
|
||||
|
||||
variables[class_field_items]["placeholder"] = (
|
||||
docs["Attributes"][class_field_items]
|
||||
if class_field_items in docs["Attributes"]
|
||||
docs.params[class_field_items]
|
||||
if class_field_items in docs.params
|
||||
else ""
|
||||
)
|
||||
base_classes = get_base_classes(_class)
|
||||
|
|
@ -151,7 +154,7 @@ def build_template_from_class(
|
|||
base_classes.append("function")
|
||||
return {
|
||||
"template": format_dict(variables, name),
|
||||
"description": docs["Description"],
|
||||
"description": docs.short_description or "",
|
||||
"base_classes": base_classes,
|
||||
}
|
||||
|
||||
|
|
@ -188,60 +191,6 @@ def get_default_factory(module: str, function: str):
|
|||
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):
|
||||
"""
|
||||
Formats a dictionary by removing certain keys and modifying the
|
||||
|
|
|
|||
|
|
@ -59,7 +59,12 @@ def eval_function(function_string: str):
|
|||
# Execute the code string in the new namespace
|
||||
exec(function_string, namespace)
|
||||
function_object = next(
|
||||
(obj for name, obj in namespace.items() if isinstance(obj, types.FunctionType)),
|
||||
(
|
||||
obj
|
||||
for name, obj in namespace.items()
|
||||
if isinstance(obj, types.FunctionType)
|
||||
and obj.__code__.co_filename == "<string>"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if function_object is None:
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@
|
|||
"type": "JsonSpec",
|
||||
"node": {
|
||||
"template": {
|
||||
"dict_": {
|
||||
"path": {
|
||||
"required": true,
|
||||
"placeholder": "",
|
||||
"show": true,
|
||||
|
|
@ -218,7 +218,7 @@
|
|||
".yml"
|
||||
],
|
||||
"password": false,
|
||||
"name": "dict_",
|
||||
"name": "path",
|
||||
"type": "file",
|
||||
"list": false,
|
||||
"fileTypes": [
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ def test_cache_creation(basic_data_graph):
|
|||
)
|
||||
save_cache(computed_hash, langchain_object, is_first_message)
|
||||
# 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ def test_conversation_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["input_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "input",
|
||||
"password": False,
|
||||
|
|
@ -64,9 +64,9 @@ def test_conversation_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["output_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "response",
|
||||
"password": False,
|
||||
|
|
@ -125,9 +125,9 @@ def test_llm_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["output_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "text",
|
||||
"password": False,
|
||||
|
|
@ -179,9 +179,9 @@ def test_llm_checker_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["input_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "query",
|
||||
"password": False,
|
||||
|
|
@ -190,9 +190,9 @@ def test_llm_checker_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["output_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "result",
|
||||
"password": False,
|
||||
|
|
@ -251,9 +251,9 @@ def test_llm_math_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["input_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "question",
|
||||
"password": False,
|
||||
|
|
@ -262,9 +262,9 @@ def test_llm_math_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["output_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "answer",
|
||||
"password": False,
|
||||
|
|
@ -338,9 +338,9 @@ def test_series_character_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["input_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "input",
|
||||
"password": False,
|
||||
|
|
@ -349,9 +349,9 @@ def test_series_character_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["output_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "response",
|
||||
"password": False,
|
||||
|
|
@ -462,7 +462,7 @@ def test_mid_journey_prompt_chain(client: TestClient):
|
|||
assert template["prompt"] == {
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": {
|
||||
"input_variables": ["history", "input"],
|
||||
|
|
@ -489,9 +489,9 @@ def test_mid_journey_prompt_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["output_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "response",
|
||||
"password": False,
|
||||
|
|
@ -500,9 +500,9 @@ def test_mid_journey_prompt_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["input_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "input",
|
||||
"password": False,
|
||||
|
|
@ -590,7 +590,7 @@ def test_time_travel_guide_chain(client: TestClient):
|
|||
assert template["prompt"] == {
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": {
|
||||
"input_variables": ["history", "input"],
|
||||
|
|
@ -617,9 +617,9 @@ def test_time_travel_guide_chain(client: TestClient):
|
|||
"list": False,
|
||||
}
|
||||
assert template["output_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "response",
|
||||
"password": False,
|
||||
|
|
@ -629,9 +629,9 @@ def test_time_travel_guide_chain(client: TestClient):
|
|||
}
|
||||
|
||||
assert template["input_key"] == {
|
||||
"required": False,
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "input",
|
||||
"password": False,
|
||||
|
|
|
|||
|
|
@ -440,7 +440,7 @@ def test_chat_open_ai(client: TestClient):
|
|||
assert template["_type"] == "ChatOpenAI"
|
||||
assert (
|
||||
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"]) == {
|
||||
"BaseChatModel",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from langflow.utils.util import (
|
|||
build_template_from_function,
|
||||
format_dict,
|
||||
get_base_classes,
|
||||
get_class_doc,
|
||||
get_default_factory,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -280,12 +279,3 @@ def test_get_default_factory():
|
|||
default_value = get_default_factory(module_name, function_repr)
|
||||
|
||||
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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue