feat: add dump and dumps methods to Graph (#3202)

* feat(utils.py): add escape_json_dump function to escape JSON strings for Edge dictionaries

* refactor(Output): streamline add_types method to prevent duplicate entries in types list for improved type management

* feat(data.py): add classmethod decorator to validate_data for enhanced validation logic when checking data types

* feat(setup.py): implement retry logic for loading starter projects to enhance robustness against JSON decode errors

* fix(input_mixin.py): improve model_config formatting and update field_type alias for clarity and consistency in field definitions

* feat(types.py): refactor vertex constructors to use NodeData and add input/output methods for better component interaction

* feat(schema.py): add NodeData and Position TypedDicts for improved type safety and structure in vertex data handling

* feat(base.py): update Vertex to use NodeData type and add to_data method for better data management and access

* refactor(schema.py): update TargetHandle and SourceHandle models to include model_config attribute

* Add TypedDict classes for graph schema serialization in `schema.py`

* Refactor `Edge` class to improve handle validation and data handling

- Consolidated imports and removed redundant `BaseModel` definitions for `SourceHandle` and `TargetHandle`.
- Added `valid_handles`, `target_param`, and `_target_handle` attributes to `Edge` class.
- Enhanced handle validation logic to distinguish between dictionary and string types.
- Introduced `to_data` method to return edge data.
- Updated attribute names to follow consistent naming conventions (`base_classes`, `input_types`, `field_name`).

* Refactor `Edge` class to improve handle validation and data handling

* Refactor: Standardize attribute naming and add `to_data` method in Edge class

- Renamed attributes to use snake_case consistently (`baseClasses` to `base_classes`, `inputTypes` to `input_types`, `fieldName` to `field_name`).
- Added `to_data` method to return `_data` attribute.
- Updated validation methods to use new attribute names.

* Refactor: Update Edge class to consistently use snake_case for attributes and improve validation logic for handles

* Refactor: Change node argument type in add_node and _create_vertex methods to NodeData for better type safety and clarity

* Refactor: Implement JSON serialization for graph data with `dumps` and `dump` methods, enhancing data export capabilities

* Refactor: Add pytest fixtures for ingestion and RAG graphs, enhance test structure for better clarity and organization

* Refactor: Add pytest fixtures for memory_chatbot_graph tests and improve test structure

* Refactor: Remove unused methods in ComponentVertex class to streamline code and improve readability

* Refactor: Remove unnecessary line in ComponentVertex class to enhance code clarity and maintainability

* Refactor: Update import path for DefaultPromptField to improve code organization and maintainability in api_utils.py

* Refactor: Update import path for DefaultPromptField to enhance code organization and maintainability in prompt.py

* fix: Remove  fixture in test_memory_chatbot.py that blocked db setup

* Refactor: Add durations path for unit tests to improve test reporting

* Refactor: Add splitting algorithm option for unit tests

* Add async option to Makefile for unit tests and update GitHub Actions workflow

- Introduced `async` variable in Makefile to conditionally run unit tests with or without parallel execution.
- Updated `unit_tests` target in Makefile to handle `async` flag.
- Modified GitHub Actions workflow to set `async=false` for unit tests.
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-08-05 18:00:46 -03:00 committed by GitHub
commit bb1bc5c2df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 797 additions and 107 deletions

View file

@ -50,7 +50,7 @@ jobs:
with:
timeout_minutes: 12
max_attempts: 2
command: make unit_tests args="--splits ${{ matrix.splitCount }} --group ${{ matrix.group }}"
command: make unit_tests async=false args="--splits ${{ matrix.splitCount }} --group ${{ matrix.group }}"
test-cli:
name: Test CLI - Python ${{ matrix.python-version }}

View file

@ -18,7 +18,7 @@ env ?= .env
open_browser ?= true
path = src/backend/base/langflow/frontend
workers ?= 1
async ?= true
all: help
######################
@ -130,14 +130,25 @@ coverage: ## run the tests and generate a coverage report
@poetry run coverage erase
unit_tests: ## run unit tests
ifeq ($(async), true)
poetry run pytest src/backend/tests \
--ignore=src/backend/tests/integration \
--instafail -ra -n auto -m "not api_key_required" \
--instafail -n auto -ra -m "not api_key_required" \
--durations-path src/backend/tests/.test_durations \
--splitting-algorithm least_duration \
$(args)
else
poetry run pytest src/backend/tests \
--ignore=src/backend/tests/integration \
--instafail -ra -m "not api_key_required" \
--durations-path src/backend/tests/.test_durations \
--splitting-algorithm least_duration \
$(args)
endif
integration_tests: ## run integration tests
poetry run pytest src/backend/tests/integration \
--instafail -ra -n auto \
--instafail -ra \
$(args)
tests: ## run unit, integration, coverage tests

View file

@ -6,7 +6,7 @@ from langchain_core.prompts import PromptTemplate
from loguru import logger
from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.template.field.prompt import DefaultPromptField
from langflow.inputs.inputs import DefaultPromptField
_INVALID_CHARACTERS = {

View file

@ -1,51 +1,31 @@
from typing import TYPE_CHECKING, Any, List, Optional, cast
from typing import TYPE_CHECKING, Any, cast
from loguru import logger
from pydantic import BaseModel, Field, field_validator
from langflow.graph.edge.schema import EdgeData
from langflow.graph.edge.schema import EdgeData, SourceHandle, TargetHandle, TargetHandleDict
from langflow.schema.schema import INPUT_FIELD_NAME
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
class SourceHandle(BaseModel):
baseClasses: list[str] = Field(default_factory=list, description="List of base classes for the source handle.")
dataType: str = Field(..., description="Data type for the source handle.")
id: str = Field(..., description="Unique identifier for the source handle.")
name: Optional[str] = Field(None, description="Name of the source handle.")
output_types: List[str] = Field(default_factory=list, description="List of output types for the source handle.")
@field_validator("name", mode="before")
@classmethod
def validate_name(cls, v, _info):
if _info.data["dataType"] == "GroupNode":
# 'OpenAIModel-u4iGV_text_output'
splits = v.split("_", 1)
if len(splits) != 2:
raise ValueError(f"Invalid source handle name {v}")
v = splits[1]
return v
class TargetHandle(BaseModel):
fieldName: str = Field(..., description="Field name for the target handle.")
id: str = Field(..., description="Unique identifier for the target handle.")
inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.")
type: str = Field(..., description="Type of the target handle.")
class Edge:
def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData):
self.source_id: str = source.id if source else ""
self.target_id: str = target.id if target else ""
self.valid_handles: bool = False
self.target_param: str | None = None
self._target_handle: TargetHandleDict | str | None = None
self._data = edge.copy()
if data := edge.get("data", {}):
self._source_handle = data.get("sourceHandle", {})
self._target_handle = data.get("targetHandle", {})
self._target_handle = cast(TargetHandleDict, data.get("targetHandle", {}))
self.source_handle: SourceHandle = SourceHandle(**self._source_handle)
self.target_handle: TargetHandle = TargetHandle(**self._target_handle)
self.target_param = self.target_handle.fieldName
if isinstance(self._target_handle, dict):
self.target_handle: TargetHandle = TargetHandle(**self._target_handle)
else:
raise ValueError("Target handle is not a dictionary")
self.target_param = self.target_handle.field_name
# validate handles
self.validate_handles(source, target)
else:
@ -55,23 +35,31 @@ class Edge:
self._target_handle = edge.get("targetHandle", "") # type: ignore
# 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD'
# target_param is documents
self.target_param = cast(str, self._target_handle.split("|")[1]) # type: ignore
if isinstance(self._target_handle, str):
self.target_param = self._target_handle.split("|")[1]
self.source_handle = None
self.target_handle = None
else:
raise ValueError("Target handle is not a string")
# Validate in __init__ to fail fast
self.validate_edge(source, target)
def to_data(self):
return self._data
def validate_handles(self, source, target) -> None:
if isinstance(self._source_handle, str) or self.source_handle.baseClasses:
if isinstance(self._source_handle, str) or self.source_handle.base_classes:
self._legacy_validate_handles(source, target)
else:
self._validate_handles(source, target)
def _validate_handles(self, source, target) -> None:
if self.target_handle.inputTypes is None:
if self.target_handle.input_types is None:
self.valid_handles = self.target_handle.type in self.source_handle.output_types
elif self.source_handle.output_types is not None:
self.valid_handles = (
any(output_type in self.target_handle.inputTypes for output_type in self.source_handle.output_types)
any(output_type in self.target_handle.input_types for output_type in self.source_handle.output_types)
or self.target_handle.type in self.source_handle.output_types
)
@ -81,12 +69,12 @@ class Edge:
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles")
def _legacy_validate_handles(self, source, target) -> None:
if self.target_handle.inputTypes is None:
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
if self.target_handle.input_types is None:
self.valid_handles = self.target_handle.type in self.source_handle.base_classes
else:
self.valid_handles = (
any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses)
or self.target_handle.type in self.source_handle.baseClasses
any(baseClass in self.target_handle.input_types for baseClass in self.source_handle.base_classes)
or self.target_handle.type in self.source_handle.base_classes
)
if not self.valid_handles:
logger.debug(self.source_handle)
@ -101,9 +89,9 @@ class Edge:
self.target_handle = state.get("target_handle")
def validate_edge(self, source, target) -> None:
# If the self.source_handle has baseClasses, then we are using the legacy
# If the self.source_handle has base_classes, then we are using the legacy
# way of defining the source and target handles
if isinstance(self._source_handle, str) or self.source_handle.baseClasses:
if isinstance(self._source_handle, str) or self.source_handle.base_classes:
self._legacy_validate_edge(source, target)
else:
self._validate_edge(source, target)
@ -230,5 +218,5 @@ class ContractEdge(Edge):
if (hasattr(self, "source_handle") and self.source_handle) and (
hasattr(self, "target_handle") and self.target_handle
):
return f"{self.source_id} -[{self.source_handle.name}->{self.target_handle.fieldName}]-> {self.target_id}"
return f"{self.source_id} -[{self.source_handle.name}->{self.target_handle.field_name}]-> {self.target_id}"
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"

View file

@ -1,6 +1,6 @@
from typing import Any, List, Optional
from pydantic import Field, field_validator
from pydantic import ConfigDict, Field, field_validator
from typing_extensions import TypedDict
from langflow.helpers.base_model import BaseModel
@ -39,7 +39,8 @@ class Payload(BaseModel):
class TargetHandle(BaseModel):
fieldName: str = Field(..., alias="fieldName", description="Field name for the target handle.")
model_config = ConfigDict(populate_by_name=True)
field_name: str = Field(..., alias="fieldName", description="Field name for the target handle.")
id: str = Field(..., description="Unique identifier for the target handle.")
input_types: List[str] = Field(
default_factory=list, alias="inputTypes", description="List of input types for the target handle."
@ -48,6 +49,7 @@ class TargetHandle(BaseModel):
class SourceHandle(BaseModel):
model_config = ConfigDict(populate_by_name=True)
base_classes: list[str] = Field(
default_factory=list, alias="baseClasses", description="List of base classes for the source handle."
)

View file

@ -1,4 +1,5 @@
import asyncio
import json
import uuid
from collections import defaultdict, deque
from datetime import datetime, timezone
@ -14,11 +15,12 @@ from langflow.graph.edge.base import ContractEdge
from langflow.graph.edge.schema import EdgeData
from langflow.graph.graph.constants import Finish, lazy_load_vertex_dict
from langflow.graph.graph.runnable_vertices_manager import RunnableVerticesManager
from langflow.graph.graph.schema import VertexBuildResult
from langflow.graph.graph.schema import GraphData, GraphDump, VertexBuildResult
from langflow.graph.graph.state_manager import GraphStateManager
from langflow.graph.graph.utils import find_start_component_id, process_flow, sort_up_to_vertex
from langflow.graph.schema import InterfaceComponentTypes, RunOutputs
from langflow.graph.vertex.base import Vertex, VertexStates
from langflow.graph.vertex.schema import NodeData
from langflow.graph.vertex.types import ComponentVertex, InterfaceVertex, StateVertex
from langflow.schema import Data
from langflow.schema.schema import INPUT_FIELD_NAME, InputType
@ -75,7 +77,7 @@ class Graph:
self.vertices: List[Vertex] = []
self.run_manager = RunnableVerticesManager()
self.state_manager = GraphStateManager()
self._vertices: List[dict] = []
self._vertices: List[NodeData] = []
self._edges: List[EdgeData] = []
self.top_level_vertices: List[str] = []
self.vertex_map: Dict[str, Vertex] = {}
@ -86,6 +88,7 @@ class Graph:
self._run_queue: deque[str] = deque()
self._first_layer: List[str] = []
self._lock = asyncio.Lock()
self.raw_graph_data: GraphData = {"nodes": [], "edges": []}
try:
self.tracing_service: "TracingService" | None = get_tracing_service()
except Exception as exc:
@ -97,7 +100,39 @@ class Graph:
if (start is not None and end is None) or (start is None and end is not None):
raise ValueError("You must provide both input and output components")
def add_nodes_and_edges(self, nodes: List[Dict], edges: List[EdgeData]):
def dumps(
self,
name: Optional[str] = None,
description: Optional[str] = None,
endpoint_name: Optional[str] = None,
) -> str:
graph_dict = self.dump(name, description, endpoint_name)
return json.dumps(graph_dict, indent=4, sort_keys=True)
def dump(
self, name: Optional[str] = None, description: Optional[str] = None, endpoint_name: Optional[str] = None
) -> GraphDump:
if self.raw_graph_data != {"nodes": [], "edges": []}:
data_dict = self.raw_graph_data
else:
# we need to convert the vertices and edges to json
nodes = [node.to_data() for node in self.vertices]
edges = [edge.to_data() for edge in self.edges]
self.raw_graph_data = {"nodes": nodes, "edges": edges}
data_dict = self.raw_graph_data
graph_dict: GraphDump = {
"data": data_dict,
"is_component": len(data_dict.get("nodes", [])) == 1 and data_dict["edges"] == [],
}
if name:
graph_dict["name"] = name
if description:
graph_dict["description"] = description
if endpoint_name:
graph_dict["endpoint_name"] = endpoint_name
return graph_dict
def add_nodes_and_edges(self, nodes: List[NodeData], edges: List[EdgeData]):
self._vertices = nodes
self._edges = edges
self.raw_graph_data = {"nodes": nodes, "edges": edges}
@ -183,7 +218,7 @@ class Graph:
return
def start(self, inputs: Optional[List[dict]] = None) -> Generator:
#! Change this soon
#! Change this ASAP
nest_asyncio.apply()
loop = asyncio.get_event_loop()
async_gen = self.async_start(inputs)
@ -208,8 +243,7 @@ class Graph:
self.in_degree_map[target_id] += 1
self.parent_child_map[source_id].append(target_id)
# TODO: Create a TypedDict to represente the node
def add_node(self, node: dict):
def add_node(self, node: NodeData):
self._vertices.append(node)
def add_edge(self, edge: EdgeData):
@ -1400,7 +1434,7 @@ class Graph:
return vertices
def _create_vertex(self, frontend_data: dict):
def _create_vertex(self, frontend_data: NodeData):
vertex_data = frontend_data["data"]
vertex_type: str = vertex_data["type"] # type: ignore
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore

View file

@ -1,10 +1,35 @@
from typing import TYPE_CHECKING, NamedTuple
from typing_extensions import NotRequired, TypedDict
from langflow.graph.edge.schema import EdgeData
from langflow.graph.vertex.schema import NodeData
if TYPE_CHECKING:
from langflow.graph.schema import ResultData
from langflow.graph.vertex.base import Vertex
class ViewPort(TypedDict):
x: float
y: float
zoom: float
class GraphData(TypedDict):
nodes: list[NodeData]
edges: list[EdgeData]
viewport: NotRequired[ViewPort]
class GraphDump(TypedDict, total=False):
data: GraphData
is_component: bool
name: str
description: str
endpoint_name: str
class VertexBuildResult(NamedTuple):
result_dict: "ResultData"
params: str

View file

@ -13,6 +13,7 @@ from loguru import logger
from langflow.exceptions.component import ComponentBuildException
from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData
from langflow.graph.utils import UnbuiltObject, UnbuiltResult, log_transaction
from langflow.graph.vertex.schema import NodeData
from langflow.interface.initialize import loading
from langflow.interface.listing import lazy_load_dict
from langflow.schema.artifact import ArtifactType
@ -42,7 +43,7 @@ class VertexStates(str, Enum):
class Vertex:
def __init__(
self,
data: Dict,
data: NodeData,
graph: "Graph",
base_type: Optional[str] = None,
is_task: bool = False,
@ -63,7 +64,7 @@ class Vertex:
self.has_external_input = False
self.has_external_output = False
self.graph = graph
self._data = data
self._data = data.copy()
self.base_type: Optional[str] = base_type
self.outputs: List[Dict] = []
self._parse_data()
@ -101,6 +102,9 @@ class Vertex:
raise ValueError(f"Vertex {self.id} does not have a component instance.")
self._custom_component._set_input_value(name, value)
def to_data(self):
return self._data
def add_component_instance(self, component_instance: "Component"):
component_instance.set_vertex(self)
self._custom_component = component_instance

View file

@ -0,0 +1,21 @@
from typing import Dict
from typing_extensions import NotRequired, TypedDict
class Position(TypedDict):
x: float
y: float
class NodeData(TypedDict):
id: str
data: Dict
dragging: NotRequired[bool]
height: NotRequired[int]
width: NotRequired[int]
position: NotRequired[Position]
positionAbsolute: NotRequired[Position]
selected: NotRequired[bool]
parent_node_id: NotRequired[str]
type: str

View file

@ -9,6 +9,7 @@ from loguru import logger
from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes, ResultData
from langflow.graph.utils import UnbuiltObject, log_transaction, log_vertex_build, serialize_field
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.schema import NodeData
from langflow.inputs.inputs import InputTypes
from langflow.schema import Data
from langflow.schema.artifact import ArtifactType
@ -23,7 +24,7 @@ if TYPE_CHECKING:
class CustomComponentVertex(Vertex):
def __init__(self, data: Dict, graph):
def __init__(self, data: NodeData, graph):
super().__init__(data, graph=graph, base_type="custom_components")
def _built_object_repr(self):
@ -32,9 +33,19 @@ class CustomComponentVertex(Vertex):
class ComponentVertex(Vertex):
def __init__(self, data: Dict, graph):
def __init__(self, data: NodeData, graph):
super().__init__(data, graph=graph, base_type="component")
def get_input(self, name: str) -> InputTypes:
if self._custom_component is None:
raise ValueError(f"Vertex {self.id} does not have a component instance.")
return self._custom_component.get_input(name)
def get_output(self, name: str) -> Output:
if self._custom_component is None:
raise ValueError(f"Vertex {self.id} does not have a component instance.")
return self._custom_component.get_output(name)
def _built_object_repr(self):
if self.artifacts and "repr" in self.artifacts:
return self.artifacts["repr"] or super()._built_object_repr()
@ -58,16 +69,6 @@ class ComponentVertex(Vertex):
for key, value in self._built_object.items():
self.add_result(key, value)
def get_input(self, name: str) -> InputTypes:
if self._custom_component is None:
raise ValueError(f"Vertex {self.id} does not have a component instance.")
return self._custom_component.get_input(name)
def get_output(self, name: str) -> Output:
if self._custom_component is None:
raise ValueError(f"Vertex {self.id} does not have a component instance.")
return self._custom_component.get_output(name)
def get_edge_with_target(self, target_id: str) -> Generator["ContractEdge", None, None]:
"""
Get the edge with the target id.
@ -174,7 +175,7 @@ class ComponentVertex(Vertex):
class InterfaceVertex(ComponentVertex):
def __init__(self, data: Dict, graph):
def __init__(self, data: NodeData, graph):
super().__init__(data, graph=graph)
self.steps = [self._build, self._run]
@ -424,7 +425,7 @@ class InterfaceVertex(ComponentVertex):
class StateVertex(ComponentVertex):
def __init__(self, data: Dict, graph):
def __init__(self, data: NodeData, graph):
super().__init__(data, graph=graph)
self.steps = [self._build]
self.is_state = False

View file

@ -2,6 +2,7 @@ import copy
import json
import os
import shutil
import time
from collections import defaultdict
from copy import deepcopy
from datetime import datetime, timezone
@ -23,6 +24,7 @@ from langflow.services.database.models.folder.utils import create_default_folder
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.deps import get_settings_service, get_storage_service, get_variable_service, session_scope
from langflow.template.field.prompt import DEFAULT_PROMPT_INTUT_TYPES
from langflow.utils.util import escape_json_dump
STARTER_FOLDER_NAME = "Starter Projects"
STARTER_FOLDER_DESCRIPTION = "Starter projects to help you get started in Langflow."
@ -319,10 +321,6 @@ def update_edges_with_latest_component_versions(project_data):
return project_data_copy
def escape_json_dump(edge_dict):
return json.dumps(edge_dict).replace('"', "œ")
def log_node_changes(node_changes_log):
# The idea here is to log the changes that were made to the nodes in debug
# Something like:
@ -339,17 +337,23 @@ def log_node_changes(node_changes_log):
logger.debug("\n".join(formatted_messages))
def load_starter_projects() -> list[tuple[Path, dict]]:
def load_starter_projects(retries=3, delay=1) -> list[tuple[Path, dict]]:
starter_projects = []
folder = Path(__file__).parent / "starter_projects"
for file in folder.glob("*.json"):
with open(file, "r", encoding="utf-8") as f:
try:
project = orjson.loads(f.read())
starter_projects.append((file, project))
logger.info(f"Loaded starter project {file}")
except orjson.JSONDecodeError as e:
raise ValueError(f"Error loading starter project {file}: {e}")
attempt = 0
while attempt < retries:
with open(file, "r", encoding="utf-8") as f:
try:
project = orjson.loads(f.read())
starter_projects.append((file, project))
logger.info(f"Loaded starter project {file}")
break # Break if load is successful
except orjson.JSONDecodeError as e:
attempt += 1
if attempt >= retries:
raise ValueError(f"Error loading starter project {file}: {e}")
time.sleep(delay) # Wait before retrying
return starter_projects

View file

@ -27,9 +27,13 @@ SerializableFieldTypes = Annotated[FieldTypes, PlainSerializer(lambda v: v.value
# Base mixin for common input field attributes and methods
class BaseInputMixin(BaseModel, validate_assignment=True): # type: ignore
model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid", populate_by_name=True)
model_config = ConfigDict(
arbitrary_types_allowed=True,
extra="forbid",
populate_by_name=True,
)
field_type: SerializableFieldTypes = Field(default=FieldTypes.TEXT)
field_type: SerializableFieldTypes = Field(default=FieldTypes.TEXT, alias="type")
required: bool = False
"""Specifies if the field is required. Defaults to False."""

View file

@ -24,6 +24,7 @@ class Data(BaseModel):
default_value: Optional[str] = ""
@model_validator(mode="before")
@classmethod
def validate_data(cls, values):
if not isinstance(values, dict):
raise ValueError("Data must be a dictionary")

View file

@ -184,12 +184,9 @@ class Output(BaseModel):
return self.model_dump(by_alias=True, exclude_none=True)
def add_types(self, _type: list[Any]):
for type_ in _type:
if self.types and type_ in self.types:
continue
if self.types is None:
self.types = []
self.types.append(type_)
if self.types is None:
self.types = []
self.types.extend([t for t in _type if t not in self.types])
def set_selected(self):
if not self.selected and self.types:

View file

@ -1,3 +1,3 @@
# This file is for backwards compatibility
from langflow.inputs.inputs import DEFAULT_PROMPT_INTUT_TYPES # noqa
from langflow.inputs import DefaultPromptField # noqa
from langflow.inputs.inputs import DefaultPromptField # noqa

View file

@ -1,5 +1,6 @@
import importlib
import inspect
import json
import re
from functools import wraps
from pathlib import Path
@ -456,3 +457,7 @@ def is_class_method(func, cls):
Check if a function is a class method.
"""
return inspect.ismethod(func) and func.__self__ is cls.__class__
def escape_json_dump(edge_dict):
return json.dumps(edge_dict).replace('"', "œ")

View file

@ -0,0 +1,354 @@
{
"src/backend/tests/test_endpoints.py::test_build_vertex_invalid_flow_id": 3.1494096249807626,
"src/backend/tests/test_endpoints.py::test_build_vertex_invalid_vertex_id": 3.0606157919974066,
"src/backend/tests/test_endpoints.py::test_get_all": 10.10167008501594,
"src/backend/tests/test_endpoints.py::test_get_vertices": 4.5017141660209745,
"src/backend/tests/test_endpoints.py::test_get_vertices_flow_not_found": 3.7886676250200253,
"src/backend/tests/test_endpoints.py::test_invalid_flow_id": 4.073716707964195,
"src/backend/tests/test_endpoints.py::test_invalid_prompt": 2.7002592499775346,
"src/backend/tests/test_endpoints.py::test_invalid_run_with_input_type_chat": 2.987766916019609,
"src/backend/tests/test_endpoints.py::test_post_validate_code": 3.0467621669813525,
"src/backend/tests/test_endpoints.py::test_successful_run_with_input_type_any": 14.8548604179814,
"src/backend/tests/test_endpoints.py::test_successful_run_with_input_type_chat": 6.242352208995726,
"src/backend/tests/test_endpoints.py::test_successful_run_with_input_type_text": 5.7594154170074034,
"src/backend/tests/test_endpoints.py::test_successful_run_with_output_type_any": 7.347130999987712,
"src/backend/tests/test_endpoints.py::test_successful_run_with_output_type_debug": 6.291947416990297,
"src/backend/tests/test_endpoints.py::test_successful_run_with_output_type_text": 14.872085083043203,
"src/backend/tests/test_endpoints.py::test_valid_prompt": 2.7850471249839757,
"src/backend/tests/test_endpoints.py::test_various_prompts[The weather is {weather} today.-expected_input_variables1]": 2.535564499994507,
"src/backend/tests/test_endpoints.py::test_various_prompts[This prompt has no variables.-expected_input_variables2]": 9.15231529099401,
"src/backend/tests/test_endpoints.py::test_various_prompts[{a}, {b}, and {c} are variables.-expected_input_variables3]": 2.640623040992068,
"src/backend/tests/test_endpoints.py::test_various_prompts[{color} is my favorite color.-expected_input_variables0]": 2.079908042011084,
"src/backend/tests/test_messages_endpoints.py::test_delete_messages": 2.515260499989381,
"src/backend/tests/test_messages_endpoints.py::test_delete_messages_session": 2.3651068749895785,
"src/backend/tests/test_messages_endpoints.py::test_update_message": 2.5627032090269495,
"src/backend/tests/test_messages_endpoints.py::test_update_message_not_found": 3.504595792008331,
"src/backend/tests/test_schema.py::TestInput::test_field_type_str": 0.0005162910092622042,
"src/backend/tests/test_schema.py::TestInput::test_field_type_type": 0.0002682080084923655,
"src/backend/tests/test_schema.py::TestInput::test_input_to_dict": 0.0003857500269077718,
"src/backend/tests/test_schema.py::TestInput::test_invalid_field_type": 0.00031291699269786477,
"src/backend/tests/test_schema.py::TestInput::test_post_process_type_function": 0.0005505419976543635,
"src/backend/tests/test_schema.py::TestInput::test_serialize_field_type": 0.0002683750062715262,
"src/backend/tests/test_schema.py::TestInput::test_validate_type_class": 0.0003414590028114617,
"src/backend/tests/test_schema.py::TestInput::test_validate_type_string": 0.0002427089784760028,
"src/backend/tests/test_schema.py::TestOutput::test_output_add_types": 0.000245749979512766,
"src/backend/tests/test_schema.py::TestOutput::test_output_default": 0.00026183397858403623,
"src/backend/tests/test_schema.py::TestOutput::test_output_set_selected": 0.0003107920056208968,
"src/backend/tests/test_schema.py::TestOutput::test_output_to_dict": 0.0004964589898008853,
"src/backend/tests/test_schema.py::TestOutput::test_output_validate_display_name": 0.0005334159650374204,
"src/backend/tests/test_schema.py::TestOutput::test_output_validate_model": 0.00029370796983130276,
"src/backend/tests/test_schema.py::TestPostProcessType::test_custom_type": 0.001362041017273441,
"src/backend/tests/test_schema.py::TestPostProcessType::test_int_type": 0.00023837501066736877,
"src/backend/tests/test_schema.py::TestPostProcessType::test_list_custom_type": 0.004543458024272695,
"src/backend/tests/test_schema.py::TestPostProcessType::test_list_int_type": 0.0002362079976592213,
"src/backend/tests/test_schema.py::TestPostProcessType::test_union_custom_type": 0.0005842499958816916,
"src/backend/tests/test_schema.py::TestPostProcessType::test_union_type": 0.003973040962591767,
"src/backend/tests/test_user.py::test_add_user": 3.298028166987933,
"src/backend/tests/test_user.py::test_data_consistency_after_delete": 10.030325876054121,
"src/backend/tests/test_user.py::test_data_consistency_after_update": 2.9754588740179315,
"src/backend/tests/test_user.py::test_deactivated_user_cannot_access": 3.544328290998237,
"src/backend/tests/test_user.py::test_deactivated_user_cannot_login": 3.9071091239748057,
"src/backend/tests/test_user.py::test_delete_user": 4.161238500004401,
"src/backend/tests/test_user.py::test_delete_user_wrong_id": 2.7632550839625765,
"src/backend/tests/test_user.py::test_inactive_user": 3.334006417018827,
"src/backend/tests/test_user.py::test_normal_user_cant_delete_user": 2.9729639159922954,
"src/backend/tests/test_user.py::test_normal_user_cant_read_all_users": 2.6966073329967912,
"src/backend/tests/test_user.py::test_patch_reset_password": 11.245606623997446,
"src/backend/tests/test_user.py::test_patch_user": 3.2588992070232052,
"src/backend/tests/test_user.py::test_patch_user_wrong_id": 3.3168086239602417,
"src/backend/tests/test_user.py::test_read_all_users": 2.440687207999872,
"src/backend/tests/test_user.py::test_user_waiting_for_approval": 9.475323291990208,
"src/backend/tests/test_webhook.py::test_webhook_endpoint": 12.348436542029958,
"src/backend/tests/test_webhook.py::test_webhook_flow_on_run_endpoint": 15.205204916041112,
"src/backend/tests/test_webhook.py::test_webhook_with_random_payload": 8.713364291994367,
"src/backend/tests/unit/components/prompts/test_prompt_component.py::TestPromptComponent::test_post_code_processing": 0.008164541970472783,
"src/backend/tests/unit/custom/custom_component/test_component.py::test_set_invalid_output": 0.000454624998383224,
"src/backend/tests/unit/graph/graph/test_base.py::test_graph": 0.007537708996096626,
"src/backend/tests/unit/graph/graph/test_base.py::test_graph_functional": 0.020996668026782572,
"src/backend/tests/unit/graph/graph/test_base.py::test_graph_functional_async_start": 0.009653333021560684,
"src/backend/tests/unit/graph/graph/test_base.py::test_graph_functional_start": 0.008162209036527202,
"src/backend/tests/unit/graph/graph/test_base.py::test_graph_functional_start_end": 0.06379004201153293,
"src/backend/tests/unit/graph/graph/test_base.py::test_graph_not_prepared": 0.01988037399132736,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_add_to_vertices_being_run": 2.476791499997489,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_are_all_predecessors_fulfilled": 2.3258769580570515,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_are_all_predecessors_fulfilled__wrong": 2.4165378749894444,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_build_run_map": 2.5142138760129455,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_from_dict": 2.629594833997544,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_from_dict_without_run_map__bad_case": 8.874073583021527,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_from_dict_without_run_predecessors__bad_case": 2.743527958955383,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_from_dict_without_vertices_being_run__bad_case": 2.8369890000030864,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_from_dict_without_vertices_to_run__bad_case": 2.9151457909611054,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_is_vertex_runnable": 8.908991582982708,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_is_vertex_runnable__wrong_is_active": 2.637443292012904,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_is_vertex_runnable__wrong_run_predecessors": 2.747438082966255,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_is_vertex_runnable__wrong_vertices_to_run": 2.8337462919880636,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_pickle": 2.065233791974606,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_remove_from_predecessors": 8.867784041009145,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_remove_vertex_from_runnables": 2.2803797090018634,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_to_dict": 9.405242958950112,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_update_run_state": 2.4422846660017967,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_update_vertex_run_state": 2.9607737089972943,
"src/backend/tests/unit/graph/graph/test_runnable_vertices_manager.py::test_update_vertex_run_state__bad_case": 2.485696541989455,
"src/backend/tests/unit/graph/graph/test_utils.py::test_get_successors_a": 8.826332042983267,
"src/backend/tests/unit/graph/graph/test_utils.py::test_get_successors_z": 2.949440208991291,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_a": 2.8428027920017485,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_g": 3.315444208041299,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_h": 2.983557416999247,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_invalid_vertex": 1.9296646670263726,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_m": 8.736605707992567,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_n_is_start": 2.5265350410190877,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_t": 2.3543146679585334,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_x": 2.094447916984791,
"src/backend/tests/unit/graph/graph/test_utils.py::test_sort_up_to_vertex_z": 2.7899541249789763,
"src/backend/tests/unit/graph/test_graph.py::test_build_edges": 2.053889958973741,
"src/backend/tests/unit/graph/test_graph.py::test_build_nodes": 3.264545250014635,
"src/backend/tests/unit/graph/test_graph.py::test_build_params": 1.6351483759935945,
"src/backend/tests/unit/graph/test_graph.py::test_circular_dependencies": 4.829830207978375,
"src/backend/tests/unit/graph/test_graph.py::test_find_last_node": 1.8075883749988861,
"src/backend/tests/unit/graph/test_graph.py::test_get_node": 2.2939607900043484,
"src/backend/tests/unit/graph/test_graph.py::test_get_node_neighbors_basic": 2.5666640420095064,
"src/backend/tests/unit/graph/test_graph.py::test_get_root_vertex": 2.33814408298349,
"src/backend/tests/unit/graph/test_graph.py::test_get_vertices_with_target": 2.0869384160032496,
"src/backend/tests/unit/graph/test_graph.py::test_graph_structure": 3.1825925829762127,
"src/backend/tests/unit/graph/test_graph.py::test_invalid_node_types": 2.1994956269918475,
"src/backend/tests/unit/graph/test_graph.py::test_matched_type": 2.3932184999866877,
"src/backend/tests/unit/graph/test_graph.py::test_pickle_graph": 2.184392209019279,
"src/backend/tests/unit/graph/test_graph.py::test_process_flow": 2.1272420000168495,
"src/backend/tests/unit/graph/test_graph.py::test_process_flow_one_group": 1.9646992909838445,
"src/backend/tests/unit/graph/test_graph.py::test_process_flow_vector_store_grouped": 2.415951082977699,
"src/backend/tests/unit/graph/test_graph.py::test_set_new_target_handle": 1.7951639160164632,
"src/backend/tests/unit/graph/test_graph.py::test_ungroup_node": 2.0279515830043238,
"src/backend/tests/unit/graph/test_graph.py::test_update_source_handle": 2.0684266670432407,
"src/backend/tests/unit/graph/test_graph.py::test_update_target_handle_proxy": 1.5892521249479614,
"src/backend/tests/unit/graph/test_graph.py::test_update_template": 1.7316221670189407,
"src/backend/tests/unit/graph/test_graph.py::test_validate_edges": 1.920275832992047,
"src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py::test_memory_chatbot": 2.1822710419946816,
"src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py::test_memory_chatbot_dump_components_and_edges": 1.6609269159962423,
"src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py::test_memory_chatbot_dump_structure": 2.5239112499984913,
"src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py::test_vector_store_rag": 0.08740387603756972,
"src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py::test_vector_store_rag_dump": 0.027446749998489395,
"src/backend/tests/unit/initial_setup/starter_projects/test_vector_store_rag.py::test_vector_store_rag_dump_components_and_edges": 0.026998917979653925,
"src/backend/tests/unit/inputs/test_inputs.py::test_bool_input_invalid": 0.0006447900377679616,
"src/backend/tests/unit/inputs/test_inputs.py::test_bool_input_valid": 0.00023025000700727105,
"src/backend/tests/unit/inputs/test_inputs.py::test_data_input_valid": 0.0005409169825725257,
"src/backend/tests/unit/inputs/test_inputs.py::test_dict_input_invalid": 0.00025333400117233396,
"src/backend/tests/unit/inputs/test_inputs.py::test_dict_input_valid": 0.0007362900068983436,
"src/backend/tests/unit/inputs/test_inputs.py::test_dropdown_input_invalid": 0.0009356669906992465,
"src/backend/tests/unit/inputs/test_inputs.py::test_dropdown_input_valid": 0.0004829160461667925,
"src/backend/tests/unit/inputs/test_inputs.py::test_file_input_valid": 0.00024799996754154563,
"src/backend/tests/unit/inputs/test_inputs.py::test_float_input_invalid": 0.00025445802020840347,
"src/backend/tests/unit/inputs/test_inputs.py::test_float_input_valid": 0.00024087497149594128,
"src/backend/tests/unit/inputs/test_inputs.py::test_handle_input_invalid": 0.0003797499812208116,
"src/backend/tests/unit/inputs/test_inputs.py::test_handle_input_valid": 0.0002122489968314767,
"src/backend/tests/unit/inputs/test_inputs.py::test_instantiate_input_comprehensive": 0.001500750018749386,
"src/backend/tests/unit/inputs/test_inputs.py::test_instantiate_input_invalid": 0.00027112496900372207,
"src/backend/tests/unit/inputs/test_inputs.py::test_instantiate_input_valid": 0.0005940830160398036,
"src/backend/tests/unit/inputs/test_inputs.py::test_int_input_invalid": 0.00029999902471899986,
"src/backend/tests/unit/inputs/test_inputs.py::test_int_input_valid": 0.0004179589741397649,
"src/backend/tests/unit/inputs/test_inputs.py::test_message_text_input_invalid": 0.0004274170205462724,
"src/backend/tests/unit/inputs/test_inputs.py::test_message_text_input_valid": 0.0003730839816853404,
"src/backend/tests/unit/inputs/test_inputs.py::test_multiline_input_invalid": 0.00033208398963324726,
"src/backend/tests/unit/inputs/test_inputs.py::test_multiline_input_valid": 0.0003288750012870878,
"src/backend/tests/unit/inputs/test_inputs.py::test_multiline_secret_input_invalid": 0.00023066697758622468,
"src/backend/tests/unit/inputs/test_inputs.py::test_multiline_secret_input_valid": 0.00022683301358483732,
"src/backend/tests/unit/inputs/test_inputs.py::test_multiselect_input_invalid": 0.0002583339810371399,
"src/backend/tests/unit/inputs/test_inputs.py::test_multiselect_input_valid": 0.00027691599098034203,
"src/backend/tests/unit/inputs/test_inputs.py::test_nested_dict_input_invalid": 0.00041799998143687844,
"src/backend/tests/unit/inputs/test_inputs.py::test_nested_dict_input_valid": 0.004873417055932805,
"src/backend/tests/unit/inputs/test_inputs.py::test_prompt_input_valid": 0.0007432089769281447,
"src/backend/tests/unit/inputs/test_inputs.py::test_secret_str_input_invalid": 0.00033837597584351897,
"src/backend/tests/unit/inputs/test_inputs.py::test_secret_str_input_valid": 0.00022083398653194308,
"src/backend/tests/unit/inputs/test_inputs.py::test_str_input_invalid": 0.0004425400111358613,
"src/backend/tests/unit/inputs/test_inputs.py::test_str_input_valid": 0.0003231659939046949,
"src/backend/tests/unit/inputs/test_inputs.py::test_table_input_invalid": 0.010438332974445075,
"src/backend/tests/unit/inputs/test_inputs.py::test_table_input_valid": 0.0003158329927828163,
"src/backend/tests/unit/schema/test_schema_message.py::test_message_async_prompt_serialization": 0.4811764149926603,
"src/backend/tests/unit/schema/test_schema_message.py::test_message_prompt_serialization": 0.0009208739793393761,
"src/backend/tests/unit/test_api_key.py::test_create_api_key": 2.661131207976723,
"src/backend/tests/unit/test_api_key.py::test_delete_api_key": 2.671240749012213,
"src/backend/tests/unit/test_api_key.py::test_get_api_keys": 2.5765499170229305,
"src/backend/tests/unit/test_cache.py::test_build_graph": 3.0347800820018165,
"src/backend/tests/unit/test_chat_endpoint.py::test_build_flow": 8.144330751005327,
"src/backend/tests/unit/test_chat_endpoint.py::test_build_flow_from_request_data": 5.783452290983405,
"src/backend/tests/unit/test_chat_endpoint.py::test_build_flow_with_frozen_path": 7.61259591698763,
"src/backend/tests/unit/test_cli.py::test_components_path": 2.2392006250447594,
"src/backend/tests/unit/test_cli.py::test_superuser": 2.476592207007343,
"src/backend/tests/unit/test_custom_component.py::test_build_config_field_keys": 2.0540905419911724,
"src/backend/tests/unit/test_custom_component.py::test_build_config_field_value_keys": 1.9443145420227665,
"src/backend/tests/unit/test_custom_component.py::test_build_config_field_values_dict": 3.319497832970228,
"src/backend/tests/unit/test_custom_component.py::test_build_config_fields_dict": 1.8987905430258252,
"src/backend/tests/unit/test_custom_component.py::test_build_config_has_fields": 2.7343585419876035,
"src/backend/tests/unit/test_custom_component.py::test_build_config_no_code": 1.792709333007224,
"src/backend/tests/unit/test_custom_component.py::test_build_config_return_type": 2.3822355829761364,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_get_tree": 1.952277623990085,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_init": 2.0454807080095634,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_ann_assign": 2.451507583988132,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_arg_no_annotation": 2.353039957990404,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_arg_with_annotation": 1.7640878760430496,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_assign": 2.0938420000020415,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_callable_details_no_args": 1.7373172499937937,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_classes": 1.7848410429724026,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_function_def_init": 2.2325071259983815,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_function_def_not_init": 2.4196665409835987,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_functions": 2.178845665999688,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_global_vars": 2.4417894990183413,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_imports_import": 1.707990164984949,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_parse_imports_importfrom": 2.1835803739959374,
"src/backend/tests/unit/test_custom_component.py::test_code_parser_syntax_error": 2.277529457001947,
"src/backend/tests/unit/test_custom_component.py::test_component_code_null_error": 1.9170069170068018,
"src/backend/tests/unit/test_custom_component.py::test_component_get_code_tree": 1.8569252080051228,
"src/backend/tests/unit/test_custom_component.py::test_component_get_code_tree_syntax_error": 2.174606874003075,
"src/backend/tests/unit/test_custom_component.py::test_component_get_function_valid": 1.9588903749827296,
"src/backend/tests/unit/test_custom_component.py::test_component_init": 2.5707569160149433,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_build_not_implemented": 2.3832541239680722,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_build_template_config": 1.767908540990902,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_class_template_validation_no_code": 1.9784962920530234,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_code_tree_syntax_error": 2.0324612080003135,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_function": 1.6494328340049833,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_function_entrypoint_args": 1.9188839579874184,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_function_entrypoint_args_no_args": 2.0736736260005273,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_function_entrypoint_return_type": 2.199567042000126,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_function_entrypoint_return_type_no_return_type": 1.7597685409709811,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_function_valid": 2.1681128749914933,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_main_class_name": 1.658911792008439,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_get_main_class_name_no_main_class": 1.4603167499881238,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_init": 2.329375083994819,
"src/backend/tests/unit/test_custom_component.py::test_custom_component_multiple_outputs": 1.8187729580095038,
"src/backend/tests/unit/test_custom_component.py::test_list_flows_flow_objects": 1.981454541994026,
"src/backend/tests/unit/test_custom_component.py::test_list_flows_return_type": 1.8733046670095064,
"src/backend/tests/unit/test_data_class.py::test_add_method_for_integers": 1.7596916669863276,
"src/backend/tests/unit/test_data_class.py::test_add_method_for_strings": 2.099744750012178,
"src/backend/tests/unit/test_data_class.py::test_add_method_with_non_overlapping_keys": 2.0075557490054052,
"src/backend/tests/unit/test_data_class.py::test_conversion_from_document": 1.9583345409773756,
"src/backend/tests/unit/test_data_class.py::test_conversion_to_document": 1.953191417036578,
"src/backend/tests/unit/test_data_class.py::test_custom_attribute_get_set_del": 2.8074895000027027,
"src/backend/tests/unit/test_data_class.py::test_custom_attribute_setting_and_getting": 1.744376168033341,
"src/backend/tests/unit/test_data_class.py::test_data_initialization": 2.3415857510408387,
"src/backend/tests/unit/test_data_class.py::test_deep_copy": 1.5598407920042519,
"src/backend/tests/unit/test_data_class.py::test_dir_includes_data_keys": 2.4137807070219424,
"src/backend/tests/unit/test_data_class.py::test_dir_reflects_attribute_deletion": 1.8897194170276634,
"src/backend/tests/unit/test_data_class.py::test_get_text_with_empty_data": 2.669506582984468,
"src/backend/tests/unit/test_data_class.py::test_get_text_with_none_data": 1.8896955420204904,
"src/backend/tests/unit/test_data_class.py::test_get_text_with_text_key": 1.953111374983564,
"src/backend/tests/unit/test_data_class.py::test_get_text_without_text_key": 1.9460047910106368,
"src/backend/tests/unit/test_data_class.py::test_str_and_dir_methods": 2.6938894579943735,
"src/backend/tests/unit/test_data_class.py::test_validate_data_with_extra_keys": 1.9336464170191903,
"src/backend/tests/unit/test_data_components.py::test_build_with_multiple_urls": 0.026251333008985966,
"src/backend/tests/unit/test_data_components.py::test_directory_component_build_with_multithreading": 0.0020231239905115217,
"src/backend/tests/unit/test_data_components.py::test_directory_without_mocks": 0.38643029099330306,
"src/backend/tests/unit/test_data_components.py::test_failed_request": 0.011844915978144854,
"src/backend/tests/unit/test_data_components.py::test_parse_curl": 0.0004114170151297003,
"src/backend/tests/unit/test_data_components.py::test_successful_get_request": 0.015994457993656397,
"src/backend/tests/unit/test_data_components.py::test_timeout": 0.01364304099115543,
"src/backend/tests/unit/test_data_components.py::test_url_component": 0.5631265829724725,
"src/backend/tests/unit/test_database.py::test_create_flow": 2.5350006250082515,
"src/backend/tests/unit/test_database.py::test_create_flow_with_invalid_data": 2.6853410840267316,
"src/backend/tests/unit/test_database.py::test_create_flows": 3.4552309999999125,
"src/backend/tests/unit/test_database.py::test_delete_flow": 4.201302792993374,
"src/backend/tests/unit/test_database.py::test_delete_flows": 2.73355954195722,
"src/backend/tests/unit/test_database.py::test_delete_flows_with_transaction_and_build": 3.3879740410193335,
"src/backend/tests/unit/test_database.py::test_delete_nonexistent_flow": 2.906195083982311,
"src/backend/tests/unit/test_database.py::test_download_file": 2.6433084169693757,
"src/backend/tests/unit/test_database.py::test_get_nonexistent_flow": 2.9141747919784393,
"src/backend/tests/unit/test_database.py::test_load_flows": 2.3472657920210622,
"src/backend/tests/unit/test_database.py::test_migrate_transactions": 2.4188965820358135,
"src/backend/tests/unit/test_database.py::test_migrate_transactions_no_duckdb": 2.4176759159890935,
"src/backend/tests/unit/test_database.py::test_read_flow": 2.524181623972254,
"src/backend/tests/unit/test_database.py::test_read_flows": 3.3437811249750666,
"src/backend/tests/unit/test_database.py::test_read_only_starter_projects": 2.8177391680073924,
"src/backend/tests/unit/test_database.py::test_sqlite_pragmas": 2.2383368749869987,
"src/backend/tests/unit/test_database.py::test_update_flow": 3.1579460009816103,
"src/backend/tests/unit/test_database.py::test_update_flow_idempotency": 2.9125417500035837,
"src/backend/tests/unit/test_database.py::test_update_nonexistent_flow": 2.838372750993585,
"src/backend/tests/unit/test_database.py::test_upload_file": 2.6103912079997826,
"src/backend/tests/unit/test_experimental_components.py::test_python_function_component": 2.076999415992759,
"src/backend/tests/unit/test_files.py::test_delete_file": 2.799217874009628,
"src/backend/tests/unit/test_files.py::test_download_file": 2.51829199999338,
"src/backend/tests/unit/test_files.py::test_file_operations": 3.3802113739948254,
"src/backend/tests/unit/test_files.py::test_list_files": 2.7689662509947084,
"src/backend/tests/unit/test_files.py::test_upload_file": 3.3243832079751883,
"src/backend/tests/unit/test_frontend_nodes.py::test_frontend_node_to_dict": 2.1432127919979393,
"src/backend/tests/unit/test_frontend_nodes.py::test_template_field_defaults": 2.2584460410289466,
"src/backend/tests/unit/test_frontend_nodes.py::test_template_to_dict": 2.8126436240272596,
"src/backend/tests/unit/test_helper_components.py::test_data_as_text_component": 2.214979208976729,
"src/backend/tests/unit/test_helper_components.py::test_uuid_generator_component": 3.2323666680022143,
"src/backend/tests/unit/test_initial_setup.py::test_create_or_update_starter_projects": 2.2390285000146832,
"src/backend/tests/unit/test_initial_setup.py::test_get_project_data": 2.6407637080119457,
"src/backend/tests/unit/test_initial_setup.py::test_load_starter_projects": 2.2846807509777136,
"src/backend/tests/unit/test_initial_setup.py::test_refresh_starter_projects": 5.397776041994803,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_create_secret": 2.1846052090113517,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_delete_secret": 2.4931142500427086,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_email_address": 3.9143964989925735,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_encode_string": 2.658771957969293,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_encode_uuid": 3.536810541001614,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_ends_with_non_alphanumeric": 2.6400313759804703,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_get_secret": 2.2426519600267056,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_long_string": 2.5769125409715343,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_starts_with_non_alphanumeric": 2.9591099170211237,
"src/backend/tests/unit/test_kubernetes_secrets.py::test_uuid_case_insensitivity": 2.8027220430085436,
"src/backend/tests/unit/test_loading.py::test_load_flow_from_json": 2.0592095839965623,
"src/backend/tests/unit/test_loading.py::test_load_flow_from_json_object": 0.05121591599890962,
"src/backend/tests/unit/test_loading.py::test_load_flow_from_json_with_tweaks": 2.3594004999904428,
"src/backend/tests/unit/test_logger.py::test_enabled": 2.366979167010868,
"src/backend/tests/unit/test_logger.py::test_get_after_timestamp": 2.843343543005176,
"src/backend/tests/unit/test_logger.py::test_get_before_timestamp": 2.0495081240078434,
"src/backend/tests/unit/test_logger.py::test_get_last_n": 3.3493437920114957,
"src/backend/tests/unit/test_logger.py::test_init_default": 4.3632337910239585,
"src/backend/tests/unit/test_logger.py::test_init_with_env_variable": 2.747672124998644,
"src/backend/tests/unit/test_logger.py::test_len": 3.0128796670178417,
"src/backend/tests/unit/test_logger.py::test_max_size": 2.5830446239560843,
"src/backend/tests/unit/test_logger.py::test_write": 3.656159915990429,
"src/backend/tests/unit/test_logger.py::test_write_overflow": 4.817402709042653,
"src/backend/tests/unit/test_login.py::test_login_successful": 3.081307165994076,
"src/backend/tests/unit/test_login.py::test_login_unsuccessful_wrong_password": 3.23898391702096,
"src/backend/tests/unit/test_login.py::test_login_unsuccessful_wrong_username": 2.315841832984006,
"src/backend/tests/unit/test_messages.py::test_add_messages": 2.261571748997085,
"src/backend/tests/unit/test_messages.py::test_add_messagetables": 2.3893967490294017,
"src/backend/tests/unit/test_messages.py::test_convert_to_langchain[convert_to_langchain_type]": 3.2416470000171103,
"src/backend/tests/unit/test_messages.py::test_convert_to_langchain[message]": 2.004590749013005,
"src/backend/tests/unit/test_messages.py::test_delete_messages": 2.3871561660198495,
"src/backend/tests/unit/test_messages.py::test_get_messages": 2.0157879999896977,
"src/backend/tests/unit/test_messages.py::test_store_message": 1.9027003319642972,
"src/backend/tests/unit/test_process.py::test_load_langchain_object_with_cached_session": 2.2040907500195317,
"src/backend/tests/unit/test_process.py::test_load_langchain_object_with_no_cached_session": 2.883888084004866,
"src/backend/tests/unit/test_process.py::test_load_langchain_object_without_session_id": 2.329415375017561,
"src/backend/tests/unit/test_process.py::test_multiple_tweaks": 2.047054874972673,
"src/backend/tests/unit/test_process.py::test_no_tweaks": 1.952550498972414,
"src/backend/tests/unit/test_process.py::test_single_tweak": 2.148759791016346,
"src/backend/tests/unit/test_process.py::test_tweak_no_node_id": 3.1278445839998312,
"src/backend/tests/unit/test_process.py::test_tweak_not_in_template": 2.113566374988295,
"src/backend/tests/unit/test_setup_superuser.py::test_teardown_superuser_default_superuser": 2.256663625012152,
"src/backend/tests/unit/test_setup_superuser.py::test_teardown_superuser_no_default_superuser": 2.211974083009409,
"src/backend/tests/unit/test_telemetry.py::test_gauge": 2.4124685839633457,
"src/backend/tests/unit/test_telemetry.py::test_gauge_with_counter_method": 2.186464792001061,
"src/backend/tests/unit/test_telemetry.py::test_gauge_with_historgram_method": 2.6113254159863573,
"src/backend/tests/unit/test_telemetry.py::test_gauge_with_up_down_counter_method": 2.225708084035432,
"src/backend/tests/unit/test_telemetry.py::test_increment_counter": 2.130592000001343,
"src/backend/tests/unit/test_telemetry.py::test_increment_counter_empty_label": 2.2976541249954607,
"src/backend/tests/unit/test_telemetry.py::test_increment_counter_missing_mandatory_label": 2.443581625993829,
"src/backend/tests/unit/test_telemetry.py::test_increment_counter_unregisted_metric": 2.630037208989961,
"src/backend/tests/unit/test_telemetry.py::test_init": 2.1476573330292013,
"src/backend/tests/unit/test_telemetry.py::test_missing_labels": 2.3570764580217656,
"src/backend/tests/unit/test_telemetry.py::test_multithreaded_singleton": 2.390594750002492,
"src/backend/tests/unit/test_telemetry.py::test_multithreaded_singleton_race_condition": 2.437567832006607,
"src/backend/tests/unit/test_telemetry.py::test_opentelementry_singleton": 3.0913529580284376,
"src/backend/tests/unit/test_template.py::test_build_template_from_function": 2.9347434579976834,
"src/backend/tests/unit/test_template.py::test_get_base_classes": 2.118878333014436,
"src/backend/tests/unit/test_template.py::test_get_default_factory": 2.143760042003123,
"src/backend/tests/unit/test_validate_code.py::test_create_function": 2.2330028339929413,
"src/backend/tests/unit/test_validate_code.py::test_execute_function_missing_function": 1.7509510009840596,
"src/backend/tests/unit/test_validate_code.py::test_execute_function_missing_module": 1.850503541965736,
"src/backend/tests/unit/test_validate_code.py::test_execute_function_missing_schema": 1.81301379200886,
"src/backend/tests/unit/test_validate_code.py::test_execute_function_success": 2.074162457982311,
"src/backend/tests/unit/test_validate_code.py::test_validate_code": 2.2336132919881493,
"src/backend/tests/unit/test_version.py::test_compute_main": 2.139949625969166,
"src/backend/tests/unit/test_version.py::test_version": 1.6260940420324914,
"src/backend/tests/unit/utils/test_connection_string_parser.py::test_transform_connection_string[protocol::password@host-protocol::password@host]": 0.0012022080190945417,
"src/backend/tests/unit/utils/test_connection_string_parser.py::test_transform_connection_string[protocol:user:pa:ss:word@host-protocol:user:pa:ss:word@host]": 0.000848833005875349,
"src/backend/tests/unit/utils/test_connection_string_parser.py::test_transform_connection_string[protocol:user:pa@ss@word@host-protocol:user:pa%40ss%40word@host]": 0.001619456976186484,
"src/backend/tests/unit/utils/test_connection_string_parser.py::test_transform_connection_string[protocol:user:pass@word@host-protocol:user:pass%40word@host]": 0.000586748996283859,
"src/backend/tests/unit/utils/test_connection_string_parser.py::test_transform_connection_string[protocol:user:password@-protocol:user:password@]": 0.0025060399784706533,
"src/backend/tests/unit/utils/test_connection_string_parser.py::test_transform_connection_string[protocol:user:password@host-protocol:user:password@host]": 0.0007859579636715353,
"src/backend/tests/unit/utils/test_connection_string_parser.py::test_transform_connection_string[protocol:user@host-protocol:user@host]": 0.0004099990183021873,
"src/backend/tests/unit/utils/test_connection_string_parser.py::test_transform_connection_string[user:password@host-user:password@host]": 0.0005190849769860506
}

View file

@ -1,5 +1,7 @@
from collections import deque
import pytest
from langflow.components.helpers.Memory import MemoryComponent
from langflow.components.inputs.ChatInput import ChatInput
from langflow.components.models.OpenAIModel import OpenAIModelComponent
@ -7,9 +9,11 @@ from langflow.components.outputs.ChatOutput import ChatOutput
from langflow.components.prompts.Prompt import PromptComponent
from langflow.graph import Graph
from langflow.graph.graph.constants import Finish
from langflow.graph.graph.schema import GraphDump
def test_memory_chatbot():
@pytest.fixture
def memory_chatbot_graph():
session_id = "test_session_id"
template = """{context}
@ -32,10 +36,87 @@ AI: """
chat_output.set(input_value=openai_component.text_response)
graph = Graph(chat_input, chat_output)
return graph
def test_memory_chatbot(memory_chatbot_graph):
# Now we run step by step
expected_order = deque(["chat_input", "chat_memory", "prompt", "openai", "chat_output"])
for step in expected_order:
result = graph.step()
result = memory_chatbot_graph.step()
if isinstance(result, Finish):
break
assert step == result.vertex.id
def test_memory_chatbot_dump_structure(memory_chatbot_graph: Graph):
# Now we run step by step
graph_dict = memory_chatbot_graph.dump(
name="Memory Chatbot", description="A memory chatbot", endpoint_name="membot"
)
assert isinstance(graph_dict, dict)
# Test structure
assert "data" in graph_dict
assert "is_component" in graph_dict
data_dict = graph_dict["data"]
assert "nodes" in data_dict
assert "edges" in data_dict
assert "description" in graph_dict
assert "endpoint_name" in graph_dict
# Test data
nodes = data_dict["nodes"]
edges = data_dict["edges"]
description = graph_dict["description"]
endpoint_name = graph_dict["endpoint_name"]
assert len(nodes) == 5
assert len(edges) == 4
assert description is not None
assert endpoint_name is not None
def test_memory_chatbot_dump_components_and_edges(memory_chatbot_graph: Graph):
# Check all components and edges were dumped correctly
graph_dict: GraphDump = memory_chatbot_graph.dump(
name="Memory Chatbot", description="A memory chatbot", endpoint_name="membot"
)
data_dict = graph_dict["data"]
nodes = data_dict["nodes"]
edges = data_dict["edges"]
# sort the nodes by id
nodes = sorted(nodes, key=lambda x: x["id"])
# Check each node
assert nodes[0]["data"]["type"] == "ChatInput"
assert nodes[0]["id"] == "chat_input"
assert nodes[1]["data"]["type"] == "MemoryComponent"
assert nodes[1]["id"] == "chat_memory"
assert nodes[2]["data"]["type"] == "ChatOutput"
assert nodes[2]["id"] == "chat_output"
assert nodes[3]["data"]["type"] == "OpenAIModelComponent"
assert nodes[3]["id"] == "openai"
assert nodes[4]["data"]["type"] == "PromptComponent"
assert nodes[4]["id"] == "prompt"
# Check edges
expected_edges = [
("chat_input", "prompt"),
("chat_memory", "prompt"),
("prompt", "openai"),
("openai", "chat_output"),
]
assert len(edges) == len(expected_edges)
for edge in edges:
source = edge["source"]
target = edge["target"]
assert (source, target) in expected_edges, edge

View file

@ -1,5 +1,7 @@
from textwrap import dedent
import pytest
from langflow.components.data.File import FileComponent
from langflow.components.embeddings.OpenAIEmbeddings import OpenAIEmbeddingsComponent
from langflow.components.helpers.ParseData import ParseDataComponent
@ -14,10 +16,17 @@ from langflow.graph.graph.constants import Finish
from langflow.schema.data import Data
def test_vector_store_rag():
@pytest.fixture
def client():
pass
@pytest.fixture
def ingestion_graph():
# Ingestion Graph
file_component = FileComponent(_id="file-123")
file_component.set(path="test.txt")
file_component.set_output_value("data", Data(text="This is a test file."))
text_splitter = SplitTextComponent(_id="text-splitter-123")
text_splitter.set(data_inputs=file_component.load_file)
openai_embeddings = OpenAIEmbeddingsComponent(_id="openai-embeddings-123")
@ -31,8 +40,18 @@ def test_vector_store_rag():
api_endpoint="https://astra.example.com",
token="token",
)
vector_store.set_output_value("vector_store", "mock_vector_store")
vector_store.set_output_value("base_retriever", "mock_retriever")
vector_store.set_output_value("search_results", [Data(text="This is a test file.")])
ingestion_graph = Graph(file_component, vector_store)
return ingestion_graph
@pytest.fixture
def rag_graph():
# RAG Graph
openai_embeddings = OpenAIEmbeddingsComponent(_id="openai-embeddings-124")
chat_input = ChatInput(_id="chatinput-123")
chat_input.get_output("message").value = "What is the meaning of life?"
rag_vector_store = AstraVectorStoreComponent(_id="rag-vector-store-123")
@ -69,21 +88,160 @@ def test_vector_store_rag():
chat_output.set(input_value=openai_component.text_response)
graph = Graph(start=chat_input, end=chat_output)
assert graph is not None
ids = [
return graph
def test_vector_store_rag(ingestion_graph, rag_graph):
assert ingestion_graph is not None
ingestion_ids = [
"file-123",
"text-splitter-123",
"openai-embeddings-123",
"vector-store-123",
]
assert rag_graph is not None
rag_ids = [
"chatinput-123",
"chatoutput-123",
"openai-123",
"parse-data-123",
"prompt-123",
"rag-vector-store-123",
"openai-embeddings-123",
"openai-embeddings-124",
]
results = []
for result in graph.start():
results.append(result)
for ids, graph, len_results in zip([ingestion_ids, rag_ids], [ingestion_graph, rag_graph], [5, 8]):
results = []
for result in graph.start():
results.append(result)
assert len(results) == 8
vids = [result.vertex.id for result in results if hasattr(result, "vertex")]
assert all(vid in ids for vid in vids), f"Diff: {set(vids) - set(ids)}"
assert results[-1] == Finish()
assert len(results) == len_results
vids = [result.vertex.id for result in results if hasattr(result, "vertex")]
assert all(vid in ids for vid in vids), f"Diff: {set(vids) - set(ids)}"
assert results[-1] == Finish()
def test_vector_store_rag_dump_components_and_edges(ingestion_graph, rag_graph):
# Test ingestion graph components and edges
ingestion_graph_dump = ingestion_graph.dump(
name="Ingestion Graph", description="Graph for data ingestion", endpoint_name="ingestion"
)
ingestion_data = ingestion_graph_dump["data"]
ingestion_nodes = ingestion_data["nodes"]
ingestion_edges = ingestion_data["edges"]
# Sort nodes by id to check components
ingestion_nodes = sorted(ingestion_nodes, key=lambda x: x["id"])
# Check components in the ingestion graph
assert ingestion_nodes[0]["data"]["type"] == "FileComponent"
assert ingestion_nodes[0]["id"] == "file-123"
assert ingestion_nodes[1]["data"]["type"] == "OpenAIEmbeddingsComponent"
assert ingestion_nodes[1]["id"] == "openai-embeddings-123"
assert ingestion_nodes[2]["data"]["type"] == "SplitTextComponent"
assert ingestion_nodes[2]["id"] == "text-splitter-123"
assert ingestion_nodes[3]["data"]["type"] == "AstraVectorStoreComponent"
assert ingestion_nodes[3]["id"] == "vector-store-123"
# Check edges in the ingestion graph
expected_ingestion_edges = [
("file-123", "text-splitter-123"),
("text-splitter-123", "vector-store-123"),
("openai-embeddings-123", "vector-store-123"),
]
assert len(ingestion_edges) == len(expected_ingestion_edges)
for edge in ingestion_edges:
source = edge["source"]
target = edge["target"]
assert (source, target) in expected_ingestion_edges, edge
# Test RAG graph components and edges
rag_graph_dump = rag_graph.dump(
name="RAG Graph", description="Graph for Retrieval-Augmented Generation", endpoint_name="rag"
)
rag_data = rag_graph_dump["data"]
rag_nodes = rag_data["nodes"]
rag_edges = rag_data["edges"]
# Sort nodes by id to check components
rag_nodes = sorted(rag_nodes, key=lambda x: x["id"])
# Check components in the RAG graph
assert rag_nodes[0]["data"]["type"] == "ChatInput"
assert rag_nodes[0]["id"] == "chatinput-123"
assert rag_nodes[1]["data"]["type"] == "ChatOutput"
assert rag_nodes[1]["id"] == "chatoutput-123"
assert rag_nodes[2]["data"]["type"] == "OpenAIModelComponent"
assert rag_nodes[2]["id"] == "openai-123"
assert rag_nodes[3]["data"]["type"] == "OpenAIEmbeddingsComponent"
assert rag_nodes[3]["id"] == "openai-embeddings-124"
assert rag_nodes[4]["data"]["type"] == "ParseDataComponent"
assert rag_nodes[4]["id"] == "parse-data-123"
assert rag_nodes[5]["data"]["type"] == "PromptComponent"
assert rag_nodes[5]["id"] == "prompt-123"
assert rag_nodes[6]["data"]["type"] == "AstraVectorStoreComponent"
assert rag_nodes[6]["id"] == "rag-vector-store-123"
# Check edges in the RAG graph
expected_rag_edges = [
("chatinput-123", "rag-vector-store-123"),
("openai-embeddings-124", "rag-vector-store-123"),
("chatinput-123", "prompt-123"),
("rag-vector-store-123", "parse-data-123"),
("parse-data-123", "prompt-123"),
("prompt-123", "openai-123"),
("openai-123", "chatoutput-123"),
]
assert len(rag_edges) == len(expected_rag_edges), rag_edges
for edge in rag_edges:
source = edge["source"]
target = edge["target"]
assert (source, target) in expected_rag_edges, f"Edge {source} -> {target} not found"
def test_vector_store_rag_dump(ingestion_graph, rag_graph):
# Test ingestion graph dump
ingestion_graph_dump = ingestion_graph.dump(
name="Ingestion Graph", description="Graph for data ingestion", endpoint_name="ingestion"
)
assert isinstance(ingestion_graph_dump, dict)
ingestion_data = ingestion_graph_dump["data"]
assert "nodes" in ingestion_data
assert "edges" in ingestion_data
assert "description" in ingestion_graph_dump
assert "endpoint_name" in ingestion_graph_dump
ingestion_nodes = ingestion_data["nodes"]
ingestion_edges = ingestion_data["edges"]
assert len(ingestion_nodes) == 4 # There are 4 components in the ingestion graph
assert len(ingestion_edges) == 3 # There are 3 connections between components
# Test RAG graph dump
rag_graph_dump = rag_graph.dump(
name="RAG Graph", description="Graph for Retrieval-Augmented Generation", endpoint_name="rag"
)
assert isinstance(rag_graph_dump, dict)
rag_data = rag_graph_dump["data"]
assert "nodes" in rag_data
assert "edges" in rag_data
assert "description" in rag_graph_dump
assert "endpoint_name" in rag_graph_dump
rag_nodes = rag_data["nodes"]
rag_edges = rag_data["edges"]
assert len(rag_nodes) == 7 # There are 7 components in the RAG graph
assert len(rag_edges) == 7 # There are 7 connections between components