Last updated

Memory Retrieval Guide

This guide covers effective strategies for searching and retrieving memories using the Papr Memory API. Learn how to write effective queries, handle search results, and optimize retrieval performance.

Search Capabilities

The Papr Memory API provides powerful semantic search capabilities:

  1. Natural Language Queries: Write detailed, descriptive queries
  2. Metadata Filtering: Use metadata for better targeting
  3. Context-Aware Search: Include conversation and emotional context
  4. Result Ranking: Automatic relevance-based ranking

Writing Effective Queries

The search API works best with detailed, contextual queries:

const searchMemories = async (query: string) => {
  const response = await fetch('https://memory.papr.ai/v1/memory/search', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': '<your-api-key>',
      'X-Client-Type': 'your_app_name',
      'Accept-Encoding': 'gzip'
    },
    body: JSON.stringify({
      query,
      rank_results: false  // Results are already semantically ranked
    })
  });

  return response.json();
};

// Good query examples
const queries = [
  "Find detailed discussions about API performance issues from the last month, focusing on timeout errors and customer impact",
  "Locate product feedback about the new dashboard features, particularly comments about usability and suggested improvements",
  "Retrieve meeting notes from recent planning sessions that discuss Q2 objectives and resource allocation"
];

Search Patterns

async function searchByTopic(
  topic: string,
  subtopics: string[] = [],
  timeframe?: string
) {
  let query = `Find comprehensive information about ${topic}`;
  
  if (subtopics.length > 0) {
    query += ` specifically relating to ${subtopics.join(', ')}`;
  }
  
  if (timeframe) {
    query += ` from ${timeframe}`;
  }

  return searchMemories(query);
}

// Example usage
const results = await searchByTopic(
  'user authentication',
  ['password reset', 'two-factor authentication'],
  'the past quarter'
);
async function searchWithContext(
  topic: string,
  context: {
    project?: string,
    team?: string,
    status?: string,
    priority?: string
  }
) {
  let query = `Find information about ${topic}`;
  
  if (context.project) {
    query += ` within the ${context.project} project`;
  }
  
  if (context.team) {
    query += ` involving the ${context.team} team`;
  }
  
  if (context.status) {
    query += ` with ${context.status} status`;
  }
  
  if (context.priority) {
    query += ` marked as ${context.priority} priority`;
  }

  return searchMemories(query);
}

// Example usage
const results = await searchWithContext('implementation timeline', {
  project: 'Dashboard Redesign',
  team: 'Frontend',
  status: 'in-progress',
  priority: 'high'
});
async function searchByEmotion(
  topic: string,
  emotion: string,
  intensity?: 'high' | 'moderate' | 'low'
) {
  let query = `Find ${topic} discussions`;
  
  if (intensity) {
    query += ` with ${intensity} levels of ${emotion} sentiment`;
  } else {
    query += ` expressing ${emotion}`;
  }

  return searchMemories(query);
}

// Example usage
const results = await searchByEmotion(
  'product feedback',
  'frustration',
  'high'
);

Processing Search Results

Handle and organize search results effectively:

interface SearchResult {
  memories: Memory[];
  nodes: Node[];
}

class MemoryProcessor {
  // Group results by hierarchy
  static groupByHierarchy(results: SearchResult) {
    return results.memories.reduce((acc, memory) => {
      const hierarchy = memory.metadata?.hierarchical_structures || 'Uncategorized';
      acc[hierarchy] = acc[hierarchy] || [];
      acc[hierarchy].push(memory);
      return acc;
    }, {});
  }

  // Sort results by relevance and date
  static sortResults(memories: Memory[]) {
    return memories.sort((a, b) => {
      const dateA = new Date(a.metadata?.createdAt || 0);
      const dateB = new Date(b.metadata?.createdAt || 0);
      return dateB.getTime() - dateA.getTime();
    });
  }

  // Extract key insights
  static extractInsights(memories: Memory[]) {
    return memories.map(memory => ({
      id: memory.id,
      content: memory.content,
      topics: memory.metadata?.topics?.split(',').map(t => t.trim()) || [],
      emotions: memory.metadata?.["emotion tags"]?.split(',').map(e => e.trim()) || [],
      created: new Date(memory.metadata?.createdAt)
    }));
  }
}

// Example usage
const response = await searchMemories('feature feedback');
const grouped = MemoryProcessor.groupByHierarchy(response);
const sorted = MemoryProcessor.sortResults(response.memories);
const insights = MemoryProcessor.extractInsights(response.memories);

Search Optimization

1. Query Building

class QueryBuilder {
  private query: string[];
  private filters: string[];

  constructor(baseQuery: string) {
    this.query = [baseQuery];
    this.filters = [];
  }

  addTimeframe(timeframe: string) {
    this.filters.push(`from ${timeframe}`);
    return this;
  }

  addContext(context: string) {
    this.filters.push(`in the context of ${context}`);
    return this;
  }

  addFocus(focus: string) {
    this.filters.push(`focusing on ${focus}`);
    return this;
  }

  build() {
    return [...this.query, ...this.filters].join(' ');
  }
}

// Example usage
const builder = new QueryBuilder('Find discussions about API performance')
  .addTimeframe('the last month')
  .addContext('customer support tickets')
  .addFocus('response time issues');

const query = builder.build();
const results = await searchMemories(query);

2. Result Caching

class MemoryCache {
  private cache: Map<string, {
    results: SearchResult,
    timestamp: number
  }>;
  private ttl: number;  // Time to live in milliseconds

  constructor(ttlMinutes: number = 5) {
    this.cache = new Map();
    this.ttl = ttlMinutes * 60 * 1000;
  }

  async get(query: string): Promise<SearchResult | null> {
    const cached = this.cache.get(query);
    if (!cached) return null;

    if (Date.now() - cached.timestamp > this.ttl) {
      this.cache.delete(query);
      return null;
    }

    return cached.results;
  }

  set(query: string, results: SearchResult) {
    this.cache.set(query, {
      results,
      timestamp: Date.now()
    });
  }

  clear() {
    this.cache.clear();
  }
}

// Example usage
const cache = new MemoryCache(10);  // 10 minutes TTL

async function cachedSearch(query: string): Promise<SearchResult> {
  const cached = await cache.get(query);
  if (cached) return cached;

  const results = await searchMemories(query);
  cache.set(query, results);
  return results;
}

Best Practices

  1. Query Writing

    • Be specific and detailed in your queries
    • Include relevant context and timeframes
    • Use natural language descriptions
  2. Result Handling

    • Process results based on your needs
    • Implement appropriate sorting/filtering
    • Cache frequently accessed results
  3. Performance

    • Use the Accept-Encoding: gzip header
    • Implement client-side caching
    • Batch related searches when possible
  4. Error Handling

    • Implement retry logic for failed searches
    • Handle rate limits appropriately
    • Provide fallback options when needed

Next Steps