refactor: move tests folder structure and update pytest commands (#2785)

* refactor: move tests folder to src/backend

* chore(Makefile): update pytest commands to run tests from the correct directory paths for unit and integration tests

* refactor: update file path in test_custom_component.py

The file path in the test_custom_component.py file has been updated to use the correct relative path to the component_multiple_outputs.py file. This change ensures that the test code can access the correct file and improves the reliability of the test.
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-07-18 12:19:43 -03:00 • committed by GitHub
commit 0122a50a35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 5 additions and 5 deletions

View file

@ -0,0 +1,466 @@
import json
import os.path
import shutil
# we need to import tmpdir
import tempfile
from contextlib import contextmanager, suppress
from pathlib import Path
from typing import TYPE_CHECKING, AsyncGenerator
import orjson
import pytest
from dotenv import load_dotenv
from fastapi.testclient import TestClient
from httpx import AsyncClient
from sqlmodel import Session, SQLModel, create_engine, select
from sqlmodel.pool import StaticPool
from typer.testing import CliRunner
from langflow.graph.graph.base import Graph
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_password_hash
from langflow.services.database.models.api_key.model import ApiKey
from langflow.services.database.models.flow.model import Flow, FlowCreate
from langflow.services.database.models.folder.model import Folder
from langflow.services.database.models.user.model import User, UserCreate
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service
if TYPE_CHECKING:
from langflow.services.database.service import DatabaseService
load_dotenv()
def pytest_configure(config):
config.addinivalue_line("markers", "noclient: don't create a client for this test")
config.addinivalue_line("markers", "load_flows: load the flows for this test")
config.addinivalue_line("markers", "api_key_required: run only if the api key is set in the environment variables")
data_path = Path(__file__).parent.absolute() / "data"
pytest.BASIC_EXAMPLE_PATH = data_path / "basic_example.json"
pytest.COMPLEX_EXAMPLE_PATH = data_path / "complex_example.json"
pytest.OPENAPI_EXAMPLE_PATH = data_path / "Openapi.json"
pytest.GROUPED_CHAT_EXAMPLE_PATH = data_path / "grouped_chat.json"
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = data_path / "one_group_chat.json"
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = data_path / "vector_store_grouped.json"
pytest.WEBHOOK_TEST = data_path / "WebhookTest.json"
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY = data_path / "BasicChatwithPromptandHistory.json"
pytest.CHAT_INPUT = data_path / "ChatInputTest.json"
pytest.TWO_OUTPUTS = data_path / "TwoOutputsTest.json"
pytest.VECTOR_STORE_PATH = data_path / "Vector_store.json"
pytest.SIMPLE_API_TEST = data_path / "SimpleAPITest.json"
pytest.CODE_WITH_SYNTAX_ERROR = """
def get_text():
retun "Hello World"
"""
# validate that all the paths are correct and the files exist
for path in [
pytest.BASIC_EXAMPLE_PATH,
pytest.COMPLEX_EXAMPLE_PATH,
pytest.OPENAPI_EXAMPLE_PATH,
pytest.GROUPED_CHAT_EXAMPLE_PATH,
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH,
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH,
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY,
pytest.CHAT_INPUT,
pytest.TWO_OUTPUTS,
pytest.VECTOR_STORE_PATH,
]:
assert path.exists(), f"File {path} does not exist. Available files: {list(data_path.iterdir())}"
@pytest.fixture()
async def async_client() -> AsyncGenerator:
from langflow.main import create_app
app = create_app()
async with AsyncClient(app=app, base_url="http://testserver") as client:
yield client
@pytest.fixture(name="session")
def session_fixture():
engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
yield session
class Config:
broker_url = "redis://localhost:6379/0"
result_backend = "redis://localhost:6379/0"
@pytest.fixture(name="load_flows_dir")
def load_flows_dir():
tempdir = tempfile.TemporaryDirectory()
yield tempdir.name
@pytest.fixture(name="distributed_env")
def setup_env(monkeypatch):
monkeypatch.setenv("LANGFLOW_CACHE_TYPE", "redis")
monkeypatch.setenv("LANGFLOW_REDIS_HOST", "result_backend")
monkeypatch.setenv("LANGFLOW_REDIS_PORT", "6379")
monkeypatch.setenv("LANGFLOW_REDIS_DB", "0")
monkeypatch.setenv("LANGFLOW_REDIS_EXPIRE", "3600")
monkeypatch.setenv("LANGFLOW_REDIS_PASSWORD", "")
monkeypatch.setenv("FLOWER_UNAUTHENTICATED_API", "True")
monkeypatch.setenv("BROKER_URL", "redis://result_backend:6379/0")
monkeypatch.setenv("RESULT_BACKEND", "redis://result_backend:6379/0")
monkeypatch.setenv("C_FORCE_ROOT", "true")
@pytest.fixture(name="distributed_client")
def distributed_client_fixture(session: Session, monkeypatch, distributed_env):
# Here we load the .env from ../deploy/.env
from langflow.core import celery_app
db_dir = tempfile.mkdtemp()
db_path = Path(db_dir) / "test.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
# monkeypatch langflow.services.task.manager.USE_CELERY to True
# monkeypatch.setattr(manager, "USE_CELERY", True)
monkeypatch.setattr(celery_app, "celery_app", celery_app.make_celery("langflow", Config))
# def get_session_override():
# return session
from langflow.main import create_app
app = create_app()
# app.dependency_overrides[get_session] = get_session_override
with TestClient(app) as client:
yield client
app.dependency_overrides.clear()
monkeypatch.undo()
def get_graph(_type="basic"):
"""Get a graph from a json file"""
if _type == "basic":
path = pytest.BASIC_EXAMPLE_PATH
elif _type == "complex":
path = pytest.COMPLEX_EXAMPLE_PATH
elif _type == "openapi":
path = pytest.OPENAPI_EXAMPLE_PATH
with open(path, "r") as f:
flow_graph = json.load(f)
data_graph = flow_graph["data"]
nodes = data_graph["nodes"]
edges = data_graph["edges"]
return Graph(nodes, edges)
@pytest.fixture
def basic_graph_data():
with open(pytest.BASIC_EXAMPLE_PATH, "r") as f:
return json.load(f)
@pytest.fixture
def basic_graph():
return get_graph()
@pytest.fixture
def complex_graph():
return get_graph("complex")
@pytest.fixture
def openapi_graph():
return get_graph("openapi")
@pytest.fixture
def json_flow():
with open(pytest.BASIC_EXAMPLE_PATH, "r") as f:
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:
return f.read()
@pytest.fixture
def json_simple_api_test():
with open(pytest.SIMPLE_API_TEST, "r") as f:
return f.read()
@pytest.fixture
def json_vector_store():
with open(pytest.VECTOR_STORE_PATH, "r") as f:
return f.read()
@pytest.fixture
def json_webhook_test():
with open(pytest.WEBHOOK_TEST, "r") as f:
return f.read()
@pytest.fixture(name="client", autouse=True)
def client_fixture(session: Session, monkeypatch, request, load_flows_dir):
# Set the database url to a test database
if "noclient" in request.keywords:
yield
else:
db_dir = tempfile.mkdtemp()
db_path = Path(db_dir) / "test.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
if "load_flows" in request.keywords:
shutil.copyfile(
pytest.BASIC_EXAMPLE_PATH, os.path.join(load_flows_dir, "c54f9130-f2fa-4a3e-b22a-3856d946351b.json")
)
monkeypatch.setenv("LANGFLOW_LOAD_FLOWS_PATH", load_flows_dir)
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "true")
from langflow.main import create_app
app = create_app()
# app.dependency_overrides[get_session] = get_session_override
with TestClient(app) as client:
yield client
# app.dependency_overrides.clear()
monkeypatch.undo()
# clear the temp db
with suppress(FileNotFoundError):
db_path.unlink()
# create a fixture for session_getter above
@pytest.fixture(name="session_getter")
def session_getter_fixture(client):
@contextmanager
def blank_session_getter(db_service: "DatabaseService"):
with Session(db_service.engine) as session:
yield session
yield blank_session_getter
@pytest.fixture
def runner():
return CliRunner()
@pytest.fixture
def test_user(client):
user_data = UserCreate(
username="testuser",
password="testpassword",
)
response = client.post("/api/v1/users", json=user_data.model_dump())
assert response.status_code == 201
return response.json()
@pytest.fixture(scope="function")
def active_user(client):
db_manager = get_db_service()
with session_getter(db_manager) as session:
user = User(
username="activeuser",
password=get_password_hash("testpassword"),
is_active=True,
is_superuser=False,
)
# check if user exists
if active_user := session.exec(select(User).where(User.username == user.username)).first():
return active_user
session.add(user)
session.commit()
session.refresh(user)
return user
@pytest.fixture
def logged_in_headers(client, active_user):
login_data = {"username": active_user.username, "password": "testpassword"}
response = client.post("/api/v1/login", data=login_data)
assert response.status_code == 200
tokens = response.json()
a_token = tokens["access_token"]
return {"Authorization": f"Bearer {a_token}"}
@pytest.fixture
def flow(client, json_flow: str, active_user):
from langflow.services.database.models.flow.model import FlowCreate
loaded_json = json.loads(json_flow)
flow_data = FlowCreate(name="test_flow", data=loaded_json.get("data"), user_id=active_user.id)
flow = Flow.model_validate(flow_data)
with session_getter(get_db_service()) as session:
session.add(flow)
session.commit()
session.refresh(flow)
return flow
@pytest.fixture
def json_chat_input():
with open(pytest.CHAT_INPUT, "r") as f:
return f.read()
@pytest.fixture
def json_two_outputs():
with open(pytest.TWO_OUTPUTS, "r") as f:
return f.read()
@pytest.fixture
def added_flow_with_prompt_and_history(client, json_flow_with_prompt_and_history, logged_in_headers):
flow = orjson.loads(json_flow_with_prompt_and_history)
data = flow["data"]
flow = FlowCreate(name="Basic Chat", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
return response.json()
@pytest.fixture
def added_flow_chat_input(client, json_chat_input, logged_in_headers):
flow = orjson.loads(json_chat_input)
data = flow["data"]
flow = FlowCreate(name="Chat Input", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
return response.json()
@pytest.fixture
def added_flow_two_outputs(client, json_two_outputs, logged_in_headers):
flow = orjson.loads(json_two_outputs)
data = flow["data"]
flow = FlowCreate(name="Two Outputs", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
return response.json()
@pytest.fixture
def added_vector_store(client, json_vector_store, logged_in_headers):
vector_store = orjson.loads(json_vector_store)
data = vector_store["data"]
vector_store = FlowCreate(name="Vector Store", description="description", data=data)
response = client.post("api/v1/flows/", json=vector_store.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == vector_store.name
assert response.json()["data"] == vector_store.data
return response.json()
@pytest.fixture
def added_webhook_test(client, json_webhook_test, logged_in_headers):
webhook_test = orjson.loads(json_webhook_test)
data = webhook_test["data"]
webhook_test = FlowCreate(
name="Webhook Test", description="description", data=data, endpoint_name=webhook_test["endpoint_name"]
)
response = client.post("api/v1/flows/", json=webhook_test.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == webhook_test.name
assert response.json()["data"] == webhook_test.data
return response.json()
@pytest.fixture
def created_api_key(active_user):
hashed = get_password_hash("random_key")
api_key = ApiKey(
name="test_api_key",
user_id=active_user.id,
api_key="random_key",
hashed_api_key=hashed,
)
db_manager = get_db_service()
with session_getter(db_manager) as session:
if existing_api_key := session.exec(select(ApiKey).where(ApiKey.api_key == api_key.api_key)).first():
return existing_api_key
session.add(api_key)
session.commit()
session.refresh(api_key)
return api_key
@pytest.fixture(name="simple_api_test")
def get_simple_api_test(client, logged_in_headers, json_simple_api_test):
# Once the client is created, we can get the starter project
# Just create a new flow with the simple api test
flow = orjson.loads(json_simple_api_test)
data = flow["data"]
flow = FlowCreate(name="Simple API Test", data=data, description="Simple API Test")
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
return response.json()
@pytest.fixture(name="starter_project")
def get_starter_project(active_user):
# once the client is created, we can get the starter project
with session_getter(get_db_service()) as session:
flow = session.exec(
select(Flow)
.where(Flow.folder.has(Folder.name == STARTER_FOLDER_NAME))
.where(Flow.name == "Basic Prompting (Hello, World)")
).first()
if not flow:
raise ValueError("No starter project found")
new_flow_create = FlowCreate(
name=flow.name,
description=flow.description,
data=flow.data,
user_id=active_user.id,
)
new_flow = Flow.model_validate(new_flow_create, from_attributes=True)
session.add(new_flow)
session.commit()
session.refresh(new_flow)
new_flow_dict = new_flow.model_dump()
return new_flow_dict

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,918 @@
{
"name": "ChatInputTest",
"description": "",
"data": {
"nodes": [
{
"width": 384,
"height": 359,
"id": "PromptTemplate-IKKOx",
"type": "genericNode",
"position": {
"x": 880,
"y": 646.9375
},
"data": {
"type": "PromptTemplate",
"node": {
"template": {
"output_parser": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "output_parser",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BaseOutputParser",
"list": false
},
"input_variables": {
"required": true,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "input_variables",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": true,
"value": [
"input"
]
},
"partial_variables": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "partial_variables",
"advanced": false,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"template": {
"required": true,
"placeholder": "",
"show": true,
"multiline": true,
"password": false,
"name": "template",
"advanced": false,
"dynamic": false,
"info": "",
"type": "prompt",
"list": false,
"value": "Input: {input}\nAI:"
},
"template_format": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": "f-string",
"password": false,
"name": "template_format",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"validate_template": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": true,
"password": false,
"name": "validate_template",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"_type": "PromptTemplate",
"input": {
"required": false,
"placeholder": "",
"show": true,
"multiline": true,
"value": "",
"password": false,
"name": "input",
"display_name": "input",
"advanced": false,
"input_types": [
"Document",
"BaseOutputParser",
"str"
],
"dynamic": false,
"info": "",
"type": "str",
"list": false
}
},
"description": "A prompt template for a language model.",
"base_classes": [
"BasePromptTemplate",
"PromptTemplate",
"StringPromptTemplate"
],
"name": "",
"display_name": "PromptTemplate",
"documentation": "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/",
"custom_fields": {
"": [
"input"
],
"template": [
"input"
]
},
"output_types": [],
"field_formatters": {
"formatters": {
"openai_api_key": {}
},
"base_formatters": {
"kwargs": {},
"optional": {},
"list": {},
"dict": {},
"union": {},
"multiline": {},
"show": {},
"password": {},
"default": {},
"headers": {},
"dict_code_file": {},
"model_fields": {
"MODEL_DICT": {
"OpenAI": [
"text-davinci-003",
"text-davinci-002",
"text-curie-001",
"text-babbage-001",
"text-ada-001"
],
"ChatOpenAI": [
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k-0613",
"gpt-3.5-turbo-16k",
"gpt-4-0613",
"gpt-4-32k-0613",
"gpt-4",
"gpt-4-32k"
],
"Anthropic": [
"claude-v1",
"claude-v1-100k",
"claude-instant-v1",
"claude-instant-v1-100k",
"claude-v1.3",
"claude-v1.3-100k",
"claude-v1.2",
"claude-v1.0",
"claude-instant-v1.1",
"claude-instant-v1.1-100k",
"claude-instant-v1.0"
],
"ChatAnthropic": [
"claude-v1",
"claude-v1-100k",
"claude-instant-v1",
"claude-instant-v1-100k",
"claude-v1.3",
"claude-v1.3-100k",
"claude-v1.2",
"claude-v1.0",
"claude-instant-v1.1",
"claude-instant-v1.1-100k",
"claude-instant-v1.0"
]
}
}
}
},
"beta": false,
"error": null
},
"id": "PromptTemplate-IKKOx"
},
"selected": false,
"positionAbsolute": {
"x": 880,
"y": 646.9375
},
"dragging": false
},
{
"width": 384,
"height": 307,
"id": "LLMChain-e2dhN",
"type": "genericNode",
"position": {
"x": 1449.330344958542,
"y": 880.1760221487797
},
"data": {
"type": "LLMChain",
"node": {
"template": {
"callbacks": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "callbacks",
"advanced": false,
"dynamic": false,
"info": "",
"type": "langchain.callbacks.base.BaseCallbackHandler",
"list": true
},
"llm": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "llm",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BaseLanguageModel",
"list": false
},
"memory": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "memory",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BaseMemory",
"list": false
},
"output_parser": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "output_parser",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BaseLLMOutputParser",
"list": false
},
"prompt": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "prompt",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BasePromptTemplate",
"list": false
},
"llm_kwargs": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "llm_kwargs",
"advanced": false,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"metadata": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "metadata",
"advanced": false,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"output_key": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"value": "text",
"password": false,
"name": "output_key",
"advanced": true,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"return_final_only": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": true,
"password": false,
"name": "return_final_only",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"tags": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "tags",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": true
},
"verbose": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "verbose",
"advanced": true,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"_type": "LLMChain"
},
"description": "Chain to run queries against LLMs.",
"base_classes": [
"Chain",
"LLMChain",
"function",
"Text"
],
"display_name": "LLMChain",
"custom_fields": {},
"output_types": [],
"documentation": "https://python.langchain.com/docs/modules/chains/foundational/llm_chain",
"beta": false,
"error": null
},
"id": "LLMChain-e2dhN"
},
"positionAbsolute": {
"x": 1449.330344958542,
"y": 880.1760221487797
}
},
{
"width": 384,
"height": 621,
"id": "ChatOpenAI-2I57f",
"type": "genericNode",
"position": {
"x": 393.3551923753797,
"y": 1061.025177453298
},
"data": {
"type": "ChatOpenAI",
"node": {
"template": {
"callbacks": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "callbacks",
"advanced": false,
"dynamic": false,
"info": "",
"type": "langchain.callbacks.base.BaseCallbackHandler",
"list": true
},
"cache": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "cache",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"client": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "client",
"advanced": false,
"dynamic": false,
"info": "",
"type": "Any",
"list": false
},
"max_retries": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 6,
"password": false,
"name": "max_retries",
"advanced": false,
"dynamic": false,
"info": "",
"type": "int",
"list": false
},
"max_tokens": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": true,
"name": "max_tokens",
"advanced": false,
"dynamic": false,
"info": "",
"type": "int",
"list": false,
"value": ""
},
"metadata": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "metadata",
"advanced": false,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"model_kwargs": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "model_kwargs",
"advanced": true,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"model_name": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "gpt-3.5-turbo-0613",
"password": false,
"options": [
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k-0613",
"gpt-3.5-turbo-16k",
"gpt-4-0613",
"gpt-4-32k-0613",
"gpt-4",
"gpt-4-32k"
],
"name": "model_name",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": true
},
"n": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 1,
"password": false,
"name": "n",
"advanced": false,
"dynamic": false,
"info": "",
"type": "int",
"list": false
},
"openai_api_base": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "openai_api_base",
"display_name": "OpenAI API Base",
"advanced": false,
"dynamic": false,
"info": "\nThe base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\n\nYou can change this to use other APIs like JinaChat, LocalAI and Prem.\n",
"type": "str",
"list": false
},
"openai_api_key": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "",
"password": true,
"name": "openai_api_key",
"display_name": "OpenAI API Key",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"openai_organization": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "openai_organization",
"display_name": "OpenAI Organization",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"openai_proxy": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "openai_proxy",
"display_name": "OpenAI Proxy",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"request_timeout": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "request_timeout",
"advanced": false,
"dynamic": false,
"info": "",
"type": "float",
"list": false
},
"streaming": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "streaming",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"tags": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "tags",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": true
},
"temperature": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": 0.7,
"password": false,
"name": "temperature",
"advanced": false,
"dynamic": false,
"info": "",
"type": "float",
"list": false
},
"tiktoken_model_name": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "tiktoken_model_name",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"verbose": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "verbose",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"_type": "ChatOpenAI"
},
"description": "`OpenAI` Chat large language models API.",
"base_classes": [
"BaseChatModel",
"ChatOpenAI",
"BaseLanguageModel",
"BaseLLM"
],
"display_name": "ChatOpenAI",
"custom_fields": {},
"output_types": [],
"documentation": "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/openai",
"beta": false,
"error": null
},
"id": "ChatOpenAI-2I57f"
},
"selected": false,
"positionAbsolute": {
"x": 393.3551923753797,
"y": 1061.025177453298
},
"dragging": false
},
{
"width": 384,
"height": 359,
"id": "ChatInput-207IY",
"type": "genericNode",
"position": {
"x": 415.1018926651509,
"y": 506.62736462360317
},
"data": {
"type": "ChatInput",
"node": {
"template": {
"code": {
"dynamic": true,
"required": true,
"placeholder": "",
"show": false,
"multiline": true,
"value": "from typing import Optional\nfrom langflow.custom import CustomComponent\n\n\nclass ChatInput(CustomComponent):\n display_name = \"Chat Input\"\n\n def build(self, message: Optional[str] = \"\") -> str:\n return message\n",
"password": false,
"name": "code",
"advanced": false,
"type": "code",
"list": false
},
"_type": "CustomComponent",
"message": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "",
"password": false,
"name": "message",
"display_name": "message",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
}
},
"description": "Used to get user input from the chat.",
"base_classes": [
"str"
],
"display_name": "Chat Input",
"custom_fields": {
"message": null
},
"output_types": [
"ChatInput"
],
"documentation": "",
"beta": true,
"error": null
},
"id": "ChatInput-207IY"
},
"positionAbsolute": {
"x": 415.1018926651509,
"y": 506.62736462360317
}
},
{
"width": 384,
"height": 389,
"id": "ChatOutput-1jlJy",
"type": "genericNode",
"position": {
"x": 2002.8008888732943,
"y": 926.1397178702218
},
"data": {
"type": "ChatOutput",
"node": {
"template": {
"code": {
"dynamic": true,
"required": true,
"placeholder": "",
"show": true,
"multiline": true,
"value": "from typing import Optional, Text\nfrom langflow.api.v1.schemas import ChatMessage\nfrom langflow.services.utils import get_chat_manager\nfrom langflow.custom import CustomComponent\nfrom anyio.from_thread import start_blocking_portal\nfrom loguru import logger\n\n\nclass ChatOutput(CustomComponent):\n display_name = \"Chat Output\"\n description = \"Used to send a message to the chat.\"\n\n field_config = {\n \"code\": {\n \"show\": False,\n }\n }\n\n def build_config(self):\n return {\"message\": {\"input_types\": [\"Text\"]}}\n\n def build(self, message: Optional[Text], is_ai: bool = False) -> Text:\n if not message:\n return \"\"\n try:\n chat_manager = get_chat_manager()\n chat_message = ChatMessage(message=message, is_bot=is_ai)\n # send_message is a coroutine\n # run in a thread safe manner\n with start_blocking_portal() as portal:\n portal.call(chat_manager.send_message, chat_message)\n chat_manager.chat_history.add_message(\n chat_manager.cache_manager.current_client_id, chat_message\n )\n except Exception as exc:\n logger.exception(exc)\n logger.debug(f\"Error sending message to chat: {exc}\")\n self.repr_value = message\n return message\n",
"password": false,
"name": "code",
"advanced": false,
"type": "code",
"list": false
},
"_type": "CustomComponent",
"is_ai": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"value": true,
"password": false,
"name": "is_ai",
"display_name": "is_ai",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"message": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "message",
"display_name": "message",
"advanced": false,
"input_types": [
"Text"
],
"dynamic": false,
"info": "",
"type": "Text",
"list": false
}
},
"description": "Used to send a message to the chat.",
"base_classes": [
"str"
],
"display_name": "Chat Output",
"custom_fields": {
"is_ai": null,
"message": null
},
"output_types": [
"ChatOutput"
],
"documentation": "",
"beta": true,
"error": null
},
"id": "ChatOutput-1jlJy"
},
"selected": true,
"dragging": false,
"positionAbsolute": {
"x": 2002.8008888732943,
"y": 926.1397178702218
}
}
],
"edges": [
{
"source": "PromptTemplate-IKKOx",
"sourceHandle": "PromptTemplate|PromptTemplate-IKKOx|BasePromptTemplate|PromptTemplate|StringPromptTemplate",
"target": "LLMChain-e2dhN",
"targetHandle": "BasePromptTemplate|prompt|LLMChain-e2dhN",
"style": {
"stroke": "#555"
},
"className": "",
"animated": false,
"id": "reactflow__edge-PromptTemplate-IKKOxPromptTemplate|PromptTemplate-IKKOx|StringPromptTemplate|BasePromptTemplate|PromptTemplate-LLMChain-e2dhNBasePromptTemplate|prompt|LLMChain-e2dhN"
},
{
"source": "ChatOpenAI-2I57f",
"sourceHandle": "ChatOpenAI|ChatOpenAI-2I57f|BaseChatModel|ChatOpenAI|BaseLanguageModel|BaseLLM",
"target": "LLMChain-e2dhN",
"targetHandle": "BaseLanguageModel|llm|LLMChain-e2dhN",
"style": {
"stroke": "#555"
},
"className": "",
"animated": false,
"id": "reactflow__edge-ChatOpenAI-2I57fChatOpenAI|ChatOpenAI-2I57f|BaseChatModel|ChatOpenAI|BaseLanguageModel|BaseLLM-LLMChain-e2dhNBaseLanguageModel|llm|LLMChain-e2dhN"
},
{
"source": "ChatInput-207IY",
"sourceHandle": "ChatInput|ChatInput-207IY|str",
"target": "PromptTemplate-IKKOx",
"targetHandle": "Document;BaseOutputParser;str|input|PromptTemplate-IKKOx",
"style": {
"stroke": "#555"
},
"className": "",
"animated": false,
"id": "reactflow__edge-ChatInput-207IYChatInput|ChatInput-207IY|str-PromptTemplate-IKKOxDocument;BaseOutputParser;str|input|PromptTemplate-IKKOx"
},
{
"source": "LLMChain-e2dhN",
"sourceHandle": "LLMChain|LLMChain-e2dhN|Chain|LLMChain|function|Text",
"target": "ChatOutput-1jlJy",
"targetHandle": "Text|message|ChatOutput-1jlJy",
"style": {
"stroke": "#555"
},
"className": "stroke-foreground stroke-connection",
"animated": true,
"id": "reactflow__edge-LLMChain-e2dhNLLMChain|LLMChain-e2dhN|Chain|LLMChain|function|Text-ChatOutput-1jlJyText|message|ChatOutput-1jlJy"
}
],
"viewport": {
"x": -141.98308184453367,
"y": -104.98637616656356,
"zoom": 0.4788209787464315
}
},
"id": "b3388ab9-b5dc-4447-b560-79caef40faa5",
"user_id": "c65bfea3-3eea-4e71-8fc4-106238eb0583"
}

View file

@ -0,0 +1,445 @@
{
"description": "",
"name": "openapi",
"id": "1",
"data": {
"nodes": [
{
"width": 384,
"height": 311,
"id": "dndnode_19",
"type": "genericNode",
"position": {
"x": -207.85635949789724,
"y": -105.73915116823618
},
"data": {
"type": "JsonToolkit",
"node": {
"template": {
"spec": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "spec",
"type": "JsonSpec",
"list": false
},
"_type": "JsonToolkit"
},
"description": "Toolkit for interacting with a JSON spec.",
"base_classes": [
"BaseToolkit"
]
},
"id": "dndnode_19",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": -207.85635949789724,
"y": -105.73915116823618
},
"dragging": false
},
{
"width": 384,
"height": 351,
"id": "dndnode_32",
"type": "genericNode",
"position": {
"x": 745.308873444751,
"y": -37.007911201107675
},
"data": {
"type": "OpenAPIToolkit",
"node": {
"template": {
"json_agent": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "json_agent",
"type": "AgentExecutor",
"list": false
},
"requests_wrapper": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "requests_wrapper",
"type": "TextRequestsWrapper",
"list": false
},
"_type": "OpenAPIToolkit"
},
"description": "Toolkit for interacting with a OpenAPI api.",
"base_classes": [
"BaseToolkit"
]
},
"id": "dndnode_32",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": 745.308873444751,
"y": -37.007911201107675
},
"dragging": false
},
{
"width": 384,
"height": 351,
"id": "dndnode_33",
"type": "genericNode",
"position": {
"x": 281.30887344475104,
"y": 2.9920887988923255
},
"data": {
"type": "JsonAgent",
"node": {
"template": {
"toolkit": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "toolkit",
"type": "BaseToolkit",
"list": false
},
"llm": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "llm",
"type": "BaseLanguageModel",
"list": false
},
"_type": "JsonAgent"
},
"description": "Construct a json agent from an LLM and tools.",
"base_classes": [
"AgentExecutor"
]
},
"id": "dndnode_33",
"value": null
},
"selected": false,
"dragging": false,
"positionAbsolute": {
"x": 281.30887344475104,
"y": 2.9920887988923255
}
},
{
"width": 384,
"height": 349,
"id": "dndnode_34",
"type": "genericNode",
"position": {
"x": 301.30887344475104,
"y": 532.9920887988924
},
"data": {
"type": "TextRequestsWrapper",
"node": {
"template": {
"headers": {
"required": false,
"placeholder": "",
"show": true,
"multiline": true,
"value": "{'Authorization':\n 'Bearer <token>'}",
"password": false,
"name": "headers",
"type": "code",
"list": false
},
"aiosession": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "aiosession",
"type": "ClientSession",
"list": false
},
"_type": "TextRequestsWrapper"
},
"description": "Lightweight wrapper around requests library.",
"base_classes": [
"TextRequestsWrapper"
]
},
"id": "dndnode_34",
"value": null
},
"positionAbsolute": {
"x": 301.30887344475104,
"y": 532.9920887988924
}
},
{
"width": 384,
"height": 407,
"id": "dndnode_35",
"type": "genericNode",
"position": {
"x": -754.691126555249,
"y": -37.00791120110762
},
"data": {
"type": "JsonSpec",
"node": {
"template": {
"path": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"value": "api-with-examples.yaml",
"suffixes": [
".json",
".yaml",
".yml"
],
"password": false,
"name": "path",
"type": "file",
"list": false,
"fileTypes": [
"json",
"yaml",
"yml"
],
"file_path": "api-with-examples.yaml"
},
"max_value_length": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "4000",
"password": false,
"name": "max_value_length",
"type": "int",
"list": false
},
"_type": "JsonSpec"
},
"description": "",
"base_classes": [
"Tool",
"JsonSpec"
]
},
"id": "dndnode_35",
"value": null
},
"selected": false,
"dragging": false,
"positionAbsolute": {
"x": -754.691126555249,
"y": -37.00791120110762
}
},
{
"width": 384,
"height": 563,
"id": "dndnode_36",
"type": "genericNode",
"position": {
"x": -310.69112655524896,
"y": 514.9920887988924
},
"data": {
"type": "ChatOpenAI",
"node": {
"template": {
"cache": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "cache",
"type": "bool",
"list": false
},
"verbose": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "verbose",
"type": "bool",
"list": false
},
"client": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "client",
"type": "Any",
"list": false
},
"model_name": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "gpt-3.5-turbo",
"password": false,
"name": "model_name",
"type": "str",
"list": false
},
"model_kwargs": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "model_kwargs",
"type": "code",
"list": false
},
"openai_api_key": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "openai_api_key",
"type": "str",
"list": false,
"value": "sk-"
},
"max_retries": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 6,
"password": false,
"name": "max_retries",
"type": "int",
"list": false
},
"prefix_messages": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "prefix_messages",
"type": "Any",
"list": true
},
"streaming": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "streaming",
"type": "bool",
"list": false
},
"_type": "ChatOpenAI"
},
"description": "Wrapper around OpenAI Chat large language models.To use, you should have the ``openai`` python package installed, and theenvironment variable ``OPENAI_API_KEY`` set with your API key.Any parameters that are valid to be passed to the openai.create call can be passedin, even if not explicitly saved on this class.",
"base_classes": [
"BaseLanguageModel",
"BaseLLM"
]
},
"id": "dndnode_36",
"value": null
},
"selected": false,
"dragging": false,
"positionAbsolute": {
"x": -310.69112655524896,
"y": 514.9920887988924
}
}
],
"edges": [
{
"source": "dndnode_19",
"sourceHandle": "JsonToolkit|dndnode_19|BaseToolkit",
"target": "dndnode_33",
"targetHandle": "BaseToolkit|toolkit|dndnode_33",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_19JsonToolkit|dndnode_19|BaseToolkit-dndnode_33BaseToolkit|toolkit|dndnode_33",
"selected": false
},
{
"source": "dndnode_33",
"sourceHandle": "JsonAgent|dndnode_33|AgentExecutor",
"target": "dndnode_32",
"targetHandle": "AgentExecutor|json_agent|dndnode_32",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_33JsonAgent|dndnode_33|AgentExecutor-dndnode_32AgentExecutor|json_agent|dndnode_32",
"selected": false
},
{
"source": "dndnode_34",
"sourceHandle": "TextRequestsWrapper|dndnode_34|TextRequestsWrapper",
"target": "dndnode_32",
"targetHandle": "TextRequestsWrapper|requests_wrapper|dndnode_32",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_34RequestsWrapper|dndnode_34|TextRequestsWrapper-dndnode_32RequestsWrapper|requests_wrapper|dndnode_32",
"selected": false
},
{
"source": "dndnode_35",
"sourceHandle": "JsonSpec|dndnode_35|Tool|JsonSpec",
"target": "dndnode_19",
"targetHandle": "JsonSpec|spec|dndnode_19",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_35JsonSpec|dndnode_35|Tool|JsonSpec-dndnode_19JsonSpec|spec|dndnode_19",
"selected": false
},
{
"source": "dndnode_36",
"sourceHandle": "ChatOpenAI|dndnode_36|BaseLanguageModel|BaseLLM",
"target": "dndnode_33",
"targetHandle": "BaseLanguageModel|llm|dndnode_33",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_36OpenAIChat|dndnode_36|BaseLanguageModel|BaseLLM-dndnode_33BaseLanguageModel|llm|dndnode_33"
}
],
"viewport": {
"x": 0,
"y": 0,
"zoom": 1
}
},
"chat": [
{
"message": "test",
"isSend": true
}
]
}

View file

@ -0,0 +1,567 @@
{
"id": "e9380216-9300-41a1-bc35-7ee92fe4b30d",
"data": {
"nodes": [
{
"id": "ChatInput-irFJf",
"type": "genericNode",
"position": {
"x": 180,
"y": 200.296875
},
"data": {
"type": "ChatInput",
"node": {
"template": {
"_type": "Component",
"files": {
"trace_as_metadata": true,
"file_path": "",
"fileTypes": [
"txt",
"md",
"mdx",
"csv",
"json",
"yaml",
"yml",
"xml",
"html",
"htm",
"pdf",
"docx",
"py",
"sh",
"sql",
"js",
"ts",
"tsx",
"jpg",
"jpeg",
"png",
"bmp",
"image"
],
"list": true,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "files",
"display_name": "Files",
"advanced": true,
"dynamic": false,
"info": "Files to be sent with the message.",
"title_case": false,
"type": "file"
},
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.memory import store_message\nfrom langflow.schema.message import Message\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[\"Machine\", \"User\"],\n value=\"User\",\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=\"User\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\", display_name=\"Session ID\", info=\"Session ID for the message.\", advanced=True\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n\n if self.session_id and isinstance(message, Message) and isinstance(message.text, str):\n store_message(\n message,\n flow_id=self.graph.flow_id,\n )\n self.message.value = message\n\n self.status = message\n return message\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"input_value": {
"trace_as_input": true,
"multiline": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "input_value",
"display_name": "Text",
"advanced": false,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Message to be passed as input.",
"title_case": false,
"type": "str"
},
"sender": {
"trace_as_metadata": true,
"options": [
"Machine",
"User"
],
"required": false,
"placeholder": "",
"show": true,
"value": "User",
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
"dynamic": false,
"info": "Type of sender.",
"title_case": false,
"type": "str"
},
"sender_name": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "User",
"name": "sender_name",
"display_name": "Sender Name",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Name of the sender.",
"title_case": false,
"type": "str"
},
"session_id": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "session_id",
"display_name": "Session ID",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Session ID for the message.",
"title_case": false,
"type": "str"
},
"store_message": {
"trace_as_metadata": true,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": true,
"name": "store_message",
"display_name": "Store Messages",
"advanced": true,
"dynamic": false,
"info": "Store the message in the history.",
"title_case": false,
"type": "bool"
}
},
"description": "Get chat inputs from the Playground.",
"icon": "ChatInput",
"base_classes": [
"Message"
],
"display_name": "Chat Input",
"documentation": "",
"custom_fields": {},
"output_types": [],
"pinned": false,
"conditional_paths": [],
"frozen": false,
"outputs": [
{
"types": [
"Message"
],
"selected": "Message",
"name": "message",
"display_name": "Message",
"method": "message_response",
"value": "__UNDEFINED__",
"cache": true
}
],
"field_order": [
"input_value",
"store_message",
"sender",
"sender_name",
"session_id",
"files"
],
"beta": false,
"edited": false
},
"id": "ChatInput-irFJf",
"description": "Get chat inputs from the Playground.",
"display_name": "Chat Input"
},
"selected": false,
"width": 384,
"height": 309
},
{
"id": "TextInput-tcoZg",
"type": "genericNode",
"position": {
"x": 186,
"y": 549.296875
},
"data": {
"type": "TextInput",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from langflow.base.io.text import TextComponent\nfrom langflow.io import MessageTextInput, Output\nfrom langflow.schema.message import Message\n\n\nclass TextInputComponent(TextComponent):\n display_name = \"Text Input\"\n description = \"Get text inputs from the Playground.\"\n icon = \"type\"\n name = \"TextInput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Text to be passed as input.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"text_response\"),\n ]\n\n def text_response(self) -> Message:\n message = Message(\n text=self.input_value,\n )\n return message\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"input_value": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "AI",
"name": "input_value",
"display_name": "Text",
"advanced": false,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Text to be passed as input.",
"title_case": false,
"type": "str"
}
},
"description": "Get text inputs from the Playground.",
"icon": "type",
"base_classes": [
"Message"
],
"display_name": "Text Input",
"documentation": "",
"custom_fields": {},
"output_types": [],
"pinned": false,
"conditional_paths": [],
"frozen": false,
"outputs": [
{
"types": [
"Message"
],
"selected": "Message",
"name": "text",
"display_name": "Text",
"method": "text_response",
"value": "__UNDEFINED__",
"cache": true
}
],
"field_order": [
"input_value"
],
"beta": false,
"edited": false
},
"id": "TextInput-tcoZg"
},
"selected": true,
"width": 384,
"height": 309,
"positionAbsolute": {
"x": 186,
"y": 549.296875
},
"dragging": false
},
{
"id": "ChatOutput-dJRst",
"type": "genericNode",
"position": {
"x": 820,
"y": 224.296875
},
"data": {
"type": "ChatOutput",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.memory import store_message\nfrom langflow.schema.message import Message\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"ChatOutput\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[\"Machine\", \"User\"],\n value=\"Machine\",\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\", display_name=\"Sender Name\", info=\"Name of the sender.\", value=\"AI\", advanced=True\n ),\n MessageTextInput(\n name=\"session_id\", display_name=\"Session ID\", info=\"Session ID for the message.\", advanced=True\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and isinstance(message.text, str):\n store_message(\n message,\n flow_id=self.graph.flow_id,\n )\n self.message.value = message\n\n self.status = message\n return message\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"data_template": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "{text}",
"name": "data_template",
"display_name": "Data Template",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
"title_case": false,
"type": "str"
},
"input_value": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "input_value",
"display_name": "Text",
"advanced": false,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Message to be passed as output.",
"title_case": false,
"type": "str"
},
"sender": {
"trace_as_metadata": true,
"options": [
"Machine",
"User"
],
"required": false,
"placeholder": "",
"show": true,
"value": "Machine",
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
"dynamic": false,
"info": "Type of sender.",
"title_case": false,
"type": "str"
},
"sender_name": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "sender_name",
"display_name": "Sender Name",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Name of the sender.",
"title_case": false,
"type": "str"
},
"session_id": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "session_id",
"display_name": "Session ID",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Session ID for the message.",
"title_case": false,
"type": "str"
},
"store_message": {
"trace_as_metadata": true,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": true,
"name": "store_message",
"display_name": "Store Messages",
"advanced": true,
"dynamic": false,
"info": "Store the message in the history.",
"title_case": false,
"type": "bool"
}
},
"description": "Display a chat message in the Playground.",
"icon": "ChatOutput",
"base_classes": [
"Message"
],
"display_name": "Chat Output",
"documentation": "",
"custom_fields": {},
"output_types": [],
"pinned": false,
"conditional_paths": [],
"frozen": false,
"outputs": [
{
"types": [
"Message"
],
"selected": "Message",
"name": "message",
"display_name": "Message",
"method": "message_response",
"value": "__UNDEFINED__",
"cache": true
}
],
"field_order": [
"input_value",
"store_message",
"sender",
"sender_name",
"session_id",
"data_template"
],
"beta": false,
"edited": false
},
"id": "ChatOutput-dJRst",
"description": "Display a chat message in the Playground.",
"display_name": "Chat Output"
},
"selected": false,
"width": 384,
"height": 403,
"positionAbsolute": {
"x": 820,
"y": 224.296875
},
"dragging": false
}
],
"edges": [
{
"source": "ChatInput-irFJf",
"sourceHandle": "{œdataTypeœ:œChatInputœ,œidœ:œChatInput-irFJfœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}",
"target": "ChatOutput-dJRst",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-dJRstœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"data": {
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-dJRst",
"inputTypes": [
"Message"
],
"type": "str"
},
"sourceHandle": {
"dataType": "ChatInput",
"id": "ChatInput-irFJf",
"name": "message",
"output_types": [
"Message"
]
}
},
"id": "reactflow__edge-ChatInput-irFJf{œdataTypeœ:œChatInputœ,œidœ:œChatInput-irFJfœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-dJRst{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-dJRstœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"className": ""
},
{
"source": "TextInput-tcoZg",
"sourceHandle": "{œdataTypeœ:œTextInputœ,œidœ:œTextInput-tcoZgœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
"target": "ChatOutput-dJRst",
"targetHandle": "{œfieldNameœ:œsender_nameœ,œidœ:œChatOutput-dJRstœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"data": {
"targetHandle": {
"fieldName": "sender_name",
"id": "ChatOutput-dJRst",
"inputTypes": [
"Message"
],
"type": "str"
},
"sourceHandle": {
"dataType": "TextInput",
"id": "TextInput-tcoZg",
"name": "text",
"output_types": [
"Message"
]
}
},
"id": "reactflow__edge-TextInput-tcoZg{œdataTypeœ:œTextInputœ,œidœ:œTextInput-tcoZgœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-dJRst{œfieldNameœ:œsender_nameœ,œidœ:œChatOutput-dJRstœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"className": ""
}
],
"viewport": {
"x": -117,
"y": -69,
"zoom": 1
}
},
"description": "Nurture NLP Nodes Here.",
"name": "Simple API Test",
"last_tested_version": "1.0.9",
"endpoint_name": null,
"is_component": false
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,749 @@
{
"id": "b00c375e-c858-42b5-a352-561d3f40bd15",
"data": {
"nodes": [
{
"id": "CustomComponent-aF0h1",
"type": "genericNode",
"position": {
"x": 888.0012384532345,
"y": 272.41352212880344
},
"data": {
"type": "CustomComponent",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "# from langflow.field_typing import Data\nfrom langflow.custom import Component\nfrom langflow.io import StrInput\nfrom langflow.schema import Data\nfrom langflow.io import Output\nfrom pathlib import Path\nimport aiofiles\n\nclass CustomComponent(Component):\n display_name = \"Async Component\"\n description = \"Use as a template to create your own component.\"\n documentation: str = \"http://docs.langflow.org/components/custom\"\n icon = \"custom_components\"\n\n inputs = [\n StrInput(name=\"input_value\", display_name=\"Input Value\", value=\"Hello, World!\", input_types=[\"Data\"]),\n ]\n\n outputs = [\n Output(display_name=\"Output\", name=\"output\", method=\"build_output\"),\n ]\n\n async def build_output(self) -> Data:\n if isinstance(self.input_value, Data):\n data = self.input_value\n else:\n data = Data(value=self.input_value)\n \n if \"path\" in data:\n path = self.resolve_path(data.path)\n path_obj = Path(path)\n async with aiofiles.open(path, \"w\") as f:\n await f.write(data.model_dump())\n \n self.status = data\n return data",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"input_value": {
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "Hello, World!",
"name": "input_value",
"display_name": "Input Value",
"advanced": false,
"input_types": [
"Data"
],
"dynamic": false,
"info": "",
"title_case": false,
"type": "str"
}
},
"description": "Use as a template to create your own component.",
"icon": "custom_components",
"base_classes": [
"Data"
],
"display_name": "Custom Component",
"documentation": "http://docs.langflow.org/components/custom",
"custom_fields": {},
"output_types": [],
"pinned": false,
"conditional_paths": [],
"frozen": false,
"outputs": [
{
"types": [
"Data"
],
"selected": "Data",
"name": "output",
"display_name": "Output",
"method": "build_output",
"value": "__UNDEFINED__",
"cache": true,
"hidden": false
}
],
"field_order": [
"input_value"
],
"beta": false,
"edited": false
},
"id": "CustomComponent-aF0h1",
"description": "Use as a template to create your own component.",
"display_name": "Custom Component"
},
"selected": false,
"width": 384,
"height": 337,
"positionAbsolute": {
"x": 888.0012384532345,
"y": 272.41352212880344
},
"dragging": false
},
{
"id": "Webhook-BeRcd",
"type": "genericNode",
"position": {
"x": 418,
"y": 270.2890625
},
"data": {
"type": "Webhook",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "import json\n\nfrom langflow.custom import Component\nfrom langflow.io import MultilineInput, Output\nfrom langflow.schema import Data\n\n\nclass WebhookComponent(Component):\n display_name = \"Webhook Input\"\n description = \"Defines a webhook input for the flow.\"\n name = \"Webhook\"\n\n inputs = [\n MultilineInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"Use this field to quickly test the webhook component by providing a JSON payload.\",\n )\n ]\n outputs = [\n Output(display_name=\"Data\", name=\"output_data\", method=\"build_data\"),\n ]\n\n def build_data(self) -> Data:\n message: str | Data = \"\"\n if not self.data:\n self.status = \"No data provided.\"\n return Data(data={})\n try:\n body = json.loads(self.data or \"{}\")\n except json.JSONDecodeError:\n body = {\"payload\": self.data}\n message = f\"Invalid JSON payload. Please check the format.\\n\\n{self.data}\"\n data = Data(data=body)\n if not message:\n message = data\n self.status = message\n return data\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"data": {
"trace_as_input": true,
"multiline": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "{\"test\":1}",
"name": "data",
"display_name": "Data",
"advanced": false,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Use this field to quickly test the webhook component by providing a JSON payload.",
"title_case": false,
"type": "str"
}
},
"description": "Defines a webhook input for the flow.",
"base_classes": [
"Data"
],
"display_name": "Webhook Input",
"documentation": "",
"custom_fields": {},
"output_types": [],
"pinned": false,
"conditional_paths": [],
"frozen": false,
"outputs": [
{
"types": [
"Data"
],
"selected": "Data",
"name": "output_data",
"display_name": "Data",
"method": "build_data",
"value": "__UNDEFINED__",
"cache": true,
"hidden": false
}
],
"field_order": [
"data"
],
"beta": false,
"edited": false
},
"id": "Webhook-BeRcd",
"description": "Defines a webhook input for the flow.",
"display_name": "Webhook Input"
},
"selected": false,
"width": 384,
"height": 309,
"dragging": true,
"positionAbsolute": {
"x": 418,
"y": 270.2890625
}
},
{
"id": "ChatInput-QivBB",
"type": "genericNode",
"position": {
"x": 419.7235078147726,
"y": 646.9863203129902
},
"data": {
"type": "ChatInput",
"node": {
"template": {
"_type": "Component",
"files": {
"trace_as_metadata": true,
"file_path": "",
"fileTypes": [
"txt",
"md",
"mdx",
"csv",
"json",
"yaml",
"yml",
"xml",
"html",
"htm",
"pdf",
"docx",
"py",
"sh",
"sql",
"js",
"ts",
"tsx",
"jpg",
"jpeg",
"png",
"bmp",
"image"
],
"list": true,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "files",
"display_name": "Files",
"advanced": true,
"dynamic": false,
"info": "Files to be sent with the message.",
"title_case": false,
"type": "file"
},
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.schema.message import Message\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[\"Machine\", \"User\"],\n value=\"User\",\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=\"User\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\", display_name=\"Session ID\", info=\"Session ID for the message.\", advanced=True\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n if self.session_id and isinstance(message, Message) and isinstance(message.text, str):\n self.store_message(message)\n self.message.value = message\n\n self.status = message\n return message\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"input_value": {
"trace_as_input": true,
"multiline": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "Should not run",
"name": "input_value",
"display_name": "Text",
"advanced": false,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Message to be passed as input.",
"title_case": false,
"type": "str"
},
"sender": {
"trace_as_metadata": true,
"options": [
"Machine",
"User"
],
"required": false,
"placeholder": "",
"show": true,
"value": "User",
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
"dynamic": false,
"info": "Type of sender.",
"title_case": false,
"type": "str"
},
"sender_name": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "User",
"name": "sender_name",
"display_name": "Sender Name",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Name of the sender.",
"title_case": false,
"type": "str"
},
"session_id": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "session_id",
"display_name": "Session ID",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Session ID for the message.",
"title_case": false,
"type": "str"
}
},
"description": "Get chat inputs from the Playground.",
"icon": "ChatInput",
"base_classes": [
"Message"
],
"display_name": "Chat Input",
"documentation": "",
"custom_fields": {},
"output_types": [],
"pinned": false,
"conditional_paths": [],
"frozen": false,
"outputs": [
{
"types": [
"Message"
],
"selected": "Message",
"name": "message",
"display_name": "Message",
"method": "message_response",
"value": "__UNDEFINED__",
"cache": true,
"hidden": false
}
],
"field_order": [
"input_value",
"sender",
"sender_name",
"session_id",
"files"
],
"beta": false,
"edited": false
},
"id": "ChatInput-QivBB"
},
"selected": false,
"width": 384,
"height": 309,
"positionAbsolute": {
"x": 419.7235078147726,
"y": 646.9863203129902
},
"dragging": false
},
{
"id": "ChatOutput-mN2VY",
"type": "genericNode",
"position": {
"x": 884.7327265656637,
"y": 662.4287265670896
},
"data": {
"type": "ChatOutput",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"ChatOutput\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[\"Machine\", \"User\"],\n value=\"Machine\",\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\", display_name=\"Sender Name\", info=\"Name of the sender.\", value=\"AI\", advanced=True\n ),\n MessageTextInput(\n name=\"session_id\", display_name=\"Session ID\", info=\"Session ID for the message.\", advanced=True\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if self.session_id and isinstance(message, Message) and isinstance(message.text, str):\n self.store_message(message)\n self.message.value = message\n\n self.status = message\n return message\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"data_template": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "{text}",
"name": "data_template",
"display_name": "Data Template",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
"title_case": false,
"type": "str"
},
"input_value": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "input_value",
"display_name": "Text",
"advanced": false,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Message to be passed as output.",
"title_case": false,
"type": "str"
},
"sender": {
"trace_as_metadata": true,
"options": [
"Machine",
"User"
],
"required": false,
"placeholder": "",
"show": true,
"value": "Machine",
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
"dynamic": false,
"info": "Type of sender.",
"title_case": false,
"type": "str"
},
"sender_name": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "AI",
"name": "sender_name",
"display_name": "Sender Name",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Name of the sender.",
"title_case": false,
"type": "str"
},
"session_id": {
"trace_as_input": true,
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "",
"name": "session_id",
"display_name": "Session ID",
"advanced": true,
"input_types": [
"Message"
],
"dynamic": false,
"info": "Session ID for the message.",
"title_case": false,
"type": "str"
}
},
"description": "Display a chat message in the Playground.",
"icon": "ChatOutput",
"base_classes": [
"Message"
],
"display_name": "Chat Output",
"documentation": "",
"custom_fields": {},
"output_types": [],
"pinned": false,
"conditional_paths": [],
"frozen": false,
"outputs": [
{
"types": [
"Message"
],
"selected": "Message",
"name": "message",
"display_name": "Message",
"method": "message_response",
"value": "__UNDEFINED__",
"cache": true
}
],
"field_order": [
"input_value",
"sender",
"sender_name",
"session_id",
"data_template"
],
"beta": false,
"edited": false
},
"id": "ChatOutput-mN2VY"
},
"selected": false,
"width": 384,
"height": 309,
"positionAbsolute": {
"x": 884.7327265656637,
"y": 662.4287265670896
},
"dragging": false
},
{
"id": "CustomComponent-Ntw7h",
"type": "genericNode",
"position": {
"x": 1396.7134608749789,
"y": 284.91367968123217
},
"data": {
"type": "CustomComponent",
"node": {
"template": {
"_type": "Component",
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "# from langflow.field_typing import Data\nfrom langflow.custom import Component\nfrom langflow.io import StrInput\nfrom langflow.schema import Data\nfrom langflow.io import Output\nfrom pathlib import Path\nimport httpx\nclass CustomComponent(Component):\n display_name = \"Async Component\"\n description = \"Use as a template to create your own component.\"\n documentation: str = \"http://docs.langflow.org/components/custom\"\n icon = \"custom_components\"\n\n inputs = [\n StrInput(name=\"input_value\", display_name=\"Input Value\", value=\"Hello, World!\", input_types=[\"Data\"]),\n ]\n\n outputs = [\n Output(display_name=\"Output\", name=\"output\", method=\"build_output\"),\n ]\n\n async def build_output(self) -> Data:\n async with httpx.AsyncClient() as client:\n response = await client.get(\"https://www.google.com\")\n response.raise_for_status()\n return Data(response=response.text)",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"input_value": {
"trace_as_metadata": true,
"load_from_db": false,
"list": false,
"required": false,
"placeholder": "",
"show": true,
"value": "Hello, World!",
"name": "input_value",
"display_name": "Input Value",
"advanced": false,
"input_types": [
"Data"
],
"dynamic": false,
"info": "",
"title_case": false,
"type": "str"
}
},
"description": "Use as a template to create your own component.",
"icon": "custom_components",
"base_classes": [],
"display_name": "Custom Component",
"documentation": "http://docs.langflow.org/components/custom",
"custom_fields": {},
"output_types": [],
"pinned": false,
"conditional_paths": [],
"frozen": false,
"outputs": [
{
"types": [],
"name": "output",
"display_name": "Output",
"method": "build_output",
"value": "__UNDEFINED__",
"cache": true
}
],
"field_order": [
"input_value"
],
"beta": false,
"edited": true
},
"id": "CustomComponent-Ntw7h",
"description": "Use as a template to create your own component.",
"display_name": "Custom Component"
},
"selected": true,
"width": 384,
"height": 337,
"positionAbsolute": {
"x": 1396.7134608749789,
"y": 284.91367968123217
},
"dragging": false
}
],
"edges": [
{
"source": "Webhook-BeRcd",
"sourceHandle": "{œdataTypeœ:œWebhookœ,œidœ:œWebhook-BeRcdœ,œnameœ:œoutput_dataœ,œoutput_typesœ:[œDataœ]}",
"target": "CustomComponent-aF0h1",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œCustomComponent-aF0h1œ,œinputTypesœ:[œDataœ],œtypeœ:œstrœ}",
"data": {
"targetHandle": {
"fieldName": "input_value",
"id": "CustomComponent-aF0h1",
"inputTypes": [
"Data"
],
"type": "str"
},
"sourceHandle": {
"dataType": "Webhook",
"id": "Webhook-BeRcd",
"name": "output_data",
"output_types": [
"Data"
]
}
},
"id": "reactflow__edge-Webhook-BeRcd{œdataTypeœ:œWebhookœ,œidœ:œWebhook-BeRcdœ,œnameœ:œoutput_dataœ,œoutput_typesœ:[œDataœ]}-CustomComponent-aF0h1{œfieldNameœ:œinput_valueœ,œidœ:œCustomComponent-aF0h1œ,œinputTypesœ:[œDataœ],œtypeœ:œstrœ}",
"className": ""
},
{
"source": "ChatInput-QivBB",
"sourceHandle": "{œdataTypeœ:œChatInputœ,œidœ:œChatInput-QivBBœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}",
"target": "ChatOutput-mN2VY",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-mN2VYœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"data": {
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-mN2VY",
"inputTypes": [
"Message"
],
"type": "str"
},
"sourceHandle": {
"dataType": "ChatInput",
"id": "ChatInput-QivBB",
"name": "message",
"output_types": [
"Message"
]
}
},
"id": "reactflow__edge-ChatInput-QivBB{œdataTypeœ:œChatInputœ,œidœ:œChatInput-QivBBœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-mN2VY{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-mN2VYœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"className": ""
},
{
"source": "CustomComponent-aF0h1",
"sourceHandle": "{œdataTypeœ:œCustomComponentœ,œidœ:œCustomComponent-aF0h1œ,œnameœ:œoutputœ,œoutput_typesœ:[œDataœ]}",
"target": "CustomComponent-Ntw7h",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œCustomComponent-Ntw7hœ,œinputTypesœ:[œDataœ],œtypeœ:œstrœ}",
"data": {
"targetHandle": {
"fieldName": "input_value",
"id": "CustomComponent-Ntw7h",
"inputTypes": [
"Data"
],
"type": "str"
},
"sourceHandle": {
"dataType": "CustomComponent",
"id": "CustomComponent-aF0h1",
"name": "output",
"output_types": [
"Data"
]
}
},
"id": "reactflow__edge-CustomComponent-aF0h1{œdataTypeœ:œCustomComponentœ,œidœ:œCustomComponent-aF0h1œ,œnameœ:œoutputœ,œoutput_typesœ:[œDataœ]}-CustomComponent-Ntw7h{œfieldNameœ:œinput_valueœ,œidœ:œCustomComponent-Ntw7hœ,œinputTypesœ:[œDataœ],œtypeœ:œstrœ}"
}
],
"viewport": {
"x": -7.6743264594028915,
"y": 186.6544574916296,
"zoom": 0.520510798842055
}
},
"description": "The Power of Language at Your Fingertips.",
"name": "Webhook Test",
"last_tested_version": "1.0.7",
"endpoint_name": "webhook-test",
"is_component": false
}

View file

@ -0,0 +1,510 @@
{
"description": "",
"name": "BasicExample",
"id": "a53f9130-f2fa-4a3e-b22a-3856d946351a",
"data": {
"nodes": [
{
"width": 384,
"height": 267,
"id": "dndnode_81",
"type": "genericNode",
"position": {
"x": 1030,
"y": 694
},
"data": {
"type": "TimeTravelGuideChain",
"node": {
"template": {
"llm": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "llm",
"advanced": false,
"type": "BaseLanguageModel",
"list": false
},
"memory": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "memory",
"advanced": false,
"type": "BaseChatMemory",
"list": false
},
"_type": "TimeTravelGuideChain"
},
"description": "Time travel guide chain to be used in the flow.",
"base_classes": [
"LLMChain",
"BaseCustomChain",
"TimeTravelGuideChain",
"Chain",
"ConversationChain"
]
},
"id": "dndnode_81",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": 1030,
"y": 694
},
"dragging": false
},
{
"width": 384,
"height": 597,
"id": "dndnode_82",
"type": "genericNode",
"position": {
"x": 520,
"y": 732
},
"data": {
"type": "OpenAI",
"node": {
"template": {
"cache": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "cache",
"advanced": false,
"type": "bool",
"list": false
},
"verbose": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "verbose",
"advanced": false,
"type": "bool",
"list": false
},
"callbacks": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "callbacks",
"advanced": false,
"type": "langchain.callbacks.base.BaseCallbackHandler",
"list": true
},
"client": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "client",
"advanced": false,
"type": "Any",
"list": false
},
"model_name": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "text-davinci-003",
"password": false,
"options": [
"text-davinci-003",
"text-davinci-002",
"text-curie-001",
"text-babbage-001",
"text-ada-001"
],
"name": "model_name",
"advanced": false,
"type": "str",
"list": true
},
"temperature": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": 0.7,
"password": false,
"name": "temperature",
"advanced": false,
"type": "float",
"list": false
},
"max_tokens": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": 256,
"password": true,
"name": "max_tokens",
"advanced": false,
"type": "int",
"list": false
},
"top_p": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 1,
"password": false,
"name": "top_p",
"advanced": false,
"type": "float",
"list": false
},
"frequency_penalty": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 0,
"password": false,
"name": "frequency_penalty",
"advanced": false,
"type": "float",
"list": false
},
"presence_penalty": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 0,
"password": false,
"name": "presence_penalty",
"advanced": false,
"type": "float",
"list": false
},
"n": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 1,
"password": false,
"name": "n",
"advanced": false,
"type": "int",
"list": false
},
"best_of": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 1,
"password": false,
"name": "best_of",
"advanced": false,
"type": "int",
"list": false
},
"model_kwargs": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "model_kwargs",
"advanced": true,
"type": "code",
"list": false
},
"openai_api_key": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": null,
"password": true,
"name": "openai_api_key",
"display_name": "OpenAI API Key",
"advanced": false,
"type": "str",
"list": false
},
"openai_api_base": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": true,
"name": "openai_api_base",
"advanced": false,
"type": "str",
"list": false
},
"openai_organization": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "openai_organization",
"advanced": false,
"type": "str",
"list": false
},
"batch_size": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 20,
"password": false,
"name": "batch_size",
"advanced": false,
"type": "int",
"list": false
},
"request_timeout": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "request_timeout",
"advanced": false,
"type": "float",
"list": false
},
"logit_bias": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "logit_bias",
"advanced": false,
"type": "code",
"list": false
},
"max_retries": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 6,
"password": false,
"name": "max_retries",
"advanced": false,
"type": "int",
"list": false
},
"streaming": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "streaming",
"advanced": false,
"type": "bool",
"list": false
},
"allowed_special": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": [],
"password": false,
"name": "allowed_special",
"advanced": false,
"type": "Literal'all'",
"list": true
},
"disallowed_special": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": "all",
"password": false,
"name": "disallowed_special",
"advanced": false,
"type": "Literal'all'",
"list": false
},
"_type": "OpenAI"
},
"description": "Wrapper around OpenAI large language models.",
"base_classes": [
"BaseLLM",
"OpenAI",
"BaseOpenAI",
"BaseLanguageModel"
]
},
"id": "dndnode_82",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": 520,
"y": 732
},
"dragging": false
},
{
"width": 384,
"height": 273,
"id": "dndnode_83",
"type": "genericNode",
"position": {
"x": 512,
"y": 402
},
"data": {
"type": "ConversationBufferMemory",
"node": {
"template": {
"chat_memory": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "chat_memory",
"advanced": false,
"type": "BaseChatMessageHistory",
"list": false
},
"output_key": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "output_key",
"advanced": false,
"type": "str",
"list": false
},
"input_key": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "input_key",
"advanced": false,
"type": "str",
"list": false
},
"return_messages": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "return_messages",
"advanced": false,
"type": "bool",
"list": false
},
"human_prefix": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": "Human",
"password": false,
"name": "human_prefix",
"advanced": false,
"type": "str",
"list": false
},
"ai_prefix": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": "AI",
"password": false,
"name": "ai_prefix",
"advanced": false,
"type": "str",
"list": false
},
"memory_key": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "history",
"password": false,
"name": "memory_key",
"advanced": false,
"type": "str",
"list": false
},
"_type": "ConversationBufferMemory"
},
"description": "Buffer for storing conversation memory.",
"base_classes": [
"ConversationBufferMemory",
"BaseChatMemory",
"BaseMemory"
]
},
"id": "dndnode_83",
"value": null
},
"selected": false,
"positionAbsolute": {
"x": 512,
"y": 402
},
"dragging": false
}
],
"edges": [
{
"source": "dndnode_82",
"sourceHandle": "OpenAI|dndnode_82|BaseLLM|OpenAI|BaseOpenAI|BaseLanguageModel",
"target": "dndnode_81",
"targetHandle": "BaseLanguageModel|llm|dndnode_81",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_82OpenAI|dndnode_82|BaseLLM|OpenAI|BaseOpenAI|BaseLanguageModel-dndnode_81BaseLanguageModel|llm|dndnode_81",
"selected": false
},
{
"source": "dndnode_83",
"sourceHandle": "ConversationBufferMemory|dndnode_83|ConversationBufferMemory|BaseChatMemory|BaseMemory",
"target": "dndnode_81",
"targetHandle": "BaseChatMemory|memory|dndnode_81",
"className": "animate-pulse",
"id": "reactflow__edge-dndnode_83ConversationBufferMemory|dndnode_83|ConversationBufferMemory|BaseChatMemory|BaseMemory-dndnode_81BaseChatMemory|memory|dndnode_81"
}
],
"viewport": {
"x": 1,
"y": 0,
"zoom": 0.5
}
},
"last_tested_version": "0.6.2"
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
import random
from langflow.custom import CustomComponent
class TestComponent(CustomComponent):
def refresh_values(self):
# This is a function that will be called every time the component is updated
# and should return a list of random strings
return [f"Random {random.randint(1, 100)}" for _ in range(5)]
def build_config(self):
return {"param": {"display_name": "Param", "options": self.refresh_values}}
def build(self, param: int):
return param

View file

@ -0,0 +1,20 @@
from langflow.custom import Component
from langflow.inputs.inputs import IntInput, MessageTextInput
from langflow.template.field.base import Output
class MultipleOutputsComponent(Component):
inputs = [
MessageTextInput(display_name="Input", name="input"),
IntInput(display_name="Number", name="number"),
]
outputs = [
Output(display_name="Certain Output", name="certain_output", method="certain_output"),
Output(display_name="Other Output", name="other_output", method="other_output"),
]
def certain_output(self) -> str:
return f"This is my string input: {self.input}"
def other_output(self) -> int:
return f"This is my int input multiplied by 2: {self.number * 2}"

View file

@ -0,0 +1,22 @@
from random import randint
from langflow.custom import Component
from langflow.inputs.inputs import IntInput, MessageTextInput
from langflow.template.field.base import Output
class MultipleOutputsComponent(Component):
inputs = [
MessageTextInput(display_name="Input", name="input"),
IntInput(display_name="Number", name="number"),
]
outputs = [
Output(display_name="Certain Output", name="certain_output", method="certain_output"),
Output(display_name="Other Output", name="other_output", method="other_output"),
]
def certain_output(self) -> int:
return randint(0, self.number)
def other_output(self) -> int:
return self.certain_output()

View file

@ -0,0 +1,17 @@
import random
from langflow.custom import CustomComponent
from langflow.field_typing import Input
class TestComponent(CustomComponent):
def refresh_values(self):
# This is a function that will be called every time the component is updated
# and should return a list of random strings
return [f"Random {random.randint(1, 100)}" for _ in range(5)]
def build_config(self):
return {"param": Input(display_name="Param", options=self.refresh_values)}
def build(self, param: int):
return param

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

@ -0,0 +1,290 @@
import os
from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions
from langflow.components.embeddings.AstraVectorize import AstraVectorizeComponent
import pytest
from integration.utils import MockEmbeddings, check_env_vars
from langchain_core.documents import Document
# from langflow.components.memories.AstraDBMessageReader import AstraDBMessageReaderComponent
# from langflow.components.memories.AstraDBMessageWriter import AstraDBMessageWriterComponent
from langflow.components.vectorstores.AstraDB import AstraVectorStoreComponent
from langflow.schema.data import Data
COLLECTION = "test_basic"
SEARCH_COLLECTION = "test_search"
# MEMORY_COLLECTION = "test_memory"
VECTORIZE_COLLECTION = "test_vectorize"
VECTORIZE_COLLECTION_OPENAI = "test_vectorize_openai"
VECTORIZE_COLLECTION_OPENAI_WITH_AUTH = "test_vectorize_openai_auth"
@pytest.fixture()
def astra_fixture(request):
"""
Sets up the astra collection and cleans up after
"""
try:
from langchain_astradb import AstraDBVectorStore
except ImportError:
raise ImportError(
"Could not import langchain Astra DB integration package. Please install it with `pip install langchain-astradb`."
)
store = AstraDBVectorStore(
collection_name=request.param,
embedding=MockEmbeddings(),
api_endpoint=os.getenv("ASTRA_DB_API_ENDPOINT"),
token=os.getenv("ASTRA_DB_APPLICATION_TOKEN"),
)
yield
store.delete_collection()
@pytest.mark.skipif(
not check_env_vars("ASTRA_DB_APPLICATION_TOKEN", "ASTRA_DB_API_ENDPOINT"),
reason="missing astra env vars",
)
@pytest.mark.parametrize("astra_fixture", [COLLECTION], indirect=True)
def test_astra_setup(astra_fixture):
application_token = os.getenv("ASTRA_DB_APPLICATION_TOKEN")
api_endpoint = os.getenv("ASTRA_DB_API_ENDPOINT")
embedding = MockEmbeddings()
component = AstraVectorStoreComponent()
component.build(
token=application_token,
api_endpoint=api_endpoint,
collection_name=COLLECTION,
embedding=embedding,
)
component.build_vector_store()
@pytest.mark.skipif(
not check_env_vars("ASTRA_DB_APPLICATION_TOKEN", "ASTRA_DB_API_ENDPOINT"),
reason="missing astra env vars",
)
@pytest.mark.parametrize("astra_fixture", [SEARCH_COLLECTION], indirect=True)
def test_astra_embeds_and_search(astra_fixture):
application_token = os.getenv("ASTRA_DB_APPLICATION_TOKEN")
api_endpoint = os.getenv("ASTRA_DB_API_ENDPOINT")
embedding = MockEmbeddings()
documents = [Document(page_content="test1"), Document(page_content="test2")]
records = [Data.from_document(d) for d in documents]
component = AstraVectorStoreComponent()
component.build(
token=application_token,
api_endpoint=api_endpoint,
collection_name=SEARCH_COLLECTION,
embedding=embedding,
ingest_data=records,
search_input="test1",
number_of_results=1,
)
component.build_vector_store()
records = component.search_documents()
assert len(records) == 1
@pytest.mark.skipif(
not check_env_vars("ASTRA_DB_APPLICATION_TOKEN", "ASTRA_DB_API_ENDPOINT"),
reason="missing astra env vars",
)
def test_astra_vectorize():
store = None
try:
options = {"provider": "nvidia", "modelName": "NV-Embed-QA", "parameters": {}, "authentication": {}}
store = AstraDBVectorStore(
collection_name=VECTORIZE_COLLECTION,
api_endpoint=os.getenv("ASTRA_DB_API_ENDPOINT"),
token=os.getenv("ASTRA_DB_APPLICATION_TOKEN"),
collection_vector_service_options=CollectionVectorServiceOptions.from_dict(options),
)
application_token = os.getenv("ASTRA_DB_APPLICATION_TOKEN")
api_endpoint = os.getenv("ASTRA_DB_API_ENDPOINT")
documents = [Document(page_content="test1"), Document(page_content="test2")]
records = [Data.from_document(d) for d in documents]
vectorize = AstraVectorizeComponent()
vectorize.build(provider="NVIDIA", model_name="NV-Embed-QA")
vectorize_options = vectorize.build_options()
component = AstraVectorStoreComponent()
component.build(
token=application_token,
api_endpoint=api_endpoint,
collection_name=VECTORIZE_COLLECTION,
ingest_data=records,
embedding=vectorize_options,
search_input="test",
number_of_results=2,
)
component.build_vector_store()
records = component.search_documents()
assert len(records) == 2
finally:
if store is not None:
store.delete_collection()
@pytest.mark.skipif(
not check_env_vars("ASTRA_DB_APPLICATION_TOKEN", "ASTRA_DB_API_ENDPOINT", "OPENAI_API_KEY"),
reason="missing env vars",
)
def test_astra_vectorize_with_provider_api_key():
"""tests vectorize using an openai api key"""
store = None
try:
application_token = os.getenv("ASTRA_DB_APPLICATION_TOKEN")
api_endpoint = os.getenv("ASTRA_DB_API_ENDPOINT")
options = {"provider": "openai", "modelName": "text-embedding-3-small", "parameters": {}, "authentication": {}}
store = AstraDBVectorStore(
collection_name=VECTORIZE_COLLECTION_OPENAI,
api_endpoint=api_endpoint,
token=application_token,
collection_vector_service_options=CollectionVectorServiceOptions.from_dict(options),
collection_embedding_api_key=os.getenv("OPENAI_API_KEY"),
)
documents = [Document(page_content="test1"), Document(page_content="test2")]
records = [Data.from_document(d) for d in documents]
vectorize = AstraVectorizeComponent()
vectorize.build(
provider="OpenAI", model_name="text-embedding-3-small", provider_api_key=os.getenv("OPENAI_API_KEY")
)
vectorize_options = vectorize.build_options()
component = AstraVectorStoreComponent()
component.build(
token=application_token,
api_endpoint=api_endpoint,
collection_name=VECTORIZE_COLLECTION_OPENAI,
ingest_data=records,
embedding=vectorize_options,
search_input="test",
)
component.build_vector_store()
records = component.search_documents()
assert len(records) == 2
finally:
if store is not None:
store.delete_collection()
@pytest.mark.skipif(
not check_env_vars("ASTRA_DB_APPLICATION_TOKEN", "ASTRA_DB_API_ENDPOINT", "OPENAI_API_KEY"),
reason="missing env vars",
)
def test_astra_vectorize_passes_authentication():
"""tests vectorize using the authentication parameter"""
store = None
try:
application_token = os.getenv("ASTRA_DB_APPLICATION_TOKEN")
api_endpoint = os.getenv("ASTRA_DB_API_ENDPOINT")
options = {
"provider": "openai",
"modelName": "text-embedding-3-small",
"parameters": {},
"authentication": {"providerKey": "providerKey"},
}
store = AstraDBVectorStore(
collection_name=VECTORIZE_COLLECTION_OPENAI_WITH_AUTH,
api_endpoint=api_endpoint,
token=application_token,
collection_vector_service_options=CollectionVectorServiceOptions.from_dict(options),
)
documents = [Document(page_content="test1"), Document(page_content="test2")]
records = [Data.from_document(d) for d in documents]
vectorize = AstraVectorizeComponent()
vectorize.build(
provider="OpenAI", model_name="text-embedding-3-small", authentication={"providerKey": "providerKey"}
)
vectorize_options = vectorize.build_options()
component = AstraVectorStoreComponent()
component.build(
token=application_token,
api_endpoint=api_endpoint,
collection_name=VECTORIZE_COLLECTION_OPENAI_WITH_AUTH,
ingest_data=records,
embedding=vectorize_options,
search_input="test",
)
component.build_vector_store()
records = component.search_documents()
assert len(records) == 2
finally:
if store is not None:
store.delete_collection()
# @pytest.mark.skipif(
# not check_env_vars("ASTRA_DB_APPLICATION_TOKEN", "ASTRA_DB_API_ENDPOINT"),
# reason="missing astra env vars",
# )
# def test_astra_memory():
# application_token = os.getenv("ASTRA_DB_APPLICATION_TOKEN")
# api_endpoint = os.getenv("ASTRA_DB_API_ENDPOINT")
# writer = AstraDBMessageWriterComponent()
# reader = AstraDBMessageReaderComponent()
# input_value = Data.from_document(
# Document(
# page_content="memory1",
# metadata={"session_id": 1, "sender": "human", "sender_name": "Bob"},
# )
# )
# writer.build(
# input_value=input_value,
# session_id=1,
# token=application_token,
# api_endpoint=api_endpoint,
# collection_name=MEMORY_COLLECTION,
# )
# # verify reading w/ same session id pulls the same record
# records = reader.build(
# session_id=1,
# token=application_token,
# api_endpoint=api_endpoint,
# collection_name=MEMORY_COLLECTION,
# )
# assert len(records) == 1
# assert isinstance(records[0], Data)
# content = records[0].get_text()
# assert content == "memory1"
# # verify reading w/ different session id does not pull the same record
# records = reader.build(
# session_id=2,
# token=application_token,
# api_endpoint=api_endpoint,
# collection_name=MEMORY_COLLECTION,
# )
# assert len(records) == 0
# # Cleanup store - doing here rather than fixture (see https://github.com/langchain-ai/langchain-datastax/pull/36)
# try:
# from langchain_astradb import AstraDBVectorStore
# except ImportError:
# raise ImportError(
# "Could not import langchain Astra DB integration package. Please install it with `pip install langchain-astradb`."
# )
# store = AstraDBVectorStore(
# collection_name=MEMORY_COLLECTION,
# embedding=MockEmbeddings(),
# api_endpoint=api_endpoint,
# token=application_token,
# )
# store.delete_collection()

View file

@ -0,0 +1,88 @@
from uuid import uuid4
import pytest
from fastapi import status
from fastapi.testclient import TestClient
from langflow.graph.schema import RunOutputs
from langflow.initial_setup.setup import load_starter_projects
from langflow.load import run_flow_from_json
@pytest.mark.api_key_required
def test_run_flow_with_caching_success(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"]
headers = {"x-api-key": created_api_key.api_key}
payload = {
"input_value": "value1",
"input_type": "text",
"output_type": "text",
"tweaks": {"parameter_name": "value"},
"stream": False,
}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert "outputs" in data
assert "session_id" in data
@pytest.mark.api_key_required
def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key):
invalid_flow_id = uuid4()
headers = {"x-api-key": created_api_key.api_key}
payload = {"input_value": "", "input_type": "text", "output_type": "text", "tweaks": {}, "stream": False}
response = client.post(f"/api/v1/run/{invalid_flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND
data = response.json()
assert "detail" in data
assert f"Flow identifier {invalid_flow_id} not found" in data["detail"]
@pytest.mark.api_key_required
def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"]
headers = {"x-api-key": created_api_key.api_key}
payload = {"input_value": {"key": "value"}, "input_type": "text", "output_type": "text", "tweaks": {}}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
@pytest.mark.api_key_required
def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {
"input_value": "value1",
"input_type": "text",
"output_type": "text",
"tweaks": {"invalid_tweak": "value"},
}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK
@pytest.mark.api_key_required
def test_run_with_inputs_and_outputs(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {
"input_value": "value1",
"input_type": "text",
"output_type": "text",
"tweaks": {"parameter_name": "value"},
"stream": False,
}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text
@pytest.mark.noclient
@pytest.mark.api_key_required
def test_run_flow_from_json_object():
"""Test loading a flow from a json file and applying tweaks"""
_, projects = zip(*load_starter_projects())
project = [project for project in projects if "Basic Prompting" in project["name"]][0]
results = run_flow_from_json(project, input_value="test", fallback_to_env_vars=True)
assert results is not None
assert all(isinstance(result, RunOutputs) for result in results)

View file

@ -0,0 +1,35 @@
import os
from typing import List
from langflow.field_typing import Embeddings
def check_env_vars(*vars):
"""
Check if all specified environment variables are set.
Args:
*vars (str): The environment variables to check.
Returns:
bool: True if all environment variables are set, False otherwise.
"""
return all(os.getenv(var) for var in vars)
class MockEmbeddings(Embeddings):
def __init__(self):
self.embedded_documents = None
self.embedded_query = None
@staticmethod
def mock_embedding(text: str):
return [len(text) / 2, len(text) / 5, len(text) / 10]
def embed_documents(self, texts: List[str]) -> List[List[float]]:
self.embedded_documents = texts
return [self.mock_embedding(text) for text in texts]
def embed_query(self, text: str) -> List[float]:
self.embedded_query = text
return self.mock_embedding(text)

View file

@ -0,0 +1,125 @@
import random
import time
from pathlib import Path
import httpx
import orjson
from locust import FastHttpUser, between, task
from rich import print
class NameTest(FastHttpUser):
wait_time = between(1, 5)
with open("names.txt", "r") as file:
names = [line.strip() for line in file.readlines()]
headers: dict = {}
def poll_task(self, task_id, sleep_time=1):
while True:
with self.rest(
"GET",
f"/task/{task_id}",
name="task_status",
headers=self.headers,
) as response:
status = response.js.get("status")
print(f"Poll Response: {response.js}")
if status == "SUCCESS":
return response.js.get("result")
elif status in ["FAILURE", "REVOKED"]:
raise ValueError(f"Task failed with status: {status}")
time.sleep(sleep_time)
def process(self, name, flow_id, payload):
task_id = None
print(f"Processing {payload}")
with self.rest(
"POST",
f"/process/{flow_id}",
json=payload,
name="process",
headers=self.headers,
) as response:
print(response.js)
if response.status_code != 200:
response.failure("Process call failed")
raise ValueError("Process call failed")
task_id = response.js.get("id")
session_id = response.js.get("session_id")
assert task_id, "Inner Task ID not found"
assert task_id, "Task ID not found"
result = self.poll_task(task_id)
print(f"Result for {name}: {result}")
return result, session_id
@task
def send_name_and_check(self):
name = random.choice(self.names)
payload1 = {
"inputs": {"text": f"Hello, My name is {name}"},
"sync": False,
}
result1, session_id = self.process(name, self.flow_id, payload1)
payload2 = {
"inputs": {"text": "What is my name? Please, answer like this: Your name is <name>"},
"session_id": session_id,
"sync": False,
}
result2, session_id = self.process(name, self.flow_id, payload2)
assert f"Your name is {name}" in str(result2), "Name not found in response"
def on_start(self):
print("Starting")
login_data = {"username": "superuser", "password": "superuser"}
response = httpx.post(f"{self.host}/login", data=login_data)
print(response.json())
tokens = response.json()
print(tokens)
a_token = tokens["access_token"]
logged_in_headers = {"Authorization": f"Bearer {a_token}"}
print("Logged in")
with open(
Path(__file__).parent.parent / "data" / "BasicChatwithPromptandHistory.json",
"r",
) as f:
json_flow = f.read()
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow = {"name": "Flow 1", "description": "description", "data": data}
print("Creating flow")
# Make request to endpoint
response = httpx.post(
f"{self.host}/flows/",
json=flow,
headers=logged_in_headers,
)
self.flow_id = response.json()["id"]
print(f"Flow ID: {self.flow_id}")
# read all users
response = httpx.get(
f"{self.host}/users/",
headers=logged_in_headers,
)
print(response.json())
user_id = next(
(user["id"] for user in response.json()["users"] if user["username"] == "superuser"),
None,
)
# Create api key
response = httpx.post(
f"{self.host}/api_key/",
json={"user_id": user_id},
headers=logged_in_headers,
)
print(response.json())
self.headers["x-api-key"] = response.json()["api_key"]

View file

@ -0,0 +1,5 @@
Bob
Alice
John
Gabriel
Lily

View file

@ -0,0 +1,672 @@
import time
from uuid import UUID, uuid4
import pytest
from fastapi import status
from fastapi.testclient import TestClient
from langflow.custom.directory_reader.directory_reader import DirectoryReader
from langflow.services.deps import get_settings_service
def run_post(client, flow_id, headers, post_data):
response = client.post(
f"api/v1/process/{flow_id}",
headers=headers,
json=post_data,
)
assert response.status_code == 200, response.json()
return response.json()
# Helper function to poll task status
def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1):
for _ in range(max_attempts):
task_status_response = client.get(
href,
headers=headers,
)
if task_status_response.status_code == 200 and task_status_response.json()["status"] == "SUCCESS":
return task_status_response.json()
time.sleep(sleep_time)
return None # Return None if task did not complete in time
PROMPT_REQUEST = {
"name": "string",
"template": "string",
"frontend_node": {
"template": {},
"description": "string",
"base_classes": ["string"],
"name": "",
"display_name": "",
"documentation": "",
"custom_fields": {},
"output_types": [],
"field_formatters": {
"formatters": {"openai_api_key": {}},
"base_formatters": {
"kwargs": {},
"optional": {},
"list": {},
"dict": {},
"union": {},
"multiline": {},
"show": {},
"password": {},
"default": {},
"headers": {},
"dict_code_file": {},
"model_fields": {
"MODEL_DICT": {
"OpenAI": [
"text-davinci-003",
"text-davinci-002",
"text-curie-001",
"text-babbage-001",
"text-ada-001",
],
"ChatOpenAI": [
"gpt-4-turbo-preview",
"gpt-4-0125-preview",
"gpt-4-1106-preview",
"gpt-4-vision-preview",
"gpt-3.5-turbo-0125",
"gpt-3.5-turbo-1106",
],
"Anthropic": [
"claude-v1",
"claude-v1-100k",
"claude-instant-v1",
"claude-instant-v1-100k",
"claude-v1.3",
"claude-v1.3-100k",
"claude-v1.2",
"claude-v1.0",
"claude-instant-v1.1",
"claude-instant-v1.1-100k",
"claude-instant-v1.0",
],
"ChatAnthropic": [
"claude-v1",
"claude-v1-100k",
"claude-instant-v1",
"claude-instant-v1-100k",
"claude-v1.3",
"claude-v1.3-100k",
"claude-v1.2",
"claude-v1.0",
"claude-instant-v1.1",
"claude-instant-v1.1-100k",
"claude-instant-v1.0",
],
}
},
},
},
},
}
# def test_process_flow_invalid_api_key(client, flow, monkeypatch):
# # Mock de process_graph_cached
# from langflow.api.v1 import endpoints
# from langflow.services.database.models.api_key import crud
# settings_service = get_settings_service()
# settings_service.auth_settings.AUTO_LOGIN = False
# async def mock_process_graph_cached(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
# def mock_update_total_uses(*args, **kwargs):
# return created_api_key
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
# headers = {"x-api-key": "invalid_api_key"}
# post_data = {
# "inputs": {"key": "value"},
# "tweaks": None,
# "clear_cache": False,
# "session_id": None,
# }
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# assert response.status_code == 403
# assert response.json() == {"detail": "Invalid or missing API key"}
# def test_process_flow_invalid_id(client, monkeypatch, created_api_key):
# async def mock_process_graph_cached(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
# from langflow.api.v1 import endpoints
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
# api_key = created_api_key.api_key
# headers = {"x-api-key": api_key}
# post_data = {
# "inputs": {"key": "value"},
# "tweaks": None,
# "clear_cache": False,
# "session_id": None,
# }
# invalid_id = uuid.uuid4()
# response = client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data)
# assert response.status_code == 404
# assert f"Flow {invalid_id} not found" in response.json()["detail"]
# def test_process_flow_without_autologin(client, flow, monkeypatch, created_api_key):
# # Mock de process_graph_cached
# from langflow.api.v1 import endpoints
# from langflow.services.database.models.api_key import crud
# settings_service = get_settings_service()
# settings_service.auth_settings.AUTO_LOGIN = False
# async def mock_process_graph_cached(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
# def mock_process_graph_cached_task(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
# # The task function is ran like this:
# # if not self.use_celery:
# # return None, await task_func(*args, **kwargs)
# # if not hasattr(task_func, "apply"):
# # raise ValueError(f"Task function {task_func} does not have an apply method")
# # task = task_func.apply(args=args, kwargs=kwargs)
# # result = task.get()
# # return task.id, result
# # So we need to mock the task function to return a task object
# # and then mock the task object to return a result
# # maybe a named tuple would be better here
# task = namedtuple("task", ["id", "get"])
# mock_process_graph_cached_task.apply = lambda *args, **kwargs: task(
# id="task_id_mock", get=lambda: Result(result={}, session_id="session_id_mock")
# )
# def mock_update_total_uses(*args, **kwargs):
# return created_api_key
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
# monkeypatch.setattr(endpoints, "process_graph_cached_task", mock_process_graph_cached_task)
# api_key = created_api_key.api_key
# headers = {"x-api-key": api_key}
# # Dummy POST data
# post_data = {
# "inputs": {"input": "value"},
# "tweaks": None,
# "clear_cache": False,
# "session_id": None,
# }
# # Make the request to the FastAPI TestClient
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# # Check the response
# assert response.status_code == 200, response.json()
# assert response.json()["result"] == {}, response.json()
# assert response.json()["session_id"] == "session_id_mock", response.json()
# def test_process_flow_fails_autologin_off(client, flow, monkeypatch):
# # Mock de process_graph_cached
# from langflow.api.v1 import endpoints
# from langflow.services.database.models.api_key import crud
# settings_service = get_settings_service()
# settings_service.auth_settings.AUTO_LOGIN = False
# async def mock_process_graph_cached(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
# async def mock_update_total_uses(*args, **kwargs):
# return created_api_key
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
# headers = {"x-api-key": "api_key"}
# # Dummy POST data
# post_data = {
# "inputs": {"key": "value"},
# "tweaks": None,
# "clear_cache": False,
# "session_id": None,
# }
# # Make the request to the FastAPI TestClient
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# # Check the response
# assert response.status_code == 403, response.json()
# assert response.json() == {"detail": "Invalid or missing API key"}
def test_get_all(client: TestClient, logged_in_headers):
response = client.get("api/v1/all", headers=logged_in_headers)
assert response.status_code == 200
settings = get_settings_service().settings
dir_reader = DirectoryReader(settings.components_path[0])
files = dir_reader.get_files()
# json_response is a dict of dicts
all_names = [component_name for _, components in response.json().items() for component_name in components]
json_response = response.json()
# We need to test the custom nodes
assert len(all_names) <= len(
files
) # Less or equal because we might have some files that don't have the dependencies installed
assert "ChatInput" in json_response["inputs"]
assert "Prompt" in json_response["prompts"]
assert "ChatOutput" in json_response["outputs"]
def test_post_validate_code(client: TestClient):
# Test case with a valid import and function
code1 = """
import math
def square(x):
return x ** 2
"""
response1 = client.post("api/v1/validate/code", json={"code": code1})
assert response1.status_code == 200
assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}}
# Test case with an invalid import and valid function
code2 = """
import non_existent_module
def square(x):
return x ** 2
"""
response2 = client.post("api/v1/validate/code", json={"code": code2})
assert response2.status_code == 200
assert response2.json() == {
"imports": {"errors": ["No module named 'non_existent_module'"]},
"function": {"errors": []},
}
# Test case with a valid import and invalid function syntax
code3 = """
import math
def square(x)
return x ** 2
"""
response3 = client.post("api/v1/validate/code", json={"code": code3})
assert response3.status_code == 200
assert response3.json() == {
"imports": {"errors": []},
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
}
# Test case with invalid JSON payload
response4 = client.post("api/v1/validate/code", json={"invalid_key": code1})
assert response4.status_code == 422
# Test case with an empty code string
response5 = client.post("api/v1/validate/code", json={"code": ""})
assert response5.status_code == 200
assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}}
# Test case with a syntax error in the code
code6 = """
import math
def square(x)
return x ** 2
"""
response6 = client.post("api/v1/validate/code", json={"code": code6})
assert response6.status_code == 200
assert response6.json() == {
"imports": {"errors": []},
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
}
VALID_PROMPT = """
I want you to act as a naming consultant for new companies.
Here are some examples of good company names:
- search engine, Google
- social media, Facebook
- video sharing, YouTube
The name should be short, catchy and easy to remember.
What is a good name for a company that makes {product}?
"""
INVALID_PROMPT = "This is an invalid prompt without any input variable."
def test_valid_prompt(client: TestClient):
PROMPT_REQUEST["template"] = VALID_PROMPT
response = client.post("api/v1/validate/prompt", json=PROMPT_REQUEST)
assert response.status_code == 200
assert response.json()["input_variables"] == ["product"]
def test_invalid_prompt(client: TestClient):
PROMPT_REQUEST["template"] = INVALID_PROMPT
response = client.post(
"api/v1/validate/prompt",
json=PROMPT_REQUEST,
)
assert response.status_code == 200
assert response.json()["input_variables"] == []
@pytest.mark.parametrize(
"prompt,expected_input_variables",
[
("{color} is my favorite color.", ["color"]),
("The weather is {weather} today.", ["weather"]),
("This prompt has no variables.", []),
("{a}, {b}, and {c} are variables.", ["a", "b", "c"]),
],
)
def test_various_prompts(client, prompt, expected_input_variables):
PROMPT_REQUEST["template"] = prompt
response = client.post("api/v1/validate/prompt", json=PROMPT_REQUEST)
assert response.status_code == 200
assert response.json()["input_variables"] == expected_input_variables
def test_get_vertices_flow_not_found(client, logged_in_headers):
uuid = uuid4()
response = client.post(f"/api/v1/build/{uuid}/vertices", headers=logged_in_headers)
assert response.status_code == 500
def test_get_vertices(client, added_flow_with_prompt_and_history, logged_in_headers):
flow_id = added_flow_with_prompt_and_history["id"]
response = client.post(f"/api/v1/build/{flow_id}/vertices", headers=logged_in_headers)
assert response.status_code == 200
assert "ids" in response.json()
# The response should contain the list in this order
# ['ConversationBufferMemory-Lu2Nb', 'PromptTemplate-5Q0W8', 'ChatOpenAI-vy7fV', 'LLMChain-UjBh1']
# The important part is before the - (ConversationBufferMemory, PromptTemplate, ChatOpenAI, LLMChain)
ids = [_id.split("-")[0] for _id in response.json()["ids"]]
assert ids == [
"ChatOpenAI",
"PromptTemplate",
"ConversationBufferMemory",
]
def test_build_vertex_invalid_flow_id(client, logged_in_headers):
uuid = uuid4()
response = client.post(f"/api/v1/build/{uuid}/vertices/vertex_id", headers=logged_in_headers)
assert response.status_code == 500
def test_build_vertex_invalid_vertex_id(client, added_flow_with_prompt_and_history, logged_in_headers):
flow_id = added_flow_with_prompt_and_history["id"]
response = client.post(f"/api/v1/build/{flow_id}/vertices/invalid_vertex_id", headers=logged_in_headers)
assert response.status_code == 500
@pytest.mark.api_key_required
def test_successful_run_no_payload(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
response = client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": ""}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 1
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
assert all(["ChatOutput" in _id for _id in ids])
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
assert all([name in display_names for name in ["Chat Output"]])
output_results_has_results = all("results" in output.get("results") for output in outputs_dict.get("outputs"))
inner_results = [output.get("results") for output in outputs_dict.get("outputs")]
assert all([result is not None for result in inner_results]), (outputs_dict, output_results_has_results)
def test_successful_run_with_output_type_text(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"output_type": "text",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": ""}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 1
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
assert all(["ChatOutput" in _id for _id in ids]), ids
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
assert all([name in display_names for name in ["Chat Output"]]), display_names
inner_results = [output.get("results") for output in outputs_dict.get("outputs")]
expected_keys = ["message"]
assert all([key in result for result in inner_results for key in expected_keys]), outputs_dict
def test_successful_run_with_output_type_any(client, simple_api_test, created_api_key):
# This one should have both the ChatOutput and TextOutput components
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"output_type": "any",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": ""}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 1
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
assert all(["ChatOutput" in _id or "TextOutput" in _id for _id in ids]), ids
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
assert all([name in display_names for name in ["Chat Output"]]), display_names
inner_results = [output.get("results") for output in outputs_dict.get("outputs")]
expected_keys = ["message"]
assert all([key in result for result in inner_results for key in expected_keys]), outputs_dict
def test_successful_run_with_output_type_debug(client, simple_api_test, created_api_key):
# This one should return outputs for all components
# Let's just check the amount of outputs(there should be 7)
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"output_type": "debug",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": ""}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 3
def test_successful_run_with_input_type_text(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"input_type": "text",
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": "value1"}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 3
# Now we get all components that contain TextInput in the component_id
text_input_outputs = [output for output in outputs_dict.get("outputs") if "TextInput" in output.get("component_id")]
assert len(text_input_outputs) == 1
# Now we check if the input_value is correct
# We get text key twice because the output is now a Message
assert all(
[output.get("results").get("text").get("text") == "value1" for output in text_input_outputs]
), text_input_outputs
def test_successful_run_with_input_type_chat(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"input_type": "chat",
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": "value1"}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 3
# Now we get all components that contain TextInput in the component_id
chat_input_outputs = [output for output in outputs_dict.get("outputs") if "ChatInput" in output.get("component_id")]
assert len(chat_input_outputs) == 1
# Now we check if the input_value is correct
assert all(
[output.get("results").get("message").get("text") == "value1" for output in chat_input_outputs]
), chat_input_outputs
def test_invalid_run_with_input_type_chat(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"input_type": "chat",
"output_type": "debug",
"input_value": "value1",
"tweaks": {"Chat Input": {"input_value": "value2"}},
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text
assert "If you pass an input_value to the chat input, you cannot pass a tweak with the same name." in response.text
def test_successful_run_with_input_type_any(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"input_type": "any",
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": "value1"}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 3
# Now we get all components that contain TextInput or ChatInput in the component_id
any_input_outputs = [
output
for output in outputs_dict.get("outputs")
if "TextInput" in output.get("component_id") or "ChatInput" in output.get("component_id")
]
assert len(any_input_outputs) == 2
# Now we check if the input_value is correct
all_result_dicts = [output.get("results") for output in any_input_outputs]
all_message_or_text_dicts = [
result_dict.get("message", result_dict.get("text")) for result_dict in all_result_dicts
]
assert all(
[message_or_text_dict.get("text") == "value1" for message_or_text_dict in all_message_or_text_dicts]
), any_input_outputs
def test_invalid_flow_id(client, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = "invalid-flow-id"
response = client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND, response.text
headers = {"x-api-key": created_api_key.api_key}
flow_id = UUID(int=0)
response = client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND, response.text
# Check if the error detail is as expected

View file

@ -0,0 +1,76 @@
from uuid import UUID
import pytest
from fastapi.testclient import TestClient
from langflow.memory import add_messagetables
# Assuming you have these imports available
from langflow.services.database.models.message import MessageCreate, MessageRead, MessageUpdate
from langflow.services.database.models.message.model import MessageTable
from langflow.services.deps import session_scope
@pytest.fixture()
def created_message():
with session_scope() as session:
message = MessageCreate(text="Test message", sender="User", sender_name="User", session_id="session_id")
messagetable = MessageTable.model_validate(message, from_attributes=True)
messagetables = add_messagetables([messagetable], session)
message_read = MessageRead.model_validate(messagetables[0], from_attributes=True)
return message_read
@pytest.fixture()
def created_messages(session):
with session_scope() as session:
messages = [
MessageCreate(text="Test message 1", sender="User", sender_name="User", session_id="session_id2"),
MessageCreate(text="Test message 2", sender="User", sender_name="User", session_id="session_id2"),
MessageCreate(text="Test message 3", sender="User", sender_name="User", session_id="session_id2"),
]
messagetables = [MessageTable.model_validate(message, from_attributes=True) for message in messages]
message_list = add_messagetables(messagetables, session)
return message_list
def test_delete_messages(client: TestClient, created_messages, logged_in_headers):
response = client.request(
"DELETE", "api/v1/monitor/messages", json=[str(msg.id) for msg in created_messages], headers=logged_in_headers
)
assert response.status_code == 204, response.text
assert response.reason_phrase == "No Content"
def test_update_message(client: TestClient, logged_in_headers, created_message):
message_id = created_message.id
message_update = MessageUpdate(text="Updated content")
response = client.put(
f"api/v1/monitor/messages/{message_id}", json=message_update.model_dump(), headers=logged_in_headers
)
assert response.status_code == 200, response.text
updated_message = MessageRead(**response.json())
assert updated_message.text == "Updated content"
def test_update_message_not_found(client: TestClient, logged_in_headers):
non_existent_id = UUID("00000000-0000-0000-0000-000000000000")
message_update = MessageUpdate(text="Updated content")
response = client.put(
f"api/v1/monitor/messages/{non_existent_id}", json=message_update.model_dump(), headers=logged_in_headers
)
assert response.status_code == 404, response.text
assert response.json()["detail"] == "Message not found"
def test_delete_messages_session(client: TestClient, created_messages, logged_in_headers):
session_id = "session_id2"
response = client.delete(f"api/v1/monitor/messages/session/{session_id}", headers=logged_in_headers)
assert response.status_code == 204
assert response.reason_phrase == "No Content"
assert len(created_messages) == 3
response = client.get("api/v1/monitor/messages", headers=logged_in_headers)
assert response.status_code == 200
assert len(response.json()) == 0

View file

@ -0,0 +1,129 @@
from typing import Union
import pytest
from pydantic import ValidationError
from langflow.template import Input, Output
from langflow.template.field.base import UNDEFINED
from langflow.type_extraction.type_extraction import post_process_type
@pytest.fixture(name="client", autouse=True)
def client_fixture():
pass
class TestInput:
def test_field_type_str(self):
input_obj = Input(field_type="str")
assert input_obj.field_type == "str"
def test_field_type_type(self):
input_obj = Input(field_type=int)
assert input_obj.field_type == "int"
def test_invalid_field_type(self):
with pytest.raises(ValidationError):
Input(field_type=123)
def test_serialize_field_type(self):
input_obj = Input(field_type="str")
assert input_obj.serialize_field_type("str", None) == "str"
def test_validate_type_string(self):
input_obj = Input(field_type="str")
assert input_obj.field_type == "str"
def test_validate_type_class(self):
input_obj = Input(field_type=int)
assert input_obj.field_type == "int"
def test_post_process_type_function(self):
assert post_process_type(int) == [int]
assert post_process_type(list[int]) == [int]
assert post_process_type(Union[int, str]) == [int, str]
def test_input_to_dict(self):
input_obj = Input(field_type="str")
assert input_obj.to_dict() == {
"type": "str",
"required": False,
"placeholder": "",
"list": False,
"show": True,
"multiline": False,
"fileTypes": [],
"file_path": "",
"password": False,
"advanced": False,
"title_case": False,
"dynamic": False,
"info": "",
"input_types": ["Text"],
"load_from_db": False,
}
class TestOutput:
def test_output_default(self):
output_obj = Output(name="test_output")
assert output_obj.name == "test_output"
assert output_obj.value == UNDEFINED
assert output_obj.cache is True
def test_output_add_types(self):
output_obj = Output(name="test_output")
output_obj.add_types(["str", "int"])
assert output_obj.types == ["str", "int"]
def test_output_set_selected(self):
output_obj = Output(name="test_output", types=["str", "int"])
output_obj.set_selected()
assert output_obj.selected == "str"
def test_output_to_dict(self):
output_obj = Output(name="test_output")
assert output_obj.to_dict() == {
"types": [],
"name": "test_output",
"display_name": "test_output",
"cache": True,
"value": "__UNDEFINED__",
}
def test_output_validate_display_name(self):
output_obj = Output(name="test_output")
assert output_obj.display_name == "test_output"
def test_output_validate_model(self):
output_obj = Output(name="test_output", value="__UNDEFINED__")
assert output_obj.validate_model() == output_obj
class TestPostProcessType:
def test_int_type(self):
assert post_process_type(int) == [int]
def test_list_int_type(self):
assert post_process_type(list[int]) == [int]
def test_union_type(self):
assert post_process_type(Union[int, str]) == [int, str]
def test_custom_type(self):
class CustomType:
pass
assert post_process_type(CustomType) == [CustomType]
def test_list_custom_type(self):
class CustomType:
pass
assert post_process_type(list[CustomType]) == [CustomType]
def test_union_custom_type(self):
class CustomType:
pass
assert post_process_type(Union[CustomType, int]) == [CustomType, int]

View file

@ -0,0 +1,232 @@
from datetime import datetime
import pytest
from langflow.services.auth.utils import create_super_user, get_password_hash
from langflow.services.database.models.user import UserUpdate
from langflow.services.database.models.user.model import User
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service, get_settings_service
@pytest.fixture
def super_user(client):
settings_manager = get_settings_service()
auth_settings = settings_manager.auth_settings
with session_getter(get_db_service()) as session:
return create_super_user(
db=session,
username=auth_settings.SUPERUSER,
password=auth_settings.SUPERUSER_PASSWORD,
)
@pytest.fixture
def super_user_headers(client, super_user):
settings_service = get_settings_service()
auth_settings = settings_service.auth_settings
login_data = {
"username": auth_settings.SUPERUSER,
"password": auth_settings.SUPERUSER_PASSWORD,
}
response = client.post("/api/v1/login", data=login_data)
assert response.status_code == 200
tokens = response.json()
a_token = tokens["access_token"]
return {"Authorization": f"Bearer {a_token}"}
@pytest.fixture
def deactivated_user():
with session_getter(get_db_service()) as session:
user = User(
username="deactivateduser",
password=get_password_hash("testpassword"),
is_active=False,
is_superuser=False,
last_login_at=datetime.now(),
)
session.add(user)
session.commit()
session.refresh(user)
return user
def test_user_waiting_for_approval(
client,
):
# Create a user that is not active and has never logged in
with session_getter(get_db_service()) as session:
user = User(
username="waitingforapproval",
password=get_password_hash("testpassword"),
is_active=False,
last_login_at=None,
)
session.add(user)
session.commit()
login_data = {"username": "waitingforapproval", "password": "testpassword"}
response = client.post("/api/v1/login", data=login_data)
assert response.status_code == 400
assert response.json()["detail"] == "Waiting for approval"
def test_deactivated_user_cannot_login(client, deactivated_user):
login_data = {"username": deactivated_user.username, "password": "testpassword"}
response = client.post("/api/v1/login", data=login_data)
assert response.status_code == 401, response.json()
assert response.json()["detail"] == "Inactive user", response.text
def test_deactivated_user_cannot_access(client, deactivated_user, logged_in_headers):
# Assuming the headers for deactivated_user
response = client.get("/api/v1/users", headers=logged_in_headers)
assert response.status_code == 403, response.json()
assert response.json()["detail"] == "The user doesn't have enough privileges", response.text
def test_data_consistency_after_update(client, active_user, logged_in_headers, super_user_headers):
user_id = active_user.id
update_data = UserUpdate(is_active=False)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=super_user_headers)
assert response.status_code == 200, response.json()
# Fetch the updated user from the database
response = client.get("/api/v1/users/whoami", headers=logged_in_headers)
assert response.status_code == 401, response.json()
assert response.json()["detail"] == "User not found or is inactive."
def test_data_consistency_after_delete(client, test_user, super_user_headers):
user_id = test_user.get("id")
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
assert response.status_code == 200, response.json()
# Attempt to fetch the deleted user from the database
response = client.get("/api/v1/users", headers=super_user_headers)
assert response.status_code == 200
assert all(user["id"] != user_id for user in response.json()["users"])
def test_inactive_user(client):
# Create a user that is not active and has a last_login_at value
with session_getter(get_db_service()) as session:
user = User(
username="inactiveuser",
password=get_password_hash("testpassword"),
is_active=False,
last_login_at=datetime(2023, 1, 1, 0, 0, 0),
)
session.add(user)
session.commit()
login_data = {"username": "inactiveuser", "password": "testpassword"}
response = client.post("/api/v1/login", data=login_data)
assert response.status_code == 401
assert response.json()["detail"] == "Inactive user"
def test_add_user(client, test_user):
assert test_user["username"] == "testuser"
# This is not used in the Frontend at the moment
# def test_read_current_user(client: TestClient, active_user):
# # First we need to login to get the access token
# login_data = {"username": "testuser", "password": "testpassword"}
# response = client.post("/api/v1/login", data=login_data)
# assert response.status_code == 200
# headers = {"Authorization": f"Bearer {response.json()['access_token']}"}
# response = client.get("/api/v1/user", headers=headers)
# assert response.status_code == 200, response.json()
# assert response.json()["username"] == "testuser"
def test_read_all_users(client, super_user_headers):
response = client.get("/api/v1/users", headers=super_user_headers)
assert response.status_code == 200, response.json()
assert isinstance(response.json()["users"], list)
def test_normal_user_cant_read_all_users(client, logged_in_headers):
response = client.get("/api/v1/users", headers=logged_in_headers)
assert response.status_code == 403, response.json()
assert response.json() == {"detail": "The user doesn't have enough privileges"}
def test_patch_user(client, active_user, logged_in_headers):
user_id = active_user.id
update_data = UserUpdate(
username="newname",
)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
assert response.status_code == 200, response.json()
update_data = UserUpdate(
profile_image="new_image",
)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
assert response.status_code == 200, response.json()
def test_patch_reset_password(client, active_user, logged_in_headers):
user_id = active_user.id
update_data = UserUpdate(
password="newpassword",
)
response = client.patch(
f"/api/v1/users/{user_id}/reset-password",
json=update_data.model_dump(),
headers=logged_in_headers,
)
assert response.status_code == 200, response.json()
# Now we need to test if the new password works
login_data = {"username": active_user.username, "password": "newpassword"}
response = client.post("/api/v1/login", data=login_data)
assert response.status_code == 200
def test_patch_user_wrong_id(client, active_user, logged_in_headers):
user_id = "wrong_id"
update_data = UserUpdate(
username="newname",
)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
assert response.status_code == 422, response.json()
json_response = response.json()
detail = json_response["detail"]
error = detail[0]
assert error["loc"] == ["path", "user_id"]
assert error["type"] == "uuid_parsing"
def test_delete_user(client, test_user, super_user_headers):
user_id = test_user["id"]
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
assert response.status_code == 200
assert response.json() == {"detail": "User deleted"}
def test_delete_user_wrong_id(client, test_user, super_user_headers):
user_id = "wrong_id"
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
assert response.status_code == 422
json_response = response.json()
detail = json_response["detail"]
error = detail[0]
assert error["loc"] == ["path", "user_id"]
assert error["type"] == "uuid_parsing"
def test_normal_user_cant_delete_user(client, test_user, logged_in_headers):
user_id = test_user["id"]
response = client.delete(f"/api/v1/users/{user_id}", headers=logged_in_headers)
assert response.status_code == 403
assert response.json() == {"detail": "The user doesn't have enough privileges"}

View file

@ -0,0 +1,59 @@
import tempfile
from pathlib import Path
import pytest
@pytest.fixture(autouse=True)
def check_openai_api_key_in_environment_variables():
pass
def test_webhook_endpoint(client, added_webhook_test):
# The test is as follows:
# 1. The flow when run will get a "path" from the payload and save a file with the path as the name.
# We will create a temporary file path and send it to the webhook endpoint, then check if the file exists.
# 2. we will delete the file, then send an invalid payload to the webhook endpoint and check if the file exists.
endpoint_name = added_webhook_test["endpoint_name"]
endpoint = f"api/v1/webhook/{endpoint_name}"
# Create a temporary file
with tempfile.TemporaryDirectory() as tmp:
file_path = Path(tmp) / "test_file.txt"
payload = {"path": str(file_path)}
response = client.post(endpoint, json=payload)
assert response.status_code == 202
assert file_path.exists()
assert not file_path.exists()
# Send an invalid payload
payload = {"invalid_key": "invalid_value"}
response = client.post(endpoint, json=payload)
assert response.status_code == 202
assert not file_path.exists()
def test_webhook_flow_on_run_endpoint(client, added_webhook_test, created_api_key):
endpoint_name = added_webhook_test["endpoint_name"]
endpoint = f"api/v1/run/{endpoint_name}?stream=false"
# Just test that "Random Payload" returns 202
# returns 202
payload = {
"output_type": "any",
}
response = client.post(endpoint, headers={"x-api-key": created_api_key.api_key}, json=payload)
assert response.status_code == 200, response.json()
def test_webhook_with_random_payload(client, added_webhook_test):
endpoint_name = added_webhook_test["endpoint_name"]
endpoint = f"api/v1/webhook/{endpoint_name}"
# Just test that "Random Payload" returns 202
# returns 202
response = client.post(
endpoint,
json="Random Payload",
)
assert response.status_code == 202

View file

View file

@ -0,0 +1,200 @@
import pickle
from collections import defaultdict
import pytest
from langflow.graph.graph.runnable_vertices_manager import RunnableVerticesManager
@pytest.fixture
def data():
run_map: defaultdict(list) = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": []}
run_predecessors: defaultdict(set) = {"A": set(), "B": {"A"}, "C": {"A"}, "D": {"B", "C"}}
vertices_to_run: set = {"A", "B", "C"}
vertices_being_run = {"A"}
return {
"run_map": run_map,
"run_predecessors": run_predecessors,
"vertices_to_run": vertices_to_run,
"vertices_being_run": vertices_being_run,
}
def test_to_dict(data):
result = RunnableVerticesManager.from_dict(data).to_dict()
assert all(key in result.keys() for key in data.keys())
def test_from_dict(data):
result = RunnableVerticesManager.from_dict(data)
assert isinstance(result, RunnableVerticesManager)
def test_from_dict_without_run_map__bad_case(data):
data.pop("run_map")
with pytest.raises(KeyError):
RunnableVerticesManager.from_dict(data)
def test_from_dict_without_run_predecessors__bad_case(data):
data.pop("run_predecessors")
with pytest.raises(KeyError):
RunnableVerticesManager.from_dict(data)
def test_from_dict_without_vertices_to_run__bad_case(data):
data.pop("vertices_to_run")
with pytest.raises(KeyError):
RunnableVerticesManager.from_dict(data)
def test_from_dict_without_vertices_being_run__bad_case(data):
data.pop("vertices_being_run")
with pytest.raises(KeyError):
RunnableVerticesManager.from_dict(data)
def test_pickle(data):
manager = RunnableVerticesManager.from_dict(data)
binary = pickle.dumps(manager)
result = pickle.loads(binary)
assert result.run_map == manager.run_map
assert result.run_predecessors == manager.run_predecessors
assert result.vertices_to_run == manager.vertices_to_run
assert result.vertices_being_run == manager.vertices_being_run
def test_update_run_state(data):
manager = RunnableVerticesManager.from_dict(data)
run_predecessors = {"E": {"D"}}
vertices_to_run = {"D"}
manager.update_run_state(run_predecessors, vertices_to_run)
assert "D" in manager.run_map
assert "D" in manager.vertices_to_run
assert "D" in manager.run_predecessors["E"]
def test_is_vertex_runnable(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "A"
is_active = True
result = manager.is_vertex_runnable(vertex_id, is_active)
assert result is False
def test_is_vertex_runnable__wrong_is_active(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "A"
is_active = False
result = manager.is_vertex_runnable(vertex_id, is_active)
assert result is False
def test_is_vertex_runnable__wrong_vertices_to_run(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "D"
is_active = True
result = manager.is_vertex_runnable(vertex_id, is_active)
assert result is False
def test_is_vertex_runnable__wrong_run_predecessors(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
is_active = True
result = manager.is_vertex_runnable(vertex_id, is_active)
assert result is False
def test_are_all_predecessors_fulfilled(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "A"
result = manager.are_all_predecessors_fulfilled(vertex_id)
assert result is True
def test_are_all_predecessors_fulfilled__wrong(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "D"
result = manager.are_all_predecessors_fulfilled(vertex_id)
assert result is False
def test_remove_from_predecessors(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "A"
manager.remove_from_predecessors(vertex_id)
assert all(vertex_id not in predecessors for predecessors in manager.run_predecessors.values())
def test_build_run_map(data):
manager = RunnableVerticesManager.from_dict(data)
vertices_to_run = {}
predecessor_map = {"Z": set(), "X": {"Z"}, "Y": {"Z"}, "W": {"X", "Y"}}
manager.build_run_map(predecessor_map, vertices_to_run)
assert all(v in manager.run_map.keys() for v in ["Z", "X", "Y"])
assert "W" not in manager.run_map.keys()
def test_update_vertex_run_state(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
is_runnable = True
manager.update_vertex_run_state(vertex_id, is_runnable)
assert vertex_id in manager.vertices_to_run
def test_update_vertex_run_state__bad_case(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
is_runnable = False
manager.update_vertex_run_state(vertex_id, is_runnable)
assert vertex_id not in manager.vertices_being_run
def test_remove_vertex_from_runnables(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
manager.remove_vertex_from_runnables(vertex_id)
assert vertex_id not in manager.vertices_being_run
def test_add_to_vertices_being_run(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
manager.add_to_vertices_being_run(vertex_id)
assert vertex_id in manager.vertices_being_run

View file

@ -0,0 +1,122 @@
import pytest
from langflow.graph.graph import utils
@pytest.fixture
def graph():
return {
"A": {"successors": ["B"], "predecessors": []},
"B": {"successors": ["D"], "predecessors": ["A", "C"]},
"C": {"successors": ["B", "I"], "predecessors": ["N"]},
"D": {"successors": ["E", "F"], "predecessors": ["B"]},
"E": {"successors": ["G"], "predecessors": ["D"]},
"F": {"successors": ["G", "H"], "predecessors": ["D"]},
"G": {"successors": [], "predecessors": ["E", "F"]},
"H": {"successors": [], "predecessors": ["F"]},
"I": {"successors": ["M"], "predecessors": ["C", "J"]},
"J": {"successors": ["I", "K"], "predecessors": ["N"]},
"K": {"successors": ["Q", "P", "O"], "predecessors": ["J", "L"]},
"L": {"successors": ["K"], "predecessors": []},
"M": {"successors": [], "predecessors": ["I"]},
"N": {"successors": ["C", "J"], "predecessors": []},
"O": {"successors": ["R"], "predecessors": ["K"]},
"P": {"successors": ["U"], "predecessors": ["K"]},
"Q": {"successors": ["V"], "predecessors": ["K"]},
"R": {"successors": ["S"], "predecessors": ["O"]},
"S": {"successors": ["T"], "predecessors": ["R"]},
"T": {"successors": [], "predecessors": ["S"]},
"U": {"successors": ["W"], "predecessors": ["P"]},
"V": {"successors": ["Y"], "predecessors": ["Q"]},
"W": {"successors": ["X"], "predecessors": ["U"]},
"X": {"successors": [], "predecessors": ["W"]},
"Y": {"successors": ["Z"], "predecessors": ["V"]},
"Z": {"successors": [], "predecessors": ["Y"]},
}
def test_get_successors_a(graph):
vertex_id = "A"
result = utils.get_successors(graph, vertex_id)
assert set(result) == {"A", "B", "D", "E", "F", "H", "G"}
def test_get_successors_z(graph):
vertex_id = "Z"
result = utils.get_successors(graph, vertex_id)
assert set(result) == {"Z"}
def test_sort_up_to_vertex_n_is_start(graph):
vertex_id = "N"
result = utils.sort_up_to_vertex(graph, vertex_id, is_start=True)
# Result shoud be all the vertices
assert set(result) == set(graph.keys())
def test_sort_up_to_vertex_z(graph):
vertex_id = "Z"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"L", "N", "J", "K", "Q", "V", "Y", "Z"}
def test_sort_up_to_vertex_x(graph):
vertex_id = "X"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"L", "N", "J", "K", "P", "U", "W", "X"}
def test_sort_up_to_vertex_t(graph):
vertex_id = "T"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"L", "N", "J", "K", "O", "R", "S", "T"}
def test_sort_up_to_vertex_m(graph):
vertex_id = "M"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"N", "C", "J", "I", "M"}
def test_sort_up_to_vertex_h(graph):
vertex_id = "H"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"N", "C", "A", "B", "D", "F", "H"}
def test_sort_up_to_vertex_g(graph):
vertex_id = "G"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"N", "C", "A", "B", "D", "F", "E", "G"}
def test_sort_up_to_vertex_a(graph):
vertex_id = "A"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"A"}
def test_sort_up_to_vertex_invalid_vertex(graph):
vertex_id = "7"
with pytest.raises(ValueError):
utils.sort_up_to_vertex(graph, vertex_id)

View file

@ -0,0 +1,45 @@
import pytest
from langflow.services.database.models.api_key import ApiKeyCreate
@pytest.fixture
def api_key(client, logged_in_headers, active_user):
api_key = ApiKeyCreate(name="test-api-key")
response = client.post("api/v1/api_key", data=api_key.model_dump_json(), headers=logged_in_headers)
assert response.status_code == 200, response.text
return response.json()
def test_get_api_keys(client, logged_in_headers, api_key):
response = client.get("api/v1/api_key", headers=logged_in_headers)
assert response.status_code == 200, response.text
data = response.json()
assert "total_count" in data
assert "user_id" in data
assert "api_keys" in data
assert any("test-api-key" in api_key["name"] for api_key in data["api_keys"])
# assert all api keys in data["api_keys"] are masked
assert all("**" in api_key["api_key"] for api_key in data["api_keys"])
def test_create_api_key(client, logged_in_headers):
api_key_name = "test-api-key"
response = client.post("api/v1/api_key", json={"name": api_key_name}, headers=logged_in_headers)
assert response.status_code == 200
data = response.json()
assert "name" in data and data["name"] == api_key_name
assert "api_key" in data
# When creating the API key is returned which is
# the only time the API key is unmasked
assert "**" not in data["api_key"]
def test_delete_api_key(client, logged_in_headers, active_user, api_key):
# Assuming a function to create a test API key, returning the key ID
api_key_id = api_key["id"]
response = client.delete(f"api/v1/api_key/{api_key_id}", headers=logged_in_headers)
assert response.status_code == 200
data = response.json()
assert data["detail"] == "API Key deleted"
# Optionally, add a follow-up check to ensure that the key is actually removed from the database

View file

@ -0,0 +1,47 @@
import json
import pytest
from langflow.graph import Graph
def get_graph(_type="basic"):
"""Get a graph from a json file"""
if _type == "basic":
path = pytest.BASIC_EXAMPLE_PATH
elif _type == "complex":
path = pytest.COMPLEX_EXAMPLE_PATH
elif _type == "openapi":
path = pytest.OPENAPI_EXAMPLE_PATH
with open(path, "r") as f:
flow_graph = json.load(f)
return flow_graph["data"]
@pytest.fixture
def basic_data_graph():
return get_graph()
@pytest.fixture
def complex_data_graph():
return get_graph("complex")
@pytest.fixture
def openapi_data_graph():
return get_graph("openapi")
def langchain_objects_are_equal(obj1, obj2):
return str(obj1) == str(obj2)
# Test build_graph
@pytest.mark.asyncio
async def test_build_graph(client, basic_data_graph):
graph = Graph.from_payload(basic_data_graph)
assert graph is not None
assert len(graph.vertices) == len(basic_data_graph["nodes"])
assert len(graph.edges) == len(basic_data_graph["edges"])

View file

@ -0,0 +1,37 @@
from pathlib import Path
from tempfile import tempdir
import pytest
from langflow.__main__ import app
from langflow.services import deps
@pytest.fixture(scope="module")
def default_settings():
return [
"--backend-only",
"--no-open-browser",
]
def test_components_path(runner, client, default_settings):
# Create a foldr in the tmp directory
temp_dir = Path(tempdir)
# create a "components" folder
temp_dir = temp_dir / "components"
temp_dir.mkdir(exist_ok=True)
result = runner.invoke(
app,
["run", "--components-path", str(temp_dir), *default_settings],
)
assert result.exit_code == 0, result.stdout
settings_service = deps.get_settings_service()
assert str(temp_dir) in settings_service.settings.components_path
def test_superuser(runner, client, session):
result = runner.invoke(app, ["superuser"], input="admin\nadmin\n")
assert result.exit_code == 0, result.stdout
assert "Superuser created successfully." in result.stdout

View file

@ -0,0 +1,528 @@
import ast
import types
from uuid import uuid4
import pytest
from langchain_core.documents import Document
from langflow.custom import Component, CustomComponent
from langflow.custom.code_parser.code_parser import CodeParser, CodeSyntaxError
from langflow.custom.custom_component.base_component import BaseComponent, ComponentCodeNullError
from langflow.custom.utils import build_custom_component_template
from langflow.services.database.models.flow import Flow, FlowCreate
@pytest.fixture
def code_component_with_multiple_outputs():
with open("src/backend/tests/data/component_multiple_outputs.py", "r") as f:
code = f.read()
return Component(code=code)
code_default = """
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_core.documents import Document
import requests
class YourComponent(CustomComponent):
display_name: str = "Your Component"
description: str = "Your description"
field_config = { "url": { "multiline": True, "required": True } }
def build(self, url: str, llm: BaseLanguageModel) -> Document:
return Document(page_content="Hello World")
"""
def test_code_parser_init():
"""
Test the initialization of the CodeParser class.
"""
parser = CodeParser(code_default)
assert parser.code == code_default
def test_code_parser_get_tree():
"""
Test the __get_tree method of the CodeParser class.
"""
parser = CodeParser(code_default)
tree = parser.get_tree()
assert isinstance(tree, ast.AST)
def test_code_parser_syntax_error():
"""
Test the __get_tree method raises the
CodeSyntaxError when given incorrect syntax.
"""
code_syntax_error = "zzz import os"
parser = CodeParser(code_syntax_error)
with pytest.raises(CodeSyntaxError):
parser.get_tree()
def test_component_init():
"""
Test the initialization of the Component class.
"""
component = BaseComponent(code=code_default, function_entrypoint_name="build")
assert component.code == code_default
assert component.function_entrypoint_name == "build"
def test_component_get_code_tree():
"""
Test the get_code_tree method of the Component class.
"""
component = BaseComponent(code=code_default, function_entrypoint_name="build")
tree = component.get_code_tree(component.code)
assert "imports" in tree
def test_component_code_null_error():
"""
Test the get_function method raises the
ComponentCodeNullError when the code is empty.
"""
component = BaseComponent(code="", function_entrypoint_name="")
with pytest.raises(ComponentCodeNullError):
component.get_function()
def test_custom_component_init():
"""
Test the initialization of the CustomComponent class.
"""
function_entrypoint_name = "build"
custom_component = CustomComponent(code=code_default, function_entrypoint_name=function_entrypoint_name)
assert custom_component.code == code_default
assert custom_component.function_entrypoint_name == function_entrypoint_name
def test_custom_component_build_template_config():
"""
Test the build_template_config property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
config = custom_component.build_template_config()
assert isinstance(config, dict)
def test_custom_component_get_function():
"""
Test the get_function property of the CustomComponent class.
"""
custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build")
my_function = custom_component.get_function()
assert isinstance(my_function, types.FunctionType)
def test_code_parser_parse_imports_import():
"""
Test the parse_imports method of the CodeParser
class with an import statement.
"""
parser = CodeParser(code_default)
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
parser.parse_imports(node)
assert "requests" in parser.data["imports"]
def test_code_parser_parse_imports_importfrom():
"""
Test the parse_imports method of the CodeParser
class with an import from statement.
"""
parser = CodeParser("from os import path")
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
parser.parse_imports(node)
assert ("os", "path") in parser.data["imports"]
def test_code_parser_parse_functions():
"""
Test the parse_functions method of the CodeParser class.
"""
parser = CodeParser("def test(): pass")
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
parser.parse_functions(node)
assert len(parser.data["functions"]) == 1
assert parser.data["functions"][0]["name"] == "test"
def test_code_parser_parse_classes():
"""
Test the parse_classes method of the CodeParser class.
"""
parser = CodeParser("class Test: pass")
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
parser.parse_classes(node)
assert len(parser.data["classes"]) == 1
assert parser.data["classes"][0]["name"] == "Test"
def test_code_parser_parse_global_vars():
"""
Test the parse_global_vars method of the CodeParser class.
"""
parser = CodeParser("x = 1")
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
parser.parse_global_vars(node)
assert len(parser.data["global_vars"]) == 1
assert parser.data["global_vars"][0]["targets"] == ["x"]
def test_component_get_function_valid():
"""
Test the get_function method of the Component
class with valid code and function_entrypoint_name.
"""
component = BaseComponent(code="def build(): pass", function_entrypoint_name="build")
my_function = component.get_function()
assert callable(my_function)
def test_custom_component_get_function_entrypoint_args():
"""
Test the get_function_entrypoint_args
property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
args = custom_component.get_function_entrypoint_args
assert len(args) == 3
assert args[0]["name"] == "self"
assert args[1]["name"] == "url"
assert args[2]["name"] == "llm"
def test_custom_component_get_function_entrypoint_return_type():
"""
Test the get_function_entrypoint_return_type
property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
return_type = custom_component.get_function_entrypoint_return_type
assert return_type == [Document]
def test_custom_component_get_main_class_name():
"""
Test the get_main_class_name property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
class_name = custom_component.get_main_class_name
assert class_name == "YourComponent"
def test_custom_component_get_function_valid():
"""
Test the get_function property of the CustomComponent
class with valid code and function_entrypoint_name.
"""
custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build")
my_function = custom_component.get_function
assert callable(my_function)
def test_code_parser_parse_arg_no_annotation():
"""
Test the parse_arg method of the CodeParser class without an annotation.
"""
parser = CodeParser("")
arg = ast.arg(arg="x", annotation=None)
result = parser.parse_arg(arg, None)
assert result["name"] == "x"
assert "type" not in result
def test_code_parser_parse_arg_with_annotation():
"""
Test the parse_arg method of the CodeParser class with an annotation.
"""
parser = CodeParser("")
arg = ast.arg(arg="x", annotation=ast.Name(id="int", ctx=ast.Load()))
result = parser.parse_arg(arg, None)
assert result["name"] == "x"
assert result["type"] == "int"
def test_code_parser_parse_callable_details_no_args():
"""
Test the parse_callable_details method of the
CodeParser class with a function with no arguments.
"""
parser = CodeParser("")
node = ast.FunctionDef(
name="test",
args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
body=[],
decorator_list=[],
returns=None,
)
result = parser.parse_callable_details(node)
assert result["name"] == "test"
assert len(result["args"]) == 0
def test_code_parser_parse_assign():
"""
Test the parse_assign method of the CodeParser class.
"""
parser = CodeParser("")
stmt = ast.Assign(targets=[ast.Name(id="x", ctx=ast.Store())], value=ast.Num(n=1))
result = parser.parse_assign(stmt)
assert result["name"] == "x"
assert result["value"] == "1"
def test_code_parser_parse_ann_assign():
"""
Test the parse_ann_assign method of the CodeParser class.
"""
parser = CodeParser("")
stmt = ast.AnnAssign(
target=ast.Name(id="x", ctx=ast.Store()),
annotation=ast.Name(id="int", ctx=ast.Load()),
value=ast.Num(n=1),
simple=1,
)
result = parser.parse_ann_assign(stmt)
assert result["name"] == "x"
assert result["value"] == "1"
assert result["annotation"] == "int"
def test_code_parser_parse_function_def_not_init():
"""
Test the parse_function_def method of the
CodeParser class with a function that is not __init__.
"""
parser = CodeParser("")
stmt = ast.FunctionDef(
name="test",
args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
body=[],
decorator_list=[],
returns=None,
)
result, is_init = parser.parse_function_def(stmt)
assert result["name"] == "test"
assert not is_init
def test_code_parser_parse_function_def_init():
"""
Test the parse_function_def method of the
CodeParser class with an __init__ function.
"""
parser = CodeParser("")
stmt = ast.FunctionDef(
name="__init__",
args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
body=[],
decorator_list=[],
returns=None,
)
result, is_init = parser.parse_function_def(stmt)
assert result["name"] == "__init__"
assert is_init
def test_component_get_code_tree_syntax_error():
"""
Test the get_code_tree method of the Component class
raises the CodeSyntaxError when given incorrect syntax.
"""
component = BaseComponent(code="import os as", function_entrypoint_name="build")
with pytest.raises(CodeSyntaxError):
component.get_code_tree(component.code)
def test_custom_component_class_template_validation_no_code():
"""
Test the _class_template_validation method of the CustomComponent class
raises the HTTPException when the code is None.
"""
custom_component = CustomComponent(code=None, function_entrypoint_name="build")
with pytest.raises(TypeError):
custom_component.get_function()
def test_custom_component_get_code_tree_syntax_error():
"""
Test the get_code_tree method of the CustomComponent class
raises the CodeSyntaxError when given incorrect syntax.
"""
custom_component = CustomComponent(code="import os as", function_entrypoint_name="build")
with pytest.raises(CodeSyntaxError):
custom_component.get_code_tree(custom_component.code)
def test_custom_component_get_function_entrypoint_args_no_args():
"""
Test the get_function_entrypoint_args property of
the CustomComponent class with a build method with no arguments.
"""
my_code = """
class MyMainClass(CustomComponent):
def build():
pass"""
custom_component = CustomComponent(code=my_code, function_entrypoint_name="build")
args = custom_component.get_function_entrypoint_args
assert len(args) == 0
def test_custom_component_get_function_entrypoint_return_type_no_return_type():
"""
Test the get_function_entrypoint_return_type property of the
CustomComponent class with a build method with no return type.
"""
my_code = """
class MyClass(CustomComponent):
def build():
pass"""
custom_component = CustomComponent(code=my_code, function_entrypoint_name="build")
return_type = custom_component.get_function_entrypoint_return_type
assert return_type == []
def test_custom_component_get_main_class_name_no_main_class():
"""
Test the get_main_class_name property of the
CustomComponent class when there is no main class.
"""
my_code = """
def build():
pass"""
custom_component = CustomComponent(code=my_code, function_entrypoint_name="build")
class_name = custom_component.get_main_class_name
assert class_name == ""
def test_custom_component_build_not_implemented():
"""
Test the build method of the CustomComponent
class raises the NotImplementedError.
"""
custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build")
with pytest.raises(NotImplementedError):
custom_component.build()
def test_build_config_no_code():
component = CustomComponent(code=None)
assert component.get_function_entrypoint_args == []
assert component.get_function_entrypoint_return_type == []
@pytest.fixture
def component(client, active_user):
return CustomComponent(
user_id=active_user.id,
field_config={
"fields": {
"llm": {"type": "str"},
"url": {"type": "str"},
"year": {"type": "int"},
}
},
)
@pytest.fixture(scope="session")
def test_flow(db):
flow_data = {
"nodes": [{"id": "1"}, {"id": "2"}],
"edges": [{"source": "1", "target": "2"}],
}
# Create flow
flow = FlowCreate(id=uuid4(), name="Test Flow", description="Fixture flow", data=flow_data)
# Add to database
db.add(flow)
db.commit()
yield flow
# Clean up
db.delete(flow)
db.commit()
@pytest.fixture(scope="session")
def db(app):
# Setup database for tests
yield app.db
# Teardown
app.db.drop_all()
def test_list_flows_return_type(component):
flows = component.list_flows()
assert isinstance(flows, list)
def test_list_flows_flow_objects(component):
flows = component.list_flows()
assert all(isinstance(flow, Flow) for flow in flows)
def test_build_config_return_type(component):
config = component.build_config()
assert isinstance(config, dict)
def test_build_config_has_fields(component):
config = component.build_config()
assert "fields" in config
def test_build_config_fields_dict(component):
config = component.build_config()
assert isinstance(config["fields"], dict)
def test_build_config_field_keys(component):
config = component.build_config()
assert all(isinstance(key, str) for key in config["fields"])
def test_build_config_field_values_dict(component):
config = component.build_config()
assert all(isinstance(value, dict) for value in config["fields"].values())
def test_build_config_field_value_keys(component):
config = component.build_config()
field_values = config["fields"].values()
assert all("type" in value for value in field_values)
def test_custom_component_multiple_outputs(code_component_with_multiple_outputs, active_user):
frontnd_node_dict, _ = build_custom_component_template(code_component_with_multiple_outputs, active_user.id)
assert frontnd_node_dict["outputs"][0]["types"] == ["Text"]

View file

@ -0,0 +1,140 @@
import pytest
from langchain_core.documents import Document
from langflow.schema import Data
def test_data_initialization():
record = Data(text_key="msg", data={"msg": "Hello, World!", "extra": "value"})
assert record.msg == "Hello, World!"
assert record.extra == "value"
def test_validate_data_with_extra_keys():
record = Data(dummy_key="dummy", data={"key": "value"})
assert record.data["dummy_key"] == "dummy"
assert "dummy_key" in record.data
assert record.key == "value"
def test_conversion_to_document():
record = Data(data={"text": "Sample text", "meta": "data"})
document = record.to_lc_document()
assert document.page_content == "Sample text"
assert document.metadata == {"meta": "data"}
def test_conversion_from_document():
document = Document(page_content="Doc content", metadata={"meta": "info"})
record = Data.from_document(document)
assert record.text == "Doc content"
assert record.meta == "info"
def test_add_method_for_strings():
record1 = Data(data={"text": "Hello"})
record2 = Data(data={"text": " World"})
combined = record1 + record2
assert combined.text == "Hello World"
def test_add_method_for_integers():
record1 = Data(data={"number": 5})
record2 = Data(data={"number": 10})
combined = record1 + record2
assert combined.number == 15
def test_add_method_with_non_overlapping_keys():
record1 = Data(data={"text": "Hello"})
record2 = Data(data={"number": 10})
combined = record1 + record2
assert combined.text == "Hello"
assert combined.number == 10
def test_custom_attribute_get_set_del():
record = Data()
record.custom_attr = "custom_value"
assert record.custom_attr == "custom_value"
del record.custom_attr
with pytest.raises(AttributeError):
_ = record.custom_attr
def test_deep_copy():
import copy
record1 = Data(data={"text": "Hello", "number": 10})
record2 = copy.deepcopy(record1)
assert record2.text == "Hello"
assert record2.number == 10
record2.text = "World"
assert record1.text == "Hello" # Ensure original is unchanged
def test_custom_attribute_setting_and_getting():
record = Data()
record.dynamic_attribute = "Dynamic Value"
assert record.dynamic_attribute == "Dynamic Value"
def test_str_and_dir_methods():
record = Data(text_key="text", data={"text": "Test Text", "key": "value"})
assert "Test Text" in str(record)
assert "key" in dir(record)
assert "data" in dir(record)
def test_dir_includes_data_keys():
record = Data(data={"text": "Hello", "new_attr": "value"})
dir_output = dir(record)
# Check for standard attributes
assert "data" in dir_output
assert "text_key" in dir_output
assert "__add__" in dir_output # Checking for a method
# Check for dynamic attributes from data
assert "text" in dir_output
assert "new_attr" in dir_output
# Optionally, verify that dynamically added attributes are listed
record.dynamic_attr = "dynamic"
assert "dynamic_attr" in dir_output or "dynamic_attr" in dir(record) # To account for the change
def test_dir_reflects_attribute_deletion():
record = Data(data={"removable": "I can be removed"})
assert "removable" in dir(record)
# Delete the attribute and check again
del record.removable
assert "removable" not in dir(record)
def test_get_text_with_text_key():
data = {"text": "Hello, World!"}
schema = Data(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "Hello, World!"
def test_get_text_without_text_key():
data = {"other_key": "Hello, World!"}
schema = Data(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"
def test_get_text_with_empty_data():
data = {}
schema = Data(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"
def test_get_text_with_none_data():
data = None
schema = Data(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"

View file

@ -0,0 +1,201 @@
import json
import os
from pathlib import Path
from unittest.mock import Mock, patch
import httpx
import pytest
import respx
from dictdiffer import diff
from httpx import Response
from langflow.components import data
@pytest.fixture
def api_request():
# This fixture provides an instance of APIRequest for each test case
return data.APIRequestComponent()
@pytest.mark.asyncio
@respx.mock
async def test_successful_get_request(api_request):
# Mocking a successful GET request
url = "https://example.com/api/test"
method = "GET"
mock_response = {"success": True}
respx.get(url).mock(return_value=Response(200, json=mock_response))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url)
# Assertions
assert result.data["status_code"] == 200
assert result.data["result"] == mock_response
def test_parse_curl(api_request):
# Arrange
field_value = (
"curl -X GET https://example.com/api/test -H 'Content-Type: application/json' -d '{\"key\": \"value\"}'"
)
build_config = {
"method": {"value": ""},
"urls": {"value": []},
"headers": {},
"body": {},
}
# Act
new_build_config = api_request.parse_curl(field_value, build_config.copy())
# Assert
assert new_build_config["method"]["value"] == "GET"
assert new_build_config["urls"]["value"] == ["https://example.com/api/test"]
assert new_build_config["headers"]["value"] == {"Content-Type": "application/json"}
assert new_build_config["body"]["value"] == {"key": "value"}
@pytest.mark.asyncio
@respx.mock
async def test_failed_request(api_request):
# Mocking a failed GET request
url = "https://example.com/api/test"
method = "GET"
respx.get(url).mock(return_value=Response(404))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url)
# Assertions
assert result.data["status_code"] == 404
@pytest.mark.asyncio
@respx.mock
async def test_timeout(api_request):
# Mocking a timeout
url = "https://example.com/api/timeout"
method = "GET"
respx.get(url).mock(side_effect=httpx.TimeoutException(message="Timeout", request=None))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url, timeout=1)
# Assertions
assert result.data["status_code"] == 408
assert result.data["error"] == "Request timed out"
@pytest.mark.asyncio
@respx.mock
async def test_build_with_multiple_urls(api_request):
# This test depends on having a working internet connection and accessible URLs
# It's better to mock these requests using respx or a similar library
# Setup for multiple URLs
method = "GET"
urls = ["https://example.com/api/one", "https://example.com/api/two"]
# You would mock these requests similarly to the single request tests
for url in urls:
respx.get(url).mock(return_value=Response(200, json={"success": True}))
# Do I have to mock the async client?
#
# Execute the build method
api_request.set_attributes(
{
"method": method,
"urls": urls,
}
)
results = await api_request.make_requests()
# Assertions
assert len(results) == len(urls)
@patch("langflow.components.data.Directory.parallel_load_data")
@patch("langflow.components.data.Directory.retrieve_file_paths")
@patch("langflow.components.data.DirectoryComponent.resolve_path")
def test_directory_component_build_with_multithreading(
mock_resolve_path, mock_retrieve_file_paths, mock_parallel_load_data
):
# Arrange
directory_component = data.DirectoryComponent()
path = os.path.dirname(os.path.abspath(__file__))
depth = 1
max_concurrency = 2
load_hidden = False
recursive = True
silent_errors = False
use_multithreading = True
mock_resolve_path.return_value = path
mock_retrieve_file_paths.return_value = [
os.path.join(path, file) for file in os.listdir(path) if file.endswith(".py")
]
mock_parallel_load_data.return_value = [Mock()]
# Act
directory_component.set_attributes(
{
"path": path,
"depth": depth,
"max_concurrency": max_concurrency,
"load_hidden": load_hidden,
"recursive": recursive,
"silent_errors": silent_errors,
"use_multithreading": use_multithreading,
}
)
directory_component.load_directory()
# Assert
mock_resolve_path.assert_called_once_with(path)
mock_retrieve_file_paths.assert_called_once_with(path, load_hidden, recursive, depth)
mock_parallel_load_data.assert_called_once_with(
mock_retrieve_file_paths.return_value, silent_errors, max_concurrency
)
def test_directory_without_mocks():
directory_component = data.DirectoryComponent()
from langflow.initial_setup import setup
from langflow.initial_setup.setup import load_starter_projects
_, projects = zip(*load_starter_projects())
# the setup module has a folder where the projects are stored
# the contents of that folder are in the projects variable
# the directory component can be used to load the projects
# and we can validate if the contents are the same as the projects variable
setup_path = Path(setup.__file__).parent / "starter_projects"
directory_component.set_attributes({"path": str(setup_path), "use_multithreading": False})
results = directory_component.load_directory()
assert len(results) == len(projects)
# each result is a Data that contains the content attribute
# each are dict that are exactly the same as one of the projects
for i, result in enumerate(results):
file_dict = json.loads(result.text)
assert file_dict in projects, list(diff(file_dict, projects[i]))
# in ../docs/docs/components there are many mdx files
# check if the directory component can load them
# just check if the number of results is the same as the number of files
directory_component = data.DirectoryComponent()
docs_path = Path(__file__).parent.parent.parent.parent.parent / "docs" / "docs" / "Components"
directory_component.set_attributes({"path": str(docs_path), "use_multithreading": False})
results = directory_component.load_directory()
docs_files = list(docs_path.glob("*.md")) + list(docs_path.glob("*.json"))
assert len(results) == len(docs_files)
def test_url_component():
url_component = data.URLComponent()
url_component.set_attributes({"urls": ["https://langflow.org"]})
# the url component can be used to load the contents of a website
_data = url_component.fetch_content()
assert all(value.data for value in _data)
assert all(value.text for value in _data)
assert all(value.source for value in _data)

View file

@ -0,0 +1,281 @@
from uuid import UUID, uuid4
import orjson
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session
from langflow.api.v1.schemas import FlowListCreate
from langflow.initial_setup.setup import load_starter_projects, load_flows_from_directory
from langflow.services.database.models.base import orjson_dumps
from langflow.services.database.models.flow import Flow, FlowCreate, FlowUpdate
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service
@pytest.fixture(scope="module")
def json_style():
# class FlowStyleBase(SQLModel):
# color: str = Field(index=True)
# emoji: str = Field(index=False)
# flow_id: UUID = Field(default=None, foreign_key="flow.id")
return orjson_dumps(
{
"color": "red",
"emoji": "👍",
}
)
def test_create_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
# flow is optional so we can create a flow without a flow
flow = FlowCreate(name="Test Flow")
response = client.post("api/v1/flows/", json=flow.model_dump(exclude_unset=True), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
def test_read_flows(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
response = client.get("api/v1/flows/", headers=logged_in_headers)
assert response.status_code == 200
assert len(response.json()) > 0
def test_read_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"] # flow_id should be a UUID but is a string
# turn it into a UUID
flow_id = UUID(flow_id)
response = client.get(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
assert response.status_code == 200
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"]
updated_flow = FlowUpdate(
name="Updated Flow",
description="updated description",
data=data,
)
response = client.patch(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 200
assert response.json()["name"] == updated_flow.name
assert response.json()["description"] == updated_flow.description
# assert response.json()["data"] == updated_flow.data
def test_delete_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"]
response = client.delete(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
assert response.status_code == 200
assert response.json()["message"] == "Flow deleted successfully"
def test_delete_flows(client: TestClient, json_flow: str, active_user, logged_in_headers):
# Create ten flows
number_of_flows = 10
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
flow_ids = []
for flow in flows:
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
flow_ids.append(response.json()["id"])
response = client.request("DELETE", "api/v1/flows/", headers=logged_in_headers, json=flow_ids)
assert response.status_code == 200, response.content
assert response.json().get("deleted") == number_of_flows
def test_create_flows(client: TestClient, session: Session, json_flow: str, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
flows=[
FlowCreate(name="Flow 1", description="description", data=data),
FlowCreate(name="Flow 2", description="description", data=data),
]
)
# Make request to endpoint
response = client.post("api/v1/flows/batch/", json=flow_list.dict(), headers=logged_in_headers)
# Check response status code
assert response.status_code == 201
# Check response data
response_data = response.json()
assert len(response_data) == 2
assert response_data[0]["name"] == "Flow 1"
assert response_data[0]["description"] == "description"
assert response_data[0]["data"] == data
assert response_data[1]["name"] == "Flow 2"
assert response_data[1]["description"] == "description"
assert response_data[1]["data"] == data
def test_upload_file(client: TestClient, session: Session, json_flow: str, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
flows=[
FlowCreate(name="Flow 1", description="description", data=data),
FlowCreate(name="Flow 2", description="description", data=data),
]
)
file_contents = orjson_dumps(flow_list.dict())
response = client.post(
"api/v1/flows/upload/",
files={"file": ("examples.json", file_contents, "application/json")},
headers=logged_in_headers,
)
# Check response status code
assert response.status_code == 201
# Check response data
response_data = response.json()
assert len(response_data) == 2
assert response_data[0]["name"] == "Flow 1"
assert response_data[0]["description"] == "description"
assert response_data[0]["data"] == data
assert response_data[1]["name"] == "Flow 2"
assert response_data[1]["description"] == "description"
assert response_data[1]["data"] == data
def test_download_file(
client: TestClient,
session: Session,
json_flow,
active_user,
logged_in_headers,
):
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
flows=[
FlowCreate(name="Flow 1", description="description", data=data),
FlowCreate(name="Flow 2", description="description", data=data),
]
)
db_manager = get_db_service()
with session_getter(db_manager) as session:
for flow in flow_list.flows:
flow.user_id = active_user.id
db_flow = Flow.model_validate(flow, from_attributes=True)
session.add(db_flow)
session.commit()
# Make request to endpoint
response = client.get("api/v1/flows/download/", headers=logged_in_headers)
# Check response status code
assert response.status_code == 200, response.json()
# Check response data
response_data = response.json()["flows"]
starter_projects = load_starter_projects()
number_of_projects = len(starter_projects) + len(flow_list.flows)
assert len(response_data) == number_of_projects, response_data
assert response_data[0]["name"] == "Flow 1"
assert response_data[0]["description"] == "description"
assert response_data[0]["data"] == data
assert response_data[1]["name"] == "Flow 2"
assert response_data[1]["description"] == "description"
assert response_data[1]["data"] == data
def test_create_flow_with_invalid_data(client: TestClient, active_user, logged_in_headers):
flow = {"name": "a" * 256, "data": "Invalid flow data"}
response = client.post("api/v1/flows/", json=flow, headers=logged_in_headers)
assert response.status_code == 422
def test_get_nonexistent_flow(client: TestClient, active_user, logged_in_headers):
uuid = uuid4()
response = client.get(f"api/v1/flows/{uuid}", headers=logged_in_headers)
assert response.status_code == 404
def test_update_flow_idempotency(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow_data = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow_data.dict(), headers=logged_in_headers)
flow_id = response.json()["id"]
updated_flow = FlowCreate(name="Updated Flow", description="description", data=data)
response1 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
response2 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response1.json() == response2.json()
def test_update_nonexistent_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
uuid = uuid4()
updated_flow = FlowCreate(
name="Updated Flow",
description="description",
data=data,
)
response = client.patch(f"api/v1/flows/{uuid}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 404, response.text
def test_delete_nonexistent_flow(client: TestClient, active_user, logged_in_headers):
uuid = uuid4()
response = client.delete(f"api/v1/flows/{uuid}", headers=logged_in_headers)
assert response.status_code == 404
def test_read_only_starter_projects(client: TestClient, active_user, logged_in_headers):
response = client.get("api/v1/flows/", headers=logged_in_headers)
starter_projects = load_starter_projects()
assert response.status_code == 200
assert len(response.json()) == len(starter_projects)
@pytest.mark.load_flows
def test_load_flows(client: TestClient, load_flows_dir):
response = client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b")
assert response.status_code == 200
assert response.json()["name"] == "BasicExample"
# re-run to ensure updates work well
load_flows_from_directory()
response = client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b")
assert response.status_code == 200
assert response.json()["name"] == "BasicExample"

View file

@ -0,0 +1,104 @@
from unittest.mock import MagicMock
import pytest
from langflow.services.deps import get_storage_service
from langflow.services.storage.service import StorageService
@pytest.fixture
def mock_storage_service():
# Create a mock instance of StorageService
service = MagicMock(spec=StorageService)
# Setup mock behaviors for the service methods as needed
service.save_file.return_value = None
service.get_file.return_value = b"file content" # Binary content for files
service.list_files.return_value = ["file1.txt", "file2.jpg"]
service.delete_file.return_value = None
return service
def test_upload_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
# Replace the actual storage service with the mock
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.post(
f"api/v1/files/upload/{flow.id}",
files={"file": ("test.txt", b"test content")},
headers=headers,
)
assert response.status_code == 201
assert response.json() == {
"flowId": str(flow.id),
"file_path": f"{flow.id}/test.txt",
}
def test_download_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.get(f"api/v1/files/download/{flow.id}/test.txt", headers=headers)
assert response.status_code == 200
assert response.content == b"file content"
def test_list_files(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.get(f"api/v1/files/list/{flow.id}", headers=headers)
assert response.status_code == 200
assert response.json() == {"files": ["file1.txt", "file2.jpg"]}
def test_delete_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.delete(f"api/v1/files/delete/{flow.id}/test.txt", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": "File test.txt deleted successfully"}
def test_file_operations(client, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
flow_id = flow.id
file_name = "test.txt"
file_content = b"Hello, world!"
# Step 1: Upload the file
response = client.post(
f"api/v1/files/upload/{flow_id}",
files={"file": (file_name, file_content)},
headers=headers,
)
assert response.status_code == 201
assert response.json() == {
"flowId": str(flow_id),
"file_path": f"{flow_id}/{file_name}",
}
# Step 2: List files in the folder
response = client.get(f"api/v1/files/list/{flow_id}", headers=headers)
assert response.status_code == 200
assert file_name in response.json()["files"]
# Step 3: Download the file and verify its content
response = client.get(f"api/v1/files/download/{flow_id}/{file_name}", headers=headers)
assert response.status_code == 200
assert response.content == file_content
# the headers are application/octet-stream
assert response.headers["content-type"] == "application/octet-stream"
# mime_type is inside media_type
# Step 4: Delete the file
response = client.delete(f"api/v1/files/delete/{flow_id}/{file_name}", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": f"File {file_name} deleted successfully"}
# Verify that the file is indeed deleted
response = client.get(f"api/v1/files/list/{flow_id}", headers=headers)
assert file_name not in response.json()["files"]

View file

@ -0,0 +1,57 @@
import pytest
from langflow.template.field.base import Input
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.template.base import Template
@pytest.fixture
def sample_template_field() -> Input:
return Input(name="test_field", field_type="str")
@pytest.fixture
def sample_template(sample_template_field: Input) -> Template:
return Template(type_name="test_template", fields=[sample_template_field])
@pytest.fixture
def sample_frontend_node(sample_template: Template) -> FrontendNode:
return FrontendNode(
template=sample_template,
description="test description",
base_classes=["base_class1", "base_class2"],
name="test_frontend_node",
)
def test_template_field_defaults(sample_template_field: Input):
assert sample_template_field.field_type == "str"
assert sample_template_field.required is False
assert sample_template_field.placeholder == ""
assert sample_template_field.is_list is False
assert sample_template_field.show is True
assert sample_template_field.multiline is False
assert sample_template_field.value is None
assert sample_template_field.file_types == []
assert sample_template_field.file_path == ""
assert sample_template_field.password is False
assert sample_template_field.name == "test_field"
def test_template_to_dict(sample_template: Template, sample_template_field: Input):
template_dict = sample_template.to_dict()
assert template_dict["_type"] == "test_template"
assert len(template_dict) == 2 # _type and test_field
assert "test_field" in template_dict
assert "type" in template_dict["test_field"]
assert "required" in template_dict["test_field"]
def test_frontend_node_to_dict(sample_frontend_node: FrontendNode):
node_dict = sample_frontend_node.to_dict()
assert len(node_dict) == 1
assert "test_frontend_node" in node_dict
assert "description" in node_dict["test_frontend_node"]
assert "template" in node_dict["test_frontend_node"]
assert "base_classes" in node_dict["test_frontend_node"]

View file

@ -0,0 +1,418 @@
import copy
import json
import pickle
from typing import Type, Union
import pytest
from langflow.graph import Graph
from langflow.graph.edge.base import Edge
from langflow.graph.graph.utils import (
find_last_node,
process_flow,
set_new_target_handle,
ungroup_node,
update_source_handle,
update_target_handle,
update_template,
)
from langflow.graph.vertex.base import Vertex
from langflow.initial_setup.setup import load_starter_projects
from langflow.utils.payload import get_root_vertex
# Test cases for the graph module
# now we have three types of graph:
# 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.vertices if isinstance(node, node_type)), None)
def test_graph_structure(basic_graph):
assert isinstance(basic_graph, Graph)
assert len(basic_graph.vertices) > 0
assert len(basic_graph.edges) > 0
for node in basic_graph.vertices:
assert isinstance(node, Vertex)
for edge in basic_graph.edges:
assert isinstance(edge, Edge)
source_vertex = basic_graph.get_vertex(edge.source_id)
target_vertex = basic_graph.get_vertex(edge.target_id)
assert source_vertex in basic_graph.vertices
assert target_vertex in basic_graph.vertices
def test_circular_dependencies(basic_graph):
assert isinstance(basic_graph, Graph)
def check_circular(node, visited):
visited.add(node)
neighbors = basic_graph.get_vertices_with_target(node)
for neighbor in neighbors:
if neighbor in visited:
return True
if check_circular(neighbor, visited.copy()):
return True
return False
for node in basic_graph.vertices:
assert not check_circular(node, set())
def test_invalid_node_types():
graph_data = {
"nodes": [
{
"id": "1",
"data": {
"node": {
"base_classes": ["BaseClass"],
"template": {
"_type": "InvalidNodeType",
},
},
},
},
],
"edges": [],
}
with pytest.raises(Exception):
Graph(graph_data["nodes"], graph_data["edges"])
def test_get_vertices_with_target(basic_graph):
"""Test getting connected nodes"""
assert isinstance(basic_graph, Graph)
# Get root node
root = get_root_vertex(basic_graph)
assert root is not None
connected_nodes = basic_graph.get_vertices_with_target(root.id)
assert connected_nodes is not None
def test_get_node_neighbors_basic(basic_graph):
"""Test getting node neighbors"""
assert isinstance(basic_graph, Graph)
# Get root node
root = get_root_vertex(basic_graph)
assert root is not None
neighbors = basic_graph.get_vertex_neighbors(root)
assert neighbors is not None
assert isinstance(neighbors, dict)
# 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'
# data attribute in the type key
assert any("ConversationBufferMemory" in neighbor.data["type"] for neighbor, val in neighbors.items() if val)
assert any("OpenAI" in neighbor.data["type"] for neighbor, val in neighbors.items() if val)
def test_get_node(basic_graph):
"""Test getting a single node"""
node_id = basic_graph.vertices[0].id
node = basic_graph.get_vertex(node_id)
assert isinstance(node, Vertex)
assert node.id == node_id
def test_build_nodes(basic_graph):
"""Test building nodes"""
assert len(basic_graph.vertices) == len(basic_graph._vertices)
for node in basic_graph.vertices:
assert isinstance(node, Vertex)
def test_build_edges(basic_graph):
"""Test building edges"""
assert len(basic_graph.edges) == len(basic_graph._edges)
for edge in basic_graph.edges:
assert isinstance(edge, Edge)
assert isinstance(edge.source_id, str)
assert isinstance(edge.target_id, str)
def test_get_root_vertex(client, basic_graph, complex_graph):
"""Test getting root node"""
assert isinstance(basic_graph, Graph)
root = get_root_vertex(basic_graph)
assert root is not None
assert isinstance(root, Vertex)
assert root.data["type"] == "TimeTravelGuideChain"
# For complex example, the root node is a ZeroShotAgent too
assert isinstance(complex_graph, Graph)
root = get_root_vertex(complex_graph)
assert root is not None
assert isinstance(root, Vertex)
assert root.data["type"] == "ZeroShotAgent"
def test_validate_edges(basic_graph):
"""Test validating edges"""
assert isinstance(basic_graph, Graph)
# all edges should be valid
assert all(edge.valid for edge in basic_graph.edges)
def test_matched_type(basic_graph):
"""Test matched type attribute in Edge"""
assert isinstance(basic_graph, Graph)
# all edges should be valid
assert all(edge.valid for edge in basic_graph.edges)
# all edges should have a matched_type attribute
assert all(hasattr(edge, "matched_type") for edge in basic_graph.edges)
# The matched_type attribute should be in the source_types attr
assert all(edge.matched_type in edge.source_types for edge in basic_graph.edges)
def test_build_params(basic_graph):
"""Test building params"""
assert isinstance(basic_graph, Graph)
# all edges should be valid
assert all(edge.valid for edge in basic_graph.edges)
# all edges should have a matched_type attribute
assert all(hasattr(edge, "matched_type") for edge in basic_graph.edges)
# The matched_type attribute should be in the source_types attr
assert all(edge.matched_type in edge.source_types for edge in basic_graph.edges)
# Get the root node
root = get_root_vertex(basic_graph)
# Root node is a TimeTravelGuideChain
# which requires an llm and memory
assert root is not None
assert isinstance(root.params, dict)
assert "llm" in root.params
assert "memory" in root.params
# def test_wrapper_node_build(openapi_graph):
# wrapper_node = get_node_by_type(openapi_graph, WrapperVertex)
# assert wrapper_node is not None
# built_object = wrapper_node.build()
# assert built_object is not None
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"
@pytest.mark.asyncio
async def test_pickle_graph():
starter_projects = load_starter_projects()
data = starter_projects[0][1]["data"]
graph = Graph.from_payload(data)
assert isinstance(graph, Graph)
pickled = pickle.dumps(graph)
assert pickled is not None
unpickled = pickle.loads(pickled)
assert unpickled is not None

View file

@ -0,0 +1,79 @@
from langflow.components import helpers
from langflow.custom.utils import build_custom_component_template
from langflow.schema import Data
# def test_update_data_component():
# # Arrange
# update_data_component = helpers.UpdateDataComponent()
# # Act
# new_data = {"new_key": "new_value"}
# existing_data = Data(data={"existing_key": "existing_value"})
# result = update_data_component.build(existing_data, new_data)
# assert result.data == {"existing_key": "existing_value", "new_key": "new_value"}
# assert result.existing_key == "existing_value"
# assert result.new_key == "new_value"
# def test_document_to_data_component():
# # Arrange
# document_to_data_component = helpers.DocumentsToDataComponent()
# # Act
# # Replace with your actual test data
# document = Document(page_content="key: value", metadata={"url": "https://example.com"})
# result = document_to_data_component.build(document)
# # Assert
# # Replace with your actual expected result
# assert result == [Data(data={"text": "key: value", "url": "https://example.com"})]
def test_uuid_generator_component():
# Arrange
uuid_generator_component = helpers.IDGeneratorComponent()
uuid_generator_component.code = open(helpers.IDGenerator.__file__, "r").read()
frontend_node, _ = build_custom_component_template(uuid_generator_component)
# Act
build_config = frontend_node.get("template")
field_name = "unique_id"
build_config = uuid_generator_component.update_build_config(build_config, None, field_name)
unique_id = build_config["unique_id"]["value"]
result = uuid_generator_component.build(unique_id)
# Assert
# UUID should be a string of length 36
assert isinstance(result, str)
assert len(result) == 36
def test_data_as_text_component():
# Arrange
data_as_text_component = helpers.ParseDataComponent()
# Act
# Replace with your actual test data
data = [Data(data={"key": "value", "bacon": "eggs"})]
template = "Data:{data} -- Bacon:{bacon}"
data_as_text_component.set_attributes({"data": data, "template": template})
result = data_as_text_component.parse_data()
# Assert
# Replace with your actual expected result
assert result.text == "Data:{'key': 'value', 'bacon': 'eggs'} -- Bacon:eggs"
# def test_text_to_data_component():
# # Arrange
# text_to_data_component = helpers.CreateDataComponent()
# # Act
# # Replace with your actual test data
# dict_with_text = {"field_1": {"key": "value"}}
# result = text_to_data_component.build(number_of_fields=1, **dict_with_text)
# # Assert
# # Replace with your actual expected result
# assert result == Data(data={"key": "value"})

View file

@ -0,0 +1,88 @@
from datetime import datetime
from pathlib import Path
import pytest
from sqlmodel import select
from langflow.initial_setup.setup import STARTER_FOLDER_NAME, get_project_data, load_starter_projects
from langflow.services.database.models.folder.model import Folder
from langflow.services.deps import session_scope
def test_load_starter_projects():
projects = load_starter_projects()
assert isinstance(projects, list)
assert all(isinstance(project[1], dict) for project in projects)
assert all(isinstance(project[0], Path) for project in projects)
def test_get_project_data():
projects = load_starter_projects()
for _, project in projects:
(
project_name,
project_description,
project_is_component,
updated_at_datetime,
project_data,
project_icon,
project_icon_bg_color,
) = get_project_data(project)
assert isinstance(project_name, str)
assert isinstance(project_description, str)
assert isinstance(project_is_component, bool)
assert isinstance(updated_at_datetime, datetime)
assert isinstance(project_data, dict)
assert isinstance(project_icon, str) or project_icon is None
assert isinstance(project_icon_bg_color, str) or project_icon_bg_color is None
@pytest.mark.asyncio
async def test_create_or_update_starter_projects():
with session_scope() as session:
# Get the number of projects returned by load_starter_projects
num_projects = len(load_starter_projects())
# Get the number of projects in the database
folder = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
assert folder is not None
num_db_projects = len(folder.flows)
# Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
assert num_db_projects == num_projects
# Some starter projects require integration
# @pytest.mark.asyncio
# async def test_starter_projects_can_run_successfully(client):
# with session_scope() as session:
# # Run the function to create or update projects
# create_or_update_starter_projects()
# # Get the number of projects returned by load_starter_projects
# num_projects = len(load_starter_projects())
# # Get the number of projects in the database
# num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
# # Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
# assert num_db_projects == num_projects
# # Get all the starter projects
# projects = session.exec(select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)).all()
# graphs: list[tuple[str, Graph]] = []
# for project in projects:
# # Add tweaks to make file_path work
# tweaks = {"path": __file__}
# graph_data = process_tweaks(project.data, tweaks)
# graph_object = Graph.from_payload(graph_data, flow_id=project.id)
# graphs.append((project.name, graph_object))
# assert len(graphs) == len(projects)
# for name, graph in graphs:
# outputs = await graph.arun(
# inputs={},
# outputs=[],
# session_id="test",
# )
# assert all(isinstance(output, RunOutputs) for output in outputs), f"Project {name} error: {outputs}"
# delete_messages(session_id="test")

View file

@ -0,0 +1,107 @@
import pytest
from unittest.mock import MagicMock
from kubernetes.client import V1ObjectMeta, V1Secret
from base64 import b64encode
from uuid import UUID
from langflow.services.variable.kubernetes_secrets import KubernetesSecretManager, encode_user_id
@pytest.fixture
def mock_kube_config(mocker):
mocker.patch("kubernetes.config.load_kube_config")
mocker.patch("kubernetes.config.load_incluster_config")
@pytest.fixture
def secret_manager(mock_kube_config):
return KubernetesSecretManager(namespace="test-namespace")
def test_create_secret(secret_manager, mocker):
mocker.patch.object(
secret_manager.core_api,
"create_namespaced_secret",
return_value=V1Secret(metadata=V1ObjectMeta(name="test-secret")),
)
secret_manager.create_secret(name="test-secret", data={"key": "value"})
secret_manager.core_api.create_namespaced_secret.assert_called_once_with(
"test-namespace",
V1Secret(
api_version="v1",
kind="Secret",
metadata=V1ObjectMeta(name="test-secret"),
type="Opaque",
data={"key": b64encode("value".encode()).decode()},
),
)
def test_get_secret(secret_manager, mocker):
mock_secret = V1Secret(data={"key": b64encode("value".encode()).decode()})
mocker.patch.object(secret_manager.core_api, "read_namespaced_secret", return_value=mock_secret)
secret_data = secret_manager.get_secret(name="test-secret")
secret_manager.core_api.read_namespaced_secret.assert_called_once_with("test-secret", "test-namespace")
assert secret_data == {"key": "value"}
def test_delete_secret(secret_manager, mocker):
mocker.patch.object(secret_manager.core_api, "delete_namespaced_secret", return_value=MagicMock(status="Success"))
secret_manager.delete_secret(name="test-secret")
secret_manager.core_api.delete_namespaced_secret.assert_called_once_with("test-secret", "test-namespace")
def test_encode_uuid():
uuid = UUID("123e4567-e89b-12d3-a456-426614174000")
result = encode_user_id(uuid)
assert result == "uuid-123e4567-e89b-12d3-a456-426614174000"
assert len(result) < 253
assert result[0].isalnum()
assert result[-1].isalnum()
def test_encode_string():
string_id = "user@example.com"
result = encode_user_id(string_id)
# assert (result.isalnum() or '-' in result or '_' in result)
assert len(result) < 253
assert result[0].isalnum()
assert result[-1].isalnum()
def test_long_string():
long_string = "a" * 300
result = encode_user_id(long_string)
assert len(result) <= 253
def test_starts_with_non_alphanumeric():
non_alnum_start = "+user123"
result = encode_user_id(non_alnum_start)
assert result[0].isalnum()
def test_ends_with_non_alphanumeric():
non_alnum_end = "user123+"
result = encode_user_id(non_alnum_end)
assert result[-1].isalnum()
def test_email_address():
email = "User.Name@Example.com"
result = encode_user_id(email)
assert result.isalnum() or "-" in result or "_" in result
assert len(result) < 253
assert result[0].isalnum()
assert result[-1].isalnum()
def test_uuid_case_insensitivity():
uuid_upper = UUID("123E4567-E89B-12D3-A456-426614174000")
uuid_lower = UUID("123e4567-e89b-12d3-a456-426614174000")
result_upper = encode_user_id(uuid_upper)
result_lower = encode_user_id(uuid_lower)
assert result_upper == result_lower

View file

@ -0,0 +1,32 @@
import pytest
from langflow.graph import Graph
from langflow.initial_setup.setup import load_starter_projects
from langflow.load import load_flow_from_json
@pytest.mark.noclient
def test_load_flow_from_json():
"""Test loading a flow from a json file"""
loaded = load_flow_from_json(pytest.BASIC_EXAMPLE_PATH)
assert loaded is not None
assert isinstance(loaded, Graph)
@pytest.mark.noclient
def test_load_flow_from_json_with_tweaks():
"""Test loading a flow from a json file and applying tweaks"""
tweaks = {"dndnode_82": {"model_name": "gpt-3.5-turbo-16k-0613"}}
loaded = load_flow_from_json(pytest.BASIC_EXAMPLE_PATH, tweaks=tweaks)
assert loaded is not None
assert isinstance(loaded, Graph)
@pytest.mark.noclient
def test_load_flow_from_json_object():
"""Test loading a flow from a json file and applying tweaks"""
_, projects = zip(*load_starter_projects())
project = projects[0]
loaded = load_flow_from_json(project)
assert loaded is not None
assert isinstance(loaded, Graph)

View file

@ -0,0 +1,100 @@
import pytest
import os
import json
from unittest.mock import patch
from langflow.utils.logger import SizedLogBuffer
@pytest.fixture
def sized_log_buffer():
return SizedLogBuffer()
def test_init_default():
buffer = SizedLogBuffer()
assert buffer.max == 0
assert buffer._max_readers == 20
def test_init_with_env_variable():
with patch.dict(os.environ, {"LANGFLOW_LOG_RETRIEVER_BUFFER_SIZE": "100"}):
buffer = SizedLogBuffer()
assert buffer.max == 100
def test_write(sized_log_buffer):
message = json.dumps({"text": "Test log", "record": {"time": {"timestamp": 1625097600.1244334}}})
sized_log_buffer.max = 1 # Set max size to 1 for testing
sized_log_buffer.write(message)
assert len(sized_log_buffer.buffer) == 1
assert 1625097600124 == sized_log_buffer.buffer[0][0]
assert "Test log" == sized_log_buffer.buffer[0][1]
def test_write_overflow(sized_log_buffer):
sized_log_buffer.max = 2
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(3)]
for message in messages:
sized_log_buffer.write(message)
assert len(sized_log_buffer.buffer) == 2
assert 1625097601000 == sized_log_buffer.buffer[0][0]
assert 1625097602000 == sized_log_buffer.buffer[1][0]
def test_len(sized_log_buffer):
sized_log_buffer.max = 3
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(3)]
for message in messages:
sized_log_buffer.write(message)
assert len(sized_log_buffer) == 3
def test_get_after_timestamp(sized_log_buffer):
sized_log_buffer.max = 5
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(5)]
for message in messages:
sized_log_buffer.write(message)
result = sized_log_buffer.get_after_timestamp(1625097602000, lines=2)
assert len(result) == 2
assert 1625097603000 in result
assert 1625097602000 in result
def test_get_before_timestamp(sized_log_buffer):
sized_log_buffer.max = 5
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(5)]
for message in messages:
sized_log_buffer.write(message)
result = sized_log_buffer.get_before_timestamp(1625097603000, lines=2)
assert len(result) == 2
assert 1625097601000 in result
assert 1625097602000 in result
def test_get_last_n(sized_log_buffer):
sized_log_buffer.max = 5
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(5)]
for message in messages:
sized_log_buffer.write(message)
result = sized_log_buffer.get_last_n(3)
assert len(result) == 3
assert 1625097602000 in result
assert 1625097603000 in result
assert 1625097604000 in result
def test_enabled(sized_log_buffer):
assert not sized_log_buffer.enabled()
sized_log_buffer.max = 1
assert sized_log_buffer.enabled()
def test_max_size(sized_log_buffer):
assert sized_log_buffer.max_size() == 0
sized_log_buffer.max = 100
assert sized_log_buffer.max_size() == 100

View file

@ -0,0 +1,45 @@
import pytest
from langflow.services.auth.utils import get_password_hash
from langflow.services.database.models.user import User
from langflow.services.deps import session_scope
from sqlalchemy.exc import IntegrityError
@pytest.fixture
def test_user():
return User(
username="testuser",
password=get_password_hash("testpassword"), # Assuming password needs to be hashed
is_active=True,
is_superuser=False,
)
def test_login_successful(client, test_user):
# Adding the test user to the database
try:
with session_scope() as session:
session.add(test_user)
session.commit()
except IntegrityError:
pass
response = client.post("api/v1/login", data={"username": "testuser", "password": "testpassword"})
assert response.status_code == 200
assert "access_token" in response.json()
def test_login_unsuccessful_wrong_username(client):
response = client.post("api/v1/login", data={"username": "wrongusername", "password": "testpassword"})
assert response.status_code == 401
assert response.json()["detail"] == "Incorrect username or password"
def test_login_unsuccessful_wrong_password(client, test_user, session):
# Adding the test user to the database
session.add(test_user)
session.commit()
response = client.post("api/v1/login", data={"username": "testuser", "password": "wrongpassword"})
assert response.status_code == 401
assert response.json()["detail"] == "Incorrect username or password"

View file

@ -0,0 +1,102 @@
import pytest
from langflow.memory import add_messages, add_messagetables, delete_messages, get_messages, store_message
from langflow.schema.message import Message
# Assuming you have these imports available
from langflow.services.database.models.message import MessageCreate, MessageRead
from langflow.services.database.models.message.model import MessageTable
from langflow.services.deps import session_scope
from langflow.services.tracing.utils import convert_to_langchain_type
@pytest.fixture()
def created_message():
with session_scope() as session:
message = MessageCreate(text="Test message", sender="User", sender_name="User", session_id="session_id")
messagetable = MessageTable.model_validate(message, from_attributes=True)
messagetables = add_messagetables([messagetable], session)
message_read = MessageRead.model_validate(messagetables[0], from_attributes=True)
return message_read
@pytest.fixture()
def created_messages(session):
with session_scope() as session:
messages = [
MessageCreate(text="Test message 1", sender="User", sender_name="User", session_id="session_id2"),
MessageCreate(text="Test message 2", sender="User", sender_name="User", session_id="session_id2"),
MessageCreate(text="Test message 3", sender="User", sender_name="User", session_id="session_id2"),
]
messagetables = [MessageTable.model_validate(message, from_attributes=True) for message in messages]
messagetables = add_messagetables(messagetables, session)
messages_read = [
MessageRead.model_validate(messagetable, from_attributes=True) for messagetable in messagetables
]
return messages_read
def test_get_messages():
add_messages(
[
Message(text="Test message 1", sender="User", sender_name="User", session_id="session_id2"),
Message(text="Test message 2", sender="User", sender_name="User", session_id="session_id2"),
]
)
messages = get_messages(sender="User", session_id="session_id2", limit=2)
assert len(messages) == 2
assert messages[0].text == "Test message 1"
assert messages[1].text == "Test message 2"
def test_add_messages():
message = Message(text="New Test message", sender="User", sender_name="User", session_id="new_session_id")
messages = add_messages(message)
assert len(messages) == 1
assert messages[0].text == "New Test message"
def test_add_messagetables(session):
messages = [MessageTable(text="New Test message", sender="User", sender_name="User", session_id="new_session_id")]
added_messages = add_messagetables(messages, session)
assert len(added_messages) == 1
assert added_messages[0].text == "New Test message"
def test_delete_messages(session):
session_id = "session_id2"
delete_messages(session_id)
messages = session.query(MessageTable).filter(MessageTable.session_id == session_id).all()
assert len(messages) == 0
def test_store_message():
message = Message(text="Stored message", sender="User", sender_name="User", session_id="stored_session_id")
stored_messages = store_message(message)
assert len(stored_messages) == 1
assert stored_messages[0].text == "Stored message"
@pytest.mark.parametrize("method_name", ["message", "convert_to_langchain_type"])
def test_convert_to_langchain(method_name):
def convert(value):
if method_name == "message":
return value.to_lc_message()
elif method_name == "convert_to_langchain_type":
return convert_to_langchain_type(value)
else:
raise ValueError(f"Invalid method: {method_name}")
lc_message = convert(Message(text="Test message 1", sender="User", sender_name="User", session_id="session_id2"))
assert lc_message.content == "Test message 1"
assert lc_message.type == "human"
lc_message = convert(Message(text="Test message 2", sender="AI", session_id="session_id2"))
assert lc_message.content == "Test message 2"
assert lc_message.type == "ai"
iterator = iter(["stream", "message"])
lc_message = convert(Message(text=iterator, sender="AI", session_id="session_id2"))
assert lc_message.content == ""
assert lc_message.type == "ai"
assert len(list(iterator)) == 2

View file

@ -0,0 +1,303 @@
import pytest
from langflow.processing.process import process_tweaks
from langflow.services.deps import get_session_service
def test_no_tweaks():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1},
"param2": {"value": 2},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3},
"param2": {"value": 4},
}
}
},
},
]
}
}
tweaks = {}
result = process_tweaks(graph_data, tweaks)
assert result == graph_data
def test_single_tweak():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
tweaks = {"node1": {"param1": 5}}
expected_result = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 5, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
result = process_tweaks(graph_data, tweaks)
assert result == expected_result
def test_multiple_tweaks():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
tweaks = {
"node1": {"param1": 5, "param2": 6},
"node2": {"param1": 7},
}
expected_result = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 5, "type": "int"},
"param2": {"value": 6, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 7, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
result = process_tweaks(graph_data, tweaks)
assert result == expected_result
# Test twekas that just pass the param and value but no node id.
# This is a new feature that was added to the process_tweaks function
def test_tweak_no_node_id():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
tweaks = {"param1": 5}
expected_result = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 5, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 5, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
result = process_tweaks(graph_data, tweaks)
assert result == expected_result
def test_tweak_not_in_template():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
tweaks = {"node1": {"param3": 5}}
result = process_tweaks(graph_data, tweaks)
assert result == graph_data
@pytest.mark.asyncio
async def test_load_langchain_object_with_cached_session(client, basic_graph_data):
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = "non-existent-session-id"
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
assert graph1 == graph2
assert artifacts1 == artifacts2
@pytest.mark.asyncio
async def test_load_langchain_object_with_no_cached_session(client, basic_graph_data):
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = "non-existent-session-id"
session_id = session_service.build_key(session_id1, basic_graph_data)
graph1, artifacts1 = await session_service.load_session(session_id, data_graph=basic_graph_data, flow_id="flow_id")
# Clear the cache
await session_service.clear_session(session_id)
# Use the new session_id to get the graph again
graph2, artifacts2 = await session_service.load_session(session_id, data_graph=basic_graph_data, flow_id="flow_id")
# Since the cache was cleared, objects should be different
assert id(graph1) != id(graph2)
@pytest.mark.asyncio
async def test_load_langchain_object_without_session_id(client, basic_graph_data):
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = None
graph1, artifacts1 = await session_service.load_session(session_id1, data_graph=basic_graph_data, flow_id="flow_id")
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(session_id1, data_graph=basic_graph_data, flow_id="flow_id")
assert graph1 == graph2

View file

@ -0,0 +1,132 @@
from unittest.mock import MagicMock, patch
from langflow.services.settings.constants import (
DEFAULT_SUPERUSER,
DEFAULT_SUPERUSER_PASSWORD,
)
from langflow.services.utils import teardown_superuser
# @patch("langflow.services.deps.get_session")
# @patch("langflow.services.utils.create_super_user")
# @patch("langflow.services.deps.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
# 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
# )
# 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
# 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
# assert str(actual_expr) == str(expected_expr)
@patch("langflow.services.deps.get_settings_service")
@patch("langflow.services.deps.get_session")
def test_teardown_superuser_default_superuser(mock_get_session, mock_get_settings_service):
mock_settings_service = MagicMock()
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 = MagicMock()
mock_user = MagicMock()
mock_user.is_superuser = True
mock_session.query.return_value.filter.return_value.first.return_value = mock_user
mock_get_session.return_value = iter([mock_session])
teardown_superuser(mock_settings_service, mock_session)
mock_session.query.assert_not_called()
@patch("langflow.services.deps.get_settings_service")
@patch("langflow.services.deps.get_session")
def test_teardown_superuser_no_default_superuser(mock_get_session, mock_get_settings_service):
ADMIN_USER_NAME = "admin_user"
mock_settings_service = MagicMock()
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_get_settings_service.return_value = mock_settings_service
mock_session = MagicMock()
mock_user = MagicMock()
mock_user.is_superuser = False
mock_session.query.return_value.filter.return_value.first.return_value = mock_user
mock_get_session.return_value = [mock_session]
teardown_superuser(mock_settings_service, mock_session)
mock_session.query.assert_not_called()
mock_session.delete.assert_not_called()
mock_session.commit.assert_not_called()

View file

@ -0,0 +1,112 @@
import pytest
import threading
from langflow.services.telemetry.opentelemetry import OpenTelemetry
from concurrent.futures import ThreadPoolExecutor, as_completed
fixed_labels = {"flow_id": "this_flow_id", "service": "this", "user": "that"}
@pytest.fixture
def opentelemetry_instance():
return OpenTelemetry()
def test_init(opentelemetry_instance):
assert isinstance(opentelemetry_instance, OpenTelemetry)
assert len(opentelemetry_instance._metrics) > 1
assert len(opentelemetry_instance._metrics) == len(opentelemetry_instance._metrics_registry) == 2
assert "file_uploads" in opentelemetry_instance._metrics
def test_gauge(opentelemetry_instance):
opentelemetry_instance.update_gauge("file_uploads", 1024, fixed_labels)
def test_gauge_with_counter_method(opentelemetry_instance):
with pytest.raises(ValueError, match="Metric 'file_uploads' is not a counter"):
opentelemetry_instance.increment_counter(metric_name="file_uploads", value=1, labels=fixed_labels)
def test_gauge_with_historgram_method(opentelemetry_instance):
with pytest.raises(ValueError, match="Metric 'file_uploads' is not a histogram"):
opentelemetry_instance.observe_histogram("file_uploads", 1, fixed_labels)
def test_gauge_with_up_down_counter_method(opentelemetry_instance):
with pytest.raises(ValueError, match="Metric 'file_uploads' is not an up down counter"):
opentelemetry_instance.up_down_counter("file_uploads", 1, labels=fixed_labels)
def test_increment_counter(opentelemetry_instance):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", value=5, labels=fixed_labels)
def test_increment_counter_empty_label(opentelemetry_instance):
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", value=5, labels={})
def test_increment_counter_missing_mandatory_label(opentelemetry_instance):
with pytest.raises(ValueError, match="Missing required labels: {'flow_id'}"):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", value=5, labels={"service": "one"})
def test_increment_counter_unregisted_metric(opentelemetry_instance):
with pytest.raises(ValueError, match="Metric 'num_files_uploaded_1' is not registered"):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded_1", value=5, labels=fixed_labels)
def test_opentelementry_singleton(opentelemetry_instance):
opentelemetry_instance_2 = OpenTelemetry()
assert opentelemetry_instance is opentelemetry_instance_2
opentelemetry_instance_3 = OpenTelemetry(prometheus_enabled=False)
assert opentelemetry_instance is opentelemetry_instance_3
assert opentelemetry_instance.prometheus_enabled == opentelemetry_instance_3.prometheus_enabled
def test_missing_labels(opentelemetry_instance):
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", labels=None, value=1.0)
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.up_down_counter("num_files_uploaded", 1, None)
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.update_gauge(metric_name="num_files_uploaded", value=1.0, labels=dict())
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.observe_histogram("num_files_uploaded", 1, dict())
def test_multithreaded_singleton():
def create_instance():
return OpenTelemetry()
# Create instances in multiple threads
with ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(create_instance) for _ in range(100)]
instances = [future.result() for future in as_completed(futures)]
# Check that all instances are the same
first_instance = instances[0]
for instance in instances[1:]:
assert instance is first_instance
def test_multithreaded_singleton_race_condition():
# This test simulates a potential race condition
start_event = threading.Event()
def create_instance():
start_event.wait() # Wait for all threads to be ready
return OpenTelemetry()
# Create instances in multiple threads, all starting at the same time
with ThreadPoolExecutor(max_workers=100) as executor:
futures = [executor.submit(create_instance) for _ in range(100)]
start_event.set() # Start all threads simultaneously
instances = [future.result() for future in as_completed(futures)]
# Check that all instances are the same
first_instance = instances[0]
for instance in instances[1:]:
assert instance is first_instance

View file

@ -0,0 +1,93 @@
import importlib
from typing import Dict, List, Optional
import pytest
from langflow.utils.util import build_template_from_function, get_base_classes, get_default_factory
from pydantic import BaseModel
# Dummy classes for testing purposes
class Parent(BaseModel):
"""Parent Class"""
parent_field: str
class Child(Parent):
"""Child Class"""
child_field: int
class ExampleClass1(BaseModel):
"""Example class 1."""
def __init__(self, data: Optional[List[int]] = None):
self.data = data or [1, 2, 3]
class ExampleClass2(BaseModel):
"""Example class 2."""
def __init__(self, data: Optional[Dict[str, int]] = None):
self.data = data or {"a": 1, "b": 2, "c": 3}
def example_loader_1() -> ExampleClass1:
"""Example loader function 1."""
return ExampleClass1()
def example_loader_2() -> ExampleClass2:
"""Example loader function 2."""
return ExampleClass2()
def test_build_template_from_function():
type_to_loader_dict = {
"example1": example_loader_1,
"example2": example_loader_2,
}
# Test with valid name
result = build_template_from_function("ExampleClass1", type_to_loader_dict)
assert result is not None
assert "template" in result
assert "description" in result
assert "base_classes" in result
# Test with add_function=True
result_with_function = build_template_from_function("ExampleClass1", type_to_loader_dict, add_function=True)
assert result_with_function is not None
assert "Callable" in result_with_function["base_classes"]
# Test with invalid name
with pytest.raises(ValueError, match=r".* not found"):
build_template_from_function("NonExistent", type_to_loader_dict)
# Test get_base_classes
def test_get_base_classes():
base_classes_parent = get_base_classes(Parent)
base_classes_child = get_base_classes(Child)
assert "Parent" in base_classes_parent
assert "Child" in base_classes_child
assert "Parent" in base_classes_child
# Test get_default_factory
def test_get_default_factory():
module_name = "langflow.utils.util"
function_repr = "<function dummy_function>"
def dummy_function():
return "default_value"
# Add dummy_function to your_module
setattr(importlib.import_module(module_name), "dummy_function", dummy_function)
default_value = get_default_factory(module_name, function_repr)
assert default_value == "default_value"

View file

@ -0,0 +1,104 @@
from pathlib import Path
from unittest import mock
import pytest
from requests.exceptions import MissingSchema
from langflow.utils.validate import create_function, execute_function, extract_function_name, validate_code
def test_create_function():
code = """
from pathlib import Path
def my_function(x: str) -> Path:
return Path(x)
"""
function_name = extract_function_name(code)
function = create_function(code, function_name)
result = function("test")
assert result == Path("test")
def test_validate_code():
# Test case with a valid import and function
code1 = """
import math
def square(x):
return x ** 2
"""
errors1 = validate_code(code1)
assert errors1 == {"imports": {"errors": []}, "function": {"errors": []}}
# Test case with an invalid import and valid function
code2 = """
import non_existent_module
def square(x):
return x ** 2
"""
errors2 = validate_code(code2)
assert errors2 == {
"imports": {"errors": ["No module named 'non_existent_module'"]},
"function": {"errors": []},
}
# Test case with a valid import and invalid function syntax
code3 = """
import math
def square(x)
return x ** 2
"""
errors3 = validate_code(code3)
assert errors3 == {
"imports": {"errors": []},
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
}
def test_execute_function_success():
code = """
import math
def my_function(x):
return math.sin(x) + 1
"""
result = execute_function(code, "my_function", 0.5)
assert result == 1.479425538604203
def test_execute_function_missing_module():
code = """
import some_missing_module
def my_function(x):
return some_missing_module.some_function(x)
"""
with pytest.raises(ModuleNotFoundError):
execute_function(code, "my_function", 0.5)
def test_execute_function_missing_function():
code = """
import math
def my_function(x):
return math.some_missing_function(x)
"""
with pytest.raises(AttributeError):
execute_function(code, "my_function", 0.5)
def test_execute_function_missing_schema():
code = """
import requests
def my_function(x):
return requests.get(x).text
"""
with mock.patch("requests.get", side_effect=MissingSchema):
with pytest.raises(MissingSchema):
execute_function(code, "my_function", "invalid_url")

View file

@ -0,0 +1,15 @@
from langflow.components import prototypes
def test_python_function_component():
# Arrange
python_function_component = prototypes.PythonFunctionComponent()
# Act
# function must be a string representation
function = "def function():\n return 'Hello, World!'"
# result is the callable function
result = python_function_component.build(function)
# Assert
assert result() == "Hello, World!"