From 6618a504d9e74aacf5ef735f090490471e915f61 Mon Sep 17 00:00:00 2001 From: metakai1 Date: Mon, 30 Dec 2024 22:04:16 -0700 Subject: [PATCH] added save-this plugin from ubc-eliza/save_memory5 --- packages/plugin-save-this/package.json | 24 +++ .../plugin-save-this/src/index copy.ts.md | 98 +++++++++++ packages/plugin-save-this/src/index.d.ts | 14 ++ packages/plugin-save-this/src/index.ts | 163 ++++++++++++++++++ packages/plugin-save-this/src/index.ts.md | 139 +++++++++++++++ packages/plugin-save-this/tsconfig.json | 13 ++ 6 files changed, 451 insertions(+) create mode 100644 packages/plugin-save-this/package.json create mode 100644 packages/plugin-save-this/src/index copy.ts.md create mode 100644 packages/plugin-save-this/src/index.d.ts create mode 100644 packages/plugin-save-this/src/index.ts create mode 100644 packages/plugin-save-this/src/index.ts.md create mode 100644 packages/plugin-save-this/tsconfig.json diff --git a/packages/plugin-save-this/package.json b/packages/plugin-save-this/package.json new file mode 100644 index 00000000..63e38ebe --- /dev/null +++ b/packages/plugin-save-this/package.json @@ -0,0 +1,24 @@ +{ + "name": "@ai16z/plugin-save-this", + "version": "0.0.1", + "description": "Plugin for saving important information from conversations", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "module", + "dependencies": { + "@ai16z/eliza": "workspace:*" + }, + "scripts": { + "build": "tsc", + "example": "tsx examples/basic-usage.ts" + }, + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "devDependencies": { + "tsx": "^4.7.0" + } +} diff --git a/packages/plugin-save-this/src/index copy.ts.md b/packages/plugin-save-this/src/index copy.ts.md new file mode 100644 index 00000000..390cb890 --- /dev/null +++ b/packages/plugin-save-this/src/index copy.ts.md @@ -0,0 +1,98 @@ +// This plugin is for loading and saving data to the database written by Kai +// Claude, please help me develop this plugin as an exercise for me +// I want to learn the function of Evaluators, Providers, and Actions +// Please help me to develop this plugin step by step. +// we'll complete a basic save-memory action and have it prompt the user for what memories to save. + +import { Plugin, AgentRuntime, knowledge, stringToUuid, settings, Action, elizaLogger } from "@ai16z/eliza"; +import type { KnowledgeItem } from "@ai16z/eliza"; + +// Get user ID from environment variables +const USER_ID = settings.USER_ID || process.env.USER_ID; +if (!USER_ID) { + throw new Error("USER_ID must be set in environment variables"); +} + +const saveMemoryAction: Action = { + name: "save-memory", + description: "Store information in the agent's memory or load data into the memory system", + similes: [ + "save a memory", + "store information", + "remember something", + "load data into memories", + "import data to memories", + "memorize this", + "keep track of this", + "add this to your memory" + ], + examples: [[ + { + user: "user", + content: { + text: "Save this memory: The sky is blue", + action: "save-memory", + }, + }, + ], [ + { + user: "user", + content: { + text: "load this data into memories: Important project deadline is next Friday", + action: "save-memory", + }, + }, + ], [ + { + user: "user", + content: { + text: "memorize that Alice prefers tea over coffee", + action: "save-memory", + }, + }, + ]], + handler: async (runtime: AgentRuntime, message: any) => { + try { + const text = message.content.text; + elizaLogger.debug("Preprocessing text:", { + input: text, + length: text?.length, + }); + + // Create a knowledge item for the incoming text + const documentId = stringToUuid(text); + const knowledgeItem: KnowledgeItem = { + id: documentId, + content: { + text, + source: "user-input" + } + }; + + // Use the high-level knowledge.set function to create document and fragment memories + await knowledge.set(runtime, knowledgeItem); + + return [{ + userId: runtime.agentId, + agentId: runtime.agentId, + roomId: message.roomId, + content: { + text: "I've stored that information in my memory", + action: "save-memory", + }, + }]; + } catch (error) { + elizaLogger.error("Failed to create memory:", error); + throw new Error("Failed to store memory: " + error.message); + } + }, + validate: async () => Promise.resolve(true), +}; + +export const databaseLoaderPlugin: Plugin = { + name: "database-loader", + description: "Plugin for loading and saving data to the database", + actions: [saveMemoryAction] +}; + +export { loadMemoriesFromFile } from './load-memories'; diff --git a/packages/plugin-save-this/src/index.d.ts b/packages/plugin-save-this/src/index.d.ts new file mode 100644 index 00000000..df20b82d --- /dev/null +++ b/packages/plugin-save-this/src/index.d.ts @@ -0,0 +1,14 @@ +export interface CalculatorPlugin { + name: string; + description: string; + initialize: (runtime: any) => void; + commands: { + add: (params: { a: number; b: number }) => number; + subtract: (params: { a: number; b: number }) => number; + multiply: (params: { a: number; b: number }) => number; + divide: (params: { a: number; b: number }) => number; + }; +} + +export const calculatorPlugin: CalculatorPlugin; +export default calculatorPlugin; diff --git a/packages/plugin-save-this/src/index.ts b/packages/plugin-save-this/src/index.ts new file mode 100644 index 00000000..6650afd8 --- /dev/null +++ b/packages/plugin-save-this/src/index.ts @@ -0,0 +1,163 @@ +import { Plugin, AgentRuntime, knowledge, stringToUuid, generateText, settings, Action, elizaLogger, MemoryManager, EvaluationExample } from "@ai16z/eliza"; +import type { KnowledgeItem } from "@ai16z/eliza"; + +import { + Evaluator, + IAgentRuntime, + Memory, + State, + Provider, + HandlerCallback, +} from "@ai16z/eliza"; + +const saveThisAction: Action = { + name: "SAVE_THIS", + description: "Stores important information from the conversation in the agent's long-term knowledge base", + similes: [], + validate: async (runtime: IAgentRuntime, message: Memory) => { + return Promise.resolve(!!message?.content?.text); + }, + + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + try { + + elizaLogger.info(state); + + // Only proceed if explicitly requested via state + if (!state?.shouldSave) { + elizaLogger.info('[SaveThisAction] handler.abort - Save not requested in state'); + return state; // Important: Return the unchanged state + } + // Get recent messages + const recentMessages = await runtime.messageManager.getMemories({ + roomId: message.roomId, + count: 7, + unique: false + }); + + // combine the text from recent messages into a string + const recentMessagesText = recentMessages + .sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0)) + .map(msg => msg.content.text) + .join("\n\n"); + + if (callback) { + await callback({ + text: "Summary in progress...", + content: { + text: "Summary in progress..." + } + }, []); + } else { + elizaLogger.error('[SaveThisAction] No callback'); + } + + elizaLogger.info("Recent messages:", recentMessagesText); + + const saveKnowledge = await generateText({ + runtime, + context: `\ +The following messages are from a conversation between an ai agent and a user. +The most recent 7 messages are sent to this query, ordered from oldest to newest. Some messages from +the user may be included. The last message is the "save this" request from the user, which is then triggers this query. Realize that conversation history may include agent responses \ +from previous user queries. You should determine by the flow of conversation what +information the user is wanting to save. Prioritize saving the most recent agent response. + +The user my also append additional words to the "save this" request, which may be relevant in +deciding what information for you to save. For example, he/she could say "save this information about cars". +By those words, you can determine what information the wants to focus on. + +Save instructions: Do not store information about the user. Focus on saving knowledge, not conversation +history. Don't save what the conversation is about, but rather the facts and details contained in the +responses by the agent, retaining style and tone. Save the memory as a paragraph of text, not in point +or bullet form. Here are the messages: +${recentMessagesText}`, + modelClass: "medium" + }); + + //elizaLogger.info("Saved knowledge: from model", saveKnowledge); + + // Save the message content to the knowledge base + const memoryToSave = { + id: stringToUuid(`memory_${Date.now()}`), + content: { + text: saveKnowledge, + source: "agentdata" + } + }; + + //elizaLogger.info("Memory to save:", memoryToSave); + + await knowledge.set(runtime as AgentRuntime, memoryToSave); + + if (callback) { + callback({ +// text: `I've stored this information: "${saveKnowledge}"`, + text: `I've stored the information for you`, + }); + return; + } + + } catch (error) { + elizaLogger.error('[Action] handler.error:', error); + if (callback) { + await callback({ + text: "Sorry, I encountered an error while saving.", + content: { + success: false, + text: "Sorry, I encountered an error while saving." + } + }, []); + } + return false; + } + }, + examples: [] +}; + +export const saveThisProvider: Provider = { + get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { + const text = message.content?.text?.toLowerCase() || ''; + + // Trigger if message starts with "save this" + if (text.trim().startsWith('save this')) { + // Modify state in place first + if (state) { + state.shouldSave = true; + } + + if(!state.shouldSave) { + elizaLogger.error('saveThisProvider: state.shouldSave is faised'); + } + + // Then trigger the SAVE_THIS action + await runtime.processActions(message, [{ + id: stringToUuid(`save_this_response_${Date.now()}`), + userId: message.userId, + agentId: message.agentId, + roomId: message.roomId, + content: { + action: 'SAVE_THIS', + text: 'Saving previous message...' + } + }]); + } + + return; + } +}; + + +export const saveThisPlugin: Plugin = { + name: "save-this", + description: "Plugin for saving important information from conversations using a save this keyphrase", + actions: [saveThisAction], + evaluators: [], + providers: [saveThisProvider] +}; \ No newline at end of file diff --git a/packages/plugin-save-this/src/index.ts.md b/packages/plugin-save-this/src/index.ts.md new file mode 100644 index 00000000..6d0c9cf9 --- /dev/null +++ b/packages/plugin-save-this/src/index.ts.md @@ -0,0 +1,139 @@ +import { Plugin, AgentRuntime, knowledge, stringToUuid, generateText, settings, Action, elizaLogger, MemoryManager, EvaluationExample } from "@ai16z/eliza"; +import type { KnowledgeItem } from "@ai16z/eliza"; + +import { + Evaluator, + IAgentRuntime, + Memory, + State, + Provider, + HandlerCallback, +} from "@ai16z/eliza"; + +interface SaveThisState extends State { + shouldSave?: boolean; +} + +const saveThisAction: Action = { + name: "SAVE_THIS", + description: "Stores important information from the conversation in the agent's long-term knowledge base", + similes: [], + validate: async (runtime: IAgentRuntime, message: Memory) => { + return Promise.resolve(!!message?.content?.text); + }, + + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: SaveThisState, + _options: { [key: string]: unknown }, + callback: HandlerCallback + ) => { + + // Get recent messages + const recentMessages = await runtime.messageManager.getMemories({ + roomId: message.roomId, + count: 7, + unique: false + }); + + // combine the text from recent messages into a string + const recentMessagesText = recentMessages + .sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0)) + .map(msg => msg.content.text) + .join("\n\n"); + +/* if (callback) { + await callback({ + text: "Summary in progress...", + content: { + text: "Summary in progress..." + } + }, []); + } */ + + elizaLogger.info("Recent messages:", recentMessagesText); + + const saveKnowledge = await generateText({ + runtime, + context: `\ +The following messages are from a conversation between an ai agent and a user. +The most recent 7 messages are sent to this query, ordered from oldest to newest. Some messages from +the user may be included. The last message is the "save this" request from the user, which is then triggers this query. Realize that conversation history may include agent responses \ +from previous user queries. You should determine by the flow of conversation what +information the user is wanting to save. Prioritize saving the most recent agent response. + +The user my also append additional words to the "save this" request, which may be relevant in +deciding what information for you to save. For example, he/she could say "save this information about cars". +By those words, you can determine what information the wants to focus on. + +Save instructions: Do not store information about the user. Focus on saving knowledge, not conversation +history. Don't save what the conversation is about, but rather the facts and details contained in the +responses by the agent, retaining style and tone. Save the memory as a paragraph of text, not in point +or bullet form. Here are the messages: +${recentMessagesText}`, + modelClass: "medium" + }); + + //elizaLogger.info("Saved knowledge: from model", saveKnowledge); + + // Save the message content to the knowledge base + const memoryToSave = { + id: stringToUuid(`memory_${Date.now()}`), + content: { + text: saveKnowledge, + source: "agentdata" + } + }; + + //elizaLogger.info("Memory to save:", memoryToSave); + + await knowledge.set(runtime as AgentRuntime, memoryToSave); + + if (!callback) { + elizaLogger.error("No callback"); + }; + + callback({ + text: "I've stored the information for you", + }); + }, + examples: [] +}; + +export const saveThisProvider: Provider = { + get: async (runtime: IAgentRuntime, message: Memory, state?: SaveThisState) => { + const text = message.content?.text?.toLowerCase() || ''; + + // Trigger if message starts with "save this" + if (text.trim().startsWith('save this')) { + // Modify state in place first + if (state) { + state.shouldSave = true; + } + + // Then trigger the SAVE_THIS action + await runtime.processActions(message, [{ + id: stringToUuid(`save_this_response_${Date.now()}`), + userId: message.userId, + agentId: message.agentId, + roomId: message.roomId, + content: { + action: 'SAVE_THIS', + text: 'Saving previous message...' + } + }]); + } + + return state; + } +}; + + +export const saveThisPlugin: Plugin = { + name: "save-this", + description: "Plugin for saving important information from conversations using a save this keyphrase", + actions: [saveThisAction], + evaluators: [], + providers: [saveThisProvider] +}; \ No newline at end of file diff --git a/packages/plugin-save-this/tsconfig.json b/packages/plugin-save-this/tsconfig.json new file mode 100644 index 00000000..8e504316 --- /dev/null +++ b/packages/plugin-save-this/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "module": "ESNext", + "moduleResolution": "Node", + "esModuleInterop": true + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file