This commit is contained in:
Gabriel Almeida 2023-03-26 10:01:50 -03:00
commit 405041f9c8
6 changed files with 73 additions and 39 deletions

View file

@ -55,7 +55,7 @@ def list_tools():
tool_params = util.get_tool_params(util.get_tools_dict(tool)) tool_params = util.get_tool_params(util.get_tools_dict(tool))
if tool_params and tool_params["name"] in settings.tools or settings.dev: if tool_params and tool_params["name"] in settings.tools or settings.dev:
tools.append(tool_params["name"]) tools.append(tool_params["name"])
tools.append("BaseTool")
return tools return tools
@ -84,3 +84,15 @@ def list_memories():
for memory in memory_type_to_cls_dict.values() for memory in memory_type_to_cls_dict.values()
if memory.__name__ in settings.memories or settings.dev if memory.__name__ in settings.memories or settings.dev
] ]
LANGCHAIN_TYPES_DICT = {
k: list_function() for k, list_function in get_type_dict().items()
}
# Now we'll build a dict with Langchain types and ours
ALL_TYPES_DICT = {
**LANGCHAIN_TYPES_DICT,
"Custom": ["Custom Tool", "Python Function"],
}

View file

@ -25,16 +25,26 @@ from langflow.interface.types import get_type_list
from langflow.utils import payload, util from langflow.utils import payload, util
def instantiate_class(module_type: str, base_type: str, params: Dict) -> Any: def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
"""Instantiate class from module type and key, and params""" """Instantiate class from module type and key, and params"""
class_object = import_by_type(_type=base_type, name=module_type) class_object = import_by_type(_type=base_type, name=node_type)
if base_type == "agents": if base_type == "agents":
# We need to initialize it differently # We need to initialize it differently
allowed_tools = params["allowed_tools"] allowed_tools = params["allowed_tools"]
llm_chain = params["llm_chain"] llm_chain = params["llm_chain"]
return load_agent_executor(class_object, allowed_tools, llm_chain) return load_agent_executor(class_object, allowed_tools, llm_chain)
elif base_type == "tools" or module_type != "ZeroShotPrompt": elif base_type == "tools" or node_type != "ZeroShotPrompt":
return class_object(**params) 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["function"]
if isinstance(function_string, str):
return util.eval_function(function_string)
raise ValueError("Function should be a string")
else: else:
return ZeroShotAgent.create_prompt(**params, tools=[]) return ZeroShotAgent.create_prompt(**params, tools=[])

View file

@ -1,3 +1,5 @@
OPENAI_MODELS = [ OPENAI_MODELS = [
"text-davinci-003", "text-davinci-003",
"text-davinci-002", "text-davinci-002",
@ -6,3 +8,4 @@ OPENAI_MODELS = [
"text-ada-001", "text-ada-001",
] ]
CHAT_OPENAI_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"] CHAT_OPENAI_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"]

View file

@ -1,10 +1,8 @@
import types
from typing import Dict, List, Union from typing import Dict, List, Union
from langflow.interface import listing, loading from langflow.interface import loading
from langflow.utils import payload, util from langflow.utils import payload, util
from langflow.interface.listing import ALL_TYPES_DICT
LANGCHAIN_TYPES_DICT = {
k: list_function() for k, list_function in listing.get_type_dict().items()
}
class Node: class Node:
@ -38,11 +36,11 @@ class Node:
] ]
template_dict = self.data["node"]["template"] template_dict = self.data["node"]["template"]
self.module_type = ( self.node_type = (
self.data["type"] if "Tool" not in self.output else template_dict["_type"] self.data["type"] if "Tool" not in self.output else template_dict["_type"]
) )
def _build_params(self) -> Dict: def _build_params(self):
# Some params are required, some are optional # Some params are required, some are optional
# but most importantly, some params are python base classes # but most importantly, some params are python base classes
# like str and others are LangChain objects like LLMChain, BasePromptTemplate # like str and others are LangChain objects like LLMChain, BasePromptTemplate
@ -68,7 +66,7 @@ class Node:
continue continue
if value["type"] not in ["str", "bool"]: if value["type"] not in ["str", "bool"]:
# Get the edge that connects to this node # Get the edge that connects to this node
edge = next( edge: Edge = next(
( (
edge edge
for edge in self.edges for edge in self.edges
@ -83,21 +81,16 @@ class Node:
# or create a new list if it doesn't # or create a new list if it doesn't
if edge is None and value["required"]: if edge is None and value["required"]:
raise ValueError( raise ValueError(
f"Required input {key} for module {self.module_type} is not connected" f"Required input {key} for module {self.node_type} is not connected"
) )
if value["list"]: if value["list"]:
if key in params: if key in params:
params[key].append(edge.source) params[key].append(edge.source)
else: else:
params[key] = [edge.source] params[key] = [edge.source]
else: elif value["required"] or edge is not None:
if not value["required"] and edge is None:
continue
params[key] = edge.source params[key] = edge.source
else: elif value["required"] or value.get("value"):
if not value["required"] and not value.get("value"):
continue
params[key] = value["value"] params[key] = value["value"]
# Add _type to params # Add _type to params
@ -112,7 +105,7 @@ class Node:
# and use the output of that build as the value for the param # and use the output of that build as the value for the param
# if the value is not a node, then we use the value as the param # if the value is not a node, then we use the value as the param
# and continue # and continue
# Another aspect is that the module_type is the class that we need to import # Another aspect is that the node_type is the class that we need to import
# and instantiate with these built params # and instantiate with these built params
# Build each node in the params dict # Build each node in the params dict
@ -120,7 +113,10 @@ class Node:
# Check if Node or list of Nodes # Check if Node or list of Nodes
if isinstance(value, Node): if isinstance(value, Node):
result = value.build() result = value.build()
self.params[key] = result.run if key == "func" else result # If the key is "func", then we need to use the run method
if key == "func" and not isinstance(result, types.FunctionType):
result = result.run
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
): ):
@ -130,12 +126,13 @@ class Node:
# and instantiate it with the params # and instantiate it with the params
# and return the instance # and return the instance
instance = None instance = None
for base_type, value in LANGCHAIN_TYPES_DICT.items(): for base_type, value in ALL_TYPES_DICT.items():
if base_type == "tools": if base_type == "tools":
value = util.get_tools_dict() value = util.get_tools_dict()
if self.module_type in value:
if self.node_type in value:
instance = loading.instantiate_class( instance = loading.instantiate_class(
module_type=self.module_type, node_type=self.node_type,
base_type=base_type, base_type=base_type,
params=self.params, params=self.params,
) )
@ -260,11 +257,11 @@ class Graph:
def _build_nodes(self) -> List[Node]: def _build_nodes(self) -> List[Node]:
return [Node(node) for node in self._nodes] return [Node(node) for node in self._nodes]
def get_children_by_module_type(self, node: Node, module_type: str) -> List[Node]: def get_children_by_node_type(self, node: Node, node_type: str) -> List[Node]:
children = [] children = []
module_types = [node.data["type"]] node_types = [node.data["type"]]
if "node" in node.data: if "node" in node.data:
module_types += node.data["node"]["base_classes"] node_types += node.data["node"]["base_classes"]
if module_type in module_types: if node_type in node_types:
children.append(node) children.append(node)
return children return children

View file

@ -45,7 +45,7 @@ def build_json(root, graph) -> Dict:
else: else:
# Otherwise, find all children whose type matches the type # Otherwise, find all children whose type matches the type
# specified in the template # specified in the template
module_type = root.module_type node_type = root.node_type
local_nodes = graph.get_nodes_with_target(root) local_nodes = graph.get_nodes_with_target(root)
if len(local_nodes) == 1: if len(local_nodes) == 1:
@ -58,25 +58,23 @@ def build_json(root, graph) -> Dict:
if key == "_type": if key == "_type":
continue continue
module_type = value["type"] node_type = value["type"]
if "value" in value and value["value"] is not None: if "value" in value and value["value"] is not None:
# If the value is specified, use it # If the value is specified, use it
value = value["value"] value = value["value"]
elif "dict" in module_type: elif "dict" in node_type:
# If the value is a dictionary, create an empty dictionary # If the value is a dictionary, create an empty dictionary
value = {} value = {}
else: else:
# Otherwise, recursively build the child nodes # Otherwise, recursively build the child nodes
children = [] children = []
for local_node in local_nodes: for local_node in local_nodes:
node_children = graph.get_children_by_module_type( node_children = graph.get_children_by_node_type(local_node, node_type)
local_node, module_type
)
children.extend(node_children) children.extend(node_children)
if value["required"] and not children: if value["required"] and not children:
raise ValueError(f"No child with type {module_type} found") raise ValueError(f"No child with type {node_type} found")
values = [build_json(child, graph) for child in children] values = [build_json(child, graph) for child in children]
value = list(values) if value["list"] else next(iter(values), None) value = list(values) if value["list"] else next(iter(values), None)
final_dict[key] = value final_dict[key] = value

View file

@ -3,6 +3,7 @@ import importlib
import inspect import inspect
import re import re
from typing import Dict, Optional from typing import Dict, Optional
import types
from langchain.agents.load_tools import ( from langchain.agents.load_tools import (
_BASE_TOOLS, _BASE_TOOLS,
@ -122,6 +123,21 @@ def build_template_from_class(
} }
def eval_function(function_string: str):
# Create an empty dictionary to serve as a separate namespace
namespace: Dict = {}
# 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)),
None,
)
if function_object is None:
raise ValueError("Function string does not contain a function")
return function_object
def get_base_classes(cls): def get_base_classes(cls):
"""Get the base classes of a class. """Get the base classes of a class.
These are used to determine the output of the nodes. These are used to determine the output of the nodes.
@ -165,9 +181,6 @@ class GenericTool(Tool):
super().__init__(name=name, description=description, func=func) super().__init__(name=name, description=description, func=func)
# from langchain.llms.base import BaseLLM
def get_base_tool(name, description, func: callable) -> BaseTool: def get_base_tool(name, description, func: callable) -> BaseTool:
return GenericTool(func=func, name="Generic Tool", description="Bacon") return GenericTool(func=func, name="Generic Tool", description="Bacon")
@ -205,6 +218,7 @@ def get_tool_params(func, **kwargs):
tool_params["description"] = ast.literal_eval( tool_params["description"] = ast.literal_eval(
keyword.value keyword.value
) )
return tool_params return tool_params
return { return {
"name": ast.literal_eval(tool.args[0]), "name": ast.literal_eval(tool.args[0]),