feat: Add Tavily search API parameters and Tavily Extract API component (#7644)
* Add Tavily search parameters and Extract API component * [autofix.ci] apply automated fixes * Fix style issues flagged by Ruff * fix Ruff Style Check issues * [autofix.ci] apply automated fixes * Fix linting issue reported by Ruff * fix inlcude and exclude domains * Fix linting issue reported by Ruff * fix: update descriptions and input types across multiple starter project JSON files - Changed "Tavily AI" to "Tavily Search" in descriptions for consistency. - Updated input types for several API keys to be empty arrays instead of containing "Message". - Added new fields such as "chunks_per_source", "days", "exclude_domains", "include_domains", and "include_raw_content" to enhance functionality in various projects. - Adjusted the "description" field in the "Youtube Analysis" project for clarity. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Edwin Jose <edwin.jose@datastax.com> Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
parent
793e5784d5
commit
8cabedae55
20 changed files with 1255 additions and 357 deletions
|
|
@ -22,8 +22,9 @@ from .search_api import SearchAPIComponent
|
||||||
from .searxng import SearXNGToolComponent
|
from .searxng import SearXNGToolComponent
|
||||||
from .serp import SerpComponent
|
from .serp import SerpComponent
|
||||||
from .serp_api import SerpAPIComponent
|
from .serp_api import SerpAPIComponent
|
||||||
from .tavily import TavilySearchComponent
|
from .tavily_extract import TavilyExtractComponent
|
||||||
from .tavily_search import TavilySearchToolComponent
|
from .tavily_search import TavilySearchComponent
|
||||||
|
from .tavily_search_tool import TavilySearchToolComponent
|
||||||
from .wikidata import WikidataComponent
|
from .wikidata import WikidataComponent
|
||||||
from .wikidata_api import WikidataAPIComponent
|
from .wikidata_api import WikidataAPIComponent
|
||||||
from .wikipedia import WikipediaComponent
|
from .wikipedia import WikipediaComponent
|
||||||
|
|
@ -60,6 +61,7 @@ __all__ = [
|
||||||
"SearchComponent",
|
"SearchComponent",
|
||||||
"SerpAPIComponent",
|
"SerpAPIComponent",
|
||||||
"SerpComponent",
|
"SerpComponent",
|
||||||
|
"TavilyExtractComponent",
|
||||||
"TavilySearchComponent",
|
"TavilySearchComponent",
|
||||||
"TavilySearchToolComponent",
|
"TavilySearchToolComponent",
|
||||||
"WikidataAPIComponent",
|
"WikidataAPIComponent",
|
||||||
|
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
import httpx
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from langflow.custom import Component
|
|
||||||
from langflow.helpers.data import data_to_text
|
|
||||||
from langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput
|
|
||||||
from langflow.schema import Data
|
|
||||||
from langflow.schema.message import Message
|
|
||||||
|
|
||||||
|
|
||||||
class TavilySearchComponent(Component):
|
|
||||||
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."""
|
|
||||||
icon = "TavilyIcon"
|
|
||||||
|
|
||||||
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.",
|
|
||||||
tool_mode=True,
|
|
||||||
),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
DropdownInput(
|
|
||||||
name="time_range",
|
|
||||||
display_name="Time Range",
|
|
||||||
info="The time range back from the current date to include in the search results.",
|
|
||||||
options=["day", "week", "month", "year"],
|
|
||||||
value=None,
|
|
||||||
advanced=True,
|
|
||||||
combobox=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,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
outputs = [
|
|
||||||
Output(display_name="Data", name="data", method="fetch_content"),
|
|
||||||
Output(display_name="Text", name="text", method="fetch_content_text"),
|
|
||||||
]
|
|
||||||
|
|
||||||
def fetch_content(self) -> list[Data]:
|
|
||||||
try:
|
|
||||||
url = "https://api.tavily.com/search"
|
|
||||||
headers = {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"accept": "application/json",
|
|
||||||
}
|
|
||||||
payload = {
|
|
||||||
"api_key": self.api_key,
|
|
||||||
"query": self.query,
|
|
||||||
"search_depth": self.search_depth,
|
|
||||||
"topic": self.topic,
|
|
||||||
"max_results": self.max_results,
|
|
||||||
"include_images": self.include_images,
|
|
||||||
"include_answer": self.include_answer,
|
|
||||||
"time_range": self.time_range,
|
|
||||||
}
|
|
||||||
|
|
||||||
with httpx.Client() as client:
|
|
||||||
response = client.post(url, json=payload, headers=headers)
|
|
||||||
|
|
||||||
response.raise_for_status()
|
|
||||||
search_results = response.json()
|
|
||||||
|
|
||||||
data_results = []
|
|
||||||
|
|
||||||
if self.include_answer and search_results.get("answer"):
|
|
||||||
data_results.append(Data(text=search_results["answer"]))
|
|
||||||
|
|
||||||
for result in search_results.get("results", []):
|
|
||||||
content = result.get("content", "")
|
|
||||||
data_results.append(
|
|
||||||
Data(
|
|
||||||
text=content,
|
|
||||||
data={
|
|
||||||
"title": result.get("title"),
|
|
||||||
"url": result.get("url"),
|
|
||||||
"content": content,
|
|
||||||
"score": result.get("score"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.include_images and search_results.get("images"):
|
|
||||||
data_results.append(Data(text="Images found", data={"images": search_results["images"]}))
|
|
||||||
except httpx.HTTPStatusError as exc:
|
|
||||||
error_message = f"HTTP error occurred: {exc.response.status_code} - {exc.response.text}"
|
|
||||||
logger.error(error_message)
|
|
||||||
return [Data(text=error_message, data={"error": error_message})]
|
|
||||||
except httpx.RequestError as exc:
|
|
||||||
error_message = f"Request error occurred: {exc}"
|
|
||||||
logger.error(error_message)
|
|
||||||
return [Data(text=error_message, data={"error": error_message})]
|
|
||||||
except ValueError as exc:
|
|
||||||
error_message = f"Invalid response format: {exc}"
|
|
||||||
logger.error(error_message)
|
|
||||||
return [Data(text=error_message, data={"error": error_message})]
|
|
||||||
else:
|
|
||||||
self.status = data_results
|
|
||||||
return data_results
|
|
||||||
|
|
||||||
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)
|
|
||||||
119
src/backend/base/langflow/components/tools/tavily_extract.py
Normal file
119
src/backend/base/langflow/components/tools/tavily_extract.py
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import httpx
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from langflow.custom import Component
|
||||||
|
from langflow.helpers.data import data_to_text
|
||||||
|
from langflow.io import BoolInput, DropdownInput, MessageTextInput, Output, SecretStrInput
|
||||||
|
from langflow.schema import Data
|
||||||
|
from langflow.schema.message import Message
|
||||||
|
|
||||||
|
|
||||||
|
class TavilyExtractComponent(Component):
|
||||||
|
"""Separate component specifically for Tavily Extract functionality."""
|
||||||
|
|
||||||
|
display_name = "Tavily Extract API"
|
||||||
|
description = """**Tavily Extract** extract raw content from URLs."""
|
||||||
|
icon = "TavilyIcon"
|
||||||
|
|
||||||
|
inputs = [
|
||||||
|
SecretStrInput(
|
||||||
|
name="api_key",
|
||||||
|
display_name="Tavily API Key",
|
||||||
|
required=True,
|
||||||
|
info="Your Tavily API Key.",
|
||||||
|
),
|
||||||
|
MessageTextInput(
|
||||||
|
name="urls",
|
||||||
|
display_name="URLs",
|
||||||
|
info="Comma-separated list of URLs to extract content from.",
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
DropdownInput(
|
||||||
|
name="extract_depth",
|
||||||
|
display_name="Extract Depth",
|
||||||
|
info="The depth of the extraction process.",
|
||||||
|
options=["basic", "advanced"],
|
||||||
|
value="basic",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
BoolInput(
|
||||||
|
name="include_images",
|
||||||
|
display_name="Include Images",
|
||||||
|
info="Include a list of images extracted from the URLs.",
|
||||||
|
value=False,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
outputs = [
|
||||||
|
Output(display_name="Data", name="data", method="fetch_content"),
|
||||||
|
Output(display_name="Text", name="text", method="fetch_content_text"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def fetch_content(self) -> list[Data]:
|
||||||
|
"""Fetches and processes extracted content into a list of Data objects."""
|
||||||
|
try:
|
||||||
|
# Split URLs by comma and clean them
|
||||||
|
urls = [url.strip() for url in (self.urls or "").split(",") if url.strip()]
|
||||||
|
if not urls:
|
||||||
|
error_message = "No valid URLs provided"
|
||||||
|
logger.error(error_message)
|
||||||
|
return [Data(text=error_message, data={"error": error_message})]
|
||||||
|
|
||||||
|
url = "https://api.tavily.com/extract"
|
||||||
|
headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
"accept": "application/json",
|
||||||
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"urls": urls,
|
||||||
|
"extract_depth": self.extract_depth,
|
||||||
|
"include_images": self.include_images,
|
||||||
|
}
|
||||||
|
|
||||||
|
with httpx.Client(timeout=90.0) as client:
|
||||||
|
response = client.post(url, json=payload, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
except httpx.TimeoutException as exc:
|
||||||
|
error_message = f"Request timed out (90s): {exc}"
|
||||||
|
logger.error(error_message)
|
||||||
|
return [Data(text=error_message, data={"error": error_message})]
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
error_message = f"HTTP error occurred: {exc.response.status_code} - {exc.response.text}"
|
||||||
|
logger.error(error_message)
|
||||||
|
return [Data(text=error_message, data={"error": error_message})]
|
||||||
|
except (ValueError, KeyError, AttributeError, httpx.RequestError) as exc:
|
||||||
|
error_message = f"Data processing error: {exc}"
|
||||||
|
logger.error(error_message)
|
||||||
|
return [Data(text=error_message, data={"error": error_message})]
|
||||||
|
else:
|
||||||
|
extract_results = response.json()
|
||||||
|
data_results = []
|
||||||
|
|
||||||
|
# Process successful extractions
|
||||||
|
for result in extract_results.get("results", []):
|
||||||
|
raw_content = result.get("raw_content", "")
|
||||||
|
images = result.get("images", [])
|
||||||
|
result_data = {"url": result.get("url"), "raw_content": raw_content, "images": images}
|
||||||
|
data_results.append(Data(text=raw_content, data=result_data))
|
||||||
|
|
||||||
|
# Process failed extractions
|
||||||
|
if extract_results.get("failed_results"):
|
||||||
|
data_results.append(
|
||||||
|
Data(
|
||||||
|
text="Failed extractions",
|
||||||
|
data={"failed_results": extract_results["failed_results"]},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.status = data_results
|
||||||
|
return data_results
|
||||||
|
|
||||||
|
def fetch_content_text(self) -> Message:
|
||||||
|
# This method should still work as it expects a list from fetch_content
|
||||||
|
data = self.fetch_content()
|
||||||
|
result_string = data_to_text("{text}", data)
|
||||||
|
self.status = result_string
|
||||||
|
return Message(text=result_string)
|
||||||
|
|
@ -1,47 +1,18 @@
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from langchain.tools import StructuredTool
|
|
||||||
from langchain_core.tools import ToolException
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
from langflow.base.langchain_utilities.model import LCToolComponent
|
from langflow.custom import Component
|
||||||
from langflow.field_typing import Tool
|
from langflow.helpers.data import data_to_text
|
||||||
from langflow.inputs import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput
|
from langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput
|
||||||
from langflow.schema import Data
|
from langflow.schema import Data
|
||||||
|
from langflow.schema.message import Message
|
||||||
|
|
||||||
|
|
||||||
class TavilySearchDepth(Enum):
|
class TavilySearchComponent(Component):
|
||||||
BASIC = "basic"
|
display_name = "Tavily Search API"
|
||||||
ADVANCED = "advanced"
|
description = """**Tavily Search** is a search engine optimized for LLMs and RAG, \
|
||||||
|
aimed at efficient, quick, and persistent search results."""
|
||||||
|
|
||||||
class TavilySearchTopic(Enum):
|
|
||||||
GENERAL = "general"
|
|
||||||
NEWS = "news"
|
|
||||||
|
|
||||||
|
|
||||||
class TavilySearchSchema(BaseModel):
|
|
||||||
query: str = Field(..., description="The search query you want to execute with Tavily.")
|
|
||||||
search_depth: TavilySearchDepth = Field(TavilySearchDepth.BASIC, description="The depth of the search.")
|
|
||||||
topic: TavilySearchTopic = Field(TavilySearchTopic.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(default=False, description="Include a list of query-related images in the response.")
|
|
||||||
include_answer: bool = Field(default=False, description="Include a short answer to original query.")
|
|
||||||
|
|
||||||
|
|
||||||
class TavilySearchToolComponent(LCToolComponent):
|
|
||||||
display_name = "Tavily AI Search [DEPRECATED]"
|
|
||||||
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"
|
icon = "TavilyIcon"
|
||||||
name = "TavilyAISearch"
|
|
||||||
documentation = "https://docs.tavily.com/"
|
|
||||||
legacy = True
|
|
||||||
|
|
||||||
inputs = [
|
inputs = [
|
||||||
SecretStrInput(
|
SecretStrInput(
|
||||||
|
|
@ -54,21 +25,36 @@ Note: Check 'Advanced' for all options.
|
||||||
name="query",
|
name="query",
|
||||||
display_name="Search Query",
|
display_name="Search Query",
|
||||||
info="The search query you want to execute with Tavily.",
|
info="The search query you want to execute with Tavily.",
|
||||||
|
tool_mode=True,
|
||||||
),
|
),
|
||||||
DropdownInput(
|
DropdownInput(
|
||||||
name="search_depth",
|
name="search_depth",
|
||||||
display_name="Search Depth",
|
display_name="Search Depth",
|
||||||
info="The depth of the search.",
|
info="The depth of the search.",
|
||||||
options=list(TavilySearchDepth),
|
options=["basic", "advanced"],
|
||||||
value=TavilySearchDepth.ADVANCED,
|
value="advanced",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
IntInput(
|
||||||
|
name="chunks_per_source",
|
||||||
|
display_name="Chunks Per Source",
|
||||||
|
info=("The number of content chunks to retrieve from each source (1-3). Only works with advanced search."),
|
||||||
|
value=3,
|
||||||
advanced=True,
|
advanced=True,
|
||||||
),
|
),
|
||||||
DropdownInput(
|
DropdownInput(
|
||||||
name="topic",
|
name="topic",
|
||||||
display_name="Search Topic",
|
display_name="Search Topic",
|
||||||
info="The category of the search.",
|
info="The category of the search.",
|
||||||
options=list(TavilySearchTopic),
|
options=["general", "news"],
|
||||||
value=TavilySearchTopic.GENERAL,
|
value="general",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
IntInput(
|
||||||
|
name="days",
|
||||||
|
display_name="Days",
|
||||||
|
info="Number of days back from current date to include. Only available with news topic.",
|
||||||
|
value=7,
|
||||||
advanced=True,
|
advanced=True,
|
||||||
),
|
),
|
||||||
IntInput(
|
IntInput(
|
||||||
|
|
@ -78,13 +64,6 @@ Note: Check 'Advanced' for all options.
|
||||||
value=5,
|
value=5,
|
||||||
advanced=True,
|
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(
|
BoolInput(
|
||||||
name="include_answer",
|
name="include_answer",
|
||||||
display_name="Include Answer",
|
display_name="Include Answer",
|
||||||
|
|
@ -92,115 +71,145 @@ Note: Check 'Advanced' for all options.
|
||||||
value=True,
|
value=True,
|
||||||
advanced=True,
|
advanced=True,
|
||||||
),
|
),
|
||||||
|
DropdownInput(
|
||||||
|
name="time_range",
|
||||||
|
display_name="Time Range",
|
||||||
|
info="The time range back from the current date to filter results.",
|
||||||
|
options=["day", "week", "month", "year"],
|
||||||
|
value=None, # Default to None to make it optional
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
MessageTextInput(
|
||||||
|
name="include_domains",
|
||||||
|
display_name="Include Domains",
|
||||||
|
info="Comma-separated list of domains to include in the search results.",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
MessageTextInput(
|
||||||
|
name="exclude_domains",
|
||||||
|
display_name="Exclude Domains",
|
||||||
|
info="Comma-separated list of domains to exclude from the search results.",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
BoolInput(
|
||||||
|
name="include_raw_content",
|
||||||
|
display_name="Include Raw Content",
|
||||||
|
info="Include the cleaned and parsed HTML content of each search result.",
|
||||||
|
value=False,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def run_model(self) -> list[Data]:
|
outputs = [
|
||||||
# Convert string values to enum instances with validation
|
Output(display_name="Data", name="data", method="fetch_content"),
|
||||||
|
Output(display_name="Text", name="text", method="fetch_content_text"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def fetch_content(self) -> list[Data]:
|
||||||
try:
|
try:
|
||||||
search_depth_enum = (
|
# Only process domains if they're provided
|
||||||
self.search_depth
|
include_domains = None
|
||||||
if isinstance(self.search_depth, TavilySearchDepth)
|
exclude_domains = None
|
||||||
else TavilySearchDepth(str(self.search_depth).lower())
|
|
||||||
)
|
|
||||||
except ValueError as e:
|
|
||||||
error_message = f"Invalid search depth value: {e!s}"
|
|
||||||
self.status = error_message
|
|
||||||
return [Data(data={"error": error_message})]
|
|
||||||
|
|
||||||
try:
|
if self.include_domains:
|
||||||
topic_enum = (
|
include_domains = [domain.strip() for domain in self.include_domains.split(",") if domain.strip()]
|
||||||
self.topic if isinstance(self.topic, TavilySearchTopic) else TavilySearchTopic(str(self.topic).lower())
|
|
||||||
)
|
|
||||||
except ValueError as e:
|
|
||||||
error_message = f"Invalid topic value: {e!s}"
|
|
||||||
self.status = error_message
|
|
||||||
return [Data(data={"error": error_message})]
|
|
||||||
|
|
||||||
return self._tavily_search(
|
if self.exclude_domains:
|
||||||
self.query,
|
exclude_domains = [domain.strip() for domain in self.exclude_domains.split(",") if domain.strip()]
|
||||||
search_depth=search_depth_enum,
|
|
||||||
topic=topic_enum,
|
|
||||||
max_results=self.max_results,
|
|
||||||
include_images=self.include_images,
|
|
||||||
include_answer=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=TavilySearchSchema,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _tavily_search(
|
|
||||||
self,
|
|
||||||
query: str,
|
|
||||||
*,
|
|
||||||
search_depth: TavilySearchDepth = TavilySearchDepth.BASIC,
|
|
||||||
topic: TavilySearchTopic = TavilySearchTopic.GENERAL,
|
|
||||||
max_results: int = 5,
|
|
||||||
include_images: bool = False,
|
|
||||||
include_answer: bool = False,
|
|
||||||
) -> list[Data]:
|
|
||||||
# Validate enum values
|
|
||||||
if not isinstance(search_depth, TavilySearchDepth):
|
|
||||||
msg = f"Invalid search_depth value: {search_depth}"
|
|
||||||
raise TypeError(msg)
|
|
||||||
if not isinstance(topic, TavilySearchTopic):
|
|
||||||
msg = f"Invalid topic value: {topic}"
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
try:
|
|
||||||
url = "https://api.tavily.com/search"
|
url = "https://api.tavily.com/search"
|
||||||
headers = {
|
headers = {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
"accept": "application/json",
|
"accept": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"api_key": self.api_key,
|
"api_key": self.api_key,
|
||||||
"query": query,
|
"query": self.query,
|
||||||
"search_depth": search_depth.value,
|
"search_depth": self.search_depth,
|
||||||
"topic": topic.value,
|
"topic": self.topic,
|
||||||
"max_results": max_results,
|
"max_results": self.max_results,
|
||||||
"include_images": include_images,
|
"include_images": self.include_images,
|
||||||
"include_answer": include_answer,
|
"include_answer": self.include_answer,
|
||||||
|
"include_raw_content": self.include_raw_content,
|
||||||
|
"days": self.days,
|
||||||
|
"time_range": self.time_range,
|
||||||
}
|
}
|
||||||
|
|
||||||
with httpx.Client() as client:
|
# Only add domains to payload if they exist and have values
|
||||||
|
if include_domains:
|
||||||
|
payload["include_domains"] = include_domains
|
||||||
|
if exclude_domains:
|
||||||
|
payload["exclude_domains"] = exclude_domains
|
||||||
|
|
||||||
|
# Add conditional parameters only if they should be included
|
||||||
|
if self.search_depth == "advanced" and self.chunks_per_source:
|
||||||
|
payload["chunks_per_source"] = self.chunks_per_source
|
||||||
|
|
||||||
|
if self.topic == "news" and self.days:
|
||||||
|
payload["days"] = int(self.days) # Ensure days is an integer
|
||||||
|
|
||||||
|
# Add time_range if it's set
|
||||||
|
if hasattr(self, "time_range") and self.time_range:
|
||||||
|
payload["time_range"] = self.time_range
|
||||||
|
|
||||||
|
# Add timeout handling
|
||||||
|
with httpx.Client(timeout=90.0) as client:
|
||||||
response = client.post(url, json=payload, headers=headers)
|
response = client.post(url, json=payload, headers=headers)
|
||||||
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
search_results = response.json()
|
search_results = response.json()
|
||||||
|
|
||||||
data_results = [
|
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"):
|
if self.include_answer and search_results.get("answer"):
|
||||||
data_results.insert(0, Data(data={"answer": search_results["answer"]}))
|
data_results.append(Data(text=search_results["answer"]))
|
||||||
|
|
||||||
if include_images and search_results.get("images"):
|
for result in search_results.get("results", []):
|
||||||
data_results.append(Data(data={"images": search_results["images"]}))
|
content = result.get("content", "")
|
||||||
|
result_data = {
|
||||||
|
"title": result.get("title"),
|
||||||
|
"url": result.get("url"),
|
||||||
|
"content": content,
|
||||||
|
"score": result.get("score"),
|
||||||
|
}
|
||||||
|
if self.include_raw_content:
|
||||||
|
result_data["raw_content"] = result.get("raw_content")
|
||||||
|
|
||||||
self.status = data_results # type: ignore[assignment]
|
data_results.append(Data(text=content, data=result_data))
|
||||||
|
|
||||||
except httpx.HTTPStatusError as e:
|
if self.include_images and search_results.get("images"):
|
||||||
error_message = f"HTTP error: {e.response.status_code} - {e.response.text}"
|
data_results.append(Data(text="Images found", data={"images": search_results["images"]}))
|
||||||
logger.debug(error_message)
|
|
||||||
self.status = error_message
|
except httpx.TimeoutException:
|
||||||
raise ToolException(error_message) from e
|
error_message = "Request timed out (90s). Please try again or adjust parameters."
|
||||||
except Exception as e:
|
logger.error(error_message)
|
||||||
error_message = f"Unexpected error: {e}"
|
return [Data(text=error_message, data={"error": error_message})]
|
||||||
logger.opt(exception=True).debug("Error running Tavily Search")
|
except httpx.HTTPStatusError as exc:
|
||||||
self.status = error_message
|
error_message = f"HTTP error occurred: {exc.response.status_code} - {exc.response.text}"
|
||||||
raise ToolException(error_message) from e
|
logger.error(error_message)
|
||||||
return data_results
|
return [Data(text=error_message, data={"error": error_message})]
|
||||||
|
except httpx.RequestError as exc:
|
||||||
|
error_message = f"Request error occurred: {exc}"
|
||||||
|
logger.error(error_message)
|
||||||
|
return [Data(text=error_message, data={"error": error_message})]
|
||||||
|
except ValueError as exc:
|
||||||
|
error_message = f"Invalid response format: {exc}"
|
||||||
|
logger.error(error_message)
|
||||||
|
return [Data(text=error_message, data={"error": error_message})]
|
||||||
|
else:
|
||||||
|
self.status = data_results
|
||||||
|
return data_results
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
|
||||||
344
src/backend/base/langflow/components/tools/tavily_search_tool.py
Normal file
344
src/backend/base/langflow/components/tools/tavily_search_tool.py
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from langchain.tools import StructuredTool
|
||||||
|
from langchain_core.tools import ToolException
|
||||||
|
from loguru import logger
|
||||||
|
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
|
||||||
|
|
||||||
|
# Add at the top with other constants
|
||||||
|
MAX_CHUNKS_PER_SOURCE = 3
|
||||||
|
|
||||||
|
|
||||||
|
class TavilySearchDepth(Enum):
|
||||||
|
BASIC = "basic"
|
||||||
|
ADVANCED = "advanced"
|
||||||
|
|
||||||
|
|
||||||
|
class TavilySearchTopic(Enum):
|
||||||
|
GENERAL = "general"
|
||||||
|
NEWS = "news"
|
||||||
|
|
||||||
|
|
||||||
|
class TavilySearchTimeRange(Enum):
|
||||||
|
DAY = "day"
|
||||||
|
WEEK = "week"
|
||||||
|
MONTH = "month"
|
||||||
|
YEAR = "year"
|
||||||
|
|
||||||
|
|
||||||
|
class TavilySearchSchema(BaseModel):
|
||||||
|
query: str = Field(..., description="The search query you want to execute with Tavily.")
|
||||||
|
search_depth: TavilySearchDepth = Field(TavilySearchDepth.BASIC, description="The depth of the search.")
|
||||||
|
topic: TavilySearchTopic = Field(TavilySearchTopic.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(default=False, description="Include a list of query-related images in the response.")
|
||||||
|
include_answer: bool = Field(default=False, description="Include a short answer to original query.")
|
||||||
|
chunks_per_source: int = Field(
|
||||||
|
default=MAX_CHUNKS_PER_SOURCE,
|
||||||
|
description=(
|
||||||
|
"The number of content chunks to retrieve from each source (max 500 chars each). Only for advanced search."
|
||||||
|
),
|
||||||
|
ge=1,
|
||||||
|
le=MAX_CHUNKS_PER_SOURCE,
|
||||||
|
)
|
||||||
|
include_domains: list[str] = Field(
|
||||||
|
default=[],
|
||||||
|
description="A list of domains to specifically include in the search results.",
|
||||||
|
)
|
||||||
|
exclude_domains: list[str] = Field(
|
||||||
|
default=[],
|
||||||
|
description="A list of domains to specifically exclude from the search results.",
|
||||||
|
)
|
||||||
|
include_raw_content: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Include the cleaned and parsed HTML content of each search result.",
|
||||||
|
)
|
||||||
|
days: int = Field(
|
||||||
|
default=7,
|
||||||
|
description="Number of days back from the current date to include. Only available if topic is news.",
|
||||||
|
ge=1,
|
||||||
|
)
|
||||||
|
time_range: TavilySearchTimeRange | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="The time range back from the current date to filter results.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TavilySearchToolComponent(LCToolComponent):
|
||||||
|
display_name = "Tavily Search API"
|
||||||
|
description = """**Tavily Search API** 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/"
|
||||||
|
legacy = True
|
||||||
|
|
||||||
|
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=list(TavilySearchDepth),
|
||||||
|
value=TavilySearchDepth.ADVANCED,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
IntInput(
|
||||||
|
name="chunks_per_source",
|
||||||
|
display_name="Chunks Per Source",
|
||||||
|
info=("The number of content chunks to retrieve from each source (1-3). Only works with advanced search."),
|
||||||
|
value=MAX_CHUNKS_PER_SOURCE,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
DropdownInput(
|
||||||
|
name="topic",
|
||||||
|
display_name="Search Topic",
|
||||||
|
info="The category of the search.",
|
||||||
|
options=list(TavilySearchTopic),
|
||||||
|
value=TavilySearchTopic.GENERAL,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
IntInput(
|
||||||
|
name="days",
|
||||||
|
display_name="Days",
|
||||||
|
info="Number of days back from current date to include. Only available with news topic.",
|
||||||
|
value=7,
|
||||||
|
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_answer",
|
||||||
|
display_name="Include Answer",
|
||||||
|
info="Include a short answer to original query.",
|
||||||
|
value=True,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
DropdownInput(
|
||||||
|
name="time_range",
|
||||||
|
display_name="Time Range",
|
||||||
|
info="The time range back from the current date to filter results.",
|
||||||
|
options=list(TavilySearchTimeRange),
|
||||||
|
value=None,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
MessageTextInput(
|
||||||
|
name="include_domains",
|
||||||
|
display_name="Include Domains",
|
||||||
|
info="Comma-separated list of domains to include in the search results.",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
MessageTextInput(
|
||||||
|
name="exclude_domains",
|
||||||
|
display_name="Exclude Domains",
|
||||||
|
info="Comma-separated list of domains to exclude from the search results.",
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
BoolInput(
|
||||||
|
name="include_raw_content",
|
||||||
|
display_name="Include Raw Content",
|
||||||
|
info="Include the cleaned and parsed HTML content of each search result.",
|
||||||
|
value=False,
|
||||||
|
advanced=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def run_model(self) -> list[Data]:
|
||||||
|
# Convert string values to enum instances with validation
|
||||||
|
try:
|
||||||
|
search_depth_enum = (
|
||||||
|
self.search_depth
|
||||||
|
if isinstance(self.search_depth, TavilySearchDepth)
|
||||||
|
else TavilySearchDepth(str(self.search_depth).lower())
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
error_message = f"Invalid search depth value: {e!s}"
|
||||||
|
self.status = error_message
|
||||||
|
return [Data(data={"error": error_message})]
|
||||||
|
|
||||||
|
try:
|
||||||
|
topic_enum = (
|
||||||
|
self.topic if isinstance(self.topic, TavilySearchTopic) else TavilySearchTopic(str(self.topic).lower())
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
error_message = f"Invalid topic value: {e!s}"
|
||||||
|
self.status = error_message
|
||||||
|
return [Data(data={"error": error_message})]
|
||||||
|
|
||||||
|
try:
|
||||||
|
time_range_enum = (
|
||||||
|
self.time_range
|
||||||
|
if isinstance(self.time_range, TavilySearchTimeRange)
|
||||||
|
else TavilySearchTimeRange(str(self.time_range).lower())
|
||||||
|
if self.time_range
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
error_message = f"Invalid time range value: {e!s}"
|
||||||
|
self.status = error_message
|
||||||
|
return [Data(data={"error": error_message})]
|
||||||
|
|
||||||
|
# Initialize domain variables as None
|
||||||
|
include_domains = None
|
||||||
|
exclude_domains = None
|
||||||
|
|
||||||
|
# Only process domains if they're provided
|
||||||
|
if self.include_domains:
|
||||||
|
include_domains = [domain.strip() for domain in self.include_domains.split(",") if domain.strip()]
|
||||||
|
|
||||||
|
if self.exclude_domains:
|
||||||
|
exclude_domains = [domain.strip() for domain in self.exclude_domains.split(",") if domain.strip()]
|
||||||
|
|
||||||
|
return self._tavily_search(
|
||||||
|
self.query,
|
||||||
|
search_depth=search_depth_enum,
|
||||||
|
topic=topic_enum,
|
||||||
|
max_results=self.max_results,
|
||||||
|
include_images=self.include_images,
|
||||||
|
include_answer=self.include_answer,
|
||||||
|
chunks_per_source=self.chunks_per_source,
|
||||||
|
include_domains=include_domains,
|
||||||
|
exclude_domains=exclude_domains,
|
||||||
|
include_raw_content=self.include_raw_content,
|
||||||
|
days=self.days,
|
||||||
|
time_range=time_range_enum,
|
||||||
|
)
|
||||||
|
|
||||||
|
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=TavilySearchSchema,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _tavily_search(
|
||||||
|
self,
|
||||||
|
query: str,
|
||||||
|
*,
|
||||||
|
search_depth: TavilySearchDepth = TavilySearchDepth.BASIC,
|
||||||
|
topic: TavilySearchTopic = TavilySearchTopic.GENERAL,
|
||||||
|
max_results: int = 5,
|
||||||
|
include_images: bool = False,
|
||||||
|
include_answer: bool = False,
|
||||||
|
chunks_per_source: int = MAX_CHUNKS_PER_SOURCE,
|
||||||
|
include_domains: list[str] | None = None,
|
||||||
|
exclude_domains: list[str] | None = None,
|
||||||
|
include_raw_content: bool = False,
|
||||||
|
days: int = 7,
|
||||||
|
time_range: TavilySearchTimeRange | None = None,
|
||||||
|
) -> list[Data]:
|
||||||
|
# Validate enum values
|
||||||
|
if not isinstance(search_depth, TavilySearchDepth):
|
||||||
|
msg = f"Invalid search_depth value: {search_depth}"
|
||||||
|
raise TypeError(msg)
|
||||||
|
if not isinstance(topic, TavilySearchTopic):
|
||||||
|
msg = f"Invalid topic value: {topic}"
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Validate chunks_per_source range
|
||||||
|
if not 1 <= chunks_per_source <= MAX_CHUNKS_PER_SOURCE:
|
||||||
|
msg = f"chunks_per_source must be between 1 and {MAX_CHUNKS_PER_SOURCE}, got {chunks_per_source}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
# Validate days is positive
|
||||||
|
if days < 1:
|
||||||
|
msg = f"days must be greater than or equal to 1, got {days}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
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.value,
|
||||||
|
"topic": topic.value,
|
||||||
|
"max_results": max_results,
|
||||||
|
"include_images": include_images,
|
||||||
|
"include_answer": include_answer,
|
||||||
|
"chunks_per_source": chunks_per_source if search_depth == TavilySearchDepth.ADVANCED else None,
|
||||||
|
"include_domains": include_domains if include_domains else None,
|
||||||
|
"exclude_domains": exclude_domains if exclude_domains else None,
|
||||||
|
"include_raw_content": include_raw_content,
|
||||||
|
"days": days if topic == TavilySearchTopic.NEWS else None,
|
||||||
|
"time_range": time_range.value if time_range else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
with httpx.Client(timeout=90.0) 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"),
|
||||||
|
"raw_content": result.get("raw_content") if include_raw_content else None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
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 # type: ignore[assignment]
|
||||||
|
|
||||||
|
except httpx.TimeoutException as e:
|
||||||
|
error_message = "Request timed out (90s). Please try again or adjust parameters."
|
||||||
|
logger.error(f"Timeout error: {e}")
|
||||||
|
self.status = error_message
|
||||||
|
raise ToolException(error_message) from e
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
error_message = f"HTTP error: {e.response.status_code} - {e.response.text}"
|
||||||
|
logger.debug(error_message)
|
||||||
|
self.status = error_message
|
||||||
|
raise ToolException(error_message) from e
|
||||||
|
except Exception as e:
|
||||||
|
error_message = f"Unexpected error: {e}"
|
||||||
|
logger.opt(exception=True).debug("Error running Tavily Search")
|
||||||
|
self.status = error_message
|
||||||
|
raise ToolException(error_message) from e
|
||||||
|
return data_results
|
||||||
|
|
@ -410,7 +410,7 @@
|
||||||
"custom_fields": {
|
"custom_fields": {
|
||||||
"template": []
|
"template": []
|
||||||
},
|
},
|
||||||
"description": "",
|
"description": "Create a prompt template with dynamic variables.",
|
||||||
"display_name": "Prompt",
|
"display_name": "Prompt",
|
||||||
"documentation": "",
|
"documentation": "",
|
||||||
"edited": false,
|
"edited": false,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1484,9 +1484,7 @@
|
||||||
"display_name": "Composio API Key",
|
"display_name": "Composio API Key",
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"info": "Refer to https://docs.composio.dev/faq/api_key/api_key",
|
"info": "Refer to https://docs.composio.dev/faq/api_key/api_key",
|
||||||
"input_types": [
|
"input_types": [],
|
||||||
"Message"
|
|
||||||
],
|
|
||||||
"load_from_db": true,
|
"load_from_db": true,
|
||||||
"name": "api_key",
|
"name": "api_key",
|
||||||
"password": true,
|
"password": true,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -735,9 +735,7 @@
|
||||||
"display_name": "Needle API Key",
|
"display_name": "Needle API Key",
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"info": "Your Needle API key.",
|
"info": "Your Needle API key.",
|
||||||
"input_types": [
|
"input_types": [],
|
||||||
"Message"
|
|
||||||
],
|
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"name": "needle_api_key",
|
"name": "needle_api_key",
|
||||||
"password": true,
|
"password": true,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -232,9 +232,7 @@
|
||||||
"display_name": "API Key",
|
"display_name": "API Key",
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"info": "Your AgentQL API key from dev.agentql.com",
|
"info": "Your AgentQL API key from dev.agentql.com",
|
||||||
"input_types": [
|
"input_types": [],
|
||||||
"Message"
|
|
||||||
],
|
|
||||||
"load_from_db": true,
|
"load_from_db": true,
|
||||||
"name": "api_key",
|
"name": "api_key",
|
||||||
"password": true,
|
"password": true,
|
||||||
|
|
@ -301,7 +299,7 @@
|
||||||
},
|
},
|
||||||
"is_stealth_mode_enabled": {
|
"is_stealth_mode_enabled": {
|
||||||
"_input_type": "BoolInput",
|
"_input_type": "BoolInput",
|
||||||
"advanced": false,
|
"advanced": true,
|
||||||
"display_name": "Enable Stealth Mode (Beta)",
|
"display_name": "Enable Stealth Mode (Beta)",
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"info": "Enable experimental anti-bot evasion strategies. May not work for all websites at all times.",
|
"info": "Enable experimental anti-bot evasion strategies. May not work for all websites at all times.",
|
||||||
|
|
|
||||||
|
|
@ -938,7 +938,7 @@
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"name": "curl",
|
"name": "curl",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"real_time_refresh": false,
|
"real_time_refresh": true,
|
||||||
"required": false,
|
"required": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"title_case": false,
|
"title_case": false,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -131,9 +131,7 @@
|
||||||
"display_name": "ScrapeGraph API Key",
|
"display_name": "ScrapeGraph API Key",
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"info": "The API key to use ScrapeGraph API.",
|
"info": "The API key to use ScrapeGraph API.",
|
||||||
"input_types": [
|
"input_types": [],
|
||||||
"Message"
|
|
||||||
],
|
|
||||||
"load_from_db": true,
|
"load_from_db": true,
|
||||||
"name": "api_key",
|
"name": "api_key",
|
||||||
"password": true,
|
"password": true,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -205,9 +205,7 @@
|
||||||
"display_name": "Apify Token",
|
"display_name": "Apify Token",
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"info": "The API token for the Apify account.",
|
"info": "The API token for the Apify account.",
|
||||||
"input_types": [
|
"input_types": [],
|
||||||
"Message"
|
|
||||||
],
|
|
||||||
"load_from_db": true,
|
"load_from_db": true,
|
||||||
"name": "apify_token",
|
"name": "apify_token",
|
||||||
"password": true,
|
"password": true,
|
||||||
|
|
@ -409,9 +407,7 @@
|
||||||
"display_name": "Apify Token",
|
"display_name": "Apify Token",
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"info": "The API token for the Apify account.",
|
"info": "The API token for the Apify account.",
|
||||||
"input_types": [
|
"input_types": [],
|
||||||
"Message"
|
|
||||||
],
|
|
||||||
"load_from_db": true,
|
"load_from_db": true,
|
||||||
"name": "apify_token",
|
"name": "apify_token",
|
||||||
"password": true,
|
"password": true,
|
||||||
|
|
|
||||||
|
|
@ -1435,9 +1435,7 @@
|
||||||
"display_name": "SearchAPI API Key",
|
"display_name": "SearchAPI API Key",
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"info": "",
|
"info": "",
|
||||||
"input_types": [
|
"input_types": [],
|
||||||
"Message"
|
|
||||||
],
|
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"name": "api_key",
|
"name": "api_key",
|
||||||
"password": true,
|
"password": true,
|
||||||
|
|
|
||||||
|
|
@ -323,7 +323,7 @@
|
||||||
"category": "helpers",
|
"category": "helpers",
|
||||||
"conditional_paths": [],
|
"conditional_paths": [],
|
||||||
"custom_fields": {},
|
"custom_fields": {},
|
||||||
"description": "Runs a language model over each row of a DataFrame's text column and returns a new DataFrame with two columns: 'text_input' (the original text) and 'model_response' containing the model's response.",
|
"description": "Runs an LLM over each row of a DataFrame's column. If no column is set, the entire row is passed.",
|
||||||
"display_name": "Batch Run",
|
"display_name": "Batch Run",
|
||||||
"documentation": "",
|
"documentation": "",
|
||||||
"edited": false,
|
"edited": false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue