SebastjanPrachovskij пре 10 месеци
родитељ
комит
b10e67be3b

+ 1 - 0
api/core/tools/provider/_position.yaml

@@ -1,6 +1,7 @@
 - google
 - bing
 - duckduckgo
+- searchapi
 - searxng
 - dalle
 - azuredalle

+ 1 - 0
api/core/tools/provider/builtin/searchapi/_assets/icon.svg

@@ -0,0 +1 @@
+<svg height="112.906975" viewBox="0 0 112.90697479 112.90697479" width="112.906975" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h112.906975v112.906975h-112.906975z" fill="#6986eb"/><path d="m97.24234 109.8245a2.57759 2.57759 0 0 1 -2.57778 2.57778 9.80672 9.80672 0 0 1 -9.79558-9.79557v-11.02305a2.57779 2.57779 0 0 1 5.15557 0v11.02305a4.64509 4.64509 0 0 0 4.64 4.64 2.57759 2.57759 0 0 1 2.57779 2.57779zm14.95114-16.6015a2.57759 2.57759 0 0 0 -2.57778 2.57779 4.64 4.64 0 1 1 -9.28 0v-22.06525a23.2 23.2 0 1 0 -46.40009 0v22.06522a4.64 4.64 0 1 1 -9.28 0 2.57779 2.57779 0 1 0 -5.15557 0 9.79558 9.79558 0 0 0 19.59115 0v-22.06522a18.04449 18.04449 0 0 1 36.089 0v22.06522a9.79558 9.79558 0 0 0 19.59115 0 2.57759 2.57759 0 0 0 -2.57786-2.57776zm-35.05785-1.443a2.57759 2.57759 0 0 0 -2.57778 2.57778v17.52893a2.57779 2.57779 0 0 0 5.15557 0v-17.52891a2.57759 2.57759 0 0 0 -2.57779-2.5778zm-10.31113-2.77412a2.57759 2.57759 0 0 0 -2.57778 2.57778v11.02305a4.64509 4.64509 0 0 1 -4.64 4.64 2.57778 2.57778 0 1 0 0 5.15556 9.80671 9.80671 0 0 0 9.79557-9.79557v-11.02304a2.57759 2.57759 0 0 0 -2.57779-2.57778zm3.0935-18.87949a3.6089 3.6089 0 1 0 3.6089 3.6089 3.60891 3.60891 0 0 0 -3.6089-3.6089zm18.04448 3.6089a3.6089 3.6089 0 1 0 -3.60889 3.60889 3.60891 3.60891 0 0 0 3.60892-3.60889z" fill="#00001b" transform="translate(-20.682205 -26.043993)"/></svg>

+ 23 - 0
api/core/tools/provider/builtin/searchapi/searchapi.py

@@ -0,0 +1,23 @@
+from typing import Any
+
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.provider.builtin.searchapi.tools.google import GoogleTool
+from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
+
+
+class SearchAPIProvider(BuiltinToolProviderController):
+    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+        try:
+            GoogleTool().fork_tool_runtime(
+                runtime={
+                    "credentials": credentials,
+                }
+            ).invoke(
+                user_id='',
+                tool_parameters={
+                    "query": "SearchApi dify",
+                    "result_type": "link"
+                },
+            )
+        except Exception as e:
+            raise ToolProviderCredentialValidationError(str(e))

+ 34 - 0
api/core/tools/provider/builtin/searchapi/searchapi.yaml

@@ -0,0 +1,34 @@
+identity:
+  author: SearchApi
+  name: searchapi
+  label:
+    en_US: SearchApi
+    zh_Hans: SearchApi
+    pt_BR: SearchApi
+  description:
+    en_US: SearchApi is a robust real-time SERP API delivering structured data from a collection of search engines including Google Search, Google Jobs, YouTube, Google News, and many more.
+    zh_Hans: SearchApi 是一个强大的实时 SERP API,可提供来自 Google 搜索、Google 招聘、YouTube、Google 新闻等搜索引擎集合的结构化数据。
+    pt_BR: SearchApi is a robust real-time SERP API delivering structured data from a collection of search engines including Google Search, Google Jobs, YouTube, Google News, and many more.
+  icon: icon.svg
+  tags:
+    - search
+    - business
+    - news
+    - productivity
+credentials_for_provider:
+  searchapi_api_key:
+    type: secret-input
+    required: true
+    label:
+      en_US: SearchApi API key
+      zh_Hans: SearchApi API key
+      pt_BR: SearchApi API key
+    placeholder:
+      en_US: Please input your SearchApi API key
+      zh_Hans: 请输入你的 SearchApi API key
+      pt_BR: Please input your SearchApi API key
+    help:
+      en_US: Get your SearchApi API key from SearchApi
+      zh_Hans: 从 SearchApi 获取您的 SearchApi API key
+      pt_BR: Get your SearchApi API key from SearchApi
+    url: https://www.searchapi.io/

+ 104 - 0
api/core/tools/provider/builtin/searchapi/tools/google.py

@@ -0,0 +1,104 @@
+from typing import Any, Union
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+SEARCH_API_URL = "https://www.searchapi.io/api/v1/search"
+
+class SearchAPI:
+    """
+    SearchAPI tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize SearchAPI tool provider."""
+        self.searchapi_api_key = api_key
+
+    def run(self, query: str, **kwargs: Any) -> str:
+        """Run query through SearchAPI and parse result."""
+        type = kwargs.get("result_type", "text")
+        return self._process_response(self.results(query, **kwargs), type=type)
+
+    def results(self, query: str, **kwargs: Any) -> dict:
+        """Run query through SearchAPI and return the raw result."""
+        params = self.get_params(query, **kwargs)
+        response = requests.get(
+            url=SEARCH_API_URL,
+            params=params,
+            headers={"Authorization": f"Bearer {self.searchapi_api_key}"},
+        )
+        response.raise_for_status()
+        return response.json()
+
+    def get_params(self, query: str, **kwargs: Any) -> dict[str, str]:
+        """Get parameters for SearchAPI."""
+        return {
+            "engine": "google",
+            "q": query,
+            **{key: value for key, value in kwargs.items() if value not in [None, ""]},
+        }
+
+    @staticmethod
+    def _process_response(res: dict, type: str) -> str:
+        """Process response from SearchAPI."""
+        if "error" in res.keys():
+            raise ValueError(f"Got error from SearchApi: {res['error']}")
+
+        toret = ""
+        if type == "text":
+            if "answer_box" in res.keys() and "answer" in res["answer_box"].keys():
+                toret += res["answer_box"]["answer"] + "\n"
+            if "answer_box" in res.keys() and "snippet" in res["answer_box"].keys():
+                toret += res["answer_box"]["snippet"] + "\n"
+            if "knowledge_graph" in res.keys() and "description" in res["knowledge_graph"].keys():
+                toret += res["knowledge_graph"]["description"] + "\n"
+            if "organic_results" in res.keys() and "snippet" in res["organic_results"][0].keys():
+                for item in res["organic_results"]:
+                    toret += "content: " + item["snippet"] + "\n" + "link: " + item["link"] + "\n"
+            if toret == "":
+                toret = "No good search result found"
+
+        elif type == "link":
+            if "answer_box" in res.keys() and "organic_result" in res["answer_box"].keys():
+                if "title" in res["answer_box"]["organic_result"].keys():
+                    toret = f"[{res['answer_box']['organic_result']['title']}]({res['answer_box']['organic_result']['link']})\n"
+            elif "organic_results" in res.keys() and "link" in res["organic_results"][0].keys():
+                toret = ""
+                for item in res["organic_results"]:
+                    toret += f"[{item['title']}]({item['link']})\n"
+            elif "related_questions" in res.keys() and "link" in res["related_questions"][0].keys():
+                toret = ""
+                for item in res["related_questions"]:
+                    toret += f"[{item['title']}]({item['link']})\n"
+            elif "related_searches" in res.keys() and "link" in res["related_searches"][0].keys():
+                toret = ""
+                for item in res["related_searches"]:
+                    toret += f"[{item['title']}]({item['link']})\n"
+            else:
+                toret = "No good search result found"
+        return toret
+
+class GoogleTool(BuiltinTool):
+    def _invoke(self,
+                user_id: str,
+                tool_parameters: dict[str, Any],
+        ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SearchApi tool.
+        """
+        query = tool_parameters['query']
+        result_type = tool_parameters['result_type']
+        num = tool_parameters.get("num", 10)
+        google_domain = tool_parameters.get("google_domain", "google.com")
+        gl = tool_parameters.get("gl", "us")
+        hl = tool_parameters.get("hl", "en")
+        location = tool_parameters.get("location", None)
+
+        api_key = self.runtime.credentials['searchapi_api_key']
+        result = SearchAPI(api_key).run(query, result_type=result_type, num=num, google_domain=google_domain, gl=gl, hl=hl, location=location)
+
+        if result_type == 'text':
+            return self.create_text_message(text=result)
+        return self.create_link_message(link=result)

+ 481 - 0
api/core/tools/provider/builtin/searchapi/tools/google.yaml

@@ -0,0 +1,481 @@
+identity:
+  name: google_search_api
+  author: SearchApi
+  label:
+    en_US: Google Search API
+    zh_Hans: Google Search API
+description:
+  human:
+    en_US: A tool to retrieve answer boxes, knowledge graphs, snippets, and webpages from Google Search engine.
+    zh_Hans: 一种从 Google 搜索引擎检索答案框、知识图、片段和网页的工具。
+  llm: A tool to retrieve answer boxes, knowledge graphs, snippets, and webpages from Google Search engine.
+parameters:
+  - name: query
+    type: string
+    required: true
+    label:
+      en_US: Query
+      zh_Hans: 询问
+    human_description:
+      en_US: Defines the query you want to search.
+      zh_Hans: 定义您要搜索的查询。
+    llm_description: Defines the search query you want to search.
+    form: llm
+  - name: result_type
+    type: select
+    required: true
+    options:
+      - value: text
+        label:
+          en_US: text
+          zh_Hans: 文本
+      - value: link
+        label:
+          en_US: link
+          zh_Hans: 链接
+    default: text
+    label:
+      en_US: Result type
+      zh_Hans: 结果类型
+    human_description:
+      en_US: used for selecting the result type, text or link
+      zh_Hans: 用于选择结果类型,使用文本还是链接进行展示
+    form: form
+  - name: location
+    type: string
+    required: false
+    label:
+      en_US: Location
+      zh_Hans: 询问
+    human_description:
+      en_US: Defines from where you want the search to originate. (For example - New York)
+      zh_Hans: 定义您想要搜索的起始位置。 (例如 - 纽约)
+    llm_description: Defines from where you want the search to originate. (For example - New York)
+    form: llm
+  - name: gl
+    type: select
+    label:
+      en_US: Country
+      zh_Hans: 国家
+    required: false
+    human_description:
+      en_US: Defines the country of the search. Default is "US".
+      zh_Hans: 定义搜索的国家/地区。默认为“美国”。
+    llm_description: Defines the gl parameter of the Google search.
+    form: form
+    default: US
+    options:
+      - value: AR
+        label:
+          en_US: Argentina
+          zh_Hans: 阿根廷
+          pt_BR: Argentina
+      - value: AU
+        label:
+          en_US: Australia
+          zh_Hans: 澳大利亚
+          pt_BR: Australia
+      - value: AT
+        label:
+          en_US: Austria
+          zh_Hans: 奥地利
+          pt_BR: Austria
+      - value: BE
+        label:
+          en_US: Belgium
+          zh_Hans: 比利时
+          pt_BR: Belgium
+      - value: BR
+        label:
+          en_US: Brazil
+          zh_Hans: 巴西
+          pt_BR: Brazil
+      - value: CA
+        label:
+          en_US: Canada
+          zh_Hans: 加拿大
+          pt_BR: Canada
+      - value: CL
+        label:
+          en_US: Chile
+          zh_Hans: 智利
+          pt_BR: Chile
+      - value: CO
+        label:
+          en_US: Colombia
+          zh_Hans: 哥伦比亚
+          pt_BR: Colombia
+      - value: CN
+        label:
+          en_US: China
+          zh_Hans: 中国
+          pt_BR: China
+      - value: CZ
+        label:
+          en_US: Czech Republic
+          zh_Hans: 捷克共和国
+          pt_BR: Czech Republic
+      - value: DK
+        label:
+          en_US: Denmark
+          zh_Hans: 丹麦
+          pt_BR: Denmark
+      - value: FI
+        label:
+          en_US: Finland
+          zh_Hans: 芬兰
+          pt_BR: Finland
+      - value: FR
+        label:
+          en_US: France
+          zh_Hans: 法国
+          pt_BR: France
+      - value: DE
+        label:
+          en_US: Germany
+          zh_Hans: 德国
+          pt_BR: Germany
+      - value: HK
+        label:
+          en_US: Hong Kong
+          zh_Hans: 香港
+          pt_BR: Hong Kong
+      - value: IN
+        label:
+          en_US: India
+          zh_Hans: 印度
+          pt_BR: India
+      - value: ID
+        label:
+          en_US: Indonesia
+          zh_Hans: 印度尼西亚
+          pt_BR: Indonesia
+      - value: IT
+        label:
+          en_US: Italy
+          zh_Hans: 意大利
+          pt_BR: Italy
+      - value: JP
+        label:
+          en_US: Japan
+          zh_Hans: 日本
+          pt_BR: Japan
+      - value: KR
+        label:
+          en_US: Korea
+          zh_Hans: 韩国
+          pt_BR: Korea
+      - value: MY
+        label:
+          en_US: Malaysia
+          zh_Hans: 马来西亚
+          pt_BR: Malaysia
+      - value: MX
+        label:
+          en_US: Mexico
+          zh_Hans: 墨西哥
+          pt_BR: Mexico
+      - value: NL
+        label:
+          en_US: Netherlands
+          zh_Hans: 荷兰
+          pt_BR: Netherlands
+      - value: NZ
+        label:
+          en_US: New Zealand
+          zh_Hans: 新西兰
+          pt_BR: New Zealand
+      - value: NO
+        label:
+          en_US: Norway
+          zh_Hans: 挪威
+          pt_BR: Norway
+      - value: PH
+        label:
+          en_US: Philippines
+          zh_Hans: 菲律宾
+          pt_BR: Philippines
+      - value: PL
+        label:
+          en_US: Poland
+          zh_Hans: 波兰
+          pt_BR: Poland
+      - value: PT
+        label:
+          en_US: Portugal
+          zh_Hans: 葡萄牙
+          pt_BR: Portugal
+      - value: RU
+        label:
+          en_US: Russia
+          zh_Hans: 俄罗斯
+          pt_BR: Russia
+      - value: SA
+        label:
+          en_US: Saudi Arabia
+          zh_Hans: 沙特阿拉伯
+          pt_BR: Saudi Arabia
+      - value: SG
+        label:
+          en_US: Singapore
+          zh_Hans: 新加坡
+          pt_BR: Singapore
+      - value: ZA
+        label:
+          en_US: South Africa
+          zh_Hans: 南非
+          pt_BR: South Africa
+      - value: ES
+        label:
+          en_US: Spain
+          zh_Hans: 西班牙
+          pt_BR: Spain
+      - value: SE
+        label:
+          en_US: Sweden
+          zh_Hans: 瑞典
+          pt_BR: Sweden
+      - value: CH
+        label:
+          en_US: Switzerland
+          zh_Hans: 瑞士
+          pt_BR: Switzerland
+      - value: TW
+        label:
+          en_US: Taiwan
+          zh_Hans: 台湾
+          pt_BR: Taiwan
+      - value: TH
+        label:
+          en_US: Thailand
+          zh_Hans: 泰国
+          pt_BR: Thailand
+      - value: TR
+        label:
+          en_US: Turkey
+          zh_Hans: 土耳其
+          pt_BR: Turkey
+      - value: GB
+        label:
+          en_US: United Kingdom
+          zh_Hans: 英国
+          pt_BR: United Kingdom
+      - value: US
+        label:
+          en_US: United States
+          zh_Hans: 美国
+          pt_BR: United States
+  - name: hl
+    type: select
+    label:
+      en_US: Language
+      zh_Hans: 语言
+    human_description:
+      en_US: Defines the interface language of the search. Default is "en".
+      zh_Hans: 定义搜索的界面语言。默认为“en”。
+    required: false
+    default: en
+    form: form
+    options:
+      - value: ar
+        label:
+          en_US: Arabic
+          zh_Hans: 阿拉伯语
+      - value: bg
+        label:
+          en_US: Bulgarian
+          zh_Hans: 保加利亚语
+      - value: ca
+        label:
+          en_US: Catalan
+          zh_Hans: 加泰罗尼亚语
+      - value: zh-cn
+        label:
+          en_US: Chinese (Simplified)
+          zh_Hans: 中文(简体)
+      - value: zh-tw
+        label:
+          en_US: Chinese (Traditional)
+          zh_Hans: 中文(繁体)
+      - value: cs
+        label:
+          en_US: Czech
+          zh_Hans: 捷克语
+      - value: da
+        label:
+          en_US: Danish
+          zh_Hans: 丹麦语
+      - value: nl
+        label:
+          en_US: Dutch
+          zh_Hans: 荷兰语
+      - value: en
+        label:
+          en_US: English
+          zh_Hans: 英语
+      - value: et
+        label:
+          en_US: Estonian
+          zh_Hans: 爱沙尼亚语
+      - value: fi
+        label:
+          en_US: Finnish
+          zh_Hans: 芬兰语
+      - value: fr
+        label:
+          en_US: French
+          zh_Hans: 法语
+      - value: de
+        label:
+          en_US: German
+          zh_Hans: 德语
+      - value: el
+        label:
+          en_US: Greek
+          zh_Hans: 希腊语
+      - value: iw
+        label:
+          en_US: Hebrew
+          zh_Hans: 希伯来语
+      - value: hi
+        label:
+          en_US: Hindi
+          zh_Hans: 印地语
+      - value: hu
+        label:
+          en_US: Hungarian
+          zh_Hans: 匈牙利语
+      - value: id
+        label:
+          en_US: Indonesian
+          zh_Hans: 印尼语
+      - value: it
+        label:
+          en_US: Italian
+          zh_Hans: 意大利语
+      - value: jp
+        label:
+          en_US: Japanese
+          zh_Hans: 日语
+      - value: kn
+        label:
+          en_US: Kannada
+          zh_Hans: 卡纳达语
+      - value: ko
+        label:
+          en_US: Korean
+          zh_Hans: 韩语
+      - value: lv
+        label:
+          en_US: Latvian
+          zh_Hans: 拉脱维亚语
+      - value: lt
+        label:
+          en_US: Lithuanian
+          zh_Hans: 立陶宛语
+      - value: my
+        label:
+          en_US: Malay
+          zh_Hans: 马来语
+      - value: ml
+        label:
+          en_US: Malayalam
+          zh_Hans: 马拉雅拉姆语
+      - value: mr
+        label:
+          en_US: Marathi
+          zh_Hans: 马拉地语
+      - value: "no"
+        label:
+          en_US: Norwegian
+          zh_Hans: 挪威语
+      - value: pl
+        label:
+          en_US: Polish
+          zh_Hans: 波兰语
+      - value: pt-br
+        label:
+          en_US: Portuguese (Brazil)
+          zh_Hans: 葡萄牙语(巴西)
+      - value: pt-pt
+        label:
+          en_US: Portuguese (Portugal)
+          zh_Hans: 葡萄牙语(葡萄牙)
+      - value: pa
+        label:
+          en_US: Punjabi
+          zh_Hans: 旁遮普语
+      - value: ro
+        label:
+          en_US: Romanian
+          zh_Hans: 罗马尼亚语
+      - value: ru
+        label:
+          en_US: Russian
+          zh_Hans: 俄语
+      - value: sr
+        label:
+          en_US: Serbian
+          zh_Hans: 塞尔维亚语
+      - value: sk
+        label:
+          en_US: Slovak
+          zh_Hans: 斯洛伐克语
+      - value: sl
+        label:
+          en_US: Slovenian
+          zh_Hans: 斯洛文尼亚语
+      - value: es
+        label:
+          en_US: Spanish
+          zh_Hans: 西班牙语
+      - value: sv
+        label:
+          en_US: Swedish
+          zh_Hans: 瑞典语
+      - value: ta
+        label:
+          en_US: Tamil
+          zh_Hans: 泰米尔语
+      - value: te
+        label:
+          en_US: Telugu
+          zh_Hans: 泰卢固语
+      - value: th
+        label:
+          en_US: Thai
+          zh_Hans: 泰语
+      - value: tr
+        label:
+          en_US: Turkish
+          zh_Hans: 土耳其语
+      - value: uk
+        label:
+          en_US: Ukrainian
+          zh_Hans: 乌克兰语
+      - value: vi
+        label:
+          en_US: Vietnamese
+          zh_Hans: 越南语
+  - name: google_domain
+    type: string
+    required: false
+    label:
+      en_US: google_domain
+      zh_Hans: google_domain
+    human_description:
+      en_US: Defines the Google domain of the search. Default is "google.com".
+      zh_Hans: 定义搜索的 Google 域。默认为“google.com”。
+    llm_description: Defines Google domain in which you want to search.
+    form: llm
+  - name: num
+    type: number
+    required: false
+    label:
+      en_US: num
+      zh_Hans: num
+    human_description:
+      en_US: Specifies the number of results to display per page. Default is 10. Max number - 100, min - 1.
+      zh_Hans: 指定每页显示的结果数。默认值为 10。最大数量 - 100,最小数量 - 1。
+    llm_description: Specifies the num of results to display per page.
+    form: llm

+ 88 - 0
api/core/tools/provider/builtin/searchapi/tools/google_jobs.py

@@ -0,0 +1,88 @@
+from typing import Any, Union
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+SEARCH_API_URL = "https://www.searchapi.io/api/v1/search"
+
+class SearchAPI:
+    """
+    SearchAPI tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize SearchAPI tool provider."""
+        self.searchapi_api_key = api_key
+
+    def run(self, query: str, **kwargs: Any) -> str:
+        """Run query through SearchAPI and parse result."""
+        type = kwargs.get("result_type", "text")
+        return self._process_response(self.results(query, **kwargs), type=type)
+
+    def results(self, query: str, **kwargs: Any) -> dict:
+        """Run query through SearchAPI and return the raw result."""
+        params = self.get_params(query, **kwargs)
+        response = requests.get(
+            url=SEARCH_API_URL,
+            params=params,
+            headers={"Authorization": f"Bearer {self.searchapi_api_key}"},
+        )
+        response.raise_for_status()
+        return response.json()
+
+    def get_params(self, query: str, **kwargs: Any) -> dict[str, str]:
+        """Get parameters for SearchAPI."""
+        return {
+            "engine": "google_jobs",
+            "q": query,
+            **{key: value for key, value in kwargs.items() if value not in [None, ""]},
+        }
+
+    @staticmethod
+    def _process_response(res: dict, type: str) -> str:
+        """Process response from SearchAPI."""
+        if "error" in res.keys():
+            raise ValueError(f"Got error from SearchApi: {res['error']}")
+
+        toret = ""
+        if type == "text":
+            if "jobs" in res.keys() and "title" in res["jobs"][0].keys():
+                for item in res["jobs"]:
+                    toret += "title: " + item["title"] + "\n" + "company_name: " + item["company_name"] + "content: " + item["description"] + "\n"
+            if toret == "":
+                toret = "No good search result found"
+
+        elif type == "link":
+            if "jobs" in res.keys() and "apply_link" in res["jobs"][0].keys():
+                for item in res["jobs"]:
+                    toret += f"[{item['title']} - {item['company_name']}]({item['apply_link']})\n"
+            else:
+                toret = "No good search result found"
+        return toret
+
+class GoogleJobsTool(BuiltinTool):
+    def _invoke(self,
+                user_id: str,
+                tool_parameters: dict[str, Any],
+        ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SearchApi tool.
+        """
+        query = tool_parameters['query']
+        result_type = tool_parameters['result_type']
+        is_remote = tool_parameters.get("is_remote", None)
+        google_domain = tool_parameters.get("google_domain", "google.com")
+        gl = tool_parameters.get("gl", "us")
+        hl = tool_parameters.get("hl", "en")
+        location = tool_parameters.get("location", None)
+
+        ltype = 1 if is_remote else None
+
+        api_key = self.runtime.credentials['searchapi_api_key']
+        result = SearchAPI(api_key).run(query, result_type=result_type, google_domain=google_domain, gl=gl, hl=hl, location=location, ltype=ltype)
+
+        if result_type == 'text':
+            return self.create_text_message(text=result)
+        return self.create_link_message(link=result)

+ 478 - 0
api/core/tools/provider/builtin/searchapi/tools/google_jobs.yaml

@@ -0,0 +1,478 @@
+identity:
+  name: google_jobs_api
+  author: SearchApi
+  label:
+    en_US: Google Jobs API
+    zh_Hans: Google Jobs API
+description:
+  human:
+    en_US: A tool to retrieve job titles, company names and description from Google Jobs engine.
+    zh_Hans: 一个从 Google 招聘引擎检索职位名称、公司名称和描述的工具。
+  llm: A tool to retrieve job titles, company names and description from Google Jobs engine.
+parameters:
+  - name: query
+    type: string
+    required: true
+    label:
+      en_US: Query
+      zh_Hans: 询问
+    human_description:
+      en_US: Defines the query you want to search.
+      zh_Hans: 定义您要搜索的查询。
+    llm_description: Defines the search query you want to search.
+    form: llm
+  - name: result_type
+    type: select
+    required: true
+    options:
+      - value: text
+        label:
+          en_US: text
+          zh_Hans: 文本
+      - value: link
+        label:
+          en_US: link
+          zh_Hans: 链接
+    default: text
+    label:
+      en_US: Result type
+      zh_Hans: 结果类型
+    human_description:
+      en_US: used for selecting the result type, text or link
+      zh_Hans: 用于选择结果类型,使用文本还是链接进行展示
+    form: form
+  - name: location
+    type: string
+    required: false
+    label:
+      en_US: Location
+      zh_Hans: 询问
+    human_description:
+      en_US: Defines from where you want the search to originate. (For example - New York)
+      zh_Hans: 定义您想要搜索的起始位置。 (例如 - 纽约)
+    llm_description: Defines from where you want the search to originate. (For example - New York)
+    form: llm
+  - name: gl
+    type: select
+    label:
+      en_US: Country
+      zh_Hans: 国家
+    required: false
+    human_description:
+      en_US: Defines the country of the search. Default is "US".
+      zh_Hans: 定义搜索的国家/地区。默认为“美国”。
+    llm_description: Defines the gl parameter of the Google search.
+    form: form
+    default: US
+    options:
+      - value: AR
+        label:
+          en_US: Argentina
+          zh_Hans: 阿根廷
+          pt_BR: Argentina
+      - value: AU
+        label:
+          en_US: Australia
+          zh_Hans: 澳大利亚
+          pt_BR: Australia
+      - value: AT
+        label:
+          en_US: Austria
+          zh_Hans: 奥地利
+          pt_BR: Austria
+      - value: BE
+        label:
+          en_US: Belgium
+          zh_Hans: 比利时
+          pt_BR: Belgium
+      - value: BR
+        label:
+          en_US: Brazil
+          zh_Hans: 巴西
+          pt_BR: Brazil
+      - value: CA
+        label:
+          en_US: Canada
+          zh_Hans: 加拿大
+          pt_BR: Canada
+      - value: CL
+        label:
+          en_US: Chile
+          zh_Hans: 智利
+          pt_BR: Chile
+      - value: CO
+        label:
+          en_US: Colombia
+          zh_Hans: 哥伦比亚
+          pt_BR: Colombia
+      - value: CN
+        label:
+          en_US: China
+          zh_Hans: 中国
+          pt_BR: China
+      - value: CZ
+        label:
+          en_US: Czech Republic
+          zh_Hans: 捷克共和国
+          pt_BR: Czech Republic
+      - value: DK
+        label:
+          en_US: Denmark
+          zh_Hans: 丹麦
+          pt_BR: Denmark
+      - value: FI
+        label:
+          en_US: Finland
+          zh_Hans: 芬兰
+          pt_BR: Finland
+      - value: FR
+        label:
+          en_US: France
+          zh_Hans: 法国
+          pt_BR: France
+      - value: DE
+        label:
+          en_US: Germany
+          zh_Hans: 德国
+          pt_BR: Germany
+      - value: HK
+        label:
+          en_US: Hong Kong
+          zh_Hans: 香港
+          pt_BR: Hong Kong
+      - value: IN
+        label:
+          en_US: India
+          zh_Hans: 印度
+          pt_BR: India
+      - value: ID
+        label:
+          en_US: Indonesia
+          zh_Hans: 印度尼西亚
+          pt_BR: Indonesia
+      - value: IT
+        label:
+          en_US: Italy
+          zh_Hans: 意大利
+          pt_BR: Italy
+      - value: JP
+        label:
+          en_US: Japan
+          zh_Hans: 日本
+          pt_BR: Japan
+      - value: KR
+        label:
+          en_US: Korea
+          zh_Hans: 韩国
+          pt_BR: Korea
+      - value: MY
+        label:
+          en_US: Malaysia
+          zh_Hans: 马来西亚
+          pt_BR: Malaysia
+      - value: MX
+        label:
+          en_US: Mexico
+          zh_Hans: 墨西哥
+          pt_BR: Mexico
+      - value: NL
+        label:
+          en_US: Netherlands
+          zh_Hans: 荷兰
+          pt_BR: Netherlands
+      - value: NZ
+        label:
+          en_US: New Zealand
+          zh_Hans: 新西兰
+          pt_BR: New Zealand
+      - value: NO
+        label:
+          en_US: Norway
+          zh_Hans: 挪威
+          pt_BR: Norway
+      - value: PH
+        label:
+          en_US: Philippines
+          zh_Hans: 菲律宾
+          pt_BR: Philippines
+      - value: PL
+        label:
+          en_US: Poland
+          zh_Hans: 波兰
+          pt_BR: Poland
+      - value: PT
+        label:
+          en_US: Portugal
+          zh_Hans: 葡萄牙
+          pt_BR: Portugal
+      - value: RU
+        label:
+          en_US: Russia
+          zh_Hans: 俄罗斯
+          pt_BR: Russia
+      - value: SA
+        label:
+          en_US: Saudi Arabia
+          zh_Hans: 沙特阿拉伯
+          pt_BR: Saudi Arabia
+      - value: SG
+        label:
+          en_US: Singapore
+          zh_Hans: 新加坡
+          pt_BR: Singapore
+      - value: ZA
+        label:
+          en_US: South Africa
+          zh_Hans: 南非
+          pt_BR: South Africa
+      - value: ES
+        label:
+          en_US: Spain
+          zh_Hans: 西班牙
+          pt_BR: Spain
+      - value: SE
+        label:
+          en_US: Sweden
+          zh_Hans: 瑞典
+          pt_BR: Sweden
+      - value: CH
+        label:
+          en_US: Switzerland
+          zh_Hans: 瑞士
+          pt_BR: Switzerland
+      - value: TW
+        label:
+          en_US: Taiwan
+          zh_Hans: 台湾
+          pt_BR: Taiwan
+      - value: TH
+        label:
+          en_US: Thailand
+          zh_Hans: 泰国
+          pt_BR: Thailand
+      - value: TR
+        label:
+          en_US: Turkey
+          zh_Hans: 土耳其
+          pt_BR: Turkey
+      - value: GB
+        label:
+          en_US: United Kingdom
+          zh_Hans: 英国
+          pt_BR: United Kingdom
+      - value: US
+        label:
+          en_US: United States
+          zh_Hans: 美国
+          pt_BR: United States
+  - name: hl
+    type: select
+    label:
+      en_US: Language
+      zh_Hans: 语言
+    human_description:
+      en_US: Defines the interface language of the search. Default is "en".
+      zh_Hans: 定义搜索的界面语言。默认为“en”。
+    required: false
+    default: en
+    form: form
+    options:
+      - value: ar
+        label:
+          en_US: Arabic
+          zh_Hans: 阿拉伯语
+      - value: bg
+        label:
+          en_US: Bulgarian
+          zh_Hans: 保加利亚语
+      - value: ca
+        label:
+          en_US: Catalan
+          zh_Hans: 加泰罗尼亚语
+      - value: zh-cn
+        label:
+          en_US: Chinese (Simplified)
+          zh_Hans: 中文(简体)
+      - value: zh-tw
+        label:
+          en_US: Chinese (Traditional)
+          zh_Hans: 中文(繁体)
+      - value: cs
+        label:
+          en_US: Czech
+          zh_Hans: 捷克语
+      - value: da
+        label:
+          en_US: Danish
+          zh_Hans: 丹麦语
+      - value: nl
+        label:
+          en_US: Dutch
+          zh_Hans: 荷兰语
+      - value: en
+        label:
+          en_US: English
+          zh_Hans: 英语
+      - value: et
+        label:
+          en_US: Estonian
+          zh_Hans: 爱沙尼亚语
+      - value: fi
+        label:
+          en_US: Finnish
+          zh_Hans: 芬兰语
+      - value: fr
+        label:
+          en_US: French
+          zh_Hans: 法语
+      - value: de
+        label:
+          en_US: German
+          zh_Hans: 德语
+      - value: el
+        label:
+          en_US: Greek
+          zh_Hans: 希腊语
+      - value: iw
+        label:
+          en_US: Hebrew
+          zh_Hans: 希伯来语
+      - value: hi
+        label:
+          en_US: Hindi
+          zh_Hans: 印地语
+      - value: hu
+        label:
+          en_US: Hungarian
+          zh_Hans: 匈牙利语
+      - value: id
+        label:
+          en_US: Indonesian
+          zh_Hans: 印尼语
+      - value: it
+        label:
+          en_US: Italian
+          zh_Hans: 意大利语
+      - value: jp
+        label:
+          en_US: Japanese
+          zh_Hans: 日语
+      - value: kn
+        label:
+          en_US: Kannada
+          zh_Hans: 卡纳达语
+      - value: ko
+        label:
+          en_US: Korean
+          zh_Hans: 韩语
+      - value: lv
+        label:
+          en_US: Latvian
+          zh_Hans: 拉脱维亚语
+      - value: lt
+        label:
+          en_US: Lithuanian
+          zh_Hans: 立陶宛语
+      - value: my
+        label:
+          en_US: Malay
+          zh_Hans: 马来语
+      - value: ml
+        label:
+          en_US: Malayalam
+          zh_Hans: 马拉雅拉姆语
+      - value: mr
+        label:
+          en_US: Marathi
+          zh_Hans: 马拉地语
+      - value: "no"
+        label:
+          en_US: Norwegian
+          zh_Hans: 挪威语
+      - value: pl
+        label:
+          en_US: Polish
+          zh_Hans: 波兰语
+      - value: pt-br
+        label:
+          en_US: Portuguese (Brazil)
+          zh_Hans: 葡萄牙语(巴西)
+      - value: pt-pt
+        label:
+          en_US: Portuguese (Portugal)
+          zh_Hans: 葡萄牙语(葡萄牙)
+      - value: pa
+        label:
+          en_US: Punjabi
+          zh_Hans: 旁遮普语
+      - value: ro
+        label:
+          en_US: Romanian
+          zh_Hans: 罗马尼亚语
+      - value: ru
+        label:
+          en_US: Russian
+          zh_Hans: 俄语
+      - value: sr
+        label:
+          en_US: Serbian
+          zh_Hans: 塞尔维亚语
+      - value: sk
+        label:
+          en_US: Slovak
+          zh_Hans: 斯洛伐克语
+      - value: sl
+        label:
+          en_US: Slovenian
+          zh_Hans: 斯洛文尼亚语
+      - value: es
+        label:
+          en_US: Spanish
+          zh_Hans: 西班牙语
+      - value: sv
+        label:
+          en_US: Swedish
+          zh_Hans: 瑞典语
+      - value: ta
+        label:
+          en_US: Tamil
+          zh_Hans: 泰米尔语
+      - value: te
+        label:
+          en_US: Telugu
+          zh_Hans: 泰卢固语
+      - value: th
+        label:
+          en_US: Thai
+          zh_Hans: 泰语
+      - value: tr
+        label:
+          en_US: Turkish
+          zh_Hans: 土耳其语
+      - value: uk
+        label:
+          en_US: Ukrainian
+          zh_Hans: 乌克兰语
+      - value: vi
+        label:
+          en_US: Vietnamese
+          zh_Hans: 越南语
+  - name: is_remote
+    type: select
+    label:
+      en_US: is_remote
+      zh_Hans: 很遥远
+    human_description:
+      en_US: Filter results based on the work arrangement. Set it to true to find jobs that offer work from home or remote work opportunities.
+      zh_Hans: 根据工作安排过滤结果。将其设置为 true 可查找提供在家工作或远程工作机会的工作。
+    required: false
+    form: form
+    options:
+      - value: true
+        label:
+          en_US: "true"
+          zh_Hans: "true"
+      - value: false
+        label:
+          en_US: "false"
+          zh_Hans: "false"

+ 92 - 0
api/core/tools/provider/builtin/searchapi/tools/google_news.py

@@ -0,0 +1,92 @@
+from typing import Any, Union
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+SEARCH_API_URL = "https://www.searchapi.io/api/v1/search"
+
+class SearchAPI:
+    """
+    SearchAPI tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize SearchAPI tool provider."""
+        self.searchapi_api_key = api_key
+
+    def run(self, query: str, **kwargs: Any) -> str:
+        """Run query through SearchAPI and parse result."""
+        type = kwargs.get("result_type", "text")
+        return self._process_response(self.results(query, **kwargs), type=type)
+
+    def results(self, query: str, **kwargs: Any) -> dict:
+        """Run query through SearchAPI and return the raw result."""
+        params = self.get_params(query, **kwargs)
+        response = requests.get(
+            url=SEARCH_API_URL,
+            params=params,
+            headers={"Authorization": f"Bearer {self.searchapi_api_key}"},
+        )
+        response.raise_for_status()
+        return response.json()
+
+    def get_params(self, query: str, **kwargs: Any) -> dict[str, str]:
+        """Get parameters for SearchAPI."""
+        return {
+            "engine": "google_news",
+            "q": query,
+            **{key: value for key, value in kwargs.items() if value not in [None, ""]},
+        }
+
+    @staticmethod
+    def _process_response(res: dict, type: str) -> str:
+        """Process response from SearchAPI."""
+        if "error" in res.keys():
+            raise ValueError(f"Got error from SearchApi: {res['error']}")
+
+        toret = ""
+        if type == "text":
+            if "organic_results" in res.keys() and "snippet" in res["organic_results"][0].keys():
+                for item in res["organic_results"]:
+                    toret += "content: " + item["snippet"] + "\n" + "link: " + item["link"] + "\n"
+            if "top_stories" in res.keys() and "title" in res["top_stories"][0].keys():
+                for item in res["top_stories"]:
+                    toret += "title: " + item["title"] + "\n" + "link: " + item["link"] + "\n"
+            if toret == "":
+                toret = "No good search result found"
+
+        elif type == "link":
+            if "organic_results" in res.keys() and "title" in res["organic_results"][0].keys():
+                for item in res["organic_results"]:
+                    toret += f"[{item['title']}]({item['link']})\n"
+            elif "top_stories" in res.keys() and "title" in res["top_stories"][0].keys():
+                for item in res["top_stories"]:
+                    toret += f"[{item['title']}]({item['link']})\n"
+            else:
+                toret = "No good search result found"
+        return toret
+
+class GoogleNewsTool(BuiltinTool):
+    def _invoke(self,
+                user_id: str,
+                tool_parameters: dict[str, Any],
+        ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SearchApi tool.
+        """
+        query = tool_parameters['query']
+        result_type = tool_parameters['result_type']
+        num = tool_parameters.get("num", 10)
+        google_domain = tool_parameters.get("google_domain", "google.com")
+        gl = tool_parameters.get("gl", "us")
+        hl = tool_parameters.get("hl", "en")
+        location = tool_parameters.get("location", None)
+
+        api_key = self.runtime.credentials['searchapi_api_key']
+        result = SearchAPI(api_key).run(query, result_type=result_type, num=num, google_domain=google_domain, gl=gl, hl=hl, location=location)
+
+        if result_type == 'text':
+            return self.create_text_message(text=result)
+        return self.create_link_message(link=result)

+ 482 - 0
api/core/tools/provider/builtin/searchapi/tools/google_news.yaml

@@ -0,0 +1,482 @@
+identity:
+  name: google_news_api
+  author: SearchApi
+  label:
+    en_US: Google News API
+    zh_Hans: Google News API
+description:
+  human:
+    en_US: A tool to retrieve organic search results snippets and links from Google News engine.
+    zh_Hans: 一种从 Google 新闻引擎检索有机搜索结果片段和链接的工具。
+  llm: A tool to retrieve organic search results snippets and links from Google News engine.
+parameters:
+  - name: query
+    type: string
+    required: true
+    label:
+      en_US: Query
+      zh_Hans: 询问
+    human_description:
+      en_US: Defines the query you want to search.
+      zh_Hans: 定义您要搜索的查询。
+    llm_description: Defines the search query you want to search.
+    form: llm
+  - name: result_type
+    type: select
+    required: true
+    options:
+      - value: text
+        label:
+          en_US: text
+          zh_Hans: 文本
+      - value: link
+        label:
+          en_US: link
+          zh_Hans: 链接
+    default: text
+    label:
+      en_US: Result type
+      zh_Hans: 结果类型
+    human_description:
+      en_US: used for selecting the result type, text or link.
+      zh_Hans: 用于选择结果类型,使用文本还是链接进行展示。
+    form: form
+  - name: location
+    type: string
+    required: false
+    label:
+      en_US: Location
+      zh_Hans: 询问
+    human_description:
+      en_US: Defines from where you want the search to originate. (For example - New York)
+      zh_Hans: 定义您想要搜索的起始位置。 (例如 - 纽约)
+    llm_description: Defines from where you want the search to originate. (For example - New York)
+    form: llm
+  - name: gl
+    type: select
+    label:
+      en_US: Country
+      zh_Hans: 国家
+    required: false
+    human_description:
+      en_US: Defines the country of the search. Default is "US".
+      zh_Hans: 定义搜索的国家/地区。默认为“美国”。
+    llm_description: Defines the gl parameter of the Google search.
+    form: form
+    default: US
+    options:
+      - value: AR
+        label:
+          en_US: Argentina
+          zh_Hans: 阿根廷
+          pt_BR: Argentina
+      - value: AU
+        label:
+          en_US: Australia
+          zh_Hans: 澳大利亚
+          pt_BR: Australia
+      - value: AT
+        label:
+          en_US: Austria
+          zh_Hans: 奥地利
+          pt_BR: Austria
+      - value: BE
+        label:
+          en_US: Belgium
+          zh_Hans: 比利时
+          pt_BR: Belgium
+      - value: BR
+        label:
+          en_US: Brazil
+          zh_Hans: 巴西
+          pt_BR: Brazil
+      - value: CA
+        label:
+          en_US: Canada
+          zh_Hans: 加拿大
+          pt_BR: Canada
+      - value: CL
+        label:
+          en_US: Chile
+          zh_Hans: 智利
+          pt_BR: Chile
+      - value: CO
+        label:
+          en_US: Colombia
+          zh_Hans: 哥伦比亚
+          pt_BR: Colombia
+      - value: CN
+        label:
+          en_US: China
+          zh_Hans: 中国
+          pt_BR: China
+      - value: CZ
+        label:
+          en_US: Czech Republic
+          zh_Hans: 捷克共和国
+          pt_BR: Czech Republic
+      - value: DK
+        label:
+          en_US: Denmark
+          zh_Hans: 丹麦
+          pt_BR: Denmark
+      - value: FI
+        label:
+          en_US: Finland
+          zh_Hans: 芬兰
+          pt_BR: Finland
+      - value: FR
+        label:
+          en_US: France
+          zh_Hans: 法国
+          pt_BR: France
+      - value: DE
+        label:
+          en_US: Germany
+          zh_Hans: 德国
+          pt_BR: Germany
+      - value: HK
+        label:
+          en_US: Hong Kong
+          zh_Hans: 香港
+          pt_BR: Hong Kong
+      - value: IN
+        label:
+          en_US: India
+          zh_Hans: 印度
+          pt_BR: India
+      - value: ID
+        label:
+          en_US: Indonesia
+          zh_Hans: 印度尼西亚
+          pt_BR: Indonesia
+      - value: IT
+        label:
+          en_US: Italy
+          zh_Hans: 意大利
+          pt_BR: Italy
+      - value: JP
+        label:
+          en_US: Japan
+          zh_Hans: 日本
+          pt_BR: Japan
+      - value: KR
+        label:
+          en_US: Korea
+          zh_Hans: 韩国
+          pt_BR: Korea
+      - value: MY
+        label:
+          en_US: Malaysia
+          zh_Hans: 马来西亚
+          pt_BR: Malaysia
+      - value: MX
+        label:
+          en_US: Mexico
+          zh_Hans: 墨西哥
+          pt_BR: Mexico
+      - value: NL
+        label:
+          en_US: Netherlands
+          zh_Hans: 荷兰
+          pt_BR: Netherlands
+      - value: NZ
+        label:
+          en_US: New Zealand
+          zh_Hans: 新西兰
+          pt_BR: New Zealand
+      - value: NO
+        label:
+          en_US: Norway
+          zh_Hans: 挪威
+          pt_BR: Norway
+      - value: PH
+        label:
+          en_US: Philippines
+          zh_Hans: 菲律宾
+          pt_BR: Philippines
+      - value: PL
+        label:
+          en_US: Poland
+          zh_Hans: 波兰
+          pt_BR: Poland
+      - value: PT
+        label:
+          en_US: Portugal
+          zh_Hans: 葡萄牙
+          pt_BR: Portugal
+      - value: RU
+        label:
+          en_US: Russia
+          zh_Hans: 俄罗斯
+          pt_BR: Russia
+      - value: SA
+        label:
+          en_US: Saudi Arabia
+          zh_Hans: 沙特阿拉伯
+          pt_BR: Saudi Arabia
+      - value: SG
+        label:
+          en_US: Singapore
+          zh_Hans: 新加坡
+          pt_BR: Singapore
+      - value: ZA
+        label:
+          en_US: South Africa
+          zh_Hans: 南非
+          pt_BR: South Africa
+      - value: ES
+        label:
+          en_US: Spain
+          zh_Hans: 西班牙
+          pt_BR: Spain
+      - value: SE
+        label:
+          en_US: Sweden
+          zh_Hans: 瑞典
+          pt_BR: Sweden
+      - value: CH
+        label:
+          en_US: Switzerland
+          zh_Hans: 瑞士
+          pt_BR: Switzerland
+      - value: TW
+        label:
+          en_US: Taiwan
+          zh_Hans: 台湾
+          pt_BR: Taiwan
+      - value: TH
+        label:
+          en_US: Thailand
+          zh_Hans: 泰国
+          pt_BR: Thailand
+      - value: TR
+        label:
+          en_US: Turkey
+          zh_Hans: 土耳其
+          pt_BR: Turkey
+      - value: GB
+        label:
+          en_US: United Kingdom
+          zh_Hans: 英国
+          pt_BR: United Kingdom
+      - value: US
+        label:
+          en_US: United States
+          zh_Hans: 美国
+          pt_BR: United States
+  - name: hl
+    type: select
+    label:
+      en_US: Language
+      zh_Hans: 语言
+    human_description:
+      en_US: Defines the interface language of the search. Default is "en".
+      zh_Hans: 定义搜索的界面语言。默认为“en”。
+    required: false
+    default: en
+    form: form
+    options:
+      - value: ar
+        label:
+          en_US: Arabic
+          zh_Hans: 阿拉伯语
+      - value: bg
+        label:
+          en_US: Bulgarian
+          zh_Hans: 保加利亚语
+      - value: ca
+        label:
+          en_US: Catalan
+          zh_Hans: 加泰罗尼亚语
+      - value: zh-cn
+        label:
+          en_US: Chinese (Simplified)
+          zh_Hans: 中文(简体)
+      - value: zh-tw
+        label:
+          en_US: Chinese (Traditional)
+          zh_Hans: 中文(繁体)
+      - value: cs
+        label:
+          en_US: Czech
+          zh_Hans: 捷克语
+      - value: da
+        label:
+          en_US: Danish
+          zh_Hans: 丹麦语
+      - value: nl
+        label:
+          en_US: Dutch
+          zh_Hans: 荷兰语
+      - value: en
+        label:
+          en_US: English
+          zh_Hans: 英语
+      - value: et
+        label:
+          en_US: Estonian
+          zh_Hans: 爱沙尼亚语
+      - value: fi
+        label:
+          en_US: Finnish
+          zh_Hans: 芬兰语
+      - value: fr
+        label:
+          en_US: French
+          zh_Hans: 法语
+      - value: de
+        label:
+          en_US: German
+          zh_Hans: 德语
+      - value: el
+        label:
+          en_US: Greek
+          zh_Hans: 希腊语
+      - value: iw
+        label:
+          en_US: Hebrew
+          zh_Hans: 希伯来语
+      - value: hi
+        label:
+          en_US: Hindi
+          zh_Hans: 印地语
+      - value: hu
+        label:
+          en_US: Hungarian
+          zh_Hans: 匈牙利语
+      - value: id
+        label:
+          en_US: Indonesian
+          zh_Hans: 印尼语
+      - value: it
+        label:
+          en_US: Italian
+          zh_Hans: 意大利语
+      - value: jp
+        label:
+          en_US: Japanese
+          zh_Hans: 日语
+      - value: kn
+        label:
+          en_US: Kannada
+          zh_Hans: 卡纳达语
+      - value: ko
+        label:
+          en_US: Korean
+          zh_Hans: 韩语
+      - value: lv
+        label:
+          en_US: Latvian
+          zh_Hans: 拉脱维亚语
+      - value: lt
+        label:
+          en_US: Lithuanian
+          zh_Hans: 立陶宛语
+      - value: my
+        label:
+          en_US: Malay
+          zh_Hans: 马来语
+      - value: ml
+        label:
+          en_US: Malayalam
+          zh_Hans: 马拉雅拉姆语
+      - value: mr
+        label:
+          en_US: Marathi
+          zh_Hans: 马拉地语
+      - value: "no"
+        label:
+          en_US: Norwegian
+          zh_Hans: 挪威语
+      - value: pl
+        label:
+          en_US: Polish
+          zh_Hans: 波兰语
+      - value: pt-br
+        label:
+          en_US: Portuguese (Brazil)
+          zh_Hans: 葡萄牙语(巴西)
+      - value: pt-pt
+        label:
+          en_US: Portuguese (Portugal)
+          zh_Hans: 葡萄牙语(葡萄牙)
+      - value: pa
+        label:
+          en_US: Punjabi
+          zh_Hans: 旁遮普语
+      - value: ro
+        label:
+          en_US: Romanian
+          zh_Hans: 罗马尼亚语
+      - value: ru
+        label:
+          en_US: Russian
+          zh_Hans: 俄语
+      - value: sr
+        label:
+          en_US: Serbian
+          zh_Hans: 塞尔维亚语
+      - value: sk
+        label:
+          en_US: Slovak
+          zh_Hans: 斯洛伐克语
+      - value: sl
+        label:
+          en_US: Slovenian
+          zh_Hans: 斯洛文尼亚语
+      - value: es
+        label:
+          en_US: Spanish
+          zh_Hans: 西班牙语
+      - value: sv
+        label:
+          en_US: Swedish
+          zh_Hans: 瑞典语
+      - value: ta
+        label:
+          en_US: Tamil
+          zh_Hans: 泰米尔语
+      - value: te
+        label:
+          en_US: Telugu
+          zh_Hans: 泰卢固语
+      - value: th
+        label:
+          en_US: Thai
+          zh_Hans: 泰语
+      - value: tr
+        label:
+          en_US: Turkish
+          zh_Hans: 土耳其语
+      - value: uk
+        label:
+          en_US: Ukrainian
+          zh_Hans: 乌克兰语
+      - value: vi
+        label:
+          en_US: Vietnamese
+          zh_Hans: 越南语
+  - name: google_domain
+    type: string
+    required: false
+    label:
+      en_US: google_domain
+      zh_Hans: google_domain
+    human_description:
+      en_US: Defines the Google domain of the search. Default is "google.com".
+      zh_Hans: 定义搜索的 Google 域。默认为“google.com”。
+    llm_description: Defines Google domain in which you want to search.
+    form: llm
+  - name: num
+    type: number
+    required: false
+    label:
+      en_US: num
+      zh_Hans: num
+    human_description:
+      en_US: Specifies the number of results to display per page. Default is 10. Max number - 100, min - 1.
+      zh_Hans: 指定每页显示的结果数。默认值为 10。最大数量 - 100,最小数量 - 1。
+      pt_BR: Specifies the number of results to display per page. Default is 10. Max number - 100, min - 1.
+    llm_description: Specifies the num of results to display per page.
+    form: llm

+ 72 - 0
api/core/tools/provider/builtin/searchapi/tools/youtube_transcripts.py

@@ -0,0 +1,72 @@
+from typing import Any, Union
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+SEARCH_API_URL = "https://www.searchapi.io/api/v1/search"
+
+class SearchAPI:
+    """
+    SearchAPI tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize SearchAPI tool provider."""
+        self.searchapi_api_key = api_key
+
+    def run(self, video_id: str, language: str, **kwargs: Any) -> str:
+        """Run video_id through SearchAPI and parse result."""
+        return self._process_response(self.results(video_id, language, **kwargs))
+
+    def results(self, video_id: str, language: str, **kwargs: Any) -> dict:
+        """Run video_id through SearchAPI and return the raw result."""
+        params = self.get_params(video_id, language, **kwargs)
+        response = requests.get(
+            url=SEARCH_API_URL,
+            params=params,
+            headers={"Authorization": f"Bearer {self.searchapi_api_key}"},
+        )
+        response.raise_for_status()
+        return response.json()
+
+    def get_params(self, video_id: str, language: str, **kwargs: Any) -> dict[str, str]:
+        """Get parameters for SearchAPI."""
+        return {
+            "engine": "youtube_transcripts",
+            "video_id": video_id,
+            "lang": language if language else "en",
+            **{key: value for key, value in kwargs.items() if value not in [None, ""]},
+        }
+
+    @staticmethod
+    def _process_response(res: dict) -> str:
+        """Process response from SearchAPI."""
+        if "error" in res.keys():
+            raise ValueError(f"Got error from SearchApi: {res['error']}")
+
+        toret = ""
+        if "transcripts" in res.keys() and "text" in res["transcripts"][0].keys():
+            for item in res["transcripts"]:
+                toret += item["text"] + " "
+        if toret == "":
+            toret = "No good search result found"
+
+        return toret
+
+class YoutubeTranscriptsTool(BuiltinTool):
+    def _invoke(self,
+                user_id: str,
+                tool_parameters: dict[str, Any],
+        ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SearchApi tool.
+        """
+        video_id = tool_parameters['video_id']
+        language = tool_parameters.get('language', "en")
+
+        api_key = self.runtime.credentials['searchapi_api_key']
+        result = SearchAPI(api_key).run(video_id, language=language)
+
+        return self.create_text_message(text=result)

+ 34 - 0
api/core/tools/provider/builtin/searchapi/tools/youtube_transcripts.yaml

@@ -0,0 +1,34 @@
+identity:
+  name: youtube_transcripts_api
+  author: SearchApi
+  label:
+    en_US: YouTube Transcripts API
+    zh_Hans: YouTube 脚本 API
+description:
+  human:
+    en_US: A tool to retrieve transcripts from the specific YouTube video.
+    zh_Hans: 一种从特定 YouTube 视频检索文字记录的工具。
+  llm: A tool to retrieve transcripts from the specific YouTube video.
+parameters:
+  - name: video_id
+    type: string
+    required: true
+    label:
+      en_US: video_id
+      zh_Hans: 视频ID
+    human_description:
+      en_US: Used to define the video you want to search. You can find the video id's in YouTube page that appears in URL. For example - https://www.youtube.com/watch?v=video_id.
+      zh_Hans: 用于定义要搜索的视频。您可以在 URL 中显示的 YouTube 页面中找到视频 ID。例如 - https://www.youtube.com/watch?v=video_id。
+    llm_description: Used to define the video you want to search.
+    form: llm
+  - name: language
+    type: string
+    required: false
+    label:
+      en_US: language
+      zh_Hans: 语言
+    human_description:
+      en_US: Used to set the language for transcripts. The default value is "en". You can find all supported languages in SearchApi documentation.
+      zh_Hans: 用于设置成绩单的语言。默认值为“en”。您可以在 SearchApi 文档中找到所有支持的语言。
+    llm_description: Used to set the language for transcripts.
+    form: llm