From 8cabedae5525876b1608e485e310c93d6e5f59ac Mon Sep 17 00:00:00 2001 From: May Ramati Kroitero <105562192+MayRamati@users.noreply.github.com> Date: Wed, 7 May 2025 01:38:14 +0300 Subject: [PATCH] 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 Co-authored-by: Gabriel Luiz Freitas Almeida --- .../langflow/components/tools/__init__.py | 6 +- .../base/langflow/components/tools/tavily.py | 148 -------- .../components/tools/tavily_extract.py | 119 ++++++ .../components/tools/tavily_search.py | 281 +++++++------- .../components/tools/tavily_search_tool.py | 344 ++++++++++++++++++ .../starter_projects/Diet Analysis.json | 2 +- .../starter_projects/Financial Agent.json | 112 +++++- .../starter_projects/Gmail Agent.json | 4 +- .../Instagram Copywriter.json | 112 +++++- .../starter_projects/Invoice Summarizer.json | 4 +- .../starter_projects/Market Research.json | 112 +++++- .../starter_projects/News Aggregator.json | 6 +- .../starter_projects/Pokédex Agent.json | 2 +- .../starter_projects/Price Deal Finder.json | 118 +++++- .../starter_projects/Research Agent.json | 112 +++++- .../starter_projects/Search agent.json | 4 +- .../Sequential Tasks Agents.json | 112 +++++- .../starter_projects/Social Media Agent.json | 8 +- .../Travel Planning Agents.json | 4 +- .../starter_projects/Youtube Analysis.json | 2 +- 20 files changed, 1255 insertions(+), 357 deletions(-) delete mode 100644 src/backend/base/langflow/components/tools/tavily.py create mode 100644 src/backend/base/langflow/components/tools/tavily_extract.py create mode 100644 src/backend/base/langflow/components/tools/tavily_search_tool.py diff --git a/src/backend/base/langflow/components/tools/__init__.py b/src/backend/base/langflow/components/tools/__init__.py index e7b67cfbc..4294a3dc3 100644 --- a/src/backend/base/langflow/components/tools/__init__.py +++ b/src/backend/base/langflow/components/tools/__init__.py @@ -22,8 +22,9 @@ from .search_api import SearchAPIComponent from .searxng import SearXNGToolComponent from .serp import SerpComponent from .serp_api import SerpAPIComponent -from .tavily import TavilySearchComponent -from .tavily_search import TavilySearchToolComponent +from .tavily_extract import TavilyExtractComponent +from .tavily_search import TavilySearchComponent +from .tavily_search_tool import TavilySearchToolComponent from .wikidata import WikidataComponent from .wikidata_api import WikidataAPIComponent from .wikipedia import WikipediaComponent @@ -60,6 +61,7 @@ __all__ = [ "SearchComponent", "SerpAPIComponent", "SerpComponent", + "TavilyExtractComponent", "TavilySearchComponent", "TavilySearchToolComponent", "WikidataAPIComponent", diff --git a/src/backend/base/langflow/components/tools/tavily.py b/src/backend/base/langflow/components/tools/tavily.py deleted file mode 100644 index 0838f2f7f..000000000 --- a/src/backend/base/langflow/components/tools/tavily.py +++ /dev/null @@ -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) diff --git a/src/backend/base/langflow/components/tools/tavily_extract.py b/src/backend/base/langflow/components/tools/tavily_extract.py new file mode 100644 index 000000000..bbb16c722 --- /dev/null +++ b/src/backend/base/langflow/components/tools/tavily_extract.py @@ -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) diff --git a/src/backend/base/langflow/components/tools/tavily_search.py b/src/backend/base/langflow/components/tools/tavily_search.py index da755a0bb..2e12e9145 100644 --- a/src/backend/base/langflow/components/tools/tavily_search.py +++ b/src/backend/base/langflow/components/tools/tavily_search.py @@ -1,47 +1,18 @@ -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.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 TavilySearchDepth(Enum): - BASIC = "basic" - ADVANCED = "advanced" - - -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. -""" +class TavilySearchComponent(Component): + display_name = "Tavily Search API" + description = """**Tavily Search** is a search engine optimized for LLMs and RAG, \ + aimed at efficient, quick, and persistent search results.""" icon = "TavilyIcon" - name = "TavilyAISearch" - documentation = "https://docs.tavily.com/" - legacy = True inputs = [ SecretStrInput( @@ -54,21 +25,36 @@ Note: Check 'Advanced' for all options. 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=list(TavilySearchDepth), - value=TavilySearchDepth.ADVANCED, + options=["basic", "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, ), DropdownInput( name="topic", display_name="Search Topic", info="The category of the search.", - options=list(TavilySearchTopic), - value=TavilySearchTopic.GENERAL, + options=["general", "news"], + 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, ), IntInput( @@ -78,13 +64,6 @@ Note: Check 'Advanced' for all options. 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", @@ -92,115 +71,145 @@ Note: Check 'Advanced' for all options. 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=["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]: - # Convert string values to enum instances with validation + 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: - 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})] + # Only process domains if they're provided + include_domains = None + exclude_domains = None - 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})] + if self.include_domains: + include_domains = [domain.strip() for domain in self.include_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, - ) + if self.exclude_domains: + exclude_domains = [domain.strip() for domain in self.exclude_domains.split(",") if domain.strip()] - 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" 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, + "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, + "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.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", []) - ] + data_results = [] - if include_answer and search_results.get("answer"): - data_results.insert(0, Data(data={"answer": search_results["answer"]})) + if self.include_answer and search_results.get("answer"): + data_results.append(Data(text=search_results["answer"])) - if include_images and search_results.get("images"): - data_results.append(Data(data={"images": search_results["images"]})) + for result in search_results.get("results", []): + 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: - 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 + if self.include_images and search_results.get("images"): + data_results.append(Data(text="Images found", data={"images": search_results["images"]})) + + except httpx.TimeoutException: + error_message = "Request timed out (90s). Please try again or adjust parameters." + 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 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) diff --git a/src/backend/base/langflow/components/tools/tavily_search_tool.py b/src/backend/base/langflow/components/tools/tavily_search_tool.py new file mode 100644 index 000000000..2515e2c9e --- /dev/null +++ b/src/backend/base/langflow/components/tools/tavily_search_tool.py @@ -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 diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Diet Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Diet Analysis.json index 6bc755111..7d5c09591 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Diet Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Diet Analysis.json @@ -410,7 +410,7 @@ "custom_fields": { "template": [] }, - "description": "", + "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Financial Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Financial Agent.json index 562b81e81..ca69c23f9 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Financial Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Financial Agent.json @@ -632,7 +632,7 @@ "beta": false, "conditional_paths": [], "custom_fields": {}, - "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", + "description": "**Tavily Search** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "display_name": "Tavily AI Search", "documentation": "", "edited": false, @@ -679,9 +679,7 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": false, "name": "api_key", "password": true, @@ -692,6 +690,24 @@ "type": "str", "value": "" }, + "chunks_per_source": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Chunks Per Source", + "dynamic": false, + "info": "The number of content chunks to retrieve from each source (1-3). Only works with advanced search.", + "list": false, + "list_add_label": "Add More", + "name": "chunks_per_source", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 3 + }, "code": { "advanced": true, "dynamic": true, @@ -708,7 +724,48 @@ "show": true, "title_case": false, "type": "code", - "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily AI Search\"\n description = \"\"\"**Tavily AI** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to include in the search results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None,\n advanced=True,\n combobox=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"time_range\": self.time_range,\n }\n\n with httpx.Client() as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n data_results.append(\n Data(\n text=content,\n data={\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n },\n )\n )\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily Search API\"\n description = \"\"\"**Tavily Search** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n IntInput(\n name=\"chunks_per_source\",\n display_name=\"Chunks Per Source\",\n info=(\"The number of content chunks to retrieve from each source (1-3). Only works with advanced search.\"),\n value=3,\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n IntInput(\n name=\"days\",\n display_name=\"Days\",\n info=\"Number of days back from current date to include. Only available with news topic.\",\n value=7,\n advanced=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to filter results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None, # Default to None to make it optional\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"include_domains\",\n display_name=\"Include Domains\",\n info=\"Comma-separated list of domains to include in the search results.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"exclude_domains\",\n display_name=\"Exclude Domains\",\n info=\"Comma-separated list of domains to exclude from the search results.\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_raw_content\",\n display_name=\"Include Raw Content\",\n info=\"Include the cleaned and parsed HTML content of each search result.\",\n value=False,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n # Only process domains if they're provided\n include_domains = None\n exclude_domains = None\n\n if self.include_domains:\n include_domains = [domain.strip() for domain in self.include_domains.split(\",\") if domain.strip()]\n\n if self.exclude_domains:\n exclude_domains = [domain.strip() for domain in self.exclude_domains.split(\",\") if domain.strip()]\n\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"include_raw_content\": self.include_raw_content,\n \"days\": self.days,\n \"time_range\": self.time_range,\n }\n\n # Only add domains to payload if they exist and have values\n if include_domains:\n payload[\"include_domains\"] = include_domains\n if exclude_domains:\n payload[\"exclude_domains\"] = exclude_domains\n\n # Add conditional parameters only if they should be included\n if self.search_depth == \"advanced\" and self.chunks_per_source:\n payload[\"chunks_per_source\"] = self.chunks_per_source\n\n if self.topic == \"news\" and self.days:\n payload[\"days\"] = int(self.days) # Ensure days is an integer\n\n # Add time_range if it's set\n if hasattr(self, \"time_range\") and self.time_range:\n payload[\"time_range\"] = self.time_range\n\n # Add timeout handling\n with httpx.Client(timeout=90.0) as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n result_data = {\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n }\n if self.include_raw_content:\n result_data[\"raw_content\"] = result.get(\"raw_content\")\n\n data_results.append(Data(text=content, data=result_data))\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n\n except httpx.TimeoutException:\n error_message = \"Request timed out (90s). Please try again or adjust parameters.\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + }, + "days": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Days", + "dynamic": false, + "info": "Number of days back from current date to include. Only available with news topic.", + "list": false, + "list_add_label": "Add More", + "name": "days", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 7 + }, + "exclude_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Exclude Domains", + "dynamic": false, + "info": "Comma-separated list of domains to exclude from the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "exclude_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" }, "include_answer": { "_input_type": "BoolInput", @@ -728,6 +785,29 @@ "type": "bool", "value": true }, + "include_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Include Domains", + "dynamic": false, + "info": "Comma-separated list of domains to include in the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "include_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "include_images": { "_input_type": "BoolInput", "advanced": true, @@ -746,6 +826,24 @@ "type": "bool", "value": true }, + "include_raw_content": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Include Raw Content", + "dynamic": false, + "info": "Include the cleaned and parsed HTML content of each search result.", + "list": false, + "list_add_label": "Add More", + "name": "include_raw_content", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, "max_results": { "_input_type": "IntInput", "advanced": true, @@ -813,11 +911,11 @@ "time_range": { "_input_type": "DropdownInput", "advanced": true, - "combobox": true, + "combobox": false, "dialog_inputs": {}, "display_name": "Time Range", "dynamic": false, - "info": "The time range back from the current date to include in the search results.", + "info": "The time range back from the current date to filter results.", "name": "time_range", "options": [ "day", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json index 753029d56..c3c87a097 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json @@ -1484,9 +1484,7 @@ "display_name": "Composio API Key", "dynamic": false, "info": "Refer to https://docs.composio.dev/faq/api_key/api_key", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "api_key", "password": true, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json index 8708f4935..d0de27edb 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json @@ -2292,7 +2292,7 @@ "beta": false, "conditional_paths": [], "custom_fields": {}, - "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", + "description": "**Tavily Search** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "display_name": "Tavily AI Search", "documentation": "", "edited": false, @@ -2339,9 +2339,7 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "api_key", "password": true, @@ -2352,6 +2350,24 @@ "type": "str", "value": "TAVILY_API_KEY" }, + "chunks_per_source": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Chunks Per Source", + "dynamic": false, + "info": "The number of content chunks to retrieve from each source (1-3). Only works with advanced search.", + "list": false, + "list_add_label": "Add More", + "name": "chunks_per_source", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 3 + }, "code": { "advanced": true, "dynamic": true, @@ -2368,7 +2384,48 @@ "show": true, "title_case": false, "type": "code", - "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily AI Search\"\n description = \"\"\"**Tavily AI** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to include in the search results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None,\n advanced=True,\n combobox=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"time_range\": self.time_range,\n }\n\n with httpx.Client() as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n data_results.append(\n Data(\n text=content,\n data={\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n },\n )\n )\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily Search API\"\n description = \"\"\"**Tavily Search** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n IntInput(\n name=\"chunks_per_source\",\n display_name=\"Chunks Per Source\",\n info=(\"The number of content chunks to retrieve from each source (1-3). Only works with advanced search.\"),\n value=3,\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n IntInput(\n name=\"days\",\n display_name=\"Days\",\n info=\"Number of days back from current date to include. Only available with news topic.\",\n value=7,\n advanced=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to filter results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None, # Default to None to make it optional\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"include_domains\",\n display_name=\"Include Domains\",\n info=\"Comma-separated list of domains to include in the search results.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"exclude_domains\",\n display_name=\"Exclude Domains\",\n info=\"Comma-separated list of domains to exclude from the search results.\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_raw_content\",\n display_name=\"Include Raw Content\",\n info=\"Include the cleaned and parsed HTML content of each search result.\",\n value=False,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n # Only process domains if they're provided\n include_domains = None\n exclude_domains = None\n\n if self.include_domains:\n include_domains = [domain.strip() for domain in self.include_domains.split(\",\") if domain.strip()]\n\n if self.exclude_domains:\n exclude_domains = [domain.strip() for domain in self.exclude_domains.split(\",\") if domain.strip()]\n\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"include_raw_content\": self.include_raw_content,\n \"days\": self.days,\n \"time_range\": self.time_range,\n }\n\n # Only add domains to payload if they exist and have values\n if include_domains:\n payload[\"include_domains\"] = include_domains\n if exclude_domains:\n payload[\"exclude_domains\"] = exclude_domains\n\n # Add conditional parameters only if they should be included\n if self.search_depth == \"advanced\" and self.chunks_per_source:\n payload[\"chunks_per_source\"] = self.chunks_per_source\n\n if self.topic == \"news\" and self.days:\n payload[\"days\"] = int(self.days) # Ensure days is an integer\n\n # Add time_range if it's set\n if hasattr(self, \"time_range\") and self.time_range:\n payload[\"time_range\"] = self.time_range\n\n # Add timeout handling\n with httpx.Client(timeout=90.0) as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n result_data = {\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n }\n if self.include_raw_content:\n result_data[\"raw_content\"] = result.get(\"raw_content\")\n\n data_results.append(Data(text=content, data=result_data))\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n\n except httpx.TimeoutException:\n error_message = \"Request timed out (90s). Please try again or adjust parameters.\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + }, + "days": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Days", + "dynamic": false, + "info": "Number of days back from current date to include. Only available with news topic.", + "list": false, + "list_add_label": "Add More", + "name": "days", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 7 + }, + "exclude_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Exclude Domains", + "dynamic": false, + "info": "Comma-separated list of domains to exclude from the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "exclude_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" }, "include_answer": { "_input_type": "BoolInput", @@ -2388,6 +2445,29 @@ "type": "bool", "value": true }, + "include_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Include Domains", + "dynamic": false, + "info": "Comma-separated list of domains to include in the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "include_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "include_images": { "_input_type": "BoolInput", "advanced": true, @@ -2406,6 +2486,24 @@ "type": "bool", "value": true }, + "include_raw_content": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Include Raw Content", + "dynamic": false, + "info": "Include the cleaned and parsed HTML content of each search result.", + "list": false, + "list_add_label": "Add More", + "name": "include_raw_content", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, "max_results": { "_input_type": "IntInput", "advanced": true, @@ -2473,11 +2571,11 @@ "time_range": { "_input_type": "DropdownInput", "advanced": true, - "combobox": true, + "combobox": false, "dialog_inputs": {}, "display_name": "Time Range", "dynamic": false, - "info": "The time range back from the current date to include in the search results.", + "info": "The time range back from the current date to filter results.", "name": "time_range", "options": [ "day", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json b/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json index 62a97a349..c57a1c89f 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Invoice Summarizer.json @@ -735,9 +735,7 @@ "display_name": "Needle API Key", "dynamic": false, "info": "Your Needle API key.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": false, "name": "needle_api_key", "password": true, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json index 81060ee8f..970c375c6 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json @@ -1799,7 +1799,7 @@ "beta": false, "conditional_paths": [], "custom_fields": {}, - "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", + "description": "**Tavily Search** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "display_name": "Tavily AI Search", "documentation": "", "edited": false, @@ -1847,9 +1847,7 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": false, "name": "api_key", "password": true, @@ -1860,6 +1858,24 @@ "type": "str", "value": "TAVILY_API_KEY" }, + "chunks_per_source": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Chunks Per Source", + "dynamic": false, + "info": "The number of content chunks to retrieve from each source (1-3). Only works with advanced search.", + "list": false, + "list_add_label": "Add More", + "name": "chunks_per_source", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 3 + }, "code": { "advanced": true, "dynamic": true, @@ -1876,7 +1892,48 @@ "show": true, "title_case": false, "type": "code", - "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily AI Search\"\n description = \"\"\"**Tavily AI** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to include in the search results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None,\n advanced=True,\n combobox=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"time_range\": self.time_range,\n }\n\n with httpx.Client() as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n data_results.append(\n Data(\n text=content,\n data={\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n },\n )\n )\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily Search API\"\n description = \"\"\"**Tavily Search** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n IntInput(\n name=\"chunks_per_source\",\n display_name=\"Chunks Per Source\",\n info=(\"The number of content chunks to retrieve from each source (1-3). Only works with advanced search.\"),\n value=3,\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n IntInput(\n name=\"days\",\n display_name=\"Days\",\n info=\"Number of days back from current date to include. Only available with news topic.\",\n value=7,\n advanced=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to filter results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None, # Default to None to make it optional\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"include_domains\",\n display_name=\"Include Domains\",\n info=\"Comma-separated list of domains to include in the search results.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"exclude_domains\",\n display_name=\"Exclude Domains\",\n info=\"Comma-separated list of domains to exclude from the search results.\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_raw_content\",\n display_name=\"Include Raw Content\",\n info=\"Include the cleaned and parsed HTML content of each search result.\",\n value=False,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n # Only process domains if they're provided\n include_domains = None\n exclude_domains = None\n\n if self.include_domains:\n include_domains = [domain.strip() for domain in self.include_domains.split(\",\") if domain.strip()]\n\n if self.exclude_domains:\n exclude_domains = [domain.strip() for domain in self.exclude_domains.split(\",\") if domain.strip()]\n\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"include_raw_content\": self.include_raw_content,\n \"days\": self.days,\n \"time_range\": self.time_range,\n }\n\n # Only add domains to payload if they exist and have values\n if include_domains:\n payload[\"include_domains\"] = include_domains\n if exclude_domains:\n payload[\"exclude_domains\"] = exclude_domains\n\n # Add conditional parameters only if they should be included\n if self.search_depth == \"advanced\" and self.chunks_per_source:\n payload[\"chunks_per_source\"] = self.chunks_per_source\n\n if self.topic == \"news\" and self.days:\n payload[\"days\"] = int(self.days) # Ensure days is an integer\n\n # Add time_range if it's set\n if hasattr(self, \"time_range\") and self.time_range:\n payload[\"time_range\"] = self.time_range\n\n # Add timeout handling\n with httpx.Client(timeout=90.0) as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n result_data = {\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n }\n if self.include_raw_content:\n result_data[\"raw_content\"] = result.get(\"raw_content\")\n\n data_results.append(Data(text=content, data=result_data))\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n\n except httpx.TimeoutException:\n error_message = \"Request timed out (90s). Please try again or adjust parameters.\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + }, + "days": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Days", + "dynamic": false, + "info": "Number of days back from current date to include. Only available with news topic.", + "list": false, + "list_add_label": "Add More", + "name": "days", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 7 + }, + "exclude_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Exclude Domains", + "dynamic": false, + "info": "Comma-separated list of domains to exclude from the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "exclude_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" }, "include_answer": { "_input_type": "BoolInput", @@ -1896,6 +1953,29 @@ "type": "bool", "value": true }, + "include_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Include Domains", + "dynamic": false, + "info": "Comma-separated list of domains to include in the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "include_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "include_images": { "_input_type": "BoolInput", "advanced": true, @@ -1914,6 +1994,24 @@ "type": "bool", "value": true }, + "include_raw_content": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Include Raw Content", + "dynamic": false, + "info": "Include the cleaned and parsed HTML content of each search result.", + "list": false, + "list_add_label": "Add More", + "name": "include_raw_content", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, "max_results": { "_input_type": "IntInput", "advanced": true, @@ -1981,11 +2079,11 @@ "time_range": { "_input_type": "DropdownInput", "advanced": true, - "combobox": true, + "combobox": false, "dialog_inputs": {}, "display_name": "Time Range", "dynamic": false, - "info": "The time range back from the current date to include in the search results.", + "info": "The time range back from the current date to filter results.", "name": "time_range", "options": [ "day", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json b/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json index ff713bb1b..26f9537f2 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json @@ -232,9 +232,7 @@ "display_name": "API Key", "dynamic": false, "info": "Your AgentQL API key from dev.agentql.com", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "api_key", "password": true, @@ -301,7 +299,7 @@ }, "is_stealth_mode_enabled": { "_input_type": "BoolInput", - "advanced": false, + "advanced": true, "display_name": "Enable Stealth Mode (Beta)", "dynamic": false, "info": "Enable experimental anti-bot evasion strategies. May not work for all websites at all times.", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json index 268c71046..746cf1fe9 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Pokédex Agent.json @@ -938,7 +938,7 @@ "multiline": true, "name": "curl", "placeholder": "", - "real_time_refresh": false, + "real_time_refresh": true, "required": false, "show": true, "title_case": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json b/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json index 5b48a684d..68b3295b3 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json @@ -751,7 +751,7 @@ "beta": false, "conditional_paths": [], "custom_fields": {}, - "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", + "description": "**Tavily Search** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "display_name": "Tavily AI Search", "documentation": "", "edited": false, @@ -798,9 +798,7 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "api_key", "password": true, @@ -811,6 +809,24 @@ "type": "str", "value": "TAVILY_API_KEY" }, + "chunks_per_source": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Chunks Per Source", + "dynamic": false, + "info": "The number of content chunks to retrieve from each source (1-3). Only works with advanced search.", + "list": false, + "list_add_label": "Add More", + "name": "chunks_per_source", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 3 + }, "code": { "advanced": true, "dynamic": true, @@ -827,7 +843,48 @@ "show": true, "title_case": false, "type": "code", - "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily AI Search\"\n description = \"\"\"**Tavily AI** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to include in the search results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None,\n advanced=True,\n combobox=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"time_range\": self.time_range,\n }\n\n with httpx.Client() as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n data_results.append(\n Data(\n text=content,\n data={\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n },\n )\n )\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily Search API\"\n description = \"\"\"**Tavily Search** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n IntInput(\n name=\"chunks_per_source\",\n display_name=\"Chunks Per Source\",\n info=(\"The number of content chunks to retrieve from each source (1-3). Only works with advanced search.\"),\n value=3,\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n IntInput(\n name=\"days\",\n display_name=\"Days\",\n info=\"Number of days back from current date to include. Only available with news topic.\",\n value=7,\n advanced=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to filter results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None, # Default to None to make it optional\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"include_domains\",\n display_name=\"Include Domains\",\n info=\"Comma-separated list of domains to include in the search results.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"exclude_domains\",\n display_name=\"Exclude Domains\",\n info=\"Comma-separated list of domains to exclude from the search results.\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_raw_content\",\n display_name=\"Include Raw Content\",\n info=\"Include the cleaned and parsed HTML content of each search result.\",\n value=False,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n # Only process domains if they're provided\n include_domains = None\n exclude_domains = None\n\n if self.include_domains:\n include_domains = [domain.strip() for domain in self.include_domains.split(\",\") if domain.strip()]\n\n if self.exclude_domains:\n exclude_domains = [domain.strip() for domain in self.exclude_domains.split(\",\") if domain.strip()]\n\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"include_raw_content\": self.include_raw_content,\n \"days\": self.days,\n \"time_range\": self.time_range,\n }\n\n # Only add domains to payload if they exist and have values\n if include_domains:\n payload[\"include_domains\"] = include_domains\n if exclude_domains:\n payload[\"exclude_domains\"] = exclude_domains\n\n # Add conditional parameters only if they should be included\n if self.search_depth == \"advanced\" and self.chunks_per_source:\n payload[\"chunks_per_source\"] = self.chunks_per_source\n\n if self.topic == \"news\" and self.days:\n payload[\"days\"] = int(self.days) # Ensure days is an integer\n\n # Add time_range if it's set\n if hasattr(self, \"time_range\") and self.time_range:\n payload[\"time_range\"] = self.time_range\n\n # Add timeout handling\n with httpx.Client(timeout=90.0) as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n result_data = {\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n }\n if self.include_raw_content:\n result_data[\"raw_content\"] = result.get(\"raw_content\")\n\n data_results.append(Data(text=content, data=result_data))\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n\n except httpx.TimeoutException:\n error_message = \"Request timed out (90s). Please try again or adjust parameters.\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + }, + "days": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Days", + "dynamic": false, + "info": "Number of days back from current date to include. Only available with news topic.", + "list": false, + "list_add_label": "Add More", + "name": "days", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 7 + }, + "exclude_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Exclude Domains", + "dynamic": false, + "info": "Comma-separated list of domains to exclude from the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "exclude_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" }, "include_answer": { "_input_type": "BoolInput", @@ -847,6 +904,29 @@ "type": "bool", "value": true }, + "include_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Include Domains", + "dynamic": false, + "info": "Comma-separated list of domains to include in the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "include_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "include_images": { "_input_type": "BoolInput", "advanced": true, @@ -865,6 +945,24 @@ "type": "bool", "value": true }, + "include_raw_content": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Include Raw Content", + "dynamic": false, + "info": "Include the cleaned and parsed HTML content of each search result.", + "list": false, + "list_add_label": "Add More", + "name": "include_raw_content", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, "max_results": { "_input_type": "IntInput", "advanced": true, @@ -932,11 +1030,11 @@ "time_range": { "_input_type": "DropdownInput", "advanced": true, - "combobox": true, + "combobox": false, "dialog_inputs": {}, "display_name": "Time Range", "dynamic": false, - "info": "The time range back from the current date to include in the search results.", + "info": "The time range back from the current date to filter results.", "name": "time_range", "options": [ "day", @@ -1155,9 +1253,7 @@ "display_name": "API Key", "dynamic": false, "info": "Your AgentQL API key from dev.agentql.com", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "api_key", "password": true, @@ -1224,7 +1320,7 @@ }, "is_stealth_mode_enabled": { "_input_type": "BoolInput", - "advanced": false, + "advanced": true, "display_name": "Enable Stealth Mode (Beta)", "dynamic": false, "info": "Enable experimental anti-bot evasion strategies. May not work for all websites at all times.", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json index dd70bad31..f361c538f 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Research Agent.json @@ -1868,7 +1868,7 @@ "beta": false, "conditional_paths": [], "custom_fields": {}, - "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", + "description": "**Tavily Search** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "display_name": "Tavily AI Search", "documentation": "", "edited": false, @@ -1915,9 +1915,7 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "api_key", "password": true, @@ -1928,6 +1926,24 @@ "type": "str", "value": "TAVILY_API_KEY" }, + "chunks_per_source": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Chunks Per Source", + "dynamic": false, + "info": "The number of content chunks to retrieve from each source (1-3). Only works with advanced search.", + "list": false, + "list_add_label": "Add More", + "name": "chunks_per_source", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 3 + }, "code": { "advanced": true, "dynamic": true, @@ -1944,7 +1960,48 @@ "show": true, "title_case": false, "type": "code", - "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily AI Search\"\n description = \"\"\"**Tavily AI** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to include in the search results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None,\n advanced=True,\n combobox=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"time_range\": self.time_range,\n }\n\n with httpx.Client() as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n data_results.append(\n Data(\n text=content,\n data={\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n },\n )\n )\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily Search API\"\n description = \"\"\"**Tavily Search** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n IntInput(\n name=\"chunks_per_source\",\n display_name=\"Chunks Per Source\",\n info=(\"The number of content chunks to retrieve from each source (1-3). Only works with advanced search.\"),\n value=3,\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n IntInput(\n name=\"days\",\n display_name=\"Days\",\n info=\"Number of days back from current date to include. Only available with news topic.\",\n value=7,\n advanced=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to filter results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None, # Default to None to make it optional\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"include_domains\",\n display_name=\"Include Domains\",\n info=\"Comma-separated list of domains to include in the search results.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"exclude_domains\",\n display_name=\"Exclude Domains\",\n info=\"Comma-separated list of domains to exclude from the search results.\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_raw_content\",\n display_name=\"Include Raw Content\",\n info=\"Include the cleaned and parsed HTML content of each search result.\",\n value=False,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n # Only process domains if they're provided\n include_domains = None\n exclude_domains = None\n\n if self.include_domains:\n include_domains = [domain.strip() for domain in self.include_domains.split(\",\") if domain.strip()]\n\n if self.exclude_domains:\n exclude_domains = [domain.strip() for domain in self.exclude_domains.split(\",\") if domain.strip()]\n\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"include_raw_content\": self.include_raw_content,\n \"days\": self.days,\n \"time_range\": self.time_range,\n }\n\n # Only add domains to payload if they exist and have values\n if include_domains:\n payload[\"include_domains\"] = include_domains\n if exclude_domains:\n payload[\"exclude_domains\"] = exclude_domains\n\n # Add conditional parameters only if they should be included\n if self.search_depth == \"advanced\" and self.chunks_per_source:\n payload[\"chunks_per_source\"] = self.chunks_per_source\n\n if self.topic == \"news\" and self.days:\n payload[\"days\"] = int(self.days) # Ensure days is an integer\n\n # Add time_range if it's set\n if hasattr(self, \"time_range\") and self.time_range:\n payload[\"time_range\"] = self.time_range\n\n # Add timeout handling\n with httpx.Client(timeout=90.0) as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n result_data = {\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n }\n if self.include_raw_content:\n result_data[\"raw_content\"] = result.get(\"raw_content\")\n\n data_results.append(Data(text=content, data=result_data))\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n\n except httpx.TimeoutException:\n error_message = \"Request timed out (90s). Please try again or adjust parameters.\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + }, + "days": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Days", + "dynamic": false, + "info": "Number of days back from current date to include. Only available with news topic.", + "list": false, + "list_add_label": "Add More", + "name": "days", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 7 + }, + "exclude_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Exclude Domains", + "dynamic": false, + "info": "Comma-separated list of domains to exclude from the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "exclude_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" }, "include_answer": { "_input_type": "BoolInput", @@ -1964,6 +2021,29 @@ "type": "bool", "value": true }, + "include_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Include Domains", + "dynamic": false, + "info": "Comma-separated list of domains to include in the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "include_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "include_images": { "_input_type": "BoolInput", "advanced": true, @@ -1982,6 +2062,24 @@ "type": "bool", "value": true }, + "include_raw_content": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Include Raw Content", + "dynamic": false, + "info": "Include the cleaned and parsed HTML content of each search result.", + "list": false, + "list_add_label": "Add More", + "name": "include_raw_content", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, "max_results": { "_input_type": "IntInput", "advanced": true, @@ -2049,11 +2147,11 @@ "time_range": { "_input_type": "DropdownInput", "advanced": true, - "combobox": true, + "combobox": false, "dialog_inputs": {}, "display_name": "Time Range", "dynamic": false, - "info": "The time range back from the current date to include in the search results.", + "info": "The time range back from the current date to filter results.", "name": "time_range", "options": [ "day", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json index b56c68f20..c364d3888 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Search agent.json @@ -131,9 +131,7 @@ "display_name": "ScrapeGraph API Key", "dynamic": false, "info": "The API key to use ScrapeGraph API.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "api_key", "password": true, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json index 591a7461b..a506c051d 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json @@ -3572,7 +3572,7 @@ "beta": false, "conditional_paths": [], "custom_fields": {}, - "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", + "description": "**Tavily Search** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.", "display_name": "Tavily AI Search", "documentation": "", "edited": false, @@ -3619,9 +3619,7 @@ "display_name": "Tavily API Key", "dynamic": false, "info": "Your Tavily API Key.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "api_key", "password": true, @@ -3632,6 +3630,24 @@ "type": "str", "value": "TAVILY_API_KEY" }, + "chunks_per_source": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Chunks Per Source", + "dynamic": false, + "info": "The number of content chunks to retrieve from each source (1-3). Only works with advanced search.", + "list": false, + "list_add_label": "Add More", + "name": "chunks_per_source", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 3 + }, "code": { "advanced": true, "dynamic": true, @@ -3648,7 +3664,48 @@ "show": true, "title_case": false, "type": "code", - "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily AI Search\"\n description = \"\"\"**Tavily AI** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to include in the search results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None,\n advanced=True,\n combobox=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"time_range\": self.time_range,\n }\n\n with httpx.Client() as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n data_results.append(\n Data(\n text=content,\n data={\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n },\n )\n )\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily Search API\"\n description = \"\"\"**Tavily Search** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n IntInput(\n name=\"chunks_per_source\",\n display_name=\"Chunks Per Source\",\n info=(\"The number of content chunks to retrieve from each source (1-3). Only works with advanced search.\"),\n value=3,\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n IntInput(\n name=\"days\",\n display_name=\"Days\",\n info=\"Number of days back from current date to include. Only available with news topic.\",\n value=7,\n advanced=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to filter results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None, # Default to None to make it optional\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"include_domains\",\n display_name=\"Include Domains\",\n info=\"Comma-separated list of domains to include in the search results.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"exclude_domains\",\n display_name=\"Exclude Domains\",\n info=\"Comma-separated list of domains to exclude from the search results.\",\n advanced=True,\n ),\n BoolInput(\n name=\"include_raw_content\",\n display_name=\"Include Raw Content\",\n info=\"Include the cleaned and parsed HTML content of each search result.\",\n value=False,\n advanced=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 fetch_content(self) -> list[Data]:\n try:\n # Only process domains if they're provided\n include_domains = None\n exclude_domains = None\n\n if self.include_domains:\n include_domains = [domain.strip() for domain in self.include_domains.split(\",\") if domain.strip()]\n\n if self.exclude_domains:\n exclude_domains = [domain.strip() for domain in self.exclude_domains.split(\",\") if domain.strip()]\n\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"include_raw_content\": self.include_raw_content,\n \"days\": self.days,\n \"time_range\": self.time_range,\n }\n\n # Only add domains to payload if they exist and have values\n if include_domains:\n payload[\"include_domains\"] = include_domains\n if exclude_domains:\n payload[\"exclude_domains\"] = exclude_domains\n\n # Add conditional parameters only if they should be included\n if self.search_depth == \"advanced\" and self.chunks_per_source:\n payload[\"chunks_per_source\"] = self.chunks_per_source\n\n if self.topic == \"news\" and self.days:\n payload[\"days\"] = int(self.days) # Ensure days is an integer\n\n # Add time_range if it's set\n if hasattr(self, \"time_range\") and self.time_range:\n payload[\"time_range\"] = self.time_range\n\n # Add timeout handling\n with httpx.Client(timeout=90.0) as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n result_data = {\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n }\n if self.include_raw_content:\n result_data[\"raw_content\"] = result.get(\"raw_content\")\n\n data_results.append(Data(text=content, data=result_data))\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n\n except httpx.TimeoutException:\n error_message = \"Request timed out (90s). Please try again or adjust parameters.\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + }, + "days": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Days", + "dynamic": false, + "info": "Number of days back from current date to include. Only available with news topic.", + "list": false, + "list_add_label": "Add More", + "name": "days", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 7 + }, + "exclude_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Exclude Domains", + "dynamic": false, + "info": "Comma-separated list of domains to exclude from the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "exclude_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" }, "include_answer": { "_input_type": "BoolInput", @@ -3668,6 +3725,29 @@ "type": "bool", "value": true }, + "include_domains": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Include Domains", + "dynamic": false, + "info": "Comma-separated list of domains to include in the search results.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "include_domains", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, "include_images": { "_input_type": "BoolInput", "advanced": true, @@ -3686,6 +3766,24 @@ "type": "bool", "value": true }, + "include_raw_content": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Include Raw Content", + "dynamic": false, + "info": "Include the cleaned and parsed HTML content of each search result.", + "list": false, + "list_add_label": "Add More", + "name": "include_raw_content", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, "max_results": { "_input_type": "IntInput", "advanced": true, @@ -3753,11 +3851,11 @@ "time_range": { "_input_type": "DropdownInput", "advanced": true, - "combobox": true, + "combobox": false, "dialog_inputs": {}, "display_name": "Time Range", "dynamic": false, - "info": "The time range back from the current date to include in the search results.", + "info": "The time range back from the current date to filter results.", "name": "time_range", "options": [ "day", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json index fed5bd6ad..02056a37b 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json @@ -205,9 +205,7 @@ "display_name": "Apify Token", "dynamic": false, "info": "The API token for the Apify account.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "apify_token", "password": true, @@ -409,9 +407,7 @@ "display_name": "Apify Token", "dynamic": false, "info": "The API token for the Apify account.", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": true, "name": "apify_token", "password": true, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json index ec510f736..9a65a983a 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json @@ -1435,9 +1435,7 @@ "display_name": "SearchAPI API Key", "dynamic": false, "info": "", - "input_types": [ - "Message" - ], + "input_types": [], "load_from_db": false, "name": "api_key", "password": true, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json index 831b6b69e..97bfe0aa8 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json @@ -323,7 +323,7 @@ "category": "helpers", "conditional_paths": [], "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", "documentation": "", "edited": false,