diff --git a/packages/client-direct/src/index.ts b/packages/client-direct/src/index.ts index 2b9ae7a445..eed6db6730 100644 --- a/packages/client-direct/src/index.ts +++ b/packages/client-direct/src/index.ts @@ -239,7 +239,7 @@ export class DirectClient { ); if (message) { - res.json([message, response]); + res.json([response, message]); } else { res.json([response]); } diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index 50d32f9044..e9bab6509d 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -31,6 +31,7 @@ import { ModelClass, ModelProviderName, ServiceType, + SearchResponse, } from "./types.ts"; import { fal, } from "@fal-ai/client"; @@ -972,6 +973,39 @@ export const generateCaption = async ( description: resp.description.trim(), }; }; + +export const generateWebSearch = async ( + query: string, + runtime: IAgentRuntime +): Promise => { + const apiUrl = "https://api.tavily.com/search"; + const apiKey = runtime.getSetting("TAVILY_API_KEY"); + + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + api_key: apiKey, + query, + include_answer: true, + }), + }); + + if (!response.ok) { + throw new elizaLogger.error( + `HTTP error! status: ${response.status}` + ); + } + + const data: SearchResponse = await response.json(); + return data; + } catch (error) { + elizaLogger.error("Error:", error); + } +}; /** * Configuration options for generating objects with a model. */ diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 74ba925975..5f9324cb7d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1100,6 +1100,23 @@ export interface IPdfService extends Service { convertPdfToText(pdfBuffer: Buffer): Promise; } +export type SearchResult = { + title: string; + url: string; + content: string; + score: number; + raw_content: string | null; +}; + +export type SearchResponse = { + query: string; + follow_up_questions: string[] | null; + answer: string | null; + images: string[]; + results: SearchResult[]; + response_time: number; +}; + export enum ServiceType { IMAGE_DESCRIPTION = "image_description", TRANSCRIPTION = "transcription", diff --git a/packages/plugin-web-search/.npmignore b/packages/plugin-web-search/.npmignore new file mode 100644 index 0000000000..078562ecea --- /dev/null +++ b/packages/plugin-web-search/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-web-search/package.json b/packages/plugin-web-search/package.json new file mode 100644 index 0000000000..84e18b8a43 --- /dev/null +++ b/packages/plugin-web-search/package.json @@ -0,0 +1,17 @@ +{ + "name": "@ai16z/plugin-web-search", + "version": "0.1.3", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@ai16z/eliza": "workspace:*", + "tsup": "^8.3.5" + }, + "scripts": { + "build": "tsup --format esm --dts" + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} diff --git a/packages/plugin-web-search/src/index.ts b/packages/plugin-web-search/src/index.ts new file mode 100644 index 0000000000..4d7d7be44c --- /dev/null +++ b/packages/plugin-web-search/src/index.ts @@ -0,0 +1,188 @@ +import { elizaLogger } from "@ai16z/eliza"; +import { + Action, + HandlerCallback, + IAgentRuntime, + Memory, + Plugin, + State, +} from "@ai16z/eliza"; +import { generateWebSearch } from "@ai16z/eliza"; + +import { SearchResult } from "@ai16z/eliza"; + +const webSearch: Action = { + name: "WEB_SEARCH", + similes: [ + "SEARCH_WEB", + "INTERNET_SEARCH", + "LOOKUP", + "QUERY_WEB", + "FIND_ONLINE", + "SEARCH_ENGINE", + "WEB_LOOKUP", + "ONLINE_SEARCH", + "FIND_INFORMATION", + ], + description: + "Perform a web search to find information related to the message.", + validate: async (runtime: IAgentRuntime, message: Memory) => { + const tavilyApiKeyOk = !!runtime.getSetting("TAVILY_API_KEY"); + + return tavilyApiKeyOk; + }, + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + options: any, + callback: HandlerCallback + ) => { + elizaLogger.log("Composing state for message:", message); + state = (await runtime.composeState(message)) as State; + const userId = runtime.agentId; + elizaLogger.log("User ID:", userId); + + const webSearchPrompt = message.content.text; + elizaLogger.log("web search prompt received:", webSearchPrompt); + + elizaLogger.log("Generating image with prompt:", webSearchPrompt); + const searchResponse = await generateWebSearch( + webSearchPrompt, + runtime + ); + + if (searchResponse && searchResponse.results.length) { + const responseList = searchResponse.answer + ? `${searchResponse.answer}${ + Array.isArray(searchResponse.results) && + searchResponse.results.length > 0 + ? `\n\nFor more details, you can check out these resources:\n${searchResponse.results + .map( + (result: SearchResult, index: number) => + `${index + 1}. [${result.title}](${result.url})` + ) + .join("\n")}` + : "" + }` + : ""; + + callback({ + text: responseList, + }); + } else { + elizaLogger.error("search failed or returned no data."); + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Find the latest news about SpaceX launches.", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Here is the latest news about SpaceX launches:", + action: "WEB_SEARCH", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Can you find details about the iPhone 16 release?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Here are the details I found about the iPhone 16 release:", + action: "WEB_SEARCH", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "What is the schedule for the next FIFA World Cup?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Here is the schedule for the next FIFA World Cup:", + action: "WEB_SEARCH", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Check the latest stock price of Tesla." }, + }, + { + user: "{{agentName}}", + content: { + text: "Here is the latest stock price of Tesla I found:", + action: "WEB_SEARCH", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "What are the current trending movies in the US?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Here are the current trending movies in the US:", + action: "WEB_SEARCH", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "What is the latest score in the NBA finals?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Here is the latest score from the NBA finals:", + action: "WEB_SEARCH", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "When is the next Apple keynote event?" }, + }, + { + user: "{{agentName}}", + content: { + text: "Here is the information about the next Apple keynote event:", + action: "WEB_SEARCH", + }, + }, + ], + ], +} as Action; + +export const webSearchPlugin: Plugin = { + name: "webSearch", + description: "Search web", + actions: [webSearch], + evaluators: [], + providers: [], +}; diff --git a/packages/plugin-web-search/tsconfig.json b/packages/plugin-web-search/tsconfig.json new file mode 100644 index 0000000000..69b4220036 --- /dev/null +++ b/packages/plugin-web-search/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/plugin-web-search/tsup.config.ts b/packages/plugin-web-search/tsup.config.ts new file mode 100644 index 0000000000..b5e4388b21 --- /dev/null +++ b/packages/plugin-web-search/tsup.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], // Ensure you're targeting CommonJS + external: [ + "dotenv", // Externalize dotenv to prevent bundling + "fs", // Externalize fs to use Node.js built-in module + "path", // Externalize other built-ins if necessary + "@reflink/reflink", + "@node-llama-cpp", + "https", + "http", + "agentkeepalive", + "zod", + // Add other modules you want to externalize + ], +}); diff --git a/scripts/build.sh b/scripts/build.sh index a006ded7dc..48c965e737 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -35,16 +35,15 @@ PACKAGES=( "plugin-bootstrap" "plugin-image-generation" "plugin-coinbase" - "plugin-node" - "plugin-bootstrap" "plugin-evm" - "plugin-image-generation" "plugin-tee" "client-auto" "client-direct" "client-discord" "client-telegram" "client-twitter" + "client-whatsapp" + "plugin-web-search" ) # Build packages in specified order