Skip to content

Commit 278e12c

Browse files
authored
feat: Add Tavily Search (#8)
* feat: Add Tavily Search * try to pull the latest master branch and resolve changes * chore: prettier format issue
1 parent ca88bdd commit 278e12c

File tree

3 files changed

+134
-1
lines changed

3 files changed

+134
-1
lines changed

docs/configuration.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ file-system:
135135

136136
### HTTP
137137

138-
Enable `http` for Athena to send HTTP requests, search the web via Jina Search or Exa Search, and download files from the Internet.
138+
Enable `http` for Athena to send HTTP requests, search the web via Jina Search, Exa Search, or Tavily Search, and download files from the Internet.
139139

140140
```yaml
141141
http:
@@ -145,6 +145,9 @@ http:
145145
exa: # Optional Exa config
146146
base_url: https://api.exa.ai # Optional, defaults to this
147147
api_key: your-exa-api-key # Required if using Exa
148+
tavily: # Optional Tavily config
149+
base_url: https://api.tavily.com # Optional, defaults to this
150+
api_key: your-tavily-api-key # Required if using Tavily
148151
```
149152

150153
- `jina`: Configuration for [Jina Search](https://jina.ai/). (Optional)
@@ -153,6 +156,9 @@ http:
153156
- `exa`: Configuration for [Exa Search](https://exa.ai/). (Optional)
154157
- `base_url`: The base URL of the Exa Search API endpoint. (Optional, defaults to `https://api.exa.ai`)
155158
- `api_key`: The API key for the Exa Search API endpoint. (Required if using Exa)
159+
- `tavily`: Configuration for [Tavily Search](https://tavily.com/). (Optional)
160+
- `base_url`: The base URL of the Tavily Search API endpoint. (Optional, defaults to `https://api.tavily.com`)
161+
- `api_key`: The API key for the Tavily Search API endpoint. (Required if using Tavily)
156162

157163
### LLM
158164

src/plugins/http/init.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { convert } from "html-to-text";
77
import { Athena, Dict } from "../../core/athena.js";
88
import { JinaSearch } from "./jina.js";
99
import { ExaSearch } from "./exa.js";
10+
import { TavilySearch } from "./tavily.js";
1011
import { PluginBase } from "../plugin-base.js";
1112

1213
export default class Http extends PluginBase {
@@ -22,6 +23,7 @@ export default class Http extends PluginBase {
2223

2324
jina!: JinaSearch;
2425
exa!: ExaSearch;
26+
tavily!: TavilySearch;
2527
boundAthenaPrivateEventHandler!: (name: string, args: Dict<any>) => void;
2628

2729
async load(athena: Athena) {
@@ -37,6 +39,12 @@ export default class Http extends PluginBase {
3739
apiKey: this.config.exa.api_key,
3840
});
3941
}
42+
if (this.config.tavily) {
43+
this.tavily = new TavilySearch({
44+
baseUrl: this.config.tavily.base_url,
45+
apiKey: this.config.tavily.api_key,
46+
});
47+
}
4048
this.boundAthenaPrivateEventHandler =
4149
this.athenaPrivateEventHandler.bind(this);
4250
athena.on("private-event", this.boundAthenaPrivateEventHandler);
@@ -214,6 +222,63 @@ export default class Http extends PluginBase {
214222
},
215223
);
216224
}
225+
if (this.config.tavily) {
226+
athena.registerTool(
227+
{
228+
name: "http/tavily-search",
229+
desc: "Searches the web for information using Tavily API.",
230+
args: {
231+
query: {
232+
type: "string",
233+
desc: "The query to search for.",
234+
required: true,
235+
},
236+
},
237+
retvals: {
238+
results: {
239+
type: "array",
240+
desc: "The results of the search.",
241+
required: true,
242+
of: {
243+
type: "object",
244+
desc: "A single search result.",
245+
of: {
246+
title: {
247+
type: "string",
248+
desc: "The title of the result.",
249+
required: true,
250+
},
251+
url: {
252+
type: "string",
253+
desc: "The URL of the result.",
254+
required: true,
255+
},
256+
content: {
257+
type: "string",
258+
desc: "Text content snippet.",
259+
required: true,
260+
},
261+
},
262+
required: true,
263+
},
264+
},
265+
},
266+
},
267+
{
268+
fn: async (args: Dict<any>) => {
269+
const results = await this.tavily.search(args.query);
270+
return { results };
271+
},
272+
explain_args: (args: Dict<any>) => ({
273+
summary: `Searching the web with Tavily for ${args.query}...`,
274+
}),
275+
explain_retvals: (args: Dict<any>, retvals: Dict<any>) => ({
276+
summary: `Found ${retvals.results.length} results with Tavily for ${args.query}.`,
277+
details: JSON.stringify(retvals.results),
278+
}),
279+
},
280+
);
281+
}
217282
athena.registerTool(
218283
{
219284
name: "http/download-file",
@@ -289,6 +354,9 @@ export default class Http extends PluginBase {
289354
if (this.config.exa) {
290355
athena.deregisterTool("http/exa-search");
291356
}
357+
if (this.config.tavily) {
358+
athena.deregisterTool("http/tavily-search");
359+
}
292360
athena.deregisterTool("http/download-file");
293361
}
294362

@@ -306,6 +374,12 @@ export default class Http extends PluginBase {
306374
apiKey: this.config.exa.api_key || args.token,
307375
});
308376
}
377+
if (this.config.tavily) {
378+
this.tavily = new TavilySearch({
379+
baseUrl: this.config.tavily.base_url,
380+
apiKey: args.token,
381+
});
382+
}
309383
}
310384
}
311385
}

src/plugins/http/tavily.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export interface ITavilySearchInitParams {
2+
baseUrl?: string;
3+
apiKey: string;
4+
}
5+
6+
export interface ITavilySearchResult {
7+
title: string;
8+
url: string;
9+
content: string; // Text content from the search result
10+
}
11+
12+
export class TavilySearch {
13+
baseUrl: string;
14+
apiKey: string;
15+
16+
constructor(initParams: ITavilySearchInitParams) {
17+
this.baseUrl = initParams.baseUrl || "https://api.tavily.com";
18+
this.apiKey = initParams.apiKey;
19+
}
20+
21+
async search(query: string): Promise<ITavilySearchResult[]> {
22+
const response = await fetch(`${this.baseUrl}/search`, {
23+
method: "POST",
24+
headers: {
25+
"Content-Type": "application/json",
26+
Authorization: `Bearer ${this.apiKey}`,
27+
},
28+
body: JSON.stringify({
29+
query: query,
30+
max_results: 10, // Request 10 results, adjust as needed
31+
}),
32+
});
33+
34+
if (!response.ok) {
35+
const errorBody = await response.text();
36+
console.error("Tavily API Error:", response.status, errorBody);
37+
throw new Error(
38+
`Failed to search with Tavily API: ${response.statusText}`,
39+
);
40+
}
41+
42+
const json = await response.json();
43+
44+
// Map the response structure to ITavilySearchResult
45+
const formattedResults = json.results.map((result: any) => ({
46+
title: result.title || "No Title",
47+
url: result.url,
48+
content: result.content || "No content",
49+
}));
50+
51+
return formattedResults;
52+
}
53+
}

0 commit comments

Comments
 (0)