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:
Lucas Oliveira 2024-09-10 16:17:55 -03:00 committed by GitHub
commit 0b1198efc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 94 additions and 17 deletions

View file

@ -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

View file

@ -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",

View file

@ -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"

View file

@ -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,

View file

@ -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",

View file

@ -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():

View file

@ -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!"