add status on building components
This commit is contained in:
commit
e395107473
23 changed files with 412 additions and 106 deletions
8
poetry.lock
generated
8
poetry.lock
generated
|
|
@ -385,13 +385,13 @@ lxml = ["lxml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bidict"
|
name = "bidict"
|
||||||
version = "0.23.0"
|
version = "0.23.1"
|
||||||
description = "The bidirectional mapping library for Python."
|
description = "The bidirectional mapping library for Python."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "bidict-0.23.0-py3-none-any.whl", hash = "sha256:f5154a0e42926b122f9b2cb06aeeb9af317f626eb864bd34c1b9cdc5eca8b040"},
|
{file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"},
|
||||||
{file = "bidict-0.23.0.tar.gz", hash = "sha256:3959ca59d4d6997702d642bf1e5fd93cba299863723fc289545198f70c468578"},
|
{file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -9383,4 +9383,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9,<3.12"
|
python-versions = ">=3.9,<3.12"
|
||||||
content-hash = "e2791fcc55065da8d8d57b7b768a5301b1c967a905596a2944472fccfc3152d8"
|
content-hash = "20b71a34763c614436d35902b6648a2a3bd970fc1abba31f21b19ec3ee4a2bba"
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ python-jose = "^3.3.0"
|
||||||
metaphor-python = "^0.1.11"
|
metaphor-python = "^0.1.11"
|
||||||
pydantic = "^2.6.0"
|
pydantic = "^2.6.0"
|
||||||
pydantic-settings = "^2.1.0"
|
pydantic-settings = "^2.1.0"
|
||||||
zep-python = "*"
|
zep-python = "1.5.0"
|
||||||
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
|
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
|
||||||
loguru = "^0.7.1"
|
loguru = "^0.7.1"
|
||||||
langfuse = "^2.9.0"
|
langfuse = "^2.9.0"
|
||||||
|
|
|
||||||
|
|
@ -284,14 +284,28 @@ async def get_vertices(
|
||||||
):
|
):
|
||||||
"""Check the flow_id is in the flow_data_store."""
|
"""Check the flow_id is in the flow_data_store."""
|
||||||
try:
|
try:
|
||||||
|
# First, we need to check if the flow_id is in the cache
|
||||||
|
graph = None
|
||||||
|
if cache := chat_service.get_cache(flow_id):
|
||||||
|
graph: Graph = cache.get("result")
|
||||||
|
|
||||||
flow: Flow = session.get(Flow, flow_id)
|
flow: Flow = session.get(Flow, flow_id)
|
||||||
if not flow or not flow.data:
|
if not flow or not flow.data:
|
||||||
raise ValueError("Invalid flow ID")
|
raise ValueError("Invalid flow ID")
|
||||||
graph = Graph.from_payload(flow.data)
|
other_graph = Graph.from_payload(flow.data)
|
||||||
|
if graph is None:
|
||||||
|
graph = other_graph
|
||||||
|
else:
|
||||||
|
graph = graph.update(other_graph)
|
||||||
chat_service.set_cache(flow_id, graph)
|
chat_service.set_cache(flow_id, graph)
|
||||||
|
|
||||||
if component_id:
|
if component_id:
|
||||||
|
try:
|
||||||
vertices = graph.sort_up_to_vertex(component_id)
|
vertices = graph.sort_up_to_vertex(component_id)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"IN DEVELOPMENT: Error getting vertices: {exc}")
|
||||||
|
logger.exception(exc)
|
||||||
|
vertices = graph.layered_topological_sort()
|
||||||
else:
|
else:
|
||||||
vertices = graph.layered_topological_sort()
|
vertices = graph.layered_topological_sort()
|
||||||
# Now vertices is a list of lists
|
# Now vertices is a list of lists
|
||||||
|
|
@ -345,12 +359,15 @@ async def build_vertex(
|
||||||
duration=duration,
|
duration=duration,
|
||||||
timedelta=timedelta,
|
timedelta=timedelta,
|
||||||
)
|
)
|
||||||
|
chat_service.set_cache(flow_id, graph)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
params = str(exc)
|
params = str(exc)
|
||||||
valid = False
|
valid = False
|
||||||
result_dict = ResultDict(results={})
|
result_dict = ResultDict(results={})
|
||||||
artifacts = {}
|
artifacts = {}
|
||||||
chat_service.set_cache(flow_id, graph)
|
# If there's an error building the vertex
|
||||||
|
# we need to clear the cache
|
||||||
|
chat_service.clear_cache(flow_id)
|
||||||
await log_vertex_build(
|
await log_vertex_build(
|
||||||
flow_id=flow_id,
|
flow_id=flow_id,
|
||||||
vertex_id=vertex_id,
|
vertex_id=vertex_id,
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,9 @@ class StreamData(BaseModel):
|
||||||
data: dict
|
data: dict
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
|
return (
|
||||||
|
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomComponentCode(BaseModel):
|
class CustomComponentCode(BaseModel):
|
||||||
|
|
@ -246,4 +248,3 @@ class VertexBuildResponse(BaseModel):
|
||||||
|
|
||||||
class VerticesBuiltResponse(BaseModel):
|
class VerticesBuiltResponse(BaseModel):
|
||||||
vertices: List[VertexBuildResponse]
|
vertices: List[VertexBuildResponse]
|
||||||
vertices: List[VertexBuildResponse]
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
class ChatInput(CustomComponent):
|
class ChatInput(CustomComponent):
|
||||||
|
|
@ -10,18 +11,35 @@ class ChatInput(CustomComponent):
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
"message": {"input_types": ["Text"], "display_name": "Message"},
|
"message": {"input_types": ["Text"], "display_name": "Message","multiline": True},
|
||||||
"sender": {"options": ["Machine", "User"], "display_name": "Sender Type"},
|
"sender_type": {
|
||||||
|
"options": ["Machine", "User"],
|
||||||
|
"display_name": "Sender Type",
|
||||||
|
},
|
||||||
"sender_name": {"display_name": "Sender Name"},
|
"sender_name": {"display_name": "Sender Name"},
|
||||||
|
"as_record": {
|
||||||
|
"display_name": "As Record",
|
||||||
|
"info": "If true, the message will be returned as a Record.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
self,
|
self,
|
||||||
sender: Optional[str] = "User",
|
sender_type: Optional[str] = "User",
|
||||||
sender_name: Optional[str] = "You",
|
sender_name: Optional[str] = "You",
|
||||||
message: Optional[str] = None,
|
message: Optional[str] = None,
|
||||||
) -> Text:
|
as_record: Optional[bool] = False,
|
||||||
self.repr_value = message
|
) -> Union[Text, Record]:
|
||||||
|
self.status = message
|
||||||
|
if as_record:
|
||||||
|
if isinstance(message, Record):
|
||||||
|
# Update the data of the record
|
||||||
|
message.data["sender"] = sender_type
|
||||||
|
message.data["sender_name"] = sender_name
|
||||||
|
return message
|
||||||
|
return Record(
|
||||||
|
text=message, data={"sender": sender_type, "sender_name": sender_name}
|
||||||
|
)
|
||||||
if not message:
|
if not message:
|
||||||
message = ""
|
message = ""
|
||||||
return message
|
return message
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
class ChatOutput(CustomComponent):
|
class ChatOutput(CustomComponent):
|
||||||
|
|
@ -17,23 +18,47 @@ class ChatOutput(CustomComponent):
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
"message": {"input_types": ["Text"], "display_name": "Message"},
|
"message": {"input_types": ["Text"], "display_name": "Message"},
|
||||||
"sender": {"options": ["Machine", "User"], "display_name": "Sender Type"},
|
"sender_type": {
|
||||||
|
"options": ["Machine", "User"],
|
||||||
|
"display_name": "Sender Type",
|
||||||
|
},
|
||||||
"sender_name": {"display_name": "Sender Name"},
|
"sender_name": {"display_name": "Sender Name"},
|
||||||
"session_id": {
|
"session_id": {
|
||||||
"display_name": "Session ID",
|
"display_name": "Session ID",
|
||||||
"info": "Session ID of the chat history.",
|
"info": "Session ID of the chat history.",
|
||||||
"input_types": ["Text"],
|
"input_types": ["Text"],
|
||||||
},
|
},
|
||||||
|
"as_record": {
|
||||||
|
"display_name": "As Record",
|
||||||
|
"info": "If true, the message will be returned as a Record.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
self,
|
self,
|
||||||
sender: Optional[str] = "Machine",
|
sender_type: Optional[str] = "Machine",
|
||||||
sender_name: Optional[str] = "AI",
|
sender_name: Optional[str] = "AI",
|
||||||
session_id: Optional[str] = None,
|
session_id: Optional[str] = None,
|
||||||
message: Optional[str] = None,
|
message: Optional[str] = None,
|
||||||
) -> Text:
|
as_record: Optional[bool] = False,
|
||||||
self.repr_value = message
|
) -> Union[Text, Record]:
|
||||||
|
self.status = message
|
||||||
|
if as_record:
|
||||||
|
if isinstance(message, Record):
|
||||||
|
# Update the data of the record
|
||||||
|
message.data["sender"] = sender_type
|
||||||
|
message.data["sender_name"] = sender_name
|
||||||
|
message.data["session_id"] = session_id
|
||||||
|
|
||||||
|
return message
|
||||||
|
return Record(
|
||||||
|
text=message,
|
||||||
|
data={
|
||||||
|
"sender": sender_type,
|
||||||
|
"sender_name": sender_name,
|
||||||
|
"session_id": session_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
if not message:
|
if not message:
|
||||||
message = ""
|
message = ""
|
||||||
return message
|
return message
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class PromptComponent(CustomComponent):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
prompt_template = PromptTemplate.from_template(template)
|
prompt_template = PromptTemplate.from_template(template)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
formated_prompt = prompt_template.format(**kwargs)
|
formated_prompt = prompt_template.format(**kwargs)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
||||||
18
src/backend/langflow/components/utilities/IDGenerator.py
Normal file
18
src/backend/langflow/components/utilities/IDGenerator.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
|
||||||
|
class UUIDGeneratorComponent(CustomComponent):
|
||||||
|
documentation: str = "http://docs.langflow.org/components/custom"
|
||||||
|
display_name = "Unique ID Generator"
|
||||||
|
description = "Generates a unique ID."
|
||||||
|
|
||||||
|
def generate(self, *args, **kwargs):
|
||||||
|
return str(uuid.uuid4().hex)
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {"unique_id": {"display_name": "Value", "value": self.generate}}
|
||||||
|
|
||||||
|
def build(self, unique_id: str) -> str:
|
||||||
|
return unique_id
|
||||||
|
|
@ -99,7 +99,7 @@ class ChromaSearchComponent(CustomComponent):
|
||||||
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
||||||
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
|
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
|
||||||
)
|
)
|
||||||
|
index_directory = self.resolve_path(index_directory)
|
||||||
chroma = Chroma(
|
chroma = Chroma(
|
||||||
embedding_function=embedding,
|
embedding_function=embedding,
|
||||||
collection_name=collection_name,
|
collection_name=collection_name,
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,28 @@ class Graph:
|
||||||
return False
|
return False
|
||||||
return self.__repr__() == other.__repr__()
|
return self.__repr__() == other.__repr__()
|
||||||
|
|
||||||
|
# update this graph with another graph by comparing the __repr__ of each vertex
|
||||||
|
# and if the __repr__ of a vertex is not the same as the other
|
||||||
|
# then update the .data of the vertex to the self
|
||||||
|
# both graphs have the same vertices and edges
|
||||||
|
# but the data of the vertices might be different
|
||||||
|
def update(self, other: "Graph", different_vertices: List[str] = None) -> None:
|
||||||
|
if different_vertices is None:
|
||||||
|
different_vertices = []
|
||||||
|
for vertex in self.vertices:
|
||||||
|
other_vertex = other.get_vertex(vertex.id)
|
||||||
|
if other_vertex is None:
|
||||||
|
continue
|
||||||
|
if (
|
||||||
|
vertex.id in different_vertices
|
||||||
|
or vertex.__repr__() != other.get_vertex(vertex.id).__repr__()
|
||||||
|
):
|
||||||
|
vertex.data = other.get_vertex(vertex.id).data
|
||||||
|
vertex._build_params()
|
||||||
|
vertex.graph = self
|
||||||
|
vertex._built = False
|
||||||
|
return self
|
||||||
|
|
||||||
def _build_graph(self) -> None:
|
def _build_graph(self) -> None:
|
||||||
"""Builds the graph from the vertices and edges."""
|
"""Builds the graph from the vertices and edges."""
|
||||||
self.vertices = self._build_vertices()
|
self.vertices = self._build_vertices()
|
||||||
|
|
@ -314,9 +336,22 @@ class Graph:
|
||||||
"""Cuts the graph up to a given vertex."""
|
"""Cuts the graph up to a given vertex."""
|
||||||
# Get the vertices that are connected to the vertex
|
# Get the vertices that are connected to the vertex
|
||||||
# and the vertex itself
|
# and the vertex itself
|
||||||
vertices = [self.get_vertex(vertex_id)]
|
vertex = self.get_vertex(vertex_id)
|
||||||
for edge in self.get_vertex(edge.target_id).edges:
|
vertices = [vertex]
|
||||||
vertices.append(self.get_vertex(edge.target_id))
|
for edge in vertex.edges:
|
||||||
|
if edge.target_id == vertex_id:
|
||||||
|
vertices.append(self.get_vertex(edge.source_id))
|
||||||
|
|
||||||
|
# Get the edges that are connected to the vertices
|
||||||
|
edges = []
|
||||||
|
for vertex in vertices:
|
||||||
|
edges.extend(self.get_vertex_edges(vertex.id))
|
||||||
|
source_vertex = self.get_vertex(edge.source_id)
|
||||||
|
target_vertex = self.get_vertex(edge.target_id)
|
||||||
|
if source_vertex not in vertices:
|
||||||
|
vertices.append(source_vertex)
|
||||||
|
if target_vertex not in vertices:
|
||||||
|
vertices.append(target_vertex)
|
||||||
|
|
||||||
edges = [edge for vertex in vertices for edge in vertex.edges]
|
edges = [edge for vertex in vertices for edge in vertex.edges]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,14 @@ class Vertex:
|
||||||
self.parent_node_id: Optional[str] = self._data.get("parent_node_id")
|
self.parent_node_id: Optional[str] = self._data.get("parent_node_id")
|
||||||
self.parent_is_top_level = False
|
self.parent_is_top_level = False
|
||||||
self.layer = None
|
self.layer = None
|
||||||
|
self.should_run = True
|
||||||
try:
|
try:
|
||||||
self.is_interface_component = InterfaceComponentTypes(self.vertex_type)
|
self.is_interface_component = InterfaceComponentTypes(self.vertex_type)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.is_interface_component = False
|
self.is_interface_component = False
|
||||||
|
|
||||||
|
self.use_result = False
|
||||||
|
|
||||||
# Build a result dict for each edge
|
# Build a result dict for each edge
|
||||||
# like so: {edge.target.id: {edge.target_param: self._built_object}}
|
# like so: {edge.target.id: {edge.target_param: self._built_object}}
|
||||||
async def get_result_dict(self, force: bool = False) -> Dict[str, Dict[str, Any]]:
|
async def get_result_dict(self, force: bool = False) -> Dict[str, Dict[str, Any]]:
|
||||||
|
|
@ -78,14 +81,18 @@ class Vertex:
|
||||||
):
|
):
|
||||||
if edge.target_id not in edge_results:
|
if edge.target_id not in edge_results:
|
||||||
edge_results[edge.target_id] = {}
|
edge_results[edge.target_id] = {}
|
||||||
edge_results[edge.target_id][edge.target_param] = await edge.get_result(source=self, target=target)
|
edge_results[edge.target_id][edge.target_param] = await edge.get_result(
|
||||||
|
source=self, target=target
|
||||||
|
)
|
||||||
return edge_results
|
return edge_results
|
||||||
|
|
||||||
def get_built_result(self):
|
def get_built_result(self):
|
||||||
# If the Vertex.type is a power component
|
# If the Vertex.type is a power component
|
||||||
# then we need to return the built object
|
# then we need to return the built object
|
||||||
# instead of the result dict
|
# instead of the result dict
|
||||||
if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject):
|
if self.is_interface_component and not isinstance(
|
||||||
|
self._built_object, UnbuiltObject
|
||||||
|
):
|
||||||
result = self._built_object
|
result = self._built_object
|
||||||
# if it is not a dict or a string and hasattr model_dump then
|
# if it is not a dict or a string and hasattr model_dump then
|
||||||
# return the model_dump
|
# return the model_dump
|
||||||
|
|
@ -95,7 +102,11 @@ class Vertex:
|
||||||
|
|
||||||
if isinstance(self._built_result, UnbuiltResult):
|
if isinstance(self._built_result, UnbuiltResult):
|
||||||
return {}
|
return {}
|
||||||
return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result}
|
return (
|
||||||
|
self._built_result
|
||||||
|
if isinstance(self._built_result, dict)
|
||||||
|
else {"result": self._built_result}
|
||||||
|
)
|
||||||
|
|
||||||
def set_artifacts(self) -> None:
|
def set_artifacts(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
@ -149,18 +160,29 @@ class Vertex:
|
||||||
self.data = self._data["data"]
|
self.data = self._data["data"]
|
||||||
self.output = self.data["node"]["base_classes"]
|
self.output = self.data["node"]["base_classes"]
|
||||||
self.pinned = self.data["node"].get("pinned", False)
|
self.pinned = self.data["node"].get("pinned", False)
|
||||||
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
template_dicts = {
|
||||||
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
key: value
|
||||||
|
for key, value in self.data["node"]["template"].items()
|
||||||
|
if isinstance(value, dict)
|
||||||
|
}
|
||||||
|
|
||||||
self.required_inputs = [
|
self.required_inputs = [
|
||||||
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
|
template_dicts[key]["type"]
|
||||||
|
for key, value in template_dicts.items()
|
||||||
|
if value["required"]
|
||||||
]
|
]
|
||||||
self.optional_inputs = [
|
self.optional_inputs = [
|
||||||
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
|
template_dicts[key]["type"]
|
||||||
|
for key, value in template_dicts.items()
|
||||||
|
if not value["required"]
|
||||||
]
|
]
|
||||||
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
||||||
self.optional_inputs.extend(
|
self.optional_inputs.extend(
|
||||||
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
|
[
|
||||||
|
input_type
|
||||||
|
for value in template_dicts.values()
|
||||||
|
for input_type in value.get("input_types", [])
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
template_dict = self.data["node"]["template"]
|
template_dict = self.data["node"]["template"]
|
||||||
|
|
@ -203,7 +225,11 @@ class Vertex:
|
||||||
if self.graph is None:
|
if self.graph is None:
|
||||||
raise ValueError("Graph not found")
|
raise ValueError("Graph not found")
|
||||||
|
|
||||||
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
template_dict = {
|
||||||
|
key: value
|
||||||
|
for key, value in self.data["node"]["template"].items()
|
||||||
|
if isinstance(value, dict)
|
||||||
|
}
|
||||||
params = self.params.copy() if self.params else {}
|
params = self.params.copy() if self.params else {}
|
||||||
|
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
|
|
@ -255,7 +281,11 @@ class Vertex:
|
||||||
# list of dicts, so we need to convert it to a dict
|
# list of dicts, so we need to convert it to a dict
|
||||||
# before passing it to the build method
|
# before passing it to the build method
|
||||||
if isinstance(val, list):
|
if isinstance(val, list):
|
||||||
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
|
params[key] = {
|
||||||
|
k: v
|
||||||
|
for item in value.get("value", [])
|
||||||
|
for k, v in item.items()
|
||||||
|
}
|
||||||
elif isinstance(val, dict):
|
elif isinstance(val, dict):
|
||||||
params[key] = val
|
params[key] = val
|
||||||
elif value.get("type") == "int" and val is not None:
|
elif value.get("type") == "int" and val is not None:
|
||||||
|
|
@ -292,7 +322,12 @@ class Vertex:
|
||||||
|
|
||||||
self._built = True
|
self._built = True
|
||||||
|
|
||||||
async def _run(self, user_id: str, inputs: Optional[dict] = None, session_id: Optional[str] = None):
|
async def _run(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
inputs: Optional[dict] = None,
|
||||||
|
session_id: Optional[str] = None,
|
||||||
|
):
|
||||||
# user_id is just for compatibility with the other build methods
|
# user_id is just for compatibility with the other build methods
|
||||||
inputs = inputs or {}
|
inputs = inputs or {}
|
||||||
# inputs = {key: value or "" for key, value in inputs.items()}
|
# inputs = {key: value or "" for key, value in inputs.items()}
|
||||||
|
|
@ -307,7 +342,9 @@ class Vertex:
|
||||||
if isinstance(self._built_object, str):
|
if isinstance(self._built_object, str):
|
||||||
self._built_result = self._built_object
|
self._built_result = self._built_object
|
||||||
|
|
||||||
result = await generate_result(self._built_object, inputs, self.has_external_output, session_id)
|
result = await generate_result(
|
||||||
|
self._built_object, inputs, self.has_external_output, session_id
|
||||||
|
)
|
||||||
self._built_result = result
|
self._built_result = result
|
||||||
|
|
||||||
async def _build_each_node_in_params_dict(self, user_id=None):
|
async def _build_each_node_in_params_dict(self, user_id=None):
|
||||||
|
|
@ -335,11 +372,13 @@ class Vertex:
|
||||||
"""
|
"""
|
||||||
return all(self._is_node(node) for node in value)
|
return all(self._is_node(node) for node in value)
|
||||||
|
|
||||||
async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any:
|
async def get_result(
|
||||||
|
self, requester: Optional["Vertex"] = None, user_id=None, timeout=None
|
||||||
|
) -> Any:
|
||||||
# PLEASE REVIEW THIS IF STATEMENT
|
# PLEASE REVIEW THIS IF STATEMENT
|
||||||
# Check if the Vertex was built already
|
# Check if the Vertex was built already
|
||||||
if self._built:
|
if self._built:
|
||||||
return self._built_object
|
return self._built_object if not self.use_result else self._built_result
|
||||||
|
|
||||||
if self.is_task and self.task_id is not None:
|
if self.is_task and self.task_id is not None:
|
||||||
task = self.get_task()
|
task = self.get_task()
|
||||||
|
|
@ -369,7 +408,9 @@ class Vertex:
|
||||||
self._extend_params_list_with_result(key, result)
|
self._extend_params_list_with_result(key, result)
|
||||||
self.params[key] = result
|
self.params[key] = result
|
||||||
|
|
||||||
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
|
async def _build_list_of_nodes_and_update_params(
|
||||||
|
self, key, nodes: List["Vertex"], user_id=None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Iterates over a list of nodes, builds each and updates the params dictionary.
|
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
@ -421,7 +462,9 @@ class Vertex:
|
||||||
self._update_built_object_and_artifacts(result)
|
self._update_built_object_and_artifacts(result)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise ValueError(f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}") from exc
|
raise ValueError(
|
||||||
|
f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
def _update_built_object_and_artifacts(self, result):
|
def _update_built_object_and_artifacts(self, result):
|
||||||
"""
|
"""
|
||||||
|
|
@ -458,7 +501,7 @@ class Vertex:
|
||||||
requester: Optional["Vertex"] = None,
|
requester: Optional["Vertex"] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
if self.pinned:
|
if self.pinned and self._built:
|
||||||
return self.get_requester_result(requester)
|
return self.get_requester_result(requester)
|
||||||
self._reset()
|
self._reset()
|
||||||
|
|
||||||
|
|
@ -480,9 +523,15 @@ class Vertex:
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
# Get the requester edge
|
# Get the requester edge
|
||||||
requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
|
requester_edge = next(
|
||||||
|
(edge for edge in self.edges if edge.target_id == requester.id), None
|
||||||
|
)
|
||||||
# Return the result of the requester edge
|
# Return the result of the requester edge
|
||||||
return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester)
|
return (
|
||||||
|
None
|
||||||
|
if requester_edge is None
|
||||||
|
else await requester_edge.get_result(source=self, target=requester)
|
||||||
|
)
|
||||||
|
|
||||||
def add_edge(self, edge: "ContractEdge") -> None:
|
def add_edge(self, edge: "ContractEdge") -> None:
|
||||||
if edge not in self.edges:
|
if edge not in self.edges:
|
||||||
|
|
@ -502,7 +551,11 @@ class Vertex:
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
# Add a message with an emoji, stars for sucess,
|
# Add a message with an emoji, stars for sucess,
|
||||||
return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵💫"
|
return (
|
||||||
|
"Built sucessfully ✨"
|
||||||
|
if self._built_object is not None
|
||||||
|
else "Failed to build 😵💫"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StatefulVertex(Vertex):
|
class StatefulVertex(Vertex):
|
||||||
|
|
@ -511,9 +564,3 @@ class StatefulVertex(Vertex):
|
||||||
|
|
||||||
class StatelessVertex(Vertex):
|
class StatelessVertex(Vertex):
|
||||||
pass
|
pass
|
||||||
pass
|
|
||||||
pass
|
|
||||||
pass
|
|
||||||
pass
|
|
||||||
pass
|
|
||||||
pass
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import json
|
||||||
from typing import Callable, Dict, List, Optional, Union
|
from typing import Callable, Dict, List, Optional, Union
|
||||||
|
|
||||||
from langchain_core.messages import AIMessage
|
from langchain_core.messages import AIMessage
|
||||||
|
|
||||||
from langflow.graph.utils import UnbuiltObject, flatten_list
|
from langflow.graph.utils import UnbuiltObject, flatten_list
|
||||||
from langflow.graph.vertex.base import StatefulVertex, StatelessVertex
|
from langflow.graph.vertex.base import StatefulVertex, StatelessVertex
|
||||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||||
|
from langflow.schema import Record
|
||||||
from langflow.utils.schemas import ChatOutputResponse
|
from langflow.utils.schemas import ChatOutputResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,9 +118,11 @@ class DocumentLoaderVertex(StatefulVertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if not isinstance(self._built_object, UnbuiltObject):
|
if not isinstance(self._built_object, UnbuiltObject):
|
||||||
avg_length = sum(len(doc.page_content) for doc in self._built_object if hasattr(doc, "page_content")) / len(
|
avg_length = sum(
|
||||||
self._built_object
|
len(doc.page_content)
|
||||||
)
|
for doc in self._built_object
|
||||||
|
if hasattr(doc, "page_content")
|
||||||
|
) / len(self._built_object)
|
||||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||||
\nAvg. Document Length (characters): {int(avg_length)}
|
\nAvg. Document Length (characters): {int(avg_length)}
|
||||||
Documents: {self._built_object[:3]}..."""
|
Documents: {self._built_object[:3]}..."""
|
||||||
|
|
@ -193,7 +195,9 @@ class TextSplitterVertex(StatefulVertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if not isinstance(self._built_object, UnbuiltObject):
|
if not isinstance(self._built_object, UnbuiltObject):
|
||||||
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
|
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
|
||||||
|
self._built_object
|
||||||
|
)
|
||||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||||
\nAvg. Document Length (characters): {int(avg_length)}
|
\nAvg. Document Length (characters): {int(avg_length)}
|
||||||
\nDocuments: {self._built_object[:3]}..."""
|
\nDocuments: {self._built_object[:3]}..."""
|
||||||
|
|
@ -240,18 +244,27 @@ class PromptVertex(StatelessVertex):
|
||||||
user_id = kwargs.get("user_id", None)
|
user_id = kwargs.get("user_id", None)
|
||||||
tools = kwargs.get("tools", [])
|
tools = kwargs.get("tools", [])
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
if "input_variables" not in self.params or self.params["input_variables"] is None:
|
if (
|
||||||
|
"input_variables" not in self.params
|
||||||
|
or self.params["input_variables"] is None
|
||||||
|
):
|
||||||
self.params["input_variables"] = []
|
self.params["input_variables"] = []
|
||||||
# Check if it is a ZeroShotPrompt and needs a tool
|
# Check if it is a ZeroShotPrompt and needs a tool
|
||||||
if "ShotPrompt" in self.vertex_type:
|
if "ShotPrompt" in self.vertex_type:
|
||||||
tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
|
tools = (
|
||||||
|
[tool_node.build(user_id=user_id) for tool_node in tools]
|
||||||
|
if tools is not None
|
||||||
|
else []
|
||||||
|
)
|
||||||
# flatten the list of tools if it is a list of lists
|
# flatten the list of tools if it is a list of lists
|
||||||
# first check if it is a list
|
# first check if it is a list
|
||||||
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
||||||
tools = flatten_list(tools)
|
tools = flatten_list(tools)
|
||||||
self.params["tools"] = tools
|
self.params["tools"] = tools
|
||||||
prompt_params = [
|
prompt_params = [
|
||||||
key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
|
key
|
||||||
|
for key, value in self.params.items()
|
||||||
|
if isinstance(value, str) and key != "format_instructions"
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
prompt_params = ["template"]
|
prompt_params = ["template"]
|
||||||
|
|
@ -261,14 +274,20 @@ class PromptVertex(StatelessVertex):
|
||||||
prompt_text = self.params[param]
|
prompt_text = self.params[param]
|
||||||
variables = extract_input_variables_from_prompt(prompt_text)
|
variables = extract_input_variables_from_prompt(prompt_text)
|
||||||
self.params["input_variables"].extend(variables)
|
self.params["input_variables"].extend(variables)
|
||||||
self.params["input_variables"] = list(set(self.params["input_variables"]))
|
self.params["input_variables"] = list(
|
||||||
|
set(self.params["input_variables"])
|
||||||
|
)
|
||||||
elif isinstance(self.params, dict):
|
elif isinstance(self.params, dict):
|
||||||
self.params.pop("input_variables", None)
|
self.params.pop("input_variables", None)
|
||||||
|
|
||||||
await self._build(user_id=user_id)
|
await self._build(user_id=user_id)
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
|
if (
|
||||||
|
not self.artifacts
|
||||||
|
or self._built_object is None
|
||||||
|
or not hasattr(self._built_object, "format")
|
||||||
|
):
|
||||||
return super()._built_object_repr()
|
return super()._built_object_repr()
|
||||||
elif isinstance(self._built_object, UnbuiltObject):
|
elif isinstance(self._built_object, UnbuiltObject):
|
||||||
return super()._built_object_repr()
|
return super()._built_object_repr()
|
||||||
|
|
@ -280,7 +299,9 @@ class PromptVertex(StatelessVertex):
|
||||||
# so the prompt format doesn't break
|
# so the prompt format doesn't break
|
||||||
artifacts.pop("handle_keys", None)
|
artifacts.pop("handle_keys", None)
|
||||||
try:
|
try:
|
||||||
if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
|
if not hasattr(self._built_object, "template") and hasattr(
|
||||||
|
self._built_object, "prompt"
|
||||||
|
):
|
||||||
template = self._built_object.prompt.template
|
template = self._built_object.prompt.template
|
||||||
else:
|
else:
|
||||||
template = self._built_object.template
|
template = self._built_object.template
|
||||||
|
|
@ -288,7 +309,11 @@ class PromptVertex(StatelessVertex):
|
||||||
if value:
|
if value:
|
||||||
replace_key = "{" + key + "}"
|
replace_key = "{" + key + "}"
|
||||||
template = template.replace(replace_key, value)
|
template = template.replace(replace_key, value)
|
||||||
return template if isinstance(template, str) else f"{self.vertex_type}({template})"
|
return (
|
||||||
|
template
|
||||||
|
if isinstance(template, str)
|
||||||
|
else f"{self.vertex_type}({template})"
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return str(self._built_object)
|
return str(self._built_object)
|
||||||
|
|
||||||
|
|
@ -337,12 +362,16 @@ class ChatVertex(StatelessVertex):
|
||||||
if isinstance(self._built_object, dict):
|
if isinstance(self._built_object, dict):
|
||||||
# Turn the dict into a pleasing to
|
# Turn the dict into a pleasing to
|
||||||
# read JSON inside a code block
|
# read JSON inside a code block
|
||||||
self._built_object = dict_to_codeblock(self._built_object)
|
message = dict_to_codeblock(self._built_object)
|
||||||
|
elif isinstance(self._built_object, Record):
|
||||||
|
message = self._built_object.text
|
||||||
elif not isinstance(self._built_object, str):
|
elif not isinstance(self._built_object, str):
|
||||||
self._built_object = str(self._built_object)
|
message = str(self._built_object)
|
||||||
|
else:
|
||||||
|
message = self._built_object
|
||||||
|
|
||||||
artifacts = ChatOutputResponse(
|
artifacts = ChatOutputResponse(
|
||||||
message=self._built_object,
|
message=message,
|
||||||
sender=sender,
|
sender=sender,
|
||||||
sender_name=sender_name,
|
sender_name=sender_name,
|
||||||
)
|
)
|
||||||
|
|
@ -354,6 +383,52 @@ class ChatVertex(StatelessVertex):
|
||||||
await super()._run(*args, **kwargs)
|
await super()._run(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RoutingVertex(StatelessVertex):
|
||||||
|
def __init__(self, data: Dict, graph):
|
||||||
|
super().__init__(data, graph=graph, base_type="routing")
|
||||||
|
self.use_result = True
|
||||||
|
self.steps = [self._build, self._run]
|
||||||
|
|
||||||
|
def _built_object_repr(self):
|
||||||
|
if self.artifacts and "repr" in self.artifacts:
|
||||||
|
return self.artifacts["repr"] or super()._built_object_repr()
|
||||||
|
return super()._built_object_repr()
|
||||||
|
|
||||||
|
def _build(self, *args, **kwargs):
|
||||||
|
super()._build(*args, **kwargs)
|
||||||
|
|
||||||
|
# After building, the _built_object should be a dict with
|
||||||
|
# {"result": Any, "condition": bool}
|
||||||
|
# if true, we need to set should_run attr in the target of true edge
|
||||||
|
# to true and should_run attr in the target of false edge to false
|
||||||
|
# TODO: Add support for multiple conditions
|
||||||
|
|
||||||
|
def _run(self, *args, **kwargs):
|
||||||
|
if self._built_object:
|
||||||
|
condition = self._built_object.get("condition")
|
||||||
|
result = self._built_object.get("result")
|
||||||
|
if condition is not None:
|
||||||
|
for edge in self.edges:
|
||||||
|
if edge.source_id == self.id:
|
||||||
|
target_vertex = self.graph.get_vertex(edge.target_id)
|
||||||
|
# source_handle.channel and condition should be the same
|
||||||
|
channel_bool = edge.source_handle.channel == "true"
|
||||||
|
if condition == channel_bool:
|
||||||
|
target_vertex.should_run = True
|
||||||
|
else:
|
||||||
|
target_vertex.should_run = False
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"RoutingVertex {self.id} must have a condition in the _built_object"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._built_result = result
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"RoutingVertex {self.id} must have a _built_object with a condition and a result"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def dict_to_codeblock(d: dict) -> str:
|
def dict_to_codeblock(d: dict) -> str:
|
||||||
from langflow.api.utils import serialize_field
|
from langflow.api.utils import serialize_field
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from langchain_core.documents import Document
|
from langchain_core.documents import Document
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
@ -38,6 +38,24 @@ class Record(BaseModel):
|
||||||
"""
|
"""
|
||||||
return Document(page_content=self.text, metadata=self.data)
|
return Document(page_content=self.text, metadata=self.data)
|
||||||
|
|
||||||
|
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
||||||
|
"""
|
||||||
|
Returns the text of the record.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: The text of the record.
|
||||||
|
"""
|
||||||
|
return self.text
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns the text of the record.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The text of the record.
|
||||||
|
"""
|
||||||
|
return self.text
|
||||||
|
|
||||||
|
|
||||||
def docs_to_records(documents: list[Document]) -> list[Record]:
|
def docs_to_records(documents: list[Document]) -> list[Record]:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,9 @@ class ChatService(Service):
|
||||||
"""Send the last chat message to the client."""
|
"""Send the last chat message to the client."""
|
||||||
client_id = self.chat_cache.current_client_id
|
client_id = self.chat_cache.current_client_id
|
||||||
if client_id in self.active_connections:
|
if client_id in self.active_connections:
|
||||||
chat_response = self.chat_history.get_history(client_id, filter_messages=False)[-1]
|
chat_response = self.chat_history.get_history(
|
||||||
|
client_id, filter_messages=False
|
||||||
|
)[-1]
|
||||||
if chat_response.is_bot:
|
if chat_response.is_bot:
|
||||||
# Process FileResponse
|
# Process FileResponse
|
||||||
if isinstance(chat_response, FileResponse):
|
if isinstance(chat_response, FileResponse):
|
||||||
|
|
@ -86,7 +88,9 @@ class ChatService(Service):
|
||||||
data_type=self.last_cached_object_dict["type"],
|
data_type=self.last_cached_object_dict["type"],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.chat_history.add_message(self.chat_cache.current_client_id, chat_response)
|
self.chat_history.add_message(
|
||||||
|
self.chat_cache.current_client_id, chat_response
|
||||||
|
)
|
||||||
|
|
||||||
async def connect(self, client_id: str, websocket: WebSocket):
|
async def connect(self, client_id: str, websocket: WebSocket):
|
||||||
self.active_connections[client_id] = websocket
|
self.active_connections[client_id] = websocket
|
||||||
|
|
@ -209,7 +213,9 @@ class ChatService(Service):
|
||||||
await self.process_message(client_id, payload, build_result)
|
await self.process_message(client_id, payload, build_result)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Could not find a build result for client_id {client_id}")
|
raise RuntimeError(
|
||||||
|
f"Could not find a build result for client_id {client_id}"
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# Handle any exceptions that might occur
|
# Handle any exceptions that might occur
|
||||||
logger.exception(f"Error handling websocket: {exc}")
|
logger.exception(f"Error handling websocket: {exc}")
|
||||||
|
|
@ -241,6 +247,12 @@ class ChatService(Service):
|
||||||
"""
|
"""
|
||||||
return self.cache_service.get(client_id)
|
return self.cache_service.get(client_id)
|
||||||
|
|
||||||
|
def clear_cache(self, client_id: str):
|
||||||
|
"""
|
||||||
|
Clear the cache for a client.
|
||||||
|
"""
|
||||||
|
self.cache_service.delete(client_id)
|
||||||
|
|
||||||
|
|
||||||
def dict_to_markdown_table(my_dict):
|
def dict_to_markdown_table(my_dict):
|
||||||
markdown_table = "| Key | Value |\n|---|---|\n"
|
markdown_table = "| Key | Value |\n|---|---|\n"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ from pydantic import BaseModel, Field, field_serializer, validator
|
||||||
|
|
||||||
class TransactionModel(BaseModel):
|
class TransactionModel(BaseModel):
|
||||||
id: Optional[int] = Field(default=None, alias="id")
|
id: Optional[int] = Field(default=None, alias="id")
|
||||||
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
|
timestamp: Optional[datetime] = Field(
|
||||||
|
default_factory=datetime.now, alias="timestamp"
|
||||||
|
)
|
||||||
source: str
|
source: str
|
||||||
target: str
|
target: str
|
||||||
target_args: dict
|
target_args: dict
|
||||||
|
|
@ -89,18 +91,13 @@ class VertexBuildModel(BaseModel):
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
# create a function that turns dicts into a
|
class VertexBuildResponseModel(VertexBuildModel):
|
||||||
# dict like this:
|
|
||||||
# my_map_dict = {
|
@field_serializer("data", "artifacts")
|
||||||
# "key": [
|
def serialize_dict(v):
|
||||||
# 1, 2, 3
|
return v
|
||||||
# ],
|
|
||||||
# "value": [
|
|
||||||
# "one", "two", "three"
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
# so map has a "key" and a "value" list
|
|
||||||
# containing the keys and values of the dict
|
|
||||||
def to_map(value: dict):
|
def to_map(value: dict):
|
||||||
keys = list(value.keys())
|
keys = list(value.keys())
|
||||||
values = list(value.values())
|
values = list(value.values())
|
||||||
|
|
@ -108,13 +105,13 @@ def to_map(value: dict):
|
||||||
|
|
||||||
|
|
||||||
class VertexBuildMapModel(BaseModel):
|
class VertexBuildMapModel(BaseModel):
|
||||||
vertex_builds: dict[str, list[VertexBuildModel]]
|
vertex_builds: dict[str, list[VertexBuildResponseModel]]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list_of_dicts(cls, vertex_build_dicts):
|
def from_list_of_dicts(cls, vertex_build_dicts):
|
||||||
vertex_build_map = {}
|
vertex_build_map = {}
|
||||||
for vertex_build_dict in vertex_build_dicts:
|
for vertex_build_dict in vertex_build_dicts:
|
||||||
vertex_build = VertexBuildModel(**vertex_build_dict)
|
vertex_build = VertexBuildResponseModel(**vertex_build_dict)
|
||||||
if vertex_build.id not in vertex_build_map:
|
if vertex_build.id not in vertex_build_map:
|
||||||
vertex_build_map[vertex_build.id] = []
|
vertex_build_map[vertex_build.id] = []
|
||||||
vertex_build_map[vertex_build.id].append(vertex_build)
|
vertex_build_map[vertex_build.id].append(vertex_build)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
import yaml
|
||||||
from langflow.services.base import Service
|
from langflow.services.base import Service
|
||||||
from langflow.services.settings.auth import AuthSettings
|
from langflow.services.settings.auth import AuthSettings
|
||||||
from langflow.services.settings.base import Settings
|
from langflow.services.settings.base import Settings
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
import os
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsService(Service):
|
class SettingsService(Service):
|
||||||
|
|
@ -11,8 +12,8 @@ class SettingsService(Service):
|
||||||
|
|
||||||
def __init__(self, settings: Settings, auth_settings: AuthSettings):
|
def __init__(self, settings: Settings, auth_settings: AuthSettings):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.settings = settings
|
self.settings: Settings = settings
|
||||||
self.auth_settings = auth_settings
|
self.auth_settings: AuthSettings = auth_settings
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_settings_from_yaml(cls, file_path: str) -> "SettingsService":
|
def load_settings_from_yaml(cls, file_path: str) -> "SettingsService":
|
||||||
|
|
@ -30,7 +31,9 @@ class SettingsService(Service):
|
||||||
for key in settings_dict:
|
for key in settings_dict:
|
||||||
if key not in Settings.model_fields.keys():
|
if key not in Settings.model_fields.keys():
|
||||||
raise KeyError(f"Key {key} not found in settings")
|
raise KeyError(f"Key {key} not found in settings")
|
||||||
logger.debug(f"Loading {len(settings_dict[key])} {key} from {file_path}")
|
logger.debug(
|
||||||
|
f"Loading {len(settings_dict[key])} {key} from {file_path}"
|
||||||
|
)
|
||||||
|
|
||||||
settings = Settings(**settings_dict)
|
settings = Settings(**settings_dict)
|
||||||
if not settings.CONFIG_DIR:
|
if not settings.CONFIG_DIR:
|
||||||
|
|
|
||||||
|
|
@ -406,6 +406,8 @@ export default function ParameterComponent({
|
||||||
data-testid={"textarea-" + data.node.template[name].name}
|
data-testid={"textarea-" + data.node.template[name].name}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
<div className="mt-2 flex w-full items-center">
|
||||||
|
<div className="w-5/6 flex-grow">
|
||||||
<InputComponent
|
<InputComponent
|
||||||
id={"input-" + index}
|
id={"input-" + index}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -413,6 +415,18 @@ export default function ParameterComponent({
|
||||||
value={data.node?.template[name].value ?? ""}
|
value={data.node?.template[name].value ?? ""}
|
||||||
onChange={handleOnNewValue}
|
onChange={handleOnNewValue}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
{data.node?.template[name].refresh && (
|
||||||
|
<button
|
||||||
|
className="extra-side-bar-buttons ml-2 mt-1 w-1/6"
|
||||||
|
onClick={() => {
|
||||||
|
handleUpdateValues(name, data);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconComponent name="RefreshCcw" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : left === true && type === "bool" ? (
|
) : left === true && type === "bool" ? (
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,18 @@ export default function GenericNode({
|
||||||
// State for outline color
|
// State for outline color
|
||||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||||
|
|
||||||
|
// should be empty string if no duration
|
||||||
|
// else should be `Duration: ${duration}`
|
||||||
|
const getDurationString = (duration: number | null): string => {
|
||||||
|
if (duration === null) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return `Duration: ${duration}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const durationString = getDurationString(validationStatus?.data.duration);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNodeDescription(data.node!.description);
|
setNodeDescription(data.node!.description);
|
||||||
}, [data.node!.description]);
|
}, [data.node!.description]);
|
||||||
|
|
@ -400,19 +412,19 @@ export default function GenericNode({
|
||||||
Build{" "}
|
Build{" "}
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Play"
|
name="Play"
|
||||||
className=" h-5 fill-build-trigger stroke-build-trigger stroke-1"
|
className=" h-5 stroke-build-trigger stroke-2"
|
||||||
/>{" "}
|
/>{" "}
|
||||||
flow to validate status.
|
flow to validate status.
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<div className="max-h-96 overflow-auto">
|
<div className="max-h-96 overflow-auto">
|
||||||
{typeof validationStatus.params === "string"
|
{typeof validationStatus.params === "string"
|
||||||
? `Duration: ${validationStatus.data.duration}\n${validationStatus.params}`
|
? `${durationString}\n${validationStatus.params}`
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line, index) => (
|
.map((line, index) => (
|
||||||
<div key={index}>{line}</div>
|
<div key={index}>{line}</div>
|
||||||
))
|
))
|
||||||
: ""}
|
: durationString}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -855,9 +855,16 @@ export async function requestLogout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVerticesOrder(
|
export async function getVerticesOrder(
|
||||||
flowId: string
|
flowId: string,
|
||||||
|
nodeId?: string | null
|
||||||
): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
|
): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
|
||||||
return await api.get(`${BASE_URL_API}build/${flowId}/vertices`);
|
// nodeId is optional and is a query parameter
|
||||||
|
// if nodeId is not provided, the API will return all vertices
|
||||||
|
const config = {};
|
||||||
|
if (nodeId) {
|
||||||
|
config["params"] = { component_id: nodeId };
|
||||||
|
}
|
||||||
|
return await api.get(`${BASE_URL_API}build/${flowId}/vertices`, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postBuildVertex(
|
export async function postBuildVertex(
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
||||||
const setSuccessData = useAlertStore.getState().setSuccessData;
|
const setSuccessData = useAlertStore.getState().setSuccessData;
|
||||||
const setErrorData = useAlertStore.getState().setErrorData;
|
const setErrorData = useAlertStore.getState().setErrorData;
|
||||||
|
const setNoticeData = useAlertStore.getState().setNoticeData;
|
||||||
function handleBuildUpdate(data: any) {
|
function handleBuildUpdate(data: any) {
|
||||||
get().addDataToFlowPool(data.data[data.id], data.id);
|
get().addDataToFlowPool(data.data[data.id], data.id);
|
||||||
useFlowStore.getState().updateBuildStatus([data.id], BuildStatus.BUILDED);
|
useFlowStore.getState().updateBuildStatus([data.id], BuildStatus.BUILDED);
|
||||||
|
|
@ -392,6 +393,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
name: currentFlow!.name,
|
name: currentFlow!.name,
|
||||||
description: currentFlow!.description,
|
description: currentFlow!.description,
|
||||||
});
|
});
|
||||||
|
setNoticeData({ title: "Running components" });
|
||||||
return buildVertices({
|
return buildVertices({
|
||||||
flowId: currentFlow!.id,
|
flowId: currentFlow!.id,
|
||||||
nodeId,
|
nodeId,
|
||||||
|
|
|
||||||
|
|
@ -293,13 +293,13 @@
|
||||||
@apply opacity-100 animate-wiggle;
|
@apply opacity-100 animate-wiggle;
|
||||||
}
|
}
|
||||||
.green-status {
|
.green-status {
|
||||||
@apply generic-node-status text-status-green fill-status-green;
|
@apply generic-node-status text-status-green;
|
||||||
}
|
}
|
||||||
.red-status {
|
.red-status {
|
||||||
@apply generic-node-status text-status-red fill-status-red;
|
@apply generic-node-status text-status-red;
|
||||||
}
|
}
|
||||||
.yellow-status {
|
.yellow-status {
|
||||||
@apply generic-node-status text-status-yellow fill-status-yellow;
|
@apply generic-node-status text-status-yellow;
|
||||||
}
|
}
|
||||||
.status-build-animation {
|
.status-build-animation {
|
||||||
@apply opacity-0;
|
@apply opacity-0;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export async function buildVertices({
|
||||||
onBuildError,
|
onBuildError,
|
||||||
onBuildStart,
|
onBuildStart,
|
||||||
}: BuildVerticesParams) {
|
}: BuildVerticesParams) {
|
||||||
let orderResponse = await getVerticesOrder(flowId);
|
let orderResponse = await getVerticesOrder(flowId, nodeId);
|
||||||
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
|
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
|
||||||
let vertices: Array<Array<string>> = [];
|
let vertices: Array<Array<string>> = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,8 @@ import {
|
||||||
X,
|
X,
|
||||||
XCircle,
|
XCircle,
|
||||||
Zap,
|
Zap,
|
||||||
|
FlaskConical,
|
||||||
|
AlertCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { FaApple, FaGithub } from "react-icons/fa";
|
import { FaApple, FaGithub } from "react-icons/fa";
|
||||||
import { AWSIcon } from "../icons/AWS";
|
import { AWSIcon } from "../icons/AWS";
|
||||||
|
|
@ -400,4 +402,6 @@ export const nodeIconsLucide: iconsType = {
|
||||||
io: Cable,
|
io: Cable,
|
||||||
ScreenShare,
|
ScreenShare,
|
||||||
Code,
|
Code,
|
||||||
|
FlaskConical,
|
||||||
|
AlertCircle,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue