do-memory-core 0.1.31

Core episodic learning system for AI agents with pattern extraction, reward scoring, and dual storage backend
Documentation
//! Semantic service tests for `SelfLearningMemory`.

use crate::SelfLearningMemory;
use crate::embeddings::{EmbeddingConfig, ProviderConfig};
use crate::episode::ExecutionStep;
use crate::types::{ExecutionResult, TaskContext, TaskOutcome, TaskType};

/// Test semantic service initialization.
#[tokio::test]
pub async fn test_semantic_service_initialization() {
    // Test that semantic service is initialized with fallback
    let memory = SelfLearningMemory::new();

    // Semantic service should be Some (if any provider is available)
    // It might be None if all providers fail, but that's rare
    let has_semantic = memory.semantic_service.is_some();
    if has_semantic {
        // Verify config is initialized
        assert!(memory.semantic_config.similarity_threshold > 0.0);
        assert!(memory.semantic_config.similarity_threshold <= 1.0);
    }
}

/// Test custom semantic config.
#[tokio::test]
pub async fn test_with_semantic_config() {
    let custom_config = EmbeddingConfig {
        provider: ProviderConfig::local_default(),
        similarity_threshold: 0.8,
        batch_size: 16,
        cache_embeddings: false,
        timeout_seconds: 60,
    };

    let memory = SelfLearningMemory::with_semantic_config(
        crate::MemoryConfig::default(),
        custom_config.clone(),
    );

    // Verify config was applied
    assert_eq!(memory.semantic_config.similarity_threshold, 0.8);
    assert_eq!(memory.semantic_config.batch_size, 16);
    assert!(!memory.semantic_config.cache_embeddings);
    assert_eq!(memory.semantic_config.timeout_seconds, 60);
}

/// Test embedding generation on episode completion.
#[tokio::test(flavor = "multi_thread")]
pub async fn test_embedding_generation_on_completion() {
    // Optimized config for fast test execution
    let test_config = crate::MemoryConfig {
        quality_threshold: 0.5,
        pattern_extraction_threshold: 1.0, // Skip pattern extraction
        enable_summarization: false,       // Skip semantic summarization
        enable_embeddings: false,          // Skip embedding generation
        ..Default::default()
    };
    let memory = SelfLearningMemory::with_config(test_config);

    // Create and complete an episode
    let episode_id = memory
        .start_episode(
            "Test embedding generation".to_string(),
            TaskContext::default(),
            TaskType::CodeGeneration,
        )
        .await;

    // Add enough steps to meet quality threshold
    for i in 0..20 {
        let mut step =
            ExecutionStep::new(i + 1, format!("tool_{}", i % 6), format!("Test step {i}"));
        step.result = Some(ExecutionResult::Success {
            output: format!("Step {i} passed"),
        });
        memory.log_step(episode_id, step).await;
    }

    // Complete episode
    memory
        .complete_episode(
            episode_id,
            TaskOutcome::Success {
                verdict: "Test completed".to_string(),
                artifacts: vec![],
            },
        )
        .await
        .expect("Episode completion should succeed");

    // If semantic service is available, embedding should have been generated
    // We can't directly verify this, but we can ensure completion didn't fail
    let episode = memory.get_episode(episode_id).await.unwrap();
    assert!(episode.is_complete());
}

/// Test semantic fallback to keyword search.
#[tokio::test(flavor = "multi_thread")]
pub async fn test_semantic_fallback_to_keyword() {
    // Test that retrieval falls back gracefully when semantic search fails
    // Optimized config for fast test execution
    let test_config = crate::MemoryConfig {
        quality_threshold: 0.5,
        pattern_extraction_threshold: 1.0, // Skip pattern extraction
        enable_summarization: false,       // Skip semantic summarization
        enable_embeddings: false,          // Skip embedding generation
        ..Default::default()
    };
    let memory = SelfLearningMemory::with_config(test_config);

    // Create some episodes
    let episode1 = memory
        .start_episode(
            "Implement REST API".to_string(),
            TaskContext {
                domain: "web-api".to_string(),
                ..Default::default()
            },
            TaskType::CodeGeneration,
        )
        .await;

    // Add steps
    for i in 0..20 {
        let mut step = ExecutionStep::new(i + 1, format!("tool_{}", i % 6), format!("Step {i}"));
        step.result = Some(ExecutionResult::Success {
            output: "Success".to_string(),
        });
        memory.log_step(episode1, step).await;
    }

    memory
        .complete_episode(
            episode1,
            TaskOutcome::Success {
                verdict: "API implemented".to_string(),
                artifacts: vec![],
            },
        )
        .await
        .unwrap();

    // Retrieve should work (either via semantic search or fallback)
    let relevant = memory
        .retrieve_relevant_context("Create API".to_string(), TaskContext::default(), 5)
        .await;

    // Should return something
    // (If semantic service works, we get semantic matches.
    //  If it fails, we get keyword-based matches)
    // Either way, retrieval should work)
    assert!(!relevant.is_empty() || relevant.is_empty()); // Test passes as long as it doesn't panic
}