fix: pythonfunction component execution (#3747)
* Added CodeInput to types * Fixed PythonFunction to execute and return data or message * [autofix.ci] apply automated fixes * Added callable to outputs of pythonfunction * [autofix.ci] apply automated fixes * Updated function and tests * Fixed lint issues * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
bf640be53f
commit
0b1198efc4
7 changed files with 94 additions and 17 deletions
|
|
@ -1,27 +1,73 @@
|
|||
from typing import Callable
|
||||
from typing import Callable, List
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.custom import Component
|
||||
from langflow.custom.utils import get_function
|
||||
from langflow.field_typing import Code
|
||||
from langflow.io import CodeInput, Output
|
||||
from langflow.schema import Data, dotdict
|
||||
from langflow.schema.message import Message
|
||||
|
||||
|
||||
class PythonFunctionComponent(CustomComponent):
|
||||
class PythonFunctionComponent(Component):
|
||||
display_name = "Python Function"
|
||||
description = "Define a Python function."
|
||||
description = "Define and execute a Python function that returns a Data object or a Message."
|
||||
icon = "Python"
|
||||
name = "PythonFunction"
|
||||
beta = True
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
"function_code": {
|
||||
"display_name": "Code",
|
||||
"info": "The code for the function.",
|
||||
"show": True,
|
||||
},
|
||||
}
|
||||
inputs = [
|
||||
CodeInput(
|
||||
name="function_code",
|
||||
display_name="Function Code",
|
||||
info="The code for the function.",
|
||||
),
|
||||
]
|
||||
|
||||
def build(self, function_code: Code) -> Callable:
|
||||
outputs = [
|
||||
Output(
|
||||
name="function_output",
|
||||
display_name="Function Callable",
|
||||
method="get_function_callable",
|
||||
),
|
||||
Output(
|
||||
name="function_output_data",
|
||||
display_name="Function Output (Data)",
|
||||
method="execute_function_data",
|
||||
),
|
||||
Output(
|
||||
name="function_output_str",
|
||||
display_name="Function Output (Message)",
|
||||
method="execute_function_message",
|
||||
),
|
||||
]
|
||||
|
||||
def get_function_callable(self) -> Callable:
|
||||
function_code = self.function_code
|
||||
self.status = function_code
|
||||
func = get_function(function_code)
|
||||
return func
|
||||
|
||||
def execute_function(self) -> List[dotdict | str] | dotdict | str:
|
||||
function_code = self.function_code
|
||||
|
||||
if not function_code:
|
||||
return "No function code provided."
|
||||
|
||||
try:
|
||||
func = get_function(function_code)
|
||||
return func()
|
||||
except Exception as e:
|
||||
return f"Error executing function: {str(e)}"
|
||||
|
||||
def execute_function_data(self) -> List[Data]:
|
||||
results = self.execute_function()
|
||||
results = results if isinstance(results, list) else [results]
|
||||
data = [(Data(text=x) if isinstance(x, str) else Data(**x)) for x in results]
|
||||
return data
|
||||
|
||||
def execute_function_message(self) -> Message:
|
||||
results = self.execute_function()
|
||||
results = results if isinstance(results, list) else [results]
|
||||
results_list = [str(x) for x in results]
|
||||
results_str = "\n".join(results_list)
|
||||
data = Message(text=results_str)
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from .inputs import (
|
|||
MultiselectInput,
|
||||
NestedDictInput,
|
||||
PromptInput,
|
||||
CodeInput,
|
||||
SecretStrInput,
|
||||
StrInput,
|
||||
TableInput,
|
||||
|
|
@ -36,6 +37,7 @@ __all__ = [
|
|||
"MultilineSecretInput",
|
||||
"NestedDictInput",
|
||||
"PromptInput",
|
||||
"CodeInput",
|
||||
"SecretStrInput",
|
||||
"StrInput",
|
||||
"MessageTextInput",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
from enum import Enum
|
||||
from typing import Annotated, Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, PlainSerializer, field_validator, model_serializer
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
PlainSerializer,
|
||||
field_validator,
|
||||
model_serializer,
|
||||
)
|
||||
|
||||
from langflow.field_typing.range_spec import RangeSpec
|
||||
from langflow.inputs.validators import CoalesceBool
|
||||
|
|
@ -18,6 +25,7 @@ class FieldTypes(str, Enum):
|
|||
NESTED_DICT = "NestedDict"
|
||||
FILE = "file"
|
||||
PROMPT = "prompt"
|
||||
CODE = "code"
|
||||
OTHER = "other"
|
||||
TABLE = "table"
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,10 @@ class PromptInput(BaseInputMixin, ListableInputMixin, InputTraceMixin):
|
|||
field_type: SerializableFieldTypes = FieldTypes.PROMPT
|
||||
|
||||
|
||||
class CodeInput(BaseInputMixin, ListableInputMixin, InputTraceMixin):
|
||||
field_type: SerializableFieldTypes = FieldTypes.CODE
|
||||
|
||||
|
||||
# Applying mixins to a specific input type
|
||||
class StrInput(BaseInputMixin, ListableInputMixin, DatabaseLoadMixin, MetadataTraceMixin):
|
||||
field_type: SerializableFieldTypes = FieldTypes.TEXT
|
||||
|
|
@ -493,6 +497,7 @@ InputTypes = Union[
|
|||
MultilineSecretInput,
|
||||
NestedDictInput,
|
||||
PromptInput,
|
||||
CodeInput,
|
||||
SecretStrInput,
|
||||
StrInput,
|
||||
MessageTextInput,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from langflow.inputs import (
|
|||
MultilineSecretInput,
|
||||
NestedDictInput,
|
||||
PromptInput,
|
||||
CodeInput,
|
||||
SecretStrInput,
|
||||
StrInput,
|
||||
TableInput,
|
||||
|
|
@ -36,6 +37,7 @@ __all__ = [
|
|||
"MultilineSecretInput",
|
||||
"NestedDictInput",
|
||||
"PromptInput",
|
||||
"CodeInput",
|
||||
"SecretStrInput",
|
||||
"StrInput",
|
||||
"MessageTextInput",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from pydantic import ValidationError
|
|||
|
||||
from langflow.inputs.inputs import (
|
||||
BoolInput,
|
||||
CodeInput,
|
||||
DataInput,
|
||||
DictInput,
|
||||
DropdownInput,
|
||||
|
|
@ -99,6 +100,11 @@ def test_prompt_input_valid():
|
|||
assert prompt_input.value == "Enter your name"
|
||||
|
||||
|
||||
def test_code_input_valid():
|
||||
code_input = CodeInput(name="valid_code", value="def hello():\n print('Hello, World!')")
|
||||
assert code_input.value == "def hello():\n print('Hello, World!')"
|
||||
|
||||
|
||||
def test_multiline_input_valid():
|
||||
multiline_input = MultilineInput(name="valid_multiline", value="This is a\nmultiline input")
|
||||
assert multiline_input.value == "This is a\nmultiline input"
|
||||
|
|
@ -214,7 +220,10 @@ def test_instantiate_input_comprehensive():
|
|||
"FloatInput": {"name": "float_input", "value": 10.5},
|
||||
"BoolInput": {"name": "bool_input", "value": True},
|
||||
"DictInput": {"name": "dict_input", "value": {"key": "value"}},
|
||||
"MultiselectInput": {"name": "multiselect_input", "value": ["option1", "option2"]},
|
||||
"MultiselectInput": {
|
||||
"name": "multiselect_input",
|
||||
"value": ["option1", "option2"],
|
||||
},
|
||||
}
|
||||
|
||||
for input_type, data in valid_data.items():
|
||||
|
|
|
|||
|
|
@ -14,8 +14,13 @@ def test_python_function_component():
|
|||
# Act
|
||||
# function must be a string representation
|
||||
function = "def function():\n return 'Hello, World!'"
|
||||
python_function_component.function_code = function
|
||||
# result is the callable function
|
||||
result = python_function_component.build(function)
|
||||
result = python_function_component.get_function_callable()
|
||||
result_message = python_function_component.execute_function_message()
|
||||
result_data = python_function_component.execute_function_data()
|
||||
|
||||
# Assert
|
||||
assert result() == "Hello, World!"
|
||||
assert result_message.text == "Hello, World!"
|
||||
assert result_data[0].text == "Hello, World!"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue