Merge branch 'dev' into db
This commit is contained in:
commit
af0d9456b5
43 changed files with 684 additions and 365 deletions
|
|
@ -9,7 +9,7 @@ from langflow.api.base import (
|
|||
PromptValidationResponse,
|
||||
validate_prompt,
|
||||
)
|
||||
from langflow.graph.nodes import VectorStoreNode
|
||||
from langflow.graph.vertex.types import VectorStoreVertex
|
||||
from langflow.interface.run import build_graph
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.validate import validate_code
|
||||
|
|
@ -49,7 +49,7 @@ def post_validate_node(node_id: str, data: dict):
|
|||
node = graph.get_node(node_id)
|
||||
if node is None:
|
||||
raise ValueError(f"Node {node_id} not found")
|
||||
if not isinstance(node, VectorStoreNode):
|
||||
if not isinstance(node, VectorStoreVertex):
|
||||
node.build()
|
||||
return json.dumps({"valid": True, "params": str(node._built_object_repr())})
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ llms:
|
|||
- LlamaCpp
|
||||
- CTransformers
|
||||
- Cohere
|
||||
- Anthropic
|
||||
- ChatAnthropic
|
||||
memories:
|
||||
- ConversationBufferMemory
|
||||
- ConversationSummaryMemory
|
||||
|
|
@ -79,7 +81,7 @@ tools:
|
|||
- Calculator
|
||||
- Serper Search
|
||||
- Tool
|
||||
- PythonFunction
|
||||
- PythonFunctionTool
|
||||
- JsonSpec
|
||||
- News API
|
||||
- TMDB API
|
||||
|
|
@ -118,6 +120,7 @@ vectorstores:
|
|||
- Chroma
|
||||
- Qdrant
|
||||
- Weaviate
|
||||
- FAISS
|
||||
wrappers:
|
||||
- RequestsWrapper
|
||||
# - ChatPromptTemplate
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from langflow.template import frontend_node
|
|||
CUSTOM_NODES = {
|
||||
"prompts": {"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode()},
|
||||
"tools": {
|
||||
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
|
||||
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
|
||||
"Tool": frontend_node.tools.ToolNode(),
|
||||
},
|
||||
"agents": {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,35 @@
|
|||
from langflow.graph.base import Edge, Node
|
||||
from langflow.graph.graph import Graph
|
||||
from langflow.graph.edge.base import Edge
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.graph.vertex.types import (
|
||||
AgentVertex,
|
||||
ChainVertex,
|
||||
DocumentLoaderVertex,
|
||||
EmbeddingVertex,
|
||||
LLMVertex,
|
||||
MemoryVertex,
|
||||
PromptVertex,
|
||||
TextSplitterVertex,
|
||||
ToolVertex,
|
||||
ToolkitVertex,
|
||||
VectorStoreVertex,
|
||||
WrapperVertex,
|
||||
)
|
||||
|
||||
__all__ = ["Graph", "Node", "Edge"]
|
||||
__all__ = [
|
||||
"Graph",
|
||||
"Vertex",
|
||||
"Edge",
|
||||
"AgentVertex",
|
||||
"ChainVertex",
|
||||
"DocumentLoaderVertex",
|
||||
"EmbeddingVertex",
|
||||
"LLMVertex",
|
||||
"MemoryVertex",
|
||||
"PromptVertex",
|
||||
"TextSplitterVertex",
|
||||
"ToolVertex",
|
||||
"ToolkitVertex",
|
||||
"VectorStoreVertex",
|
||||
"WrapperVertex",
|
||||
]
|
||||
|
|
|
|||
0
src/backend/langflow/graph/edge/__init__.py
Normal file
0
src/backend/langflow/graph/edge/__init__.py
Normal file
52
src/backend/langflow/graph/edge/base.py
Normal file
52
src/backend/langflow/graph/edge/base.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from langflow.utils.logger import logger
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
|
||||
|
||||
class Edge:
|
||||
def __init__(self, source: "Vertex", target: "Vertex"):
|
||||
self.source: "Vertex" = source
|
||||
self.target: "Vertex" = target
|
||||
self.validate_edge()
|
||||
|
||||
def validate_edge(self) -> None:
|
||||
# Validate that the outputs of the source node are valid inputs
|
||||
# for the target node
|
||||
self.source_types = self.source.output
|
||||
self.target_reqs = self.target.required_inputs + self.target.optional_inputs
|
||||
# Both lists contain strings and sometimes a string contains the value we are
|
||||
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
||||
# so we need to check if any of the strings in source_types is in target_reqs
|
||||
self.valid = any(
|
||||
output in target_req
|
||||
for output in self.source_types
|
||||
for target_req in self.target_reqs
|
||||
)
|
||||
# Get what type of input the target node is expecting
|
||||
|
||||
self.matched_type = next(
|
||||
(
|
||||
output
|
||||
for output in self.source_types
|
||||
for target_req in self.target_reqs
|
||||
if output in target_req
|
||||
),
|
||||
None,
|
||||
)
|
||||
no_matched_type = self.matched_type is None
|
||||
if no_matched_type:
|
||||
logger.debug(self.source_types)
|
||||
logger.debug(self.target_reqs)
|
||||
if no_matched_type:
|
||||
raise ValueError(
|
||||
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} "
|
||||
f"has no matched type"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Edge(source={self.source.id}, target={self.target.id}, valid={self.valid}"
|
||||
f", matched_type={self.matched_type})"
|
||||
)
|
||||
0
src/backend/langflow/graph/graph/__init__.py
Normal file
0
src/backend/langflow/graph/graph/__init__.py
Normal file
|
|
@ -1,38 +1,20 @@
|
|||
from typing import Dict, List, Type, Union
|
||||
|
||||
from langflow.graph.base import Edge, Node
|
||||
from langflow.graph.nodes import (
|
||||
AgentNode,
|
||||
ChainNode,
|
||||
DocumentLoaderNode,
|
||||
EmbeddingNode,
|
||||
FileToolNode,
|
||||
LLMNode,
|
||||
MemoryNode,
|
||||
PromptNode,
|
||||
TextSplitterNode,
|
||||
ToolkitNode,
|
||||
ToolNode,
|
||||
VectorStoreNode,
|
||||
WrapperNode,
|
||||
from langflow.graph.edge.base import Edge
|
||||
from langflow.graph.graph.constants import VERTEX_TYPE_MAP
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.graph.vertex.types import (
|
||||
FileToolVertex,
|
||||
LLMVertex,
|
||||
ToolkitVertex,
|
||||
)
|
||||
from langflow.interface.agents.base import agent_creator
|
||||
from langflow.interface.chains.base import chain_creator
|
||||
from langflow.interface.document_loaders.base import documentloader_creator
|
||||
from langflow.interface.embeddings.base import embedding_creator
|
||||
from langflow.interface.llms.base import llm_creator
|
||||
from langflow.interface.memories.base import memory_creator
|
||||
from langflow.interface.prompts.base import prompt_creator
|
||||
from langflow.interface.text_splitters.base import textsplitter_creator
|
||||
from langflow.interface.toolkits.base import toolkits_creator
|
||||
from langflow.interface.tools.base import tool_creator
|
||||
from langflow.interface.tools.constants import FILE_TOOLS
|
||||
from langflow.interface.vector_store.base import vectorstore_creator
|
||||
from langflow.interface.wrappers.base import wrapper_creator
|
||||
from langflow.utils import payload
|
||||
|
||||
|
||||
class Graph:
|
||||
"""A class representing a graph of nodes and edges."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
nodes: List[Dict[str, Union[str, Dict[str, Union[str, List[str]]]]]],
|
||||
|
|
@ -43,7 +25,8 @@ class Graph:
|
|||
self._build_graph()
|
||||
|
||||
def _build_graph(self) -> None:
|
||||
self.nodes = self._build_nodes()
|
||||
"""Builds the graph from the nodes and edges."""
|
||||
self.nodes = self._build_vertices()
|
||||
self.edges = self._build_edges()
|
||||
for edge in self.edges:
|
||||
edge.source.add_edge(edge)
|
||||
|
|
@ -51,17 +34,25 @@ class Graph:
|
|||
|
||||
# This is a hack to make sure that the LLM node is sent to
|
||||
# the toolkit node
|
||||
self._build_node_params()
|
||||
# remove invalid nodes
|
||||
self._remove_invalid_nodes()
|
||||
|
||||
def _build_node_params(self) -> None:
|
||||
"""Identifies and handles the LLM node within the graph."""
|
||||
llm_node = None
|
||||
for node in self.nodes:
|
||||
node._build_params()
|
||||
|
||||
if isinstance(node, LLMNode):
|
||||
if isinstance(node, LLMVertex):
|
||||
llm_node = node
|
||||
|
||||
for node in self.nodes:
|
||||
if isinstance(node, ToolkitNode):
|
||||
node.params["llm"] = llm_node
|
||||
# remove invalid nodes
|
||||
if llm_node:
|
||||
for node in self.nodes:
|
||||
if isinstance(node, ToolkitVertex):
|
||||
node.params["llm"] = llm_node
|
||||
|
||||
def _remove_invalid_nodes(self) -> None:
|
||||
"""Removes invalid nodes from the graph."""
|
||||
self.nodes = [
|
||||
node
|
||||
for node in self.nodes
|
||||
|
|
@ -69,28 +60,33 @@ class Graph:
|
|||
or (len(self.nodes) == 1 and len(self.edges) == 0)
|
||||
]
|
||||
|
||||
def _validate_node(self, node: Node) -> bool:
|
||||
def _validate_node(self, node: Vertex) -> bool:
|
||||
"""Validates a node."""
|
||||
# All nodes that do not have edges are invalid
|
||||
return len(node.edges) > 0
|
||||
|
||||
def get_node(self, node_id: str) -> Union[None, Node]:
|
||||
def get_node(self, node_id: str) -> Union[None, Vertex]:
|
||||
"""Returns a node by id."""
|
||||
return next((node for node in self.nodes if node.id == node_id), None)
|
||||
|
||||
def get_nodes_with_target(self, node: Node) -> List[Node]:
|
||||
connected_nodes: List[Node] = [
|
||||
def get_nodes_with_target(self, node: Vertex) -> List[Vertex]:
|
||||
"""Returns the nodes connected to a node."""
|
||||
connected_nodes: List[Vertex] = [
|
||||
edge.source for edge in self.edges if edge.target == node
|
||||
]
|
||||
return connected_nodes
|
||||
|
||||
def build(self) -> List[Node]:
|
||||
def build(self) -> List[Vertex]:
|
||||
"""Builds the graph."""
|
||||
# Get root node
|
||||
root_node = payload.get_root_node(self)
|
||||
if root_node is None:
|
||||
raise ValueError("No root node found")
|
||||
return root_node.build()
|
||||
|
||||
def get_node_neighbors(self, node: Node) -> Dict[Node, int]:
|
||||
neighbors: Dict[Node, int] = {}
|
||||
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
||||
"""Returns the neighbors of a node."""
|
||||
neighbors: Dict[Vertex, int] = {}
|
||||
for edge in self.edges:
|
||||
if edge.source == node:
|
||||
neighbor = edge.target
|
||||
|
|
@ -105,6 +101,7 @@ class Graph:
|
|||
return neighbors
|
||||
|
||||
def _build_edges(self) -> List[Edge]:
|
||||
"""Builds the edges of the graph."""
|
||||
# Edge takes two nodes as arguments, so we need to build the nodes first
|
||||
# and then build the edges
|
||||
# if we can't find a node, we raise an error
|
||||
|
|
@ -120,43 +117,31 @@ class Graph:
|
|||
edges.append(Edge(source, target))
|
||||
return edges
|
||||
|
||||
def _get_node_class(self, node_type: str, node_lc_type: str) -> Type[Node]:
|
||||
node_type_map: Dict[str, Type[Node]] = {
|
||||
**{t: PromptNode for t in prompt_creator.to_list()},
|
||||
**{t: AgentNode for t in agent_creator.to_list()},
|
||||
**{t: ChainNode for t in chain_creator.to_list()},
|
||||
**{t: ToolNode for t in tool_creator.to_list()},
|
||||
**{t: ToolkitNode for t in toolkits_creator.to_list()},
|
||||
**{t: WrapperNode for t in wrapper_creator.to_list()},
|
||||
**{t: LLMNode for t in llm_creator.to_list()},
|
||||
**{t: MemoryNode for t in memory_creator.to_list()},
|
||||
**{t: EmbeddingNode for t in embedding_creator.to_list()},
|
||||
**{t: VectorStoreNode for t in vectorstore_creator.to_list()},
|
||||
**{t: DocumentLoaderNode for t in documentloader_creator.to_list()},
|
||||
**{t: TextSplitterNode for t in textsplitter_creator.to_list()},
|
||||
}
|
||||
|
||||
def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]:
|
||||
"""Returns the node class based on the node type."""
|
||||
if node_type in FILE_TOOLS:
|
||||
return FileToolNode
|
||||
if node_type in node_type_map:
|
||||
return node_type_map[node_type]
|
||||
if node_lc_type in node_type_map:
|
||||
return node_type_map[node_lc_type]
|
||||
return Node
|
||||
return FileToolVertex
|
||||
if node_type in VERTEX_TYPE_MAP:
|
||||
return VERTEX_TYPE_MAP[node_type]
|
||||
return (
|
||||
VERTEX_TYPE_MAP[node_lc_type] if node_lc_type in VERTEX_TYPE_MAP else Vertex
|
||||
)
|
||||
|
||||
def _build_nodes(self) -> List[Node]:
|
||||
nodes: List[Node] = []
|
||||
def _build_vertices(self) -> List[Vertex]:
|
||||
"""Builds the vertices of the graph."""
|
||||
nodes: List[Vertex] = []
|
||||
for node in self._nodes:
|
||||
node_data = node["data"]
|
||||
node_type: str = node_data["type"] # type: ignore
|
||||
node_lc_type: str = node_data["node"]["template"]["_type"] # type: ignore
|
||||
|
||||
NodeClass = self._get_node_class(node_type, node_lc_type)
|
||||
nodes.append(NodeClass(node))
|
||||
VertexClass = self._get_vertex_class(node_type, node_lc_type)
|
||||
nodes.append(VertexClass(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: Vertex, node_type: str) -> List[Vertex]:
|
||||
"""Returns the children of a node based on the node type."""
|
||||
children = []
|
||||
node_types = [node.data["type"]]
|
||||
if "node" in node.data:
|
||||
49
src/backend/langflow/graph/graph/constants.py
Normal file
49
src/backend/langflow/graph/graph/constants.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.graph.vertex.types import (
|
||||
AgentVertex,
|
||||
ChainVertex,
|
||||
DocumentLoaderVertex,
|
||||
EmbeddingVertex,
|
||||
LLMVertex,
|
||||
MemoryVertex,
|
||||
PromptVertex,
|
||||
TextSplitterVertex,
|
||||
ToolVertex,
|
||||
ToolkitVertex,
|
||||
VectorStoreVertex,
|
||||
WrapperVertex,
|
||||
)
|
||||
from langflow.interface.agents.base import agent_creator
|
||||
from langflow.interface.chains.base import chain_creator
|
||||
from langflow.interface.document_loaders.base import documentloader_creator
|
||||
from langflow.interface.embeddings.base import embedding_creator
|
||||
from langflow.interface.llms.base import llm_creator
|
||||
from langflow.interface.memories.base import memory_creator
|
||||
from langflow.interface.prompts.base import prompt_creator
|
||||
from langflow.interface.text_splitters.base import textsplitter_creator
|
||||
from langflow.interface.toolkits.base import toolkits_creator
|
||||
from langflow.interface.tools.base import tool_creator
|
||||
from langflow.interface.vector_store.base import vectorstore_creator
|
||||
from langflow.interface.wrappers.base import wrapper_creator
|
||||
|
||||
|
||||
from typing import Dict, Type
|
||||
|
||||
|
||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
||||
|
||||
|
||||
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
||||
**{t: PromptVertex for t in prompt_creator.to_list()},
|
||||
**{t: AgentVertex for t in agent_creator.to_list()},
|
||||
**{t: ChainVertex for t in chain_creator.to_list()},
|
||||
**{t: ToolVertex for t in tool_creator.to_list()},
|
||||
**{t: ToolkitVertex for t in toolkits_creator.to_list()},
|
||||
**{t: WrapperVertex for t in wrapper_creator.to_list()},
|
||||
**{t: LLMVertex for t in llm_creator.to_list()},
|
||||
**{t: MemoryVertex for t in memory_creator.to_list()},
|
||||
**{t: EmbeddingVertex for t in embedding_creator.to_list()},
|
||||
**{t: VectorStoreVertex for t in vectorstore_creator.to_list()},
|
||||
**{t: DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||
**{t: TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||
}
|
||||
0
src/backend/langflow/graph/vertex/__init__.py
Normal file
0
src/backend/langflow/graph/vertex/__init__.py
Normal file
|
|
@ -1,27 +1,27 @@
|
|||
# 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
|
||||
|
||||
import contextlib
|
||||
import inspect
|
||||
import types
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from langflow.cache import base as cache_utils
|
||||
from langflow.graph.constants import DIRECT_TYPES
|
||||
from langflow.graph.vertex.constants import DIRECT_TYPES
|
||||
from langflow.interface import loading
|
||||
from langflow.interface.listing import ALL_TYPES_DICT
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.util import sync_to_async
|
||||
|
||||
|
||||
class Node:
|
||||
import contextlib
|
||||
import inspect
|
||||
import types
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.edge.base import Edge
|
||||
|
||||
|
||||
class Vertex:
|
||||
def __init__(self, data: Dict, base_type: Optional[str] = None) -> None:
|
||||
self.id: str = data["id"]
|
||||
self._data = data
|
||||
self.edges: List[Edge] = []
|
||||
self.edges: List["Edge"] = []
|
||||
self.base_type: Optional[str] = base_type
|
||||
self._parse_data()
|
||||
self._built_object = None
|
||||
|
|
@ -48,12 +48,12 @@ class Node:
|
|||
]
|
||||
|
||||
template_dict = self.data["node"]["template"]
|
||||
self.node_type = (
|
||||
self.vertex_type = (
|
||||
self.data["type"] if "Tool" not in self.output else template_dict["_type"]
|
||||
)
|
||||
if self.base_type is None:
|
||||
for base_type, value in ALL_TYPES_DICT.items():
|
||||
if self.node_type in value:
|
||||
if self.vertex_type in value:
|
||||
self.base_type = base_type
|
||||
break
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ class Node:
|
|||
if value["required"] and not edges:
|
||||
# If a required parameter is not found, raise an error
|
||||
raise ValueError(
|
||||
f"Required input {key} for module {self.node_type} not found"
|
||||
f"Required input {key} for module {self.vertex_type} not found"
|
||||
)
|
||||
elif value["list"]:
|
||||
# If this is a list parameter, append all sources to a list
|
||||
|
|
@ -128,7 +128,7 @@ class Node:
|
|||
# so we need to check if value has value
|
||||
new_value = value.get("value")
|
||||
if new_value is None:
|
||||
warnings.warn(f"Value for {key} in {self.node_type} is None. ")
|
||||
warnings.warn(f"Value for {key} in {self.vertex_type} is None. ")
|
||||
if value.get("type") == "int":
|
||||
with contextlib.suppress(TypeError, ValueError):
|
||||
new_value = int(new_value) # type: ignore
|
||||
|
|
@ -148,12 +148,12 @@ class Node:
|
|||
# and continue
|
||||
# Another aspect is that the node_type is the class that we need to import
|
||||
# and instantiate with these built params
|
||||
logger.debug(f"Building {self.node_type}")
|
||||
logger.debug(f"Building {self.vertex_type}")
|
||||
# Build each node in the params dict
|
||||
for key, value in self.params.copy().items():
|
||||
# Check if Node or list of Nodes and not self
|
||||
# to avoid recursion
|
||||
if isinstance(value, Node):
|
||||
if isinstance(value, Vertex):
|
||||
if value == self:
|
||||
del self.params[key]
|
||||
continue
|
||||
|
|
@ -177,7 +177,7 @@ class Node:
|
|||
|
||||
self.params[key] = result
|
||||
elif isinstance(value, list) and all(
|
||||
isinstance(node, Node) for node in value
|
||||
isinstance(node, Vertex) for node in value
|
||||
):
|
||||
self.params[key] = []
|
||||
for node in value:
|
||||
|
|
@ -193,17 +193,17 @@ class Node:
|
|||
|
||||
try:
|
||||
self._built_object = loading.instantiate_class(
|
||||
node_type=self.node_type,
|
||||
node_type=self.vertex_type,
|
||||
base_type=self.base_type,
|
||||
params=self.params,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise ValueError(
|
||||
f"Error building node {self.node_type}: {str(exc)}"
|
||||
f"Error building node {self.vertex_type}: {str(exc)}"
|
||||
) from exc
|
||||
|
||||
if self._built_object is None:
|
||||
raise ValueError(f"Node type {self.node_type} not found")
|
||||
raise ValueError(f"Node type {self.vertex_type} not found")
|
||||
|
||||
self._built = True
|
||||
|
||||
|
|
@ -220,57 +220,10 @@ class Node:
|
|||
return f"Node(id={self.id}, data={self.data})"
|
||||
|
||||
def __eq__(self, __o: object) -> bool:
|
||||
return self.id == __o.id if isinstance(__o, Node) else False
|
||||
return self.id == __o.id if isinstance(__o, Vertex) else False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return id(self)
|
||||
|
||||
def _built_object_repr(self):
|
||||
return repr(self._built_object)
|
||||
|
||||
|
||||
class Edge:
|
||||
def __init__(self, source: "Node", target: "Node"):
|
||||
self.source: "Node" = source
|
||||
self.target: "Node" = target
|
||||
self.validate_edge()
|
||||
|
||||
def validate_edge(self) -> None:
|
||||
# Validate that the outputs of the source node are valid inputs
|
||||
# for the target node
|
||||
self.source_types = self.source.output
|
||||
self.target_reqs = self.target.required_inputs + self.target.optional_inputs
|
||||
# Both lists contain strings and sometimes a string contains the value we are
|
||||
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
||||
# so we need to check if any of the strings in source_types is in target_reqs
|
||||
self.valid = any(
|
||||
output in target_req
|
||||
for output in self.source_types
|
||||
for target_req in self.target_reqs
|
||||
)
|
||||
# Get what type of input the target node is expecting
|
||||
|
||||
self.matched_type = next(
|
||||
(
|
||||
output
|
||||
for output in self.source_types
|
||||
for target_req in self.target_reqs
|
||||
if output in target_req
|
||||
),
|
||||
None,
|
||||
)
|
||||
no_matched_type = self.matched_type is None
|
||||
if no_matched_type:
|
||||
logger.debug(self.source_types)
|
||||
logger.debug(self.target_reqs)
|
||||
if no_matched_type:
|
||||
raise ValueError(
|
||||
f"Edge between {self.source.node_type} and {self.target.node_type} "
|
||||
f"has no matched type"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Edge(source={self.source.id}, target={self.target.id}, valid={self.valid}"
|
||||
f", matched_type={self.matched_type})"
|
||||
)
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from langflow.graph.base import Node
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.graph.utils import extract_input_variables_from_prompt
|
||||
|
||||
|
||||
class AgentNode(Node):
|
||||
class AgentVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="agents")
|
||||
|
||||
self.tools: List[ToolNode] = []
|
||||
self.chains: List[ChainNode] = []
|
||||
self.tools: List[ToolVertex] = []
|
||||
self.chains: List[ChainVertex] = []
|
||||
|
||||
def _set_tools_and_chains(self) -> None:
|
||||
for edge in self.edges:
|
||||
source_node = edge.source
|
||||
if isinstance(source_node, ToolNode):
|
||||
if isinstance(source_node, ToolVertex):
|
||||
self.tools.append(source_node)
|
||||
elif isinstance(source_node, ChainNode):
|
||||
elif isinstance(source_node, ChainVertex):
|
||||
self.chains.append(source_node)
|
||||
|
||||
def build(self, force: bool = False) -> Any:
|
||||
|
|
@ -33,24 +33,28 @@ class AgentNode(Node):
|
|||
self._build()
|
||||
|
||||
#! Cannot deepcopy VectorStore, VectorStoreRouter, or SQL agents
|
||||
if self.node_type in ["VectorStoreAgent", "VectorStoreRouterAgent", "SQLAgent"]:
|
||||
if self.vertex_type in [
|
||||
"VectorStoreAgent",
|
||||
"VectorStoreRouterAgent",
|
||||
"SQLAgent",
|
||||
]:
|
||||
return self._built_object
|
||||
return self._built_object
|
||||
|
||||
|
||||
class ToolNode(Node):
|
||||
class ToolVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="tools")
|
||||
|
||||
|
||||
class PromptNode(Node):
|
||||
class PromptVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="prompts")
|
||||
|
||||
def build(
|
||||
self,
|
||||
force: bool = False,
|
||||
tools: Optional[Union[List[Node], List[ToolNode]]] = None,
|
||||
tools: Optional[Union[List[Vertex], List[ToolVertex]]] = None,
|
||||
) -> Any:
|
||||
if not self._built or force:
|
||||
if (
|
||||
|
|
@ -59,7 +63,7 @@ class PromptNode(Node):
|
|||
):
|
||||
self.params["input_variables"] = []
|
||||
# Check if it is a ZeroShotPrompt and needs a tool
|
||||
if "ShotPrompt" in self.node_type:
|
||||
if "ShotPrompt" in self.vertex_type:
|
||||
tools = (
|
||||
[tool_node.build() for tool_node in tools]
|
||||
if tools is not None
|
||||
|
|
@ -83,31 +87,31 @@ class PromptNode(Node):
|
|||
return self._built_object
|
||||
|
||||
|
||||
class ChainNode(Node):
|
||||
class ChainVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="chains")
|
||||
|
||||
def build(
|
||||
self,
|
||||
force: bool = False,
|
||||
tools: Optional[Union[List[Node], List[ToolNode]]] = None,
|
||||
tools: Optional[Union[List[Vertex], List[ToolVertex]]] = None,
|
||||
) -> 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):
|
||||
if isinstance(value, PromptVertex):
|
||||
# Build the PromptNode, passing the tools if available
|
||||
self.params[key] = value.build(tools=tools, force=force)
|
||||
|
||||
self._build()
|
||||
|
||||
#! Cannot deepcopy SQLDatabaseChain
|
||||
if self.node_type in ["SQLDatabaseChain"]:
|
||||
if self.vertex_type in ["SQLDatabaseChain"]:
|
||||
return self._built_object
|
||||
return self._built_object
|
||||
|
||||
|
||||
class LLMNode(Node):
|
||||
class LLMVertex(Vertex):
|
||||
built_node_type = None
|
||||
class_built_object = None
|
||||
|
||||
|
|
@ -117,28 +121,28 @@ class LLMNode(Node):
|
|||
def build(self, force: bool = False) -> Any:
|
||||
# LLM is different because some models might take up too much memory
|
||||
# or time to load. So we only load them when we need them.ß
|
||||
if self.node_type == self.built_node_type:
|
||||
if self.vertex_type == self.built_node_type:
|
||||
return self.class_built_object
|
||||
if not self._built or force:
|
||||
self._build()
|
||||
self.built_node_type = self.node_type
|
||||
self.built_node_type = self.vertex_type
|
||||
self.class_built_object = self._built_object
|
||||
# Avoid deepcopying the LLM
|
||||
# that are loaded from a file
|
||||
return self._built_object
|
||||
|
||||
|
||||
class ToolkitNode(Node):
|
||||
class ToolkitVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="toolkits")
|
||||
|
||||
|
||||
class FileToolNode(ToolNode):
|
||||
class FileToolVertex(ToolVertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data)
|
||||
|
||||
|
||||
class WrapperNode(Node):
|
||||
class WrapperVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="wrappers")
|
||||
|
||||
|
|
@ -150,7 +154,7 @@ class WrapperNode(Node):
|
|||
return self._built_object
|
||||
|
||||
|
||||
class DocumentLoaderNode(Node):
|
||||
class DocumentLoaderVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="documentloaders")
|
||||
|
||||
|
|
@ -158,17 +162,17 @@ class DocumentLoaderNode(Node):
|
|||
# This built_object is a list of documents. Maybe we should
|
||||
# show how many documents are in the list?
|
||||
if self._built_object:
|
||||
return f"""{self.node_type}({len(self._built_object)} documents)
|
||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||
Documents: {self._built_object[:3]}..."""
|
||||
return f"{self.node_type}()"
|
||||
return f"{self.vertex_type}()"
|
||||
|
||||
|
||||
class EmbeddingNode(Node):
|
||||
class EmbeddingVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="embeddings")
|
||||
|
||||
|
||||
class VectorStoreNode(Node):
|
||||
class VectorStoreVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="vectorstores")
|
||||
|
||||
|
|
@ -176,12 +180,12 @@ class VectorStoreNode(Node):
|
|||
return "Vector stores can take time to build. It will build on the first query."
|
||||
|
||||
|
||||
class MemoryNode(Node):
|
||||
class MemoryVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="memory")
|
||||
|
||||
|
||||
class TextSplitterNode(Node):
|
||||
class TextSplitterVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="textsplitters")
|
||||
|
||||
|
|
@ -189,5 +193,6 @@ class TextSplitterNode(Node):
|
|||
# This built_object is a list of documents. Maybe we should
|
||||
# show how many documents are in the list?
|
||||
if self._built_object:
|
||||
return f"""{self.node_type}({len(self._built_object)} documents)\nDocuments: {self._built_object[:3]}..."""
|
||||
return f"{self.node_type}()"
|
||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||
\nDocuments: {self._built_object[:3]}..."""
|
||||
return f"{self.vertex_type}()"
|
||||
|
|
@ -11,12 +11,14 @@ from langchain import (
|
|||
text_splitter,
|
||||
)
|
||||
from langchain.agents import agent_toolkits
|
||||
from langchain.chat_models import ChatAnthropic
|
||||
from langchain.chat_models import ChatOpenAI
|
||||
|
||||
from langflow.interface.importing.utils import import_class
|
||||
|
||||
## LLMs
|
||||
llm_type_to_cls_dict = llms.type_to_cls_dict
|
||||
llm_type_to_cls_dict["anthropic-chat"] = ChatAnthropic # type: ignore
|
||||
llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore
|
||||
|
||||
## Chains
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from langchain.base_language import BaseLanguageModel
|
|||
from langchain.chains.base import Chain
|
||||
from langchain.chat_models.base import BaseChatModel
|
||||
from langchain.tools import BaseTool
|
||||
from langflow.utils import validate
|
||||
|
||||
|
||||
def import_module(module_path: str) -> Any:
|
||||
|
|
@ -147,3 +148,10 @@ def import_utility(utility: str) -> Any:
|
|||
if utility == "SQLDatabase":
|
||||
return import_class(f"langchain.sql_database.{utility}")
|
||||
return import_class(f"langchain.utilities.{utility}")
|
||||
|
||||
|
||||
def get_function(code):
|
||||
"""Get the function"""
|
||||
function_name = validate.extract_function_name(code)
|
||||
|
||||
return validate.create_function(code, function_name)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from langchain.agents.load_tools import (
|
|||
_LLM_TOOLS,
|
||||
)
|
||||
from langchain.agents.loading import load_agent_from_config
|
||||
from langflow.graph import Graph
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.callbacks.base import BaseCallbackManager
|
||||
|
|
@ -20,12 +21,12 @@ from langchain.llms.loading import load_llm_from_config
|
|||
from pydantic import ValidationError
|
||||
|
||||
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
||||
from langflow.interface.importing.utils import import_by_type
|
||||
from langflow.interface.importing.utils import get_function, import_by_type
|
||||
from langflow.interface.run import fix_memory_inputs
|
||||
from langflow.interface.toolkits.base import toolkits_creator
|
||||
from langflow.interface.types import get_type_list
|
||||
from langflow.interface.utils import load_file_into_dict
|
||||
from langflow.utils import util, validate
|
||||
from langflow.utils import util
|
||||
|
||||
|
||||
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||
|
|
@ -99,11 +100,9 @@ def instantiate_tool(node_type, class_object, params):
|
|||
if node_type == "JsonSpec":
|
||||
params["dict_"] = load_file_into_dict(params.pop("path"))
|
||||
return class_object(**params)
|
||||
elif node_type == "PythonFunction":
|
||||
function_string = params["code"]
|
||||
if isinstance(function_string, str):
|
||||
return validate.eval_function(function_string)
|
||||
raise ValueError("Function should be a string")
|
||||
elif node_type == "PythonFunctionTool":
|
||||
params["func"] = get_function(params.get("code"))
|
||||
return class_object(**params)
|
||||
elif node_type.lower() == "tool":
|
||||
return class_object(**params)
|
||||
return class_object(**params)
|
||||
|
|
@ -164,7 +163,6 @@ def instantiate_utility(node_type, class_object, params):
|
|||
def load_flow_from_json(path: str, build=True):
|
||||
"""Load flow from json file"""
|
||||
# This is done to avoid circular imports
|
||||
from langflow.graph import Graph
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
flow_graph = json.load(f)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from langchain.schema import AgentAction
|
|||
|
||||
from langflow.api.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler # type: ignore
|
||||
from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict
|
||||
from langflow.graph.graph import Graph
|
||||
from langflow.graph import Graph
|
||||
from langflow.utils.logger import logger
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ class ToolCreator(LangChainTypeCreator):
|
|||
|
||||
for tool, tool_fcn in ALL_TOOLS_NAMES.items():
|
||||
tool_params = get_tool_params(tool_fcn)
|
||||
tool_name = tool_params.get("name", tool)
|
||||
|
||||
tool_name = tool_params.get("name") or tool
|
||||
|
||||
if tool_name in settings.tools or settings.dev:
|
||||
if tool_name == "JsonSpec":
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ from langchain.agents.load_tools import (
|
|||
from langchain.tools.json.tool import JsonSpec
|
||||
|
||||
from langflow.interface.importing.utils import import_class
|
||||
from langflow.interface.tools.custom import PythonFunction
|
||||
from langflow.interface.tools.custom import PythonFunctionTool
|
||||
|
||||
FILE_TOOLS = {"JsonSpec": JsonSpec}
|
||||
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction}
|
||||
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunctionTool": PythonFunctionTool}
|
||||
|
||||
OTHER_TOOLS = {tool: import_class(f"langchain.tools.{tool}") for tool in tools.__all__}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
from typing import Callable, Optional
|
||||
from typing import Optional
|
||||
from langflow.interface.importing.utils import get_function
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
from langflow.utils import validate
|
||||
from langchain.agents.tools import Tool
|
||||
|
||||
|
||||
class Function(BaseModel):
|
||||
code: str
|
||||
function: Optional[Callable] = None
|
||||
imports: Optional[str] = None
|
||||
|
||||
# Eval code and store the function
|
||||
|
|
@ -24,14 +25,17 @@ class Function(BaseModel):
|
|||
|
||||
return v
|
||||
|
||||
def get_function(self):
|
||||
"""Get the function"""
|
||||
function_name = validate.extract_function_name(self.code)
|
||||
|
||||
return validate.create_function(self.code, function_name)
|
||||
|
||||
|
||||
class PythonFunction(Function):
|
||||
class PythonFunctionTool(Function, Tool):
|
||||
"""Python function"""
|
||||
|
||||
name: str = "Custom Tool"
|
||||
description: str
|
||||
code: str
|
||||
|
||||
def ___init__(self, name: str, description: str, code: str):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.code = code
|
||||
self.func = get_function(self.code)
|
||||
super().__init__(name=name, description=description, func=self.func)
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ class FrontendNode(BaseModel):
|
|||
elif name == "ChatOpenAI" and key == "model_name":
|
||||
field.options = constants.CHAT_OPENAI_MODELS
|
||||
field.is_list = True
|
||||
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
||||
field.options = constants.ANTHROPIC_MODELS
|
||||
field.is_list = True
|
||||
if "api_key" in key and "OpenAI" in str(name):
|
||||
field.display_name = "OpenAI API Key"
|
||||
field.required = False
|
||||
|
|
|
|||
|
|
@ -59,11 +59,33 @@ class ToolNode(FrontendNode):
|
|||
return super().to_dict()
|
||||
|
||||
|
||||
class PythonFunctionNode(FrontendNode):
|
||||
name: str = "PythonFunction"
|
||||
class PythonFunctionToolNode(FrontendNode):
|
||||
name: str = "PythonFunctionTool"
|
||||
template: Template = Template(
|
||||
type_name="python_function",
|
||||
type_name="PythonFunctionTool",
|
||||
fields=[
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=True,
|
||||
placeholder="",
|
||||
is_list=False,
|
||||
show=True,
|
||||
multiline=False,
|
||||
value="",
|
||||
name="name",
|
||||
advanced=False,
|
||||
),
|
||||
TemplateField(
|
||||
field_type="str",
|
||||
required=True,
|
||||
placeholder="",
|
||||
is_list=False,
|
||||
show=True,
|
||||
multiline=False,
|
||||
value="",
|
||||
name="description",
|
||||
advanced=False,
|
||||
),
|
||||
TemplateField(
|
||||
field_type="code",
|
||||
required=True,
|
||||
|
|
@ -73,11 +95,11 @@ class PythonFunctionNode(FrontendNode):
|
|||
value=DEFAULT_PYTHON_FUNCTION,
|
||||
name="code",
|
||||
advanced=False,
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
description: str = "Python function to be executed."
|
||||
base_classes: list[str] = ["function"]
|
||||
base_classes: list[str] = ["Tool"]
|
||||
|
||||
def to_dict(self):
|
||||
return super().to_dict()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,20 @@ OPENAI_MODELS = [
|
|||
]
|
||||
CHAT_OPENAI_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"]
|
||||
|
||||
ANTHROPIC_MODELS = [
|
||||
"claude-v1", # largest model, ideal for a wide range of more complex tasks.
|
||||
"claude-v1-100k", # An enhanced version of claude-v1 with a 100,000 token (roughly 75,000 word) context window.
|
||||
"claude-instant-v1", # A smaller model with far lower latency, sampling at roughly 40 words/sec!
|
||||
"claude-instant-v1-100k", # Like claude-instant-v1 with a 100,000 token context window but retains its performance.
|
||||
# Specific sub-versions of the above models:
|
||||
"claude-v1.3", # Vs claude-v1.2: better instruction-following, code, and non-English dialogue and writing.
|
||||
"claude-v1.3-100k", # An enhanced version of claude-v1.3 with a 100,000 token (roughly 75,000 word) context window.
|
||||
"claude-v1.2", # Vs claude-v1.1: small adv in general helpfulness, instruction following, coding, and other tasks.
|
||||
"claude-v1.0", # An earlier version of claude-v1.
|
||||
"claude-instant-v1.1", # Latest version of claude-instant-v1. Better than claude-instant-v1.0 at most tasks.
|
||||
"claude-instant-v1.1-100k", # Version of claude-instant-v1.1 with a 100K token context window.
|
||||
"claude-instant-v1.0", # An earlier version of claude-instant-v1.
|
||||
]
|
||||
|
||||
DEFAULT_PYTHON_FUNCTION = """
|
||||
def python_function(text: str) -> str:
|
||||
|
|
|
|||
|
|
@ -302,7 +302,9 @@ def format_dict(d, name: Optional[str] = None):
|
|||
elif name == "ChatOpenAI" and key == "model_name":
|
||||
value["options"] = constants.CHAT_OPENAI_MODELS
|
||||
value["list"] = True
|
||||
|
||||
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
||||
value["options"] = constants.ANTHROPIC_MODELS
|
||||
value["list"] = True
|
||||
return d
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue