feat: agents as tools working
This commit is contained in:
parent
09827dc0cb
commit
e465be5941
3 changed files with 179 additions and 4 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))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import re
|
|||
from typing import Any, Dict
|
||||
|
||||
from langflow.interface import loading
|
||||
from langflow.utils import payload
|
||||
from langflow.utils.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 = ""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
# 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 langchain.agents.load_tools import get_all_tool_names
|
||||
|
||||
from copy import deepcopy
|
||||
import types
|
||||
from typing import Any, Dict, List, Union
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from langflow.interface import loading
|
||||
from langflow.utils import payload, util
|
||||
from langflow.interface.listing import ALL_TYPES_DICT
|
||||
|
|
@ -166,6 +172,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 +239,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 +350,24 @@ class Graph:
|
|||
return edges
|
||||
|
||||
def _build_nodes(self) -> List[Node]:
|
||||
return [Node(node) for node in self._nodes]
|
||||
nodes = []
|
||||
all_tool_names = set(get_all_tool_names())
|
||||
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_tool_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 = []
|
||||
|
|
@ -280,3 +377,32 @@ class Graph:
|
|||
if node_type in node_types:
|
||||
children.append(node)
|
||||
return children
|
||||
|
||||
def _build_agent(self, agent_node: Node) -> None:
|
||||
# Identify the ZeroShotPrompt node and any inner ZeroShotAgent nodes
|
||||
zero_shot_prompt_node = None
|
||||
inner_agent_nodes = []
|
||||
for edge in agent_node.edges:
|
||||
if edge.source == agent_node:
|
||||
source_node = edge.target
|
||||
if (
|
||||
isinstance(source_node, DeferredNode)
|
||||
and source_node.node_type == "ZeroShotPrompt"
|
||||
):
|
||||
zero_shot_prompt_node = source_node
|
||||
elif source_node.node_type == "ZeroShotAgent":
|
||||
inner_agent_nodes.append(source_node)
|
||||
|
||||
# First, build any inner ZeroShotAgent nodes
|
||||
for inner_agent_node in inner_agent_nodes:
|
||||
self._build_agent(inner_agent_node)
|
||||
|
||||
# Build the ZeroShotAgent node itself
|
||||
agent_built = agent_node.build()
|
||||
|
||||
if zero_shot_prompt_node:
|
||||
# Set the tools parameter in the ZeroShotPrompt node
|
||||
zero_shot_prompt_node.params["tools"] = agent_built.tools
|
||||
|
||||
# Build the ZeroShotPrompt node
|
||||
zero_shot_prompt_node.build()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue