Skip to main content

Rust Examples

This document provides examples of how to use EngramDB in Rust applications.

Basic Usage

This example demonstrates the basic operations of creating a database, adding memories, and searching for similar memories.
use engramdb::{Database, MemoryNode};
use engramdb::core::AttributeValue;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create an in-memory database
    let mut db = Database::in_memory();
    println!("Created in-memory database");

    // Create a memory node
    let mut memory1 = MemoryNode::new(vec![0.1, 0.2, 0.3, 0.4]);
    memory1.set_attribute(
        "title".to_string(),
        AttributeValue::String("Meeting notes".to_string())
    );
    memory1.set_attribute(
        "category".to_string(),
        AttributeValue::String("work".to_string())
    );
    memory1.set_attribute(
        "importance".to_string(),
        AttributeValue::Float(0.8)
    );

    // Save to database
    let memory1_id = db.save(&memory1)?;
    println!("Saved memory with ID: {}", memory1_id);

    // Create and save another memory
    let mut memory2 = MemoryNode::new(vec![0.2, 0.3, 0.4, 0.5]);
    memory2.set_attribute(
        "title".to_string(),
        AttributeValue::String("Project idea".to_string())
    );
    memory2.set_attribute(
        "category".to_string(),
        AttributeValue::String("work".to_string())
    );
    memory2.set_attribute(
        "importance".to_string(),
        AttributeValue::Float(0.9)
    );

    let memory2_id = db.save(&memory2)?;
    println!("Saved memory with ID: {}", memory2_id);

    // List all memories
    let all_ids = db.list_all()?;
    println!("Database contains {} memories", all_ids.len());

    // Load a memory
    let loaded_memory = db.load(memory1_id)?;
    println!("Loaded memory: {:?}", loaded_memory);

    // Search for similar memories
    let query_vector = vec![0.15, 0.25, 0.35, 0.45];
    let results = db.search_similar(&query_vector, 5, 0.0)?;

    println!("Search results:");
    for (id, similarity) in results {
        let memory = db.load(id)?;
        if let Some(AttributeValue::String(title)) = memory.get_attribute("title") {
            println!("  {} (similarity: {:.4})", title, similarity);
        }
    }

    // Delete a memory
    db.delete(memory1_id)?;
    println!("Deleted memory with ID: {}", memory1_id);

    // Verify it's gone
    let remaining_ids = db.list_all()?;
    println!("Database now contains {} memories", remaining_ids.len());

    Ok(())
}

Working with Connections

This example demonstrates how to create and manage connections between memory nodes.
use engramdb::{Database, MemoryNode, RelationshipType};
use engramdb::core::AttributeValue;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create an in-memory database
    let mut db = Database::in_memory();

    // Create memory nodes
    let mut memory1 = MemoryNode::new(vec![0.1, 0.2, 0.3, 0.4]);
    memory1.set_attribute(
        "title".to_string(),
        AttributeValue::String("Meeting with team".to_string())
    );

    let mut memory2 = MemoryNode::new(vec![0.2, 0.3, 0.4, 0.5]);
    memory2.set_attribute(
        "title".to_string(),
        AttributeValue::String("Action items from meeting".to_string())
    );

    let mut memory3 = MemoryNode::new(vec![0.3, 0.4, 0.5, 0.6]);
    memory3.set_attribute(
        "title".to_string(),
        AttributeValue::String("Project timeline".to_string())
    );

    // Save to database
    let memory1_id = db.save(&memory1)?;
    let memory2_id = db.save(&memory2)?;
    let memory3_id = db.save(&memory3)?;

    // Create connections
    db.add_connection(
        memory1_id,
        memory2_id,
        RelationshipType::Causation,
        0.9
    )?;

    db.add_connection(
        memory2_id,
        memory3_id,
        RelationshipType::Association,
        0.7
    )?;

    // Get connections for a memory
    let connections = db.get_connections(memory1_id)?;

    println!("Connections for memory 'Meeting with team':");
    for connection in connections {
        let target = db.load(connection.target_id)?;
        if let Some(AttributeValue::String(title)) = target.get_attribute("title") {
            println!("  Connected to '{}' with relationship '{}' (strength: {:.2})",
                title, connection.type_name, connection.strength);
        }
    }

    // Remove a connection
    let removed = db.remove_connection(memory1_id, memory2_id)?;
    println!("Connection removed: {}", removed);

    // Verify it's gone
    let updated_connections = db.get_connections(memory1_id)?;
    println!("Memory now has {} connections", updated_connections.len());

    Ok(())
}

Complex Queries

This example demonstrates how to use the query builder to perform complex queries.
use engramdb::{Database, MemoryNode};
use engramdb::core::AttributeValue;
use engramdb::query::{AttributeFilter, TemporalFilter};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create an in-memory database
    let mut db = Database::in_memory();

    // Add some test memories
    add_test_memories(&mut db)?;

    // Create filters
    let category_filter = AttributeFilter::equals(
        "category",
        AttributeValue::String("work".to_string())
    );

    let importance_filter = AttributeFilter::greater_than(
        "importance",
        AttributeValue::Float(0.7)
    );

    // Create temporal filter for recent memories (last 24 hours)
    let time_filter = TemporalFilter::within_last(24 * 60 * 60);

    // Execute the combined query
    let query_vector = vec![0.1, 0.3, 0.5, 0.1]; // Similar to "project plan"

    let results = db.query()
        .with_vector(query_vector)
        .with_attribute_filter(category_filter)
        .with_attribute_filter(importance_filter)
        .with_temporal_filter(time_filter)
        .with_limit(10)
        .execute()?;

    println!("Found {} important work memories:", results.len());
    for node in &results {
        println!("  {}",
            node.get_attribute("title")
                .and_then(|v| if let AttributeValue::String(s) = v { Some(s) } else { None })
                .unwrap_or(&"Untitled".to_string())
        );

        println!("    Importance: {}",
            node.get_attribute("importance")
                .and_then(|v| if let AttributeValue::Float(f) = v { Some(f) } else { None })
                .unwrap_or(&0.0)
        );
    }

    Ok(())
}

// Helper function to add test memories
fn add_test_memories(db: &mut Database) -> Result<(), Box<dyn Error>> {
    // Memory 1: Meeting notes
    let mut memory1 = MemoryNode::new(vec![0.1, 0.3, 0.5, 0.1]);
    memory1.set_attribute(
        "title".to_string(),
        AttributeValue::String("Meeting Notes".to_string())
    );
    memory1.set_attribute(
        "category".to_string(),
        AttributeValue::String("work".to_string())
    );
    memory1.set_attribute(
        "importance".to_string(),
        AttributeValue::Float(0.8)
    );
    db.save(&memory1)?;

    // Memory 2: Shopping list
    let mut memory2 = MemoryNode::new(vec![0.8, 0.1, 0.0, 0.1]);
    memory2.set_attribute(
        "title".to_string(),
        AttributeValue::String("Shopping List".to_string())
    );
    memory2.set_attribute(
        "category".to_string(),
        AttributeValue::String("personal".to_string())
    );
    memory2.set_attribute(
        "importance".to_string(),
        AttributeValue::Float(0.4)
    );
    db.save(&memory2)?;

    // Memory 3: Project idea
    let mut memory3 = MemoryNode::new(vec![0.2, 0.4, 0.4, 0.0]);
    memory3.set_attribute(
        "title".to_string(),
        AttributeValue::String("Project Idea".to_string())
    );
    memory3.set_attribute(
        "category".to_string(),
        AttributeValue::String("work".to_string())
    );
    memory3.set_attribute(
        "importance".to_string(),
        AttributeValue::Float(0.9)
    );
    db.save(&memory3)?;

    // Memory 4: Project plan
    let mut memory4 = MemoryNode::new(vec![0.15, 0.35, 0.45, 0.05]);
    memory4.set_attribute(
        "title".to_string(),
        AttributeValue::String("Project Plan".to_string())
    );
    memory4.set_attribute(
        "category".to_string(),
        AttributeValue::String("work".to_string())
    );
    memory4.set_attribute(
        "importance".to_string(),
        AttributeValue::Float(0.95)
    );
    db.save(&memory4)?;

    Ok(())
}

Persistent Storage

This example demonstrates how to use file-based storage for persistence.
use engramdb::{Database, DatabaseConfig, MemoryNode};
use engramdb::core::AttributeValue;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create a file-based database
    let config = DatabaseConfig {
        use_memory_storage: false,
        storage_dir: Some("./my_database".to_string()),
        cache_size: 100,
    };

    let mut db = Database::new(config)?;
    println!("Created file-based database");

    // Initialize to load existing memories
    db.initialize()?;

    // Check if we have existing memories
    let existing_ids = db.list_all()?;
    println!("Found {} existing memories", existing_ids.len());

    if existing_ids.is_empty() {
        // Add some test memories
        let mut memory = MemoryNode::new(vec![0.1, 0.2, 0.3, 0.4]);
        memory.set_attribute(
            "title".to_string(),
            AttributeValue::String("Persistent memory".to_string())
        );

        let memory_id = db.save(&memory)?;
        println!("Created new memory with ID: {}", memory_id);
    } else {
        // Load and display existing memories
        for id in existing_ids {
            let memory = db.load(id)?;
            if let Some(AttributeValue::String(title)) = memory.get_attribute("title") {
                println!("Loaded memory: {} (ID: {})", title, id);
            }
        }
    }

    println!("Database operations completed successfully");

    Ok(())
}

Working with Temporal Layers

This example demonstrates how to use temporal layers to track memory evolution.
use engramdb::{Database, MemoryNode};
use engramdb::core::{AttributeValue, TemporalLayer};
use std::collections::HashMap;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create an in-memory database
    let mut db = Database::in_memory();

    // Create initial memory
    let mut memory = MemoryNode::new(vec![0.1, 0.2, 0.3, 0.4]);
    memory.set_attribute(
        "title".to_string(),
        AttributeValue::String("Initial knowledge".to_string())
    );
    memory.set_attribute(
        "confidence".to_string(),
        AttributeValue::Float(0.6)
    );

    // Save to database
    let memory_id = db.save(&memory)?;
    println!("Created initial memory with ID: {}", memory_id);

    // Later, we want to update the memory
    // First, load it
    let mut memory = db.load(memory_id)?;

    // Save the current state as a temporal layer
    let old_embeddings = memory.embeddings().to_vec();

    let mut old_attributes = HashMap::new();
    for (key, value) in memory.attributes() {
        old_attributes.insert(key.clone(), value.clone());
    }

    let layer = TemporalLayer::new(
        Some(old_embeddings),
        Some(old_attributes),
        "Updated with new information".to_string()
    );

    memory.add_temporal_layer(layer);

    // Now update the memory
    memory.set_embeddings(vec![0.15, 0.25, 0.35, 0.45]);
    memory.set_attribute(
        "title".to_string(),
        AttributeValue::String("Updated knowledge".to_string())
    );
    memory.set_attribute(
        "confidence".to_string(),
        AttributeValue::Float(0.8)
    );

    // Save the updated memory
    db.save(&memory)?;
    println!("Updated memory with a new temporal layer");

    // Load and examine the memory
    let memory = db.load(memory_id)?;

    println!("Current state:");
    if let Some(AttributeValue::String(title)) = memory.get_attribute("title") {
        println!("  Title: {}", title);
    }
    if let Some(AttributeValue::Float(confidence)) = memory.get_attribute("confidence") {
        println!("  Confidence: {:.2}", confidence);
    }

    println!("Temporal layers: {}", memory.temporal_layers().len());
    for (i, layer) in memory.temporal_layers().iter().enumerate() {
        println!("Layer {}:", i + 1);
        println!("  Timestamp: {}", layer.timestamp());
        println!("  Reason: {}", layer.reason());

        if let Some(attributes) = layer.attributes() {
            if let Some(AttributeValue::String(title)) = attributes.get("title") {
                println!("  Title: {}", title);
            }
            if let Some(AttributeValue::Float(confidence)) = attributes.get("confidence") {
                println!("  Confidence: {:.2}", confidence);
            }
        }
    }

    Ok(())
}
This example demonstrates how to use EngramDB’s built-in embedding functionality to create memory nodes from text and perform semantic search.
use engramdb::{Database, MemoryNode};
use engramdb::core::AttributeValue;
use engramdb::embeddings::{EmbeddingService, EmbeddingModel};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create a database for our examples
    let mut db = Database::in_memory();
    println!("Created in-memory database");
    
    // You can choose from multiple embedding models:
    
    // 1. Default model (E5 Multilingual Large Instruct)
    let default_service = EmbeddingService::default();
    println!("Default embedding service initialized (dimensions: {})", default_service.dimensions());
    
    // 2. Specific model types
    // E5 Multilingual (standard)
    let e5_service = EmbeddingService::with_model_type(EmbeddingModel::E5MultilingualLargeInstruct);
    println!("E5 embedding service initialized (dimensions: {})", e5_service.dimensions());
    
    // GTE Modern BERT Base
    let gte_service = EmbeddingService::with_model_type(EmbeddingModel::GteModernBertBase);
    println!("GTE embedding service initialized (dimensions: {})", gte_service.dimensions());
    
    // Jina Embeddings V3
    let jina_service = EmbeddingService::with_model_type(EmbeddingModel::JinaEmbeddingsV3);
    println!("Jina embedding service initialized (dimensions: {})", jina_service.dimensions());
    
    // 3. Or specify a custom model by name
    // let custom_service = EmbeddingService::new_with_model(Some("sentence-transformers/all-MiniLM-L6-v2"));
    
    // For this example, we'll use the default model
    let embedding_service = default_service;
    
    // Create memory nodes from text
    let texts = vec![
        "Artificial intelligence is transforming how we interact with technology",
        "Machine learning algorithms can identify patterns in large datasets",
        "Natural language processing enables computers to understand human speech",
        "Computer vision systems can recognize objects in images and videos",
        "Reinforcement learning helps AI agents learn through trial and error",
        "Quantum computing may accelerate certain AI algorithms exponentially"
    ];
    
    // Store all texts as memories
    println!("\nCreating memories from text...");
    let mut memory_ids = Vec::new();
    
    for (i, text) in texts.iter().enumerate() {
        // The create_memory_from_text method handles everything:
        // 1. Converts text to embeddings
        // 2. Creates a memory node with those embeddings
        // 3. Stores the text in the 'content' attribute
        // 4. Saves the memory to the database
        
        let memory_id = db.create_memory_from_text(
            text,
            &embedding_service,
            Some("AI concepts"),
            Some({
                let mut attributes = std::collections::HashMap::new();
                attributes.insert(
                    "title".to_string(),
                    AttributeValue::String(format!("AI Concept {}", i+1))
                );
                attributes
            })
        )?;
        
        memory_ids.push(memory_id);
        println!("Created memory {}: ID {}", i+1, memory_id);
    }
    
    // Demonstrate semantic search
    println!("\nPerforming semantic searches...");
    
    // Create query embeddings
    let queries = vec![
        "How is AI changing our technology interactions?",
        "Finding patterns in data",
        "Understanding human language",
        "Recognizing objects in pictures",
        "Learning through experimentation",
        "Advanced computing methods"
    ];
    
    for (i, query) in queries.iter().enumerate() {
        println!("\nQuery: '{}'", query);
        
        // Generate embeddings for the query
        let query_embedding = embedding_service.generate_for_query(query)?;
        
        // Search for similar memories
        let results = db.search_similar(&query_embedding, 2, 0.0)?;
        
        // Display results
        println!("Found {} results:", results.len());
        for (j, (memory_id, similarity)) in results.iter().enumerate() {
            // Load the memory to get its content
            let memory = db.load(*memory_id)?;
            
            let content = match memory.get_attribute("content") {
                Some(AttributeValue::String(content)) => content,
                _ => "No content available",
            };
            
            println!("  Result {}: (similarity: {:.4})", j+1, similarity);
            println!("    Content: '{}'", content);
        }
    }
    
    Ok(())
}
These examples demonstrate the core functionality of EngramDB in Rust applications. You can combine these patterns to build more complex applications that leverage the full power of the database.