refactor: Search API component ensuring standard output pattern (#5435)

* refactor: Update Search API component to standard output pattern

* fix: rename component class to avoid conflict with legacy version

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* fix: address performance regression

---------

Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
This commit is contained in:
Raphael Valdetaro 2025-01-20 12:43:22 -03:00 • committed by GitHub
commit a9559515e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 106 additions and 27 deletions

View file

@ -16,6 +16,7 @@ from .mcp_stdio import MCPStdio
from .python_code_structured_tool import PythonCodeStructuredTool
from .python_repl import PythonREPLToolComponent
from .python_repl_core import PythonREPLComponent
from .search import SearchComponent
from .search_api import SearchAPIComponent
from .searxng import SearXNGToolComponent
from .serp import SerpComponent
@ -53,6 +54,7 @@ __all__ = [
"PythonREPLToolComponent",
"SearXNGToolComponent",
"SearchAPIComponent",
"SearchComponent",
"SerpAPIComponent",
"SerpComponent",
"TavilySearchComponent",

View file

@ -0,0 +1,79 @@
from typing import Any
from langchain_community.utilities.searchapi import SearchApiAPIWrapper
from langflow.custom import Component
from langflow.inputs import DictInput, DropdownInput, IntInput, MultilineInput, SecretStrInput
from langflow.io import Output
from langflow.schema import Data
from langflow.schema.message import Message
class SearchComponent(Component):
display_name: str = "Search API"
description: str = "Call the searchapi.io API with result limiting"
documentation: str = "https://www.searchapi.io/docs/google"
icon = "SearchAPI"
inputs = [
DropdownInput(name="engine", display_name="Engine", value="google", options=["google", "bing", "duckduckgo"]),
SecretStrInput(name="api_key", display_name="SearchAPI API Key", required=True),
MultilineInput(
name="input_value",
display_name="Input",
tool_mode=True,
),
DictInput(name="search_params", display_name="Search parameters", advanced=True, is_list=True),
IntInput(name="max_results", display_name="Max Results", value=5, advanced=True),
IntInput(name="max_snippet_length", display_name="Max Snippet Length", value=100, advanced=True),
]
outputs = [
Output(display_name="Data", name="data", method="fetch_content"),
Output(display_name="Text", name="text", method="fetch_content_text"),
]
def _build_wrapper(self):
return SearchApiAPIWrapper(engine=self.engine, searchapi_api_key=self.api_key)
def run_model(self) -> list[Data]:
return self.fetch_content()
def fetch_content(self) -> list[Data]:
wrapper = self._build_wrapper()
def search_func(
query: str, params: dict[str, Any] | None = None, max_results: int = 5, max_snippet_length: int = 100
) -> list[Data]:
params = params or {}
full_results = wrapper.results(query=query, **params)
organic_results = full_results.get("organic_results", [])[:max_results]
return [
Data(
text=result.get("snippet", ""),
data={
"title": result.get("title", "")[:max_snippet_length],
"link": result.get("link", ""),
"snippet": result.get("snippet", "")[:max_snippet_length],
},
)
for result in organic_results
]
results = search_func(
self.input_value,
self.search_params or {},
self.max_results,
self.max_snippet_length,
)
self.status = results
return results
def fetch_content_text(self) -> Message:
data = self.fetch_content()
result_string = ""
for item in data:
result_string += item.text + "\n"
self.status = result_string
return Message(text=result_string)

View file

@ -11,11 +11,12 @@ from langflow.schema import Data
class SearchAPIComponent(LCToolComponent):
display_name: str = "Search API"
display_name: str = "Search API [DEPRECATED]"
description: str = "Call the searchapi.io API with result limiting"
name = "SearchAPI"
documentation: str = "https://www.searchapi.io/docs/google"
icon = "SearchAPI"
legacy = True
inputs = [
MessageTextInput(name="engine", display_name="Engine", value="google"),

View file

@ -1,5 +1,6 @@
import { expect, test } from "@playwright/test";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { zoomOut } from "../../utils/zoom-out";
test(
"user should be able to use ComposIO without getting api_key error",
@ -42,53 +43,49 @@ test(
async ({ page }) => {
await awaitBootstrapTest(page);
await page.waitForSelector('[data-testid="blank-flow"]', {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
//first component
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("search api");
await page.waitForSelector('[data-testid="toolsSearch API"]', {
timeout: 3000,
timeout: 1000,
});
let modelElement = await page.getByTestId("toolsSearch API");
let targetElement = await page.locator('//*[@id="react-flow-id"]');
await modelElement.dragTo(targetElement);
await zoomOut(page, 3);
await page.mouse.up();
await page.mouse.down();
await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click();
await page
.getByTestId("toolsSearch API")
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
targetPosition: { x: 100, y: 100 },
});
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("tool calling agent");
await page.waitForSelector(
'[data-testid="langchain_utilitiesTool Calling Agent"]',
{
timeout: 3000,
timeout: 1000,
},
);
modelElement = page.getByTestId("langchain_utilitiesTool Calling Agent");
targetElement = await page.locator('//*[@id="react-flow-id"]');
await modelElement.dragTo(targetElement);
await page.mouse.up();
await page.mouse.down();
await page
.getByTestId("langchain_utilitiesTool Calling Agent")
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
targetPosition: { x: 300, y: 300 },
});
await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("title-Search API").first().click();
await page.getByTestId("tool-mode-button").click();
//connection
const searchApiOutput = await page
.getByTestId("handle-searchapi-shownode-tool-right")
.nth(0);
.getByTestId("handle-searchcomponent-shownode-toolset-right")
.first();
await searchApiOutput.hover();
await page.mouse.down();
const toolCallingAgentInput = await page