diff --git a/agent/tools/searxng.py b/agent/tools/searxng.py
new file mode 100644
index 000000000..25a8c0e46
--- /dev/null
+++ b/agent/tools/searxng.py
@@ -0,0 +1,156 @@
+#
+# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import logging
+import os
+import time
+from abc import ABC
+import requests
+from agent.tools.base import ToolMeta, ToolParamBase, ToolBase
+from api.utils.api_utils import timeout
+
+
+class SearXNGParam(ToolParamBase):
+ """
+ Define the SearXNG component parameters.
+ """
+
+ def __init__(self):
+ self.meta: ToolMeta = {
+ "name": "searxng_search",
+ "description": "SearXNG is a privacy-focused metasearch engine that aggregates results from multiple search engines without tracking users. It provides comprehensive web search capabilities.",
+ "parameters": {
+ "query": {
+ "type": "string",
+ "description": "The search keywords to execute with SearXNG. The keywords should be the most important words/terms(includes synonyms) from the original request.",
+ "default": "{sys.query}",
+ "required": True
+ },
+ "searxng_url": {
+ "type": "string",
+ "description": "The base URL of your SearXNG instance (e.g., http://localhost:4000). This is required to connect to your SearXNG server.",
+ "required": False,
+ "default": ""
+ }
+ }
+ }
+ super().__init__()
+ self.top_n = 10
+ self.searxng_url = ""
+
+ def check(self):
+ # Keep validation lenient so opening try-run panel won't fail without URL.
+ # Coerce top_n to int if it comes as string from UI.
+ try:
+ if isinstance(self.top_n, str):
+ self.top_n = int(self.top_n.strip())
+ except Exception:
+ pass
+ self.check_positive_integer(self.top_n, "Top N")
+
+ def get_input_form(self) -> dict[str, dict]:
+ return {
+ "query": {
+ "name": "Query",
+ "type": "line"
+ },
+ "searxng_url": {
+ "name": "SearXNG URL",
+ "type": "line",
+ "placeholder": "http://localhost:4000"
+ }
+ }
+
+
+class SearXNG(ToolBase, ABC):
+ component_name = "SearXNG"
+
+ @timeout(os.environ.get("COMPONENT_EXEC_TIMEOUT", 12))
+ def _invoke(self, **kwargs):
+ # Gracefully handle try-run without inputs
+ query = kwargs.get("query")
+ if not query or not isinstance(query, str) or not query.strip():
+ self.set_output("formalized_content", "")
+ return ""
+
+ searxng_url = (kwargs.get("searxng_url") or getattr(self._param, "searxng_url", "") or "").strip()
+ # In try-run, if no URL configured, just return empty instead of raising
+ if not searxng_url:
+ self.set_output("formalized_content", "")
+ return ""
+
+ last_e = ""
+ for _ in range(self._param.max_retries+1):
+ try:
+ # 构建搜索参数
+ search_params = {
+ 'q': query,
+ 'format': 'json',
+ 'categories': 'general',
+ 'language': 'auto',
+ 'safesearch': 1,
+ 'pageno': 1
+ }
+
+ # 发送搜索请求
+ response = requests.get(
+ f"{searxng_url}/search",
+ params=search_params,
+ timeout=10
+ )
+ response.raise_for_status()
+
+ data = response.json()
+
+ # 验证响应数据
+ if not data or not isinstance(data, dict):
+ raise ValueError("Invalid response from SearXNG")
+
+ results = data.get("results", [])
+ if not isinstance(results, list):
+ raise ValueError("Invalid results format from SearXNG")
+
+ # 限制结果数量
+ results = results[:self._param.top_n]
+
+ # 处理搜索结果
+ self._retrieve_chunks(results,
+ get_title=lambda r: r.get("title", ""),
+ get_url=lambda r: r.get("url", ""),
+ get_content=lambda r: r.get("content", ""))
+
+ self.set_output("json", results)
+ return self.output("formalized_content")
+
+ except requests.RequestException as e:
+ last_e = f"Network error: {e}"
+ logging.exception(f"SearXNG network error: {e}")
+ time.sleep(self._param.delay_after_error)
+ except Exception as e:
+ last_e = str(e)
+ logging.exception(f"SearXNG error: {e}")
+ time.sleep(self._param.delay_after_error)
+
+ if last_e:
+ self.set_output("_ERROR", last_e)
+ return f"SearXNG error: {last_e}"
+
+ assert False, self.output()
+
+ def thoughts(self) -> str:
+ return """
+Keywords: {}
+Searching with SearXNG for relevant results...
+ """.format(self.get_input().get("query", "-_-!"))
diff --git a/web/src/assets/svg/searxng.svg b/web/src/assets/svg/searxng.svg
new file mode 100644
index 000000000..8b6fd7e3c
--- /dev/null
+++ b/web/src/assets/svg/searxng.svg
@@ -0,0 +1,5 @@
+
diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts
index 8dd3c40f3..19ccb12b4 100644
--- a/web/src/locales/de.ts
+++ b/web/src/locales/de.ts
@@ -868,6 +868,9 @@ export default {
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'Eine Komponente, die auf duckduckgo.com sucht und Ihnen ermöglicht, die Anzahl der Suchergebnisse mit TopN anzugeben. Sie ergänzt die vorhandenen Wissensdatenbanken.',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'Eine Komponente, die auf https://searxng.org/ sucht und Ihnen ermöglicht, die Anzahl der Suchergebnisse mit TopN anzugeben. Sie ergänzt die vorhandenen Wissensdatenbanken.',
channel: 'Kanal',
channelTip:
'Führt eine Textsuche oder Nachrichtensuche für die Eingabe der Komponente durch',
diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts
index c1de9bad3..08f8b9790 100644
--- a/web/src/locales/en.ts
+++ b/web/src/locales/en.ts
@@ -1005,6 +1005,9 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'A component that searches from duckduckgo.com, allowing you to specify the number of search results using TopN. It supplements the existing knowledge bases.',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'A component that searches via your provided SearXNG instance URL. Specify TopN and the instance URL.',
channel: 'Channel',
channelTip: `Perform text search or news search on the component's input`,
text: 'Text',
diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts
index 3a52ef172..57d3a29db 100644
--- a/web/src/locales/es.ts
+++ b/web/src/locales/es.ts
@@ -571,6 +571,9 @@ export default {
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'Un componente que recupera resultados de búsqueda de duckduckgo.com, con TopN especificando el número de resultados de búsqueda. Complementa las bases de conocimiento existentes.',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'Un componente que realiza búsquedas mediante la URL de la instancia de SearXNG que usted proporcione. Especifique TopN y la URL de la instancia.',
channel: 'Canal',
channelTip:
'Realizar búsqueda de texto o búsqueda de noticias en la entrada del componente.',
diff --git a/web/src/locales/fr.ts b/web/src/locales/fr.ts
index 34b439bc3..1d9286015 100644
--- a/web/src/locales/fr.ts
+++ b/web/src/locales/fr.ts
@@ -781,6 +781,9 @@ export default {
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'Un composant qui recherche sur duckduckgo.com, vous permettant de spécifier le nombre de résultats avec TopN. Il complète les bases de connaissances existantes.',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'Un composant qui effectue des recherches via la URL de l\'instance de SearXNG que vous fournissez. Spécifiez TopN et l\'URL de l\'instance.',
channel: 'Canal',
channelTip:
"Effectuer une recherche de texte ou d'actualités sur l'entrée du composant",
diff --git a/web/src/locales/id.ts b/web/src/locales/id.ts
index 0c2d24a8e..6cec4894b 100644
--- a/web/src/locales/id.ts
+++ b/web/src/locales/id.ts
@@ -759,6 +759,9 @@ export default {
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'Komponen yang mengambil hasil pencarian dari duckduckgo.com, dengan TopN menentukan jumlah hasil pencarian. Ini melengkapi basis pengetahuan yang ada.',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'Komponen yang melakukan pencarian menggunakan URL instance SearXNG yang Anda berikan. Spesifikasikan TopN dan URL instance.',
channel: 'Saluran',
channelTip: `Lakukan pencarian teks atau pencarian berita pada input komponen`,
text: 'Teks',
diff --git a/web/src/locales/ja.ts b/web/src/locales/ja.ts
index cc862fb14..690a5fd0d 100644
--- a/web/src/locales/ja.ts
+++ b/web/src/locales/ja.ts
@@ -739,6 +739,9 @@ export default {
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'duckduckgo.comから検索を行うコンポーネントで、TopNを使用して検索結果の数を指定します。既存のナレッジベースを補完します。',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'SearXNGのインスタンスURLを提供して検索を行うコンポーネント。TopNとインスタンスURLを指定してください。',
channel: 'チャンネル',
channelTip: `コンポーネントの入力に対してテキスト検索またはニュース検索を実行します`,
text: 'テキスト',
diff --git a/web/src/locales/pt-br.ts b/web/src/locales/pt-br.ts
index b76fb5529..59ae2692d 100644
--- a/web/src/locales/pt-br.ts
+++ b/web/src/locales/pt-br.ts
@@ -726,6 +726,9 @@ export default {
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'Um componente que realiza buscas no duckduckgo.com, permitindo especificar o número de resultados de pesquisa usando TopN. Ele complementa as bases de conhecimento existentes.',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'Um componente que realiza buscas via URL da instância SearXNG que você fornece. Especifique TopN e URL da instância.',
channel: 'Canal',
channelTip: `Realize uma busca por texto ou por notícias na entrada do componente`,
text: 'Texto',
diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts
index 6332ea827..bf6a08760 100644
--- a/web/src/locales/ru.ts
+++ b/web/src/locales/ru.ts
@@ -859,6 +859,9 @@ export default {
baiduDescription: `Ищет на baidu.com.`,
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription: 'Ищет на duckduckgo.com.',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'Компонент, который выполняет поиск по указанному вами URL-адресу экземпляра SearXNG. Укажите TopN и URL-адрес экземпляра.',
channel: 'Канал',
channelTip: `Текстовый или новостной поиск`,
text: 'Текст',
diff --git a/web/src/locales/vi.ts b/web/src/locales/vi.ts
index 6ec3cbc42..b09738d60 100644
--- a/web/src/locales/vi.ts
+++ b/web/src/locales/vi.ts
@@ -818,6 +818,9 @@ export default {
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'Một thành phần truy xuất kết quả tìm kiếm từ duckduckgo.com, với TopN xác định số lượng kết quả tìm kiếm. Nó bổ sung cho các cơ sở kiến thức hiện có.',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ 'Một thành phần truy xuất kết quả tìm kiếm từ searxng.com, với TopN xác định số lượng kết quả tìm kiếm. Nó bổ sung cho các cơ sở kiến thức hiện có.',
channel: 'Kênh',
channelTip: `Thực hiện tìm kiếm văn bản hoặc tìm kiếm tin tức trên đầu vào của thành phần`,
text: 'Văn bản',
diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts
index 15f6cf121..40faa6680 100644
--- a/web/src/locales/zh-traditional.ts
+++ b/web/src/locales/zh-traditional.ts
@@ -845,6 +845,9 @@ export default {
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要採用的搜尋結果數。',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ '該組件通過您提供的 SearXNG 實例地址進行搜索。請設置 Top N 和實例 URL。',
channel: '頻道',
channelTip: '針對該組件的輸入進行文字搜尋或新聞搜索',
text: '文字',
diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts
index 1ce6e8e3a..bc0fa37ce 100644
--- a/web/src/locales/zh.ts
+++ b/web/src/locales/zh.ts
@@ -971,6 +971,9 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要調整的搜尋結果數。',
+ searXNG: 'SearXNG',
+ searXNGDescription:
+ '该组件通过您提供的 SearXNG 实例地址进行搜索。请设置 Top N 和实例 URL。',
channel: '频道',
channelTip: '针对该组件的输入进行文本搜索或新闻搜索',
text: '文本',
diff --git a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
index 222d1cbc0..6d9f32453 100644
--- a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
+++ b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
@@ -201,6 +201,7 @@ function AccordionOperators({
Operator.GitHub,
Operator.Invoke,
Operator.WenCai,
+ Operator.SearXNG,
]}
isCustomDropdown={isCustomDropdown}
mousePosition={mousePosition}
diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx
index 6c3c9f434..7e57c4abc 100644
--- a/web/src/pages/agent/constant.tsx
+++ b/web/src/pages/agent/constant.tsx
@@ -88,6 +88,7 @@ export enum Operator {
TavilyExtract = 'TavilyExtract',
UserFillUp = 'UserFillUp',
StringTransform = 'StringTransform',
+ SearXNG = 'SearXNG',
}
export const SwitchLogicOperatorOptions = ['and', 'or'];
@@ -211,6 +212,9 @@ export const componentMenuList = [
{
name: Operator.Email,
},
+ {
+ name: Operator.SearXNG,
+ },
];
export const SwitchOperatorOptions = [
@@ -340,6 +344,22 @@ export const initialDuckValues = {
},
};
+export const initialSearXNGValues = {
+ top_n: '10',
+ searxng_url: '',
+ query: AgentGlobals.SysQuery,
+ outputs: {
+ formalized_content: {
+ value: '',
+ type: 'string',
+ },
+ json: {
+ value: [],
+ type: 'Array