From a9c7fc0a6983c942b96b45f0158cbaecad11f74f Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 30 May 2023 00:38:14 -0300 Subject: [PATCH 01/59] =?UTF-8?q?=F0=9F=94=A7=20chore(config):=20add=20Vec?= =?UTF-8?q?torStoreToolkit=20to=20toolkits=20list=20=F0=9F=90=9B=20fix(bas?= =?UTF-8?q?e.py):=20remove=20deepcopy=20for=20VectorStore=20and=20VectorSt?= =?UTF-8?q?oreRouter=20agents=20=F0=9F=90=9B=20fix(nodes.py):=20remove=20d?= =?UTF-8?q?eepcopy=20for=20VectorStore=20and=20VectorStoreRouter=20agents?= =?UTF-8?q?=20=F0=9F=94=A7=20chore(loading.py):=20comment=20out=20unused?= =?UTF-8?q?=20code=20for=20loading=20toolkits=20=F0=9F=90=9B=20fix(toolkit?= =?UTF-8?q?s/base.py):=20add=20Tool=20to=20base=5Fclasses=20in=20get=5Fsig?= =?UTF-8?q?nature=20method=20The=20changes=20to=20the=20config=20file=20ad?= =?UTF-8?q?d=20the=20VectorStoreToolkit=20to=20the=20list=20of=20toolkits.?= =?UTF-8?q?=20The=20deepcopy=20for=20VectorStore=20and=20VectorStoreRouter?= =?UTF-8?q?=20agents=20was=20causing=20issues,=20so=20it=20was=20removed?= =?UTF-8?q?=20from=20the=20base.py=20and=20nodes.py=20files.=20The=20loadi?= =?UTF-8?q?ng.py=20file=20had=20some=20unused=20code=20for=20loading=20too?= =?UTF-8?q?lkits,=20so=20it=20was=20commented=20out.=20Finally,=20the=20ba?= =?UTF-8?q?se.py=20file=20had=20a=20bug=20where=20the=20Tool=20class=20was?= =?UTF-8?q?=20not=20being=20added=20to=20the=20base=5Fclasses=20list=20in?= =?UTF-8?q?=20the=20get=5Fsignature=20method,=20so=20it=20was=20added.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/config.yaml | 1 + src/backend/langflow/graph/base.py | 14 +------------- src/backend/langflow/graph/nodes.py | 5 +---- src/backend/langflow/interface/loading.py | 7 +++++-- src/backend/langflow/interface/toolkits/base.py | 15 +++++++++------ 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/backend/langflow/config.yaml b/src/backend/langflow/config.yaml index 02b17cd85..b073ed544 100644 --- a/src/backend/langflow/config.yaml +++ b/src/backend/langflow/config.yaml @@ -74,6 +74,7 @@ toolkits: - JsonToolkit - VectorStoreInfo - VectorStoreRouterToolkit + - VectorStoreToolkit tools: - Search - PAL-MATH diff --git a/src/backend/langflow/graph/base.py b/src/backend/langflow/graph/base.py index 187d2983e..5b64885fb 100644 --- a/src/backend/langflow/graph/base.py +++ b/src/backend/langflow/graph/base.py @@ -212,19 +212,7 @@ class Node: if not self._built or force: self._build() - #! Deepcopy is breaking for vectorstores - if self.base_type in [ - "vectorstores", - "VectorStoreRouterAgent", - "VectorStoreAgent", - "VectorStoreInfo", - ] or self.node_type in [ - "VectorStoreInfo", - "VectorStoreRouterToolkit", - "SQLDatabase", - ]: - return self._built_object - return deepcopy(self._built_object) + return self._built_object def add_edge(self, edge: "Edge") -> None: self.edges.append(edge) diff --git a/src/backend/langflow/graph/nodes.py b/src/backend/langflow/graph/nodes.py index 189e40b5c..7d9b05366 100644 --- a/src/backend/langflow/graph/nodes.py +++ b/src/backend/langflow/graph/nodes.py @@ -14,7 +14,7 @@ class AgentNode(Node): def _set_tools_and_chains(self) -> None: for edge in self.edges: source_node = edge.source - if isinstance(source_node, ToolNode): + if isinstance(source_node, (ToolNode, ToolkitNode)): self.tools.append(source_node) elif isinstance(source_node, ChainNode): self.chains.append(source_node) @@ -32,9 +32,6 @@ class AgentNode(Node): self._build() - #! Cannot deepcopy VectorStore, VectorStoreRouter, or SQL agents - if self.node_type in ["VectorStoreAgent", "VectorStoreRouterAgent", "SQLAgent"]: - return self._built_object return self._built_object diff --git a/src/backend/langflow/interface/loading.py b/src/backend/langflow/interface/loading.py index cd6898a7f..d720c6b0c 100644 --- a/src/backend/langflow/interface/loading.py +++ b/src/backend/langflow/interface/loading.py @@ -101,8 +101,11 @@ def instantiate_tool(node_type, class_object, params): def instantiate_toolkit(node_type, class_object, params): loaded_toolkit = class_object(**params) - if toolkits_creator.has_create_function(node_type): - return load_toolkits_executor(node_type, loaded_toolkit, params) + # Commenting this out for now to use toolkits as normal tools + # if toolkits_creator.has_create_function(node_type): + # return load_toolkits_executor(node_type, loaded_toolkit, params) + if isinstance(loaded_toolkit, BaseToolkit): + return loaded_toolkit.get_tools() return loaded_toolkit diff --git a/src/backend/langflow/interface/toolkits/base.py b/src/backend/langflow/interface/toolkits/base.py index cbe625f0d..9f01b2bb2 100644 --- a/src/backend/langflow/interface/toolkits/base.py +++ b/src/backend/langflow/interface/toolkits/base.py @@ -42,24 +42,27 @@ class ToolkitCreator(LangChainTypeCreator): def get_signature(self, name: str) -> Optional[Dict]: try: - return build_template_from_class(name, self.type_to_loader_dict) + template = build_template_from_class(name, self.type_to_loader_dict) + # add Tool to base_classes + if template: + template["base_classes"].append("Tool") + return template except ValueError as exc: - raise ValueError("Prompt not found") from exc + raise ValueError("Toolkit not found") from exc except AttributeError as exc: - logger.error(f"Prompt {name} not loaded: {exc}") + logger.error(f"Toolkit {name} not loaded: {exc}") return None def to_list(self) -> List[str]: return list(self.type_to_loader_dict.keys()) def get_create_function(self, name: str) -> Callable: - if loader_name := self.create_functions.get(name, None): - # import loader + if loader_name := self.create_functions.get(name): return import_module( f"from langchain.agents.agent_toolkits import {loader_name[0]}" ) else: - raise ValueError("Loader not found") + raise ValueError("Toolkit not found") def has_create_function(self, name: str) -> bool: # check if the function list is not empty From ad3bb997eed981c9426e0b76b16f84309b18ae74 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 30 May 2023 01:11:27 -0300 Subject: [PATCH 02/59] =?UTF-8?q?=F0=9F=90=9B=20fix(base.py):=20extend=20l?= =?UTF-8?q?ist=20only=20if=20key=20exists=20and=20is=20a=20list=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(nodes.py):=20flatten=20list=20of=20tools=20i?= =?UTF-8?q?f=20it=20is=20a=20list=20of=20lists=20=F0=9F=90=9B=20fix(toolki?= =?UTF-8?q?ts/base.py):=20add=20"toolkit"=20check=20to=20avoid=20adding=20?= =?UTF-8?q?"Tool"=20to=20non-toolkit=20classes=20=F0=9F=93=9D=20docs(agent?= =?UTF-8?q?s.py):=20update=20node=20descriptions=20to=20reflect=20CSV=20an?= =?UTF-8?q?d=20zero=20shot=20agents=20The=20changes=20in=20base.py=20and?= =?UTF-8?q?=20nodes.py=20ensure=20that=20the=20code=20works=20as=20intende?= =?UTF-8?q?d=20and=20avoids=20errors=20when=20extending=20lists.=20The=20c?= =?UTF-8?q?hange=20in=20toolkits/base.py=20ensures=20that=20"Tool"=20is=20?= =?UTF-8?q?only=20added=20to=20classes=20that=20are=20toolkits.=20The=20ch?= =?UTF-8?q?anges=20in=20agents.py=20update=20the=20node=20descriptions=20t?= =?UTF-8?q?o=20reflect=20that=20the=20CSVAgentNode=20constructs=20a=20CSV?= =?UTF-8?q?=20agent=20and=20the=20InitializeAgentNode=20constructs=20a=20z?= =?UTF-8?q?ero=20shot=20agent.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/graph/base.py | 6 ++++++ src/backend/langflow/graph/nodes.py | 5 +++++ src/backend/langflow/interface/toolkits/base.py | 2 +- src/backend/langflow/template/frontend_node/agents.py | 4 ++-- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/backend/langflow/graph/base.py b/src/backend/langflow/graph/base.py index 5b64885fb..08b255441 100644 --- a/src/backend/langflow/graph/base.py +++ b/src/backend/langflow/graph/base.py @@ -175,6 +175,12 @@ class Node: # turn result which is a function into a coroutine # so that it can be awaited self.params["coroutine"] = sync_to_async(result) + if isinstance(result, list): + # If the result is a list, then we need to extend the list + # with the result but first check if the key exists + # if it doesn't, then we need to create a new list + if isinstance(self.params[key], list): + self.params[key].extend(result) self.params[key] = result elif isinstance(value, list) and all( diff --git a/src/backend/langflow/graph/nodes.py b/src/backend/langflow/graph/nodes.py index 7d9b05366..ea94e10b8 100644 --- a/src/backend/langflow/graph/nodes.py +++ b/src/backend/langflow/graph/nodes.py @@ -62,6 +62,11 @@ class PromptNode(Node): if tools is not None else [] ) + # flatten the list of tools if it is a list of lists + # first check if it is a list + if isinstance(tools, list) and isinstance(tools[0], list): + tools = [tool for sublist in tools for tool in sublist] + self.params["tools"] = tools prompt_params = [ key diff --git a/src/backend/langflow/interface/toolkits/base.py b/src/backend/langflow/interface/toolkits/base.py index 9f01b2bb2..be2345c02 100644 --- a/src/backend/langflow/interface/toolkits/base.py +++ b/src/backend/langflow/interface/toolkits/base.py @@ -44,7 +44,7 @@ class ToolkitCreator(LangChainTypeCreator): try: template = build_template_from_class(name, self.type_to_loader_dict) # add Tool to base_classes - if template: + if "toolkit" in name.lower() and template: template["base_classes"].append("Tool") return template except ValueError as exc: diff --git a/src/backend/langflow/template/frontend_node/agents.py b/src/backend/langflow/template/frontend_node/agents.py index e4fe40187..451dd7eca 100644 --- a/src/backend/langflow/template/frontend_node/agents.py +++ b/src/backend/langflow/template/frontend_node/agents.py @@ -146,7 +146,7 @@ class CSVAgentNode(FrontendNode): ), ], ) - description: str = """Construct a json agent from a CSV and tools.""" + description: str = """Construct a CSV agent from a CSV and tools.""" base_classes: list[str] = ["AgentExecutor"] def to_dict(self): @@ -194,7 +194,7 @@ class InitializeAgentNode(FrontendNode): ), ], ) - description: str = """Construct a json agent from an LLM and tools.""" + description: str = """Construct a zero shot agent from an LLM and tools.""" base_classes: list[str] = ["AgentExecutor", "function"] def to_dict(self): From 1a8d5561e9dbbecd636495ca3a75aecdaa2f3623 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 30 May 2023 01:15:48 -0300 Subject: [PATCH 03/59] fix --- src/backend/langflow/graph/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/graph/nodes.py b/src/backend/langflow/graph/nodes.py index ea94e10b8..5f136b3c7 100644 --- a/src/backend/langflow/graph/nodes.py +++ b/src/backend/langflow/graph/nodes.py @@ -64,7 +64,7 @@ class PromptNode(Node): ) # flatten the list of tools if it is a list of lists # first check if it is a list - if isinstance(tools, list) and isinstance(tools[0], list): + if tools and isinstance(tools, list) and isinstance(tools[0], list): tools = [tool for sublist in tools for tool in sublist] self.params["tools"] = tools From 4b6a8595df8e519a24fe1c064baf85c04ebe345b Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 30 May 2023 21:34:23 -0300 Subject: [PATCH 04/59] =?UTF-8?q?=F0=9F=90=9B=20fix(nodes.py):=20change=20?= =?UTF-8?q?type=20hint=20of=20tools=20list=20to=20include=20ToolkitNode=20?= =?UTF-8?q?The=20type=20hint=20of=20the=20tools=20list=20in=20the=20AgentN?= =?UTF-8?q?ode=20class=20has=20been=20updated=20to=20include=20the=20Toolk?= =?UTF-8?q?itNode=20class.=20This=20is=20because=20the=20tools=20list=20ca?= =?UTF-8?q?n=20now=20contain=20instances=20of=20the=20ToolkitNode=20class?= =?UTF-8?q?=20in=20addition=20to=20the=20ToolNode=20class.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/graph/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/graph/nodes.py b/src/backend/langflow/graph/nodes.py index 5f136b3c7..9db6260e9 100644 --- a/src/backend/langflow/graph/nodes.py +++ b/src/backend/langflow/graph/nodes.py @@ -8,7 +8,7 @@ class AgentNode(Node): def __init__(self, data: Dict): super().__init__(data, base_type="agents") - self.tools: List[ToolNode] = [] + self.tools: List[Union[ToolNode, ToolkitNode]] = [] self.chains: List[ChainNode] = [] def _set_tools_and_chains(self) -> None: From 041748b2fb29a1641e889973baddf56941611fe9 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 30 May 2023 21:47:42 -0300 Subject: [PATCH 05/59] =?UTF-8?q?=F0=9F=94=A8=20refactor(nodes.py):=20extr?= =?UTF-8?q?act=20flatten=5Flist=20function=20to=20utils=20module=20and=20u?= =?UTF-8?q?se=20it=20in=20PromptNode.build=20method=20=F0=9F=90=9B=20fix(n?= =?UTF-8?q?odes.py):=20change=20tools=20parameter=20type=20hint=20in=20Pro?= =?UTF-8?q?mptNode.build=20method=20to=20accept=20a=20list=20of=20Union[To?= =?UTF-8?q?olNode,=20ToolkitNode]=20The=20flatten=5Flist=20function=20was?= =?UTF-8?q?=20extracted=20from=20the=20PromptNode.build=20method=20and=20m?= =?UTF-8?q?oved=20to=20the=20utils=20module=20to=20improve=20code=20reusab?= =?UTF-8?q?ility.=20The=20PromptNode.build=20method=20now=20uses=20the=20f?= =?UTF-8?q?latten=5Flist=20function=20to=20flatten=20the=20list=20of=20too?= =?UTF-8?q?ls=20if=20it=20is=20a=20list=20of=20lists.=20The=20tools=20para?= =?UTF-8?q?meter=20type=20hint=20was=20changed=20to=20accept=20a=20list=20?= =?UTF-8?q?of=20Union[ToolNode,=20ToolkitNode]=20to=20improve=20type=20saf?= =?UTF-8?q?ety.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/graph/nodes.py | 62 ++++++++++++++--------------- src/backend/langflow/graph/utils.py | 12 ++++++ 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/backend/langflow/graph/nodes.py b/src/backend/langflow/graph/nodes.py index 9db6260e9..21fe0f673 100644 --- a/src/backend/langflow/graph/nodes.py +++ b/src/backend/langflow/graph/nodes.py @@ -1,7 +1,12 @@ from typing import Any, Dict, List, Optional, Union from langflow.graph.base import Node -from langflow.graph.utils import extract_input_variables_from_prompt +from langflow.graph.utils import extract_input_variables_from_prompt, flatten_list + + +class ToolkitNode(Node): + def __init__(self, data: Dict): + super().__init__(data, base_type="toolkits") class AgentNode(Node): @@ -47,7 +52,7 @@ class PromptNode(Node): def build( self, force: bool = False, - tools: Optional[Union[List[Node], List[ToolNode]]] = None, + tools: Optional[List[Union[ToolNode, ToolkitNode]]] = None, ) -> Any: if not self._built or force: if ( @@ -65,8 +70,7 @@ class PromptNode(Node): # flatten the list of tools if it is a list of lists # first check if it is a list if tools and isinstance(tools, list) and isinstance(tools[0], list): - tools = [tool for sublist in tools for tool in sublist] - + tools = flatten_list(tools) self.params["tools"] = tools prompt_params = [ key @@ -85,30 +89,6 @@ class PromptNode(Node): return self._built_object -class ChainNode(Node): - def __init__(self, data: Dict): - super().__init__(data, base_type="chains") - - def build( - self, - force: bool = False, - tools: Optional[Union[List[Node], List[ToolNode]]] = None, - ) -> Any: - if not self._built or force: - # Check if the chain requires a PromptNode - for key, value in self.params.items(): - if isinstance(value, PromptNode): - # Build the PromptNode, passing the tools if available - self.params[key] = value.build(tools=tools, force=force) - - self._build() - - #! Cannot deepcopy SQLDatabaseChain - if self.node_type in ["SQLDatabaseChain"]: - return self._built_object - return self._built_object - - class LLMNode(Node): built_node_type = None class_built_object = None @@ -130,11 +110,6 @@ class LLMNode(Node): return self._built_object -class ToolkitNode(Node): - def __init__(self, data: Dict): - super().__init__(data, base_type="toolkits") - - class FileToolNode(ToolNode): def __init__(self, data: Dict): super().__init__(data) @@ -193,3 +168,24 @@ class TextSplitterNode(Node): if self._built_object: return f"""{self.node_type}({len(self._built_object)} documents)\nDocuments: {self._built_object[:3]}...""" return f"{self.node_type}()" + + +class ChainNode(Node): + def __init__(self, data: Dict): + super().__init__(data, base_type="chains") + + def build( + self, + force: bool = False, + tools: Optional[List[Union[ToolNode, ToolkitNode]]] = None, + ) -> Any: + if not self._built or force: + # Check if the chain requires a PromptNode + for key, value in self.params.items(): + if isinstance(value, PromptNode): + # Build the PromptNode, passing the tools if available + self.params[key] = value.build(tools=tools, force=force) + + self._build() + + return self._built_object diff --git a/src/backend/langflow/graph/utils.py b/src/backend/langflow/graph/utils.py index 6d56e933e..e22b27cf5 100644 --- a/src/backend/langflow/graph/utils.py +++ b/src/backend/langflow/graph/utils.py @@ -1,4 +1,5 @@ import re +from typing import Any, Union def validate_prompt(prompt: str): @@ -17,3 +18,14 @@ def fix_prompt(prompt: str): def extract_input_variables_from_prompt(prompt: str) -> list[str]: """Extract input variables from prompt.""" return re.findall(r"{(.*?)}", prompt) + + +def flatten_list(list_of_lists: list[Union[list, Any]]) -> list: + """Flatten list of lists.""" + new_list = [] + for item in list_of_lists: + if isinstance(item, list): + new_list.extend(item) + else: + new_list.append(item) + return new_list From 4160274b298a8ade902eccecc36268a80bb849dd Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Tue, 30 May 2023 22:15:01 -0300 Subject: [PATCH 06/59] Adding search nodes to ExtraSideBarComponent --- .../ExtraSidebarComponent/index.tsx | 9 +- .../extraSidebarComponent/index.tsx | 163 +++++++++++------- 2 files changed, 107 insertions(+), 65 deletions(-) diff --git a/src/frontend/src/components/ExtraSidebarComponent/index.tsx b/src/frontend/src/components/ExtraSidebarComponent/index.tsx index f5acd5a23..b06a58d9c 100644 --- a/src/frontend/src/components/ExtraSidebarComponent/index.tsx +++ b/src/frontend/src/components/ExtraSidebarComponent/index.tsx @@ -1,6 +1,6 @@ import { Disclosure } from "@headlessui/react"; import { ChevronLeftIcon } from "@heroicons/react/24/outline"; -import { useContext } from "react"; +import { useContext, useState } from "react"; import { Link } from "react-router-dom"; import { classNames } from "../../utils"; import { locationContext } from "../../contexts/locationContext"; @@ -13,6 +13,7 @@ export default function ExtraSidebar() { extraNavigation, extraComponent, } = useContext(locationContext); + return ( <>