Skip to main content
Build a personal AI assistant that learns and remembers everything about the user - their preferences, habits, work context, and conversation history.

What You’ll Build

A personal AI assistant that:
  • Remembers user preferences (dietary restrictions, work schedule, communication style)
  • Maintains context across multiple chat sessions
  • Provides personalized recommendations based on user history
  • Handles multiple conversation topics while maintaining context

Choose Your Implementation

Python + FastAPI

Thoroughly tested, production-ready. Uses FastAPI + Streamlit + OpenAI.

TypeScript + AI SDK

Modern React approach. Uses Next.js + Vercel AI SDK + Supermemory tools.

Prerequisites

Never hardcode API keys in your code. Use environment variables.

Python Implementation

Step 1: Project Setup

Create a .env file:

Step 2: Backend (FastAPI)

Create main.py. Let’s build it step by step:

Import Dependencies

  • FastAPI: Web framework for building the API endpoint
  • StreamingResponse: Enables real-time response streaming (words appear as they’re generated)
  • AsyncOpenAI: OpenAI client that supports async/await for non-blocking operations
  • Supermemory: Client for storing and retrieving long-term memories
  • uuid: Creates stable, deterministic user IDs from emails

Initialize Application and Clients

load_dotenv() loads API keys from your .env file into environment variables. We create two clients:
  • OpenAI client: Handles conversations and generates responses
  • Supermemory client: Stores and retrieves user-specific memories
These are separate because you can swap providers independently (e.g., switch from OpenAI to Anthropic without changing memory logic).

Define System Prompt

This prompt guides the assistant’s behavior. It tells the AI to:
  • Be proactive about learning user preferences
  • Always search memory before responding
  • Respect privacy boundaries
The system prompt is injected at the start of every conversation, so the AI consistently follows these rules.

Create Identity Helpers

Why normalize? "User@Mail.com" and " user@mail.com " should map to the same person. We trim whitespace and lowercase to ensure consistency. Why UUIDv5? It’s deterministic—same email always produces the same ID. This means:
  • User memories persist across sessions
  • No raw emails in logs or database tags
  • Privacy-preserving yet stable identity
We use uuid.NAMESPACE_DNS as the namespace to ensure uniqueness.

Memory Search Function

This searches the user’s memory store for context relevant to their current message. Parameters:
  • q: The search query (usually the user’s latest message)
  • container_tag: Isolates memories per user (e.g., user_abc123)
  • limit=5: Returns top 5 most relevant memories
Why search before responding? The AI can provide personalized answers based on what it knows about the user (e.g., dietary preferences, work context, communication style). Error handling: If memory search fails, we return a fallback message instead of crashing. The conversation continues even if memory has a hiccup.

Memory Storage Function

Stores new information about the user. Parameters:
  • content: The text to remember
  • container_tag: User isolation tag
  • metadata: Additional context (type of info, associated email)
Why metadata? Makes it easier to filter and organize memories later (e.g., “show me all personal_info memories”). Error handling: We log errors but don’t crash. Failing to save one memory shouldn’t break the entire conversation.

Main Chat Endpoint

This endpoint receives the chat request. It expects:
  • messages: Full conversation history [{role: "user", content: "..."}]
  • email: User’s email for identity
Why require email? Without it, we can’t create a stable user ID, meaning no persistent personalization.

Derive User Identity

Convert email → stable user ID → container tag. The container tag (user_abc123) isolates this user’s memories from everyone else’s. Each user has their own “memory box.”

Search and Inject Memories

We take the user’s latest message, search for relevant memories, then inject them into the system prompt. Example:
Now the AI can answer: “Try overnight oats with plant-based protein—perfect for post-workout!”

Stream OpenAI Response

Key parameters:
  • model="gpt-5": Fast, capable model
  • messages: Full conversation + memory context
  • temperature=0.7: Balanced creativity (0=deterministic, 1=creative)
  • stream=True: Enables word-by-word streaming
Why stream? Users see responses appear in real-time instead of waiting for the complete answer. Much better UX.

Handle Streaming

This async generator:
  1. Receives chunks from OpenAI as they’re generated
  2. Extracts the text content from each chunk
  3. Formats it as Server-Sent Events (SSE): data: {...}\n\n
  4. Yields it to the client
SSE format is a web standard for server→client streaming. The frontend can process each chunk as it arrives.

Optional Memory Storage

After streaming completes, check if the user explicitly asked to remember something. If yes, store it. Why opt-in? Gives users control over what gets remembered. You could also make this automatic based on content analysis.

Return Streaming Response

StreamingResponse keeps the HTTP connection open and sends chunks as they’re generated. The frontend receives them in real-time.

Local Development Server

Run with python main.py and the server starts on port 8000. 0.0.0.0 means it accepts connections from any IP (useful for testing from other devices).

Step 3: Frontend (Streamlit)

Create streamlit_app.py:

Complete Frontend Code

Step 4: Run It

Terminal 1 - Start backend:
Terminal 2 - Start frontend:
Open http://localhost:8501 in your browser.

TypeScript Implementation

Step 1: Project Setup

Create .env.local:

Step 2: API Route

Create app/api/chat/route.ts. Let’s break it down:

Import Dependencies

  • streamText: Vercel AI SDK function that handles streaming responses and tool calling
  • createOpenAI: Factory function to create an OpenAI provider
  • supermemoryTools: Pre-built tools for memory search and storage

Initialize OpenAI Provider

Creates an OpenAI provider configured with your API key. The ! tells TypeScript “this definitely exists” (because we set it in .env.local). This provider object will be passed to streamText to specify which AI model to use.

Define System Prompt

This guides the AI’s behavior and tells it:
  • When to use tools: Search memories before responding, add memories when users share info
  • Personality: Be helpful and personalized
  • Boundaries: Respect privacy
The AI SDK uses this to decide when to call searchMemories and addMemory tools automatically.

Create POST Handler

Next.js App Router convention: export an async function named after the HTTP method. This handles POST requests to /api/chat. We extract:
  • messages: Chat history array [{role, content}]
  • email: User identifier

Validate Input

Why validate? Prevents crashes from malformed requests. We need:
  • At least one message to respond to
  • An email to isolate user memories
Without email, we can’t maintain personalization across sessions.

Create Container Tag

Convert email to a container tag for memory isolation. Simpler than Python: We skip UUID generation here for simplicity. In production, you might want to hash the email for privacy:

Call streamText with Tools

This is where the magic happens! Let’s break down each parameter: model: openai('gpt-5')
  • Specifies which AI model to use
  • The AI SDK handles the API calls
messages
  • Full conversation history
  • Format: [{role: "user"|"assistant", content: "..."}]
tools: supermemoryTools(...)
  • Gives the AI access to memory operations
  • The AI SDK automatically:
    • Decides when to call tools based on the conversation
    • Calls searchMemories when it needs context
    • Calls addMemory when users share information
    • Handles tool execution and error handling
containerTags: [containerTag]
  • Scopes all memory operations to this specific user
  • Ensures User A can’t access User B’s memories
system: SYSTEM_PROMPT
  • Guides the AI’s behavior and tool usage
How tools work:
  1. User: “Remember that I’m vegetarian”
  2. AI SDK detects this is memory-worthy
  3. Automatically calls addMemory("User is vegetarian")
  4. Stores in Supermemory with the user’s container tag
  5. Responds: “Got it, I’ll remember that!”
Later:
  1. User: “What should I eat?”
  2. AI SDK calls searchMemories("food preferences")
  3. Retrieves: “User is vegetarian”
  4. Responds: “How about a delicious veggie stir-fry?”
No manual tool handling needed! The AI SDK manages the entire flow.

Return Streaming Response

toAIStreamResponse() converts the streaming result into a format the frontend can consume. It:
  • Sets appropriate headers for streaming
  • Formats data for the useChat hook
  • Handles errors gracefully
This returns immediately (doesn’t wait for completion), and chunks stream to the client as they’re generated.

Error Handling

Catches any errors (API failures, tool errors, etc.) and returns a clean error response. Why log to console? In production, you’d send this to a monitoring service (Sentry, DataDog, etc.) to track issues.
Key Differences from Python:
AspectPythonTypeScript
Memory SearchManual search_user_memories() callAI SDK calls searchMemories tool automatically
Memory AddManual add_user_memory() callAI SDK calls addMemory tool automatically
Tool DecisionYou decide when to search/addAI decides based on conversation context
StreamingManual SSE formattingtoAIStreamResponse() handles it
Error HandlingTry/catch in each functionAI SDK handles tool errors
Python = Manual Control You explicitly search and add memories. More control, more code. TypeScript = AI-Driven The AI decides when to use tools. Less code, more “magic.”

Step 3: Chat UI

Replace app/page.tsx:

Complete Frontend Code

Step 4: Run It

Open http://localhost:3000

Testing Your Assistant

Try these conversations to test memory: Personal Preferences:
Dietary & Lifestyle:
Work Context:

Verify Memory Storage

Python

Create check_memories.py:

TypeScript

Create scripts/check-memories.ts:

Troubleshooting

Memory not persisting?
  • Verify container tags are consistent
  • Check API key has write permissions
  • Ensure email is properly normalized
Responses not personalized?
  • Increase search limit to find more memories
  • Check that memories are being added
  • Verify system prompt guides tool usage
Performance issues?
  • Reduce search limits
  • Implement caching for frequent queries
  • Use appropriate thresholds

Built with Supermemory. Customize based on your needs.