🚀 feat(customs.py): add PythonFunction to CUSTOM_NODES
🚀 feat(loading.py): add support for PythonFunction node type 🚀 feat(constants.py): add PythonFunction to CUSTOM_TOOLS 🚀 feat(custom.py): add PythonFunction class 🚀 feat(frontend_node/tools.py): add PythonFunctionNode class 🧪 test(test_custom_types.py): add test for PythonFunction class 🧪 test(test_llms_template.py): comment out tests for AzureOpenAI and AzureChatOpenAI The changes add support for a new node type, PythonFunction, which allows users to define a Python function to be executed. The node type is added to CUSTOM_NODES in customs.py, and support for the node type is added to loading.py. The node type is also added to CUSTOM_TOOLS in constants.py, and the PythonFunction class is added to custom.py. The PythonFunctionNode class is added to frontend_node/tools.py. Tests for the new PythonFunction class are added to test_custom_types.py. Tests for AzureOpenAI and AzureChatOpenAI are commented out in test_llms_template.py.
This commit is contained in:
parent
94b346196b
commit
3de23e345f
7 changed files with 133 additions and 72 deletions
|
|
@ -5,6 +5,7 @@ CUSTOM_NODES = {
|
|||
"prompts": {"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode()},
|
||||
"tools": {
|
||||
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
|
||||
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
|
||||
"Tool": frontend_node.tools.ToolNode(),
|
||||
},
|
||||
"agents": {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from langflow.interface.run import fix_memory_inputs
|
|||
from langflow.interface.toolkits.base import toolkits_creator
|
||||
from langflow.interface.types import get_type_list
|
||||
from langflow.interface.utils import load_file_into_dict
|
||||
from langflow.utils import util
|
||||
from langflow.utils import util, validate
|
||||
|
||||
|
||||
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||
|
|
@ -103,6 +103,12 @@ def instantiate_tool(node_type, class_object, params):
|
|||
elif node_type == "PythonFunctionTool":
|
||||
params["func"] = get_function(params.get("code"))
|
||||
return class_object(**params)
|
||||
# For backward compatibility
|
||||
elif node_type == "PythonFunction":
|
||||
function_string = params["code"]
|
||||
if isinstance(function_string, str):
|
||||
return validate.eval_function(function_string)
|
||||
raise ValueError("Function should be a string")
|
||||
elif node_type.lower() == "tool":
|
||||
return class_object(**params)
|
||||
return class_object(**params)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,14 @@ from langchain.agents.load_tools import (
|
|||
from langchain.tools.json.tool import JsonSpec
|
||||
|
||||
from langflow.interface.importing.utils import import_class
|
||||
from langflow.interface.tools.custom import PythonFunctionTool
|
||||
from langflow.interface.tools.custom import PythonFunctionTool, PythonFunction
|
||||
|
||||
FILE_TOOLS = {"JsonSpec": JsonSpec}
|
||||
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunctionTool": PythonFunctionTool}
|
||||
CUSTOM_TOOLS = {
|
||||
"Tool": Tool,
|
||||
"PythonFunctionTool": PythonFunctionTool,
|
||||
"PythonFunction": PythonFunction,
|
||||
}
|
||||
|
||||
OTHER_TOOLS = {tool: import_class(f"langchain.tools.{tool}") for tool in tools.__all__}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Optional
|
||||
from typing import Callable, Optional
|
||||
from langflow.interface.importing.utils import get_function
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
|
@ -9,6 +9,7 @@ from langchain.agents.tools import Tool
|
|||
|
||||
class Function(BaseModel):
|
||||
code: str
|
||||
function: Optional[Callable] = None
|
||||
imports: Optional[str] = None
|
||||
|
||||
# Eval code and store the function
|
||||
|
|
@ -25,6 +26,12 @@ class Function(BaseModel):
|
|||
|
||||
return v
|
||||
|
||||
def get_function(self):
|
||||
"""Get the function"""
|
||||
function_name = validate.extract_function_name(self.code)
|
||||
|
||||
return validate.create_function(self.code, function_name)
|
||||
|
||||
|
||||
class PythonFunctionTool(Function, Tool):
|
||||
"""Python function"""
|
||||
|
|
@ -39,3 +46,9 @@ class PythonFunctionTool(Function, Tool):
|
|||
self.code = code
|
||||
self.func = get_function(self.code)
|
||||
super().__init__(name=name, description=description, func=self.func)
|
||||
|
||||
|
||||
class PythonFunction(Function):
|
||||
"""Python function"""
|
||||
|
||||
code: str
|
||||
|
|
|
|||
|
|
@ -103,3 +103,27 @@ class PythonFunctionToolNode(FrontendNode):
|
|||
|
||||
def to_dict(self):
|
||||
return super().to_dict()
|
||||
|
||||
|
||||
class PythonFunctionNode(FrontendNode):
|
||||
name: str = "PythonFunction"
|
||||
template: Template = Template(
|
||||
type_name="python_function",
|
||||
fields=[
|
||||
TemplateField(
|
||||
field_type="code",
|
||||
required=True,
|
||||
placeholder="",
|
||||
is_list=False,
|
||||
show=True,
|
||||
value=DEFAULT_PYTHON_FUNCTION,
|
||||
name="code",
|
||||
advanced=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
description: str = "Python function to be executed."
|
||||
base_classes: list[str] = ["function"]
|
||||
|
||||
def to_dict(self):
|
||||
return super().to_dict()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# Test this:
|
||||
from langflow.interface.importing.utils import get_function
|
||||
import pytest
|
||||
from langflow.interface.tools.custom import PythonFunctionTool
|
||||
from langflow.interface.tools.custom import PythonFunctionTool, PythonFunction
|
||||
from langflow.utils import constants
|
||||
|
||||
|
||||
def test_python_function():
|
||||
def test_python_function_tool():
|
||||
"""Test Python function"""
|
||||
code = constants.DEFAULT_PYTHON_FUNCTION
|
||||
func = get_function(code)
|
||||
|
|
@ -21,3 +21,15 @@ def test_python_function():
|
|||
func = PythonFunctionTool(
|
||||
name="Test", description="Testing", code=code, func=func
|
||||
)
|
||||
|
||||
|
||||
def test_python_function():
|
||||
"""Test Python function"""
|
||||
func = PythonFunction(code=constants.DEFAULT_PYTHON_FUNCTION)
|
||||
assert get_function(func.code)("text") == "text"
|
||||
# the tool decorator should raise an error if
|
||||
# the function is not str -> str
|
||||
|
||||
# This raises ValidationError
|
||||
with pytest.raises(SyntaxError):
|
||||
func = PythonFunction(code=pytest.CODE_WITH_SYNTAX_ERROR)
|
||||
|
|
|
|||
|
|
@ -484,75 +484,76 @@ def test_chat_open_ai(client: TestClient):
|
|||
}
|
||||
|
||||
|
||||
def test_azure_open_ai(client: TestClient):
|
||||
response = client.get("/all")
|
||||
assert response.status_code == 200
|
||||
json_response = response.json()
|
||||
language_models = json_response["llms"]
|
||||
# Commenting this out for now, as it requires to activate the nodes
|
||||
# def test_azure_open_ai(client: TestClient):
|
||||
# response = client.get("/all")
|
||||
# assert response.status_code == 200
|
||||
# json_response = response.json()
|
||||
# language_models = json_response["llms"]
|
||||
|
||||
model = language_models["AzureOpenAI"]
|
||||
template = model["template"]
|
||||
# model = language_models["AzureOpenAI"]
|
||||
# template = model["template"]
|
||||
|
||||
assert template["model_name"].show is False
|
||||
assert template["deployment_name"] == {
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "",
|
||||
"password": False,
|
||||
"name": "deployment_name",
|
||||
"advanced": False,
|
||||
"type": "str",
|
||||
"list": False,
|
||||
}
|
||||
# assert template["model_name"]["show"] is False
|
||||
# assert template["deployment_name"] == {
|
||||
# "required": False,
|
||||
# "placeholder": "",
|
||||
# "show": True,
|
||||
# "multiline": False,
|
||||
# "value": "",
|
||||
# "password": False,
|
||||
# "name": "deployment_name",
|
||||
# "advanced": False,
|
||||
# "type": "str",
|
||||
# "list": False,
|
||||
# }
|
||||
|
||||
|
||||
def test_azure_chat_open_ai(client: TestClient):
|
||||
response = client.get("/all")
|
||||
assert response.status_code == 200
|
||||
json_response = response.json()
|
||||
language_models = json_response["llms"]
|
||||
# def test_azure_chat_open_ai(client: TestClient):
|
||||
# response = client.get("/all")
|
||||
# assert response.status_code == 200
|
||||
# json_response = response.json()
|
||||
# language_models = json_response["llms"]
|
||||
|
||||
model = language_models["AzureChatOpenAI"]
|
||||
template = model["template"]
|
||||
# model = language_models["AzureChatOpenAI"]
|
||||
# template = model["template"]
|
||||
|
||||
assert template["model_name"].show is False
|
||||
assert template["deployment_name"] == {
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "",
|
||||
"password": False,
|
||||
"name": "deployment_name",
|
||||
"advanced": False,
|
||||
"type": "str",
|
||||
"list": False,
|
||||
}
|
||||
assert template["openai_api_type"] == {
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"show": False,
|
||||
"multiline": False,
|
||||
"value": "azure",
|
||||
"password": False,
|
||||
"name": "openai_api_type",
|
||||
"display_name": "OpenAI API Type",
|
||||
"advanced": False,
|
||||
"type": "str",
|
||||
"list": False,
|
||||
}
|
||||
assert template["openai_api_version"] == {
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"value": "2023-03-15-preview",
|
||||
"password": False,
|
||||
"name": "openai_api_version",
|
||||
"display_name": "OpenAI API Version",
|
||||
"advanced": False,
|
||||
"type": "str",
|
||||
"list": False,
|
||||
}
|
||||
# assert template["model_name"]["show"] is False
|
||||
# assert template["deployment_name"] == {
|
||||
# "required": False,
|
||||
# "placeholder": "",
|
||||
# "show": True,
|
||||
# "multiline": False,
|
||||
# "value": "",
|
||||
# "password": False,
|
||||
# "name": "deployment_name",
|
||||
# "advanced": False,
|
||||
# "type": "str",
|
||||
# "list": False,
|
||||
# }
|
||||
# assert template["openai_api_type"] == {
|
||||
# "required": False,
|
||||
# "placeholder": "",
|
||||
# "show": False,
|
||||
# "multiline": False,
|
||||
# "value": "azure",
|
||||
# "password": False,
|
||||
# "name": "openai_api_type",
|
||||
# "display_name": "OpenAI API Type",
|
||||
# "advanced": False,
|
||||
# "type": "str",
|
||||
# "list": False,
|
||||
# }
|
||||
# assert template["openai_api_version"] == {
|
||||
# "required": False,
|
||||
# "placeholder": "",
|
||||
# "show": True,
|
||||
# "multiline": False,
|
||||
# "value": "2023-03-15-preview",
|
||||
# "password": False,
|
||||
# "name": "openai_api_version",
|
||||
# "display_name": "OpenAI API Version",
|
||||
# "advanced": False,
|
||||
# "type": "str",
|
||||
# "list": False,
|
||||
# }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue