Merge branch '45-implement-agents-as-tools' into 45-implement-agents-as-tools-ui

This commit is contained in:
anovazzi1 2023-03-28 17:08:11 -03:00
commit 357ac7735d
19 changed files with 185 additions and 39 deletions

View file

@ -2,7 +2,7 @@ from typing import Any, Dict
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from langflow.interface.run import process_data_graph from langflow.interface.run import process_graph
from langflow.interface.types import build_langchain_types_dict from langflow.interface.types import build_langchain_types_dict
# build router # build router
@ -17,6 +17,6 @@ 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_data_graph(data) return process_graph(data)
except Exception as e: except Exception as e:
return HTTPException(status_code=500, detail=str(e)) return HTTPException(status_code=500, detail=str(e))

View file

@ -1,4 +1,4 @@
from langflow.node import nodes from langflow.template import nodes
CUSTOM_NODES = { CUSTOM_NODES = {

View file

@ -1,9 +1,14 @@
# Description: Graph class for building a graph of nodes and edges
# Insights:
# - Defer prompts building to the last moment or when they have all the tools
# - Build each inner agent first, then build the outer agent
from copy import deepcopy from copy import deepcopy
import types import types
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Optional, Union
from langflow.utils import payload
from langflow.interface.listing import ALL_TYPES_DICT, ALL_TOOLS_NAMES, TOOLS_DICT
from langflow.interface import loading from langflow.interface import loading
from langflow.utils import payload, util
from langflow.interface.listing import ALL_TYPES_DICT
class Node: class Node:
@ -133,7 +138,7 @@ class Node:
# and return the instance # and return the instance
for base_type, value in ALL_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 = TOOLS_DICT
if self.node_type in value: if self.node_type in value:
self._built_object = loading.instantiate_class( self._built_object = loading.instantiate_class(
@ -166,6 +171,35 @@ class Node:
return id(self) return id(self)
class AgentNode(Node):
def __init__(self, data: Dict):
super().__init__(data)
self.tools: List[ToolNode] = []
self.chains: List[ChainNode] = []
def _set_tools_and_chains(self) -> None:
for edge in self.edges:
source_node = edge.source
if isinstance(source_node, ToolNode):
self.tools.append(source_node)
elif isinstance(source_node, ChainNode):
self.chains.append(source_node)
def build(self, force: bool = False) -> Any:
if not self._built or force:
self._set_tools_and_chains()
# First, build the tools
for tool_node in self.tools:
tool_node.build()
# Next, build the chains and the rest
for chain_node in self.chains:
chain_node.build(tools=self.tools)
self._build()
return deepcopy(self._built_object)
class Edge: class Edge:
def __init__(self, source: "Node", target: "Node"): def __init__(self, source: "Node", target: "Node"):
self.source: "Node" = source self.source: "Node" = source
@ -204,6 +238,51 @@ class Edge:
) )
class PromptNode(Node):
def __init__(self, data: Dict):
super().__init__(data)
def build(self, tools: Optional[List[Node]] = None, force: bool = False) -> Any:
if not self._built or force:
# Check if it is a ZeroShotPrompt and needs a tool
if self.node_type == "ZeroShotPrompt":
tools = (
[tool_node.build() for tool_node in tools]
if tools is not None
else []
)
self.params["tools"] = tools
self._build()
return deepcopy(self._built_object)
class ToolNode(Node):
def __init__(self, data: Dict):
super().__init__(data)
def build(self, force: bool = False) -> Any:
if not self._built or force:
self._build()
return deepcopy(self._built_object)
class ChainNode(Node):
def __init__(self, data: Dict):
super().__init__(data)
def build(self, tools: Optional[List[Node]] = None, force: bool = False) -> 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 or [], force=force)
self._build()
return deepcopy(self._built_object)
class Graph: class Graph:
def __init__( def __init__(
self, self,
@ -270,7 +349,23 @@ class Graph:
return edges return edges
def _build_nodes(self) -> List[Node]: def _build_nodes(self) -> List[Node]:
return [Node(node) for node in self._nodes] nodes = []
for node in self._nodes:
node_data = node["data"]
node_type = node_data["type"]
node_lc_type = node_data["node"]["template"]["_type"]
if node_type in ["ZeroShotPrompt", "PromptTemplate"]:
nodes.append(PromptNode(node))
elif "agent" in node_type.lower():
nodes.append(AgentNode(node))
elif "chain" in node_type.lower():
nodes.append(ChainNode(node))
elif "tool" in node_type.lower() or node_lc_type in ALL_TOOLS_NAMES:
nodes.append(ToolNode(node))
else:
nodes.append(Node(node))
return nodes
def get_children_by_node_type(self, node: Node, node_type: str) -> List[Node]: def get_children_by_node_type(self, node: Node, node_type: str) -> List[Node]:
children = [] children = []

View file

View file

@ -0,0 +1,7 @@
from langflow.interface.importing.utils import import_by_type # noqa: F401
# This module is used to import any langchain class by name.
ALL = [
"import_by_type",
]

View file

@ -1,5 +1,4 @@
from langchain import agents, chains, prompts from langchain import agents, chains, prompts
from langchain.agents.load_tools import get_all_tool_names
from langflow.custom import customs from langflow.custom import customs
from langflow.interface.custom_lists import ( from langflow.interface.custom_lists import (
@ -8,6 +7,14 @@ from langflow.interface.custom_lists import (
) )
from langflow.settings import settings from langflow.settings import settings
from langflow.utils import util from langflow.utils import util
from langchain.agents.load_tools import get_all_tool_names
from langchain.agents import Tool
from langflow.interface.custom_types import PythonFunction
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction}
TOOLS_DICT = util.get_tools_dict()
ALL_TOOLS_NAMES = set(get_all_tool_names() + list(CUSTOM_TOOLS.keys()))
def get_type_dict(): def get_type_dict():
@ -51,9 +58,9 @@ def list_tools():
tools = [] tools = []
for tool in get_all_tool_names(): for tool in ALL_TOOLS_NAMES:
tool_params = util.get_tool_params(util.get_tool_by_name(tool)) tool_params = util.get_tool_params(util.get_tool_by_name(tool))
if tool_params and tool_params["name"] in settings.tools or settings.dev: if tool_params and tool_params.get("name") in settings.tools or settings.dev:
tools.append(tool_params["name"]) tools.append(tool_params["name"])
# Add Tool # Add Tool

View file

@ -9,9 +9,9 @@ from langchain.agents.load_tools import (
_LLM_TOOLS, _LLM_TOOLS,
) )
from langchain.agents import agent as agent_module from langchain.agents import agent as agent_module
from langflow.utils.graph import Graph
from langflow.interface.importing import import_by_type
from langflow.interface.importing.utils import import_by_type
from langchain.agents import ZeroShotAgent from langchain.agents import ZeroShotAgent
from langchain.agents.loading import load_agent_from_config from langchain.agents.loading import load_agent_from_config
@ -61,6 +61,8 @@ def load_flow_from_json(path: str):
def extract_json(data_graph): def extract_json(data_graph):
from langflow.graph.graph import Graph
nodes = data_graph["nodes"] nodes = data_graph["nodes"]
# Substitute ZeroShotPrompt with PromptTemplate # Substitute ZeroShotPrompt with PromptTemplate
nodes = replace_zero_shot_prompt_with_prompt_template(nodes) nodes = replace_zero_shot_prompt_with_prompt_template(nodes)

View file

@ -4,6 +4,8 @@ import re
from typing import Any, Dict from typing import Any, Dict
from langflow.interface import loading from langflow.interface import loading
from langflow.utils import payload
from langflow.graph.graph import Graph
def process_data_graph(data_graph: Dict[str, Any]): def process_data_graph(data_graph: Dict[str, Any]):
@ -27,6 +29,52 @@ def process_data_graph(data_graph: Dict[str, Any]):
} }
def process_graph(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.
"""
nodes = data_graph["nodes"]
# Add input variables
# ? Is this necessary?
nodes = payload.extract_input_variables(nodes)
# Nodes, edges and root node
edges = data_graph["edges"]
graph = Graph(nodes, edges)
langchain_object = graph.build()
message = data_graph["message"]
# Process json
result, thought = get_result_and_thought_using_graph(langchain_object, message)
return {
"result": result,
"thought": re.sub(
r"\x1b\[([0-9,A-Z]{1,2}(;[0-9,A-Z]{1,2})?)?[m|K]", "", thought
).strip(),
}
def get_result_and_thought_using_graph(loaded_langchain, message: str):
"""Get result and thought from extracted json"""
loaded_langchain.verbose = True
try:
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
result = loaded_langchain(message)
result = (
result.get(loaded_langchain.output_keys[0])
if isinstance(result, dict)
else result
)
thought = output_buffer.getvalue()
except Exception as e:
result = f"Error: {str(e)}"
thought = ""
return result, thought
def get_result_and_thought(extracted_json: Dict[str, Any], message: str): def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
"""Get result and thought from extracted json""" """Get result and thought from extracted json"""
try: try:
@ -41,6 +89,7 @@ def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
else result else result
) )
thought = output_buffer.getvalue() thought = output_buffer.getvalue()
except Exception as e: except Exception as e:
result = f"Error: {str(e)}" result = f"Error: {str(e)}"
thought = "" thought = ""

View file

@ -6,7 +6,6 @@ from langchain.agents.load_tools import (
_EXTRA_LLM_TOOLS, _EXTRA_LLM_TOOLS,
_EXTRA_OPTIONAL_TOOLS, _EXTRA_OPTIONAL_TOOLS,
_LLM_TOOLS, _LLM_TOOLS,
get_all_tool_names,
) )
from langflow.custom import customs from langflow.custom import customs
@ -14,9 +13,9 @@ from langflow.interface.custom_lists import (
llm_type_to_cls_dict, llm_type_to_cls_dict,
memory_type_to_cls_dict, memory_type_to_cls_dict,
) )
from langflow.node.template import Field, Template from langflow.interface.listing import CUSTOM_TOOLS, ALL_TOOLS_NAMES
from langflow.template.template import Field, Template
from langflow.utils import util from langflow.utils import util
from langflow.utils.constants import CUSTOM_TOOLS
def get_signature(name: str, object_type: str): def get_signature(name: str, object_type: str):
@ -86,8 +85,7 @@ def get_tool_signature(name: str):
NODE_INPUTS = ["llm", "func"] NODE_INPUTS = ["llm", "func"]
base_classes = ["Tool"] base_classes = ["Tool"]
all_tools = {} all_tools = {}
all_tool_names: list[str] = get_all_tool_names() + list(CUSTOM_TOOLS.keys()) for tool in ALL_TOOLS_NAMES:
for tool in all_tool_names:
if tool_params := util.get_tool_params(util.get_tool_by_name(tool)): if tool_params := util.get_tool_params(util.get_tool_by_name(tool)):
tool_name = tool_params.get("name") or str(tool) tool_name = tool_params.get("name") or str(tool)
all_tools[tool_name] = {"type": tool, "params": tool_params} all_tools[tool_name] = {"type": tool, "params": tool_params}

View file

@ -1,4 +1,4 @@
from langflow.node.template import Field, FrontendNode, Template from langflow.template.template import Field, FrontendNode, Template
from langchain.agents.mrkl import prompt from langchain.agents.mrkl import prompt
from langflow.utils.constants import DEFAULT_PYTHON_FUNCTION from langflow.utils.constants import DEFAULT_PYTHON_FUNCTION
@ -53,12 +53,11 @@ class PythonFunctionNode(FrontendNode):
type_name="python_function", type_name="python_function",
fields=[ fields=[
Field( Field(
field_type="str", field_type="code",
required=True, required=True,
placeholder="", placeholder="",
is_list=False, is_list=False,
show=True, show=True,
multiline=True,
value=DEFAULT_PYTHON_FUNCTION, value=DEFAULT_PYTHON_FUNCTION,
name="code", name="code",
) )

View file

@ -1,6 +1,3 @@
from langchain.agents import Tool
from langflow.interface.custom_types import PythonFunction
OPENAI_MODELS = [ OPENAI_MODELS = [
"text-davinci-003", "text-davinci-003",
"text-davinci-002", "text-davinci-002",
@ -10,7 +7,6 @@ OPENAI_MODELS = [
] ]
CHAT_OPENAI_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"] CHAT_OPENAI_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"]
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction}
DEFAULT_PYTHON_FUNCTION = """ DEFAULT_PYTHON_FUNCTION = """
def python_function(text: str) -> str: def python_function(text: str) -> str:

View file

@ -15,6 +15,7 @@ from langchain.agents.load_tools import (
from langchain.agents.tools import Tool from langchain.agents.tools import Tool
from langflow.utils import constants from langflow.utils import constants
@ -165,12 +166,14 @@ def get_default_factory(module: str, function: str):
def get_tools_dict(): def get_tools_dict():
"""Get the tools dictionary.""" """Get the tools dictionary."""
from langflow.interface.listing import CUSTOM_TOOLS
tools = { tools = {
**_BASE_TOOLS, **_BASE_TOOLS,
**_LLM_TOOLS, **_LLM_TOOLS,
**{k: v[0] for k, v in _EXTRA_LLM_TOOLS.items()}, **{k: v[0] for k, v in _EXTRA_LLM_TOOLS.items()},
**{k: v[0] for k, v in _EXTRA_OPTIONAL_TOOLS.items()}, **{k: v[0] for k, v in _EXTRA_OPTIONAL_TOOLS.items()},
**constants.CUSTOM_TOOLS, **CUSTOM_TOOLS,
} }
return tools return tools

View file

@ -1,14 +1,4 @@
# # build router from langflow.interface.listing import CUSTOM_TOOLS
# router = APIRouter()
# @router.get("/all")
# def get_all():
# return build_langchain_types_dict()
# Buil test for /all endpoint
from langflow.utils.constants import CUSTOM_TOOLS
from fastapi.testclient import TestClient from fastapi.testclient import TestClient

View file

@ -1,5 +1,5 @@
import json import json
from langflow.utils.graph import Edge, Graph, Node from langflow.graph.graph import Edge, Graph, Node
import pytest import pytest
from langflow.utils.payload import build_json, get_root_node from langflow.utils.payload import build_json, get_root_node
from langchain.agents import AgentExecutor from langchain.agents import AgentExecutor

View file

@ -1,6 +1,6 @@
import json import json
from langchain import LLMChain, OpenAI from langchain import LLMChain, OpenAI
from langflow.utils.graph import Graph from langflow.graph.graph import Graph
import pytest import pytest
from langflow import load_flow_from_json from langflow import load_flow_from_json