Skip to content

Commit 640b4e8

Browse files
authored
Merge pull request elizaOS#2195 from chuasonglin1995/refactor-websearch
refactor: websearch into a service
2 parents 6381485 + cd7a811 commit 640b4e8

File tree

12 files changed

+501
-274
lines changed

12 files changed

+501
-274
lines changed

agent/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import path from "path";
109109
import { fileURLToPath } from "url";
110110
import yargs from "yargs";
111111

112+
112113
const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
113114
const __dirname = path.dirname(__filename); // get the name of the directory
114115

packages/core/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
"@rollup/plugin-terser": "0.1.0",
4141
"@rollup/plugin-typescript": "11.1.6",
4242
"@solana/web3.js": "1.95.8",
43-
"@tavily/core": "^0.0.2",
4443
"@types/fluent-ffmpeg": "2.1.27",
4544
"@types/jest": "29.5.14",
4645
"@types/mocha": "10.0.10",

packages/core/src/generation.ts

+1-24
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import {
4141
ModelClass,
4242
ModelProviderName,
4343
ServiceType,
44-
SearchResponse,
4544
ActionResponse,
4645
IVerifiableInferenceAdapter,
4746
VerifiableInferenceOptions,
@@ -51,7 +50,7 @@ import {
5150
TokenizerType,
5251
} from "./types.ts";
5352
import { fal } from "@fal-ai/client";
54-
import { tavily } from "@tavily/core";
53+
5554
import BigNumber from "bignumber.js";
5655
import {createPublicClient, http} from "viem";
5756

@@ -1818,28 +1817,6 @@ export const generateCaption = async (
18181817
};
18191818
};
18201819

1821-
export const generateWebSearch = async (
1822-
query: string,
1823-
runtime: IAgentRuntime
1824-
): Promise<SearchResponse> => {
1825-
try {
1826-
const apiKey = runtime.getSetting("TAVILY_API_KEY") as string;
1827-
if (!apiKey) {
1828-
throw new Error("TAVILY_API_KEY is not set");
1829-
}
1830-
const tvly = tavily({ apiKey });
1831-
const response = await tvly.search(query, {
1832-
includeAnswer: true,
1833-
maxResults: 3, // 5 (default)
1834-
topic: "general", // "general"(default) "news"
1835-
searchDepth: "basic", // "basic"(default) "advanced"
1836-
includeImages: false, // false (default) true
1837-
});
1838-
return response;
1839-
} catch (error) {
1840-
elizaLogger.error("Error:", error);
1841-
}
1842-
};
18431820
/**
18441821
* Configuration options for generating objects with a model.
18451822
*/

packages/core/src/types.ts

+1-22
Original file line numberDiff line numberDiff line change
@@ -1418,28 +1418,6 @@ export interface ITeeLogService extends Service {
14181418
): Promise<boolean>;
14191419
}
14201420

1421-
export type SearchImage = {
1422-
url: string;
1423-
description?: string;
1424-
};
1425-
1426-
export type SearchResult = {
1427-
title: string;
1428-
url: string;
1429-
content: string;
1430-
rawContent?: string;
1431-
score: number;
1432-
publishedDate?: string;
1433-
};
1434-
1435-
export type SearchResponse = {
1436-
answer?: string;
1437-
query: string;
1438-
responseTime: number;
1439-
images: SearchImage[];
1440-
results: SearchResult[];
1441-
};
1442-
14431421
export enum ServiceType {
14441422
IMAGE_DESCRIPTION = "image_description",
14451423
TRANSCRIPTION = "transcription",
@@ -1456,6 +1434,7 @@ export enum ServiceType {
14561434
IRYS = "irys",
14571435
TEE_LOG = "tee_log",
14581436
GOPLUS_SECURITY = "goplus_security",
1437+
WEB_SEARCH = "web_search",
14591438
}
14601439

14611440
export enum LoggingLevel {

packages/core/tsup.config.ts

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export default defineConfig({
1818
"http",
1919
"https",
2020
// Add other modules you want to externalize
21-
"@tavily/core",
2221
"onnxruntime-node",
2322
"sharp",
2423
],

packages/plugin-web-search/README.MD

+23-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ TAVILY_API_KEY=your_api_key # Required: API key for search service
2828

2929
## Usage
3030

31-
Import and register the plugin in your Eliza configuration:
31+
Import and register the plugin in your Eliza configuration.
3232

3333
```typescript
3434
import { webSearchPlugin } from "@elizaos/plugin-web-search";
@@ -39,6 +39,28 @@ export default {
3939
};
4040
```
4141

42+
**Custom Usage**
43+
If you want custom usage, for example, twitter-client to search the web before posting a tweet, you can also import the webSearchService and use it directly. Here's how you can do it:
44+
45+
```typescript
46+
// packages/client-twitter/src/post.ts
47+
const webSearchService = new WebSearchService();
48+
await webSearchService.initialize(runtime);
49+
const latestNews = await webSearchService.search(
50+
"latest news on AI Agents",
51+
// searchOptions
52+
);
53+
54+
const state = await this.runtime.composeState(
55+
{ } // memory,
56+
{ // additional keys
57+
latestNews: latestNews,
58+
}
59+
);
60+
61+
// Then modify the tweet template to include the {{latestNews}} and however you need
62+
```
63+
4264
## Features
4365

4466
### Web Search

packages/plugin-web-search/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
],
2121
"dependencies": {
2222
"@elizaos/core": "workspace:*",
23-
"tsup": "8.3.5"
23+
"@tavily/core": "^0.0.2",
24+
"tsup": "8.3.5",
25+
"js-tiktoken": "1.0.15"
2426
},
2527
"scripts": {
2628
"build": "tsup --format esm --dts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import {
2+
Action,
3+
HandlerCallback,
4+
IAgentRuntime,
5+
Memory,
6+
State,
7+
elizaLogger
8+
} from "@elizaos/core";
9+
import { encodingForModel, TiktokenModel } from "js-tiktoken";
10+
import { WebSearchService } from "../services/webSearchService";
11+
import { SearchResult } from "../types";
12+
13+
const DEFAULT_MAX_WEB_SEARCH_TOKENS = 4000;
14+
const DEFAULT_MODEL_ENCODING = "gpt-3.5-turbo";
15+
16+
function getTotalTokensFromString(
17+
str: string,
18+
encodingName: TiktokenModel = DEFAULT_MODEL_ENCODING
19+
) {
20+
const encoding = encodingForModel(encodingName);
21+
return encoding.encode(str).length;
22+
}
23+
24+
function MaxTokens(
25+
data: string,
26+
maxTokens: number = DEFAULT_MAX_WEB_SEARCH_TOKENS
27+
): string {
28+
if (getTotalTokensFromString(data) >= maxTokens) {
29+
return data.slice(0, maxTokens);
30+
}
31+
return data;
32+
}
33+
34+
export const webSearch: Action = {
35+
name: "WEB_SEARCH",
36+
similes: [
37+
"SEARCH_WEB",
38+
"INTERNET_SEARCH",
39+
"LOOKUP",
40+
"QUERY_WEB",
41+
"FIND_ONLINE",
42+
"SEARCH_ENGINE",
43+
"WEB_LOOKUP",
44+
"ONLINE_SEARCH",
45+
"FIND_INFORMATION",
46+
],
47+
suppressInitialMessage: true,
48+
description:
49+
"Perform a web search to find information related to the message.",
50+
validate: async (runtime: IAgentRuntime, message: Memory) => {
51+
const tavilyApiKeyOk = !!runtime.getSetting("TAVILY_API_KEY");
52+
53+
return tavilyApiKeyOk;
54+
},
55+
handler: async (
56+
runtime: IAgentRuntime,
57+
message: Memory,
58+
state: State,
59+
options: any,
60+
callback: HandlerCallback
61+
) => {
62+
elizaLogger.log("Composing state for message:", message);
63+
state = (await runtime.composeState(message)) as State;
64+
const userId = runtime.agentId;
65+
elizaLogger.log("User ID:", userId);
66+
67+
const webSearchPrompt = message.content.text;
68+
elizaLogger.log("web search prompt received:", webSearchPrompt);
69+
70+
const webSearchService = new WebSearchService();
71+
await webSearchService.initialize(runtime);
72+
const searchResponse = await webSearchService.search(
73+
webSearchPrompt,
74+
);
75+
76+
if (searchResponse && searchResponse.results.length) {
77+
const responseList = searchResponse.answer
78+
? `${searchResponse.answer}${
79+
Array.isArray(searchResponse.results) &&
80+
searchResponse.results.length > 0
81+
? `\n\nFor more details, you can check out these resources:\n${searchResponse.results
82+
.map(
83+
(result: SearchResult, index: number) =>
84+
`${index + 1}. [${result.title}](${result.url})`
85+
)
86+
.join("\n")}`
87+
: ""
88+
}`
89+
: "";
90+
91+
callback({
92+
text: MaxTokens(responseList, DEFAULT_MAX_WEB_SEARCH_TOKENS),
93+
});
94+
} else {
95+
elizaLogger.error("search failed or returned no data.");
96+
}
97+
},
98+
examples: [
99+
[
100+
{
101+
user: "{{user1}}",
102+
content: {
103+
text: "Find the latest news about SpaceX launches.",
104+
},
105+
},
106+
{
107+
user: "{{agentName}}",
108+
content: {
109+
text: "Here is the latest news about SpaceX launches:",
110+
action: "WEB_SEARCH",
111+
},
112+
},
113+
],
114+
[
115+
{
116+
user: "{{user1}}",
117+
content: {
118+
text: "Can you find details about the iPhone 16 release?",
119+
},
120+
},
121+
{
122+
user: "{{agentName}}",
123+
content: {
124+
text: "Here are the details I found about the iPhone 16 release:",
125+
action: "WEB_SEARCH",
126+
},
127+
},
128+
],
129+
[
130+
{
131+
user: "{{user1}}",
132+
content: {
133+
text: "What is the schedule for the next FIFA World Cup?",
134+
},
135+
},
136+
{
137+
user: "{{agentName}}",
138+
content: {
139+
text: "Here is the schedule for the next FIFA World Cup:",
140+
action: "WEB_SEARCH",
141+
},
142+
},
143+
],
144+
[
145+
{
146+
user: "{{user1}}",
147+
content: { text: "Check the latest stock price of Tesla." },
148+
},
149+
{
150+
user: "{{agentName}}",
151+
content: {
152+
text: "Here is the latest stock price of Tesla I found:",
153+
action: "WEB_SEARCH",
154+
},
155+
},
156+
],
157+
[
158+
{
159+
user: "{{user1}}",
160+
content: {
161+
text: "What are the current trending movies in the US?",
162+
},
163+
},
164+
{
165+
user: "{{agentName}}",
166+
content: {
167+
text: "Here are the current trending movies in the US:",
168+
action: "WEB_SEARCH",
169+
},
170+
},
171+
],
172+
[
173+
{
174+
user: "{{user1}}",
175+
content: {
176+
text: "What is the latest score in the NBA finals?",
177+
},
178+
},
179+
{
180+
user: "{{agentName}}",
181+
content: {
182+
text: "Here is the latest score from the NBA finals:",
183+
action: "WEB_SEARCH",
184+
},
185+
},
186+
],
187+
[
188+
{
189+
user: "{{user1}}",
190+
content: { text: "When is the next Apple keynote event?" },
191+
},
192+
{
193+
user: "{{agentName}}",
194+
content: {
195+
text: "Here is the information about the next Apple keynote event:",
196+
action: "WEB_SEARCH",
197+
},
198+
},
199+
],
200+
],
201+
} as Action;

0 commit comments

Comments
 (0)