Skip to content

Commit

Permalink
Merge branch 'main' into toolsAndOthersBugs
Browse files Browse the repository at this point in the history
  • Loading branch information
italojohnny authored Sep 10, 2024
2 parents 5c8400c + 0088b46 commit 4f9e241
Show file tree
Hide file tree
Showing 16 changed files with 297 additions and 71 deletions.
10 changes: 10 additions & 0 deletions src/backend/base/langflow/components/data/URL.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

from langchain_community.document_loaders.web_base import WebBaseLoader

from langflow.helpers.data import data_to_text
from langflow.custom import Component
from langflow.io import MessageTextInput, Output
from langflow.schema import Data
from langflow.schema.message import Message


class URLComponent(Component):
Expand All @@ -24,6 +26,7 @@ class URLComponent(Component):

outputs = [
Output(display_name="Data", name="data", method="fetch_content"),
Output(display_name="Text", name="text", method="fetch_content_text"),
]

def ensure_url(self, string: str) -> str:
Expand Down Expand Up @@ -66,3 +69,10 @@ def fetch_content(self) -> list[Data]:
data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]
self.status = data
return data

def fetch_content_text(self) -> Message:
data = self.fetch_content()

result_string = data_to_text("{text}", data)
self.status = result_string
return Message(text=result_string)
40 changes: 24 additions & 16 deletions src/backend/base/langflow/components/helpers/IDGenerator.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
import uuid
from typing import Any, Optional

from langflow.custom import CustomComponent
from langflow.schema.dotdict import dotdict
from langflow.custom import Component
from langflow.io import MessageTextInput, Output
from langflow.schema import dotdict
from langflow.schema.message import Message


class IDGeneratorComponent(CustomComponent):
class IDGeneratorComponent(Component):
display_name = "ID Generator"
description = "Generates a unique ID."
icon = "fingerprint"
name = "IDGenerator"

def update_build_config( # type: ignore
self, build_config: dotdict, field_value: Any, field_name: Optional[str] = None
):
inputs = [
MessageTextInput(
name="unique_id",
display_name="Value",
info="The generated unique ID.",
refresh_button=True,
),
]

outputs = [
Output(display_name="ID", name="id", method="generate_id"),
]

def update_build_config(self, build_config: dotdict, field_value: Any, field_name: Optional[str] = None):
if field_name == "unique_id":
build_config[field_name]["value"] = str(uuid.uuid4())
return build_config

def build_config(self):
return {
"unique_id": {
"display_name": "Value",
"refresh_button": True,
}
}

def build(self, unique_id: str) -> str:
return unique_id
def generate_id(self) -> Message:
unique_id = self.unique_id or str(uuid.uuid4())
self.status = f"Generated ID: {unique_id}"
return Message(text=unique_id)
84 changes: 42 additions & 42 deletions src/backend/base/langflow/components/tools/PythonREPLTool.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,47 @@
import importlib
from typing import List, Union
from pydantic import BaseModel, Field
from typing import cast

from langchain_experimental.utilities import PythonREPL

from langflow.base.langchain_utilities.model import LCToolComponent
from langflow.inputs import StrInput
from langflow.schema import Data
from langflow.field_typing import Tool
from langchain.tools import StructuredTool
from langchain_experimental.utilities import PythonREPL
from langflow.io import MessageTextInput, MultiselectInput
from langflow.schema.data import Data
from langflow.template.field.base import Output


class PythonREPLToolComponent(LCToolComponent):
display_name = "Python REPL Tool"
description = "A tool for running Python code in a REPL environment."
name = "PythonREPLTool"

inputs = [
StrInput(
name="name",
display_name="Tool Name",
info="The name of the tool.",
value="python_repl",
),
StrInput(
MessageTextInput(name="input_value", display_name="Input", value=""),
MessageTextInput(name="name", display_name="Name", value="python_repl"),
MessageTextInput(
name="description",
display_name="Tool Description",
info="A description of the tool.",
display_name="Description",
value="A Python shell. Use this to execute python commands. Input should be a valid python command. If you want to see the output of a value, you should print it out with `print(...)`.",
),
StrInput(
MultiselectInput(
name="global_imports",
display_name="Global Imports",
info="A comma-separated list of modules to import globally, e.g. 'math,numpy'.",
value="math",
),
StrInput(
name="code",
display_name="Python Code",
info="The Python code to execute.",
value="print('Hello, World!')",
info="A list of modules to import globally, e.g. ['math', 'numpy'].",
value=["math"],
combobox=True,
),
]

outputs = [
Output(name="api_run_model", display_name="Data", method="run_model"),
# Keep this for backwards compatibility
Output(name="tool", display_name="Tool", method="build_tool"),
]

def get_globals(self, globals: list[str]) -> dict:
"""
Retrieves the global variables from the specified modules.
class PythonREPLSchema(BaseModel):
code: str = Field(..., description="The Python code to execute.")
Expand All @@ -61,26 +63,24 @@ def get_globals(self, global_imports: Union[str, List[str]]) -> dict:
return global_dict
def build_tool(self) -> Tool:
"""
Builds a Python REPL tool.

Check failure on line 67 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:67:16: SyntaxError: Simple statements must be separated by newlines or semicolons

Check failure on line 67 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:67:18: SyntaxError: Simple statements must be separated by newlines or semicolons

Check failure on line 67 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:67:25: SyntaxError: Simple statements must be separated by newlines or semicolons

Check failure on line 67 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:67:30: SyntaxError: Simple statements must be separated by newlines or semicolons

Check failure on line 68 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:67:35: SyntaxError: Expected an identifier
Returns:
Tool: The built Python REPL tool.

Check failure on line 70 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:69:17: SyntaxError: Expected an expression

Check failure on line 70 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:70:1: SyntaxError: Unexpected indentation

Check failure on line 70 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:70:23: SyntaxError: Simple statements must be separated by newlines or semicolons

Check failure on line 70 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:70:29: SyntaxError: Simple statements must be separated by newlines or semicolons

Check failure on line 70 in src/backend/base/langflow/components/tools/PythonREPLTool.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff

src/backend/base/langflow/components/tools/PythonREPLTool.py:70:36: SyntaxError: Simple statements must be separated by newlines or semicolons
"""
_globals = self.get_globals(self.global_imports)
python_repl = PythonREPL(_globals=_globals)

def run_python_code(code: str) -> str:
try:
return python_repl.run(code)
except Exception as e:
return f"Error: {str(e)}"

tool = StructuredTool.from_function(
name=self.name,
description=self.description,
func=run_python_code,
args_schema=self.PythonREPLSchema,
return cast(
Tool,
Tool(
name=self.name,
description=self.description,
func=python_repl.run,
),
)

self.status = f"Python REPL Tool created with global imports: {self.global_imports}"
return tool

def run_model(self) -> List[Data]:
def run_model(self) -> Data:
tool = self.build_tool()
result = tool.run(self.code)
return [Data(data={"result": result})]
result = tool.invoke(self.input_value)
return Data(text=result)
25 changes: 20 additions & 5 deletions src/backend/base/langflow/components/tools/YfinanceTool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,34 @@

from langchain_community.tools.yahoo_finance_news import YahooFinanceNewsTool

from langflow.custom import Component
from langflow.field_typing import Tool
from langflow.io import Output
from langflow.base.langchain_utilities.model import LCToolComponent
from langflow.field_typing import Data, Tool
from langflow.inputs.inputs import MessageTextInput
from langflow.template.field.base import Output


class YfinanceToolComponent(Component):
class YfinanceToolComponent(LCToolComponent):
display_name = "Yahoo Finance News Tool"
description = "Tool for interacting with Yahoo Finance News."
name = "YFinanceTool"

inputs = [
MessageTextInput(
name="input_value",
display_name="Query",
info="Input should be a company ticker. For example, AAPL for Apple, MSFT for Microsoft.",
)
]

outputs = [
Output(display_name="Tool", name="tool", method="build_tool"),
Output(name="api_run_model", display_name="Data", method="run_model"),
# Keep this for backwards compatibility
Output(name="tool", display_name="Tool", method="build_tool"),
]

def build_tool(self) -> Tool:
return cast(Tool, YahooFinanceNewsTool())

def run_model(self) -> Data:
tool = self.build_tool()
return tool.run(self.input_value)
8 changes: 8 additions & 0 deletions src/backend/base/langflow/custom/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ def getattr_return_list_of_object(value):
return []


def getattr_return_list_of_values_from_dict(value):
if isinstance(value, dict):
return list(value.values())
return []


ATTR_FUNC_MAPPING: dict[str, Callable] = {
"display_name": getattr_return_str,
"description": getattr_return_str,
Expand All @@ -53,6 +59,8 @@ def getattr_return_list_of_object(value):
"is_input": getattr_return_bool,
"is_output": getattr_return_bool,
"conditional_paths": getattr_return_list_of_str,
"_outputs_maps": getattr_return_list_of_values_from_dict,
"_inputs": getattr_return_list_of_values_from_dict,
"outputs": getattr_return_list_of_object,
"inputs": getattr_return_list_of_object,
}
2 changes: 1 addition & 1 deletion src/backend/base/langflow/graph/edge/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def _validate_handles(self, source, target) -> None:
if not self.valid_handles:
logger.debug(self.source_handle)
logger.debug(self.target_handle)
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles")
raise ValueError(f"Edge between {source.display_name} and {target.display_name} " f"has invalid handles")

def _legacy_validate_handles(self, source, target) -> None:
if self.target_handle.input_types is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,8 @@
"dynamic": false,
"info": "A description of the tool.",
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"_input_type": "StrInput"
},
Expand All @@ -1184,9 +1186,31 @@
"dynamic": false,
"info": "A comma-separated list of modules to import globally, e.g. 'math,numpy'.",
"title_case": false,
"trace_as_metadata": true,
"type": "str",
"_input_type": "StrInput"
},
"input_value": {
"_input_type": "MessageTextInput",
"advanced": false,
"display_name": "Input",
"dynamic": false,
"info": "",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "input_value",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
},
"name": {
"trace_as_metadata": true,
"load_from_db": false,
Expand All @@ -1201,6 +1225,8 @@
"dynamic": false,
"info": "The name of the tool.",
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"_input_type": "StrInput"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@
"Data"
],
"value": "__UNDEFINED__"
},
{
"cache": true,
"display_name": "Text",
"method": "fetch_content_text",
"name": "text",
"selected": "Message",
"types": [
"Message"
],
"value": "__UNDEFINED__"
}
],
"pinned": false,
Expand All @@ -189,7 +200,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "import re\n\nfrom langchain_community.document_loaders.web_base import WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.io import MessageTextInput, Output\nfrom langflow.schema import Data\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Fetch content from one or more URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n info=\"Enter one or more URLs, by clicking the '+' button.\",\n is_list=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"\n Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n raise ValueError(f\"Invalid URL: {string}\")\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n"
"value": "import re\n\nfrom langchain_community.document_loaders.web_base import WebBaseLoader\n\nfrom langflow.helpers.data import data_to_text\nfrom langflow.custom import Component\nfrom langflow.io import MessageTextInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Fetch content from one or more URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n info=\"Enter one or more URLs, by clicking the '+' button.\",\n is_list=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"\n Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n raise ValueError(f\"Invalid URL: {string}\")\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n\n def fetch_content_text(self) -> Message:\n content = self.fetch_content()\n data = content if isinstance(content, list) else [content]\n\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n"
},
"urls": {
"advanced": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2618,6 +2618,17 @@
"frozen": false,
"output_types": [],
"outputs": [
{
"cache": true,
"display_name": "Data",
"method": "run_model",
"name": "api_run_model",
"selected": "Data",
"types": [
"Data"
],
"value": "__UNDEFINED__"
},
{
"cache": true,
"display_name": "Tool",
Expand Down Expand Up @@ -2649,7 +2660,28 @@
"show": true,
"title_case": false,
"type": "code",
"value": "from typing import cast\n\nfrom langchain_community.tools.yahoo_finance_news import YahooFinanceNewsTool\n\nfrom langflow.custom import Component\nfrom langflow.field_typing import Tool\nfrom langflow.io import Output\n\n\nclass YfinanceToolComponent(Component):\n display_name = \"Yahoo Finance News Tool\"\n description = \"Tool for interacting with Yahoo Finance News.\"\n name = \"YFinanceTool\"\n\n outputs = [\n Output(display_name=\"Tool\", name=\"tool\", method=\"build_tool\"),\n ]\n\n def build_tool(self) -> Tool:\n return cast(Tool, YahooFinanceNewsTool())\n"
"value": "from typing import cast\n\nfrom langchain_community.tools.yahoo_finance_news import YahooFinanceNewsTool\n\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.field_typing import Data, Tool\nfrom langflow.inputs.inputs import MessageTextInput\nfrom langflow.template.field.base import Output\n\n\nclass YfinanceToolComponent(LCToolComponent):\n display_name = \"Yahoo Finance News Tool\"\n description = \"Tool for interacting with Yahoo Finance News.\"\n name = \"YFinanceTool\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Query\",\n info=\"Input should be a company ticker. For example, AAPL for Apple, MSFT for Microsoft.\",\n )\n ]\n\n outputs = [\n Output(name=\"api_run_model\", display_name=\"Data\", method=\"run_model\"),\n # Keep this for backwards compatibility\n Output(name=\"tool\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n def build_tool(self) -> Tool:\n return cast(Tool, YahooFinanceNewsTool())\n\n def run_model(self) -> Data:\n tool = self.build_tool()\n return tool.run(self.input_value)\n"
},
"input_value": {
"_input_type": "MessageTextInput",
"advanced": false,
"display_name": "Query",
"dynamic": false,
"info": "Input should be a company ticker. For example, AAPL for Apple, MSFT for Microsoft.",
"input_types": [
"Message"
],
"list": false,
"load_from_db": false,
"name": "input_value",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_input": true,
"trace_as_metadata": true,
"type": "str",
"value": ""
}
}
},
Expand Down
Loading

0 comments on commit 4f9e241

Please sign in to comment.