Skip to content

Commit 963f303

Browse files
authored
feat(agent-tars): add searxng search (close: #326) (#372)
1 parent dc5adac commit 963f303

File tree

8 files changed

+204
-1
lines changed

8 files changed

+204
-1
lines changed
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# SearXNG Search Configuration
2+
3+
## Overview
4+
5+
SearXNG is a privacy-respecting, self-hosted metasearch engine that aggregates results from various search engines. This document explains how to configure SearXNG search in Agent TARS.
6+
7+
## How to Use
8+
9+
[search-searxng](../images/search-searxng.png)
10+
11+
## Configuration Options
12+
13+
### Basic Configuration
14+
15+
To use SearXNG search, you need to specify the following parameters:
16+
17+
| Parameter | Description | Default Value |
18+
|-----------|-------------|---------------|
19+
| `baseUrl` | URL of the SearXNG instance | `https://127.0.0.1:8081` |
20+
21+
### Advanced Options
22+
23+
> The following configurations are optional and currently use default values. In the future, configuration via the GUI will be supported.
24+
25+
When performing searches, you can customize the following options:
26+
27+
| Option | Description | Default Value |
28+
|--------|-------------|---------------|
29+
| `count` | Maximum number of results to return | 10 |
30+
| `language` | Search language code | `zh-CN` |
31+
| `categories` | Categories to search in (e.g., general, images, news) | `['general']` |
32+
| `time_range` | Time range for results (e.g., day, week, month, year) | - |
33+
| `safesearch` | Safe search level (0: off, 1: moderate, 2: strict) | 1 |
34+
35+
## Setting Up SearXNG
36+
37+
### Using a Public Instance
38+
39+
You can use a public SearXNG instance by setting the `baseUrl` to the instance URL. However, for privacy and reliability reasons, we recommend hosting your own instance.
40+
41+
### Self-Hosting SearXNG
42+
43+
To set up your own SearXNG instance:
44+
45+
1. Follow the [official SearXNG installation guide](https://searxng.github.io/searxng/admin/installation.html)
46+
2. Configure your instance according to your needs
47+
3. Update the `baseUrl` in Agent TARS settings to point to your instance

apps/agent-tars/src/main/customTools/search.ts

+12
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ export async function search(toolCall: ToolCall): Promise<MCPToolResult> {
8989
// query: args.query,
9090
// count: args.count || 10,
9191
// });
92+
} else if (currentSearchConfig.provider === SearchProvider.SEARXNG) {
93+
const client = new SearchClient({
94+
provider: SearchProviderEnum.SearXNG,
95+
providerConfig: {
96+
baseUrl: currentSearchConfig.baseUrl,
97+
},
98+
});
99+
100+
results = await client.search({
101+
query: args.query,
102+
count: args.count,
103+
});
92104
} else {
93105
// Only for Bing Search, because Tavily is not supported in the bundle of this packages
94106
// Error info: trvily is not defined

apps/agent-tars/src/renderer/src/components/LeftSidebar/Settings/SearchSettingsTab.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ export function SearchSettingsTab({
4848
>
4949
Bing Search
5050
</SelectItem>
51+
<SelectItem
52+
key={SearchProvider.SEARXNG}
53+
startContent={getSearchProviderLogo(SearchProvider.SEARXNG)}
54+
>
55+
SearXNG Search
56+
</SelectItem>
5157
</Select>
5258

5359
{[SearchProvider.TAVILY, SearchProvider.BING_SEARCH].includes(
@@ -100,6 +106,18 @@ export function SearchSettingsTab({
100106
description="Override the default Bing Search API endpoint"
101107
/>
102108
)}
109+
110+
{settings.provider === SearchProvider.SEARXNG && (
111+
<Input
112+
label="Custom Endpoint"
113+
placeholder="https://127.0.0.1:8081/"
114+
value={settings.baseUrl || ''}
115+
onChange={(e) =>
116+
setSettings({ ...settings, baseUrl: e.target.value })
117+
}
118+
description="Override the default SearXNG API endpoint"
119+
/>
120+
)}
103121
</div>
104122
);
105123
}

apps/agent-tars/src/renderer/src/components/LeftSidebar/Settings/searchUtils.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SiDuckduckgo, SiMicrosoftbing } from 'react-icons/si';
1+
import { SiDuckduckgo, SiMicrosoftbing, SiSearxng } from 'react-icons/si';
22
import { TbSearch } from 'react-icons/tb';
33
import { SearchProvider } from '@agent-infra/shared';
44

@@ -10,6 +10,8 @@ export function getSearchProviderLogo(provider: SearchProvider) {
1010
return <TbSearch size={18} />;
1111
case SearchProvider.DUCKDUCKGO_SEARCH:
1212
return <SiDuckduckgo size={18} />;
13+
case SearchProvider.SEARXNG:
14+
return <SiSearxng size={18} />;
1315
// case SearchProvider.BROWSER_SEARCH:
1416
// return <TbBrowser size={18} />;
1517
default:

packages/agent-infra/search/search/src/index.ts

+27
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
DuckDuckGoSearchOptions,
2020
} from '@agent-infra/duckduckgo-search';
2121
import { TavilySearchConfig, TavilySearchOptions, tavily } from './tavily';
22+
import { SearXNGSearchConfig, SearXNGSearchOptions, searxng } from './searxng';
2223

2324
/**
2425
* Supported search providers
@@ -40,13 +41,18 @@ export enum SearchProvider {
4041
* Duckduckgo Search API
4142
*/
4243
DuckduckgoSearch,
44+
/**
45+
* SearXNG Search API
46+
*/
47+
SearXNG,
4348
}
4449

4550
export interface SearchProviderConfigMap {
4651
[SearchProvider.BrowserSearch]: BrowserSearchConfig;
4752
[SearchProvider.BingSearch]: BingSearchConfig;
4853
[SearchProvider.Tavily]: TavilySearchConfig;
4954
[SearchProvider.DuckduckgoSearch]: DuckDuckGoSearchClientConfig;
55+
[SearchProvider.SearXNG]: SearXNGSearchConfig;
5056
}
5157

5258
export type SearchProviderConfig<T> = T extends SearchProvider
@@ -58,6 +64,7 @@ export interface SearchProviderSearchOptionsMap {
5864
[SearchProvider.BingSearch]: BingSearchOptions;
5965
[SearchProvider.Tavily]: TavilySearchOptions;
6066
[SearchProvider.DuckduckgoSearch]: DuckDuckGoSearchOptions;
67+
[SearchProvider.SearXNG]: SearXNGSearchOptions;
6168
}
6269

6370
export type SearchProviderSearchOptions<T> = T extends SearchProvider
@@ -196,6 +203,26 @@ export class SearchClient<T extends SearchProvider> {
196203
};
197204
}
198205

206+
case SearchProvider.SearXNG: {
207+
const client = searxng(
208+
this.config.providerConfig as SearXNGSearchConfig,
209+
);
210+
const searchOptions: SearXNGSearchOptions = {
211+
count: options.count,
212+
...((originalOptions as SearXNGSearchOptions) || {}),
213+
query: options.query,
214+
};
215+
216+
const response = await client.search(options.query, searchOptions);
217+
return {
218+
pages: (response.results || []).map((item) => ({
219+
title: item.title || '',
220+
url: item.url,
221+
content: item.content,
222+
})),
223+
};
224+
}
225+
199226
case SearchProvider.DuckduckgoSearch: {
200227
const client = new DuckDuckGoSearchClient(
201228
this.config.providerConfig as DuckDuckGoSearchClientConfig,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import fetch from 'node-fetch';
6+
7+
export interface SearXNGSearchConfig {
8+
baseUrl?: string;
9+
}
10+
11+
export interface SearXNGSearchOptions {
12+
query: string;
13+
count?: number;
14+
language?: string;
15+
categories?: string[];
16+
time_range?: string;
17+
safesearch?: 0 | 1 | 2; // 0: close, 1: mid, 2: high
18+
}
19+
20+
export interface SearXNGSearchResult {
21+
title: string;
22+
url: string;
23+
content: string;
24+
engine?: string;
25+
score?: number;
26+
}
27+
28+
export interface SearXNGSearchResponse {
29+
query: string;
30+
results: SearXNGSearchResult[];
31+
}
32+
33+
export const searxng = (config: SearXNGSearchConfig = {}) => {
34+
const { baseUrl = 'https://127.0.0.1:8081' } = config;
35+
36+
const search = async (
37+
query: string,
38+
options: Partial<SearXNGSearchOptions> = {},
39+
): Promise<SearXNGSearchResponse> => {
40+
const {
41+
count = 10,
42+
language = 'zh-CN',
43+
categories = ['general'],
44+
time_range,
45+
safesearch = 1,
46+
} = options;
47+
48+
try {
49+
const params = new URLSearchParams({
50+
q: query,
51+
format: 'json',
52+
language,
53+
categories: categories.join(','),
54+
safesearch: safesearch.toString(),
55+
count: count.toString(),
56+
});
57+
58+
if (time_range) {
59+
params.append('time_range', time_range);
60+
}
61+
62+
const response = await fetch(`${baseUrl}/search?${params.toString()}`, {
63+
method: 'GET',
64+
headers: {
65+
Accept: 'application/json',
66+
},
67+
});
68+
69+
if (!response.ok) {
70+
throw new Error(
71+
`SearXNG search failed with status: ${response.status}`,
72+
);
73+
}
74+
75+
const data = await response.json();
76+
77+
return {
78+
query,
79+
results: data.results
80+
.map((result: any) => ({
81+
title: result.title || '',
82+
url: result.url || '',
83+
content: result.content || result.snippet || '',
84+
engine: result.engine,
85+
score: result.score,
86+
}))
87+
.slice(0, count),
88+
};
89+
} catch (error) {
90+
console.error('SearXNG search error:', error);
91+
throw error;
92+
}
93+
};
94+
95+
return { search };
96+
};

packages/agent-infra/shared/src/agent-tars-types/search.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export enum SearchProvider {
2424
TAVILY = 'tavily',
2525
DUCKDUCKGO_SEARCH = 'duckduckgo_search',
2626
BROWSER_SEARCH = 'browser_search',
27+
SEARXNG = 'searxng',
2728
}
2829

2930
export interface SearchSettings {

0 commit comments

Comments
 (0)