Merge remote-tracking branch 'origin/dev' into v2

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-10-25 12:43:44 -03:00
commit 3983d0cdb1
159 changed files with 12323 additions and 3611 deletions

View file

@ -35,6 +35,16 @@ def pytest_configure():
pytest.OPENAPI_EXAMPLE_PATH = (
Path(__file__).parent.absolute() / "data" / "Openapi.json"
)
pytest.GROUPED_CHAT_EXAMPLE_PATH = (
Path(__file__).parent.absolute() / "data" / "grouped_chat.json"
)
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = (
Path(__file__).parent.absolute() / "data" / "one_group_chat.json"
)
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = (
Path(__file__).parent.absolute() / "data" / "vector_store_grouped.json"
)
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY = (
Path(__file__).parent.absolute() / "data" / "BasicChatwithPromptandHistory.json"
)
@ -159,6 +169,24 @@ def json_flow():
return f.read()
@pytest.fixture
def grouped_chat_json_flow():
with open(pytest.GROUPED_CHAT_EXAMPLE_PATH, "r") as f:
return f.read()
@pytest.fixture
def one_grouped_chat_json_flow():
with open(pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH, "r") as f:
return f.read()
@pytest.fixture
def vector_store_grouped_json_flow():
with open(pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH, "r") as f:
return f.read()
@pytest.fixture
def json_flow_with_prompt_and_history():
with open(pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY, "r") as f:

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -241,7 +241,7 @@ def test_initialize_agent(client: TestClient, logged_in_headers):
"info": "",
}
assert template["tools"] == {
"required": False,
"required": True,
"dynamic": False,
"placeholder": "",
"show": True,

View file

@ -20,7 +20,7 @@ from langflow.interface.custom.custom_component import CustomComponent
from langchain.llms.base import BaseLLM
from langchain.chains import LLMChain
from langchain import PromptTemplate
from langchain.prompts import PromptTemplate
from langchain.schema import Document
import requests

View file

@ -1,3 +1,4 @@
import copy
import json
import os
from pathlib import Path
@ -17,6 +18,15 @@ from langflow.graph.vertex.types import (
)
from langflow.processing.process import get_result_and_thought
from langflow.utils.payload import get_root_node
from langflow.graph.graph.utils import (
find_last_node,
set_new_target_handle,
ungroup_node,
process_flow,
update_source_handle,
update_target_handle,
update_template,
)
# Test cases for the graph module
@ -24,6 +34,52 @@ from langflow.utils.payload import get_root_node
# BASIC_EXAMPLE_PATH, COMPLEX_EXAMPLE_PATH, OPENAPI_EXAMPLE_PATH
@pytest.fixture
def sample_template():
return {
"field1": {"proxy": {"field": "some_field", "id": "node1"}},
"field2": {"proxy": {"field": "other_field", "id": "node2"}},
}
@pytest.fixture
def sample_nodes():
return [
{
"id": "node1",
"data": {
"node": {
"template": {
"some_field": {"show": True, "advanced": False, "name": "Name1"}
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"other_field": {
"show": False,
"advanced": True,
"display_name": "DisplayName2",
}
}
}
},
},
{
"id": "node3",
"data": {
"node": {
"template": {"unrelated_field": {"show": True, "advanced": True}}
}
},
},
]
def get_node_by_type(graph, node_type: Type[Vertex]) -> Union[Vertex, None]:
"""Get a node by type"""
return next((node for node in graph.nodes if isinstance(node, node_type)), None)
@ -113,55 +169,6 @@ def test_get_node_neighbors_basic(basic_graph):
)
# def test_get_node_neighbors_complex(complex_graph):
# """Test getting node neighbors"""
# assert isinstance(complex_graph, Graph)
# # Get root node
# root = get_root_node(complex_graph)
# assert root is not None
# neighbors = complex_graph.get_nodes_with_target(root)
# assert neighbors is not None
# # Neighbors should be a list of nodes
# assert isinstance(neighbors, list)
# # Root Node is an Agent, it requires an LLMChain and tools
# # We need to check if there is a Chain in the one of the neighbors'
# assert any("Chain" in neighbor.data["type"] for neighbor in neighbors)
# # assert Tool is in the neighbors
# assert any("Tool" in neighbor.data["type"] for neighbor in neighbors)
# # Now on to the Chain's neighbors
# chain = next(neighbor for neighbor in neighbors if "Chain" in neighbor.data["type"])
# chain_neighbors = complex_graph.get_nodes_with_target(chain)
# assert chain_neighbors is not None
# # Check if there is a LLM in the chain's neighbors
# assert any("OpenAI" in neighbor.data["type"] for neighbor in chain_neighbors)
# # Chain should have a Prompt as a neighbor
# assert any("Prompt" in neighbor.data["type"] for neighbor in chain_neighbors)
# # Now on to the Tool's neighbors
# tool = next(neighbor for neighbor in neighbors if "Tool" in neighbor.data["type"])
# tool_neighbors = complex_graph.get_nodes_with_target(tool)
# assert tool_neighbors is not None
# # Check if there is an Agent in the tool's neighbors
# assert any("Agent" in neighbor.data["type"] for neighbor in tool_neighbors)
# # This Agent has a Tool that has a PythonFunction as func
# agent = next(
# neighbor for neighbor in tool_neighbors if "Agent" in neighbor.data["type"]
# )
# agent_neighbors = complex_graph.get_nodes_with_target(agent)
# assert agent_neighbors is not None
# # Check if there is a Tool in the agent's neighbors
# assert any("Tool" in neighbor.data["type"] for neighbor in agent_neighbors)
# # This Tool has a PythonFunction as func
# tool = next(
# neighbor for neighbor in agent_neighbors if "Tool" in neighbor.data["type"]
# )
# tool_neighbors = complex_graph.get_nodes_with_target(tool)
# assert tool_neighbors is not None
# # Check if there is a PythonFunction in the tool's neighbors
# assert any(
# "PythonFunctionTool" in neighbor.data["type"] for neighbor in tool_neighbors
# )
def test_get_node(basic_graph):
"""Test getting a single node"""
node_id = basic_graph.nodes[0].id
@ -322,6 +329,196 @@ def test_get_result_and_thought(basic_graph):
assert isinstance(result, dict)
def test_find_last_node(grouped_chat_json_flow):
grouped_chat_data = json.loads(grouped_chat_json_flow).get("data")
nodes, edges = grouped_chat_data["nodes"], grouped_chat_data["edges"]
last_node = find_last_node(nodes, edges)
assert last_node is not None # Replace with the actual expected value
assert last_node["id"] == "LLMChain-pimAb" # Replace with the actual expected value
def test_ungroup_node(grouped_chat_json_flow):
grouped_chat_data = json.loads(grouped_chat_json_flow).get("data")
group_node = grouped_chat_data["nodes"][
2
] # Assuming the first node is a group node
base_flow = copy.deepcopy(grouped_chat_data)
ungroup_node(group_node["data"], base_flow)
# after ungroup_node is called, the base_flow and grouped_chat_data should be different
assert base_flow != grouped_chat_data
# assert node 2 is not a group node anymore
assert base_flow["nodes"][2]["data"]["node"].get("flow") is None
# assert the edges are updated
assert len(base_flow["edges"]) > len(grouped_chat_data["edges"])
assert base_flow["edges"][0]["source"] == "ConversationBufferMemory-kUMif"
assert base_flow["edges"][0]["target"] == "LLMChain-2P369"
assert base_flow["edges"][1]["source"] == "PromptTemplate-Wjk4g"
assert base_flow["edges"][1]["target"] == "LLMChain-2P369"
assert base_flow["edges"][2]["source"] == "ChatOpenAI-rUJ1b"
assert base_flow["edges"][2]["target"] == "LLMChain-2P369"
def test_process_flow(grouped_chat_json_flow):
grouped_chat_data = json.loads(grouped_chat_json_flow).get("data")
processed_flow = process_flow(grouped_chat_data)
assert processed_flow is not None
assert isinstance(processed_flow, dict)
assert "nodes" in processed_flow
assert "edges" in processed_flow
def test_process_flow_one_group(one_grouped_chat_json_flow):
grouped_chat_data = json.loads(one_grouped_chat_json_flow).get("data")
# There should be only one node
assert len(grouped_chat_data["nodes"]) == 1
# Get the node, it should be a group node
group_node = grouped_chat_data["nodes"][0]
node_data = group_node["data"]["node"]
assert node_data.get("flow") is not None
template_data = node_data["template"]
assert any("openai_api_key" in key for key in template_data.keys())
# Get the openai_api_key dict
openai_api_key = next(
(template_data[key] for key in template_data.keys() if "openai_api_key" in key),
None,
)
assert openai_api_key is not None
assert openai_api_key["value"] == "test"
processed_flow = process_flow(grouped_chat_data)
assert processed_flow is not None
assert isinstance(processed_flow, dict)
assert "nodes" in processed_flow
assert "edges" in processed_flow
# Now get the node that has ChatOpenAI in its id
chat_openai_node = next(
(node for node in processed_flow["nodes"] if "ChatOpenAI" in node["id"]), None
)
assert chat_openai_node is not None
assert (
chat_openai_node["data"]["node"]["template"]["openai_api_key"]["value"]
== "test"
)
def test_process_flow_vector_store_grouped(vector_store_grouped_json_flow):
grouped_chat_data = json.loads(vector_store_grouped_json_flow).get("data")
nodes = grouped_chat_data["nodes"]
assert len(nodes) == 4
# There are two group nodes in this flow
# One of them is inside the other totalling 7 nodes
# 4 nodes grouped, one of these turns into 1 normal node and 1 group node
# This group node has 2 nodes inside it
processed_flow = process_flow(grouped_chat_data)
assert processed_flow is not None
processed_nodes = processed_flow["nodes"]
assert len(processed_nodes) == 7
assert isinstance(processed_flow, dict)
assert "nodes" in processed_flow
assert "edges" in processed_flow
edges = processed_flow["edges"]
# Expected keywords in source and target fields
expected_keywords = [
{"source": "VectorStoreInfo", "target": "VectorStoreAgent"},
{"source": "ChatOpenAI", "target": "VectorStoreAgent"},
{"source": "OpenAIEmbeddings", "target": "Chroma"},
{"source": "Chroma", "target": "VectorStoreInfo"},
{"source": "WebBaseLoader", "target": "RecursiveCharacterTextSplitter"},
{"source": "RecursiveCharacterTextSplitter", "target": "Chroma"},
]
for idx, expected_keyword in enumerate(expected_keywords):
for key, value in expected_keyword.items():
assert (
value in edges[idx][key].split("-")[0]
), f"Edge {idx}, key {key} expected to contain {value} but got {edges[idx][key]}"
def test_update_template(sample_template, sample_nodes):
# Making a deep copy to keep original sample_nodes unchanged
nodes_copy = copy.deepcopy(sample_nodes)
update_template(sample_template, nodes_copy)
# Now, validate the updates.
node1_updated = next((n for n in nodes_copy if n["id"] == "node1"), None)
node2_updated = next((n for n in nodes_copy if n["id"] == "node2"), None)
node3_updated = next((n for n in nodes_copy if n["id"] == "node3"), None)
assert node1_updated["data"]["node"]["template"]["some_field"]["show"] is True
assert node1_updated["data"]["node"]["template"]["some_field"]["advanced"] is False
assert (
node1_updated["data"]["node"]["template"]["some_field"]["display_name"]
== "Name1"
)
assert node2_updated["data"]["node"]["template"]["other_field"]["show"] is False
assert node2_updated["data"]["node"]["template"]["other_field"]["advanced"] is True
assert (
node2_updated["data"]["node"]["template"]["other_field"]["display_name"]
== "DisplayName2"
)
# Ensure node3 remains unchanged
assert node3_updated == sample_nodes[2]
# Test `update_target_handle`
def test_update_target_handle_proxy():
new_edge = {
"data": {
"targetHandle": {
"type": "some_type",
"proxy": {"id": "some_id", "field": ""},
}
}
}
g_nodes = [{"id": "some_id", "data": {"node": {"flow": None}}}]
group_node_id = "group_id"
updated_edge = update_target_handle(new_edge, g_nodes, group_node_id)
assert updated_edge["data"]["targetHandle"] == new_edge["data"]["targetHandle"]
# Test `set_new_target_handle`
def test_set_new_target_handle():
proxy_id = "proxy_id"
new_edge = {"target": None, "data": {"targetHandle": {}}}
target_handle = {"type": "type_1", "proxy": {"field": "field_1"}}
node = {
"data": {
"node": {
"flow": True,
"template": {
"field_1": {"proxy": {"field": "new_field", "id": "new_id"}}
},
}
}
}
set_new_target_handle(proxy_id, new_edge, target_handle, node)
assert new_edge["target"] == "proxy_id"
assert new_edge["data"]["targetHandle"]["fieldName"] == "field_1"
assert new_edge["data"]["targetHandle"]["proxy"] == {
"field": "new_field",
"id": "new_id",
}
# Test `update_source_handle`
def test_update_source_handle():
new_edge = {"source": None, "data": {"sourceHandle": {"id": None}}}
flow_data = {
"nodes": [{"id": "some_node"}, {"id": "last_node"}],
"edges": [{"source": "some_node"}],
}
updated_edge = update_source_handle(
new_edge, flow_data["nodes"], flow_data["edges"]
)
assert updated_edge["source"] == "last_node"
assert updated_edge["data"]["sourceHandle"]["id"] == "last_node"
def test_pickle_graph(json_vector_store):
loaded_json = json.loads(json_vector_store)
graph = Graph.from_payload(loaded_json)

View file

@ -21,7 +21,7 @@ def test_prompt_template(client: TestClient, logged_in_headers):
template = prompt["template"]
assert template["input_variables"] == {
"required": True,
"dynamic": False,
"dynamic": True,
"placeholder": "",
"show": False,
"multiline": False,
@ -35,7 +35,7 @@ def test_prompt_template(client: TestClient, logged_in_headers):
assert template["output_parser"] == {
"required": False,
"dynamic": False,
"dynamic": True,
"placeholder": "",
"show": False,
"multiline": False,
@ -49,7 +49,7 @@ def test_prompt_template(client: TestClient, logged_in_headers):
assert template["partial_variables"] == {
"required": False,
"dynamic": False,
"dynamic": True,
"placeholder": "",
"show": False,
"multiline": False,
@ -63,7 +63,7 @@ def test_prompt_template(client: TestClient, logged_in_headers):
assert template["template"] == {
"required": True,
"dynamic": False,
"dynamic": True,
"placeholder": "",
"show": True,
"multiline": True,
@ -77,7 +77,7 @@ def test_prompt_template(client: TestClient, logged_in_headers):
assert template["template_format"] == {
"required": False,
"dynamic": False,
"dynamic": True,
"placeholder": "",
"show": False,
"multiline": False,
@ -92,7 +92,7 @@ def test_prompt_template(client: TestClient, logged_in_headers):
assert template["validate_template"] == {
"required": False,
"dynamic": False,
"dynamic": True,
"placeholder": "",
"show": False,
"multiline": False,

View file

@ -1,93 +1,95 @@
from unittest.mock import patch, Mock, MagicMock, call
from unittest.mock import patch, MagicMock
from langflow.services.database.models.user.user import User
from langflow.services.settings.constants import (
DEFAULT_SUPERUSER,
DEFAULT_SUPERUSER_PASSWORD,
)
from langflow.services.utils import (
setup_superuser,
teardown_superuser,
)
@patch("langflow.services.getters.get_settings_service")
@patch("langflow.services.utils.create_super_user")
@patch("langflow.services.getters.get_session")
def test_setup_superuser(
mock_get_session, mock_create_super_user, mock_get_settings_service
):
# Test when AUTO_LOGIN is True
calls = []
mock_settings_service = Mock()
mock_settings_service.auth_settings.AUTO_LOGIN = True
mock_settings_service.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
mock_settings_service.auth_settings.SUPERUSER_PASSWORD = DEFAULT_SUPERUSER_PASSWORD
mock_get_settings_service.return_value = mock_settings_service
mock_session = Mock()
mock_session.query.return_value.filter.return_value.first.return_value = (
mock_session
)
# return value of get_session is a generator
mock_get_session.return_value = iter([mock_session, mock_session, mock_session])
setup_superuser(mock_settings_service, mock_session)
mock_session.query.assert_called_once_with(User)
actual_expr = mock_session.query.return_value.filter.call_args[0][0]
expected_expr = User.username == DEFAULT_SUPERUSER
# @patch("langflow.services.getters.get_session")
# @patch("langflow.services.utils.create_super_user")
# @patch("langflow.services.getters.get_settings_service")
# # @patch("langflow.services.utils.verify_password")
# def test_setup_superuser(
# mock_get_session, mock_create_super_user, mock_get_settings_service
# ):
# # Test when AUTO_LOGIN is True
# calls = []
# mock_settings_service = Mock()
# mock_settings_service.auth_settings.AUTO_LOGIN = True
# mock_settings_service.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
# mock_settings_service.auth_settings.SUPERUSER_PASSWORD = DEFAULT_SUPERUSER_PASSWORD
# mock_get_settings_service.return_value = mock_settings_service
# mock_session = Mock()
# mock_session.query.return_value.filter.return_value.first.return_value = (
# mock_session
# )
# # return value of get_session is a generator
# mock_get_session.return_value = iter([mock_session, mock_session, mock_session])
# setup_superuser(mock_settings_service, mock_session)
# mock_session.query.assert_called_once_with(User)
# # Set return value of filter to be None
# mock_session.query.return_value.filter.return_value.first.return_value = None
# actual_expr = mock_session.query.return_value.filter.call_args[0][0]
# expected_expr = User.username == DEFAULT_SUPERUSER
assert str(actual_expr) == str(expected_expr)
create_call = call(
db=mock_session, username=DEFAULT_SUPERUSER, password=DEFAULT_SUPERUSER_PASSWORD
)
calls.append(create_call)
# mock_create_super_user.assert_has_calls(calls)
assert 1 == mock_create_super_user.call_count
# assert str(actual_expr) == str(expected_expr)
# create_call = call(
# db=mock_session, username=DEFAULT_SUPERUSER, password=DEFAULT_SUPERUSER_PASSWORD
# )
# calls.append(create_call)
# # mock_create_super_user.assert_has_calls(calls)
# assert 1 == mock_create_super_user.call_count
def reset_mock_credentials():
mock_settings_service.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
mock_settings_service.auth_settings.SUPERUSER_PASSWORD = (
DEFAULT_SUPERUSER_PASSWORD
)
# def reset_mock_credentials():
# mock_settings_service.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
# mock_settings_service.auth_settings.SUPERUSER_PASSWORD = (
# DEFAULT_SUPERUSER_PASSWORD
# )
ADMIN_USER_NAME = "admin_user"
# Test when username and password are default
mock_settings_service.auth_settings = Mock()
mock_settings_service.auth_settings.AUTO_LOGIN = False
mock_settings_service.auth_settings.SUPERUSER = ADMIN_USER_NAME
mock_settings_service.auth_settings.SUPERUSER_PASSWORD = "password"
mock_settings_service.auth_settings.reset_credentials = Mock(
side_effect=reset_mock_credentials
)
# ADMIN_USER_NAME = "admin_user"
# # Test when username and password are default
# mock_settings_service.auth_settings = Mock()
# mock_settings_service.auth_settings.AUTO_LOGIN = False
# mock_settings_service.auth_settings.SUPERUSER = ADMIN_USER_NAME
# mock_settings_service.auth_settings.SUPERUSER_PASSWORD = "password"
# mock_settings_service.auth_settings.reset_credentials = Mock(
# side_effect=reset_mock_credentials
# )
mock_get_settings_service.return_value = mock_settings_service
# mock_get_settings_service.return_value = mock_settings_service
setup_superuser(mock_settings_service, mock_session)
mock_session.query.assert_called_with(User)
actual_expr = mock_session.query.return_value.filter.call_args[0][0]
expected_expr = User.username == ADMIN_USER_NAME
# setup_superuser(mock_settings_service, mock_session)
# mock_session.query.assert_called_with(User)
# actual_expr = mock_session.query.return_value.filter.call_args[0][0]
# expected_expr = User.username == ADMIN_USER_NAME
assert str(actual_expr) == str(expected_expr)
create_call = call(db=mock_session, username=ADMIN_USER_NAME, password="password")
calls.append(create_call)
# mock_create_super_user.assert_has_calls(calls)
assert 2 == mock_create_super_user.call_count
# Test that superuser credentials are reset
mock_settings_service.auth_settings.reset_credentials.assert_called_once()
assert mock_settings_service.auth_settings.SUPERUSER != ADMIN_USER_NAME
assert mock_settings_service.auth_settings.SUPERUSER_PASSWORD != "password"
# assert str(actual_expr) == str(expected_expr)
# create_call = call(db=mock_session, username=ADMIN_USER_NAME, password="password")
# calls.append(create_call)
# # mock_create_super_user.assert_has_calls(calls)
# assert 2 == mock_create_super_user.call_count
# # Test that superuser credentials are reset
# mock_settings_service.auth_settings.reset_credentials.assert_called_once()
# assert mock_settings_service.auth_settings.SUPERUSER != ADMIN_USER_NAME
# assert mock_settings_service.auth_settings.SUPERUSER_PASSWORD != "password"
# Test when superuser already exists
mock_settings_service.auth_settings.AUTO_LOGIN = False
mock_settings_service.auth_settings.SUPERUSER = ADMIN_USER_NAME
mock_settings_service.auth_settings.SUPERUSER_PASSWORD = "password"
mock_user = Mock()
mock_user.is_superuser = True
mock_session.query.return_value.filter.return_value.first.return_value = mock_user
setup_superuser(mock_settings_service, mock_session)
mock_session.query.assert_called_with(User)
actual_expr = mock_session.query.return_value.filter.call_args[0][0]
expected_expr = User.username == ADMIN_USER_NAME
# # Test when superuser already exists
# mock_settings_service.auth_settings.AUTO_LOGIN = False
# mock_settings_service.auth_settings.SUPERUSER = ADMIN_USER_NAME
# mock_settings_service.auth_settings.SUPERUSER_PASSWORD = "password"
# mock_user = Mock()
# mock_user.is_superuser = True
# mock_session.query.return_value.filter.return_value.first.return_value = mock_user
# setup_superuser(mock_settings_service, mock_session)
# mock_session.query.assert_called_with(User)
# actual_expr = mock_session.query.return_value.filter.call_args[0][0]
# expected_expr = User.username == ADMIN_USER_NAME
assert str(actual_expr) == str(expected_expr)
# assert str(actual_expr) == str(expected_expr)
@patch("langflow.services.getters.get_settings_service")