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 langflow.interface.run import process_data_graph
from langflow.interface.run import process_graph
from langflow.interface.types import build_langchain_types_dict
# build router
@ -17,6 +17,6 @@ def get_all():
@router.post("/predict")
def get_load(data: Dict[str, Any]):
try:
return process_data_graph(data)
return process_graph(data)
except Exception as 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 = {

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
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.utils import payload, util
from langflow.interface.listing import ALL_TYPES_DICT
class Node:
@ -133,7 +138,7 @@ class Node:
# and return the instance
for base_type, value in ALL_TYPES_DICT.items():
if base_type == "tools":
value = util.get_tools_dict()
value = TOOLS_DICT
if self.node_type in value:
self._built_object = loading.instantiate_class(
@ -166,6 +171,35 @@ class Node:
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:
def __init__(self, source: "Node", target: "Node"):
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:
def __init__(
self,
@ -270,7 +349,23 @@ class Graph:
return edges
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]:
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.agents.load_tools import get_all_tool_names
from langflow.custom import customs
from langflow.interface.custom_lists import (
@ -8,6 +7,14 @@ from langflow.interface.custom_lists import (
)
from langflow.settings import settings
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():
@ -51,9 +58,9 @@ def list_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))
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"])
# Add Tool

View file

@ -9,9 +9,9 @@ from langchain.agents.load_tools import (
_LLM_TOOLS,
)
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.loading import load_agent_from_config
@ -61,6 +61,8 @@ def load_flow_from_json(path: str):
def extract_json(data_graph):
from langflow.graph.graph import Graph
nodes = data_graph["nodes"]
# Substitute ZeroShotPrompt with PromptTemplate
nodes = replace_zero_shot_prompt_with_prompt_template(nodes)

View file

@ -4,6 +4,8 @@ import re
from typing import Any, Dict
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]):
@ -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):
"""Get result and thought from extracted json"""
try:
@ -41,6 +89,7 @@ def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
else result
)
thought = output_buffer.getvalue()
except Exception as e:
result = f"Error: {str(e)}"
thought = ""

View file

@ -6,7 +6,6 @@ from langchain.agents.load_tools import (
_EXTRA_LLM_TOOLS,
_EXTRA_OPTIONAL_TOOLS,
_LLM_TOOLS,
get_all_tool_names,
)
from langflow.custom import customs
@ -14,9 +13,9 @@ from langflow.interface.custom_lists import (
llm_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.constants import CUSTOM_TOOLS
def get_signature(name: str, object_type: str):
@ -86,8 +85,7 @@ def get_tool_signature(name: str):
NODE_INPUTS = ["llm", "func"]
base_classes = ["Tool"]
all_tools = {}
all_tool_names: list[str] = get_all_tool_names() + list(CUSTOM_TOOLS.keys())
for tool in all_tool_names:
for tool in ALL_TOOLS_NAMES:
if tool_params := util.get_tool_params(util.get_tool_by_name(tool)):
tool_name = tool_params.get("name") or str(tool)
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 langflow.utils.constants import DEFAULT_PYTHON_FUNCTION
@ -53,12 +53,11 @@ class PythonFunctionNode(FrontendNode):
type_name="python_function",
fields=[
Field(
field_type="str",
field_type="code",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=True,
value=DEFAULT_PYTHON_FUNCTION,
name="code",
)

View file

@ -1,6 +1,3 @@
from langchain.agents import Tool
from langflow.interface.custom_types import PythonFunction
OPENAI_MODELS = [
"text-davinci-003",
"text-davinci-002",
@ -10,7 +7,6 @@ OPENAI_MODELS = [
]
CHAT_OPENAI_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"]
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction}
DEFAULT_PYTHON_FUNCTION = """
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 langflow.utils import constants
@ -165,12 +166,14 @@ def get_default_factory(module: str, function: str):
def get_tools_dict():
"""Get the tools dictionary."""
from langflow.interface.listing import CUSTOM_TOOLS
tools = {
**_BASE_TOOLS,
**_LLM_TOOLS,
**{k: v[0] for k, v in _EXTRA_LLM_TOOLS.items()},
**{k: v[0] for k, v in _EXTRA_OPTIONAL_TOOLS.items()},
**constants.CUSTOM_TOOLS,
**CUSTOM_TOOLS,
}
return tools

View file

@ -1,14 +1,4 @@
# # build router
# 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 langflow.interface.listing import CUSTOM_TOOLS
from fastapi.testclient import TestClient

View file

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

View file

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