Przeglądaj źródła

feat(Tools): Add Serply Web/Job/Scholar/News Search tool for more options (#5186)

Co-authored-by: teampen <136991215+teampen@users.noreply.github.com>
Serply 10 miesięcy temu
rodzic
commit
795714bc2f

+ 23 - 0
api/core/tools/provider/builtin/websearch/_assets/icon.svg

@@ -0,0 +1,23 @@
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="100%" viewBox="0 0 176 323" enable-background="new 0 0 176 323" xml:space="preserve">
+<path fill="#F03F30" opacity="1.000000" stroke="none"
+	d="
+M41.044968,257.626282
+	C27.696184,236.155075 31.957315,212.441483 52.045750,197.344925
+	C79.002586,177.086716 106.149689,157.081665 133.645813,136.647858
+	C139.760117,144.272156 142.374191,152.876511 142.340073,162.386032
+	C142.284485,177.881165 135.226303,189.665787 122.998329,198.722351
+	C97.096687,217.906250 71.232124,237.140259 45.308189,256.293945
+	C44.162773,257.140198 42.580475,257.395203 41.044968,257.626282
+z"/>
+<path fill="#F03F30" opacity="1.000000" stroke="none"
+	d="
+M99.096405,138.076599
+	C79.500099,152.629852 60.200359,166.985321 40.905945,181.336838
+	C27.259842,163.327515 29.469128,137.268982 46.741867,123.517693
+	C67.654190,106.868828 89.408379,91.276695 110.817230,75.252617
+	C117.976021,69.894424 125.193146,64.614182 132.376923,59.303246
+	C147.358932,78.560143 142.926590,105.166771 122.208939,120.964401
+	C114.681282,126.704391 107.002701,132.246460 99.096405,138.076599
+z"/>
+</svg>

+ 51 - 0
api/core/tools/provider/builtin/websearch/tools/get_markdown.py

@@ -0,0 +1,51 @@
+from typing import Any, Union
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+BASE_URL = "https://api.serply.io/v1/request"
+
+
+class SerplyApi:
+    """
+    SerplyAPI tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize SerplyAPI tool provider."""
+        self.serply_api_key = api_key
+
+    def run(self, url: str, **kwargs: Any) -> str:
+        """Run query through SerplyAPI and parse result."""
+
+        location = kwargs.get("location", "US")
+
+        headers = {
+            "X-API-KEY": self.serply_api_key,
+            "X-User-Agent": kwargs.get("device", "desktop"),
+            "X-Proxy-Location": location,
+            "User-Agent": "Dify",
+        }
+        data = {"url": url, "method": "GET", "response_type": "markdown"}
+        res = requests.post(url, headers=headers, json=data)
+        return res.text
+
+
+class GetMarkdownTool(BuiltinTool):
+    def _invoke(
+        self,
+        user_id: str,
+        tool_parameters: dict[str, Any],
+    ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SerplyApi tool.
+        """
+        url = tool_parameters["url"]
+        location = tool_parameters.get("location", None)
+
+        api_key = self.runtime.credentials["serply_api_key"]
+        result = SerplyApi(api_key).run(url, location=location)
+
+        return self.create_text_message(text=result)

+ 96 - 0
api/core/tools/provider/builtin/websearch/tools/get_markdown.yaml

@@ -0,0 +1,96 @@
+identity:
+  name: get_markdown
+  author: Dify
+  label:
+    en_US: Get Markdown API
+    zh_Hans: Get Markdown API
+description:
+  human:
+    en_US: A tool to perform convert a webpage to markdown to make it easier for LLMs to understand.
+    zh_Hans: 一个将网页转换为 Markdown 的工具,以便模型更容易理解
+  llm: A tool to perform convert a webpage to markdown to make it easier for LLMs to understand.
+parameters:
+  - name: url
+    type: string
+    required: true
+    label:
+      en_US: URL
+      zh_Hans: URL
+    human_description:
+      en_US: URL that you want to grab the content from
+      zh_Hans: 您要从中获取内容的 URL
+    llm_description: Defines the link want to grab content from.
+    form: llm
+  - name: location
+    type: string
+    required: false
+    default: US
+    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: form
+    options:
+      - value: AU
+        label:
+          en_US: Australia
+          zh_Hans: 澳大利亚
+          pt_BR: Australia
+      - value: BR
+        label:
+          en_US: Brazil
+          zh_Hans: 巴西
+          pt_BR: Brazil
+      - value: CA
+        label:
+          en_US: Canada
+          zh_Hans: 加拿大
+          pt_BR: Canada
+      - value: DE
+        label:
+          en_US: Germany
+          zh_Hans: 德国
+          pt_BR: Germany
+      - value: FR
+        label:
+          en_US: France
+          zh_Hans: 法国
+          pt_BR: France
+      - 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
+      - value: JP
+        label:
+          en_US: Japan
+          zh_Hans: 日本
+          pt_BR: Japan
+      - value: IN
+        label:
+          en_US: India
+          zh_Hans: 印度
+          pt_BR: India
+      - value: KR
+        label:
+          en_US: Korea
+          zh_Hans: 韩国
+          pt_BR: Korea
+      - value: SG
+        label:
+          en_US: Singapore
+          zh_Hans: 新加坡
+          pt_BR: Singapore
+      - value: SE
+        label:
+          en_US: Sweden
+          zh_Hans: 瑞典
+          pt_BR: Sweden

+ 86 - 0
api/core/tools/provider/builtin/websearch/tools/job_search.py

@@ -0,0 +1,86 @@
+from typing import Any, Union
+from urllib.parse import urlencode
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+BASE_URL = "https://api.serply.io/v1/news/"
+
+
+class SerplyApi:
+    """
+    SerplyAPI tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize SerplyAPI tool provider."""
+        self.serply_api_key = api_key
+
+    def run(self, query: str, **kwargs: Any) -> str:
+        """Run query through SerplyAPI and parse result."""
+        params = {"q": query, "hl": kwargs.get("hl", "en"), "gl": kwargs.get("gl", "US"), "num": kwargs.get("num", 10)}
+        location = kwargs.get("location", "US")
+
+        headers = {
+            "X-API-KEY": self.serply_api_key,
+            "X-User-Agent": kwargs.get("device", "desktop"),
+            "X-Proxy-Location": location,
+            "User-Agent": "Dify",
+        }
+
+        url = f"{BASE_URL}{urlencode(params)}"
+        res = requests.get(
+            url,
+            headers=headers,
+        )
+        res = res.json()
+
+        return self.parse_results(res)
+
+    @staticmethod
+    def parse_results(res: dict) -> str:
+        """Process response from Serply Job Search."""
+        jobs = res.get("jobs", [])
+        if not jobs:
+            raise ValueError(f"Got error from Serply: {res}")
+
+        string = []
+        for job in jobs[:10]:
+            try:
+                string.append(
+                    "\n".join([
+                        f"Position: {job['position']}",
+                        f"Employer: {job['employer']}",
+                        f"Location: {job['location']}",
+                        f"Link: {job['link']}",
+                        f"""Highest: {", ".join([h for h in job["highlights"]])}""",
+                        "---",
+                    ])
+                )
+            except KeyError:
+                continue
+
+        content = "\n".join(string)
+        return f"\nJobs results:\n {content}\n"
+
+
+class JobSearchTool(BuiltinTool):
+    def _invoke(
+        self,
+        user_id: str,
+        tool_parameters: dict[str, Any],
+    ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SerplyApi tool.
+        """
+        query = tool_parameters["query"]
+        gl = tool_parameters.get("gl", "us")
+        hl = tool_parameters.get("hl", "en")
+        location = tool_parameters.get("location", None)
+
+        api_key = self.runtime.credentials["serply_api_key"]
+        result = SerplyApi(api_key).run(query, gl=gl, hl=hl, location=location)
+
+        return self.create_text_message(text=result)

+ 41 - 0
api/core/tools/provider/builtin/websearch/tools/job_search.yaml

@@ -0,0 +1,41 @@
+identity:
+  name: job_search
+  author: Dify
+  label:
+    en_US: Job Search API
+    zh_Hans: Job Search 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: location
+    type: string
+    required: false
+    default: US
+    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: form
+    options:
+      - value: US
+        label:
+          en_US: United States
+          zh_Hans: 美国
+          pt_BR: United States

+ 88 - 0
api/core/tools/provider/builtin/websearch/tools/news_search.py

@@ -0,0 +1,88 @@
+from typing import Any, Union
+from urllib.parse import urlencode
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+BASE_URL = "https://api.serply.io/v1/news/"
+
+
+class SerplyApi:
+    """
+    SerplyApi tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize SerplyApi tool provider."""
+        self.serply_api_key = api_key
+
+    def run(self, query: str, **kwargs: Any) -> str:
+        """Run query through SerplyApi and parse result."""
+        params = {"q": query, "hl": kwargs.get("hl", "en"), "gl": kwargs.get("gl", "US"), "num": kwargs.get("num", 10)}
+        location = kwargs.get("location", "US")
+
+        headers = {
+            "X-API-KEY": self.serply_api_key,
+            "X-User-Agent": kwargs.get("device", "desktop"),
+            "X-Proxy-Location": location,
+            "User-Agent": "Dify",
+        }
+
+        url = f"{BASE_URL}{urlencode(params)}"
+        res = requests.get(
+            url,
+            headers=headers,
+        )
+        res = res.json()
+
+        return self.parse_results(res)
+
+    @staticmethod
+    def parse_results(res: dict) -> str:
+        """Process response from Serply News Search."""
+        news = res.get("entries", [])
+        if not news:
+            raise ValueError(f"Got error from Serply: {res}")
+
+        string = []
+        for entry in news:
+            try:
+                # follow url
+                r = requests.get(entry["link"])
+                final_link = r.history[-1].headers["Location"]
+                string.append(
+                    "\n".join([
+                        f"Title: {entry['title']}",
+                        f"Link: {final_link}",
+                        f"Source: {entry['source']['title']}",
+                        f"Published: {entry['published']}",
+                        "---",
+                    ])
+                )
+            except KeyError:
+                continue
+
+        content = "\n".join(string)
+        return f"\nNews:\n {content}\n"
+
+
+class NewsSearchTool(BuiltinTool):
+    def _invoke(
+        self,
+        user_id: str,
+        tool_parameters: dict[str, Any],
+    ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SerplyApi tool.
+        """
+        query = tool_parameters["query"]
+        gl = tool_parameters.get("gl", "us")
+        hl = tool_parameters.get("hl", "en")
+        location = tool_parameters.get("location", None)
+
+        api_key = self.runtime.credentials["serply_api_key"]
+        result = SerplyApi(api_key).run(query, gl=gl, hl=hl, location=location)
+
+        return self.create_text_message(text=result)

+ 501 - 0
api/core/tools/provider/builtin/websearch/tools/news_search.yaml

@@ -0,0 +1,501 @@
+identity:
+  name: news_search
+  author: Dify
+  label:
+    en_US: News Search API
+    zh_Hans: News Search 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: location
+    type: string
+    required: false
+    default: US
+    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: form
+    options:
+      - value: AU
+        label:
+          en_US: Australia
+          zh_Hans: 澳大利亚
+          pt_BR: Australia
+      - value: BR
+        label:
+          en_US: Brazil
+          zh_Hans: 巴西
+          pt_BR: Brazil
+      - value: CA
+        label:
+          en_US: Canada
+          zh_Hans: 加拿大
+          pt_BR: Canada
+      - value: DE
+        label:
+          en_US: Germany
+          zh_Hans: 德国
+          pt_BR: Germany
+      - value: FR
+        label:
+          en_US: France
+          zh_Hans: 法国
+          pt_BR: France
+      - 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
+      - value: JP
+        label:
+          en_US: Japan
+          zh_Hans: 日本
+          pt_BR: Japan
+      - value: IN
+        label:
+          en_US: India
+          zh_Hans: 印度
+          pt_BR: India
+      - value: KR
+        label:
+          en_US: Korea
+          zh_Hans: 韩国
+          pt_BR: Korea
+      - value: SG
+        label:
+          en_US: Singapore
+          zh_Hans: 新加坡
+          pt_BR: Singapore
+      - value: SE
+        label:
+          en_US: Sweden
+          zh_Hans: 瑞典
+          pt_BR: Sweden
+  - 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: ja
+        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: 越南语

+ 91 - 0
api/core/tools/provider/builtin/websearch/tools/scholar_search.py

@@ -0,0 +1,91 @@
+from typing import Any, Union
+from urllib.parse import urlencode
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+BASE_URL = "https://api.serply.io/v1/scholar/"
+
+
+class SerplyApi:
+    """
+    SerplyApi tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize SerplyApi tool provider."""
+        self.serply_api_key = api_key
+
+    def run(self, query: str, **kwargs: Any) -> str:
+        """Run query through SerplyApi and parse result."""
+        params = {"q": query, "hl": kwargs.get("hl", "en"), "gl": kwargs.get("gl", "US"), "num": kwargs.get("num", 10)}
+        location = kwargs.get("location", "US")
+
+        headers = {
+            "X-API-KEY": self.serply_api_key,
+            "X-User-Agent": kwargs.get("device", "desktop"),
+            "X-Proxy-Location": location,
+            "User-Agent": "Dify",
+        }
+
+        url = f"{BASE_URL}{urlencode(params)}"
+        res = requests.get(
+            url,
+            headers=headers,
+        )
+        res = res.json()
+
+        return self.parse_results(res)
+
+    @staticmethod
+    def parse_results(res: dict) -> str:
+        """Process response from Serply News Search."""
+        articles = res.get("articles", [])
+        if not articles:
+            raise ValueError(f"Got error from Serply: {res}")
+
+        string = []
+        for article in articles:
+            try:
+                if "doc" in article:
+                    link = article["doc"]["link"]
+                else:
+                    link = article["link"]
+                authors = [author["name"] for author in article["author"]["authors"]]
+                string.append(
+                    "\n".join([
+                        f"Title: {article['title']}",
+                        f"Link: {link}",
+                        f"Description: {article['description']}",
+                        f"Cite: {article['cite']}",
+                        f"Authors: {', '.join(authors)}",
+                        "---",
+                    ])
+                )
+            except KeyError:
+                continue
+
+        content = "\n".join(string)
+        return f"\nScholar results:\n {content}\n"
+
+
+class ScholarSearchTool(BuiltinTool):
+    def _invoke(
+        self,
+        user_id: str,
+        tool_parameters: dict[str, Any],
+    ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SerplyApi tool.
+        """
+        query = tool_parameters["query"]
+        gl = tool_parameters.get("gl", "us")
+        hl = tool_parameters.get("hl", "en")
+        location = tool_parameters.get("location", None)
+
+        api_key = self.runtime.credentials["serply_api_key"]
+        result = SerplyApi(api_key).run(query, gl=gl, hl=hl, location=location)
+
+        return self.create_text_message(text=result)

+ 501 - 0
api/core/tools/provider/builtin/websearch/tools/scholar_search.yaml

@@ -0,0 +1,501 @@
+identity:
+  name: scholar_search
+  author: Dify
+  label:
+    en_US: Scholar API
+    zh_Hans: Scholar API
+description:
+  human:
+    en_US: A tool to retrieve scholarly literature.
+    zh_Hans: 学术文献检索工具
+  llm: A tool to retrieve scholarly literature.
+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: location
+    type: string
+    required: false
+    default: US
+    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: form
+    options:
+      - value: AU
+        label:
+          en_US: Australia
+          zh_Hans: 澳大利亚
+          pt_BR: Australia
+      - value: BR
+        label:
+          en_US: Brazil
+          zh_Hans: 巴西
+          pt_BR: Brazil
+      - value: CA
+        label:
+          en_US: Canada
+          zh_Hans: 加拿大
+          pt_BR: Canada
+      - value: DE
+        label:
+          en_US: Germany
+          zh_Hans: 德国
+          pt_BR: Germany
+      - value: FR
+        label:
+          en_US: France
+          zh_Hans: 法国
+          pt_BR: France
+      - 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
+      - value: JP
+        label:
+          en_US: Japan
+          zh_Hans: 日本
+          pt_BR: Japan
+      - value: IN
+        label:
+          en_US: India
+          zh_Hans: 印度
+          pt_BR: India
+      - value: KR
+        label:
+          en_US: Korea
+          zh_Hans: 韩国
+          pt_BR: Korea
+      - value: SG
+        label:
+          en_US: Singapore
+          zh_Hans: 新加坡
+          pt_BR: Singapore
+      - value: SE
+        label:
+          en_US: Sweden
+          zh_Hans: 瑞典
+          pt_BR: Sweden
+  - 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: ja
+        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: 越南语

+ 88 - 0
api/core/tools/provider/builtin/websearch/tools/web_search.py

@@ -0,0 +1,88 @@
+import typing
+from urllib.parse import urlencode
+
+import requests
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class SerplyApi:
+    """
+    SerplyApi tool provider.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        """Initialize Serply Web Search Tool provider."""
+        self.serply_api_key = api_key
+        self.base_url = "https://api.serply.io/v1/search/"
+
+    def run(self, query: str, **kwargs: typing.Any) -> str:
+        """Run query through Serply and parse result."""
+        params = {"q": query, "hl": kwargs.get("hl", "en"), "gl": kwargs.get("gl", "US"), "num": kwargs.get("num", 10)}
+        location = kwargs.get("location", "US")
+
+        headers = {
+            "X-API-KEY": self.serply_api_key,
+            "X-User-Agent": kwargs.get("device", "desktop"),
+            "X-Proxy-Location": location,
+            "User-Agent": "Dify",
+        }
+
+        url = f"{self.base_url}{urlencode(params)}"
+        res = requests.get(
+            url,
+            headers=headers,
+        )
+        res = res.json()
+
+        return self.parse_results(res)
+
+    @staticmethod
+    def parse_results(res: dict) -> str:
+        """Process response from Serply Web Search."""
+        results = res.get("results", [])
+        if not results:
+            raise ValueError(f"Got error from Serply: {res}")
+
+        string = []
+        for result in results:
+            try:
+                string.append(
+                    "\n".join([
+                        f"Title: {result['title']}",
+                        f"Link: {result['link']}",
+                        f"Description: {result['description'].strip()}",
+                        "---",
+                    ])
+                )
+            except KeyError:
+                continue
+
+        if related_questions := res.get("related_questions", []):
+            string.append("---")
+            string.append("Related Questions: ")
+            string.append("\n".join(related_questions))
+
+        content = "\n".join(string)
+        return f"\nSearch results:\n {content}\n"
+
+
+class WebSearchTool(BuiltinTool):
+    def _invoke(
+        self,
+        user_id: str,
+        tool_parameters: dict[str, typing.Any],
+    ) -> typing.Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+        Invoke the SerplyApi tool.
+        """
+        query = tool_parameters["query"]
+        num = tool_parameters.get("num", 10)
+        gl = tool_parameters.get("gl", "us")
+        hl = tool_parameters.get("hl", "en")
+        location = tool_parameters.get("location", "None")
+
+        api_key = self.runtime.credentials["serply_api_key"]
+        result = SerplyApi(api_key).run(query=query, num=num, gl=gl, hl=hl, location=location)
+        return self.create_text_message(text=result)

+ 376 - 0
api/core/tools/provider/builtin/websearch/tools/web_search.yaml

@@ -0,0 +1,376 @@
+identity:
+  name: web_search
+  author: Dify
+  label:
+    en_US: Web Search API
+    zh_Hans: Web 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: location
+    type: string
+    required: false
+    default: US
+    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: form
+    options:
+      - value: AU
+        label:
+          en_US: Australia
+          zh_Hans: 澳大利亚
+          pt_BR: Australia
+      - value: BR
+        label:
+          en_US: Brazil
+          zh_Hans: 巴西
+          pt_BR: Brazil
+      - value: CA
+        label:
+          en_US: Canada
+          zh_Hans: 加拿大
+          pt_BR: Canada
+      - value: DE
+        label:
+          en_US: Germany
+          zh_Hans: 德国
+          pt_BR: Germany
+      - value: FR
+        label:
+          en_US: France
+          zh_Hans: 法国
+          pt_BR: France
+      - 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
+      - value: JP
+        label:
+          en_US: Japan
+          zh_Hans: 日本
+          pt_BR: Japan
+      - value: IN
+        label:
+          en_US: India
+          zh_Hans: 印度
+          pt_BR: India
+      - value: KR
+        label:
+          en_US: Korea
+          zh_Hans: 韩国
+          pt_BR: Korea
+      - value: SG
+        label:
+          en_US: Singapore
+          zh_Hans: 新加坡
+          pt_BR: Singapore
+      - value: SE
+        label:
+          en_US: Sweden
+          zh_Hans: 瑞典
+          pt_BR: Sweden
+  - name: device
+    type: select
+    label:
+      en_US: Device Type
+      zh_Hans: 汉斯先生
+    human_description:
+      en_US: Defines the device to make interface search. Default is "desktop".
+      zh_Hans: 定义进行接口搜索的设备。默认为“桌面”
+    required: false
+    default: desktop
+    form: form
+    options:
+      - value: desktop
+        label:
+          en_US: Desktop
+          zh_Hans: 桌面
+      - value: mobile
+        label:
+          en_US: Mobile
+          zh_Hans: 移动的
+  - 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: AU
+        label:
+          en_US: Australia
+          zh_Hans: 澳大利亚
+          pt_BR: Australia
+      - value: BR
+        label:
+          en_US: Brazil
+          zh_Hans: 巴西
+          pt_BR: Brazil
+      - value: CA
+        label:
+          en_US: Canada
+          zh_Hans: 加拿大
+          pt_BR: Canada
+      - value: DE
+        label:
+          en_US: Germany
+          zh_Hans: 德国
+          pt_BR: Germany
+      - value: FR
+        label:
+          en_US: France
+          zh_Hans: 法国
+          pt_BR: France
+      - value: GB
+        label:
+          en_US: United Kingdom
+          zh_Hans: 英国
+          pt_BR: United Kingdom
+      - value: IN
+        label:
+          en_US: India
+          zh_Hans: 印度
+          pt_BR: India
+      - value: KR
+        label:
+          en_US: Korea
+          zh_Hans: 韩国
+          pt_BR: Korea
+      - value: SE
+        label:
+          en_US: Sweden
+          zh_Hans: 瑞典
+          pt_BR: Sweden
+      - value: SG
+        label:
+          en_US: Singapore
+          zh_Hans: 新加坡
+          pt_BR: Singapore
+      - 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: ja
+        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: 越南语

+ 21 - 0
api/core/tools/provider/builtin/websearch/websearch.py

@@ -0,0 +1,21 @@
+from typing import Any
+
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.provider.builtin.websearch.tools.web_search import WebSearchTool
+from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
+
+
+class WebSearchAPIProvider(BuiltinToolProviderController):
+    # validate when saving the api_key
+    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+        try:
+            WebSearchTool().fork_tool_runtime(
+                runtime={
+                    "credentials": credentials,
+                }
+            ).invoke(
+                user_id="",
+                tool_parameters={"query": "what is llm"},
+            )
+        except Exception as e:
+            raise ToolProviderCredentialValidationError(str(e))

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

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