Tai Truong
fix readme
d202ada
import json
from collections.abc import Sequence
from typing import Any
import requests
from langchain.agents import Tool
from langchain_core.tools import StructuredTool
from loguru import logger
from pydantic.v1 import Field, create_model
from langflow.base.langchain_utilities.model import LCToolComponent
from langflow.inputs import DropdownInput, IntInput, MessageTextInput, MultiselectInput
from langflow.io import Output
from langflow.schema.dotdict import dotdict
class SearXNGToolComponent(LCToolComponent):
search_headers: dict = {}
display_name = "SearXNG Search"
description = "A component that searches for tools using SearXNG."
name = "SearXNGTool"
legacy: bool = True
inputs = [
MessageTextInput(
name="url",
display_name="URL",
value="http://localhost",
required=True,
refresh_button=True,
),
IntInput(
name="max_results",
display_name="Max Results",
value=10,
required=True,
),
MultiselectInput(
name="categories",
display_name="Categories",
options=[],
value=[],
),
DropdownInput(
name="language",
display_name="Language",
options=[],
),
]
outputs = [
Output(display_name="Tool", name="result_tool", method="build_tool"),
]
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:
if field_name is None:
return build_config
if field_name != "url":
return build_config
try:
url = f"{field_value}/config"
response = requests.get(url=url, headers=self.search_headers.copy(), timeout=10)
data = None
if response.headers.get("Content-Encoding") == "zstd":
data = json.loads(response.content)
else:
data = response.json()
build_config["categories"]["options"] = data["categories"].copy()
for selected_category in build_config["categories"]["value"]:
if selected_category not in build_config["categories"]["options"]:
build_config["categories"]["value"].remove(selected_category)
languages = list(data["locales"])
build_config["language"]["options"] = languages.copy()
except Exception as e: # noqa: BLE001
self.status = f"Failed to extract names: {e}"
logger.opt(exception=True).debug(self.status)
build_config["categories"]["options"] = ["Failed to parse", str(e)]
return build_config
def build_tool(self) -> Tool:
class SearxSearch:
_url: str = ""
_categories: list[str] = []
_language: str = ""
_headers: dict = {}
_max_results: int = 10
@staticmethod
def search(query: str, categories: Sequence[str] = ()) -> list:
if not SearxSearch._categories and not categories:
msg = "No categories provided."
raise ValueError(msg)
all_categories = SearxSearch._categories + list(set(categories) - set(SearxSearch._categories))
try:
url = f"{SearxSearch._url}/"
headers = SearxSearch._headers.copy()
response = requests.get(
url=url,
headers=headers,
params={
"q": query,
"categories": ",".join(all_categories),
"language": SearxSearch._language,
"format": "json",
},
timeout=10,
).json()
num_results = min(SearxSearch._max_results, len(response["results"]))
return [response["results"][i] for i in range(num_results)]
except Exception as e: # noqa: BLE001
logger.opt(exception=True).debug("Error running SearXNG Search")
return [f"Failed to search: {e}"]
SearxSearch._url = self.url
SearxSearch._categories = self.categories.copy()
SearxSearch._language = self.language
SearxSearch._headers = self.search_headers.copy()
SearxSearch._max_results = self.max_results
globals_ = globals()
local = {}
local["SearxSearch"] = SearxSearch
globals_.update(local)
schema_fields = {
"query": (str, Field(..., description="The query to search for.")),
"categories": (
list[str],
Field(default=[], description="The categories to search in."),
),
}
searx_search_schema = create_model("SearxSearchSchema", **schema_fields)
return StructuredTool.from_function(
func=local["SearxSearch"].search,
args_schema=searx_search_schema,
name="searxng_search_tool",
description="A tool that searches for tools using SearXNG.\nThe available categories are: "
+ ", ".join(self.categories),
)