Merge branch 'dev' into python_function_tool

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-06-02 12:11:02 -03:00 committed by GitHub
commit 73e07be0c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 221 additions and 129 deletions

View file

@ -59,7 +59,7 @@ lcserve_push:
make build_frontend
@version=$$(poetry version --short); \
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:
@:$(if $(uses),,$(error `uses` is not set. Please run `make uses=... lcserve_deploy`))

8
poetry.lock generated
View file

@ -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]]
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_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-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-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-win32.whl", hash = "sha256:c4207d18b42387c4a035846d8878eb967070198be8ac26fd77797ce320d1a400"},
{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]]
name = "langchain-serve"
version = "0.0.38"
version = "0.0.40"
description = "Langchain Serve - serve your langchain apps on Jina AI Cloud."
category = "main"
optional = true
python-versions = "*"
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]

View file

@ -49,7 +49,7 @@ psycopg2-binary = "^2.9.6"
pyarrow = "^11.0.0"
tiktoken = "^0.3.3"
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"
websockets = "^11.0.3"
weaviate-client = "^3.19.2"

View file

@ -9,7 +9,7 @@ from langflow.api.base import (
PromptValidationResponse,
validate_prompt,
)
from langflow.graph.nodes import VectorStoreNode
from langflow.graph.node.types import VectorStoreNode
from langflow.interface.run import build_graph
from langflow.utils.logger import logger
from langflow.utils.validate import validate_code

View file

@ -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.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",
]

View 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})"
)

View 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,
from langflow.graph.edge.base import Edge
from langflow.graph.graph.constants import NODE_TYPE_MAP
from langflow.graph.node.base import Node
from langflow.graph.node.types import (
FileToolNode,
LLMNode,
MemoryNode,
PromptNode,
TextSplitterNode,
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.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,6 +25,7 @@ class Graph:
self._build_graph()
def _build_graph(self) -> None:
"""Builds the graph from the nodes and edges."""
self.nodes = self._build_nodes()
self.edges = self._build_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
# 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):
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, ToolkitNode):
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
@ -70,19 +61,23 @@ class Graph:
]
def _validate_node(self, node: Node) -> 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]:
"""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]:
"""Returns the nodes connected to a node."""
connected_nodes: List[Node] = [
edge.source for edge in self.edges if edge.target == node
]
return connected_nodes
def build(self) -> List[Node]:
"""Builds the graph."""
# Get root node
root_node = payload.get_root_node(self)
if root_node is None:
@ -90,6 +85,7 @@ class Graph:
return root_node.build()
def get_node_neighbors(self, node: Node) -> Dict[Node, int]:
"""Returns the neighbors of a node."""
neighbors: Dict[Node, int] = {}
for edge in self.edges:
if edge.source == node:
@ -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
@ -121,30 +118,15 @@ class Graph:
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()},
}
"""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
if node_type in NODE_TYPE_MAP:
return NODE_TYPE_MAP[node_type]
return NODE_TYPE_MAP[node_lc_type] if node_lc_type in NODE_TYPE_MAP else Node
def _build_nodes(self) -> List[Node]:
"""Builds the nodes of the graph."""
nodes: List[Node] = []
for node in self._nodes:
node_data = node["data"]
@ -157,6 +139,7 @@ class Graph:
return nodes
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 = []
node_types = [node.data["type"]]
if "node" in node.data:

View 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()},
}

View 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.node.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
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:
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
@ -227,50 +227,3 @@ class Node:
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})"
)

View file

@ -1,6 +1,6 @@
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

View file

@ -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
@ -162,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)

View file

@ -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

View file

@ -15,3 +15,9 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
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;
}

View file

@ -16,9 +16,11 @@ import Convert from "ansi-to-html";
export default function ChatMessage({
chat,
lockChat,
lastMessage,
}: {
chat: ChatMessageType;
lockChat: boolean;
lastMessage: boolean;
}) {
const convert = new Convert({ newline: true });
const [message, setMessage] = useState("");
@ -48,7 +50,7 @@ export default function ChatMessage({
"absolute transition-opacity duration-500 scale-150 " +
(lockChat ? "opacity-100" : "opacity-0")
}
src={AiIcon}
src={lastMessage ? AiIcon : AiIconStill}
/>
<img
className={

View file

@ -290,7 +290,9 @@ export default function ChatModal({
errors.concat(
template[t].required &&
template[t].show &&
(!template[t].value || template[t].value === "") &&
(template[t].value === undefined ||
template[t].value === null ||
template[t].value === "") &&
!reactFlowInstance
.getEdges()
.some(
@ -414,7 +416,12 @@ export default function ChatModal({
>
{chatHistory.length > 0 ? (
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">

View file

@ -5,6 +5,7 @@ import { DisclosureComponentType } from "../../../../types/components";
export default function DisclosureComponent({
button: { title, Icon, buttons = [] },
children,
openDisc,
}: DisclosureComponentType) {
return (
<Disclosure as="div" key={title}>
@ -27,14 +28,14 @@ export default function DisclosureComponent({
<div>
<ChevronRightIcon
className={`${
open ? "rotate-90 transform" : ""
open || openDisc ? "rotate-90 transform" : ""
} h-4 w-4 text-gray-800 dark:text-white`}
/>
</div>
</div>
</Disclosure.Button>
</div>
<Disclosure.Panel as="div" className="-mt-px">
<Disclosure.Panel as="div" className="-mt-px" static={openDisc}>
{children}
</Disclosure.Panel>
</>

View file

@ -15,6 +15,7 @@ import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
export default function ExtraSidebar() {
const { data } = useContext(typesContext);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
function onDragStart(
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"
onChange={(e) => {
handleSearchInput(e.target.value);
setSearch(e.target.value);
}}
/>
<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) =>
Object.keys(dataFilter[d]).length > 0 ? (
<DisclosureComponent
openDisc={search.length == 0 ? false : true}
key={i}
button={{
title: nodeNames[d] ?? nodeNames.unknown,

View file

@ -56,6 +56,7 @@ export type FileComponentType = {
export type DisclosureComponentType = {
children: ReactNode;
openDisc: boolean;
button: {
title: string;
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;

View file

@ -2,6 +2,7 @@ import json
from pathlib import Path
from typing import AsyncGenerator
from langflow.graph.graph.base import Graph
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
@ -46,7 +47,6 @@ def client():
def get_graph(_type="basic"):
"""Get a graph from a json file"""
from langflow.graph.graph import Graph
if _type == "basic":
path = pytest.BASIC_EXAMPLE_PATH

View file

@ -1,10 +1,12 @@
from typing import Type, Union
from langflow.graph.edge.base import Edge
from langflow.graph.node.base import Node
import pytest
from langchain.chains.base import Chain
from langchain.llms.fake import FakeListLLM
from langflow.graph import Edge, Graph, Node
from langflow.graph.nodes import (
from langflow.graph import Graph
from langflow.graph.node.types import (
AgentNode,
ChainNode,
FileToolNode,