Skip to content

Commit

Permalink
fix: flow experimental components (langflow-ai#3093)
Browse files Browse the repository at this point in the history
* refactor(utils.py): simplify data processing logic in build_data_from_result_data function for better readability and maintainability

* feat: set default output type to "chat" in run_flow function

* refactor(FlowTool.py): refactor FlowToolComponent class to inherit from LCToolComponent

* Fixed Flow as Tool component

* Fixed refresh button not appearing at the end

* Added way of connecting SecretStrInput to messages

* Added real_time_refresh when field has button update too

* Refactored SubFlow component

* Fixed FlowTool to only output tool and removed async function def

* fix: two statements in the same line

* [autofix.ci] apply automated fixes

* Fixed lint issues

* fixed dataobj with wrong name

* changed tweaks dict type

* Fixed margin appearing in output too

* Fixed useless button that made styling worse on handlerendercomponent

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
(cherry picked from commit 5076f20)
  • Loading branch information
lucaseduoli authored and nicoloboschi committed Aug 1, 2024
1 parent a98a93d commit ec32795
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 79 deletions.
117 changes: 54 additions & 63 deletions src/backend/base/langflow/components/prototypes/SubFlow.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
from typing import Any, List, Optional

from loguru import logger

from langflow.base.flow_processing.utils import build_data_from_result_data
from langflow.custom import CustomComponent
from langflow.custom import Component
from langflow.graph.graph.base import Graph
from langflow.graph.schema import RunOutputs
from langflow.graph.vertex.base import Vertex
from langflow.helpers.flow import get_flow_inputs
from langflow.schema import Data
from langflow.schema.dotdict import dotdict
from langflow.template.field.base import Input
from loguru import logger
from langflow.io import DropdownInput, Output
from langflow.schema import Data, dotdict


class SubFlowComponent(CustomComponent):
class SubFlowComponent(Component):
display_name = "Sub Flow"
description = (
"Dynamically Generates a Component from a Flow. The output is a list of data with keys 'result' and 'message'."
)
description = "Generates a Component from a Flow, with all of its inputs, and "
name = "SubFlow"
beta: bool = True
field_order = ["flow_name"]

def get_flow_names(self) -> List[str]:
flow_datas = self.list_flows()
return [flow_data.data["name"] for flow_data in flow_datas]
flow_data = self.list_flows()
return [flow_data.data["name"] for flow_data in flow_data]

def get_flow(self, flow_name: str) -> Optional[Data]:
flow_datas = self.list_flows()
Expand All @@ -33,12 +29,11 @@ def get_flow(self, flow_name: str) -> Optional[Data]:
return None

def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
logger.debug(f"Updating build config with field value {field_value} and field name {field_name}")
if field_name == "flow_name":
build_config["flow_name"]["options"] = self.get_flow_names()
# Clean up the build config

for key in list(build_config.keys()):
if key not in self.field_order + ["code", "_type", "get_final_results_only"]:
if key not in [x.name for x in self.inputs] + ["code", "_type", "get_final_results_only"]:
del build_config[key]
if field_value is not None and field_name == "flow_name":
try:
Expand All @@ -55,62 +50,58 @@ def update_build_config(self, build_config: dotdict, field_value: Any, field_nam

return build_config

def add_inputs_to_build_config(self, inputs: List[Vertex], build_config: dotdict):
new_fields: list[Input] = []
for vertex in inputs:
field = Input(
display_name=vertex.display_name,
name=vertex.id,
info=vertex.description,
field_type="str",
value=None,
)
new_fields.append(field)
logger.debug(new_fields)
def add_inputs_to_build_config(self, inputs_vertex: List[Vertex], build_config: dotdict):
new_fields: list[dotdict] = []

for vertex in inputs_vertex:
new_vertex_inputs = []
field_template = vertex.data["node"]["template"]
for inp in field_template.keys():
if inp not in ["code", "_type"]:
field_template[inp]["display_name"] = (
vertex.display_name + " - " + field_template[inp]["display_name"]
)
field_template[inp]["name"] = vertex.id + "|" + inp
new_vertex_inputs.append(field_template[inp])
new_fields += new_vertex_inputs
for field in new_fields:
build_config[field.name] = field.to_dict()
build_config[field["name"]] = field
return build_config

def build_config(self):
return {
"input_value": {
"display_name": "Input Value",
"multiline": True,
},
"flow_name": {
"display_name": "Flow Name",
"info": "The name of the flow to run.",
"options": [],
"real_time_refresh": True,
"refresh_button": True,
},
"tweaks": {
"display_name": "Tweaks",
"info": "Tweaks to apply to the flow.",
},
"get_final_results_only": {
"display_name": "Get Final Results Only",
"info": "If False, the output will contain all outputs from the flow.",
"advanced": True,
},
}

async def build(self, flow_name: str, get_final_results_only: bool = True, **kwargs) -> List[Data]:
tweaks = {key: {"input_value": value} for key, value in kwargs.items()}
run_outputs: List[Optional[RunOutputs]] = await self.run_flow(
inputs = [
DropdownInput(
name="flow_name",
display_name="Flow Name",
info="The name of the flow to run.",
options=[],
refresh_button=True,
real_time_refresh=True,
),
]

outputs = [Output(name="flow_outputs", display_name="Flow Outputs", method="generate_results")]

async def generate_results(self) -> List[Data]:
tweaks: dict = {}
for field in self._attributes.keys():
if field != "flow_name":
[node, name] = field.split("|")
if node not in tweaks.keys():
tweaks[node] = {}
tweaks[node][name] = self._attributes[field]

run_outputs = await self.run_flow(
tweaks=tweaks,
flow_name=flow_name,
flow_name=self.flow_name,
output_type="all",
)
data: list[Data] = []
if not run_outputs:
return []
return data
run_output = run_outputs[0]

data = []
if run_output is not None:
for output in run_output.outputs:
if output:
data.extend(build_data_from_result_data(output, get_final_results_only))

self.status = data
logger.debug(data)
data.extend(build_data_from_result_data(output))
return data
5 changes: 4 additions & 1 deletion src/backend/base/langflow/initial_setup/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,10 @@ def load_starter_projects() -> list[tuple[Path, dict]]:
starter_projects = []
folder = Path(__file__).parent / "starter_projects"
for file in folder.glob("*.json"):
project = orjson.loads(file.read_text(encoding="utf-8"))
try:
project = orjson.loads(file.read_text(encoding="utf-8"))
except orjson.JSONDecodeError as e:
raise ValueError(f"Error loading starter project {file}: {e}")
starter_projects.append((file, project))
logger.info(f"Loaded starter project {file}")
return starter_projects
Expand Down
39 changes: 38 additions & 1 deletion src/backend/base/langflow/inputs/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,46 @@ class SecretStrInput(BaseInputMixin, DatabaseLoadMixin):

field_type: Optional[SerializableFieldTypes] = FieldTypes.PASSWORD
password: CoalesceBool = Field(default=True)
input_types: list[str] = []
input_types: list[str] = ["Message"]
load_from_db: CoalesceBool = True

@field_validator("value")
@classmethod
def validate_value(cls, v: Any, _info):
"""
Validates the given value and returns the processed value.
Args:
v (Any): The value to be validated.
_info: Additional information about the input.
Returns:
The processed value.
Raises:
ValueError: If the value is not of a valid type or if the input is missing a required key.
"""
value: str | AsyncIterator | Iterator | None = None
if isinstance(v, str):
value = v
elif isinstance(v, Message):
value = v.text
elif isinstance(v, Data):
if v.text_key in v.data:
value = v.data[v.text_key]
else:
keys = ", ".join(v.data.keys())
input_name = _info.data["name"]
raise ValueError(
f"The input to '{input_name}' must contain the key '{v.text_key}'."
f"You can set `text_key` to one of the following keys: {keys} or set the value using another Component."
)
elif isinstance(v, (AsyncIterator, Iterator)):
value = v
else:
raise ValueError(f"Invalid value type {type(v)}")
return value


class IntInput(BaseInputMixin, ListableInputMixin, RangeMixin, MetadataTraceMixin):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { title } from "process";
import { Handle, Position } from "reactflow";
import ShadTooltip from "../../../../components/shadTooltipComponent";
import { Button } from "../../../../components/ui/button";
import {
isValidConnection,
scapedJSONStringfy,
Expand Down Expand Up @@ -37,10 +35,7 @@ export default function HandleRenderComponent({
testIdComplement?: string;
}) {
return (
<Button
unstyled
className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted"
>
<div>
<ShadTooltip
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
delayDuration={1000}
Expand Down Expand Up @@ -100,6 +95,6 @@ export default function HandleRenderComponent({
left ? "-left-[4px] -ml-0.5" : "-right-[4px] -mr-0.5",
)}
/>
</Button>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,8 @@ export default function ParameterComponent({
testIdComplement={`${data?.type?.toLowerCase()}-shownode`}
/>
)}
<div className="mt-2 w-full">
{data.node?.template[name] !== undefined && (
{data.node?.template[name] !== undefined && (
<div className="mt-2 w-full">
<ParameterRenderComponent
handleOnNewValue={handleOnNewValue}
name={name}
Expand All @@ -362,8 +362,8 @@ export default function ParameterComponent({
nodeClass={data.node!}
disabled={disabled}
/>
)}
</div>
</div>
)}
{openOutputModal && (
<OutputModal
open={openOutputModal}
Expand Down
3 changes: 1 addition & 2 deletions src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ const useHandleOnNewValue = ({
if (value !== undefined) parameter[key] = value;
});

const shouldUpdate =
parameter.real_time_refresh && !parameter.refresh_button;
const shouldUpdate = parameter.real_time_refresh;

const setNodeClass = (newNodeClass: APIClassType) => {
options?.setNodeClass && options.setNodeClass(newNodeClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function RefreshParameterComponent({
setErrorData,
);
return (
<div className="flex w-full items-center gap-2">
<div className="flex w-full items-center justify-between gap-2">
{children}
{templateData.refresh_button && (
<div className="shrink-0 flex-col">
Expand Down

0 comments on commit ec32795

Please sign in to comment.