@letta-ai/memory-sdk
Version: 
TypeScript SDK for using Letta subagents for pluggable memory management
174 lines (173 loc) • 6.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Memory = void 0;
const letta_client_1 = require("@letta-ai/letta-client");
const prompt_formatter_1 = require("./prompt-formatter");
class Memory {
    constructor(config = {}) {
        const apiKey = config.lettaApiKey || process.env.LETTA_API_KEY;
        this.lettaClient = new letta_client_1.LettaClient({ token: apiKey });
    }
    async createSleeptimeAgent(name, tags) {
        const agentState = await this.lettaClient.agents.create({
            name,
            model: 'openai/gpt-4',
            agentType: 'sleeptime_agent',
            initialMessageSequence: [],
            tags,
        });
        return agentState.id;
    }
    async getMatchingAgent(tags) {
        const agents = await this.lettaClient.agents.list({
            tags,
            matchAllTags: true,
        });
        return agents.length > 0 ? agents[0] : null;
    }
    async createContextBlock(agentId, label, description, charLimit = 10000, value = '') {
        const block = await this.lettaClient.blocks.create({
            label,
            description,
            limit: charLimit,
            value,
        });
        // Attach block to agent
        await this.lettaClient.agents.blocks.attach(agentId, block.id);
        return block.id;
    }
    async listContextBlocks(agentId) {
        return await this.lettaClient.agents.blocks.list(agentId);
    }
    async deleteContextBlock(agentId, blockId) {
        await this.lettaClient.agents.blocks.detach(agentId, blockId);
        await this.lettaClient.blocks.delete(blockId);
    }
    async deleteAgent(agentId) {
        await this.lettaClient.agents.delete(agentId);
    }
    async learnMessages(agentId, messages, skipVectorStorage = true) {
        const messageCreates = messages.map(msg => ({
            content: msg.content,
            role: msg.role,
            name: msg.name,
            metadata: msg.metadata
        }));
        const formattedMessages = (0, prompt_formatter_1.formatMessages)(messageCreates);
        console.log('FORMATTED MESSAGES', formattedMessages);
        console.log('AGENT ID', agentId);
        const lettaRun = await this.lettaClient.agents.messages.createAsync(agentId, {
            messages: formattedMessages,
        });
        // Insert into archival in parallel if not skipping vector storage
        if (!skipVectorStorage) {
            const tasks = messages.map(message => this.lettaClient.agents.passages.create(agentId, {
                text: message.content,
                tags: [message.role],
            }));
            await Promise.all(tasks);
        }
        return lettaRun.id;
    }
    formatBlock(block) {
        return `<${block.label} description="${block.description}">${block.value}</${block.label}>`;
    }
    async getRunStatus(runId) {
        const run = await this.lettaClient.runs.retrieve(runId);
        if (!run) {
            throw new Error(`Run ${runId} not found`);
        }
        return run.status;
    }
    async waitForRun(runId) {
        while (await this.getRunStatus(runId) !== 'completed') {
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
    }
    async initializeUserMemory(userId, options = {}) {
        const { userContextBlockPrompt = 'Details about the human user you are speaking to.', userContextBlockCharLimit = 10000, userContextBlockValue = '', summaryBlockPrompt = 'A short (1-2 sentences) running summary of the conversation.', summaryBlockCharLimit = 1000, reset = false, } = options;
        // Check if agent already exists
        const existingAgent = await this.getMatchingAgent([userId]);
        if (existingAgent) {
            if (reset) {
                await this.deleteAgent(existingAgent.id);
            }
            else {
                throw new Error(`Agent ${existingAgent.id} already exists for user ${userId}. Cannot re-initialize memory unless deleted.`);
            }
        }
        // Create the agent
        const agentId = await this.createSleeptimeAgent(`subconscious_agent_user_${userId}`, [userId]);
        // Create context blocks
        await this.createContextBlock(agentId, 'human', userContextBlockPrompt, userContextBlockCharLimit, userContextBlockValue);
        await this.createContextBlock(agentId, 'summary', summaryBlockPrompt, summaryBlockCharLimit, '');
        // Attach a single archival memory (workaround)
        await this.lettaClient.agents.passages.create(agentId, {
            text: `Initialized memory for user ${userId}`,
        });
        return agentId;
    }
    async addMessages(userId, messages, skipVectorStorage = true) {
        let agentId;
        const agent = await this.getMatchingAgent([userId]);
        if (agent) {
            agentId = agent.id;
        }
        else {
            agentId = await this.initializeUserMemory(userId);
        }
        return await this.learnMessages(agentId, messages, skipVectorStorage);
    }
    async addFiles(files) {
        throw new Error('Not implemented');
    }
    async getUserMemory(userId, promptFormatted = false) {
        const agent = await this.getMatchingAgent([userId]);
        if (agent) {
            const block = await this.lettaClient.agents.blocks.retrieve(agent.id, 'human');
            if (promptFormatted) {
                return this.formatBlock(block);
            }
            return block.value;
        }
        return null;
    }
    async getSummary(userId, promptFormatted = false) {
        const agent = await this.getMatchingAgent([userId]);
        if (agent) {
            const block = await this.lettaClient.agents.blocks.retrieve(agent.id, 'summary');
            if (promptFormatted) {
                return `<conversation_summary>${block.value}</conversation_summary>`;
            }
            return block.value;
        }
        return null;
    }
    async getMemoryAgentId(userId) {
        const agent = await this.getMatchingAgent([userId]);
        if (agent) {
            return agent.id;
        }
        return null;
    }
    async deleteUser(userId) {
        const agent = await this.getMatchingAgent([userId]);
        if (agent) {
            // Deleting the agent also deletes associated messages/blocks
            await this.lettaClient.agents.delete(agent.id);
            console.log(`Deleted agent ${agent.id} for user ${userId}`);
        }
    }
    async search(userId, query) {
        const agent = await this.getMatchingAgent([userId]);
        if (agent) {
            const response = await this.lettaClient.agents.passages.search(agent.id, {
                query,
                tags: ['user'],
            });
            return response.results.map(result => result.content);
        }
        return [];
    }
}
exports.Memory = Memory;