468 lines
15 KiB
Rust
468 lines
15 KiB
Rust
//! Integration tests for temporal comparison and scheduling
|
|
//!
|
|
//! Tests real-world scenarios combining temporal analysis and scheduling
|
|
|
|
use midstream::{
|
|
TemporalComparator, Sequence, ComparisonAlgorithm,
|
|
RealtimeScheduler, SchedulingPolicy, Priority,
|
|
Action, AgentContext, AgenticLoop, LeanAgenticConfig,
|
|
};
|
|
use std::collections::HashMap;
|
|
use std::time::Duration;
|
|
|
|
#[tokio::test]
|
|
async fn test_temporal_conversation_pattern_matching() {
|
|
// Simulate detecting similar conversation patterns
|
|
let mut comparator = TemporalComparator::<String>::new();
|
|
|
|
// Add historical conversation sequences
|
|
comparator.add_sequence(Sequence {
|
|
data: vec![
|
|
"greeting".to_string(),
|
|
"weather_query".to_string(),
|
|
"location_query".to_string(),
|
|
"weather_response".to_string(),
|
|
],
|
|
timestamp: 1000,
|
|
id: "conv1".to_string(),
|
|
});
|
|
|
|
comparator.add_sequence(Sequence {
|
|
data: vec![
|
|
"greeting".to_string(),
|
|
"weather_query".to_string(),
|
|
"location_query".to_string(),
|
|
"weather_response".to_string(),
|
|
"followup".to_string(),
|
|
],
|
|
timestamp: 2000,
|
|
id: "conv2".to_string(),
|
|
});
|
|
|
|
comparator.add_sequence(Sequence {
|
|
data: vec![
|
|
"greeting".to_string(),
|
|
"calendar_query".to_string(),
|
|
"calendar_response".to_string(),
|
|
],
|
|
timestamp: 3000,
|
|
id: "conv3".to_string(),
|
|
});
|
|
|
|
// Query with new conversation
|
|
let query = vec![
|
|
"greeting".to_string(),
|
|
"weather_query".to_string(),
|
|
"location_query".to_string(),
|
|
];
|
|
|
|
let similar = comparator.find_similar(&query, 0.7, ComparisonAlgorithm::LCS);
|
|
|
|
// Should find conv1 and conv2 as similar (weather conversations)
|
|
assert!(similar.len() >= 2);
|
|
println!("Found {} similar conversations", similar.len());
|
|
|
|
for (idx, score) in similar.iter() {
|
|
println!("Conversation {}: similarity = {}", idx, score);
|
|
assert!(*score >= 0.7);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_temporal_action_sequence_analysis() {
|
|
// Test analyzing agent action sequences over time
|
|
let mut comparator = TemporalComparator::<String>::new();
|
|
|
|
// Normal behavior pattern
|
|
let normal_sequence = vec![
|
|
"plan".to_string(),
|
|
"verify".to_string(),
|
|
"execute".to_string(),
|
|
"observe".to_string(),
|
|
"learn".to_string(),
|
|
];
|
|
|
|
// Anomalous behavior (skips verification)
|
|
let anomalous_sequence = vec![
|
|
"plan".to_string(),
|
|
"execute".to_string(),
|
|
"observe".to_string(),
|
|
"learn".to_string(),
|
|
];
|
|
|
|
// Compare sequences
|
|
let similarity = comparator.compare(
|
|
&normal_sequence,
|
|
&anomalous_sequence,
|
|
ComparisonAlgorithm::LCS,
|
|
);
|
|
|
|
println!("Similarity between normal and anomalous: {}", similarity);
|
|
|
|
// LCS should show high similarity but not perfect
|
|
assert!(similarity > 0.6);
|
|
assert!(similarity < 1.0);
|
|
|
|
// Edit distance should show difference
|
|
let distance = comparator.compare(
|
|
&normal_sequence,
|
|
&anomalous_sequence,
|
|
ComparisonAlgorithm::EditDistance,
|
|
);
|
|
|
|
println!("Edit distance: {}", distance);
|
|
assert!(distance > 0.0); // Should detect the missing step
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_scheduler_with_deadlines() {
|
|
// Test real-time scheduling with various deadline constraints
|
|
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
|
|
|
// Schedule critical task with tight deadline
|
|
let critical_action = Action {
|
|
action_type: "critical_response".to_string(),
|
|
description: "User safety check".to_string(),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: Some("safe".to_string()),
|
|
expected_reward: 1.0,
|
|
};
|
|
|
|
let critical_id = scheduler.schedule(
|
|
critical_action,
|
|
Priority::Critical,
|
|
Duration::from_millis(50), // Very tight deadline
|
|
Duration::from_millis(10),
|
|
).await;
|
|
|
|
// Schedule normal task with relaxed deadline
|
|
let normal_action = Action {
|
|
action_type: "normal_query".to_string(),
|
|
description: "Regular information request".to_string(),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: None,
|
|
expected_reward: 0.7,
|
|
};
|
|
|
|
scheduler.schedule(
|
|
normal_action,
|
|
Priority::Medium,
|
|
Duration::from_secs(5), // Relaxed deadline
|
|
Duration::from_millis(100),
|
|
).await;
|
|
|
|
// EDF should prioritize the critical task due to earlier deadline
|
|
let next = scheduler.next_task().await.unwrap();
|
|
assert_eq!(next.id, critical_id);
|
|
assert_eq!(next.action.action_type, "critical_response");
|
|
|
|
println!("Scheduler correctly prioritized critical task with tight deadline");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_scheduler_priority_override() {
|
|
// Test that priority scheduling overrides based on priority level
|
|
let scheduler = RealtimeScheduler::new(SchedulingPolicy::FixedPriority);
|
|
|
|
// Schedule low priority task first
|
|
scheduler.schedule(
|
|
Action {
|
|
action_type: "background_task".to_string(),
|
|
description: "Background processing".to_string(),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: None,
|
|
expected_reward: 0.3,
|
|
},
|
|
Priority::Background,
|
|
Duration::from_secs(10),
|
|
Duration::from_millis(100),
|
|
).await;
|
|
|
|
// Schedule high priority task second
|
|
scheduler.schedule(
|
|
Action {
|
|
action_type: "urgent_task".to_string(),
|
|
description: "Urgent response needed".to_string(),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: None,
|
|
expected_reward: 0.9,
|
|
},
|
|
Priority::Critical,
|
|
Duration::from_secs(10),
|
|
Duration::from_millis(50),
|
|
).await;
|
|
|
|
// Should get high priority task first despite being scheduled later
|
|
let next = scheduler.next_task().await.unwrap();
|
|
assert_eq!(next.action.action_type, "urgent_task");
|
|
|
|
println!("Priority scheduling correctly prioritized critical task");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_combined_temporal_and_scheduling() {
|
|
// Integration test: Use temporal patterns to inform scheduling decisions
|
|
let mut comparator = TemporalComparator::<String>::new();
|
|
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
|
|
|
// Historical pattern: queries that led to good outcomes
|
|
comparator.add_sequence(Sequence {
|
|
data: vec![
|
|
"user_query".to_string(),
|
|
"context_check".to_string(),
|
|
"knowledge_lookup".to_string(),
|
|
"response".to_string(),
|
|
],
|
|
timestamp: 1000,
|
|
id: "good_pattern".to_string(),
|
|
});
|
|
|
|
// Current query sequence
|
|
let current = vec!["user_query".to_string(), "context_check".to_string()];
|
|
|
|
// Find similar patterns
|
|
let similar = comparator.find_similar(¤t, 0.5, ComparisonAlgorithm::LCS);
|
|
|
|
if !similar.is_empty() {
|
|
println!("Found similar successful pattern, scheduling with high priority");
|
|
|
|
// Schedule next expected action with higher priority
|
|
scheduler.schedule(
|
|
Action {
|
|
action_type: "knowledge_lookup".to_string(),
|
|
description: "Predicted next action from pattern".to_string(),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: Some("success".to_string()),
|
|
expected_reward: 0.85,
|
|
},
|
|
Priority::High, // Higher priority based on pattern match
|
|
Duration::from_millis(100),
|
|
Duration::from_millis(20),
|
|
).await;
|
|
}
|
|
|
|
let stats = scheduler.get_stats().await;
|
|
assert_eq!(stats.total_scheduled, 1);
|
|
|
|
println!("Successfully combined temporal pattern matching with scheduling");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_scheduler_deadline_checking() {
|
|
// Test the can_meet_deadline functionality
|
|
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
|
|
|
// Empty queue - should be able to meet deadline
|
|
let can_meet = scheduler.can_meet_deadline(
|
|
Duration::from_millis(10),
|
|
Duration::from_secs(1),
|
|
).await;
|
|
assert!(can_meet);
|
|
|
|
// Add many tasks
|
|
for i in 0..50 {
|
|
scheduler.schedule(
|
|
Action {
|
|
action_type: format!("task_{}", i),
|
|
description: format!("Task {}", i),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: None,
|
|
expected_reward: 0.7,
|
|
},
|
|
Priority::Medium,
|
|
Duration::from_secs(10),
|
|
Duration::from_millis(50), // Each task takes 50ms
|
|
).await;
|
|
}
|
|
|
|
// Now with 50 tasks * 50ms = 2500ms pending work
|
|
let can_meet_tight = scheduler.can_meet_deadline(
|
|
Duration::from_millis(10),
|
|
Duration::from_millis(100), // Want to finish in 100ms
|
|
).await;
|
|
|
|
assert!(!can_meet_tight); // Should not be able to meet tight deadline
|
|
|
|
let can_meet_loose = scheduler.can_meet_deadline(
|
|
Duration::from_millis(10),
|
|
Duration::from_secs(10), // Generous deadline
|
|
).await;
|
|
|
|
assert!(can_meet_loose); // Should be able to meet loose deadline
|
|
|
|
println!("Deadline checking correctly estimates feasibility");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_temporal_caching() {
|
|
// Test that temporal comparison caching works correctly
|
|
let mut comparator = TemporalComparator::<i32>::new();
|
|
|
|
let seq1: Vec<i32> = (0..100).collect();
|
|
let seq2: Vec<i32> = (0..100).map(|x| x + 1).collect();
|
|
|
|
// First comparison - not cached
|
|
let result1 = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW);
|
|
|
|
// Second comparison - should be cached
|
|
let result2 = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW);
|
|
|
|
assert_eq!(result1, result2);
|
|
|
|
let stats = comparator.cache_stats();
|
|
println!("Cache stats: {:?}", stats);
|
|
|
|
// Should have cached the result
|
|
assert_eq!(stats.dtw_count, 1); // Only computed once
|
|
|
|
// Try different algorithm - should compute again
|
|
let _result3 = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::LCS);
|
|
|
|
let stats2 = comparator.cache_stats();
|
|
assert_eq!(stats2.lcs_count, 1);
|
|
assert_eq!(stats2.total_comparisons, 2); // DTW + LCS
|
|
|
|
println!("Caching working correctly: {} total comparisons", stats2.total_comparisons);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_pattern_detection_in_stream() {
|
|
// Simulate detecting recurring patterns in a stream
|
|
let comparator = TemporalComparator::<String>::new();
|
|
|
|
// Simulated stream of user intents
|
|
let intent_stream = vec![
|
|
"weather", "location", "weather", "news", "sports",
|
|
"weather", "location", "weather", "calendar", "weather",
|
|
"location", "weather",
|
|
].into_iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
|
|
|
// Pattern we're looking for
|
|
let pattern = vec!["weather".to_string(), "location".to_string(), "weather".to_string()];
|
|
|
|
let positions = comparator.detect_pattern(&intent_stream, &pattern);
|
|
|
|
println!("Found pattern at positions: {:?}", positions);
|
|
assert!(!positions.is_empty());
|
|
|
|
// Should find the pattern at position 0 and position 9
|
|
assert!(positions.contains(&0));
|
|
assert!(positions.contains(&9));
|
|
|
|
println!("Successfully detected {} pattern occurrences in stream", positions.len());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_scheduler_stats_tracking() {
|
|
// Test that scheduler correctly tracks statistics
|
|
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
|
|
|
// Schedule and execute several tasks
|
|
for i in 0..10 {
|
|
let task_id = scheduler.schedule(
|
|
Action {
|
|
action_type: format!("task_{}", i),
|
|
description: format!("Task {}", i),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: None,
|
|
expected_reward: 0.7,
|
|
},
|
|
Priority::Medium,
|
|
Duration::from_secs(1),
|
|
Duration::from_millis(10),
|
|
).await;
|
|
|
|
// Mark as executed with varying durations
|
|
scheduler.mark_executed(task_id, Duration::from_micros(100 * (i + 1))).await;
|
|
}
|
|
|
|
let stats = scheduler.get_stats().await;
|
|
|
|
assert_eq!(stats.total_scheduled, 10);
|
|
assert_eq!(stats.total_executed, 10);
|
|
assert!(stats.average_latency_ns > 0);
|
|
assert!(stats.max_latency_ns >= stats.min_latency_ns);
|
|
|
|
println!("Scheduler stats: {:?}", stats);
|
|
println!("Average latency: {} μs", stats.average_latency_ns / 1000);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_real_world_conversation_flow() {
|
|
// Simulate a realistic conversation flow with scheduling
|
|
let mut comparator = TemporalComparator::<String>::new();
|
|
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
|
|
|
// Add historical successful conversation patterns
|
|
comparator.add_sequence(Sequence {
|
|
data: vec![
|
|
"greeting".to_string(),
|
|
"clarification".to_string(),
|
|
"action".to_string(),
|
|
"confirmation".to_string(),
|
|
],
|
|
timestamp: 1000,
|
|
id: "success_pattern".to_string(),
|
|
});
|
|
|
|
// Current conversation
|
|
let current_flow = vec!["greeting".to_string(), "clarification".to_string()];
|
|
|
|
// Check similarity to successful patterns
|
|
let similar = comparator.find_similar(¤t_flow, 0.6, ComparisonAlgorithm::LCS);
|
|
|
|
if !similar.is_empty() {
|
|
// We found a similar successful pattern, schedule next actions accordingly
|
|
|
|
// Schedule the predicted next action (from pattern)
|
|
scheduler.schedule(
|
|
Action {
|
|
action_type: "action".to_string(),
|
|
description: "Execute predicted action from pattern".to_string(),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: Some("confirmation".to_string()),
|
|
expected_reward: 0.8,
|
|
},
|
|
Priority::High,
|
|
Duration::from_millis(200),
|
|
Duration::from_millis(50),
|
|
).await;
|
|
|
|
// Schedule confirmation as follow-up
|
|
scheduler.schedule(
|
|
Action {
|
|
action_type: "confirmation".to_string(),
|
|
description: "Confirm action completion".to_string(),
|
|
parameters: HashMap::new(),
|
|
tool_calls: vec![],
|
|
expected_outcome: Some("success".to_string()),
|
|
expected_reward: 0.9,
|
|
},
|
|
Priority::Medium,
|
|
Duration::from_millis(500),
|
|
Duration::from_millis(30),
|
|
).await;
|
|
|
|
println!("Scheduled actions based on historical success pattern");
|
|
}
|
|
|
|
// Execute scheduled tasks
|
|
let mut executed_count = 0;
|
|
while let Some(task) = scheduler.next_task().await {
|
|
println!("Executing: {}", task.action.action_type);
|
|
scheduler.mark_executed(task.id, Duration::from_millis(10)).await;
|
|
executed_count += 1;
|
|
}
|
|
|
|
assert_eq!(executed_count, 2);
|
|
println!("Successfully completed conversation flow based on patterns");
|
|
}
|