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
  5. Agentic Graph Search: Intelligent entity relationship navigation

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,
      enable_agentic_graph: true,  // HIGHLY RECOMMENDED for intelligent search
      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"
];

For best search results, follow these recommendations:

  1. Enable Agentic Graph Search: Set enable_agentic_graph: true for more intelligent, context-aware search that can understand ambiguous references

  2. Set Appropriate Memory Limits: Use max_memories: 15-20 (via query parameter) for comprehensive coverage. Lower values may miss relevant information.

  3. Set Appropriate Node Limits: Use max_nodes: 10-15 (via query parameter) for comprehensive graph entity relationships.

  4. Use Accept-Encoding Header: Add 'Accept-Encoding': 'gzip' header to enable response compression for improved performance.

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[];  // Graph nodes for relationships
}

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.data);
const sorted = MemoryProcessor.sortResults(response.data.memories);
const insights = MemoryProcessor.extractInsights(response.data.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,
    search_id: string  // Store search_id for feedback submission
  }>;
  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) {
    const key = this.generateKey(query);
    const cached = this.cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.results;
    }
    
    const results = await searchMemories(query);
    this.cache.set(key, {
      results,
      timestamp: Date.now(),
      search_id: results.search_id
    });
    
    return results;
  }

  getSearchId(query: string) {
    const key = this.generateKey(query);
    return this.cache.get(key)?.search_id;
  }

  private generateKey(query: string) {
    return query.toLowerCase().trim();
  }
}

3. Search with Feedback

Improve future search results by providing feedback:

async function searchWithFeedback(query: string) {
  const results = await searchMemories(query);
  const searchId = results.search_id;
  
  // Store search_id for later feedback submission
  localStorage.setItem('last_search_id', searchId);
  
  return results;
}

async function submitFeedback(searchId: string, isHelpful: boolean, comments?: string) {
  const response = await fetch('https://memory.papr.ai/v1/feedback', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': '<your-api-key>',
      'X-Client-Type': 'your_app_name'
    },
    body: JSON.stringify({
      search_id: searchId,
      feedbackData: {
        feedbackType: isHelpful ? 'thumbs_up' : 'thumbs_down',
        feedbackSource: 'inline',
        feedbackText: comments
      }
    })
  });
  
  return response.json();
}

Working with Graph Nodes

When enable_agentic_graph is enabled, search results include both memories and graph nodes:

interface Node {
  label: 'Memory' | 'Person' | 'Company' | 'Project' | 'Task' | 'Insight' | 'Meeting' | 'Opportunity' | 'Code';
  properties: {
    id: string;
    [key: string]: any;  // Additional properties based on node type
  };
}

function processNodes(nodes: Node[]) {
  const personNodes = nodes.filter(node => node.label === 'Person');
  const projectNodes = nodes.filter(node => node.label === 'Project');
  const taskNodes = nodes.filter(node => node.label === 'Task');
  
  // Process each node type
  const people = personNodes.map(node => ({
    id: node.properties.id,
    name: node.properties.name,
    role: node.properties.role
  }));
  
  const projects = projectNodes.map(node => ({
    id: node.properties.id,
    name: node.properties.name,
    type: node.properties.type
  }));
  
  return {
    people,
    projects,
    tasks: taskNodes
  };
}

Next Steps