Skip to content

Commit a03d2c6

Browse files
Add SearchApi tools
1 parent 164d6e4 commit a03d2c6

12 files changed

Lines changed: 724 additions & 0 deletions

File tree

api/core/tools/provider/_position.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
- google
22
- bing
33
- duckduckgo
4+
- searchapi
45
- searxng
56
- dalle
67
- azuredalle
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import Any
2+
3+
from core.tools.errors import ToolProviderCredentialValidationError
4+
from core.tools.provider.builtin.searchapi.tools.google import GoogleTool
5+
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
6+
7+
8+
class SearchAPIProvider(BuiltinToolProviderController):
9+
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
10+
try:
11+
GoogleTool().fork_tool_runtime(
12+
meta={
13+
"credentials": credentials,
14+
}
15+
).invoke(
16+
user_id='',
17+
tool_parameters={
18+
"query": "SearchApi dify",
19+
"result_type": "link"
20+
},
21+
)
22+
except Exception as e:
23+
raise ToolProviderCredentialValidationError(str(e))
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
identity:
2+
author: SearchApi
3+
name: searchapi
4+
label:
5+
en_US: SearchApi
6+
zh_Hans: SearchApi
7+
pt_BR: SearchApi
8+
description:
9+
en_US: SearchApi
10+
zh_Hans: SearchApi
11+
pt_BR: SearchApi
12+
icon: icon.svg
13+
credentials_for_provider:
14+
searchapi_api_key:
15+
type: secret-input
16+
required: true
17+
label:
18+
en_US: SearchApi API key
19+
zh_Hans: SearchApi API key
20+
pt_BR: SearchApi API key
21+
placeholder:
22+
en_US: Please input your SearchApi API key
23+
zh_Hans: 请输入你的 SearchApi API key
24+
pt_BR: Please input your SearchApi API key
25+
help:
26+
en_US: Get your SearchApi API key from SearchApi
27+
zh_Hans: 从 SearchApi 获取您的 SearchApi API key
28+
pt_BR: Get your SearchApi API key from SearchApi
29+
url: https://www.searchapi.io/
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from typing import Any, Union
2+
3+
import requests
4+
5+
from core.tools.entities.tool_entities import ToolInvokeMessage
6+
from core.tools.tool.builtin_tool import BuiltinTool
7+
8+
SEARCH_API_URL = "https://www.searchapi.io/api/v1/search"
9+
10+
class SearchAPI:
11+
"""
12+
SearchAPI tool provider.
13+
"""
14+
15+
def __init__(self, api_key: str) -> None:
16+
"""Initialize SearchAPI tool provider."""
17+
self.searchapi_api_key = api_key
18+
19+
def run(self, query: str, **kwargs: Any) -> str:
20+
"""Run query through SearchAPI and parse result."""
21+
type = kwargs.get("result_type", "text")
22+
return self._process_response(self.results(query, **kwargs), type=type)
23+
24+
def results(self, query: str, **kwargs: Any) -> dict:
25+
"""Run query through SearchAPI and return the raw result."""
26+
params = self.get_params(query, **kwargs)
27+
response = requests.get(
28+
url=SEARCH_API_URL,
29+
params=params,
30+
headers={"Authorization": f"Bearer {self.searchapi_api_key}"},
31+
)
32+
response.raise_for_status()
33+
return response.json()
34+
35+
def get_params(self, query: str, **kwargs: Any) -> dict[str, str]:
36+
"""Get parameters for SearchAPI."""
37+
return {
38+
"engine": "google",
39+
"q": query,
40+
**{key: value for key, value in kwargs.items() if value not in [None, ""]},
41+
}
42+
43+
@staticmethod
44+
def _process_response(res: dict, type: str) -> str:
45+
"""Process response from SearchAPI."""
46+
if "error" in res.keys():
47+
raise ValueError(f"Got error from SearchApi: {res['error']}")
48+
49+
toret = ""
50+
if type == "text":
51+
if "answer_box" in res.keys() and "answer" in res["answer_box"].keys():
52+
toret += res["answer_box"]["answer"] + "\n"
53+
if "answer_box" in res.keys() and "snippet" in res["answer_box"].keys():
54+
toret += res["answer_box"]["snippet"] + "\n"
55+
if "knowledge_graph" in res.keys() and "description" in res["knowledge_graph"].keys():
56+
toret += res["knowledge_graph"]["description"] + "\n"
57+
if "organic_results" in res.keys() and "snippet" in res["organic_results"][0].keys():
58+
for item in res["organic_results"]:
59+
toret += "content: " + item["snippet"] + "\n" + "link: " + item["link"] + "\n"
60+
if toret == "":
61+
toret = "No good search result found"
62+
63+
elif type == "link":
64+
if "answer_box" in res.keys() and "organic_result" in res["answer_box"].keys():
65+
if "title" in res["answer_box"]["organic_result"].keys():
66+
toret = f"[{res['answer_box']['organic_result']['title']}]({res['answer_box']['organic_result']['link']})\n"
67+
elif "organic_results" in res.keys() and "link" in res["organic_results"][0].keys():
68+
toret = ""
69+
for item in res["organic_results"]:
70+
toret += f"[{item['title']}]({item['link']})\n"
71+
elif "related_questions" in res.keys() and "link" in res["related_questions"][0].keys():
72+
toret = ""
73+
for item in res["related_questions"]:
74+
toret += f"[{item['title']}]({item['link']})\n"
75+
elif "related_searches" in res.keys() and "link" in res["related_searches"][0].keys():
76+
toret = ""
77+
for item in res["related_searches"]:
78+
toret += f"[{item['title']}]({item['link']})\n"
79+
else:
80+
toret = "No good search result found"
81+
return toret
82+
83+
class GoogleTool(BuiltinTool):
84+
def _invoke(self,
85+
user_id: str,
86+
tool_parameters: dict[str, Any],
87+
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
88+
"""
89+
Invoke the SearchApi tool.
90+
"""
91+
query = tool_parameters['query']
92+
result_type = tool_parameters['result_type']
93+
num = tool_parameters.get("num", 10)
94+
google_domain = tool_parameters.get("google_domain", "google.com")
95+
gl = tool_parameters.get("gl", "us")
96+
hl = tool_parameters.get("hl", "en")
97+
location = tool_parameters.get("location", None)
98+
99+
api_key = self.runtime.credentials['searchapi_api_key']
100+
result = SearchAPI(api_key).run(query, result_type=result_type, num=num, google_domain=google_domain, gl=gl, hl=hl, location=location)
101+
102+
return self.create_text_message(text=result)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
identity:
2+
name: google_search_api
3+
author: SearchApi
4+
label:
5+
en_US: Google Search API
6+
zh_Hans: Google Search API
7+
description:
8+
human:
9+
en_US: A tool to retrieve answer boxes, knowledge graphs, snippets, and webpages from Google Search engine.
10+
zh_Hans: 一种从 Google 搜索引擎检索答案框、知识图、片段和网页的工具。
11+
llm: A tool to retrieve answer boxes, knowledge graphs, snippets, and webpages from Google Search engine.
12+
parameters:
13+
- name: query
14+
type: string
15+
required: true
16+
label:
17+
en_US: query
18+
zh_Hans: 询问
19+
human_description:
20+
en_US: Defines the query you want to search.
21+
zh_Hans: 定义您要搜索的查询。
22+
llm_description: Defines the search query you want to search.
23+
form: llm
24+
- name: result_type
25+
type: select
26+
required: true
27+
options:
28+
- value: text
29+
label:
30+
en_US: text
31+
zh_Hans: 文本
32+
- value: link
33+
label:
34+
en_US: link
35+
zh_Hans: 链接
36+
default: text
37+
label:
38+
en_US: Result type
39+
zh_Hans: 结果类型
40+
human_description:
41+
en_US: used for selecting the result type, text or link
42+
zh_Hans: 用于选择结果类型,使用文本还是链接进行展示
43+
form: form
44+
- name: location
45+
type: string
46+
required: false
47+
label:
48+
en_US: location
49+
zh_Hans: 询问
50+
human_description:
51+
en_US: Defines from where you want the search to originate. (For example - New York)
52+
zh_Hans: 定义您想要搜索的起始位置。 (例如 - 纽约)
53+
llm_description: Defines from where you want the search to originate. (For example - New York)
54+
form: llm
55+
- name: gl
56+
type: string
57+
required: false
58+
label:
59+
en_US: gl
60+
zh_Hans: gl
61+
human_description:
62+
en_US: Defines the country of the search. Default is "US".
63+
zh_Hans: 定义搜索的国家/地区。默认为“美国”。
64+
llm_description: Defines the gl parameter of the Google News search.
65+
form: llm
66+
- name: hl
67+
type: string
68+
required: false
69+
label:
70+
en_US: hl
71+
zh_Hans: hl
72+
human_description:
73+
en_US: Defines the interface language of the search. Default is "en".
74+
zh_Hans: 定义搜索的界面语言。默认为“en”。
75+
llm_description: Defines the hl parameter of the Google News search.
76+
form: llm
77+
- name: google_domain
78+
type: string
79+
required: false
80+
label:
81+
en_US: google_domain
82+
zh_Hans: google_domain
83+
human_description:
84+
en_US: Defines the Google domain of the search. Default is "google.com".
85+
zh_Hans: 定义搜索的 Google 域。默认为“google.com”。
86+
llm_description: Defines Google domain in which you want to search.
87+
form: llm
88+
- name: num
89+
type: number
90+
required: false
91+
label:
92+
en_US: num
93+
zh_Hans: num
94+
human_description:
95+
en_US: Specifies the number of results to display per page. Default is 10. Max number - 100, min - 1.
96+
zh_Hans: 指定每页显示的结果数。默认值为 10。最大数量 - 100,最小数量 - 1。
97+
pt_BR: Specifies the number of results to display per page. Default is 10. Max number - 100, min - 1.
98+
llm_description: Specifies the num of results to display per page.
99+
form: llm
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from typing import Any, Union
2+
3+
import requests
4+
5+
from core.tools.entities.tool_entities import ToolInvokeMessage
6+
from core.tools.tool.builtin_tool import BuiltinTool
7+
8+
SEARCH_API_URL = "https://www.searchapi.io/api/v1/search"
9+
10+
class SearchAPI:
11+
"""
12+
SearchAPI tool provider.
13+
"""
14+
15+
def __init__(self, api_key: str) -> None:
16+
"""Initialize SearchAPI tool provider."""
17+
self.searchapi_api_key = api_key
18+
19+
def run(self, query: str, **kwargs: Any) -> str:
20+
"""Run query through SearchAPI and parse result."""
21+
type = kwargs.get("result_type", "text")
22+
return self._process_response(self.results(query, **kwargs), type=type)
23+
24+
def results(self, query: str, **kwargs: Any) -> dict:
25+
"""Run query through SearchAPI and return the raw result."""
26+
params = self.get_params(query, **kwargs)
27+
response = requests.get(
28+
url=SEARCH_API_URL,
29+
params=params,
30+
headers={"Authorization": f"Bearer {self.searchapi_api_key}"},
31+
)
32+
response.raise_for_status()
33+
return response.json()
34+
35+
def get_params(self, query: str, **kwargs: Any) -> dict[str, str]:
36+
"""Get parameters for SearchAPI."""
37+
return {
38+
"engine": "google_jobs",
39+
"q": query,
40+
**{key: value for key, value in kwargs.items() if value not in [None, ""]},
41+
}
42+
43+
@staticmethod
44+
def _process_response(res: dict, type: str) -> str:
45+
"""Process response from SearchAPI."""
46+
if "error" in res.keys():
47+
raise ValueError(f"Got error from SearchApi: {res['error']}")
48+
49+
toret = ""
50+
if type == "text":
51+
if "jobs" in res.keys() and "title" in res["jobs"][0].keys():
52+
for item in res["jobs"]:
53+
toret += "title: " + item["title"] + "\n" + "company_name: " + item["company_name"] + "content: " + item["description"] + "\n"
54+
if toret == "":
55+
toret = "No good search result found"
56+
57+
elif type == "link":
58+
if "jobs" in res.keys() and "apply_link" in res["jobs"][0].keys():
59+
for item in res["jobs"]:
60+
toret += f"[{item['title']} - {item['company_name']}]({item['apply_link']})\n"
61+
else:
62+
toret = "No good search result found"
63+
return toret
64+
65+
class GoogleJobsTool(BuiltinTool):
66+
def _invoke(self,
67+
user_id: str,
68+
tool_parameters: dict[str, Any],
69+
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
70+
"""
71+
Invoke the SearchApi tool.
72+
"""
73+
query = tool_parameters['query']
74+
result_type = tool_parameters['result_type']
75+
is_remote = tool_parameters.get("is_remote", None)
76+
google_domain = tool_parameters.get("google_domain", "google.com")
77+
gl = tool_parameters.get("gl", "us")
78+
hl = tool_parameters.get("hl", "en")
79+
location = tool_parameters.get("location", None)
80+
81+
ltype = 1 if is_remote else None
82+
83+
api_key = self.runtime.credentials['searchapi_api_key']
84+
result = SearchAPI(api_key).run(query, result_type=result_type, google_domain=google_domain, gl=gl, hl=hl, location=location, ltype=ltype)
85+
86+
return self.create_text_message(text=result)

0 commit comments

Comments
 (0)