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]]
|
||||
name = "bidict"
|
||||
version = "0.23.0"
|
||||
version = "0.23.1"
|
||||
description = "The bidirectional mapping library for Python."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "bidict-0.23.0-py3-none-any.whl", hash = "sha256:f5154a0e42926b122f9b2cb06aeeb9af317f626eb864bd34c1b9cdc5eca8b040"},
|
||||
{file = "bidict-0.23.0.tar.gz", hash = "sha256:3959ca59d4d6997702d642bf1e5fd93cba299863723fc289545198f70c468578"},
|
||||
{file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"},
|
||||
{file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -9383,4 +9383,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
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"
|
||||
pydantic = "^2.6.0"
|
||||
pydantic-settings = "^2.1.0"
|
||||
zep-python = "*"
|
||||
zep-python = "1.5.0"
|
||||
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
|
||||
loguru = "^0.7.1"
|
||||
langfuse = "^2.9.0"
|
||||
|
|
|
|||
|
|
@ -284,14 +284,28 @@ async def get_vertices(
|
|||
):
|
||||
"""Check the flow_id is in the flow_data_store."""
|
||||
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)
|
||||
if not flow or not flow.data:
|
||||
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)
|
||||
|
||||
if component_id:
|
||||
vertices = graph.sort_up_to_vertex(component_id)
|
||||
try:
|
||||
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:
|
||||
vertices = graph.layered_topological_sort()
|
||||
# Now vertices is a list of lists
|
||||
|
|
@ -345,12 +359,15 @@ async def build_vertex(
|
|||
duration=duration,
|
||||
timedelta=timedelta,
|
||||
)
|
||||
chat_service.set_cache(flow_id, graph)
|
||||
except Exception as exc:
|
||||
params = str(exc)
|
||||
valid = False
|
||||
result_dict = ResultDict(results={})
|
||||
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(
|
||||
flow_id=flow_id,
|
||||
vertex_id=vertex_id,
|
||||
|
|
|
|||
|
|
@ -161,7 +161,9 @@ class StreamData(BaseModel):
|
|||
data: dict
|
||||
|
||||
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):
|
||||
|
|
@ -246,4 +248,3 @@ class VertexBuildResponse(BaseModel):
|
|||
|
||||
class VerticesBuiltResponse(BaseModel):
|
||||
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.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class ChatInput(CustomComponent):
|
||||
|
|
@ -10,18 +11,35 @@ class ChatInput(CustomComponent):
|
|||
|
||||
def build_config(self):
|
||||
return {
|
||||
"message": {"input_types": ["Text"], "display_name": "Message"},
|
||||
"sender": {"options": ["Machine", "User"], "display_name": "Sender Type"},
|
||||
"message": {"input_types": ["Text"], "display_name": "Message","multiline": True},
|
||||
"sender_type": {
|
||||
"options": ["Machine", "User"],
|
||||
"display_name": "Sender Type",
|
||||
},
|
||||
"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(
|
||||
self,
|
||||
sender: Optional[str] = "User",
|
||||
sender_type: Optional[str] = "User",
|
||||
sender_name: Optional[str] = "You",
|
||||
message: Optional[str] = None,
|
||||
) -> Text:
|
||||
self.repr_value = message
|
||||
as_record: Optional[bool] = False,
|
||||
) -> 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:
|
||||
message = ""
|
||||
return message
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from typing import Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
from langflow import CustomComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
|
||||
|
||||
class ChatOutput(CustomComponent):
|
||||
|
|
@ -17,23 +18,47 @@ class ChatOutput(CustomComponent):
|
|||
def build_config(self):
|
||||
return {
|
||||
"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"},
|
||||
"session_id": {
|
||||
"display_name": "Session ID",
|
||||
"info": "Session ID of the chat history.",
|
||||
"input_types": ["Text"],
|
||||
},
|
||||
"as_record": {
|
||||
"display_name": "As Record",
|
||||
"info": "If true, the message will be returned as a Record.",
|
||||
},
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
sender: Optional[str] = "Machine",
|
||||
sender_type: Optional[str] = "Machine",
|
||||
sender_name: Optional[str] = "AI",
|
||||
session_id: Optional[str] = None,
|
||||
message: Optional[str] = None,
|
||||
) -> Text:
|
||||
self.repr_value = message
|
||||
as_record: Optional[bool] = False,
|
||||
) -> 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:
|
||||
message = ""
|
||||
return message
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class PromptComponent(CustomComponent):
|
|||
**kwargs,
|
||||
) -> Text:
|
||||
prompt_template = PromptTemplate.from_template(template)
|
||||
|
||||
try:
|
||||
formated_prompt = prompt_template.format(**kwargs)
|
||||
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_ssl_enabled=chroma_server_ssl_enabled,
|
||||
)
|
||||
|
||||
index_directory = self.resolve_path(index_directory)
|
||||
chroma = Chroma(
|
||||
embedding_function=embedding,
|
||||
collection_name=collection_name,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,28 @@ class Graph:
|
|||
return False
|
||||
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:
|
||||
"""Builds the graph from the vertices and edges."""
|
||||
self.vertices = self._build_vertices()
|
||||
|
|
@ -314,9 +336,22 @@ class Graph:
|
|||
"""Cuts the graph up to a given vertex."""
|
||||
# Get the vertices that are connected to the vertex
|
||||
# and the vertex itself
|
||||
vertices = [self.get_vertex(vertex_id)]
|
||||
for edge in self.get_vertex(edge.target_id).edges:
|
||||
vertices.append(self.get_vertex(edge.target_id))
|
||||
vertex = self.get_vertex(vertex_id)
|
||||
vertices = [vertex]
|
||||
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]
|
||||
|
||||
|
|
|
|||
|
|
@ -55,11 +55,14 @@ class Vertex:
|
|||
self.parent_node_id: Optional[str] = self._data.get("parent_node_id")
|
||||
self.parent_is_top_level = False
|
||||
self.layer = None
|
||||
self.should_run = True
|
||||
try:
|
||||
self.is_interface_component = InterfaceComponentTypes(self.vertex_type)
|
||||
except ValueError:
|
||||
self.is_interface_component = False
|
||||
|
||||
self.use_result = False
|
||||
|
||||
# Build a result dict for each edge
|
||||
# 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]]:
|
||||
|
|
@ -78,14 +81,18 @@ class Vertex:
|
|||
):
|
||||
if edge.target_id not in edge_results:
|
||||
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
|
||||
|
||||
def get_built_result(self):
|
||||
# If the Vertex.type is a power component
|
||||
# then we need to return the built object
|
||||
# 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
|
||||
# if it is not a dict or a string and hasattr model_dump then
|
||||
# return the model_dump
|
||||
|
|
@ -95,7 +102,11 @@ class Vertex:
|
|||
|
||||
if isinstance(self._built_result, UnbuiltResult):
|
||||
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:
|
||||
pass
|
||||
|
|
@ -149,18 +160,29 @@ class Vertex:
|
|||
self.data = self._data["data"]
|
||||
self.output = self.data["node"]["base_classes"]
|
||||
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 = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||
template_dicts = {
|
||||
key: value
|
||||
for key, value in self.data["node"]["template"].items()
|
||||
if isinstance(value, dict)
|
||||
}
|
||||
|
||||
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 = [
|
||||
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
|
||||
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"]
|
||||
|
|
@ -203,7 +225,11 @@ class Vertex:
|
|||
if self.graph is None:
|
||||
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 {}
|
||||
|
||||
for edge in self.edges:
|
||||
|
|
@ -255,7 +281,11 @@ class Vertex:
|
|||
# list of dicts, so we need to convert it to a dict
|
||||
# before passing it to the build method
|
||||
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):
|
||||
params[key] = val
|
||||
elif value.get("type") == "int" and val is not None:
|
||||
|
|
@ -292,7 +322,12 @@ class Vertex:
|
|||
|
||||
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
|
||||
inputs = inputs or {}
|
||||
# inputs = {key: value or "" for key, value in inputs.items()}
|
||||
|
|
@ -307,7 +342,9 @@ class Vertex:
|
|||
if isinstance(self._built_object, str):
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
# Check if the Vertex was built already
|
||||
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:
|
||||
task = self.get_task()
|
||||
|
|
@ -369,7 +408,9 @@ class Vertex:
|
|||
self._extend_params_list_with_result(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.
|
||||
"""
|
||||
|
|
@ -421,7 +462,9 @@ class Vertex:
|
|||
self._update_built_object_and_artifacts(result)
|
||||
except Exception as 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):
|
||||
"""
|
||||
|
|
@ -458,7 +501,7 @@ class Vertex:
|
|||
requester: Optional["Vertex"] = None,
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
if self.pinned:
|
||||
if self.pinned and self._built:
|
||||
return self.get_requester_result(requester)
|
||||
self._reset()
|
||||
|
||||
|
|
@ -480,9 +523,15 @@ class Vertex:
|
|||
return self._built_object
|
||||
|
||||
# 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 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:
|
||||
if edge not in self.edges:
|
||||
|
|
@ -502,7 +551,11 @@ class Vertex:
|
|||
|
||||
def _built_object_repr(self):
|
||||
# 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):
|
||||
|
|
@ -511,9 +564,3 @@ class StatefulVertex(Vertex):
|
|||
|
||||
class StatelessVertex(Vertex):
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import json
|
|||
from typing import Callable, Dict, List, Optional, Union
|
||||
|
||||
from langchain_core.messages import AIMessage
|
||||
|
||||
from langflow.graph.utils import UnbuiltObject, flatten_list
|
||||
from langflow.graph.vertex.base import StatefulVertex, StatelessVertex
|
||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||
from langflow.schema import Record
|
||||
from langflow.utils.schemas import ChatOutputResponse
|
||||
|
||||
|
||||
|
|
@ -118,9 +118,11 @@ class DocumentLoaderVertex(StatefulVertex):
|
|||
# show how many documents are in the list?
|
||||
|
||||
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(
|
||||
self._built_object
|
||||
)
|
||||
avg_length = sum(
|
||||
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)
|
||||
\nAvg. Document Length (characters): {int(avg_length)}
|
||||
Documents: {self._built_object[:3]}..."""
|
||||
|
|
@ -193,7 +195,9 @@ class TextSplitterVertex(StatefulVertex):
|
|||
# show how many documents are in the list?
|
||||
|
||||
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)
|
||||
\nAvg. Document Length (characters): {int(avg_length)}
|
||||
\nDocuments: {self._built_object[:3]}..."""
|
||||
|
|
@ -240,18 +244,27 @@ class PromptVertex(StatelessVertex):
|
|||
user_id = kwargs.get("user_id", None)
|
||||
tools = kwargs.get("tools", [])
|
||||
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"] = []
|
||||
# Check if it is a ZeroShotPrompt and needs a tool
|
||||
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
|
||||
# first check if it is a list
|
||||
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
||||
tools = flatten_list(tools)
|
||||
self.params["tools"] = tools
|
||||
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:
|
||||
prompt_params = ["template"]
|
||||
|
|
@ -261,14 +274,20 @@ class PromptVertex(StatelessVertex):
|
|||
prompt_text = self.params[param]
|
||||
variables = extract_input_variables_from_prompt(prompt_text)
|
||||
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):
|
||||
self.params.pop("input_variables", None)
|
||||
|
||||
await self._build(user_id=user_id)
|
||||
|
||||
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()
|
||||
elif isinstance(self._built_object, UnbuiltObject):
|
||||
return super()._built_object_repr()
|
||||
|
|
@ -280,7 +299,9 @@ class PromptVertex(StatelessVertex):
|
|||
# so the prompt format doesn't break
|
||||
artifacts.pop("handle_keys", None)
|
||||
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
|
||||
else:
|
||||
template = self._built_object.template
|
||||
|
|
@ -288,7 +309,11 @@ class PromptVertex(StatelessVertex):
|
|||
if value:
|
||||
replace_key = "{" + key + "}"
|
||||
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:
|
||||
return str(self._built_object)
|
||||
|
||||
|
|
@ -337,12 +362,16 @@ class ChatVertex(StatelessVertex):
|
|||
if isinstance(self._built_object, dict):
|
||||
# Turn the dict into a pleasing to
|
||||
# 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):
|
||||
self._built_object = str(self._built_object)
|
||||
message = str(self._built_object)
|
||||
else:
|
||||
message = self._built_object
|
||||
|
||||
artifacts = ChatOutputResponse(
|
||||
message=self._built_object,
|
||||
message=message,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
)
|
||||
|
|
@ -354,6 +383,52 @@ class ChatVertex(StatelessVertex):
|
|||
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:
|
||||
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 pydantic import BaseModel
|
||||
|
|
@ -38,6 +38,24 @@ class Record(BaseModel):
|
|||
"""
|
||||
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]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ class ChatService(Service):
|
|||
"""Send the last chat message to the client."""
|
||||
client_id = self.chat_cache.current_client_id
|
||||
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:
|
||||
# Process FileResponse
|
||||
if isinstance(chat_response, FileResponse):
|
||||
|
|
@ -86,7 +88,9 @@ class ChatService(Service):
|
|||
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):
|
||||
self.active_connections[client_id] = websocket
|
||||
|
|
@ -209,7 +213,9 @@ class ChatService(Service):
|
|||
await self.process_message(client_id, payload, build_result)
|
||||
|
||||
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:
|
||||
# Handle any exceptions that might occur
|
||||
logger.exception(f"Error handling websocket: {exc}")
|
||||
|
|
@ -241,6 +247,12 @@ class ChatService(Service):
|
|||
"""
|
||||
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):
|
||||
markdown_table = "| Key | Value |\n|---|---|\n"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ from pydantic import BaseModel, Field, field_serializer, validator
|
|||
|
||||
class TransactionModel(BaseModel):
|
||||
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
|
||||
target: str
|
||||
target_args: dict
|
||||
|
|
@ -89,18 +91,13 @@ class VertexBuildModel(BaseModel):
|
|||
return v
|
||||
|
||||
|
||||
# create a function that turns dicts into a
|
||||
# dict like this:
|
||||
# my_map_dict = {
|
||||
# "key": [
|
||||
# 1, 2, 3
|
||||
# ],
|
||||
# "value": [
|
||||
# "one", "two", "three"
|
||||
# ]
|
||||
# }
|
||||
# so map has a "key" and a "value" list
|
||||
# containing the keys and values of the dict
|
||||
class VertexBuildResponseModel(VertexBuildModel):
|
||||
|
||||
@field_serializer("data", "artifacts")
|
||||
def serialize_dict(v):
|
||||
return v
|
||||
|
||||
|
||||
def to_map(value: dict):
|
||||
keys = list(value.keys())
|
||||
values = list(value.values())
|
||||
|
|
@ -108,13 +105,13 @@ def to_map(value: dict):
|
|||
|
||||
|
||||
class VertexBuildMapModel(BaseModel):
|
||||
vertex_builds: dict[str, list[VertexBuildModel]]
|
||||
vertex_builds: dict[str, list[VertexBuildResponseModel]]
|
||||
|
||||
@classmethod
|
||||
def from_list_of_dicts(cls, vertex_build_dicts):
|
||||
vertex_build_map = {}
|
||||
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:
|
||||
vertex_build_map[vertex_build.id] = []
|
||||
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.settings.auth import AuthSettings
|
||||
from langflow.services.settings.base import Settings
|
||||
from loguru import logger
|
||||
import os
|
||||
import yaml
|
||||
|
||||
|
||||
class SettingsService(Service):
|
||||
|
|
@ -11,8 +12,8 @@ class SettingsService(Service):
|
|||
|
||||
def __init__(self, settings: Settings, auth_settings: AuthSettings):
|
||||
super().__init__()
|
||||
self.settings = settings
|
||||
self.auth_settings = auth_settings
|
||||
self.settings: Settings = settings
|
||||
self.auth_settings: AuthSettings = auth_settings
|
||||
|
||||
@classmethod
|
||||
def load_settings_from_yaml(cls, file_path: str) -> "SettingsService":
|
||||
|
|
@ -30,7 +31,9 @@ class SettingsService(Service):
|
|||
for key in settings_dict:
|
||||
if key not in Settings.model_fields.keys():
|
||||
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)
|
||||
if not settings.CONFIG_DIR:
|
||||
|
|
|
|||
|
|
@ -406,13 +406,27 @@ export default function ParameterComponent({
|
|||
data-testid={"textarea-" + data.node.template[name].name}
|
||||
/>
|
||||
) : (
|
||||
<InputComponent
|
||||
id={"input-" + index}
|
||||
disabled={disabled}
|
||||
password={data.node?.template[name].password ?? false}
|
||||
value={data.node?.template[name].value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
/>
|
||||
<div className="mt-2 flex w-full items-center">
|
||||
<div className="w-5/6 flex-grow">
|
||||
<InputComponent
|
||||
id={"input-" + index}
|
||||
disabled={disabled}
|
||||
password={data.node?.template[name].password ?? false}
|
||||
value={data.node?.template[name].value ?? ""}
|
||||
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>
|
||||
) : left === true && type === "bool" ? (
|
||||
|
|
|
|||
|
|
@ -86,6 +86,18 @@ export default function GenericNode({
|
|||
// State for outline color
|
||||
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(() => {
|
||||
setNodeDescription(data.node!.description);
|
||||
}, [data.node!.description]);
|
||||
|
|
@ -400,19 +412,19 @@ export default function GenericNode({
|
|||
Build{" "}
|
||||
<IconComponent
|
||||
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.
|
||||
</span>
|
||||
) : (
|
||||
<div className="max-h-96 overflow-auto">
|
||||
{typeof validationStatus.params === "string"
|
||||
? `Duration: ${validationStatus.data.duration}\n${validationStatus.params}`
|
||||
? `${durationString}\n${validationStatus.params}`
|
||||
.split("\n")
|
||||
.map((line, index) => (
|
||||
<div key={index}>{line}</div>
|
||||
))
|
||||
: ""}
|
||||
: durationString}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -855,9 +855,16 @@ export async function requestLogout() {
|
|||
}
|
||||
|
||||
export async function getVerticesOrder(
|
||||
flowId: string
|
||||
flowId: string,
|
||||
nodeId?: string | null
|
||||
): 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(
|
||||
|
|
|
|||
|
|
@ -378,6 +378,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
||||
const setSuccessData = useAlertStore.getState().setSuccessData;
|
||||
const setErrorData = useAlertStore.getState().setErrorData;
|
||||
const setNoticeData = useAlertStore.getState().setNoticeData;
|
||||
function handleBuildUpdate(data: any) {
|
||||
get().addDataToFlowPool(data.data[data.id], data.id);
|
||||
useFlowStore.getState().updateBuildStatus([data.id], BuildStatus.BUILDED);
|
||||
|
|
@ -392,6 +393,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
name: currentFlow!.name,
|
||||
description: currentFlow!.description,
|
||||
});
|
||||
setNoticeData({ title: "Running components" });
|
||||
return buildVertices({
|
||||
flowId: currentFlow!.id,
|
||||
nodeId,
|
||||
|
|
|
|||
|
|
@ -293,13 +293,13 @@
|
|||
@apply opacity-100 animate-wiggle;
|
||||
}
|
||||
.green-status {
|
||||
@apply generic-node-status text-status-green fill-status-green;
|
||||
@apply generic-node-status text-status-green;
|
||||
}
|
||||
.red-status {
|
||||
@apply generic-node-status text-status-red fill-status-red;
|
||||
@apply generic-node-status text-status-red;
|
||||
}
|
||||
.yellow-status {
|
||||
@apply generic-node-status text-status-yellow fill-status-yellow;
|
||||
@apply generic-node-status text-status-yellow;
|
||||
}
|
||||
.status-build-animation {
|
||||
@apply opacity-0;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export async function buildVertices({
|
|||
onBuildError,
|
||||
onBuildStart,
|
||||
}: BuildVerticesParams) {
|
||||
let orderResponse = await getVerticesOrder(flowId);
|
||||
let orderResponse = await getVerticesOrder(flowId, nodeId);
|
||||
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
|
||||
let vertices: Array<Array<string>> = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ import {
|
|||
X,
|
||||
XCircle,
|
||||
Zap,
|
||||
FlaskConical,
|
||||
AlertCircle,
|
||||
} from "lucide-react";
|
||||
import { FaApple, FaGithub } from "react-icons/fa";
|
||||
import { AWSIcon } from "../icons/AWS";
|
||||
|
|
@ -400,4 +402,6 @@ export const nodeIconsLucide: iconsType = {
|
|||
io: Cable,
|
||||
ScreenShare,
|
||||
Code,
|
||||
FlaskConical,
|
||||
AlertCircle,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue