feat: Add CustomComponent tool to Langflow API

- Added support for the CustomComponent tool in the Langflow API.
- The tool has been added to the config.yaml file.
- The CustomComponentNode class has been implemented in the frontend nodes.
- The code changes include modifications in various files for the implementation of the CustomComponent tool.
- The code changes include the addition of a new field "code" in the TemplateField class.
- The build_langchain_template_custom_component function has been implemented to build the template for the CustomComponent tool.
- New custom fields "my_id", "year", and "other_field" have been added to the template for the CustomComponent tool.
This commit is contained in:
gustavoschaedler 2023-06-29 01:57:53 +01:00
commit 82b76840e3
9 changed files with 144 additions and 26 deletions

View file

@ -71,10 +71,14 @@ class ClassCodeExtractor:
self.function_entrypoint_name), None
)
funtion_args = build_function.get("arguments", None)
return_type = build_function.get("return_type", None)
if build_function:
function_args = build_function.get("arguments", None)
return_type = build_function.get("return_type", None)
else:
function_args = None
return_type = None
return funtion_args, return_type
return function_args, return_type
def is_valid_class_template(code: dict):

View file

@ -95,6 +95,7 @@ tools:
- Calculator
- Serper Search
- Tool
- CustomComponent
- PythonFunctionTool
- PythonFunction
- JsonSpec

View file

@ -8,6 +8,7 @@ CUSTOM_NODES = {
"tools": {
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
"CustomComponent": frontend_node.tools.CustomComponentNode(),
"Tool": frontend_node.tools.ToolNode(),
},
"agents": {

View file

@ -131,7 +131,7 @@ def instantiate_tool(node_type, class_object, params):
if node_type == "JsonSpec":
params["dict_"] = load_file_into_dict(params.pop("path"))
return class_object(**params)
elif node_type == "PythonFunctionTool":
elif node_type in ["PythonFunctionTool", "CustomComponent"]:
params["func"] = get_function(params.get("code"))
return class_object(**params)
# For backward compatibility
@ -243,7 +243,8 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
if tool["type"] != "chatOutputNode"
and "Tool" in tool["data"]["node"]["base_classes"]
]
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
node["data"] = build_prompt_template(
prompt=node["data"], tools=tools)
break
return nodes
@ -260,7 +261,8 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
tool_names = [tool.name for tool in allowed_tools]
# Agent class requires an output_parser but Agent classes
# have a default output_parser.
agent = agent_class(allowed_tools=tool_names, llm_chain=llm_chain) # type: ignore
agent = agent_class(allowed_tools=tool_names,
llm_chain=llm_chain) # type: ignore
return AgentExecutor.from_agent_and_tools(
agent=agent,
tools=allowed_tools,

View file

@ -9,16 +9,22 @@ 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, PythonFunction
from langflow.interface.tools.custom import (
PythonFunctionTool,
PythonFunction,
CustomComponent
)
FILE_TOOLS = {"JsonSpec": JsonSpec}
CUSTOM_TOOLS = {
"Tool": Tool,
"CustomComponent": CustomComponent,
"PythonFunctionTool": PythonFunctionTool,
"PythonFunction": PythonFunction,
}
OTHER_TOOLS = {tool: import_class(f"langchain.tools.{tool}") for tool in tools.__all__}
OTHER_TOOLS = {tool: import_class(f"langchain.tools.{tool}")
for tool in tools.__all__}
ALL_TOOLS_NAMES = {
**_BASE_TOOLS,

View file

@ -52,3 +52,9 @@ class PythonFunction(Function):
"""Python function"""
code: str
class CustomComponent(Function):
"""Python function"""
code: str

View file

@ -12,6 +12,9 @@ from langflow.interface.utilities.base import utility_creator
from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.tools import CustomComponentNode
def get_type_list():
"""Get a list of all langchain types"""
@ -54,6 +57,7 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
return all_types
# TODO: Move to correct place
def find_class_type(class_name, classes_dict):
return next(
(
@ -65,10 +69,23 @@ def find_class_type(class_name, classes_dict):
)
def build_langchain_template_custom_component(raw_code, function_args, function_return_type):
type_list = get_type_list()
type_and_class = find_class_type("Tool", type_list)
# TODO: Move to correct place
def add_new_custom_field(template, field_name: str, field_type: str):
new_field = TemplateField(
name=field_name,
field_type=field_type,
show=True,
advanced=False
)
template.get('template')[field_name] = new_field.to_dict()
template.get('custom_fields').append(field_name)
return template
# TODO: Move to correct place
def add_code_field(template, raw_code):
# Field with the Python code to allow update
code_field = {
"code": {
@ -84,13 +101,47 @@ def build_langchain_template_custom_component(raw_code, function_args, function_
"list": False
}
}
template.get('template')['code'] = code_field.get('code')
return template
def build_langchain_template_custom_component(raw_code, function_args, function_return_type):
# type_list = get_type_list()
# type_and_class = find_class_type("Tool", type_list)
# node = get_custom_nodes(node_type: str)
# TODO: Build base template
template = llm_creator.to_dict()['llms']['ChatOpenAI']
template = CustomComponentNode().to_dict().get('CustomComponent')
# TODO: Add extra fields
template = add_new_custom_field(
template,
"my_id",
"str"
)
# TODO: Build template result
template = chain_creator.to_dict()['chains']['ConversationChain']
template = add_new_custom_field(
template,
"year",
"int"
)
template.get('template')['code'] = code_field.get('code')
template = add_new_custom_field(
template,
"other_field",
"bool"
)
template = add_code_field(
template,
raw_code
)
# criar um vertex
# olhar loading.py
return template
# return globals()['tool_creator'].to_dict()[type_and_class['type']][type_and_class['class']]

View file

@ -1,7 +1,10 @@
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.template.base import Template
from langflow.utils.constants import DEFAULT_PYTHON_FUNCTION
from langflow.utils.constants import (
DEFAULT_PYTHON_FUNCTION,
DEFAULT_CUSTOM_COMPONENT_CODE
)
class ToolNode(FrontendNode):
@ -137,3 +140,27 @@ class PythonFunctionNode(FrontendNode):
def to_dict(self):
return super().to_dict()
class CustomComponentNode(FrontendNode):
name: str = "CustomComponent"
template: Template = Template(
type_name="CustomComponent",
fields=[
TemplateField(
field_type="code",
required=True,
placeholder="",
is_list=False,
show=True,
value=DEFAULT_CUSTOM_COMPONENT_CODE,
name="code",
advanced=False,
)
],
)
description: str = "Python Class to be executed."
base_classes: list[str] = []
def to_dict(self):
return super().to_dict()

View file

@ -17,18 +17,30 @@ CHAT_OPENAI_MODELS = [
]
ANTHROPIC_MODELS = [
"claude-v1", # largest model, ideal for a wide range of more complex tasks.
"claude-v1-100k", # An enhanced version of claude-v1 with a 100,000 token (roughly 75,000 word) context window.
"claude-instant-v1", # A smaller model with far lower latency, sampling at roughly 40 words/sec!
"claude-instant-v1-100k", # Like claude-instant-v1 with a 100,000 token context window but retains its performance.
# largest model, ideal for a wide range of more complex tasks.
"claude-v1",
# An enhanced version of claude-v1 with a 100,000 token (roughly 75,000 word) context window.
"claude-v1-100k",
# A smaller model with far lower latency, sampling at roughly 40 words/sec!
"claude-instant-v1",
# Like claude-instant-v1 with a 100,000 token context window but retains its performance.
"claude-instant-v1-100k",
# Specific sub-versions of the above models:
"claude-v1.3", # Vs claude-v1.2: better instruction-following, code, and non-English dialogue and writing.
"claude-v1.3-100k", # An enhanced version of claude-v1.3 with a 100,000 token (roughly 75,000 word) context window.
"claude-v1.2", # Vs claude-v1.1: small adv in general helpfulness, instruction following, coding, and other tasks.
"claude-v1.0", # An earlier version of claude-v1.
"claude-instant-v1.1", # Latest version of claude-instant-v1. Better than claude-instant-v1.0 at most tasks.
"claude-instant-v1.1-100k", # Version of claude-instant-v1.1 with a 100K token context window.
"claude-instant-v1.0", # An earlier version of claude-instant-v1.
# Vs claude-v1.2: better instruction-following, code, and non-English dialogue and writing.
"claude-v1.3",
# An enhanced version of claude-v1.3 with a 100,000 token (roughly 75,000 word) context window.
"claude-v1.3-100k",
# Vs claude-v1.1: small adv in general helpfulness, instruction following, coding, and other tasks.
"claude-v1.2",
# An earlier version of claude-v1.
"claude-v1.0",
# Latest version of claude-instant-v1. Better than claude-instant-v1.0 at most tasks.
"claude-instant-v1.1",
# Version of claude-instant-v1.1 with a 100K token context window.
"claude-instant-v1.1-100k",
# An earlier version of claude-instant-v1.
"claude-instant-v1.0",
]
DEFAULT_PYTHON_FUNCTION = """
@ -36,4 +48,12 @@ def python_function(text: str) -> str:
\"\"\"This is a default python function that returns the input text\"\"\"
return text
"""
DEFAULT_CUSTOM_COMPONENT_CODE = """
def custom_component(text: str) -> str:
\"\"\"This is a default custom component function that returns the input text\"\"\"
\"\"\"TODO: Add a Class template\"\"\"
return text
"""
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]