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.

Three Retrieval Methods

Papr Memory offers three complementary retrieval approaches:

1. Semantic Retrieval (Default or reranking_provider: "none")

High-dimensional vector search with cosine similarity. Fast and efficient for similarity-based recall.

Best for:

  • RAG applications needing quick semantic lookup
  • Simple similarity matching
  • Low-latency requirements

Use when: You need the fastest vector search with good semantic understanding.

2. External Reranking (reranking_provider: "cohere" or "openai")

Uses external reranking models (Cohere rerank-v3.5 or OpenAI gpt-5-nano/mini) to rerank cosine candidates.

Best for:

  • General-purpose reranking across any content type
  • When you want best-in-class commercial rerankers
  • Cross-domain applications

Providers:

  • cohere (default) - Cohere rerank-v3.5, fast and accurate
  • openai - OpenAI gpt-5-nano or gpt-5-mini for reranking

3. Graph-Native Reranking (reranking_provider: "papr_enhanced" or "papr_max")

Signal-based transforms that rerank results using learned domain patterns (CAESAR-8). Dramatically improves recall on domain-specific queries.

Best for:

  • Domain-specific search (code, scientific papers, legal docs)
  • When you need topical alignment beyond flat similarity
  • Applications where recall quality matters more than speed

Providers:

  • papr_enhanced - Balanced quality/speed tradeoff (recommended)
  • papr_max - Maximum quality (graph rerank + CE + EGR)

Use when: You have domain-specific content and need results aligned on specific dimensions (language, methodology, topic, etc.).

4. Knowledge Graph Retrieval (enable_agentic_graph: true)

Traditional graph traversal through Neo4j relationships. Explores entity connections and follows relationships.

Best for:

  • Multi-hop reasoning ("Pen is coming, what should I cook?")
  • Entity-centric queries
  • When relationships matter as much as content

Use when: Your query requires understanding relationships between entities, not just semantic similarity.

Combining Methods

You can combine reranking with knowledge graph in a single search request:

results = client.memory.search(
    query="Find Python sorting code for lists",
    enable_agentic_graph=True,  # Knowledge Graph Retrieval
    reranking_config={
        "reranking_provider": "papr_enhanced",  # Graph-Native Reranking
        "domain_id": "cosqa",
        "signal_thresholds": {
            "language": 0.9  # 90%+ must be Python
        }
    }
)

Search Capabilities

The Papr Memory API provides powerful search capabilities:

  1. Natural Language Queries: Write detailed, descriptive queries
  2. Metadata Filtering: Use metadata for better targeting, including custom metadata fields
  3. Context-Aware Search: Include conversation and emotional context
  4. Intelligent Ranking: Automatic relevance-based ranking with optional graph-native scoring
  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,  // Knowledge Graph Retrieval
      reranking_config: {  // Optional: Reranking configuration
        reranking_provider: "papr_enhanced",  // or "cohere", "openai", "papr_max", "none"
        domain_id: "general",  // For papr providers
        signal_thresholds: {  // For papr providers
          topic_category: 0.8
        }
      },
      metadata: {  // Optional: Filter by metadata fields
        // Standard metadata fields
        topics: ["API", "performance"],
        location: "US",
        // Custom metadata fields
        customMetadata: {
          priority: "high",
          department: "engineering"
        }
      }
    })
  });

  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. Choose the Right Reranking Provider:

    • none - Cosine-only (fastest, no reranking)
    • cohere - Cohere rerank-v3.5 (default, great for general use)
    • openai - OpenAI gpt-5-nano/mini (alternative commercial reranker)
    • papr_enhanced - Graph-native reranking (best for domain-specific content)
    • papr_max - Maximum quality (graph + CE + EGR, when recall is critical)
  2. Enable Knowledge Graph Search: Set enable_agentic_graph: true for multi-hop reasoning and entity relationship understanding

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

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

  5. 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'
});

Use metadata filters to precisely target specific memories:

async function searchWithMetadataFilter(
  query: string,
  filters: {
    topics?: string[],
    location?: string,
    customFields?: Record<string, any>
  }
) {
  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,
      metadata: {
        topics: filters.topics,
        location: filters.location,
        customMetadata: filters.customFields
      }
    })
  });

  return response.json();
}

// Example usage
const results = await searchWithMetadataFilter(
  'Find customer feedback about API performance',
  {
    topics: ['API', 'performance', 'customer-feedback'],
    location: 'US',
    customFields: {
      priority: 'high',
      department: 'engineering',
      severity: 'critical',
      customer_tier: 'enterprise'
    }
  }
);
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