Skip to main content

Overview

This example shows how to use Satori to store and retrieve user preferences, enabling personalized experiences across your application.

Use Case

Build an AI assistant that remembers user preferences for:
  • UI settings (theme, language, layout)
  • Content preferences (topics, formats, difficulty)
  • Communication style (formal vs casual, verbosity)
  • Notification preferences

Implementation

Saving Preferences

app/api/preferences/route.ts
import { MemoryClient } from '@satori/tools';
import { auth } from '@clerk/nextjs/server';

export async function POST(req: Request) {
  const { userId } = await auth();
  
  if (!userId) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  const { preference, category } = await req.json();
  
  const client = new MemoryClient({
    apiKey: process.env.SATORI_API_KEY!,
    baseUrl: process.env.SATORI_URL!,
    userId,
  });
  
  // Save preference with metadata
  await client.addMemory(preference, {
    metadata: {
      type: 'preference',
      category,
      timestamp: new Date().toISOString(),
    },
  });
  
  return Response.json({ success: true });
}

Retrieving Preferences

app/api/preferences/get/route.ts
import { MemoryClient } from '@satori/tools';
import { auth } from '@clerk/nextjs/server';

export async function GET(req: Request) {
  const { userId } = await auth();
  
  if (!userId) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  const { searchParams } = new URL(req.url);
  const category = searchParams.get('category');
  
  const client = new MemoryClient({
    apiKey: process.env.SATORI_API_KEY!,
    baseUrl: process.env.SATORI_URL!,
    userId,
  });
  
  // Search for preferences in a specific category
  const query = category 
    ? `${category} preferences`
    : 'user preferences';
    
  const preferences = await client.searchMemories(query, {
    limit: 20,
    threshold: 0.7,
  });
  
  // Filter by metadata if needed
  const filtered = category
    ? preferences.filter(p => p.metadata?.category === category)
    : preferences;
  
  return Response.json({ preferences: filtered });
}

Preferences UI Component

components/PreferencesPanel.tsx
'use client';

import { useState, useEffect } from 'react';

interface Preference {
  id: string;
  content: string;
  category: string;
  createdAt: string;
}

export function PreferencesPanel() {
  const [preferences, setPreferences] = useState<Preference[]>([]);
  const [newPref, setNewPref] = useState('');
  const [category, setCategory] = useState('general');

  useEffect(() => {
    loadPreferences();
  }, []);

  async function loadPreferences() {
    const response = await fetch('/api/preferences/get');
    const data = await response.json();
    setPreferences(data.preferences);
  }

  async function savePreference() {
    await fetch('/api/preferences', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        preference: newPref,
        category,
      }),
    });
    
    setNewPref('');
    loadPreferences();
  }

  return (
    <div className="p-6 max-w-2xl mx-auto">
      <h2 className="text-2xl font-bold mb-6">Your Preferences</h2>
      
      {/* Add Preference */}
      <div className="mb-6 p-4 bg-gray-50 rounded-lg">
        <h3 className="font-semibold mb-3">Add New Preference</h3>
        <div className="space-y-3">
          <select
            value={category}
            onChange={(e) => setCategory(e.target.value)}
            className="w-full p-2 border rounded"
          >
            <option value="general">General</option>
            <option value="ui">UI/Theme</option>
            <option value="content">Content</option>
            <option value="communication">Communication</option>
            <option value="notifications">Notifications</option>
          </select>
          
          <input
            value={newPref}
            onChange={(e) => setNewPref(e.target.value)}
            placeholder="e.g., I prefer dark mode"
            className="w-full p-2 border rounded"
          />
          
          <button
            onClick={savePreference}
            disabled={!newPref.trim()}
            className="w-full px-4 py-2 bg-purple-600 text-white rounded hover:bg-purple-700 disabled:opacity-50"
          >
            Save Preference
          </button>
        </div>
      </div>
      
      {/* Preferences List */}
      <div className="space-y-3">
        <h3 className="font-semibold">Saved Preferences</h3>
        {preferences.length === 0 ? (
          <p className="text-gray-500">No preferences saved yet</p>
        ) : (
          preferences.map((pref) => (
            <div
              key={pref.id}
              className="p-3 bg-white border rounded-lg"
            >
              <p className="text-sm font-medium text-purple-600 mb-1">
                {pref.category}
              </p>
              <p>{pref.content}</p>
              <p className="text-xs text-gray-500 mt-1">
                {new Date(pref.createdAt).toLocaleDateString()}
              </p>
            </div>
          ))
        )}
      </div>
    </div>
  );
}

Preference Categories

UI Preferences

// Theme preference
await client.addMemory('User prefers dark mode', {
  metadata: { category: 'ui', subcategory: 'theme' },
});

// Layout preference
await client.addMemory('User prefers compact layout with sidebar', {
  metadata: { category: 'ui', subcategory: 'layout' },
});

// Language preference
await client.addMemory('User prefers Spanish language', {
  metadata: { category: 'ui', subcategory: 'language' },
});

Content Preferences

// Topic interests
await client.addMemory('User is interested in AI and machine learning', {
  metadata: { category: 'content', subcategory: 'topics' },
});

// Content format
await client.addMemory('User prefers video tutorials over text articles', {
  metadata: { category: 'content', subcategory: 'format' },
});

// Difficulty level
await client.addMemory('User prefers advanced technical content', {
  metadata: { category: 'content', subcategory: 'difficulty' },
});

Communication Preferences

// Tone preference
await client.addMemory('User prefers casual, friendly communication', {
  metadata: { category: 'communication', subcategory: 'tone' },
});

// Verbosity
await client.addMemory('User prefers concise responses', {
  metadata: { category: 'communication', subcategory: 'verbosity' },
});

// Explanation style
await client.addMemory('User likes code examples with explanations', {
  metadata: { category: 'communication', subcategory: 'style' },
});

Using Preferences in Chat

app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { MemoryClient } from '@satori/tools';

export async function POST(req: Request) {
  const { userId } = await auth();
  const { messages } = await req.json();
  
  const client = new MemoryClient({
    apiKey: process.env.SATORI_API_KEY!,
    baseUrl: process.env.SATORI_URL!,
    userId: userId!,
  });
  
  // Fetch user preferences
  const preferences = await client.searchMemories('user preferences', {
    limit: 10,
  });
  
  // Build preferences context
  const preferencesContext = preferences
    .map(p => p.content)
    .join('\n');
  
  const result = await streamText({
    model: openai('gpt-4o'),
    system: `You are a helpful assistant. Adapt your responses based on these user preferences:

${preferencesContext}

Follow these preferences in your communication style, content recommendations, and overall interaction.`,
    messages,
  });
  
  return result.toDataStreamResponse();
}

Example Interactions

User: “I prefer dark mode and a compact layout”Assistant: “Got it! I’ll remember that you prefer dark mode and a compact layout. Your interface should reflect these preferences.”Saves: “User prefers dark mode” and “User prefers compact layout”
User: “Show me some tutorials”Assistant: “Based on your preferences for advanced technical content in video format, here are some great video tutorials on AI and machine learning…”Uses preferences to personalize recommendations
User: “Explain how embeddings work”Assistant: “Sure! Here’s a concise explanation with code:
# Embeddings convert text to vectors
embedding = model.encode('Hello world')
# Result: [0.23, -0.45, 0.67, ...]
Embeddings capture semantic meaning in numbers that computers can compare.”Adapts to preference for concise responses with code examples

Advanced Patterns

Preference Conflicts

// Handle conflicting preferences
const preferences = await client.searchMemories('theme preference');

// If multiple theme preferences exist, use the most recent
const sortedByDate = preferences.sort((a, b) => 
  new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);

const currentTheme = sortedByDate[0];

Preference Hierarchy

// Define preference importance
await client.addMemory('User strongly prefers TypeScript', {
  metadata: {
    category: 'content',
    importance: 'high',
    strength: 'strong',
  },
});

await client.addMemory('User somewhat likes Python', {
  metadata: {
    category: 'content',
    importance: 'medium',
    strength: 'moderate',
  },
});

Preference Expiration

// Add expiration to temporary preferences
await client.addMemory('User is currently learning React', {
  metadata: {
    category: 'content',
    temporary: true,
    expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString(),
  },
});

// Clean up expired preferences
const allPrefs = await client.getAllMemories();
const now = Date.now();

for (const pref of allPrefs) {
  if (pref.metadata?.expiresAt) {
    const expiresAt = new Date(pref.metadata.expiresAt).getTime();
    if (now > expiresAt) {
      await client.deleteMemory(pref.id);
    }
  }
}

Best Practices

// ✅ Good: Specific and actionable
"User prefers dark mode with high contrast"
"User wants email notifications only for critical alerts"

// ❌ Bad: Vague
"User likes dark stuff"
"User doesn't want spam"
await client.addMemory('User prefers TypeScript', {
  metadata: {
    category: 'programming',
    subcategory: 'languages',
    importance: 'high',
    source: 'explicit', // vs 'inferred'
    confidence: 0.95,
  },
});
// Search for existing preference
const existing = await client.searchMemories('theme preference', {
  threshold: 0.9,
});

// Delete old preference
if (existing.length > 0) {
  await client.deleteMemory(existing[0].id);
}

// Add new preference
await client.addMemory('User now prefers light mode');

Next Steps