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:
parent
2dffc5dfd3
commit
a9559515e5
4 changed files with 106 additions and 27 deletions
|
|
@ -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",
|
||||
|
|
|
|||
79
src/backend/base/langflow/components/tools/search.py
Normal file
79
src/backend/base/langflow/components/tools/search.py
Normal 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)
|
||||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue