Last updated

Context and Relationships

Context and relationships are fundamental to how Papr Memory organizes and retrieves information. This page explains how Papr Memory handles context and manages relationships between memories.

Papr automatically handles most context and relationship features. You only need to provide relevant metadata for your use case.

Understanding Context

Context in Papr Memory is primarily handled through metadata. The API supports various context elements that help organize and retrieve your memories effectively.

Key Metadata Fields

Papr Memory uses these key metadata fields to establish context, but you can also add your own custom metadata fields based on your specific needs:

  1. Content Metadata

    {
      "topics": "product, planning, roadmap",
      "hierarchical_structures": "Business/Planning/Product"
    }
  2. Source Metadata

    {
      "sourceUrl": "https://meeting-notes.example.com/123",
      "conversationId": "conv-123"
    }
  3. Organization Metadata

    {
      "workspaceId": "ws_456",
      "userId": "user_789"
    }
  4. Temporal Metadata

    {
      "createdAt": "2024-03-20T10:00:00Z"
    }
  5. Custom Metadata

    {
      "priority": "high",
      "reviewStatus": "approved",
      "department": "engineering",
      "projectPhase": "planning"
    }

Metadata Considerations

  • Custom Fields: You can define any custom metadata fields that make sense for your application
  • Search Behavior: All metadata fields contribute to semantic search quality
  • Filtering Limitations: Currently, you can only filter search results by userId - filtering by other metadata fields is not yet supported

Defining Relationships

Papr Memory automatically identifies relationships between memories. You can also define explicit relationships between memory items if you want to using the relationships_json field. This is helpful if you want to connect specific memories to each other or for an AI agent to relate different chats to each other in a chat conversation for example:

# Add a memory with relationships to other memories
memory_response = client.memory.add(
    content="Follow-up meeting notes",
    type="text",
    metadata={
        "topics": "project, follow-up, action-items",
        "conversationId": "conv-123"
    },
    relationships_json=[
        {
            "related_item_id": "mem_previous_meeting",  # ID of related memory
            "relation_type": "follows",                # Type of relationship
            "related_item_type": "TextMemoryItem",     # Type of related memory
            "metadata": {
                "relevance": "high",
                "notes": "Direct follow-up to previous discussion"
            }
        },
        {
            "related_item_id": "mem_project_plan",
            "relation_type": "references",
            "related_item_type": "TextMemoryItem",
            "metadata": {
                "relevance": "medium",
                "notes": "References the main project plan"
            }
        }
    ]
)

Common Relationship Types

While you can define custom relationship types, here are some common patterns:

  • follows: Memory item follows chronologically after another
  • references: Memory item references or mentions another
  • elaborates: Memory item provides more detail on another
  • contradicts: Memory item provides contrary information to another
  • summarizes: Memory item summarizes information from another

Conversation Context

The context parameter allows you to provide conversation history or relevant context for a memory item:

# Add memory with conversation context
memory_response = client.memory.add(
    content="Decision to increase API rate limits",
    type="text",
    metadata={
        "topics": "api, infrastructure, decisions"
    },
    context=[
        {"role": "user", "content": "Our customers are hitting API rate limits frequently"},
        {"role": "assistant", "content": "What specific endpoints are causing issues?"},
        {"role": "user", "content": "The /data and /analytics endpoints during peak hours"},
        {"role": "assistant", "content": "Based on our usage patterns, we could safely increase limits"}
    ]
)

Hierarchical Structures

The hierarchical_structures metadata field enables organizing memories in a tree-like structure. While not currently filterable, this field improves semantic search by adding important context:

# Add memory with hierarchical organization
memory_response = client.memory.add(
    content="Team performance review Q1 2024",
    type="text",
    metadata={
        "topics": "review, performance, metrics",
        "hierarchical_structures": "Company/HR/Reviews/Q1_2024"
    }
)

# Another memory in a different part of the hierarchy
memory_response = client.memory.add(
    content="Product roadmap for mobile apps",
    type="text",
    metadata={
        "topics": "roadmap, mobile, planning",
        "hierarchical_structures": "Company/Product/Mobile/Roadmaps"
    }
)

When searching, including hierarchical path information in your query can help find relevant memories, even though direct filtering by hierarchy is not yet supported:

# Query that includes hierarchical context
results = client.memory.search(
    query="Find the latest mobile team performance metrics in the HR reviews from Q1 2024"
)

When searching with Papr Memory, your detailed query provides context for finding the most relevant information:

# Search with a detailed, context-rich query
results = client.memory.search(
    query="Find our product roadmap discussion from last month's planning meeting. I need to review the Q3 milestones and resource allocation we agreed on."
)

# Filter search by userId (currently the only metadata field that can be used as a filter)
results = client.memory.search(
    query="Find the latest performance metrics for the mobile team",
    metadata={
        "userId": "user_123"  # Only userId filtering is currently supported
    }
)

Note: While you can include other metadata fields in the search request, currently only userId is used for filtering. Other fields like hierarchical_structures contribute to the semantic search but cannot yet be used as explicit filters.

User-Specific Context

You can manage user-specific memories by including user information in metadata. The userId field is special because it's currently the only metadata field that can be explicitly filtered on:

# Add a memory with user context
memory_response = client.memory.add(
    content="Personal notes from the design review",
    type="text",
    metadata={
        "topics": "design, feedback, review",
        "userId": "user_123",
        "hierarchical_structures": "Projects/Website/Design"
    }
)

# Search memories for a specific user (filtering by userId works)
results = client.memory.search(
    query="Find my design review notes from last week",
    metadata={
        "userId": "user_123"  # This will properly filter results to this user
    }
)

This capability is particularly useful for applications serving multiple users, as it allows you to keep memories properly segregated by user while still benefiting from the shared semantic search infrastructure.

Best Practices

  1. Metadata Consistency

    • Use consistent metadata schemas across your application
    • Establish conventions for topics, hierarchies, and relationship types
    • Document your metadata schema for team reference
  2. Custom Metadata Fields

    • Define custom metadata fields that have meaning in your domain
    • Use consistent naming conventions (camelCase or snake_case)
    • Include metadata that will be useful for semantic search
    • Remember that while all metadata improves search quality, only userId can currently be used for filtering
  3. Hierarchical Structures

    • Create clear hierarchies in hierarchical_structures
    • Use consistent path formats (e.g., "Projects/Marketing/Campaigns")
    • Limit hierarchy depth to 3-5 levels for best performance
    • Include hierarchical information in search queries since direct filtering isn't yet supported
  4. Relationship Design

    • Use specific relationship types that convey semantic meaning
    • Add relevant metadata to relationships to enhance context
    • Create bidirectional relationships when appropriate
    • Avoid creating too many relationships per memory item
  5. Conversation Context

    • Include relevant parts of conversations for context
    • Limit context to 5-10 turns for optimal performance
    • Include both user and assistant messages for complete context
  6. Search Queries

    • Write detailed 2-3 sentence queries with specific details
    • Include relevant time frames, people, or topics
    • Mention metadata information in your natural language query
    • Use userId filtering when you need to limit results to a specific user

Examples of Context-Rich Queries

"Find all discussions about the authentication service outage from last week's incident response meetings. I need to review the root cause analysis and the mitigation steps we implemented."

"Locate our customer feedback analysis from the Q1 review sessions. I'm specifically looking for insights about the mobile app user experience and any prioritized improvements we agreed on."

"Find documentation about our API rate limiting implementation from the technical design discussions. I need details about how we handle authentication failures and retry mechanisms."

Next Steps