Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ file-system:

### HTTP

Enable `http` for Athena to send HTTP requests, search the web via Jina Search or Exa Search, and download files from the Internet.
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.

```yaml
http:
Expand All @@ -145,6 +145,9 @@ http:
exa: # Optional Exa config
base_url: https://api.exa.ai # Optional, defaults to this
api_key: your-exa-api-key # Required if using Exa
tavily: # Optional Tavily config
base_url: https://api.tavily.com # Optional, defaults to this
api_key: your-tavily-api-key # Required if using Tavily
```

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

### LLM

Expand Down
74 changes: 74 additions & 0 deletions src/plugins/http/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { convert } from "html-to-text";
import { Athena, Dict } from "../../core/athena.js";
import { JinaSearch } from "./jina.js";
import { ExaSearch } from "./exa.js";
import { TavilySearch } from "./tavily.js";
import { PluginBase } from "../plugin-base.js";

export default class Http extends PluginBase {
Expand All @@ -22,6 +23,7 @@ export default class Http extends PluginBase {

jina!: JinaSearch;
exa!: ExaSearch;
tavily!: TavilySearch;
boundAthenaPrivateEventHandler!: (name: string, args: Dict<any>) => void;

async load(athena: Athena) {
Expand All @@ -37,6 +39,12 @@ export default class Http extends PluginBase {
apiKey: this.config.exa.api_key,
});
}
if (this.config.tavily) {
this.tavily = new TavilySearch({
baseUrl: this.config.tavily.base_url,
apiKey: this.config.tavily.api_key,
});
}
this.boundAthenaPrivateEventHandler =
this.athenaPrivateEventHandler.bind(this);
athena.on("private-event", this.boundAthenaPrivateEventHandler);
Expand Down Expand Up @@ -214,6 +222,63 @@ export default class Http extends PluginBase {
},
);
}
if (this.config.tavily) {
athena.registerTool(
{
name: "http/tavily-search",
desc: "Searches the web for information using Tavily API.",
args: {
query: {
type: "string",
desc: "The query to search for.",
required: true,
},
},
retvals: {
results: {
type: "array",
desc: "The results of the search.",
required: true,
of: {
type: "object",
desc: "A single search result.",
of: {
title: {
type: "string",
desc: "The title of the result.",
required: true,
},
url: {
type: "string",
desc: "The URL of the result.",
required: true,
},
content: {
type: "string",
desc: "Text content snippet.",
required: true,
},
},
required: true,
},
},
},
},
{
fn: async (args: Dict<any>) => {
const results = await this.tavily.search(args.query);
return { results };
},
explain_args: (args: Dict<any>) => ({
summary: `Searching the web with Tavily for ${args.query}...`,
}),
explain_retvals: (args: Dict<any>, retvals: Dict<any>) => ({
summary: `Found ${retvals.results.length} results with Tavily for ${args.query}.`,
details: JSON.stringify(retvals.results),
}),
},
);
}
athena.registerTool(
{
name: "http/download-file",
Expand Down Expand Up @@ -289,6 +354,9 @@ export default class Http extends PluginBase {
if (this.config.exa) {
athena.deregisterTool("http/exa-search");
}
if (this.config.tavily) {
athena.deregisterTool("http/tavily-search");
}
athena.deregisterTool("http/download-file");
}

Expand All @@ -306,6 +374,12 @@ export default class Http extends PluginBase {
apiKey: this.config.exa.api_key || args.token,
});
}
if (this.config.tavily) {
this.tavily = new TavilySearch({
baseUrl: this.config.tavily.base_url,
apiKey: args.token,
});
}
}
}
}
53 changes: 53 additions & 0 deletions src/plugins/http/tavily.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
export interface ITavilySearchInitParams {
baseUrl?: string;
apiKey: string;
}

export interface ITavilySearchResult {
title: string;
url: string;
content: string; // Text content from the search result
}

export class TavilySearch {
baseUrl: string;
apiKey: string;

constructor(initParams: ITavilySearchInitParams) {
this.baseUrl = initParams.baseUrl || "https://api.tavily.com";
this.apiKey = initParams.apiKey;
}

async search(query: string): Promise<ITavilySearchResult[]> {
const response = await fetch(`${this.baseUrl}/search`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
},
body: JSON.stringify({
query: query,
max_results: 10, // Request 10 results, adjust as needed
}),
});

if (!response.ok) {
const errorBody = await response.text();
console.error("Tavily API Error:", response.status, errorBody);
throw new Error(
`Failed to search with Tavily API: ${response.statusText}`,
);
}

const json = await response.json();

// Map the response structure to ITavilySearchResult
const formattedResults = json.results.map((result: any) => ({
title: result.title || "No Title",
url: result.url,
content: result.content || "No content",
}));

return formattedResults;
}
}