Merge branch 'dev' into python_function_tool
This commit is contained in:
commit
73e07be0c7
24 changed files with 221 additions and 129 deletions
2
Makefile
2
Makefile
|
|
@ -59,7 +59,7 @@ lcserve_push:
|
||||||
make build_frontend
|
make build_frontend
|
||||||
@version=$$(poetry version --short); \
|
@version=$$(poetry version --short); \
|
||||||
lc-serve push --app langflow.lcserve:app --app-dir . \
|
lc-serve push --app langflow.lcserve:app --app-dir . \
|
||||||
--image-name langflow --image-tag $${version} --verbose
|
--image-name langflow --image-tag $${version} --verbose --public
|
||||||
|
|
||||||
lcserve_deploy:
|
lcserve_deploy:
|
||||||
@:$(if $(uses),,$(error `uses` is not set. Please run `make uses=... lcserve_deploy`))
|
@:$(if $(uses),,$(error `uses` is not set. Please run `make uses=... lcserve_deploy`))
|
||||||
|
|
|
||||||
8
poetry.lock
generated
8
poetry.lock
generated
|
|
@ -1,4 +1,4 @@
|
||||||
# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiofiles"
|
name = "aiofiles"
|
||||||
|
|
@ -1096,7 +1096,9 @@ files = [
|
||||||
{file = "duckdb-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2707096d6df4321044fcde2c9f04da632d11a8be60957fd09d49a42fae71a29"},
|
{file = "duckdb-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2707096d6df4321044fcde2c9f04da632d11a8be60957fd09d49a42fae71a29"},
|
||||||
{file = "duckdb-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b27df1b70ae74d2c88efb5ffca8490954fdc678099509a9c4404ca30acc53426"},
|
{file = "duckdb-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b27df1b70ae74d2c88efb5ffca8490954fdc678099509a9c4404ca30acc53426"},
|
||||||
{file = "duckdb-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75a97c800271b52dd0f37696d074c50576dcb4b2750b6115932a98696a268070"},
|
{file = "duckdb-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75a97c800271b52dd0f37696d074c50576dcb4b2750b6115932a98696a268070"},
|
||||||
|
{file = "duckdb-0.8.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:804cac261a5e016506a6d67838a65d19b06a237f7949f1704f0e800eb708286a"},
|
||||||
{file = "duckdb-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6b9abca7fa6713e1d031c18485343b4de99742c7e1b85c10718aa2f31a4e2c6"},
|
{file = "duckdb-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6b9abca7fa6713e1d031c18485343b4de99742c7e1b85c10718aa2f31a4e2c6"},
|
||||||
|
{file = "duckdb-0.8.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:51aa6d606d49072abcfeb3be209eb559ac94c1b5e70f58ac3adbb94aca9cd69f"},
|
||||||
{file = "duckdb-0.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7c8dc769aaf2be0a1c57995ca657e5b92c1c56fc8437edb720ca6cab571adf14"},
|
{file = "duckdb-0.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7c8dc769aaf2be0a1c57995ca657e5b92c1c56fc8437edb720ca6cab571adf14"},
|
||||||
{file = "duckdb-0.8.0-cp311-cp311-win32.whl", hash = "sha256:c4207d18b42387c4a035846d8878eb967070198be8ac26fd77797ce320d1a400"},
|
{file = "duckdb-0.8.0-cp311-cp311-win32.whl", hash = "sha256:c4207d18b42387c4a035846d8878eb967070198be8ac26fd77797ce320d1a400"},
|
||||||
{file = "duckdb-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:0c392257547c20794c3072fcbca99a49ef0a49974005d755e93893e2b4875267"},
|
{file = "duckdb-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:0c392257547c20794c3072fcbca99a49ef0a49974005d755e93893e2b4875267"},
|
||||||
|
|
@ -2409,13 +2411,13 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain-serve"
|
name = "langchain-serve"
|
||||||
version = "0.0.38"
|
version = "0.0.40"
|
||||||
description = "Langchain Serve - serve your langchain apps on Jina AI Cloud."
|
description = "Langchain Serve - serve your langchain apps on Jina AI Cloud."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "langchain-serve-0.0.38.tar.gz", hash = "sha256:649b8e26eebe6b33960c081b388fb7118acbfdc00f97dd935a580ab88aca53d6"},
|
{file = "langchain-serve-0.0.40.tar.gz", hash = "sha256:c60b173fcf0b682fbb70d34e8f485ce168e2229f55cb5c4ffbc26a5206af1c06"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ psycopg2-binary = "^2.9.6"
|
||||||
pyarrow = "^11.0.0"
|
pyarrow = "^11.0.0"
|
||||||
tiktoken = "^0.3.3"
|
tiktoken = "^0.3.3"
|
||||||
wikipedia = "^1.4.0"
|
wikipedia = "^1.4.0"
|
||||||
langchain-serve = { version = "^0.0.38", optional = true }
|
langchain-serve = { version = ">0.0.39", optional = true }
|
||||||
qdrant-client = "^1.2.0"
|
qdrant-client = "^1.2.0"
|
||||||
websockets = "^11.0.3"
|
websockets = "^11.0.3"
|
||||||
weaviate-client = "^3.19.2"
|
weaviate-client = "^3.19.2"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from langflow.api.base import (
|
||||||
PromptValidationResponse,
|
PromptValidationResponse,
|
||||||
validate_prompt,
|
validate_prompt,
|
||||||
)
|
)
|
||||||
from langflow.graph.nodes import VectorStoreNode
|
from langflow.graph.node.types import VectorStoreNode
|
||||||
from langflow.interface.run import build_graph
|
from langflow.interface.run import build_graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.validate import validate_code
|
from langflow.utils.validate import validate_code
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,35 @@
|
||||||
from langflow.graph.base import Edge, Node
|
from langflow.graph.edge.base import Edge
|
||||||
from langflow.graph.graph import Graph
|
from langflow.graph.graph.base import Graph
|
||||||
|
from langflow.graph.node.base import Node
|
||||||
|
from langflow.graph.node.types import (
|
||||||
|
AgentNode,
|
||||||
|
ChainNode,
|
||||||
|
DocumentLoaderNode,
|
||||||
|
EmbeddingNode,
|
||||||
|
LLMNode,
|
||||||
|
MemoryNode,
|
||||||
|
PromptNode,
|
||||||
|
TextSplitterNode,
|
||||||
|
ToolNode,
|
||||||
|
ToolkitNode,
|
||||||
|
VectorStoreNode,
|
||||||
|
WrapperNode,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = ["Graph", "Node", "Edge"]
|
__all__ = [
|
||||||
|
"Graph",
|
||||||
|
"Node",
|
||||||
|
"Edge",
|
||||||
|
"AgentNode",
|
||||||
|
"ChainNode",
|
||||||
|
"DocumentLoaderNode",
|
||||||
|
"EmbeddingNode",
|
||||||
|
"LLMNode",
|
||||||
|
"MemoryNode",
|
||||||
|
"PromptNode",
|
||||||
|
"TextSplitterNode",
|
||||||
|
"ToolNode",
|
||||||
|
"ToolkitNode",
|
||||||
|
"VectorStoreNode",
|
||||||
|
"WrapperNode",
|
||||||
|
]
|
||||||
|
|
|
||||||
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.node.base import Node
|
||||||
|
|
||||||
|
|
||||||
|
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})"
|
||||||
|
)
|
||||||
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 typing import Dict, List, Type, Union
|
||||||
|
|
||||||
from langflow.graph.base import Edge, Node
|
from langflow.graph.edge.base import Edge
|
||||||
from langflow.graph.nodes import (
|
from langflow.graph.graph.constants import NODE_TYPE_MAP
|
||||||
AgentNode,
|
from langflow.graph.node.base import Node
|
||||||
ChainNode,
|
from langflow.graph.node.types import (
|
||||||
DocumentLoaderNode,
|
|
||||||
EmbeddingNode,
|
|
||||||
FileToolNode,
|
FileToolNode,
|
||||||
LLMNode,
|
LLMNode,
|
||||||
MemoryNode,
|
|
||||||
PromptNode,
|
|
||||||
TextSplitterNode,
|
|
||||||
ToolkitNode,
|
ToolkitNode,
|
||||||
ToolNode,
|
|
||||||
VectorStoreNode,
|
|
||||||
WrapperNode,
|
|
||||||
)
|
)
|
||||||
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.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
|
from langflow.utils import payload
|
||||||
|
|
||||||
|
|
||||||
class Graph:
|
class Graph:
|
||||||
|
"""A class representing a graph of nodes and edges."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
nodes: List[Dict[str, Union[str, Dict[str, Union[str, List[str]]]]]],
|
nodes: List[Dict[str, Union[str, Dict[str, Union[str, List[str]]]]]],
|
||||||
|
|
@ -43,6 +25,7 @@ class Graph:
|
||||||
self._build_graph()
|
self._build_graph()
|
||||||
|
|
||||||
def _build_graph(self) -> None:
|
def _build_graph(self) -> None:
|
||||||
|
"""Builds the graph from the nodes and edges."""
|
||||||
self.nodes = self._build_nodes()
|
self.nodes = self._build_nodes()
|
||||||
self.edges = self._build_edges()
|
self.edges = self._build_edges()
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
|
|
@ -51,17 +34,25 @@ class Graph:
|
||||||
|
|
||||||
# This is a hack to make sure that the LLM node is sent to
|
# This is a hack to make sure that the LLM node is sent to
|
||||||
# the toolkit node
|
# 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
|
llm_node = None
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
node._build_params()
|
node._build_params()
|
||||||
|
|
||||||
if isinstance(node, LLMNode):
|
if isinstance(node, LLMNode):
|
||||||
llm_node = node
|
llm_node = node
|
||||||
|
|
||||||
|
if llm_node:
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
if isinstance(node, ToolkitNode):
|
if isinstance(node, ToolkitNode):
|
||||||
node.params["llm"] = llm_node
|
node.params["llm"] = llm_node
|
||||||
# remove invalid nodes
|
|
||||||
|
def _remove_invalid_nodes(self) -> None:
|
||||||
|
"""Removes invalid nodes from the graph."""
|
||||||
self.nodes = [
|
self.nodes = [
|
||||||
node
|
node
|
||||||
for node in self.nodes
|
for node in self.nodes
|
||||||
|
|
@ -70,19 +61,23 @@ class Graph:
|
||||||
]
|
]
|
||||||
|
|
||||||
def _validate_node(self, node: Node) -> bool:
|
def _validate_node(self, node: Node) -> bool:
|
||||||
|
"""Validates a node."""
|
||||||
# All nodes that do not have edges are invalid
|
# All nodes that do not have edges are invalid
|
||||||
return len(node.edges) > 0
|
return len(node.edges) > 0
|
||||||
|
|
||||||
def get_node(self, node_id: str) -> Union[None, Node]:
|
def get_node(self, node_id: str) -> Union[None, Node]:
|
||||||
|
"""Returns a node by id."""
|
||||||
return next((node for node in self.nodes if node.id == node_id), None)
|
return next((node for node in self.nodes if node.id == node_id), None)
|
||||||
|
|
||||||
def get_nodes_with_target(self, node: Node) -> List[Node]:
|
def get_nodes_with_target(self, node: Node) -> List[Node]:
|
||||||
|
"""Returns the nodes connected to a node."""
|
||||||
connected_nodes: List[Node] = [
|
connected_nodes: List[Node] = [
|
||||||
edge.source for edge in self.edges if edge.target == node
|
edge.source for edge in self.edges if edge.target == node
|
||||||
]
|
]
|
||||||
return connected_nodes
|
return connected_nodes
|
||||||
|
|
||||||
def build(self) -> List[Node]:
|
def build(self) -> List[Node]:
|
||||||
|
"""Builds the graph."""
|
||||||
# Get root node
|
# Get root node
|
||||||
root_node = payload.get_root_node(self)
|
root_node = payload.get_root_node(self)
|
||||||
if root_node is None:
|
if root_node is None:
|
||||||
|
|
@ -90,6 +85,7 @@ class Graph:
|
||||||
return root_node.build()
|
return root_node.build()
|
||||||
|
|
||||||
def get_node_neighbors(self, node: Node) -> Dict[Node, int]:
|
def get_node_neighbors(self, node: Node) -> Dict[Node, int]:
|
||||||
|
"""Returns the neighbors of a node."""
|
||||||
neighbors: Dict[Node, int] = {}
|
neighbors: Dict[Node, int] = {}
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
if edge.source == node:
|
if edge.source == node:
|
||||||
|
|
@ -105,6 +101,7 @@ class Graph:
|
||||||
return neighbors
|
return neighbors
|
||||||
|
|
||||||
def _build_edges(self) -> List[Edge]:
|
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
|
# Edge takes two nodes as arguments, so we need to build the nodes first
|
||||||
# and then build the edges
|
# and then build the edges
|
||||||
# if we can't find a node, we raise an error
|
# if we can't find a node, we raise an error
|
||||||
|
|
@ -121,30 +118,15 @@ class Graph:
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
def _get_node_class(self, node_type: str, node_lc_type: str) -> Type[Node]:
|
def _get_node_class(self, node_type: str, node_lc_type: str) -> Type[Node]:
|
||||||
node_type_map: Dict[str, Type[Node]] = {
|
"""Returns the node class based on the node type."""
|
||||||
**{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()},
|
|
||||||
}
|
|
||||||
|
|
||||||
if node_type in FILE_TOOLS:
|
if node_type in FILE_TOOLS:
|
||||||
return FileToolNode
|
return FileToolNode
|
||||||
if node_type in node_type_map:
|
if node_type in NODE_TYPE_MAP:
|
||||||
return node_type_map[node_type]
|
return NODE_TYPE_MAP[node_type]
|
||||||
if node_lc_type in node_type_map:
|
return NODE_TYPE_MAP[node_lc_type] if node_lc_type in NODE_TYPE_MAP else Node
|
||||||
return node_type_map[node_lc_type]
|
|
||||||
return Node
|
|
||||||
|
|
||||||
def _build_nodes(self) -> List[Node]:
|
def _build_nodes(self) -> List[Node]:
|
||||||
|
"""Builds the nodes of the graph."""
|
||||||
nodes: List[Node] = []
|
nodes: List[Node] = []
|
||||||
for node in self._nodes:
|
for node in self._nodes:
|
||||||
node_data = node["data"]
|
node_data = node["data"]
|
||||||
|
|
@ -157,6 +139,7 @@ class Graph:
|
||||||
return nodes
|
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]:
|
||||||
|
"""Returns the children of a node based on the node type."""
|
||||||
children = []
|
children = []
|
||||||
node_types = [node.data["type"]]
|
node_types = [node.data["type"]]
|
||||||
if "node" in node.data:
|
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.node.base import Node
|
||||||
|
from langflow.graph.node.types import (
|
||||||
|
AgentNode,
|
||||||
|
ChainNode,
|
||||||
|
DocumentLoaderNode,
|
||||||
|
EmbeddingNode,
|
||||||
|
LLMNode,
|
||||||
|
MemoryNode,
|
||||||
|
PromptNode,
|
||||||
|
TextSplitterNode,
|
||||||
|
ToolNode,
|
||||||
|
ToolkitNode,
|
||||||
|
VectorStoreNode,
|
||||||
|
WrapperNode,
|
||||||
|
)
|
||||||
|
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"]
|
||||||
|
|
||||||
|
|
||||||
|
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()},
|
||||||
|
}
|
||||||
0
src/backend/langflow/graph/node/__init__.py
Normal file
0
src/backend/langflow/graph/node/__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.cache import base as cache_utils
|
||||||
from langflow.graph.constants import DIRECT_TYPES
|
from langflow.graph.node.constants import DIRECT_TYPES
|
||||||
from langflow.interface import loading
|
from langflow.interface import loading
|
||||||
from langflow.interface.listing import ALL_TYPES_DICT
|
from langflow.interface.listing import ALL_TYPES_DICT
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import sync_to_async
|
from langflow.utils.util import sync_to_async
|
||||||
|
|
||||||
|
|
||||||
|
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 Node:
|
class Node:
|
||||||
def __init__(self, data: Dict, base_type: Optional[str] = None) -> None:
|
def __init__(self, data: Dict, base_type: Optional[str] = None) -> None:
|
||||||
self.id: str = data["id"]
|
self.id: str = data["id"]
|
||||||
self._data = data
|
self._data = data
|
||||||
self.edges: List[Edge] = []
|
self.edges: List["Edge"] = []
|
||||||
self.base_type: Optional[str] = base_type
|
self.base_type: Optional[str] = base_type
|
||||||
self._parse_data()
|
self._parse_data()
|
||||||
self._built_object = None
|
self._built_object = None
|
||||||
|
|
@ -227,50 +227,3 @@ class Node:
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
return repr(self._built_object)
|
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,6 +1,6 @@
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from langflow.graph.base import Node
|
from langflow.graph.node.base import Node
|
||||||
from langflow.graph.utils import extract_input_variables_from_prompt
|
from langflow.graph.utils import extract_input_variables_from_prompt
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -12,6 +12,7 @@ from langchain.agents.load_tools import (
|
||||||
_LLM_TOOLS,
|
_LLM_TOOLS,
|
||||||
)
|
)
|
||||||
from langchain.agents.loading import load_agent_from_config
|
from langchain.agents.loading import load_agent_from_config
|
||||||
|
from langflow.graph import Graph
|
||||||
from langchain.agents.tools import Tool
|
from langchain.agents.tools import Tool
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
from langchain.callbacks.base import BaseCallbackManager
|
from langchain.callbacks.base import BaseCallbackManager
|
||||||
|
|
@ -162,7 +163,6 @@ def instantiate_utility(node_type, class_object, params):
|
||||||
def load_flow_from_json(path: str, build=True):
|
def load_flow_from_json(path: str, build=True):
|
||||||
"""Load flow from json file"""
|
"""Load flow from json file"""
|
||||||
# This is done to avoid circular imports
|
# This is done to avoid circular imports
|
||||||
from langflow.graph import Graph
|
|
||||||
|
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
flow_graph = json.load(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.api.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler # type: ignore
|
||||||
from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict
|
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
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,9 @@ code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The style below sets the cursor property of the element with the class .react-flow__pane to the default cursor.
|
||||||
|
The cursor: default; property value restores the browser's default cursor style for the targeted element. By applying this style, the element will no longer have a custom cursor appearance such as "grab" or any other custom cursor defined elsewhere in the application. Instead, it will revert to the default cursor style determined by the browser, typically an arrow-shaped cursor. */
|
||||||
|
.react-flow__pane {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@ import Convert from "ansi-to-html";
|
||||||
export default function ChatMessage({
|
export default function ChatMessage({
|
||||||
chat,
|
chat,
|
||||||
lockChat,
|
lockChat,
|
||||||
|
lastMessage,
|
||||||
}: {
|
}: {
|
||||||
chat: ChatMessageType;
|
chat: ChatMessageType;
|
||||||
lockChat: boolean;
|
lockChat: boolean;
|
||||||
|
lastMessage: boolean;
|
||||||
}) {
|
}) {
|
||||||
const convert = new Convert({ newline: true });
|
const convert = new Convert({ newline: true });
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
|
|
@ -48,7 +50,7 @@ export default function ChatMessage({
|
||||||
"absolute transition-opacity duration-500 scale-150 " +
|
"absolute transition-opacity duration-500 scale-150 " +
|
||||||
(lockChat ? "opacity-100" : "opacity-0")
|
(lockChat ? "opacity-100" : "opacity-0")
|
||||||
}
|
}
|
||||||
src={AiIcon}
|
src={lastMessage ? AiIcon : AiIconStill}
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
className={
|
className={
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,9 @@ export default function ChatModal({
|
||||||
errors.concat(
|
errors.concat(
|
||||||
template[t].required &&
|
template[t].required &&
|
||||||
template[t].show &&
|
template[t].show &&
|
||||||
(!template[t].value || template[t].value === "") &&
|
(template[t].value === undefined ||
|
||||||
|
template[t].value === null ||
|
||||||
|
template[t].value === "") &&
|
||||||
!reactFlowInstance
|
!reactFlowInstance
|
||||||
.getEdges()
|
.getEdges()
|
||||||
.some(
|
.some(
|
||||||
|
|
@ -414,7 +416,12 @@ export default function ChatModal({
|
||||||
>
|
>
|
||||||
{chatHistory.length > 0 ? (
|
{chatHistory.length > 0 ? (
|
||||||
chatHistory.map((c, i) => (
|
chatHistory.map((c, i) => (
|
||||||
<ChatMessage lockChat={lockChat} chat={c} key={i} />
|
<ChatMessage
|
||||||
|
lockChat={lockChat}
|
||||||
|
chat={c}
|
||||||
|
lastMessage={chatHistory.length - 1 == i ? true : false}
|
||||||
|
key={i}
|
||||||
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col h-full text-center justify-center w-full items-center align-middle">
|
<div className="flex flex-col h-full text-center justify-center w-full items-center align-middle">
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { DisclosureComponentType } from "../../../../types/components";
|
||||||
export default function DisclosureComponent({
|
export default function DisclosureComponent({
|
||||||
button: { title, Icon, buttons = [] },
|
button: { title, Icon, buttons = [] },
|
||||||
children,
|
children,
|
||||||
|
openDisc,
|
||||||
}: DisclosureComponentType) {
|
}: DisclosureComponentType) {
|
||||||
return (
|
return (
|
||||||
<Disclosure as="div" key={title}>
|
<Disclosure as="div" key={title}>
|
||||||
|
|
@ -27,14 +28,14 @@ export default function DisclosureComponent({
|
||||||
<div>
|
<div>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
className={`${
|
className={`${
|
||||||
open ? "rotate-90 transform" : ""
|
open || openDisc ? "rotate-90 transform" : ""
|
||||||
} h-4 w-4 text-gray-800 dark:text-white`}
|
} h-4 w-4 text-gray-800 dark:text-white`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
</div>
|
</div>
|
||||||
<Disclosure.Panel as="div" className="-mt-px">
|
<Disclosure.Panel as="div" className="-mt-px" static={openDisc}>
|
||||||
{children}
|
{children}
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||||
export default function ExtraSidebar() {
|
export default function ExtraSidebar() {
|
||||||
const { data } = useContext(typesContext);
|
const { data } = useContext(typesContext);
|
||||||
const [dataFilter, setFilterData] = useState(data);
|
const [dataFilter, setFilterData] = useState(data);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
function onDragStart(
|
function onDragStart(
|
||||||
event: React.DragEvent<any>,
|
event: React.DragEvent<any>,
|
||||||
|
|
@ -58,6 +59,7 @@ export default function ExtraSidebar() {
|
||||||
className="dark:text-white focus:outline-none block w-full rounded-md py-1.5 ps-3 pr-9 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 sm:text-sm sm:leading-6 dark:ring-0 dark:bg-[#2d3747] dark:focus:outline-none"
|
className="dark:text-white focus:outline-none block w-full rounded-md py-1.5 ps-3 pr-9 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 sm:text-sm sm:leading-6 dark:ring-0 dark:bg-[#2d3747] dark:focus:outline-none"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleSearchInput(e.target.value);
|
handleSearchInput(e.target.value);
|
||||||
|
setSearch(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-3 items-center">
|
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-3 items-center">
|
||||||
|
|
@ -71,6 +73,7 @@ export default function ExtraSidebar() {
|
||||||
.map((d: keyof APIObjectType, i) =>
|
.map((d: keyof APIObjectType, i) =>
|
||||||
Object.keys(dataFilter[d]).length > 0 ? (
|
Object.keys(dataFilter[d]).length > 0 ? (
|
||||||
<DisclosureComponent
|
<DisclosureComponent
|
||||||
|
openDisc={search.length == 0 ? false : true}
|
||||||
key={i}
|
key={i}
|
||||||
button={{
|
button={{
|
||||||
title: nodeNames[d] ?? nodeNames.unknown,
|
title: nodeNames[d] ?? nodeNames.unknown,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ export type FileComponentType = {
|
||||||
|
|
||||||
export type DisclosureComponentType = {
|
export type DisclosureComponentType = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
openDisc: boolean;
|
||||||
button: {
|
button: {
|
||||||
title: string;
|
title: string;
|
||||||
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
|
|
||||||
|
from langflow.graph.graph.base import Graph
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
|
|
@ -46,7 +47,6 @@ def client():
|
||||||
|
|
||||||
def get_graph(_type="basic"):
|
def get_graph(_type="basic"):
|
||||||
"""Get a graph from a json file"""
|
"""Get a graph from a json file"""
|
||||||
from langflow.graph.graph import Graph
|
|
||||||
|
|
||||||
if _type == "basic":
|
if _type == "basic":
|
||||||
path = pytest.BASIC_EXAMPLE_PATH
|
path = pytest.BASIC_EXAMPLE_PATH
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
from typing import Type, Union
|
from typing import Type, Union
|
||||||
|
from langflow.graph.edge.base import Edge
|
||||||
|
from langflow.graph.node.base import Node
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langchain.llms.fake import FakeListLLM
|
from langchain.llms.fake import FakeListLLM
|
||||||
from langflow.graph import Edge, Graph, Node
|
from langflow.graph import Graph
|
||||||
from langflow.graph.nodes import (
|
from langflow.graph.node.types import (
|
||||||
AgentNode,
|
AgentNode,
|
||||||
ChainNode,
|
ChainNode,
|
||||||
FileToolNode,
|
FileToolNode,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue