feat(tools): add TavilyAI search tool for enhanced LLM search results (#3774)
* add tavily new icon * feat(tools): add TavilyAI search tool for enhanced LLM search results * [autofix.ci] apply automated fixes * add tavily icon * [autofix.ci] apply automated fixes --------- Co-authored-by: cristhianzl <cristhian.lousa@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
parent
4d6fe61e71
commit
350189c88d
5 changed files with 238 additions and 0 deletions
157
src/backend/base/langflow/components/tools/TavilyAISearch.py
Normal file
157
src/backend/base/langflow/components/tools/TavilyAISearch.py
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
import httpx
|
||||||
|
from langchain.tools import StructuredTool
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from langflow.base.langchain_utilities.model import LCToolComponent
|
||||||
|
from langflow.field_typing import Tool
|
||||||
|
from langflow.inputs import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput
|
||||||
|
from langflow.schema import Data
|
||||||
|
|
||||||
|
|
||||||
|
class TavilySearchToolComponent(LCToolComponent):
|
||||||
|
display_name = "Tavily AI Search"
|
||||||
|
description = """**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results. It can be used independently or as an agent tool.
|
||||||
|
|
||||||
|
Note: Check 'Advanced' for all options.
|
||||||
|
"""
|
||||||
|
icon = "TavilyIcon"
|
||||||
|
name = "TavilyAISearch"
|
||||||
|
documentation = "https://docs.tavily.com/"
|
||||||
|
|
||||||
|
inputs = [
|
||||||
|
SecretStrInput(
|
||||||
|
name="api_key",
|
||||||
|
display_name="Tavily API Key",
|
||||||
|
required=True,
|
||||||
|
info="Your Tavily API Key.",
|
||||||
|
),
|
||||||
|
MessageTextInput(
|
||||||
|
name="query",
|
||||||
|
display_name="Search Query",
|
||||||
|
info="The search query you want to execute with Tavily.",
|
||||||
|
),
|
||||||
|
DropdownInput(
|
||||||
|
name="search_depth",
|
||||||
|
display_name="Search Depth",
|
||||||
|
info="The depth of the search.",
|
||||||
|
options=["basic", "advanced"],
|
||||||
|
value="advanced",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
DropdownInput(
|
||||||
|
name="topic",
|
||||||
|
display_name="Search Topic",
|
||||||
|
info="The category of the search.",
|
||||||
|
options=["general", "news"],
|
||||||
|
value="general",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
IntInput(
|
||||||
|
name="max_results",
|
||||||
|
display_name="Max Results",
|
||||||
|
info="The maximum number of search results to return.",
|
||||||
|
value=5,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
BoolInput(
|
||||||
|
name="include_images",
|
||||||
|
display_name="Include Images",
|
||||||
|
info="Include a list of query-related images in the response.",
|
||||||
|
value=True,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
BoolInput(
|
||||||
|
name="include_answer",
|
||||||
|
display_name="Include Answer",
|
||||||
|
info="Include a short answer to original query.",
|
||||||
|
value=True,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TavilySearchSchema(BaseModel):
|
||||||
|
query: str = Field(..., description="The search query you want to execute with Tavily.")
|
||||||
|
search_depth: str = Field("basic", description="The depth of the search.")
|
||||||
|
topic: str = Field("general", description="The category of the search.")
|
||||||
|
max_results: int = Field(5, description="The maximum number of search results to return.")
|
||||||
|
include_images: bool = Field(False, description="Include a list of query-related images in the response.")
|
||||||
|
include_answer: bool = Field(False, description="Include a short answer to original query.")
|
||||||
|
|
||||||
|
def run_model(self) -> list[Data]:
|
||||||
|
return self._tavily_search(
|
||||||
|
self.query,
|
||||||
|
self.search_depth,
|
||||||
|
self.topic,
|
||||||
|
self.max_results,
|
||||||
|
self.include_images,
|
||||||
|
self.include_answer,
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_tool(self) -> Tool:
|
||||||
|
return StructuredTool.from_function(
|
||||||
|
name="tavily_search",
|
||||||
|
description="Perform a web search using the Tavily API.",
|
||||||
|
func=self._tavily_search,
|
||||||
|
args_schema=self.TavilySearchSchema,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _tavily_search(
|
||||||
|
self,
|
||||||
|
query: str,
|
||||||
|
search_depth: str = "basic",
|
||||||
|
topic: str = "general",
|
||||||
|
max_results: int = 5,
|
||||||
|
include_images: bool = False,
|
||||||
|
include_answer: bool = False,
|
||||||
|
) -> list[Data]:
|
||||||
|
try:
|
||||||
|
url = "https://api.tavily.com/search"
|
||||||
|
headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
"accept": "application/json",
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"api_key": self.api_key,
|
||||||
|
"query": query,
|
||||||
|
"search_depth": search_depth,
|
||||||
|
"topic": topic,
|
||||||
|
"max_results": max_results,
|
||||||
|
"include_images": include_images,
|
||||||
|
"include_answer": include_answer,
|
||||||
|
}
|
||||||
|
|
||||||
|
with httpx.Client() as client:
|
||||||
|
response = client.post(url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
search_results = response.json()
|
||||||
|
|
||||||
|
data_results = [
|
||||||
|
Data(
|
||||||
|
data={
|
||||||
|
"title": result.get("title"),
|
||||||
|
"url": result.get("url"),
|
||||||
|
"content": result.get("content"),
|
||||||
|
"score": result.get("score"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for result in search_results.get("results", [])
|
||||||
|
]
|
||||||
|
|
||||||
|
if include_answer and search_results.get("answer"):
|
||||||
|
data_results.insert(0, Data(data={"answer": search_results["answer"]}))
|
||||||
|
|
||||||
|
if include_images and search_results.get("images"):
|
||||||
|
data_results.append(Data(data={"images": search_results["images"]}))
|
||||||
|
|
||||||
|
self.status = data_results
|
||||||
|
return data_results
|
||||||
|
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
error_message = f"HTTP error: {e.response.status_code} - {e.response.text}"
|
||||||
|
self.status = error_message
|
||||||
|
return [Data(data={"error": error_message})]
|
||||||
|
except Exception as e:
|
||||||
|
error_message = f"Unexpected error: {str(e)}"
|
||||||
|
self.status = error_message
|
||||||
|
return [Data(data={"error": error_message})]
|
||||||
69
src/frontend/src/icons/Tavily/Tavily.jsx
Normal file
69
src/frontend/src/icons/Tavily/Tavily.jsx
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
const Tavily = (props) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 375 375"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path d="M109.379 231.133h37.105v37.105H109.38Zm0 0" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="b">
|
||||||
|
<path d="M127.934 231.133c-10.246 0-18.555 8.304-18.555 18.554 0 10.247 8.308 18.551 18.555 18.551 10.246 0 18.55-8.304 18.55-18.55 0-10.25-8.304-18.555-18.55-18.555Zm0 0" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#f25022"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth={28.360419999999998}
|
||||||
|
d="M127.926 239.96V50.165"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#f25022"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={28.360419999999998}
|
||||||
|
d="m85.387 99.793 42.539-56.719 42.539 56.719"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#ffb901"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth={28.360419999999998}
|
||||||
|
d="m141.012 254.168 172.476-.02"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#ffb901"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={28.360419999999998}
|
||||||
|
d="m263.855 211.613 56.723 42.535-56.715 42.547"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#04a3ec"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth={23.20398}
|
||||||
|
d="m117.852 259.344-57.446 66.015"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#04a3ec"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={23.20398}
|
||||||
|
d="m113.32 317.574-56.718 12.16 4.207-57.859"
|
||||||
|
/>
|
||||||
|
<g clipPath="url(#a)">
|
||||||
|
<g clipPath="url(#b)">
|
||||||
|
<path fill="#32b37f" d="M109.379 231.133h37.105v37.105H109.38Zm0 0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
export default Tavily;
|
||||||
9
src/frontend/src/icons/Tavily/index.tsx
Normal file
9
src/frontend/src/icons/Tavily/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React, { forwardRef } from "react";
|
||||||
|
import Tavily from "./Tavily";
|
||||||
|
|
||||||
|
export const TavilyIcon = forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
>((props, ref) => {
|
||||||
|
return <Tavily ref={ref} {...props} />;
|
||||||
|
});
|
||||||
1
src/frontend/src/icons/Tavily/tavily.svg
Normal file
1
src/frontend/src/icons/Tavily/tavily.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="d0348dc115"><path d="M 109.378906 231.132812 L 146.484375 231.132812 L 146.484375 268.238281 L 109.378906 268.238281 Z M 109.378906 231.132812 " clip-rule="nonzero"/></clipPath><clipPath id="a28b194a7a"><path d="M 127.933594 231.132812 C 117.6875 231.132812 109.378906 239.4375 109.378906 249.6875 C 109.378906 259.933594 117.6875 268.238281 127.933594 268.238281 C 138.179688 268.238281 146.484375 259.933594 146.484375 249.6875 C 146.484375 239.4375 138.179688 231.132812 127.933594 231.132812 Z M 127.933594 231.132812 " clip-rule="nonzero"/></clipPath></defs><path stroke-linecap="round" transform="matrix(0, -2.578223, 2.578223, 0, 113.745458, 254.140061)" fill="none" stroke-linejoin="miter" d="M 5.499573 5.500038 L 79.114962 5.500038 " stroke="#f25022" stroke-width="11" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(0, -2.578223, 2.578223, 0, 113.745458, 254.140061)" fill="none" stroke-linejoin="round" d="M 59.865692 -10.999336 L 81.864858 5.500038 L 59.865692 21.999412 " stroke="#f25022" stroke-width="11" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(2.578223, -0.000251357, 0.000251357, 2.578223, 126.828174, 239.987372)" fill="none" stroke-linejoin="miter" d="M 5.500751 5.50068 L 72.398214 5.499627 " stroke="#ffb901" stroke-width="11" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(2.578223, -0.000251357, 0.000251357, 2.578223, 126.828174, 239.987372)" fill="none" stroke-linejoin="round" d="M 53.149037 -11.000109 L 75.148109 5.499895 L 53.14885 22.000154 " stroke="#ffb901" stroke-width="11" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(-1.692446, 1.944957, -1.944957, -1.692446, 134.219043, 258.208373)" fill="none" stroke-linejoin="miter" d="M 4.499518 4.49999 L 38.441562 4.500107 " stroke="#04a3ec" stroke-width="9" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(-1.692446, 1.944957, -1.944957, -1.692446, 134.219043, 258.208373)" fill="none" stroke-linejoin="round" d="M 22.691248 -9.000192 L 40.69038 4.49943 L 22.68978 17.999994 " stroke="#04a3ec" stroke-width="9" stroke-opacity="1" stroke-miterlimit="4"/><g clip-path="url(#d0348dc115)"><g clip-path="url(#a28b194a7a)"><path fill="#32b37f" d="M 109.378906 231.132812 L 146.484375 231.132812 L 146.484375 268.238281 L 109.378906 268.238281 Z M 109.378906 231.132812 " fill-opacity="1" fill-rule="nonzero"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -1,6 +1,7 @@
|
||||||
import { AIMLIcon } from "@/icons/AIML";
|
import { AIMLIcon } from "@/icons/AIML";
|
||||||
import { DuckDuckGoIcon } from "@/icons/DuckDuckGo";
|
import { DuckDuckGoIcon } from "@/icons/DuckDuckGo";
|
||||||
import Perplexity from "@/icons/Perplexity/Perplexity";
|
import Perplexity from "@/icons/Perplexity/Perplexity";
|
||||||
|
import { TavilyIcon } from "@/icons/Tavily";
|
||||||
import { UnstructuredIcon } from "@/icons/Unstructured";
|
import { UnstructuredIcon } from "@/icons/Unstructured";
|
||||||
import { AthenaIcon } from "@/icons/athena/index";
|
import { AthenaIcon } from "@/icons/athena/index";
|
||||||
import { freezeAllIcon } from "@/icons/freezeAll";
|
import { freezeAllIcon } from "@/icons/freezeAll";
|
||||||
|
|
@ -636,6 +637,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
OptionIcon: OptionIcon,
|
OptionIcon: OptionIcon,
|
||||||
Option: OptionIcon,
|
Option: OptionIcon,
|
||||||
Perplexity,
|
Perplexity,
|
||||||
|
TavilyIcon,
|
||||||
DuckDuckGo: DuckDuckGoIcon,
|
DuckDuckGo: DuckDuckGoIcon,
|
||||||
OpenSearch,
|
OpenSearch,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue