MemoryStackMemoryStack/Documentation
    Back to Documentation

    Building Emma: A Memory-Powered Chatbot

    Follow along as we create Emma, an AI assistant that remembers everything about her users and gets smarter with every conversation.

    The Challenge

    Imagine you're building a customer support chatbot for your SaaS company. Users are frustrated because every time they start a new conversation, they have to re-explain their preferences, their account details, and their previous issues.

    What if your chatbot could remember everything? What if it knew that Sarah prefers technical explanations, that John always asks about billing on Fridays, and that Maria's account has had three support tickets this month?

    Building Emma: Step by Step

    Step 1: Environment Setup

    First, let's set up our environment with the necessary dependencies:

    # Install the required packages
    pip install memorystack openai python-dotenv
    
    # Create your .env file
    echo "MEMORY_OS_API_KEY=your_memory_os_key_here" > .env
    echo "OPENAI_API_KEY=your_openai_key_here" >> .env

    Step 2: Emma's Core Brain

    Here's Emma's main class - her "brain" that handles memory and conversations:

    from memorystack import MemoryStackClient
    from openai import OpenAI
    import os
    from dotenv import load_dotenv
    from datetime import datetime
    
    load_dotenv()
    
    class Emma:
        """Emma - A memory-powered chatbot that remembers everything"""
        
        def __init__(self, user_id: str):
            self.user_id = user_id
            self.name = "Emma"
            
            # Initialize Memorystack client
            self.memory = MemoryStackClient(
                api_key=os.getenv("MEMORYSTACK_API_KEY"),
                user_id=user_id
            )
            
            # Initialize OpenAI client
            self.ai = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
            
            print(f"šŸ‘‹ Hi! I'm {self.name}, your memory-powered assistant!")
        
        def chat(self, user_message: str) -> str:
            """The main conversation method"""
            print(f"\n🧠 Processing: '{user_message}'")
            
            # Step 1: Store the user's message
            self._store_user_message(user_message)
            
            # Step 2: Retrieve relevant memories
            relevant_memories = self._get_relevant_memories(user_message)
            
            # Step 3: Generate response with context
            response = self._generate_response(user_message, relevant_memories)
            
            # Step 4: Store the response and learn
            self._store_response(response)
            self._learn_from_interaction(user_message)
            
            return response

    Step 3: Complete Emma Implementation

    Here are all the helper methods that make Emma work:

        def _store_user_message(self, message: str):
            """Store user message in memory"""
            self.memory.create_memory(
                content=f"User said: {message}",
                memory_type="conversation",
                metadata={"role": "user", "timestamp": datetime.now().isoformat()}
            )
        
        def _get_relevant_memories(self, query: str) -> list:
            """Retrieve memories relevant to the current query"""
            memories = self.memory.search_memories(query=query, limit=8)
            results = memories.get('results', [])
            print(f"šŸ“š Found {len(results)} relevant memories")
            return results
        
        def _generate_response(self, user_message: str, memories: list) -> str:
            """Generate contextual response using memories"""
            context_parts = []
            for memory in memories:
                content = memory.get('content', '')
                memory_type = memory.get('memory_type', 'unknown')
                context_parts.append(f"[{memory_type}] {content}")
            
            context_text = "\n".join(context_parts)
            
            system_prompt = f"""You are Emma, a friendly and intelligent AI assistant with perfect memory.
    
    PERSONALITY:
    - Warm, helpful, and personable
    - Remember details about users and reference them naturally
    - Adapt your communication style to user preferences
    
    CONTEXT FROM MEMORY:
    {context_text}
    
    Use this context to provide personalized responses."""
            
            response = self.ai.chat.completions.create(
                model="gpt-4",
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_message}
                ],
                temperature=0.7
            )
            return response.choices[0].message.content
        
        def _store_response(self, response: str):
            """Store Emma's response in memory"""
            self.memory.create_memory(
                content=f"Emma said: {response}",
                memory_type="conversation",
                metadata={"role": "assistant", "timestamp": datetime.now().isoformat()}
            )
        
        def _learn_from_interaction(self, user_message: str):
            """Extract and store learnings from user message"""
            analysis = self.ai.chat.completions.create(
                model="gpt-4",
                messages=[{
                    "role": "system",
                    "content": """Extract preferences, facts, and context from the message.
    Return as JSON: {"preferences": [], "facts": [], "context": []}"""
                }, {"role": "user", "content": user_message}]
            )
            
            try:
                import json
                learnings = json.loads(analysis.choices[0].message.content)
                
                for pref in learnings.get('preferences', []):
                    self.memory.create_memory(
                        content=f"User preference: {pref}",
                        memory_type="preference"
                    )
                
                for fact in learnings.get('facts', []):
                    self.memory.create_memory(
                        content=f"User fact: {fact}",
                        memory_type="fact"
                    )
            except Exception as e:
                print(f"āš ļø Learning extraction failed: {e}")

    Using Emma: See Her in Action

    Complete Usage Example

    Here's how to use Emma in your application:

    # main.py - Let's chat with Emma!
    def main():
        # Create Emma for a specific user
        emma = Emma(user_id="sarah_engineer_123")
        print("šŸ¤– Emma is ready to chat!")
        print("=" * 50)
        
        # First conversation - Emma learns about Sarah
        response1 = emma.chat(
            "Hi Emma! I'm Sarah, a software engineer. I prefer detailed technical explanations."
        )
        print(f"Emma: {response1}")
        
        # Second conversation - Emma remembers
        response2 = emma.chat(
            "I'm getting a 429 error when making requests. What should I do?"
        )
        print(f"Emma: {response2}")
        
        # Interactive chat loop
        while True:
            user_input = input("\nYou: ")
            if user_input.lower() in ['quit', 'exit', 'bye']:
                print("Emma: Goodbye! I'll remember our conversation! šŸ‘‹")
                break
            
            response = emma.chat(user_input)
            print(f"Emma: {response}")
    
    if __name__ == "__main__":
        main()

    Expected Output

    Emma: Hello Sarah! Nice to meet you. I've noted that you're a software engineer who prefers detailed technical explanations.

    ---

    Emma: Hi Sarah! That 429 error indicates you've hit rate limits. Here's the technical solution: implement exponential backoff with jitter...

    Making Emma Even Smarter

    šŸŽÆ Smart Context Selection

    Prioritize memories based on recency, importance, and relevance:

    def _get_smart_context(self, query: str):
        # Get recent conversations
        recent = self.memory.search_memories(
            query=query, 
            limit=3,
            metadata_filter={"days_ago": "< 7"}
        )
        
        # Get important facts
        facts = self.memory.search_memories(
            query=query,
            memory_type="fact",
            limit=2
        )
        
        return recent + facts

    šŸ”„ Conversation Summaries

    Create summaries of long conversations to maintain context efficiently:

    def create_conversation_summary(self):
        # Get last 20 messages
        recent_messages = self.memory.search_memories(
            memory_type="conversation",
            limit=20
        )
        
        # Create summary
        summary = self.ai.chat.completions.create(
            model="gpt-4",
            messages=[{
                "role": "system",
                "content": "Summarize key points"
            }]
        )
        
        # Store summary
        self.memory.create_memory(
            content=summary,
            memory_type="summary"
        )

    Best Practices

    1. Store Both Sides of Conversation

    Always store both user messages and bot responses to maintain complete context.

    2. Use Semantic Search

    Leverage Memorystack's semantic search to find relevant context, not just keyword matches.

    3. Limit Context Window

    Retrieve only the most relevant memories (5-10) to avoid overwhelming the AI model.

    4. Add Metadata

    Include timestamps, roles, and other metadata for better organization and filtering.

    Next Steps