Merge branch '45-implement-agents-as-tools' into 45-implement-agents-as-tools-ui
This commit is contained in:
commit
357ac7735d
19 changed files with 185 additions and 39 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from langflow.node import nodes
|
||||
from langflow.template import nodes
|
||||
|
||||
|
||||
CUSTOM_NODES = {
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
0
src/backend/langflow/graph/utils.py
Normal file
0
src/backend/langflow/graph/utils.py
Normal file
7
src/backend/langflow/interface/importing/__init__.py
Normal file
7
src/backend/langflow/interface/importing/__init__.py
Normal 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",
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
0
src/backend/langflow/template/__init__.py
Normal file
0
src/backend/langflow/template/__init__.py
Normal 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",
|
||||
)
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue