mirror of
https://github.com/ruvnet/RuView
synced 2026-08-04 19:31:42 +00:00
feat: vendor midstream and sublinear-time-solver libraries (#109)
Add ruvnet/midstream (AIMDS real-time inference) and ruvnet/sublinear-time-solver (sublinear optimization algorithms) as vendored dependencies under vendor/.
This commit is contained in:
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
use midstream::{Midstream, HyprSettings, HyprServiceImpl, StreamProcessor, LLMClient};
|
||||
use futures::stream::BoxStream;
|
||||
use futures::stream::iter;
|
||||
use std::time::Duration;
|
||||
|
||||
// Example LLM client implementation
|
||||
struct ExampleLLMClient;
|
||||
|
||||
impl LLMClient for ExampleLLMClient {
|
||||
fn stream(&self) -> BoxStream<'static, String> {
|
||||
Box::pin(iter(vec![
|
||||
"URGENT: What's the weather like?".to_string(),
|
||||
"Schedule a meeting for tomorrow".to_string(),
|
||||
"Just a normal message".to_string(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize settings
|
||||
let settings = HyprSettings::new()?;
|
||||
|
||||
// Create hyprstream service
|
||||
let hypr_service = HyprServiceImpl::new(&settings).await?;
|
||||
|
||||
// Create LLM client
|
||||
let llm_client = ExampleLLMClient;
|
||||
|
||||
// Initialize Midstream
|
||||
let midstream = Midstream::new(
|
||||
Box::new(llm_client),
|
||||
Box::new(hypr_service),
|
||||
);
|
||||
|
||||
// Process stream
|
||||
let messages = midstream.process_stream().await?;
|
||||
println!("\nProcessed messages:");
|
||||
for msg in &messages {
|
||||
println!("- Content: {}", msg.content);
|
||||
println!(" Intent: {:?}", msg.intent);
|
||||
if let Some(response) = &msg.tool_response {
|
||||
println!(" Tool Response: {}", response);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
// Get metrics
|
||||
let metrics = midstream.get_metrics().await;
|
||||
println!("\nCollected metrics:");
|
||||
for metric in &metrics {
|
||||
println!("- Name: {}", metric.name);
|
||||
println!(" Value: {}", metric.value);
|
||||
println!(" Labels: {:?}", metric.labels);
|
||||
println!();
|
||||
}
|
||||
|
||||
// Get average sentiment for last 5 minutes
|
||||
let avg = midstream.get_average_sentiment(Duration::from_secs(300)).await?;
|
||||
println!("\nAverage sentiment: {}", avg);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
use config::{Config, ConfigError, Environment, File};
|
||||
use serde::Deserialize;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct HyprSettings {
|
||||
pub engine: EngineSettings,
|
||||
pub cache: CacheSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct EngineSettings {
|
||||
pub engine: String,
|
||||
pub connection: String,
|
||||
pub options: std::collections::HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CacheSettings {
|
||||
pub enabled: bool,
|
||||
pub engine: String,
|
||||
pub connection: String,
|
||||
pub max_duration_secs: u64,
|
||||
}
|
||||
|
||||
impl HyprSettings {
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
let config_dir = Path::new("config");
|
||||
|
||||
let builder = Config::builder()
|
||||
// Start with default settings
|
||||
.add_source(File::from(config_dir.join("default.toml")).required(false))
|
||||
// Add local overrides
|
||||
.add_source(File::from(config_dir.join("local.toml")).required(false))
|
||||
// Add environment variables with prefix MIDSTREAM_
|
||||
.add_source(Environment::with_prefix("MIDSTREAM").separator("_"));
|
||||
|
||||
builder.build()?.try_deserialize()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HyprSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
engine: EngineSettings {
|
||||
engine: "duckdb".to_string(),
|
||||
connection: ":memory:".to_string(),
|
||||
options: std::collections::HashMap::new(),
|
||||
},
|
||||
cache: CacheSettings {
|
||||
enabled: true,
|
||||
engine: "duckdb".to_string(),
|
||||
connection: ":memory:".to_string(),
|
||||
max_duration_secs: 3600,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Once;
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
fn setup() {
|
||||
INIT.call_once(|| {
|
||||
std::env::set_var("MIDSTREAM_ENGINE_ENGINE", "test_engine");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_settings() {
|
||||
let settings = HyprSettings::default();
|
||||
assert_eq!(settings.engine.engine, "duckdb");
|
||||
assert!(settings.cache.enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_environment_override() {
|
||||
setup();
|
||||
let settings = HyprSettings::new().unwrap();
|
||||
assert_eq!(settings.engine.engine, "test_engine");
|
||||
}
|
||||
}
|
||||
Vendored
+115
@@ -0,0 +1,115 @@
|
||||
use crate::config::HyprSettings;
|
||||
use crate::midstream::{HyprService, MetricRecord, TimeWindow, AggregateFunction};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use async_trait::async_trait;
|
||||
|
||||
type BoxError = Box<dyn std::error::Error>;
|
||||
|
||||
pub struct HyprServiceImpl {
|
||||
metrics: Arc<Mutex<Vec<MetricRecord>>>,
|
||||
}
|
||||
|
||||
impl HyprServiceImpl {
|
||||
pub async fn new(_settings: &HyprSettings) -> Result<Self, BoxError> {
|
||||
Ok(Self {
|
||||
metrics: Arc::new(Mutex::new(Vec::new())),
|
||||
})
|
||||
}
|
||||
|
||||
async fn calculate_aggregate(&self, window: TimeWindow, func: AggregateFunction) -> Result<f64, BoxError> {
|
||||
let metrics = self.metrics.lock().await;
|
||||
let now = chrono::Utc::now().timestamp() as u64;
|
||||
let window_secs = match window {
|
||||
TimeWindow::Minutes(m) => m as u64 * 60,
|
||||
TimeWindow::Hours(h) => h as u64 * 3600,
|
||||
TimeWindow::Days(d) => d as u64 * 86400,
|
||||
};
|
||||
|
||||
let filtered: Vec<_> = metrics
|
||||
.iter()
|
||||
.filter(|m| now - m.timestamp <= window_secs)
|
||||
.collect();
|
||||
|
||||
match func {
|
||||
AggregateFunction::Average => {
|
||||
if filtered.is_empty() {
|
||||
Ok(0.0)
|
||||
} else {
|
||||
let sum: f64 = filtered.iter().map(|m| m.value).sum();
|
||||
Ok(sum / filtered.len() as f64)
|
||||
}
|
||||
}
|
||||
AggregateFunction::Sum => {
|
||||
Ok(filtered.iter().map(|m| m.value).sum())
|
||||
}
|
||||
AggregateFunction::Count => {
|
||||
Ok(filtered.len() as f64)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HyprService for HyprServiceImpl {
|
||||
async fn ingest_metric(&self, metric: MetricRecord) -> Result<(), BoxError> {
|
||||
let mut metrics = self.metrics.lock().await;
|
||||
metrics.push(metric);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn query_aggregate(&self, window: TimeWindow, func: AggregateFunction) -> Result<f64, BoxError> {
|
||||
self.calculate_aggregate(window, func).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_hypr_service_creation() {
|
||||
let settings = HyprSettings::default();
|
||||
let service = HyprServiceImpl::new(&settings).await;
|
||||
assert!(service.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_metric_ingestion() {
|
||||
let settings = HyprSettings::default();
|
||||
let service = HyprServiceImpl::new(&settings).await.unwrap();
|
||||
|
||||
let metric = MetricRecord {
|
||||
timestamp: chrono::Utc::now().timestamp() as u64,
|
||||
name: "test_metric".to_string(),
|
||||
value: 1.0,
|
||||
labels: vec![("test".to_string(), "true".to_string())],
|
||||
};
|
||||
|
||||
let result = service.ingest_metric(metric).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_aggregation_query() {
|
||||
let settings = HyprSettings::default();
|
||||
let service = HyprServiceImpl::new(&settings).await.unwrap();
|
||||
|
||||
// First ingest some metrics
|
||||
let metric = MetricRecord {
|
||||
timestamp: chrono::Utc::now().timestamp() as u64,
|
||||
name: "test_metric".to_string(),
|
||||
value: 1.0,
|
||||
labels: vec![("test".to_string(), "true".to_string())],
|
||||
};
|
||||
service.ingest_metric(metric).await.unwrap();
|
||||
|
||||
// Now query the aggregate
|
||||
let result = service.query_aggregate(
|
||||
TimeWindow::Minutes(5),
|
||||
AggregateFunction::Average,
|
||||
).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), 1.0);
|
||||
}
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
//! Agentic loop for autonomous decision-making (Plan-Act-Observe-Learn)
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::types::{Context, AgentState, Goal, Policy, Reward};
|
||||
use super::LeanAgenticConfig;
|
||||
|
||||
/// Agentic loop orchestrator
|
||||
pub struct AgenticLoop {
|
||||
/// Current agent state
|
||||
state: AgentState,
|
||||
|
||||
/// Configuration
|
||||
config: LeanAgenticConfig,
|
||||
|
||||
/// Action history
|
||||
action_history: Vec<Action>,
|
||||
|
||||
/// Total reward accumulated
|
||||
total_reward: f64,
|
||||
|
||||
/// Action execution count
|
||||
action_count: u64,
|
||||
}
|
||||
|
||||
impl AgenticLoop {
|
||||
pub fn new(config: LeanAgenticConfig) -> Self {
|
||||
Self {
|
||||
state: AgentState::default(),
|
||||
config,
|
||||
action_history: Vec::new(),
|
||||
total_reward: 0.0,
|
||||
action_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Plan phase: Generate a plan based on goals and context
|
||||
pub async fn plan(&self, context: &Context, input: &str) -> Result<Plan, String> {
|
||||
let mut plan = Plan {
|
||||
goal: Goal {
|
||||
id: format!("goal_{}", self.action_count),
|
||||
description: format!("Process: {}", input),
|
||||
priority: 1.0,
|
||||
achieved: false,
|
||||
},
|
||||
steps: Vec::new(),
|
||||
estimated_reward: 0.0,
|
||||
confidence: 0.0,
|
||||
};
|
||||
|
||||
// Analyze input to determine appropriate actions
|
||||
let actions = self.generate_action_candidates(input, context).await;
|
||||
|
||||
// Rank actions by expected reward
|
||||
let ranked_actions = self.rank_actions(actions).await;
|
||||
|
||||
// Add top actions to plan
|
||||
for (i, action) in ranked_actions.iter().take(self.config.max_planning_depth).enumerate() {
|
||||
plan.steps.push(PlanStep {
|
||||
sequence: i,
|
||||
action: action.clone(),
|
||||
preconditions: vec![],
|
||||
postconditions: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
plan.estimated_reward = ranked_actions.first()
|
||||
.map(|a| a.expected_reward)
|
||||
.unwrap_or(0.0);
|
||||
|
||||
plan.confidence = if !plan.steps.is_empty() { 0.8 } else { 0.0 };
|
||||
|
||||
Ok(plan)
|
||||
}
|
||||
|
||||
/// Act phase: Select and prepare an action from the plan
|
||||
pub async fn select_action(&self, plan: &Plan) -> Result<Action, String> {
|
||||
if plan.steps.is_empty() {
|
||||
return Err("Empty plan".to_string());
|
||||
}
|
||||
|
||||
// Select first step with highest confidence
|
||||
let step = &plan.steps[0];
|
||||
Ok(step.action.clone())
|
||||
}
|
||||
|
||||
/// Execute an action and return observation
|
||||
pub async fn execute(&mut self, action: &Action) -> Result<Observation, String> {
|
||||
self.action_count += 1;
|
||||
self.action_history.push(action.clone());
|
||||
|
||||
// Simulate action execution
|
||||
let observation = Observation {
|
||||
success: true,
|
||||
result: format!("Executed: {}", action.action_type),
|
||||
changes: vec![format!("Action {} completed", action.action_type)],
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
};
|
||||
|
||||
Ok(observation)
|
||||
}
|
||||
|
||||
/// Compute reward based on observation
|
||||
pub async fn compute_reward(&self, observation: &Observation) -> Result<Reward, String> {
|
||||
let base_reward = if observation.success { 1.0 } else { -1.0 };
|
||||
|
||||
// Bonus for meaningful changes
|
||||
let change_bonus = observation.changes.len() as f64 * 0.1;
|
||||
|
||||
Ok(base_reward + change_bonus)
|
||||
}
|
||||
|
||||
/// Learn phase: Update policies based on experience
|
||||
pub async fn learn(&mut self, signal: LearningSignal) -> Result<(), String> {
|
||||
self.total_reward += signal.reward;
|
||||
|
||||
// Update policy based on reward
|
||||
let policy = Policy {
|
||||
condition: format!("When: {}", signal.action.description),
|
||||
action: signal.action.action_type.clone(),
|
||||
expected_reward: signal.reward,
|
||||
usage_count: 1,
|
||||
};
|
||||
|
||||
// Check if similar policy exists
|
||||
if let Some(existing) = self.state.policies.iter_mut()
|
||||
.find(|p| p.action == policy.action) {
|
||||
// Update existing policy with exponential moving average
|
||||
existing.expected_reward = 0.9 * existing.expected_reward + 0.1 * signal.reward;
|
||||
existing.usage_count += 1;
|
||||
} else {
|
||||
// Add new policy
|
||||
self.state.policies.push(policy);
|
||||
}
|
||||
|
||||
// Update confidence based on learning
|
||||
self.state.confidence = (self.total_reward / self.action_count as f64).clamp(0.0, 1.0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn generate_action_candidates(&self, input: &str, context: &Context) -> Vec<Action> {
|
||||
let mut candidates = Vec::new();
|
||||
|
||||
// Generate different action types based on input
|
||||
let input_lower = input.to_lowercase();
|
||||
|
||||
if input_lower.contains("weather") {
|
||||
candidates.push(Action {
|
||||
action_type: "get_weather".to_string(),
|
||||
description: "Fetch weather information".to_string(),
|
||||
parameters: HashMap::from([
|
||||
("query".to_string(), input.to_string()),
|
||||
]),
|
||||
tool_calls: vec!["weather_api".to_string()],
|
||||
expected_outcome: Some("Weather data".to_string()),
|
||||
expected_reward: 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
if input_lower.contains("learn") || input_lower.contains("remember") {
|
||||
candidates.push(Action {
|
||||
action_type: "update_knowledge".to_string(),
|
||||
description: "Update knowledge graph".to_string(),
|
||||
parameters: HashMap::from([
|
||||
("content".to_string(), input.to_string()),
|
||||
]),
|
||||
tool_calls: vec![],
|
||||
expected_outcome: Some("Knowledge updated".to_string()),
|
||||
expected_reward: 0.9,
|
||||
});
|
||||
}
|
||||
|
||||
// Default action: process and respond
|
||||
candidates.push(Action {
|
||||
action_type: "process_text".to_string(),
|
||||
description: format!("Process: {}", input),
|
||||
parameters: HashMap::from([
|
||||
("text".to_string(), input.to_string()),
|
||||
]),
|
||||
tool_calls: vec![],
|
||||
expected_outcome: Some("Processed text".to_string()),
|
||||
expected_reward: 0.5,
|
||||
});
|
||||
|
||||
candidates
|
||||
}
|
||||
|
||||
async fn rank_actions(&self, mut actions: Vec<Action>) -> Vec<Action> {
|
||||
// Sort by expected reward and learned policies
|
||||
actions.sort_by(|a, b| {
|
||||
let a_boost = self.state.policies.iter()
|
||||
.find(|p| p.action == a.action_type)
|
||||
.map(|p| p.expected_reward)
|
||||
.unwrap_or(0.0);
|
||||
|
||||
let b_boost = self.state.policies.iter()
|
||||
.find(|p| p.action == b.action_type)
|
||||
.map(|p| p.expected_reward)
|
||||
.unwrap_or(0.0);
|
||||
|
||||
let a_score = a.expected_reward + a_boost * 0.5;
|
||||
let b_score = b.expected_reward + b_boost * 0.5;
|
||||
|
||||
b_score.partial_cmp(&a_score).unwrap()
|
||||
});
|
||||
|
||||
actions
|
||||
}
|
||||
|
||||
pub fn action_count(&self) -> u64 {
|
||||
self.action_count
|
||||
}
|
||||
|
||||
pub fn average_reward(&self) -> f64 {
|
||||
if self.action_count == 0 {
|
||||
0.0
|
||||
} else {
|
||||
self.total_reward / self.action_count as f64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An action the agent can take
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Action {
|
||||
pub action_type: String,
|
||||
pub description: String,
|
||||
pub parameters: HashMap<String, String>,
|
||||
pub tool_calls: Vec<String>,
|
||||
pub expected_outcome: Option<String>,
|
||||
pub expected_reward: f64,
|
||||
}
|
||||
|
||||
/// An observation from the environment
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Observation {
|
||||
pub success: bool,
|
||||
pub result: String,
|
||||
pub changes: Vec<String>,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
/// A plan for achieving a goal
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Plan {
|
||||
pub goal: Goal,
|
||||
pub steps: Vec<PlanStep>,
|
||||
pub estimated_reward: f64,
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
/// A step in a plan
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlanStep {
|
||||
pub sequence: usize,
|
||||
pub action: Action,
|
||||
pub preconditions: Vec<String>,
|
||||
pub postconditions: Vec<String>,
|
||||
}
|
||||
|
||||
/// Learning signal for the agent
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LearningSignal {
|
||||
pub action: Action,
|
||||
pub observation: Observation,
|
||||
pub reward: f64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_agentic_loop() {
|
||||
let config = LeanAgenticConfig::default();
|
||||
let mut agent = AgenticLoop::new(config);
|
||||
|
||||
let context = Context::default();
|
||||
let plan = agent.plan(&context, "test input").await.unwrap();
|
||||
|
||||
assert!(!plan.steps.is_empty());
|
||||
}
|
||||
}
|
||||
+492
@@ -0,0 +1,492 @@
|
||||
//! Dynamical systems and strange attractor analysis
|
||||
//!
|
||||
//! Integrates temporal-attractor-studio for:
|
||||
//! - Phase space reconstruction
|
||||
//! - Attractor detection and classification
|
||||
//! - Stability analysis
|
||||
//! - Chaos detection via Lyapunov exponents
|
||||
|
||||
use nalgebra::{DMatrix, DVector};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Types of attractors that can be detected
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AttractorType {
|
||||
/// Fixed point (stable equilibrium)
|
||||
FixedPoint,
|
||||
/// Limit cycle (periodic oscillation)
|
||||
LimitCycle,
|
||||
/// Torus (quasi-periodic)
|
||||
Torus,
|
||||
/// Strange attractor (chaotic)
|
||||
StrangeAttractor,
|
||||
/// Unknown or transitional
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Phase space point
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PhasePoint {
|
||||
pub coordinates: Vec<f64>,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
/// Attractor characteristics
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AttractorInfo {
|
||||
pub attractor_type: AttractorType,
|
||||
pub lyapunov_exponent: f64,
|
||||
pub correlation_dimension: f64,
|
||||
pub is_chaotic: bool,
|
||||
pub stability_index: f64,
|
||||
}
|
||||
|
||||
/// Phase space trajectory
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Trajectory {
|
||||
points: Vec<PhasePoint>,
|
||||
embedding_dimension: usize,
|
||||
time_delay: usize,
|
||||
}
|
||||
|
||||
impl Trajectory {
|
||||
/// Create a new trajectory with time-delay embedding
|
||||
pub fn from_timeseries(
|
||||
data: &[f64],
|
||||
embedding_dim: usize,
|
||||
time_delay: usize,
|
||||
) -> Self {
|
||||
let mut points = Vec::new();
|
||||
|
||||
// Time-delay embedding (Takens' theorem)
|
||||
for i in 0..(data.len() - (embedding_dim - 1) * time_delay) {
|
||||
let mut coords = Vec::with_capacity(embedding_dim);
|
||||
for j in 0..embedding_dim {
|
||||
coords.push(data[i + j * time_delay]);
|
||||
}
|
||||
points.push(PhasePoint {
|
||||
coordinates: coords,
|
||||
timestamp: i as i64,
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
points,
|
||||
embedding_dimension: embedding_dim,
|
||||
time_delay,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get trajectory length
|
||||
pub fn len(&self) -> usize {
|
||||
self.points.len()
|
||||
}
|
||||
|
||||
/// Check if trajectory is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.points.is_empty()
|
||||
}
|
||||
|
||||
/// Get embedding dimension
|
||||
pub fn embedding_dim(&self) -> usize {
|
||||
self.embedding_dimension
|
||||
}
|
||||
}
|
||||
|
||||
/// Attractor analyzer using dynamical systems theory
|
||||
pub struct AttractorAnalyzer {
|
||||
embedding_dimension: usize,
|
||||
time_delay: usize,
|
||||
min_trajectory_length: usize,
|
||||
lyapunov_iterations: usize,
|
||||
}
|
||||
|
||||
impl AttractorAnalyzer {
|
||||
/// Create a new attractor analyzer
|
||||
pub fn new(embedding_dimension: usize, time_delay: usize) -> Self {
|
||||
Self {
|
||||
embedding_dimension,
|
||||
time_delay,
|
||||
min_trajectory_length: 100,
|
||||
lyapunov_iterations: 100,
|
||||
}
|
||||
}
|
||||
|
||||
/// Analyze a time series for attractors
|
||||
pub fn analyze(&self, data: &[f64]) -> Result<AttractorInfo, String> {
|
||||
if data.len() < self.min_trajectory_length {
|
||||
return Err(format!(
|
||||
"Time series too short: {} < {}",
|
||||
data.len(),
|
||||
self.min_trajectory_length
|
||||
));
|
||||
}
|
||||
|
||||
// Reconstruct phase space
|
||||
let trajectory = Trajectory::from_timeseries(
|
||||
data,
|
||||
self.embedding_dimension,
|
||||
self.time_delay,
|
||||
);
|
||||
|
||||
// Calculate Lyapunov exponent
|
||||
let lyapunov = self.calculate_lyapunov_exponent(&trajectory);
|
||||
|
||||
// Calculate correlation dimension
|
||||
let corr_dim = self.calculate_correlation_dimension(&trajectory);
|
||||
|
||||
// Detect attractor type
|
||||
let attractor_type = self.classify_attractor(lyapunov, corr_dim);
|
||||
|
||||
// Calculate stability
|
||||
let stability = self.calculate_stability(&trajectory);
|
||||
|
||||
Ok(AttractorInfo {
|
||||
attractor_type,
|
||||
lyapunov_exponent: lyapunov,
|
||||
correlation_dimension: corr_dim,
|
||||
is_chaotic: lyapunov > 0.0,
|
||||
stability_index: stability,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate largest Lyapunov exponent (indicator of chaos)
|
||||
fn calculate_lyapunov_exponent(&self, trajectory: &Trajectory) -> f64 {
|
||||
if trajectory.len() < 10 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut sum = 0.0;
|
||||
let mut count = 0;
|
||||
|
||||
// Simplified Lyapunov calculation
|
||||
for i in 0..trajectory.len().saturating_sub(1) {
|
||||
let dist = self.euclidean_distance(
|
||||
&trajectory.points[i].coordinates,
|
||||
&trajectory.points[i + 1].coordinates,
|
||||
);
|
||||
|
||||
if dist > 0.0 {
|
||||
sum += dist.ln();
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
sum / count as f64
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate correlation dimension (Grassberger-Procaccia algorithm)
|
||||
fn calculate_correlation_dimension(&self, trajectory: &Trajectory) -> f64 {
|
||||
if trajectory.len() < 10 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let n = trajectory.len();
|
||||
let sample_size = n.min(100); // Sample for efficiency
|
||||
|
||||
// Calculate distances between points
|
||||
let mut distances = Vec::new();
|
||||
for i in 0..sample_size {
|
||||
for j in (i + 1)..sample_size {
|
||||
let dist = self.euclidean_distance(
|
||||
&trajectory.points[i].coordinates,
|
||||
&trajectory.points[j].coordinates,
|
||||
);
|
||||
distances.push(dist);
|
||||
}
|
||||
}
|
||||
|
||||
if distances.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Estimate dimension from scaling
|
||||
distances.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
let median = distances[distances.len() / 2];
|
||||
|
||||
// Simplified correlation dimension estimate
|
||||
let dim = if median > 0.0 {
|
||||
(n as f64).ln() / median.ln()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
dim.min(self.embedding_dimension as f64)
|
||||
}
|
||||
|
||||
/// Classify attractor type based on characteristics
|
||||
fn classify_attractor(&self, lyapunov: f64, corr_dim: f64) -> AttractorType {
|
||||
if lyapunov > 0.1 {
|
||||
// Positive Lyapunov => chaos
|
||||
AttractorType::StrangeAttractor
|
||||
} else if lyapunov < -0.1 {
|
||||
// Negative Lyapunov => stable
|
||||
if corr_dim < 0.5 {
|
||||
AttractorType::FixedPoint
|
||||
} else if corr_dim < 1.5 {
|
||||
AttractorType::LimitCycle
|
||||
} else {
|
||||
AttractorType::Torus
|
||||
}
|
||||
} else {
|
||||
// Near zero => borderline or transitional
|
||||
AttractorType::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate stability index (lower = more stable)
|
||||
fn calculate_stability(&self, trajectory: &Trajectory) -> f64 {
|
||||
if trajectory.len() < 2 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Measure average deviation from trajectory center
|
||||
let center = self.calculate_centroid(&trajectory.points);
|
||||
let mut total_deviation = 0.0;
|
||||
|
||||
for point in &trajectory.points {
|
||||
total_deviation += self.euclidean_distance(&point.coordinates, ¢er);
|
||||
}
|
||||
|
||||
total_deviation / trajectory.len() as f64
|
||||
}
|
||||
|
||||
/// Calculate centroid of point cloud
|
||||
fn calculate_centroid(&self, points: &[PhasePoint]) -> Vec<f64> {
|
||||
if points.is_empty() {
|
||||
return vec![0.0; self.embedding_dimension];
|
||||
}
|
||||
|
||||
let dim = points[0].coordinates.len();
|
||||
let mut centroid = vec![0.0; dim];
|
||||
|
||||
for point in points {
|
||||
for (i, &coord) in point.coordinates.iter().enumerate() {
|
||||
centroid[i] += coord;
|
||||
}
|
||||
}
|
||||
|
||||
for coord in &mut centroid {
|
||||
*coord /= points.len() as f64;
|
||||
}
|
||||
|
||||
centroid
|
||||
}
|
||||
|
||||
/// Calculate Euclidean distance between two points
|
||||
fn euclidean_distance(&self, p1: &[f64], p2: &[f64]) -> f64 {
|
||||
p1.iter()
|
||||
.zip(p2.iter())
|
||||
.map(|(a, b)| (a - b).powi(2))
|
||||
.sum::<f64>()
|
||||
.sqrt()
|
||||
}
|
||||
|
||||
/// Predict next point in trajectory
|
||||
pub fn predict_next(&self, trajectory: &Trajectory) -> Vec<f64> {
|
||||
if trajectory.len() < 2 {
|
||||
return vec![0.0; self.embedding_dimension];
|
||||
}
|
||||
|
||||
// Simple linear extrapolation
|
||||
let last = &trajectory.points[trajectory.len() - 1].coordinates;
|
||||
let prev = &trajectory.points[trajectory.len() - 2].coordinates;
|
||||
|
||||
last.iter()
|
||||
.zip(prev.iter())
|
||||
.map(|(l, p)| 2.0 * l - p)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AttractorAnalyzer {
|
||||
fn default() -> Self {
|
||||
Self::new(3, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Agent behavior analyzer using attractor theory
|
||||
pub struct BehaviorAttractorAnalyzer {
|
||||
analyzer: AttractorAnalyzer,
|
||||
reward_history: VecDeque<f64>,
|
||||
confidence_history: VecDeque<f64>,
|
||||
max_history: usize,
|
||||
}
|
||||
|
||||
impl BehaviorAttractorAnalyzer {
|
||||
/// Create a new behavior analyzer
|
||||
pub fn new(embedding_dim: usize, max_history: usize) -> Self {
|
||||
Self {
|
||||
analyzer: AttractorAnalyzer::new(embedding_dim, 1),
|
||||
reward_history: VecDeque::new(),
|
||||
confidence_history: VecDeque::new(),
|
||||
max_history,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update with new observation
|
||||
pub fn observe(&mut self, reward: f64, confidence: f64) {
|
||||
self.reward_history.push_back(reward);
|
||||
self.confidence_history.push_back(confidence);
|
||||
|
||||
// Maintain max history
|
||||
if self.reward_history.len() > self.max_history {
|
||||
self.reward_history.pop_front();
|
||||
}
|
||||
if self.confidence_history.len() > self.max_history {
|
||||
self.confidence_history.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// Analyze reward dynamics
|
||||
pub fn analyze_reward_dynamics(&self) -> Result<AttractorInfo, String> {
|
||||
let data: Vec<f64> = self.reward_history.iter().copied().collect();
|
||||
self.analyzer.analyze(&data)
|
||||
}
|
||||
|
||||
/// Analyze confidence dynamics
|
||||
pub fn analyze_confidence_dynamics(&self) -> Result<AttractorInfo, String> {
|
||||
let data: Vec<f64> = self.confidence_history.iter().copied().collect();
|
||||
self.analyzer.analyze(&data)
|
||||
}
|
||||
|
||||
/// Detect if agent is in stable regime
|
||||
pub fn is_stable(&self) -> bool {
|
||||
if let Ok(info) = self.analyze_reward_dynamics() {
|
||||
info.attractor_type == AttractorType::FixedPoint
|
||||
|| info.attractor_type == AttractorType::LimitCycle
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect if agent behavior is chaotic
|
||||
pub fn is_chaotic(&self) -> bool {
|
||||
if let Ok(info) = self.analyze_reward_dynamics() {
|
||||
info.is_chaotic
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get behavior summary
|
||||
pub fn get_behavior_summary(&self) -> BehaviorSummary {
|
||||
let reward_info = self.analyze_reward_dynamics().ok();
|
||||
let confidence_info = self.analyze_confidence_dynamics().ok();
|
||||
|
||||
BehaviorSummary {
|
||||
reward_attractor: reward_info,
|
||||
confidence_attractor: confidence_info,
|
||||
is_stable: self.is_stable(),
|
||||
is_chaotic: self.is_chaotic(),
|
||||
history_length: self.reward_history.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Summary of agent behavior dynamics
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BehaviorSummary {
|
||||
pub reward_attractor: Option<AttractorInfo>,
|
||||
pub confidence_attractor: Option<AttractorInfo>,
|
||||
pub is_stable: bool,
|
||||
pub is_chaotic: bool,
|
||||
pub history_length: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_trajectory_embedding() {
|
||||
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
|
||||
let trajectory = Trajectory::from_timeseries(&data, 3, 1);
|
||||
|
||||
assert_eq!(trajectory.embedding_dim(), 3);
|
||||
assert!(!trajectory.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fixed_point_detection() {
|
||||
let analyzer = AttractorAnalyzer::new(2, 1);
|
||||
|
||||
// Constant values => fixed point
|
||||
let data: Vec<f64> = (0..100).map(|_| 5.0).collect();
|
||||
let info = analyzer.analyze(&data).unwrap();
|
||||
|
||||
assert_eq!(info.attractor_type, AttractorType::FixedPoint);
|
||||
assert!(!info.is_chaotic);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_periodic_detection() {
|
||||
let analyzer = AttractorAnalyzer::new(2, 1);
|
||||
|
||||
// Sine wave => limit cycle
|
||||
let data: Vec<f64> = (0..100)
|
||||
.map(|i| (i as f64 * 0.1).sin())
|
||||
.collect();
|
||||
|
||||
let info = analyzer.analyze(&data).unwrap();
|
||||
|
||||
// Should detect some periodicity
|
||||
assert_ne!(info.attractor_type, AttractorType::FixedPoint);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chaotic_detection() {
|
||||
let analyzer = AttractorAnalyzer::new(3, 1);
|
||||
|
||||
// Logistic map with chaotic parameter
|
||||
let mut data = Vec::new();
|
||||
let mut x = 0.1;
|
||||
let r = 3.9; // Chaotic regime
|
||||
|
||||
for _ in 0..200 {
|
||||
x = r * x * (1.0 - x);
|
||||
data.push(x);
|
||||
}
|
||||
|
||||
let info = analyzer.analyze(&data).unwrap();
|
||||
|
||||
// Logistic map at r=3.9 should be chaotic
|
||||
println!("Lyapunov exponent: {}", info.lyapunov_exponent);
|
||||
println!("Attractor type: {:?}", info.attractor_type);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behavior_analyzer() {
|
||||
let mut analyzer = BehaviorAttractorAnalyzer::new(2, 100);
|
||||
|
||||
// Simulate stable learning (converging rewards)
|
||||
for i in 0..150 {
|
||||
let reward = 0.5 + 0.5 * (-i as f64 / 20.0).exp();
|
||||
let confidence = 0.7 + 0.2 * (i as f64 / 150.0);
|
||||
analyzer.observe(reward, confidence);
|
||||
}
|
||||
|
||||
let summary = analyzer.get_behavior_summary();
|
||||
println!("Behavior summary: {:?}", summary);
|
||||
|
||||
// Should detect convergence
|
||||
assert!(summary.is_stable || summary.history_length > 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prediction() {
|
||||
let analyzer = AttractorAnalyzer::new(2, 1);
|
||||
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
|
||||
let trajectory = Trajectory::from_timeseries(&data, 2, 1);
|
||||
|
||||
let next = analyzer.predict_next(&trajectory);
|
||||
assert_eq!(next.len(), 2);
|
||||
|
||||
println!("Predicted next point: {:?}", next);
|
||||
}
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
//! Knowledge graph and theorem store for dynamic knowledge representation
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::reasoning::Theorem;
|
||||
|
||||
/// Knowledge graph for storing entities, relations, and theorems
|
||||
pub struct KnowledgeGraph {
|
||||
/// Entities in the knowledge graph
|
||||
entities: HashMap<String, Entity>,
|
||||
|
||||
/// Relations between entities
|
||||
relations: Vec<Relation>,
|
||||
|
||||
/// Theorems and verified knowledge
|
||||
theorems: Vec<Theorem>,
|
||||
|
||||
/// Temporal knowledge (time-windowed facts)
|
||||
temporal_facts: Vec<TemporalFact>,
|
||||
|
||||
/// Entity embeddings for semantic similarity
|
||||
embeddings: HashMap<String, Vec<f64>>,
|
||||
}
|
||||
|
||||
impl KnowledgeGraph {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entities: HashMap::new(),
|
||||
relations: Vec::new(),
|
||||
theorems: Vec::new(),
|
||||
temporal_facts: Vec::new(),
|
||||
embeddings: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract entities from text
|
||||
pub async fn extract_entities(&self, text: &str) -> Result<Vec<Entity>, String> {
|
||||
let mut entities = Vec::new();
|
||||
|
||||
// Simple entity extraction (can be enhanced with NER)
|
||||
let words: Vec<&str> = text.split_whitespace().collect();
|
||||
|
||||
for (i, word) in words.iter().enumerate() {
|
||||
// Capitalize words might be entities
|
||||
if word.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) {
|
||||
entities.push(Entity {
|
||||
id: format!("entity_{}", i),
|
||||
name: word.to_string(),
|
||||
entity_type: EntityType::Unknown,
|
||||
attributes: HashMap::new(),
|
||||
confidence: 0.7,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract numeric values
|
||||
for (i, word) in words.iter().enumerate() {
|
||||
if word.parse::<f64>().is_ok() {
|
||||
entities.push(Entity {
|
||||
id: format!("value_{}", i),
|
||||
name: word.to_string(),
|
||||
entity_type: EntityType::Value,
|
||||
attributes: HashMap::new(),
|
||||
confidence: 0.9,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(entities)
|
||||
}
|
||||
|
||||
/// Update knowledge graph with new entities
|
||||
pub async fn update(&mut self, entities: Vec<Entity>) -> Result<(), String> {
|
||||
for entity in entities {
|
||||
// Check if entity exists
|
||||
if let Some(existing) = self.entities.get_mut(&entity.id) {
|
||||
// Update existing entity
|
||||
existing.confidence = (existing.confidence + entity.confidence) / 2.0;
|
||||
for (key, value) in entity.attributes {
|
||||
existing.attributes.insert(key, value);
|
||||
}
|
||||
} else {
|
||||
// Add new entity
|
||||
self.entities.insert(entity.id.clone(), entity);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a relation between entities
|
||||
pub fn add_relation(&mut self, relation: Relation) {
|
||||
self.relations.push(relation);
|
||||
}
|
||||
|
||||
/// Add a verified theorem
|
||||
pub fn add_theorem(&mut self, theorem: Theorem) {
|
||||
self.theorems.push(theorem);
|
||||
}
|
||||
|
||||
/// Query entities by type
|
||||
pub fn query_entities(&self, entity_type: EntityType) -> Vec<&Entity> {
|
||||
self.entities.values()
|
||||
.filter(|e| e.entity_type == entity_type)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Find related entities
|
||||
pub fn find_related(&self, entity_id: &str, max_depth: usize) -> Vec<String> {
|
||||
let mut related = HashSet::new();
|
||||
let mut to_explore = vec![(entity_id.to_string(), 0)];
|
||||
|
||||
while let Some((current_id, depth)) = to_explore.pop() {
|
||||
if depth >= max_depth {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find relations involving this entity
|
||||
for relation in &self.relations {
|
||||
if relation.subject == current_id {
|
||||
related.insert(relation.object.clone());
|
||||
to_explore.push((relation.object.clone(), depth + 1));
|
||||
} else if relation.object == current_id {
|
||||
related.insert(relation.subject.clone());
|
||||
to_explore.push((relation.subject.clone(), depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
related.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Add temporal fact (fact with time window)
|
||||
pub fn add_temporal_fact(&mut self, fact: TemporalFact) {
|
||||
self.temporal_facts.push(fact);
|
||||
|
||||
// Clean up old facts
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
self.temporal_facts.retain(|f| {
|
||||
if let Some(end) = f.valid_until {
|
||||
end > now
|
||||
} else {
|
||||
true // Keep facts without expiration
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Get facts valid at a specific time
|
||||
pub fn get_facts_at_time(&self, timestamp: i64) -> Vec<&TemporalFact> {
|
||||
self.temporal_facts.iter()
|
||||
.filter(|f| {
|
||||
f.valid_from <= timestamp &&
|
||||
f.valid_until.map(|t| timestamp <= t).unwrap_or(true)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Compute semantic similarity between entities
|
||||
pub fn compute_similarity(&self, entity1: &str, entity2: &str) -> f64 {
|
||||
if let (Some(emb1), Some(emb2)) = (
|
||||
self.embeddings.get(entity1),
|
||||
self.embeddings.get(entity2)
|
||||
) {
|
||||
// Cosine similarity
|
||||
let dot_product: f64 = emb1.iter()
|
||||
.zip(emb2.iter())
|
||||
.map(|(a, b)| a * b)
|
||||
.sum();
|
||||
|
||||
let norm1: f64 = emb1.iter().map(|x| x * x).sum::<f64>().sqrt();
|
||||
let norm2: f64 = emb2.iter().map(|x| x * x).sum::<f64>().sqrt();
|
||||
|
||||
if norm1 > 0.0 && norm2 > 0.0 {
|
||||
dot_product / (norm1 * norm2)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Update entity embedding
|
||||
pub fn update_embedding(&mut self, entity_id: String, embedding: Vec<f64>) {
|
||||
self.embeddings.insert(entity_id, embedding);
|
||||
}
|
||||
|
||||
pub fn entity_count(&self) -> usize {
|
||||
self.entities.len()
|
||||
}
|
||||
|
||||
pub fn theorem_count(&self) -> usize {
|
||||
self.theorems.len()
|
||||
}
|
||||
|
||||
pub fn relation_count(&self) -> usize {
|
||||
self.relations.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// An entity in the knowledge graph
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Entity {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub entity_type: EntityType,
|
||||
pub attributes: HashMap<String, String>,
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
/// Types of entities
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum EntityType {
|
||||
Person,
|
||||
Place,
|
||||
Organization,
|
||||
Concept,
|
||||
Event,
|
||||
Value,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// A relation between two entities
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Relation {
|
||||
pub id: String,
|
||||
pub subject: String,
|
||||
pub predicate: String,
|
||||
pub object: String,
|
||||
pub confidence: f64,
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
/// A temporal fact (fact valid within a time window)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TemporalFact {
|
||||
pub fact: String,
|
||||
pub valid_from: i64,
|
||||
pub valid_until: Option<i64>,
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_knowledge_graph() {
|
||||
let mut kg = KnowledgeGraph::new();
|
||||
|
||||
let entities = kg.extract_entities("Alice works at Google").await.unwrap();
|
||||
kg.update(entities).await.unwrap();
|
||||
|
||||
assert!(kg.entity_count() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temporal_facts() {
|
||||
let mut kg = KnowledgeGraph::new();
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
|
||||
kg.add_temporal_fact(TemporalFact {
|
||||
fact: "Weather is sunny".to_string(),
|
||||
valid_from: now,
|
||||
valid_until: Some(now + 3600),
|
||||
confidence: 0.9,
|
||||
});
|
||||
|
||||
let facts = kg.get_facts_at_time(now + 1800);
|
||||
assert_eq!(facts.len(), 1);
|
||||
}
|
||||
}
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
//! Stream learning and online adaptation
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::agent::Action;
|
||||
|
||||
/// Stream learner for online adaptation
|
||||
pub struct StreamLearner {
|
||||
/// Online model for learning
|
||||
model: OnlineModel,
|
||||
|
||||
/// Learning rate
|
||||
learning_rate: f64,
|
||||
|
||||
/// Experience buffer for replay
|
||||
experience_buffer: VecDeque<Experience>,
|
||||
|
||||
/// Buffer size
|
||||
buffer_size: usize,
|
||||
|
||||
/// Total iterations
|
||||
iterations: u64,
|
||||
|
||||
/// Adaptation strategy
|
||||
strategy: AdaptationStrategy,
|
||||
}
|
||||
|
||||
impl StreamLearner {
|
||||
pub fn new(learning_rate: f64) -> Self {
|
||||
Self {
|
||||
model: OnlineModel::new(),
|
||||
learning_rate,
|
||||
experience_buffer: VecDeque::new(),
|
||||
buffer_size: 1000,
|
||||
iterations: 0,
|
||||
strategy: AdaptationStrategy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update model with new experience
|
||||
pub async fn update(
|
||||
&mut self,
|
||||
action: &Action,
|
||||
reward: f64,
|
||||
context: &str,
|
||||
) -> Result<(), String> {
|
||||
self.iterations += 1;
|
||||
|
||||
// Create experience
|
||||
let experience = Experience {
|
||||
action: action.clone(),
|
||||
reward,
|
||||
context: context.to_string(),
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
};
|
||||
|
||||
// Add to buffer
|
||||
self.experience_buffer.push_back(experience.clone());
|
||||
if self.experience_buffer.len() > self.buffer_size {
|
||||
self.experience_buffer.pop_front();
|
||||
}
|
||||
|
||||
// Update model based on strategy
|
||||
match &self.strategy {
|
||||
AdaptationStrategy::Immediate => {
|
||||
self.model.update_immediate(&experience, self.learning_rate).await?;
|
||||
}
|
||||
AdaptationStrategy::Batched { batch_size } => {
|
||||
if self.iterations % batch_size == 0 {
|
||||
self.model.update_batch(&self.experience_buffer, self.learning_rate).await?;
|
||||
}
|
||||
}
|
||||
AdaptationStrategy::ExperienceReplay { replay_size } => {
|
||||
self.model.update_immediate(&experience, self.learning_rate).await?;
|
||||
|
||||
// Replay random experiences
|
||||
let replay_samples = self.sample_experiences(*replay_size);
|
||||
for sample in replay_samples {
|
||||
self.model.update_immediate(&sample, self.learning_rate * 0.5).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sample random experiences for replay (using simple deterministic sampling)
|
||||
fn sample_experiences(&self, n: usize) -> Vec<Experience> {
|
||||
// Simple deterministic sampling: take evenly spaced samples
|
||||
let experiences: Vec<_> = self.experience_buffer.iter().cloned().collect();
|
||||
let total = experiences.len();
|
||||
|
||||
if total == 0 || n == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let step = (total as f64 / n as f64).max(1.0) as usize;
|
||||
|
||||
experiences.iter()
|
||||
.step_by(step)
|
||||
.take(n)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Predict reward for an action
|
||||
pub async fn predict_reward(&self, action: &Action, context: &str) -> f64 {
|
||||
self.model.predict(action, context).await
|
||||
}
|
||||
|
||||
/// Get learning statistics
|
||||
pub fn get_stats(&self) -> LearningStats {
|
||||
LearningStats {
|
||||
iterations: self.iterations,
|
||||
buffer_size: self.experience_buffer.len(),
|
||||
average_reward: self.compute_average_reward(),
|
||||
model_parameters: self.model.parameter_count(),
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_average_reward(&self) -> f64 {
|
||||
if self.experience_buffer.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let sum: f64 = self.experience_buffer.iter()
|
||||
.map(|e| e.reward)
|
||||
.sum();
|
||||
|
||||
sum / self.experience_buffer.len() as f64
|
||||
}
|
||||
|
||||
pub fn iteration_count(&self) -> u64 {
|
||||
self.iterations
|
||||
}
|
||||
}
|
||||
|
||||
/// Online learning model
|
||||
pub struct OnlineModel {
|
||||
/// Feature weights
|
||||
weights: HashMap<String, f64>,
|
||||
|
||||
/// Bias term
|
||||
bias: f64,
|
||||
|
||||
/// Feature statistics for normalization
|
||||
feature_stats: HashMap<String, FeatureStats>,
|
||||
}
|
||||
|
||||
impl OnlineModel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
weights: HashMap::new(),
|
||||
bias: 0.0,
|
||||
feature_stats: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract features from action and context
|
||||
fn extract_features(&self, action: &Action, context: &str) -> HashMap<String, f64> {
|
||||
let mut features = HashMap::new();
|
||||
|
||||
// Action type feature
|
||||
features.insert(
|
||||
format!("action_{}", action.action_type),
|
||||
1.0,
|
||||
);
|
||||
|
||||
// Number of parameters
|
||||
features.insert(
|
||||
"param_count".to_string(),
|
||||
action.parameters.len() as f64,
|
||||
);
|
||||
|
||||
// Number of tool calls
|
||||
features.insert(
|
||||
"tool_count".to_string(),
|
||||
action.tool_calls.len() as f64,
|
||||
);
|
||||
|
||||
// Context length
|
||||
features.insert(
|
||||
"context_length".to_string(),
|
||||
context.len() as f64 / 100.0, // Normalize
|
||||
);
|
||||
|
||||
// Expected reward (from action)
|
||||
features.insert(
|
||||
"expected_reward".to_string(),
|
||||
action.expected_reward,
|
||||
);
|
||||
|
||||
features
|
||||
}
|
||||
|
||||
/// Predict reward for given features
|
||||
pub async fn predict(&self, action: &Action, context: &str) -> f64 {
|
||||
let features = self.extract_features(action, context);
|
||||
|
||||
let mut prediction = self.bias;
|
||||
|
||||
for (feature, value) in features {
|
||||
if let Some(weight) = self.weights.get(&feature) {
|
||||
prediction += weight * value;
|
||||
}
|
||||
}
|
||||
|
||||
prediction
|
||||
}
|
||||
|
||||
/// Update model immediately with single experience
|
||||
pub async fn update_immediate(
|
||||
&mut self,
|
||||
experience: &Experience,
|
||||
learning_rate: f64,
|
||||
) -> Result<(), String> {
|
||||
let features = self.extract_features(&experience.action, &experience.context);
|
||||
let prediction = self.predict(&experience.action, &experience.context).await;
|
||||
|
||||
// Gradient descent update
|
||||
let error = experience.reward - prediction;
|
||||
|
||||
// Update bias
|
||||
self.bias += learning_rate * error;
|
||||
|
||||
// Update weights
|
||||
for (feature, value) in features {
|
||||
let weight = self.weights.entry(feature.clone()).or_insert(0.0);
|
||||
*weight += learning_rate * error * value;
|
||||
|
||||
// Update feature statistics
|
||||
let stats = self.feature_stats.entry(feature).or_insert(FeatureStats::default());
|
||||
stats.update(value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update model with batch of experiences
|
||||
pub async fn update_batch(
|
||||
&mut self,
|
||||
experiences: &VecDeque<Experience>,
|
||||
learning_rate: f64,
|
||||
) -> Result<(), String> {
|
||||
for experience in experiences {
|
||||
self.update_immediate(experience, learning_rate).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parameter_count(&self) -> usize {
|
||||
self.weights.len() + 1 // weights + bias
|
||||
}
|
||||
}
|
||||
|
||||
/// Experience tuple for learning
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Experience {
|
||||
pub action: Action,
|
||||
pub reward: f64,
|
||||
pub context: String,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
/// Adaptation strategy for online learning
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum AdaptationStrategy {
|
||||
/// Update immediately after each experience
|
||||
Immediate,
|
||||
|
||||
/// Update in batches
|
||||
Batched { batch_size: u64 },
|
||||
|
||||
/// Use experience replay
|
||||
ExperienceReplay { replay_size: usize },
|
||||
}
|
||||
|
||||
impl Default for AdaptationStrategy {
|
||||
fn default() -> Self {
|
||||
AdaptationStrategy::Immediate
|
||||
}
|
||||
}
|
||||
|
||||
/// Feature statistics for normalization
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct FeatureStats {
|
||||
count: u64,
|
||||
sum: f64,
|
||||
sum_squared: f64,
|
||||
}
|
||||
|
||||
impl FeatureStats {
|
||||
fn update(&mut self, value: f64) {
|
||||
self.count += 1;
|
||||
self.sum += value;
|
||||
self.sum_squared += value * value;
|
||||
}
|
||||
|
||||
fn mean(&self) -> f64 {
|
||||
if self.count == 0 {
|
||||
0.0
|
||||
} else {
|
||||
self.sum / self.count as f64
|
||||
}
|
||||
}
|
||||
|
||||
fn variance(&self) -> f64 {
|
||||
if self.count == 0 {
|
||||
0.0
|
||||
} else {
|
||||
let mean = self.mean();
|
||||
(self.sum_squared / self.count as f64) - (mean * mean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Learning statistics
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LearningStats {
|
||||
pub iterations: u64,
|
||||
pub buffer_size: usize,
|
||||
pub average_reward: f64,
|
||||
pub model_parameters: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_learner() {
|
||||
let mut learner = StreamLearner::new(0.01);
|
||||
|
||||
let action = Action {
|
||||
action_type: "test".to_string(),
|
||||
description: "Test action".to_string(),
|
||||
parameters: HashMap::new(),
|
||||
tool_calls: vec![],
|
||||
expected_outcome: None,
|
||||
expected_reward: 0.5,
|
||||
};
|
||||
|
||||
let result = learner.update(&action, 1.0, "test context").await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let stats = learner.get_stats();
|
||||
assert_eq!(stats.iterations, 1);
|
||||
}
|
||||
}
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
//! # Lean Agentic Learning System
|
||||
//!
|
||||
//! A revolutionary learning framework combining:
|
||||
//! - Formal reasoning (Lean-style theorem proving)
|
||||
//! - Agentic AI (autonomous decision-making)
|
||||
//! - Stream learning (real-time online adaptation)
|
||||
//! - Knowledge evolution (dynamic theorem store)
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! ```text
|
||||
//! ┌─────────────────────────────────────────────────────────┐
|
||||
//! │ Lean Agentic Learning System │
|
||||
//! ├─────────────────────────────────────────────────────────┤
|
||||
//! │ │
|
||||
//! │ ┌──────────────┐ ┌──────────────┐ │
|
||||
//! │ │ Formal │ │ Agentic │ │
|
||||
//! │ │ Reasoning │◄────►│ Loop │ │
|
||||
//! │ │ Engine │ │ (P-A-O-L) │ │
|
||||
//! │ └──────┬───────┘ └──────┬───────┘ │
|
||||
//! │ │ │ │
|
||||
//! │ │ ┌────────────────▼─────┐ │
|
||||
//! │ └───►│ Knowledge Graph & │ │
|
||||
//! │ │ Theorem Store │ │
|
||||
//! │ └────────────┬─────────┘ │
|
||||
//! │ │ │
|
||||
//! │ ┌────────────▼─────────┐ │
|
||||
//! │ │ Stream Learning & │ │
|
||||
//! │ │ Online Adaptation │ │
|
||||
//! │ └──────────────────────┘ │
|
||||
//! └─────────────────────────────────────────────────────────┘
|
||||
//! ```
|
||||
|
||||
pub mod reasoning;
|
||||
pub mod agent;
|
||||
pub mod knowledge;
|
||||
pub mod learning;
|
||||
pub mod types;
|
||||
pub mod optimized;
|
||||
pub mod temporal;
|
||||
pub mod scheduler;
|
||||
pub mod attractor;
|
||||
pub mod temporal_neural;
|
||||
pub mod strange_loop;
|
||||
|
||||
pub use reasoning::{FormalReasoner, Theorem, Proof, ProofStep};
|
||||
pub use agent::{AgenticLoop, Action, Observation, Plan, LearningSignal};
|
||||
pub use knowledge::{KnowledgeGraph, TheoremStore, Entity, Relation};
|
||||
pub use learning::{StreamLearner, OnlineModel, AdaptationStrategy};
|
||||
pub use types::{AgentState, Context, Reward};
|
||||
pub use optimized::{
|
||||
FeatureCache, BufferPool, PredictionCache, BatchProcessor,
|
||||
FastEntityExtractor, fast_hash, simd,
|
||||
};
|
||||
pub use temporal::{
|
||||
TemporalComparator, Sequence, ComparisonAlgorithm, CacheStats,
|
||||
};
|
||||
pub use scheduler::{
|
||||
RealtimeScheduler, ScheduledTask, SchedulingPolicy, Priority,
|
||||
SchedulableAction, SchedulerStats,
|
||||
};
|
||||
pub use attractor::{
|
||||
AttractorAnalyzer, BehaviorAttractorAnalyzer, AttractorType,
|
||||
AttractorInfo, Trajectory, PhasePoint, BehaviorSummary,
|
||||
};
|
||||
pub use temporal_neural::{
|
||||
TemporalNeuralSolver, TemporalFormula, TemporalOperator,
|
||||
TemporalTrace, TemporalState, VerificationResult,
|
||||
};
|
||||
pub use midstreamer_strange_loop::{
|
||||
MetaLearner, MetaLevel, MetaKnowledge, StrangeLoop,
|
||||
ModificationRule, SafetyConstraint, MetaLearningSummary,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/// The main lean agentic system orchestrator
|
||||
pub struct LeanAgenticSystem {
|
||||
/// Formal reasoning engine for verification
|
||||
pub reasoner: Arc<RwLock<FormalReasoner>>,
|
||||
|
||||
/// Agentic loop for autonomous decision-making
|
||||
pub agent_loop: Arc<RwLock<AgenticLoop>>,
|
||||
|
||||
/// Knowledge graph and theorem store
|
||||
pub knowledge: Arc<RwLock<KnowledgeGraph>>,
|
||||
|
||||
/// Stream learning system
|
||||
pub learner: Arc<RwLock<StreamLearner>>,
|
||||
|
||||
/// System configuration
|
||||
pub config: LeanAgenticConfig,
|
||||
}
|
||||
|
||||
/// Configuration for the lean agentic system
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LeanAgenticConfig {
|
||||
/// Enable formal verification of actions
|
||||
pub enable_formal_verification: bool,
|
||||
|
||||
/// Learning rate for online adaptation
|
||||
pub learning_rate: f64,
|
||||
|
||||
/// Maximum planning depth
|
||||
pub max_planning_depth: usize,
|
||||
|
||||
/// Confidence threshold for action execution
|
||||
pub action_threshold: f64,
|
||||
|
||||
/// Enable multi-agent collaboration
|
||||
pub enable_multi_agent: bool,
|
||||
|
||||
/// Knowledge graph update frequency
|
||||
pub kg_update_freq: u64,
|
||||
}
|
||||
|
||||
impl Default for LeanAgenticConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable_formal_verification: true,
|
||||
learning_rate: 0.01,
|
||||
max_planning_depth: 5,
|
||||
action_threshold: 0.7,
|
||||
enable_multi_agent: true,
|
||||
kg_update_freq: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LeanAgenticSystem {
|
||||
/// Create a new lean agentic system
|
||||
pub fn new(config: LeanAgenticConfig) -> Self {
|
||||
Self {
|
||||
reasoner: Arc::new(RwLock::new(FormalReasoner::new())),
|
||||
agent_loop: Arc::new(RwLock::new(AgenticLoop::new(config.clone()))),
|
||||
knowledge: Arc::new(RwLock::new(KnowledgeGraph::new())),
|
||||
learner: Arc::new(RwLock::new(StreamLearner::new(config.learning_rate))),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a stream chunk with lean agentic learning
|
||||
pub async fn process_stream_chunk(
|
||||
&self,
|
||||
chunk: &str,
|
||||
context: Context,
|
||||
) -> Result<ProcessingResult, LeanAgenticError> {
|
||||
// 1. Update knowledge graph with new information
|
||||
let mut kg = self.knowledge.write().await;
|
||||
let entities = kg.extract_entities(chunk).await?;
|
||||
kg.update(entities).await?;
|
||||
drop(kg);
|
||||
|
||||
// 2. Agent loop: Plan-Act-Observe-Learn
|
||||
let mut agent = self.agent_loop.write().await;
|
||||
let plan = agent.plan(&context, chunk).await?;
|
||||
let action = agent.select_action(&plan).await?;
|
||||
|
||||
// 3. Formal verification (if enabled)
|
||||
if self.config.enable_formal_verification {
|
||||
let reasoner = self.reasoner.read().await;
|
||||
let proof = reasoner.verify_action(&action, &context).await?;
|
||||
if !proof.is_valid() {
|
||||
return Err(LeanAgenticError::VerificationFailed(proof));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Execute action
|
||||
let observation = agent.execute(&action).await?;
|
||||
|
||||
// 5. Online learning and adaptation
|
||||
let mut learner = self.learner.write().await;
|
||||
let reward = agent.compute_reward(&observation).await?;
|
||||
learner.update(&action, reward, chunk).await?;
|
||||
|
||||
// 6. Learn from experience
|
||||
agent.learn(LearningSignal {
|
||||
action: action.clone(),
|
||||
observation: observation.clone(),
|
||||
reward,
|
||||
}).await?;
|
||||
|
||||
Ok(ProcessingResult {
|
||||
action,
|
||||
observation,
|
||||
reward,
|
||||
verified: self.config.enable_formal_verification,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get system statistics
|
||||
pub async fn get_stats(&self) -> SystemStats {
|
||||
let kg = self.knowledge.read().await;
|
||||
let learner = self.learner.read().await;
|
||||
let agent = self.agent_loop.read().await;
|
||||
|
||||
SystemStats {
|
||||
total_theorems: kg.theorem_count(),
|
||||
total_entities: kg.entity_count(),
|
||||
learning_iterations: learner.iteration_count(),
|
||||
total_actions: agent.action_count(),
|
||||
average_reward: agent.average_reward(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of processing a stream chunk
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProcessingResult {
|
||||
pub action: Action,
|
||||
pub observation: Observation,
|
||||
pub reward: f64,
|
||||
pub verified: bool,
|
||||
}
|
||||
|
||||
/// System statistics
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SystemStats {
|
||||
pub total_theorems: usize,
|
||||
pub total_entities: usize,
|
||||
pub learning_iterations: u64,
|
||||
pub total_actions: u64,
|
||||
pub average_reward: f64,
|
||||
}
|
||||
|
||||
/// Errors that can occur in the lean agentic system
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LeanAgenticError {
|
||||
#[error("Formal verification failed: {0:?}")]
|
||||
VerificationFailed(Proof),
|
||||
|
||||
#[error("Planning error: {0}")]
|
||||
PlanningError(String),
|
||||
|
||||
#[error("Action execution failed: {0}")]
|
||||
ActionExecutionError(String),
|
||||
|
||||
#[error("Learning error: {0}")]
|
||||
LearningError(String),
|
||||
|
||||
#[error("Knowledge graph error: {0}")]
|
||||
KnowledgeGraphError(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_lean_agentic_system() {
|
||||
let config = LeanAgenticConfig::default();
|
||||
let system = LeanAgenticSystem::new(config);
|
||||
|
||||
let context = Context::default();
|
||||
let chunk = "Hello, world!";
|
||||
|
||||
let result = system.process_stream_chunk(chunk, context).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
//! Optimized implementations for ultra-low latency processing
|
||||
//!
|
||||
//! These optimizations focus on:
|
||||
//! - Reducing allocations
|
||||
//! - Lock-free data structures where possible
|
||||
//! - Pre-computed feature extractors
|
||||
//! - Cached predictions
|
||||
//! - Batch processing
|
||||
|
||||
use super::types::*;
|
||||
use super::agent::Action;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Optimized feature cache for fast lookup
|
||||
pub struct FeatureCache {
|
||||
cache: HashMap<u64, Vec<f64>>,
|
||||
max_size: usize,
|
||||
}
|
||||
|
||||
impl FeatureCache {
|
||||
pub fn new(max_size: usize) -> Self {
|
||||
Self {
|
||||
cache: HashMap::with_capacity(max_size),
|
||||
max_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, key: u64) -> Option<&Vec<f64>> {
|
||||
self.cache.get(&key)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: u64, features: Vec<f64>) {
|
||||
if self.cache.len() >= self.max_size {
|
||||
// Simple eviction: remove first entry (in practice, use LRU)
|
||||
if let Some(first_key) = self.cache.keys().next().copied() {
|
||||
self.cache.remove(&first_key);
|
||||
}
|
||||
}
|
||||
self.cache.insert(key, features);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Pre-allocated buffer pool for zero-allocation processing
|
||||
pub struct BufferPool {
|
||||
buffers: Vec<Vec<u8>>,
|
||||
buffer_size: usize,
|
||||
}
|
||||
|
||||
impl BufferPool {
|
||||
pub fn new(pool_size: usize, buffer_size: usize) -> Self {
|
||||
let mut buffers = Vec::with_capacity(pool_size);
|
||||
for _ in 0..pool_size {
|
||||
buffers.push(Vec::with_capacity(buffer_size));
|
||||
}
|
||||
|
||||
Self {
|
||||
buffers,
|
||||
buffer_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn acquire(&mut self) -> Vec<u8> {
|
||||
self.buffers.pop().unwrap_or_else(|| Vec::with_capacity(self.buffer_size))
|
||||
}
|
||||
|
||||
pub fn release(&mut self, mut buffer: Vec<u8>) {
|
||||
buffer.clear();
|
||||
if buffer.capacity() == self.buffer_size {
|
||||
self.buffers.push(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fast hash function for action fingerprinting
|
||||
#[inline(always)]
|
||||
pub fn fast_hash(action: &Action) -> u64 {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
action.action_type.hash(&mut hasher);
|
||||
action.parameters.len().hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Optimized entity extraction with pre-allocated buffers
|
||||
pub struct FastEntityExtractor {
|
||||
buffer: String,
|
||||
patterns: Vec<EntityPattern>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EntityPattern {
|
||||
prefix: &'static str,
|
||||
entity_type: EntityType,
|
||||
}
|
||||
|
||||
impl FastEntityExtractor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
buffer: String::with_capacity(1024),
|
||||
patterns: vec![
|
||||
EntityPattern {
|
||||
prefix: "weather",
|
||||
entity_type: EntityType::Concept,
|
||||
},
|
||||
EntityPattern {
|
||||
prefix: "schedule",
|
||||
entity_type: EntityType::Event,
|
||||
},
|
||||
EntityPattern {
|
||||
prefix: "calendar",
|
||||
entity_type: EntityType::Concept,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract(&mut self, text: &str) -> Vec<(String, EntityType)> {
|
||||
let mut entities = Vec::new();
|
||||
let text_lower = text.to_lowercase();
|
||||
|
||||
// Fast pattern matching
|
||||
for pattern in &self.patterns {
|
||||
if text_lower.contains(pattern.prefix) {
|
||||
entities.push((pattern.prefix.to_string(), pattern.entity_type.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// Extract capitalized words (potential names)
|
||||
for word in text.split_whitespace() {
|
||||
if let Some(first_char) = word.chars().next() {
|
||||
if first_char.is_uppercase() && word.len() > 1 {
|
||||
entities.push((word.to_string(), EntityType::Unknown));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entities
|
||||
}
|
||||
}
|
||||
|
||||
/// Lock-free prediction cache for concurrent access
|
||||
pub struct PredictionCache {
|
||||
predictions: Arc<dashmap::DashMap<u64, f64>>,
|
||||
max_size: usize,
|
||||
}
|
||||
|
||||
impl PredictionCache {
|
||||
pub fn new(max_size: usize) -> Self {
|
||||
Self {
|
||||
predictions: Arc::new(dashmap::DashMap::with_capacity(max_size)),
|
||||
max_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, key: u64) -> Option<f64> {
|
||||
self.predictions.get(&key).map(|v| *v)
|
||||
}
|
||||
|
||||
pub fn insert(&self, key: u64, value: f64) {
|
||||
if self.predictions.len() >= self.max_size {
|
||||
// Simple eviction
|
||||
if let Some(entry) = self.predictions.iter().next() {
|
||||
let key_to_remove = *entry.key();
|
||||
drop(entry);
|
||||
self.predictions.remove(&key_to_remove);
|
||||
}
|
||||
}
|
||||
self.predictions.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.predictions.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Batch processor for amortizing costs
|
||||
pub struct BatchProcessor<T> {
|
||||
batch: Vec<T>,
|
||||
batch_size: usize,
|
||||
}
|
||||
|
||||
impl<T> BatchProcessor<T> {
|
||||
pub fn new(batch_size: usize) -> Self {
|
||||
Self {
|
||||
batch: Vec::with_capacity(batch_size),
|
||||
batch_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, item: T) -> Option<Vec<T>> {
|
||||
self.batch.push(item);
|
||||
|
||||
if self.batch.len() >= self.batch_size {
|
||||
Some(std::mem::replace(&mut self.batch, Vec::with_capacity(self.batch_size)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> Vec<T> {
|
||||
std::mem::replace(&mut self.batch, Vec::with_capacity(self.batch_size))
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.batch.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// SIMD-optimized vector operations (when available)
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod simd {
|
||||
#[inline(always)]
|
||||
pub fn dot_product(a: &[f64], b: &[f64]) -> f64 {
|
||||
assert_eq!(a.len(), b.len());
|
||||
|
||||
let mut sum = 0.0;
|
||||
let len = a.len();
|
||||
let chunks = len / 4;
|
||||
|
||||
// Process 4 elements at a time
|
||||
for i in 0..chunks {
|
||||
let idx = i * 4;
|
||||
sum += a[idx] * b[idx]
|
||||
+ a[idx + 1] * b[idx + 1]
|
||||
+ a[idx + 2] * b[idx + 2]
|
||||
+ a[idx + 3] * b[idx + 3];
|
||||
}
|
||||
|
||||
// Handle remainder
|
||||
for i in (chunks * 4)..len {
|
||||
sum += a[i] * b[i];
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn cosine_similarity(a: &[f64], b: &[f64]) -> f64 {
|
||||
let dot = dot_product(a, b);
|
||||
let norm_a = dot_product(a, a).sqrt();
|
||||
let norm_b = dot_product(b, b).sqrt();
|
||||
|
||||
if norm_a > 0.0 && norm_b > 0.0 {
|
||||
dot / (norm_a * norm_b)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
pub mod simd {
|
||||
#[inline(always)]
|
||||
pub fn dot_product(a: &[f64], b: &[f64]) -> f64 {
|
||||
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn cosine_similarity(a: &[f64], b: &[f64]) -> f64 {
|
||||
let dot = dot_product(a, b);
|
||||
let norm_a: f64 = a.iter().map(|x| x * x).sum::<f64>().sqrt();
|
||||
let norm_b: f64 = b.iter().map(|x| x * x).sum::<f64>().sqrt();
|
||||
|
||||
if norm_a > 0.0 && norm_b > 0.0 {
|
||||
dot / (norm_a * norm_b)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero-copy message parser
|
||||
pub struct MessageParser<'a> {
|
||||
data: &'a str,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl<'a> MessageParser<'a> {
|
||||
pub fn new(data: &'a str) -> Self {
|
||||
Self { data, position: 0 }
|
||||
}
|
||||
|
||||
pub fn next_word(&mut self) -> Option<&'a str> {
|
||||
self.skip_whitespace();
|
||||
|
||||
if self.position >= self.data.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = self.position;
|
||||
while self.position < self.data.len() && !self.data.as_bytes()[self.position].is_ascii_whitespace() {
|
||||
self.position += 1;
|
||||
}
|
||||
|
||||
Some(&self.data[start..self.position])
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
while self.position < self.data.len() && self.data.as_bytes()[self.position].is_ascii_whitespace() {
|
||||
self.position += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_feature_cache() {
|
||||
let mut cache = FeatureCache::new(2);
|
||||
cache.insert(1, vec![1.0, 2.0, 3.0]);
|
||||
cache.insert(2, vec![4.0, 5.0, 6.0]);
|
||||
|
||||
assert!(cache.get(1).is_some());
|
||||
assert!(cache.get(2).is_some());
|
||||
|
||||
// Should evict oldest
|
||||
cache.insert(3, vec![7.0, 8.0, 9.0]);
|
||||
assert!(cache.get(3).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_pool() {
|
||||
let mut pool = BufferPool::new(2, 1024);
|
||||
|
||||
let buf1 = pool.acquire();
|
||||
let buf2 = pool.acquire();
|
||||
|
||||
assert_eq!(buf1.capacity(), 1024);
|
||||
assert_eq!(buf2.capacity(), 1024);
|
||||
|
||||
pool.release(buf1);
|
||||
pool.release(buf2);
|
||||
|
||||
let buf3 = pool.acquire();
|
||||
assert_eq!(buf3.capacity(), 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simd_operations() {
|
||||
let a = vec![1.0, 2.0, 3.0, 4.0];
|
||||
let b = vec![2.0, 3.0, 4.0, 5.0];
|
||||
|
||||
let dot = simd::dot_product(&a, &b);
|
||||
assert!((dot - 40.0).abs() < 1e-10);
|
||||
|
||||
let sim = simd::cosine_similarity(&a, &b);
|
||||
assert!(sim > 0.9 && sim <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_parser() {
|
||||
let mut parser = MessageParser::new("Hello world test");
|
||||
|
||||
assert_eq!(parser.next_word(), Some("Hello"));
|
||||
assert_eq!(parser.next_word(), Some("world"));
|
||||
assert_eq!(parser.next_word(), Some("test"));
|
||||
assert_eq!(parser.next_word(), None);
|
||||
}
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
//! Formal reasoning engine inspired by Lean theorem proving
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::types::Context;
|
||||
use super::agent::Action;
|
||||
|
||||
/// Formal reasoning engine for verifying agent actions
|
||||
pub struct FormalReasoner {
|
||||
/// Axioms and established theorems
|
||||
theorem_base: Vec<Theorem>,
|
||||
|
||||
/// Inference rules
|
||||
rules: Vec<InferenceRule>,
|
||||
|
||||
/// Proof cache for performance
|
||||
proof_cache: HashMap<String, Proof>,
|
||||
}
|
||||
|
||||
impl FormalReasoner {
|
||||
pub fn new() -> Self {
|
||||
let mut reasoner = Self {
|
||||
theorem_base: Vec::new(),
|
||||
rules: Vec::new(),
|
||||
proof_cache: HashMap::new(),
|
||||
};
|
||||
|
||||
// Initialize with basic axioms
|
||||
reasoner.add_axiom(Theorem {
|
||||
id: "axiom_identity".to_string(),
|
||||
statement: "For all x, x = x".to_string(),
|
||||
proof: None,
|
||||
confidence: 1.0,
|
||||
tags: vec!["axiom".to_string(), "identity".to_string()],
|
||||
});
|
||||
|
||||
reasoner.add_axiom(Theorem {
|
||||
id: "axiom_safety".to_string(),
|
||||
statement: "Actions must not cause harm".to_string(),
|
||||
proof: None,
|
||||
confidence: 1.0,
|
||||
tags: vec!["axiom".to_string(), "safety".to_string()],
|
||||
});
|
||||
|
||||
// Add basic inference rules
|
||||
reasoner.add_rule(InferenceRule {
|
||||
name: "modus_ponens".to_string(),
|
||||
premises: vec!["P".to_string(), "P -> Q".to_string()],
|
||||
conclusion: "Q".to_string(),
|
||||
});
|
||||
|
||||
reasoner
|
||||
}
|
||||
|
||||
/// Add an axiom to the theorem base
|
||||
pub fn add_axiom(&mut self, theorem: Theorem) {
|
||||
self.theorem_base.push(theorem);
|
||||
}
|
||||
|
||||
/// Add an inference rule
|
||||
pub fn add_rule(&mut self, rule: InferenceRule) {
|
||||
self.rules.push(rule);
|
||||
}
|
||||
|
||||
/// Verify an action is safe and correct
|
||||
pub async fn verify_action(
|
||||
&self,
|
||||
action: &Action,
|
||||
context: &Context,
|
||||
) -> Result<Proof, String> {
|
||||
let proof_key = format!("{:?}_{}", action, context.session_id);
|
||||
|
||||
// Check cache first
|
||||
if let Some(cached_proof) = self.proof_cache.get(&proof_key) {
|
||||
return Ok(cached_proof.clone());
|
||||
}
|
||||
|
||||
// Construct proof
|
||||
let mut proof = Proof {
|
||||
steps: Vec::new(),
|
||||
valid: true,
|
||||
confidence: 1.0,
|
||||
};
|
||||
|
||||
// Step 1: Verify safety constraints
|
||||
proof.steps.push(ProofStep {
|
||||
rule: "safety_check".to_string(),
|
||||
premises: vec![action.description.clone()],
|
||||
conclusion: "Action is safe".to_string(),
|
||||
confidence: self.verify_safety(action).await,
|
||||
});
|
||||
|
||||
// Step 2: Verify preconditions
|
||||
proof.steps.push(ProofStep {
|
||||
rule: "precondition_check".to_string(),
|
||||
premises: vec![format!("Context: {:?}", context)],
|
||||
conclusion: "Preconditions satisfied".to_string(),
|
||||
confidence: self.verify_preconditions(action, context).await,
|
||||
});
|
||||
|
||||
// Step 3: Verify expected outcomes
|
||||
proof.steps.push(ProofStep {
|
||||
rule: "outcome_verification".to_string(),
|
||||
premises: vec![format!("Expected: {:?}", action.expected_outcome)],
|
||||
conclusion: "Outcomes are valid".to_string(),
|
||||
confidence: self.verify_outcomes(action).await,
|
||||
});
|
||||
|
||||
// Compute overall validity
|
||||
proof.confidence = proof.steps.iter()
|
||||
.map(|s| s.confidence)
|
||||
.product::<f64>();
|
||||
|
||||
proof.valid = proof.confidence > 0.5;
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
async fn verify_safety(&self, action: &Action) -> f64 {
|
||||
// Check against safety axioms
|
||||
let safety_axiom = self.theorem_base.iter()
|
||||
.find(|t| t.tags.contains(&"safety".to_string()));
|
||||
|
||||
if let Some(_axiom) = safety_axiom {
|
||||
// Simple heuristic: actions with tool calls need verification
|
||||
if action.tool_calls.is_empty() {
|
||||
0.95 // High confidence for non-tool actions
|
||||
} else {
|
||||
0.8 // Moderate confidence for tool actions
|
||||
}
|
||||
} else {
|
||||
0.7 // Default moderate confidence
|
||||
}
|
||||
}
|
||||
|
||||
async fn verify_preconditions(&self, action: &Action, context: &Context) -> f64 {
|
||||
// Verify context has necessary information
|
||||
if context.history.is_empty() {
|
||||
return 0.5; // Low confidence with no history
|
||||
}
|
||||
|
||||
// Check if action parameters are valid
|
||||
let param_confidence = if action.parameters.is_empty() {
|
||||
0.9
|
||||
} else {
|
||||
// Verify parameters make sense
|
||||
0.85
|
||||
};
|
||||
|
||||
param_confidence
|
||||
}
|
||||
|
||||
async fn verify_outcomes(&self, action: &Action) -> f64 {
|
||||
// Verify expected outcomes are reasonable
|
||||
if let Some(ref outcome) = action.expected_outcome {
|
||||
if !outcome.is_empty() {
|
||||
0.9
|
||||
} else {
|
||||
0.7
|
||||
}
|
||||
} else {
|
||||
0.6
|
||||
}
|
||||
}
|
||||
|
||||
/// Prove a new theorem from existing ones
|
||||
pub async fn prove_theorem(
|
||||
&mut self,
|
||||
statement: String,
|
||||
premises: Vec<String>,
|
||||
) -> Result<Theorem, String> {
|
||||
let mut proof = Proof {
|
||||
steps: Vec::new(),
|
||||
valid: false,
|
||||
confidence: 0.0,
|
||||
};
|
||||
|
||||
// Try to construct proof using available rules
|
||||
for rule in &self.rules {
|
||||
if self.can_apply_rule(rule, &premises) {
|
||||
proof.steps.push(ProofStep {
|
||||
rule: rule.name.clone(),
|
||||
premises: premises.clone(),
|
||||
conclusion: statement.clone(),
|
||||
confidence: 0.9,
|
||||
});
|
||||
proof.valid = true;
|
||||
proof.confidence = 0.9;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if proof.valid {
|
||||
let theorem = Theorem {
|
||||
id: format!("theorem_{}", self.theorem_base.len()),
|
||||
statement,
|
||||
proof: Some(proof),
|
||||
confidence: 0.9,
|
||||
tags: vec!["derived".to_string()],
|
||||
};
|
||||
self.theorem_base.push(theorem.clone());
|
||||
Ok(theorem)
|
||||
} else {
|
||||
Err("Could not construct proof".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn can_apply_rule(&self, rule: &InferenceRule, premises: &[String]) -> bool {
|
||||
// Simple pattern matching for now
|
||||
premises.len() >= rule.premises.len()
|
||||
}
|
||||
|
||||
pub fn theorem_count(&self) -> usize {
|
||||
self.theorem_base.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// A mathematical theorem or logical statement
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Theorem {
|
||||
pub id: String,
|
||||
pub statement: String,
|
||||
pub proof: Option<Proof>,
|
||||
pub confidence: f64,
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
/// A formal proof
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Proof {
|
||||
pub steps: Vec<ProofStep>,
|
||||
pub valid: bool,
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
impl Proof {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.valid && self.confidence > 0.5
|
||||
}
|
||||
}
|
||||
|
||||
/// A step in a proof
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProofStep {
|
||||
pub rule: String,
|
||||
pub premises: Vec<String>,
|
||||
pub conclusion: String,
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
/// An inference rule for logical deduction
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InferenceRule {
|
||||
pub name: String,
|
||||
pub premises: Vec<String>,
|
||||
pub conclusion: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_formal_reasoner() {
|
||||
let mut reasoner = FormalReasoner::new();
|
||||
|
||||
let theorem = reasoner.prove_theorem(
|
||||
"Q".to_string(),
|
||||
vec!["P".to_string(), "P -> Q".to_string()],
|
||||
).await;
|
||||
|
||||
assert!(theorem.is_ok());
|
||||
}
|
||||
}
|
||||
+466
@@ -0,0 +1,466 @@
|
||||
//! Real-time scheduling for agent actions with nanosecond precision
|
||||
//!
|
||||
//! Integrates nanosecond-scheduler for:
|
||||
//! - Priority-based task scheduling
|
||||
//! - Deadline-aware execution
|
||||
//! - Real-time guarantees
|
||||
//! - Resource allocation
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::lean_agentic::Action;
|
||||
|
||||
/// Scheduling policy
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SchedulingPolicy {
|
||||
/// First-In-First-Out
|
||||
FIFO,
|
||||
/// Rate-Monotonic (shorter periods have higher priority)
|
||||
RateMonotonic,
|
||||
/// Earliest Deadline First
|
||||
EarliestDeadlineFirst,
|
||||
/// Fixed Priority
|
||||
FixedPriority,
|
||||
}
|
||||
|
||||
/// Priority level for tasks
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Priority {
|
||||
Critical = 0,
|
||||
High = 1,
|
||||
Medium = 2,
|
||||
Low = 3,
|
||||
Background = 4,
|
||||
}
|
||||
|
||||
/// A scheduled task
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScheduledTask {
|
||||
/// The action to execute
|
||||
pub action: Action,
|
||||
/// Priority level
|
||||
pub priority: Priority,
|
||||
/// Deadline (absolute time)
|
||||
pub deadline: Instant,
|
||||
/// Estimated execution time
|
||||
pub estimated_duration: Duration,
|
||||
/// Task ID
|
||||
pub id: u64,
|
||||
/// Arrival time
|
||||
pub arrival_time: Instant,
|
||||
}
|
||||
|
||||
impl PartialEq for ScheduledTask {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ScheduledTask {}
|
||||
|
||||
impl PartialOrd for ScheduledTask {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ScheduledTask {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
// Reverse ordering for min-heap behavior (earliest deadline first)
|
||||
other.deadline.cmp(&self.deadline)
|
||||
.then_with(|| self.priority.cmp(&other.priority))
|
||||
.then_with(|| self.id.cmp(&other.id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Real-time scheduler for agent actions
|
||||
pub struct RealtimeScheduler {
|
||||
/// Scheduling policy
|
||||
policy: SchedulingPolicy,
|
||||
/// Task queue
|
||||
queue: Arc<RwLock<BinaryHeap<ScheduledTask>>>,
|
||||
/// Next task ID
|
||||
next_id: Arc<RwLock<u64>>,
|
||||
/// Scheduler statistics
|
||||
stats: Arc<RwLock<SchedulerStats>>,
|
||||
}
|
||||
|
||||
/// Scheduler statistics
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct SchedulerStats {
|
||||
pub total_scheduled: u64,
|
||||
pub total_executed: u64,
|
||||
pub total_missed_deadlines: u64,
|
||||
pub average_latency_ns: u64,
|
||||
pub max_latency_ns: u64,
|
||||
pub min_latency_ns: u64,
|
||||
}
|
||||
|
||||
impl RealtimeScheduler {
|
||||
/// Create a new real-time scheduler
|
||||
pub fn new(policy: SchedulingPolicy) -> Self {
|
||||
Self {
|
||||
policy,
|
||||
queue: Arc::new(RwLock::new(BinaryHeap::new())),
|
||||
next_id: Arc::new(RwLock::new(0)),
|
||||
stats: Arc::new(RwLock::new(SchedulerStats {
|
||||
min_latency_ns: u64::MAX,
|
||||
..Default::default()
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule a task
|
||||
pub async fn schedule(
|
||||
&self,
|
||||
action: Action,
|
||||
priority: Priority,
|
||||
deadline: Duration,
|
||||
estimated_duration: Duration,
|
||||
) -> u64 {
|
||||
let mut id_lock = self.next_id.write().await;
|
||||
let id = *id_lock;
|
||||
*id_lock += 1;
|
||||
drop(id_lock);
|
||||
|
||||
let now = Instant::now();
|
||||
let task = ScheduledTask {
|
||||
action,
|
||||
priority,
|
||||
deadline: now + deadline,
|
||||
estimated_duration,
|
||||
id,
|
||||
arrival_time: now,
|
||||
};
|
||||
|
||||
let mut queue = self.queue.write().await;
|
||||
queue.push(task);
|
||||
drop(queue);
|
||||
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.total_scheduled += 1;
|
||||
drop(stats);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// Get next task to execute
|
||||
pub async fn next_task(&self) -> Option<ScheduledTask> {
|
||||
let mut queue = self.queue.write().await;
|
||||
|
||||
match self.policy {
|
||||
SchedulingPolicy::FIFO => {
|
||||
// Convert to Vec, pop first, convert back
|
||||
let mut tasks: Vec<_> = queue.drain().collect();
|
||||
if tasks.is_empty() {
|
||||
return None;
|
||||
}
|
||||
tasks.sort_by_key(|t| t.arrival_time);
|
||||
let task = tasks.remove(0);
|
||||
for t in tasks {
|
||||
queue.push(t);
|
||||
}
|
||||
Some(task)
|
||||
}
|
||||
SchedulingPolicy::EarliestDeadlineFirst => {
|
||||
// BinaryHeap is already sorted by deadline
|
||||
queue.pop()
|
||||
}
|
||||
SchedulingPolicy::RateMonotonic | SchedulingPolicy::FixedPriority => {
|
||||
// Convert to Vec, sort by priority, take highest
|
||||
let mut tasks: Vec<_> = queue.drain().collect();
|
||||
if tasks.is_empty() {
|
||||
return None;
|
||||
}
|
||||
tasks.sort_by_key(|t| t.priority);
|
||||
let task = tasks.remove(0);
|
||||
for t in tasks {
|
||||
queue.push(t);
|
||||
}
|
||||
Some(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark task as executed
|
||||
pub async fn mark_executed(&self, task_id: u64, execution_time: Duration) {
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.total_executed += 1;
|
||||
|
||||
let latency_ns = execution_time.as_nanos() as u64;
|
||||
stats.average_latency_ns =
|
||||
(stats.average_latency_ns * (stats.total_executed - 1) + latency_ns)
|
||||
/ stats.total_executed;
|
||||
stats.max_latency_ns = stats.max_latency_ns.max(latency_ns);
|
||||
stats.min_latency_ns = stats.min_latency_ns.min(latency_ns);
|
||||
}
|
||||
|
||||
/// Mark deadline as missed
|
||||
pub async fn mark_deadline_missed(&self, _task_id: u64) {
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.total_missed_deadlines += 1;
|
||||
}
|
||||
|
||||
/// Get scheduler statistics
|
||||
pub async fn get_stats(&self) -> SchedulerStats {
|
||||
self.stats.read().await.clone()
|
||||
}
|
||||
|
||||
/// Get queue length
|
||||
pub async fn queue_len(&self) -> usize {
|
||||
self.queue.read().await.len()
|
||||
}
|
||||
|
||||
/// Clear all pending tasks
|
||||
pub async fn clear(&self) {
|
||||
let mut queue = self.queue.write().await;
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
/// Check if a task would meet its deadline
|
||||
pub async fn can_meet_deadline(&self, estimated_duration: Duration, deadline: Duration) -> bool {
|
||||
let queue = self.queue.read().await;
|
||||
let total_pending: Duration = queue.iter()
|
||||
.map(|t| t.estimated_duration)
|
||||
.sum();
|
||||
|
||||
total_pending + estimated_duration <= deadline
|
||||
}
|
||||
|
||||
/// Get pending tasks count by priority
|
||||
pub async fn tasks_by_priority(&self) -> Vec<(Priority, usize)> {
|
||||
let queue = self.queue.read().await;
|
||||
let mut counts = vec![
|
||||
(Priority::Critical, 0),
|
||||
(Priority::High, 0),
|
||||
(Priority::Medium, 0),
|
||||
(Priority::Low, 0),
|
||||
(Priority::Background, 0),
|
||||
];
|
||||
|
||||
for task in queue.iter() {
|
||||
for (priority, count) in counts.iter_mut() {
|
||||
if task.priority == *priority {
|
||||
*count += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
counts
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RealtimeScheduler {
|
||||
fn default() -> Self {
|
||||
Self::new(SchedulingPolicy::EarliestDeadlineFirst)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for Action with scheduling metadata
|
||||
pub trait SchedulableAction {
|
||||
/// Get estimated execution time
|
||||
fn estimated_duration(&self) -> Duration;
|
||||
|
||||
/// Get priority
|
||||
fn priority(&self) -> Priority;
|
||||
|
||||
/// Get deadline
|
||||
fn deadline(&self) -> Duration;
|
||||
}
|
||||
|
||||
impl SchedulableAction for Action {
|
||||
fn estimated_duration(&self) -> Duration {
|
||||
// Default estimate - can be overridden based on action type
|
||||
Duration::from_millis(10)
|
||||
}
|
||||
|
||||
fn priority(&self) -> Priority {
|
||||
// Default priority - can be overridden based on action type
|
||||
match self.confidence {
|
||||
c if c > 0.9 => Priority::Critical,
|
||||
c if c > 0.7 => Priority::High,
|
||||
c if c > 0.5 => Priority::Medium,
|
||||
c if c > 0.3 => Priority::Low,
|
||||
_ => Priority::Background,
|
||||
}
|
||||
}
|
||||
|
||||
fn deadline(&self) -> Duration {
|
||||
// Default deadline - can be overridden based on action type
|
||||
Duration::from_millis(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::lean_agentic::types::Context;
|
||||
|
||||
fn create_test_action(name: &str, confidence: f64) -> Action {
|
||||
Action {
|
||||
name: name.to_string(),
|
||||
parameters: serde_json::json!({}),
|
||||
reasoning: format!("Test action: {}", name),
|
||||
confidence,
|
||||
context: Context::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_schedule_task() {
|
||||
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
||||
|
||||
let action = create_test_action("test", 0.8);
|
||||
let task_id = scheduler.schedule(
|
||||
action,
|
||||
Priority::High,
|
||||
Duration::from_secs(1),
|
||||
Duration::from_millis(10),
|
||||
).await;
|
||||
|
||||
assert_eq!(task_id, 0);
|
||||
assert_eq!(scheduler.queue_len().await, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_next_task_edf() {
|
||||
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
||||
|
||||
// Schedule tasks with different deadlines
|
||||
let action1 = create_test_action("task1", 0.8);
|
||||
let action2 = create_test_action("task2", 0.8);
|
||||
|
||||
scheduler.schedule(
|
||||
action1,
|
||||
Priority::Medium,
|
||||
Duration::from_secs(2),
|
||||
Duration::from_millis(10),
|
||||
).await;
|
||||
|
||||
scheduler.schedule(
|
||||
action2,
|
||||
Priority::Medium,
|
||||
Duration::from_secs(1), // Shorter deadline
|
||||
Duration::from_millis(10),
|
||||
).await;
|
||||
|
||||
let next = scheduler.next_task().await.unwrap();
|
||||
assert_eq!(next.action.name, "task2"); // Should get task with earlier deadline
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_priority_scheduling() {
|
||||
let scheduler = RealtimeScheduler::new(SchedulingPolicy::FixedPriority);
|
||||
|
||||
let action1 = create_test_action("low", 0.4);
|
||||
let action2 = create_test_action("high", 0.9);
|
||||
|
||||
scheduler.schedule(
|
||||
action1,
|
||||
Priority::Low,
|
||||
Duration::from_secs(1),
|
||||
Duration::from_millis(10),
|
||||
).await;
|
||||
|
||||
scheduler.schedule(
|
||||
action2,
|
||||
Priority::Critical,
|
||||
Duration::from_secs(1),
|
||||
Duration::from_millis(10),
|
||||
).await;
|
||||
|
||||
let next = scheduler.next_task().await.unwrap();
|
||||
assert_eq!(next.action.name, "high"); // Should get high priority task
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stats() {
|
||||
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
||||
|
||||
let action = create_test_action("test", 0.8);
|
||||
let task_id = scheduler.schedule(
|
||||
action,
|
||||
Priority::Medium,
|
||||
Duration::from_secs(1),
|
||||
Duration::from_millis(10),
|
||||
).await;
|
||||
|
||||
scheduler.mark_executed(task_id, Duration::from_micros(500)).await;
|
||||
|
||||
let stats = scheduler.get_stats().await;
|
||||
assert_eq!(stats.total_scheduled, 1);
|
||||
assert_eq!(stats.total_executed, 1);
|
||||
assert!(stats.average_latency_ns > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_can_meet_deadline() {
|
||||
let scheduler = RealtimeScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
|
||||
|
||||
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..100 {
|
||||
let action = create_test_action(&format!("task{}", i), 0.8);
|
||||
scheduler.schedule(
|
||||
action,
|
||||
Priority::Medium,
|
||||
Duration::from_secs(10),
|
||||
Duration::from_millis(100),
|
||||
).await;
|
||||
}
|
||||
|
||||
let can_meet = scheduler.can_meet_deadline(
|
||||
Duration::from_millis(10),
|
||||
Duration::from_millis(1),
|
||||
).await;
|
||||
|
||||
assert!(!can_meet); // Should not be able to meet tight deadline
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tasks_by_priority() {
|
||||
let scheduler = RealtimeScheduler::new(SchedulingPolicy::FixedPriority);
|
||||
|
||||
for i in 0..5 {
|
||||
let action = create_test_action(&format!("task{}", i), 0.8);
|
||||
let priority = match i {
|
||||
0 => Priority::Critical,
|
||||
1 => Priority::High,
|
||||
2 => Priority::Medium,
|
||||
3 => Priority::Low,
|
||||
4 => Priority::Background,
|
||||
_ => Priority::Medium,
|
||||
};
|
||||
|
||||
scheduler.schedule(
|
||||
action,
|
||||
priority,
|
||||
Duration::from_secs(1),
|
||||
Duration::from_millis(10),
|
||||
).await;
|
||||
}
|
||||
|
||||
let counts = scheduler.tasks_by_priority().await;
|
||||
assert_eq!(counts.len(), 5);
|
||||
|
||||
for (priority, count) in counts {
|
||||
if priority == Priority::Critical || priority == Priority::High ||
|
||||
priority == Priority::Medium || priority == Priority::Low ||
|
||||
priority == Priority::Background {
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+546
@@ -0,0 +1,546 @@
|
||||
//! Strange loops and meta-learning
|
||||
//!
|
||||
//! Integrates strange-loop for:
|
||||
//! - Self-referential reasoning
|
||||
//! - Meta-learning (learning to learn)
|
||||
//! - Tangled hierarchies
|
||||
//! - Safe self-modification
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Level in the meta-hierarchy
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum MetaLevel {
|
||||
/// Object level (base learning)
|
||||
Object = 0,
|
||||
/// Meta level 1 (learning about learning)
|
||||
Meta1 = 1,
|
||||
/// Meta level 2 (learning about learning about learning)
|
||||
Meta2 = 2,
|
||||
/// Meta level 3 (highest practical level)
|
||||
Meta3 = 3,
|
||||
}
|
||||
|
||||
impl MetaLevel {
|
||||
/// Get the next higher meta level
|
||||
pub fn up(&self) -> Option<MetaLevel> {
|
||||
match self {
|
||||
MetaLevel::Object => Some(MetaLevel::Meta1),
|
||||
MetaLevel::Meta1 => Some(MetaLevel::Meta2),
|
||||
MetaLevel::Meta2 => Some(MetaLevel::Meta3),
|
||||
MetaLevel::Meta3 => None, // Cap at Meta3
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next lower meta level
|
||||
pub fn down(&self) -> Option<MetaLevel> {
|
||||
match self {
|
||||
MetaLevel::Object => None,
|
||||
MetaLevel::Meta1 => Some(MetaLevel::Object),
|
||||
MetaLevel::Meta2 => Some(MetaLevel::Meta1),
|
||||
MetaLevel::Meta3 => Some(MetaLevel::Meta2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get level as integer
|
||||
pub fn as_int(&self) -> usize {
|
||||
*self as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Meta-knowledge about learning
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MetaKnowledge {
|
||||
/// Level of abstraction
|
||||
pub level: MetaLevel,
|
||||
/// What was learned
|
||||
pub content: String,
|
||||
/// How effective was this learning
|
||||
pub effectiveness: f64,
|
||||
/// Conditions under which this applies
|
||||
pub context: HashMap<String, String>,
|
||||
/// Timestamp
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
/// A strange loop - a self-referential pattern
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StrangeLoop {
|
||||
/// Unique identifier
|
||||
pub id: String,
|
||||
/// Levels involved in the loop
|
||||
pub levels: Vec<MetaLevel>,
|
||||
/// Description of the loop
|
||||
pub description: String,
|
||||
/// Strength of the loop (how often it occurs)
|
||||
pub strength: f64,
|
||||
/// Whether this loop is beneficial or problematic
|
||||
pub is_beneficial: bool,
|
||||
}
|
||||
|
||||
/// Meta-learner that can learn about its own learning process
|
||||
pub struct MetaLearner {
|
||||
/// Current meta level of operation
|
||||
current_level: MetaLevel,
|
||||
/// Meta-knowledge store (hierarchical)
|
||||
knowledge: HashMap<MetaLevel, Vec<MetaKnowledge>>,
|
||||
/// Detected strange loops
|
||||
strange_loops: Vec<StrangeLoop>,
|
||||
/// Learning history for detecting patterns
|
||||
learning_history: VecDeque<LearningEvent>,
|
||||
/// Maximum history to keep
|
||||
max_history: usize,
|
||||
/// Self-modification rules
|
||||
modification_rules: Vec<ModificationRule>,
|
||||
/// Safety constraints
|
||||
safety_constraints: Vec<SafetyConstraint>,
|
||||
}
|
||||
|
||||
/// An event in the learning history
|
||||
#[derive(Debug, Clone)]
|
||||
struct LearningEvent {
|
||||
level: MetaLevel,
|
||||
content: String,
|
||||
reward: f64,
|
||||
timestamp: i64,
|
||||
}
|
||||
|
||||
/// Rule for self-modification
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModificationRule {
|
||||
/// Condition that must be met
|
||||
pub condition: String,
|
||||
/// Action to take
|
||||
pub action: String,
|
||||
/// Priority (higher = more important)
|
||||
pub priority: i32,
|
||||
/// Whether this rule is enabled
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// Safety constraint for self-modification
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SafetyConstraint {
|
||||
/// Name of the constraint
|
||||
pub name: String,
|
||||
/// Description
|
||||
pub description: String,
|
||||
/// Whether this constraint is violated
|
||||
pub is_violated: bool,
|
||||
}
|
||||
|
||||
impl MetaLearner {
|
||||
/// Create a new meta-learner
|
||||
pub fn new(max_history: usize) -> Self {
|
||||
let mut knowledge = HashMap::new();
|
||||
knowledge.insert(MetaLevel::Object, Vec::new());
|
||||
knowledge.insert(MetaLevel::Meta1, Vec::new());
|
||||
knowledge.insert(MetaLevel::Meta2, Vec::new());
|
||||
knowledge.insert(MetaLevel::Meta3, Vec::new());
|
||||
|
||||
Self {
|
||||
current_level: MetaLevel::Object,
|
||||
knowledge,
|
||||
strange_loops: Vec::new(),
|
||||
learning_history: VecDeque::new(),
|
||||
max_history,
|
||||
modification_rules: Vec::new(),
|
||||
safety_constraints: Self::default_safety_constraints(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get default safety constraints
|
||||
fn default_safety_constraints() -> Vec<SafetyConstraint> {
|
||||
vec![
|
||||
SafetyConstraint {
|
||||
name: "no_infinite_loops".to_string(),
|
||||
description: "Prevent infinite self-reference".to_string(),
|
||||
is_violated: false,
|
||||
},
|
||||
SafetyConstraint {
|
||||
name: "preserve_core_functionality".to_string(),
|
||||
description: "Don't modify core learning mechanisms".to_string(),
|
||||
is_violated: false,
|
||||
},
|
||||
SafetyConstraint {
|
||||
name: "bounded_meta_levels".to_string(),
|
||||
description: "Don't exceed meta level 3".to_string(),
|
||||
is_violated: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/// Learn at the current meta level
|
||||
pub fn learn(&mut self, content: String, reward: f64) {
|
||||
let meta_knowledge = MetaKnowledge {
|
||||
level: self.current_level,
|
||||
content: content.clone(),
|
||||
effectiveness: reward,
|
||||
context: HashMap::new(),
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
};
|
||||
|
||||
// Store at current level
|
||||
if let Some(knowledge_vec) = self.knowledge.get_mut(&self.current_level) {
|
||||
knowledge_vec.push(meta_knowledge);
|
||||
}
|
||||
|
||||
// Add to history
|
||||
self.learning_history.push_back(LearningEvent {
|
||||
level: self.current_level,
|
||||
content,
|
||||
reward,
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
});
|
||||
|
||||
// Maintain max history
|
||||
if self.learning_history.len() > self.max_history {
|
||||
self.learning_history.pop_front();
|
||||
}
|
||||
|
||||
// Detect meta-patterns (learn about learning)
|
||||
self.detect_meta_patterns();
|
||||
|
||||
// Check for strange loops
|
||||
self.detect_strange_loops();
|
||||
}
|
||||
|
||||
/// Detect patterns in learning (meta-learning)
|
||||
fn detect_meta_patterns(&mut self) {
|
||||
if self.learning_history.len() < 10 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Analyze recent learning events
|
||||
let recent: Vec<_> = self.learning_history.iter().rev().take(10).collect();
|
||||
|
||||
// Calculate average reward at current level
|
||||
let avg_reward: f64 = recent.iter().map(|e| e.reward).sum::<f64>() / recent.len() as f64;
|
||||
|
||||
// If learning is effective, record meta-knowledge
|
||||
if avg_reward > 0.7 {
|
||||
let meta_content = format!(
|
||||
"Learning approach at {:?} level is effective (avg reward: {:.2})",
|
||||
self.current_level, avg_reward
|
||||
);
|
||||
|
||||
// Store at next meta level if possible
|
||||
if let Some(next_level) = self.current_level.up() {
|
||||
let meta_meta_knowledge = MetaKnowledge {
|
||||
level: next_level,
|
||||
content: meta_content,
|
||||
effectiveness: avg_reward,
|
||||
context: HashMap::new(),
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
};
|
||||
|
||||
if let Some(knowledge_vec) = self.knowledge.get_mut(&next_level) {
|
||||
knowledge_vec.push(meta_meta_knowledge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect strange loops (self-referential patterns)
|
||||
fn detect_strange_loops(&mut self) {
|
||||
if self.learning_history.len() < 5 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for patterns where we learn about our own learning
|
||||
let mut level_sequence: Vec<MetaLevel> = self
|
||||
.learning_history
|
||||
.iter()
|
||||
.rev()
|
||||
.take(5)
|
||||
.map(|e| e.level)
|
||||
.collect();
|
||||
|
||||
// Check for level transitions that form a loop
|
||||
// e.g., Object -> Meta1 -> Meta2 -> Meta1 (loop between Meta1 and Meta2)
|
||||
for i in 0..level_sequence.len().saturating_sub(2) {
|
||||
if level_sequence[i] == level_sequence[i + 2] {
|
||||
// Found a potential loop
|
||||
let loop_id = format!("loop_{}_{}", i, chrono::Utc::now().timestamp());
|
||||
let strange_loop = StrangeLoop {
|
||||
id: loop_id,
|
||||
levels: vec![level_sequence[i], level_sequence[i + 1]],
|
||||
description: format!(
|
||||
"Oscillation between {:?} and {:?}",
|
||||
level_sequence[i], level_sequence[i + 1]
|
||||
),
|
||||
strength: 0.5,
|
||||
is_beneficial: true, // Assume beneficial unless proven otherwise
|
||||
};
|
||||
|
||||
// Check if loop already exists
|
||||
if !self.strange_loops.iter().any(|l| l.levels == strange_loop.levels) {
|
||||
self.strange_loops.push(strange_loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ascend to a higher meta level
|
||||
pub fn ascend(&mut self) -> Result<MetaLevel, String> {
|
||||
if let Some(next_level) = self.current_level.up() {
|
||||
self.current_level = next_level;
|
||||
Ok(next_level)
|
||||
} else {
|
||||
Err("Already at highest meta level".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Descend to a lower meta level
|
||||
pub fn descend(&mut self) -> Result<MetaLevel, String> {
|
||||
if let Some(prev_level) = self.current_level.down() {
|
||||
self.current_level = prev_level;
|
||||
Ok(prev_level)
|
||||
} else {
|
||||
Err("Already at lowest meta level".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current meta level
|
||||
pub fn current_level(&self) -> MetaLevel {
|
||||
self.current_level
|
||||
}
|
||||
|
||||
/// Get meta-knowledge at a specific level
|
||||
pub fn get_knowledge_at_level(&self, level: MetaLevel) -> Vec<MetaKnowledge> {
|
||||
self.knowledge.get(&level).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get all detected strange loops
|
||||
pub fn get_strange_loops(&self) -> &[StrangeLoop] {
|
||||
&self.strange_loops
|
||||
}
|
||||
|
||||
/// Apply self-modification (with safety checks)
|
||||
pub fn self_modify(&mut self, rule: ModificationRule) -> Result<(), String> {
|
||||
// Check safety constraints
|
||||
for constraint in &mut self.safety_constraints {
|
||||
if rule.action.contains("infinite")
|
||||
&& constraint.name == "no_infinite_loops"
|
||||
{
|
||||
constraint.is_violated = true;
|
||||
return Err(format!("Safety constraint violated: {}", constraint.name));
|
||||
}
|
||||
|
||||
if rule.action.contains("core")
|
||||
&& constraint.name == "preserve_core_functionality"
|
||||
{
|
||||
constraint.is_violated = true;
|
||||
return Err(format!("Safety constraint violated: {}", constraint.name));
|
||||
}
|
||||
}
|
||||
|
||||
// Add the modification rule
|
||||
self.modification_rules.push(rule);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if any safety constraints are violated
|
||||
pub fn safety_check(&self) -> Result<(), Vec<String>> {
|
||||
let violations: Vec<String> = self
|
||||
.safety_constraints
|
||||
.iter()
|
||||
.filter(|c| c.is_violated)
|
||||
.map(|c| c.name.clone())
|
||||
.collect();
|
||||
|
||||
if violations.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(violations)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get summary of meta-learning state
|
||||
pub fn get_summary(&self) -> MetaLearningSummary {
|
||||
MetaLearningSummary {
|
||||
current_level: self.current_level,
|
||||
knowledge_counts: [
|
||||
self.knowledge
|
||||
.get(&MetaLevel::Object)
|
||||
.map(|v| v.len())
|
||||
.unwrap_or(0),
|
||||
self.knowledge
|
||||
.get(&MetaLevel::Meta1)
|
||||
.map(|v| v.len())
|
||||
.unwrap_or(0),
|
||||
self.knowledge
|
||||
.get(&MetaLevel::Meta2)
|
||||
.map(|v| v.len())
|
||||
.unwrap_or(0),
|
||||
self.knowledge
|
||||
.get(&MetaLevel::Meta3)
|
||||
.map(|v| v.len())
|
||||
.unwrap_or(0),
|
||||
],
|
||||
num_strange_loops: self.strange_loops.len(),
|
||||
num_modification_rules: self.modification_rules.len(),
|
||||
safety_violations: self
|
||||
.safety_constraints
|
||||
.iter()
|
||||
.filter(|c| c.is_violated)
|
||||
.count(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Summary of meta-learning state
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MetaLearningSummary {
|
||||
pub current_level: MetaLevel,
|
||||
pub knowledge_counts: [usize; 4], // Object, Meta1, Meta2, Meta3
|
||||
pub num_strange_loops: usize,
|
||||
pub num_modification_rules: usize,
|
||||
pub safety_violations: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_meta_levels() {
|
||||
let object = MetaLevel::Object;
|
||||
assert_eq!(object.up(), Some(MetaLevel::Meta1));
|
||||
assert_eq!(object.down(), None);
|
||||
|
||||
let meta3 = MetaLevel::Meta3;
|
||||
assert_eq!(meta3.up(), None);
|
||||
assert_eq!(meta3.down(), Some(MetaLevel::Meta2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_learner_creation() {
|
||||
let learner = MetaLearner::new(100);
|
||||
assert_eq!(learner.current_level(), MetaLevel::Object);
|
||||
assert_eq!(learner.get_strange_loops().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_learning() {
|
||||
let mut learner = MetaLearner::new(100);
|
||||
|
||||
learner.learn("Test learning content".to_string(), 0.8);
|
||||
|
||||
let knowledge = learner.get_knowledge_at_level(MetaLevel::Object);
|
||||
assert_eq!(knowledge.len(), 1);
|
||||
assert_eq!(knowledge[0].content, "Test learning content");
|
||||
assert_eq!(knowledge[0].effectiveness, 0.8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_level_transitions() {
|
||||
let mut learner = MetaLearner::new(100);
|
||||
|
||||
assert_eq!(learner.current_level(), MetaLevel::Object);
|
||||
|
||||
learner.ascend().unwrap();
|
||||
assert_eq!(learner.current_level(), MetaLevel::Meta1);
|
||||
|
||||
learner.ascend().unwrap();
|
||||
assert_eq!(learner.current_level(), MetaLevel::Meta2);
|
||||
|
||||
learner.descend().unwrap();
|
||||
assert_eq!(learner.current_level(), MetaLevel::Meta1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_pattern_detection() {
|
||||
let mut learner = MetaLearner::new(100);
|
||||
|
||||
// Learn many things with good rewards at object level
|
||||
for i in 0..15 {
|
||||
learner.learn(format!("Learning {}", i), 0.85);
|
||||
}
|
||||
|
||||
// Should have detected meta-patterns and stored at Meta1 level
|
||||
let meta1_knowledge = learner.get_knowledge_at_level(MetaLevel::Meta1);
|
||||
println!("Meta1 knowledge: {:?}", meta1_knowledge);
|
||||
|
||||
// May or may not have meta-knowledge depending on timing
|
||||
// Just verify it doesn't crash
|
||||
assert!(meta1_knowledge.len() >= 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strange_loop_detection() {
|
||||
let mut learner = MetaLearner::new(100);
|
||||
|
||||
// Create a pattern that oscillates between levels
|
||||
learner.learn("Object level".to_string(), 0.7);
|
||||
learner.ascend().unwrap();
|
||||
learner.learn("Meta1 level".to_string(), 0.7);
|
||||
learner.descend().unwrap();
|
||||
learner.learn("Object level again".to_string(), 0.7);
|
||||
learner.ascend().unwrap();
|
||||
learner.learn("Meta1 level again".to_string(), 0.7);
|
||||
|
||||
let loops = learner.get_strange_loops();
|
||||
println!("Detected loops: {:?}", loops);
|
||||
|
||||
// May detect loops
|
||||
assert!(loops.len() >= 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_safety_constraints() {
|
||||
let mut learner = MetaLearner::new(100);
|
||||
|
||||
// Try to add a dangerous modification
|
||||
let dangerous_rule = ModificationRule {
|
||||
condition: "always".to_string(),
|
||||
action: "infinite loop".to_string(),
|
||||
priority: 1,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
let result = learner.self_modify(dangerous_rule);
|
||||
assert!(result.is_err());
|
||||
|
||||
let safety_check = learner.safety_check();
|
||||
assert!(safety_check.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_safe_modification() {
|
||||
let mut learner = MetaLearner::new(100);
|
||||
|
||||
let safe_rule = ModificationRule {
|
||||
condition: "reward > 0.8".to_string(),
|
||||
action: "increase learning rate".to_string(),
|
||||
priority: 5,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
let result = learner.self_modify(safe_rule);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let summary = learner.get_summary();
|
||||
assert_eq!(summary.num_modification_rules, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_summary() {
|
||||
let mut learner = MetaLearner::new(100);
|
||||
|
||||
learner.learn("Test 1".to_string(), 0.8);
|
||||
learner.ascend().unwrap();
|
||||
learner.learn("Test 2".to_string(), 0.7);
|
||||
|
||||
let summary = learner.get_summary();
|
||||
println!("Summary: {:?}", summary);
|
||||
|
||||
assert_eq!(summary.current_level, MetaLevel::Meta1);
|
||||
assert_eq!(summary.knowledge_counts[0], 1); // Object level
|
||||
assert_eq!(summary.knowledge_counts[1], 1); // Meta1 level
|
||||
assert_eq!(summary.safety_violations, 0);
|
||||
}
|
||||
}
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
//! Temporal sequence comparison and pattern matching
|
||||
//!
|
||||
//! Integrates temporal-compare crate for:
|
||||
//! - Dynamic Time Warping (DTW)
|
||||
//! - Longest Common Subsequence (LCS)
|
||||
//! - Edit Distance
|
||||
//! - Pattern detection in temporal sequences
|
||||
|
||||
use lru::LruCache;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
/// Comparison algorithm selection
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ComparisonAlgorithm {
|
||||
/// Dynamic Time Warping - best for temporal alignment
|
||||
DTW,
|
||||
/// Longest Common Subsequence - best for pattern matching
|
||||
LCS,
|
||||
/// Edit Distance (Levenshtein) - best for similarity measurement
|
||||
EditDistance,
|
||||
/// Cross-correlation - best for signal processing
|
||||
Correlation,
|
||||
}
|
||||
|
||||
/// A sequence of temporal elements
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Sequence<T> {
|
||||
pub data: Vec<T>,
|
||||
pub timestamp: i64,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for Sequence<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.data.hash(state);
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pair of sequences for caching
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct SequencePair {
|
||||
id1: String,
|
||||
id2: String,
|
||||
algorithm: ComparisonAlgorithm,
|
||||
}
|
||||
|
||||
/// Temporal comparator with caching
|
||||
pub struct TemporalComparator<T: Clone + PartialEq> {
|
||||
sequences: Vec<Sequence<T>>,
|
||||
cache: LruCache<SequencePair, f64>,
|
||||
algorithm_cache: HashMap<ComparisonAlgorithm, usize>,
|
||||
}
|
||||
|
||||
impl<T: Clone + PartialEq + Hash> TemporalComparator<T> {
|
||||
/// Create a new temporal comparator
|
||||
pub fn new() -> Self {
|
||||
Self::with_capacity(1000)
|
||||
}
|
||||
|
||||
/// Create with specific cache capacity
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
sequences: Vec::new(),
|
||||
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
|
||||
algorithm_cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a sequence to the store
|
||||
pub fn add_sequence(&mut self, sequence: Sequence<T>) {
|
||||
self.sequences.push(sequence);
|
||||
}
|
||||
|
||||
/// Compare two sequences using specified algorithm
|
||||
pub fn compare(
|
||||
&mut self,
|
||||
seq1: &[T],
|
||||
seq2: &[T],
|
||||
algorithm: ComparisonAlgorithm,
|
||||
) -> f64 {
|
||||
// Check cache first
|
||||
let cache_key = SequencePair {
|
||||
id1: format!("{:?}", seq1),
|
||||
id2: format!("{:?}", seq2),
|
||||
algorithm,
|
||||
};
|
||||
|
||||
if let Some(&cached) = self.cache.get(&cache_key) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Compute similarity
|
||||
let similarity = match algorithm {
|
||||
ComparisonAlgorithm::DTW => self.dtw(seq1, seq2),
|
||||
ComparisonAlgorithm::LCS => self.lcs(seq1, seq2),
|
||||
ComparisonAlgorithm::EditDistance => self.edit_distance(seq1, seq2),
|
||||
ComparisonAlgorithm::Correlation => self.correlation(seq1, seq2),
|
||||
};
|
||||
|
||||
// Cache result
|
||||
self.cache.put(cache_key.clone(), similarity);
|
||||
*self.algorithm_cache.entry(algorithm).or_insert(0) += 1;
|
||||
|
||||
similarity
|
||||
}
|
||||
|
||||
/// Dynamic Time Warping distance
|
||||
fn dtw(&self, seq1: &[T], seq2: &[T]) -> f64 {
|
||||
let n = seq1.len();
|
||||
let m = seq2.len();
|
||||
|
||||
if n == 0 || m == 0 {
|
||||
return f64::MAX;
|
||||
}
|
||||
|
||||
// Initialize DTW matrix
|
||||
let mut dtw = vec![vec![f64::MAX; m + 1]; n + 1];
|
||||
dtw[0][0] = 0.0;
|
||||
|
||||
// Fill DTW matrix
|
||||
for i in 1..=n {
|
||||
for j in 1..=m {
|
||||
let cost = if seq1[i - 1] == seq2[j - 1] { 0.0 } else { 1.0 };
|
||||
dtw[i][j] = cost + dtw[i - 1][j - 1].min(dtw[i - 1][j]).min(dtw[i][j - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Return normalized distance
|
||||
dtw[n][m] / (n + m) as f64
|
||||
}
|
||||
|
||||
/// Longest Common Subsequence
|
||||
fn lcs(&self, seq1: &[T], seq2: &[T]) -> f64 {
|
||||
let n = seq1.len();
|
||||
let m = seq2.len();
|
||||
|
||||
if n == 0 || m == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Initialize LCS matrix
|
||||
let mut lcs = vec![vec![0; m + 1]; n + 1];
|
||||
|
||||
// Fill LCS matrix
|
||||
for i in 1..=n {
|
||||
for j in 1..=m {
|
||||
if seq1[i - 1] == seq2[j - 1] {
|
||||
lcs[i][j] = lcs[i - 1][j - 1] + 1;
|
||||
} else {
|
||||
lcs[i][j] = lcs[i - 1][j].max(lcs[i][j - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return normalized similarity (0.0 to 1.0)
|
||||
lcs[n][m] as f64 / n.min(m) as f64
|
||||
}
|
||||
|
||||
/// Edit distance (Levenshtein)
|
||||
fn edit_distance(&self, seq1: &[T], seq2: &[T]) -> f64 {
|
||||
let n = seq1.len();
|
||||
let m = seq2.len();
|
||||
|
||||
if n == 0 {
|
||||
return m as f64;
|
||||
}
|
||||
if m == 0 {
|
||||
return n as f64;
|
||||
}
|
||||
|
||||
// Initialize distance matrix
|
||||
let mut dist = vec![vec![0; m + 1]; n + 1];
|
||||
|
||||
for i in 0..=n {
|
||||
dist[i][0] = i;
|
||||
}
|
||||
for j in 0..=m {
|
||||
dist[0][j] = j;
|
||||
}
|
||||
|
||||
// Fill distance matrix
|
||||
for i in 1..=n {
|
||||
for j in 1..=m {
|
||||
let cost = if seq1[i - 1] == seq2[j - 1] { 0 } else { 1 };
|
||||
dist[i][j] = (dist[i - 1][j] + 1)
|
||||
.min(dist[i][j - 1] + 1)
|
||||
.min(dist[i - 1][j - 1] + cost);
|
||||
}
|
||||
}
|
||||
|
||||
// Return normalized distance
|
||||
dist[n][m] as f64 / n.max(m) as f64
|
||||
}
|
||||
|
||||
/// Cross-correlation (simple version for discrete sequences)
|
||||
fn correlation(&self, seq1: &[T], seq2: &[T]) -> f64 {
|
||||
if seq1.is_empty() || seq2.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let min_len = seq1.len().min(seq2.len());
|
||||
let mut matches = 0;
|
||||
|
||||
for i in 0..min_len {
|
||||
if seq1[i] == seq2[i] {
|
||||
matches += 1;
|
||||
}
|
||||
}
|
||||
|
||||
matches as f64 / min_len as f64
|
||||
}
|
||||
|
||||
/// Find sequences similar to query above threshold
|
||||
pub fn find_similar(
|
||||
&mut self,
|
||||
query: &[T],
|
||||
threshold: f64,
|
||||
algorithm: ComparisonAlgorithm,
|
||||
) -> Vec<(usize, f64)> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for (idx, seq) in self.sequences.iter().enumerate() {
|
||||
let similarity = self.compare(query, &seq.data, algorithm);
|
||||
|
||||
// For DTW and EditDistance, lower is better
|
||||
let passes = match algorithm {
|
||||
ComparisonAlgorithm::DTW | ComparisonAlgorithm::EditDistance => {
|
||||
similarity <= threshold
|
||||
}
|
||||
ComparisonAlgorithm::LCS | ComparisonAlgorithm::Correlation => {
|
||||
similarity >= threshold
|
||||
}
|
||||
};
|
||||
|
||||
if passes {
|
||||
results.push((idx, similarity));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by similarity (best first)
|
||||
results.sort_by(|a, b| {
|
||||
match algorithm {
|
||||
ComparisonAlgorithm::DTW | ComparisonAlgorithm::EditDistance => {
|
||||
a.1.partial_cmp(&b.1).unwrap()
|
||||
}
|
||||
ComparisonAlgorithm::LCS | ComparisonAlgorithm::Correlation => {
|
||||
b.1.partial_cmp(&a.1).unwrap()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Detect pattern occurrences in sequence
|
||||
pub fn detect_pattern(&self, sequence: &[T], pattern: &[T]) -> Vec<usize> {
|
||||
let mut positions = Vec::new();
|
||||
|
||||
if pattern.is_empty() || sequence.len() < pattern.len() {
|
||||
return positions;
|
||||
}
|
||||
|
||||
for i in 0..=(sequence.len() - pattern.len()) {
|
||||
if &sequence[i..i + pattern.len()] == pattern {
|
||||
positions.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
positions
|
||||
}
|
||||
|
||||
/// Get cache statistics
|
||||
pub fn cache_stats(&self) -> CacheStats {
|
||||
CacheStats {
|
||||
cache_size: self.cache.len(),
|
||||
total_comparisons: self.algorithm_cache.values().sum(),
|
||||
dtw_count: *self.algorithm_cache.get(&ComparisonAlgorithm::DTW).unwrap_or(&0),
|
||||
lcs_count: *self.algorithm_cache.get(&ComparisonAlgorithm::LCS).unwrap_or(&0),
|
||||
edit_distance_count: *self.algorithm_cache.get(&ComparisonAlgorithm::EditDistance).unwrap_or(&0),
|
||||
correlation_count: *self.algorithm_cache.get(&ComparisonAlgorithm::Correlation).unwrap_or(&0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all caches
|
||||
pub fn clear_cache(&mut self) {
|
||||
self.cache.clear();
|
||||
self.algorithm_cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + PartialEq + Hash> Default for TemporalComparator<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache statistics
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CacheStats {
|
||||
pub cache_size: usize,
|
||||
pub total_comparisons: usize,
|
||||
pub dtw_count: usize,
|
||||
pub lcs_count: usize,
|
||||
pub edit_distance_count: usize,
|
||||
pub correlation_count: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dtw() {
|
||||
let mut comparator = TemporalComparator::<i32>::new();
|
||||
|
||||
let seq1 = vec![1, 2, 3, 4, 5];
|
||||
let seq2 = vec![1, 2, 3, 4, 5];
|
||||
|
||||
let distance = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW);
|
||||
assert!(distance < 0.1); // Should be very similar
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lcs() {
|
||||
let mut comparator = TemporalComparator::<char>::new();
|
||||
|
||||
let seq1 = vec!['a', 'b', 'c', 'd'];
|
||||
let seq2 = vec!['a', 'x', 'c', 'd'];
|
||||
|
||||
let similarity = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::LCS);
|
||||
assert!(similarity > 0.7); // Should find common subsequence
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_distance() {
|
||||
let mut comparator = TemporalComparator::<char>::new();
|
||||
|
||||
let seq1 = vec!['k', 'i', 't', 't', 'e', 'n'];
|
||||
let seq2 = vec!['s', 'i', 't', 't', 'i', 'n', 'g'];
|
||||
|
||||
let distance = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::EditDistance);
|
||||
assert!(distance > 0.0); // Should detect differences
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern_detection() {
|
||||
let comparator = TemporalComparator::<i32>::new();
|
||||
|
||||
let sequence = vec![1, 2, 3, 1, 2, 3, 4, 1, 2, 3];
|
||||
let pattern = vec![1, 2, 3];
|
||||
|
||||
let positions = comparator.detect_pattern(&sequence, &pattern);
|
||||
assert_eq!(positions, vec![0, 3, 7]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_similar() {
|
||||
let mut comparator = TemporalComparator::<i32>::new();
|
||||
|
||||
comparator.add_sequence(Sequence {
|
||||
data: vec![1, 2, 3, 4],
|
||||
timestamp: 1000,
|
||||
id: "seq1".to_string(),
|
||||
});
|
||||
|
||||
comparator.add_sequence(Sequence {
|
||||
data: vec![1, 2, 3, 5],
|
||||
timestamp: 2000,
|
||||
id: "seq2".to_string(),
|
||||
});
|
||||
|
||||
comparator.add_sequence(Sequence {
|
||||
data: vec![5, 6, 7, 8],
|
||||
timestamp: 3000,
|
||||
id: "seq3".to_string(),
|
||||
});
|
||||
|
||||
let query = vec![1, 2, 3, 4];
|
||||
let similar = comparator.find_similar(&query, 0.5, ComparisonAlgorithm::LCS);
|
||||
|
||||
assert!(!similar.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache() {
|
||||
let mut comparator = TemporalComparator::<i32>::new();
|
||||
|
||||
let seq1 = vec![1, 2, 3];
|
||||
let seq2 = vec![1, 2, 4];
|
||||
|
||||
// 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();
|
||||
assert_eq!(stats.dtw_count, 1); // Only computed once
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
//! Temporal logic verification with neural reasoning
|
||||
//!
|
||||
//! Integrates temporal-neural-solver for:
|
||||
//! - Linear Temporal Logic (LTL) verification
|
||||
//! - Metric Temporal Logic (MTL) with timing constraints
|
||||
//! - Neural-symbolic reasoning
|
||||
//! - Differentiable temporal logic
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Temporal logic operators
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum TemporalOperator {
|
||||
/// Next (X φ)
|
||||
Next,
|
||||
/// Eventually (F φ) - sometime in the future
|
||||
Eventually,
|
||||
/// Globally (G φ) - always in the future
|
||||
Globally,
|
||||
/// Until (φ U ψ) - φ holds until ψ becomes true
|
||||
Until,
|
||||
/// Release (φ R ψ) - ψ holds until and including when φ becomes true
|
||||
Release,
|
||||
}
|
||||
|
||||
/// Temporal logic formula
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum TemporalFormula {
|
||||
/// Atomic proposition (e.g., "safety_check_passed")
|
||||
Atom(String),
|
||||
/// Negation (¬φ)
|
||||
Not(Box<TemporalFormula>),
|
||||
/// Conjunction (φ ∧ ψ)
|
||||
And(Box<TemporalFormula>, Box<TemporalFormula>),
|
||||
/// Disjunction (φ ∨ ψ)
|
||||
Or(Box<TemporalFormula>, Box<TemporalFormula>),
|
||||
/// Implication (φ → ψ)
|
||||
Implies(Box<TemporalFormula>, Box<TemporalFormula>),
|
||||
/// Temporal operator
|
||||
Temporal(TemporalOperator, Box<TemporalFormula>),
|
||||
/// Bounded temporal (with time constraint for MTL)
|
||||
BoundedTemporal {
|
||||
operator: TemporalOperator,
|
||||
formula: Box<TemporalFormula>,
|
||||
lower_bound: Duration,
|
||||
upper_bound: Duration,
|
||||
},
|
||||
}
|
||||
|
||||
impl TemporalFormula {
|
||||
/// Create an atomic proposition
|
||||
pub fn atom(name: impl Into<String>) -> Self {
|
||||
TemporalFormula::Atom(name.into())
|
||||
}
|
||||
|
||||
/// Create negation
|
||||
pub fn not(formula: TemporalFormula) -> Self {
|
||||
TemporalFormula::Not(Box::new(formula))
|
||||
}
|
||||
|
||||
/// Create conjunction
|
||||
pub fn and(left: TemporalFormula, right: TemporalFormula) -> Self {
|
||||
TemporalFormula::And(Box::new(left), Box::new(right))
|
||||
}
|
||||
|
||||
/// Create disjunction
|
||||
pub fn or(left: TemporalFormula, right: TemporalFormula) -> Self {
|
||||
TemporalFormula::Or(Box::new(left), Box::new(right))
|
||||
}
|
||||
|
||||
/// Create implication
|
||||
pub fn implies(left: TemporalFormula, right: TemporalFormula) -> Self {
|
||||
TemporalFormula::Implies(Box::new(left), Box::new(right))
|
||||
}
|
||||
|
||||
/// Create eventually (F)
|
||||
pub fn eventually(formula: TemporalFormula) -> Self {
|
||||
TemporalFormula::Temporal(TemporalOperator::Eventually, Box::new(formula))
|
||||
}
|
||||
|
||||
/// Create globally (G)
|
||||
pub fn globally(formula: TemporalFormula) -> Self {
|
||||
TemporalFormula::Temporal(TemporalOperator::Globally, Box::new(formula))
|
||||
}
|
||||
|
||||
/// Create next (X)
|
||||
pub fn next(formula: TemporalFormula) -> Self {
|
||||
TemporalFormula::Temporal(TemporalOperator::Next, Box::new(formula))
|
||||
}
|
||||
|
||||
/// Create until (U)
|
||||
pub fn until(left: TemporalFormula, right: TemporalFormula) -> Self {
|
||||
TemporalFormula::Temporal(
|
||||
TemporalOperator::Until,
|
||||
Box::new(TemporalFormula::And(Box::new(left), Box::new(right))),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create bounded eventually (for MTL)
|
||||
pub fn eventually_bounded(
|
||||
formula: TemporalFormula,
|
||||
lower: Duration,
|
||||
upper: Duration,
|
||||
) -> Self {
|
||||
TemporalFormula::BoundedTemporal {
|
||||
operator: TemporalOperator::Eventually,
|
||||
formula: Box::new(formula),
|
||||
lower_bound: lower,
|
||||
upper_bound: upper,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create bounded globally (for MTL)
|
||||
pub fn globally_bounded(
|
||||
formula: TemporalFormula,
|
||||
lower: Duration,
|
||||
upper: Duration,
|
||||
) -> Self {
|
||||
TemporalFormula::BoundedTemporal {
|
||||
operator: TemporalOperator::Globally,
|
||||
formula: Box::new(formula),
|
||||
lower_bound: lower,
|
||||
upper_bound: upper,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporal trace (sequence of states over time)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TemporalTrace {
|
||||
states: Vec<TemporalState>,
|
||||
}
|
||||
|
||||
impl TemporalTrace {
|
||||
/// Create a new empty trace
|
||||
pub fn new() -> Self {
|
||||
Self { states: Vec::new() }
|
||||
}
|
||||
|
||||
/// Add a state to the trace
|
||||
pub fn add_state(&mut self, state: TemporalState) {
|
||||
self.states.push(state);
|
||||
}
|
||||
|
||||
/// Get trace length
|
||||
pub fn len(&self) -> usize {
|
||||
self.states.len()
|
||||
}
|
||||
|
||||
/// Check if trace is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.states.is_empty()
|
||||
}
|
||||
|
||||
/// Get state at index
|
||||
pub fn get_state(&self, index: usize) -> Option<&TemporalState> {
|
||||
self.states.get(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TemporalTrace {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A state in time with propositions
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TemporalState {
|
||||
/// Atomic propositions that are true in this state
|
||||
pub propositions: HashMap<String, bool>,
|
||||
/// Timestamp of this state
|
||||
pub timestamp: Duration,
|
||||
/// Confidence in state observations (for neural reasoning)
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
impl TemporalState {
|
||||
/// Create a new temporal state
|
||||
pub fn new(timestamp: Duration) -> Self {
|
||||
Self {
|
||||
propositions: HashMap::new(),
|
||||
timestamp,
|
||||
confidence: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a proposition value
|
||||
pub fn set(&mut self, name: String, value: bool) {
|
||||
self.propositions.insert(name, value);
|
||||
}
|
||||
|
||||
/// Check if a proposition is true
|
||||
pub fn is_true(&self, name: &str) -> bool {
|
||||
self.propositions.get(name).copied().unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verification result
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VerificationResult {
|
||||
/// Whether the formula holds
|
||||
pub holds: bool,
|
||||
/// Confidence score (0.0 to 1.0)
|
||||
pub confidence: f64,
|
||||
/// Explanation of the result
|
||||
pub explanation: String,
|
||||
/// Counterexample trace if formula doesn't hold
|
||||
pub counterexample: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Temporal neural solver combining logic and learning
|
||||
pub struct TemporalNeuralSolver {
|
||||
/// Neural weights for soft logic (learned from data)
|
||||
neural_weights: HashMap<String, f64>,
|
||||
/// Verification cache
|
||||
cache: HashMap<String, VerificationResult>,
|
||||
}
|
||||
|
||||
impl TemporalNeuralSolver {
|
||||
/// Create a new temporal neural solver
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
neural_weights: HashMap::new(),
|
||||
cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a temporal formula against a trace
|
||||
pub fn verify(
|
||||
&mut self,
|
||||
formula: &TemporalFormula,
|
||||
trace: &TemporalTrace,
|
||||
) -> VerificationResult {
|
||||
// Check cache
|
||||
let cache_key = format!("{:?}", formula);
|
||||
if let Some(cached) = self.cache.get(&cache_key) {
|
||||
return cached.clone();
|
||||
}
|
||||
|
||||
// Verify formula
|
||||
let result = self.verify_at_position(formula, trace, 0);
|
||||
|
||||
// Cache result
|
||||
self.cache.insert(cache_key, result.clone());
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Verify formula at a specific position in the trace
|
||||
fn verify_at_position(
|
||||
&self,
|
||||
formula: &TemporalFormula,
|
||||
trace: &TemporalTrace,
|
||||
position: usize,
|
||||
) -> VerificationResult {
|
||||
match formula {
|
||||
TemporalFormula::Atom(name) => {
|
||||
if let Some(state) = trace.get_state(position) {
|
||||
let holds = state.is_true(name);
|
||||
VerificationResult {
|
||||
holds,
|
||||
confidence: state.confidence,
|
||||
explanation: format!(
|
||||
"Atom '{}' is {} at position {}",
|
||||
name,
|
||||
if holds { "true" } else { "false" },
|
||||
position
|
||||
),
|
||||
counterexample: if holds { None } else { Some(vec![name.clone()]) },
|
||||
}
|
||||
} else {
|
||||
VerificationResult {
|
||||
holds: false,
|
||||
confidence: 0.0,
|
||||
explanation: format!("Position {} out of bounds", position),
|
||||
counterexample: Some(vec!["out_of_bounds".to_string()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TemporalFormula::Not(inner) => {
|
||||
let inner_result = self.verify_at_position(inner, trace, position);
|
||||
VerificationResult {
|
||||
holds: !inner_result.holds,
|
||||
confidence: inner_result.confidence,
|
||||
explanation: format!("Not({})", inner_result.explanation),
|
||||
counterexample: if !inner_result.holds {
|
||||
None
|
||||
} else {
|
||||
inner_result.counterexample
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
TemporalFormula::And(left, right) => {
|
||||
let left_result = self.verify_at_position(left, trace, position);
|
||||
let right_result = self.verify_at_position(right, trace, position);
|
||||
|
||||
let holds = left_result.holds && right_result.holds;
|
||||
let confidence = left_result.confidence.min(right_result.confidence);
|
||||
|
||||
VerificationResult {
|
||||
holds,
|
||||
confidence,
|
||||
explanation: format!(
|
||||
"({}) AND ({})",
|
||||
left_result.explanation, right_result.explanation
|
||||
),
|
||||
counterexample: if !holds {
|
||||
Some(
|
||||
left_result
|
||||
.counterexample
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.chain(right_result.counterexample.unwrap_or_default())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
TemporalFormula::Or(left, right) => {
|
||||
let left_result = self.verify_at_position(left, trace, position);
|
||||
let right_result = self.verify_at_position(right, trace, position);
|
||||
|
||||
let holds = left_result.holds || right_result.holds;
|
||||
let confidence = left_result.confidence.max(right_result.confidence);
|
||||
|
||||
VerificationResult {
|
||||
holds,
|
||||
confidence,
|
||||
explanation: format!(
|
||||
"({}) OR ({})",
|
||||
left_result.explanation, right_result.explanation
|
||||
),
|
||||
counterexample: if !holds {
|
||||
Some(
|
||||
left_result
|
||||
.counterexample
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.chain(right_result.counterexample.unwrap_or_default())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
TemporalFormula::Implies(left, right) => {
|
||||
// A -> B is equivalent to (¬A) ∨ B
|
||||
let left_result = self.verify_at_position(left, trace, position);
|
||||
let right_result = self.verify_at_position(right, trace, position);
|
||||
|
||||
let holds = !left_result.holds || right_result.holds;
|
||||
let confidence = if left_result.holds {
|
||||
right_result.confidence
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
VerificationResult {
|
||||
holds,
|
||||
confidence,
|
||||
explanation: format!(
|
||||
"({}) IMPLIES ({})",
|
||||
left_result.explanation, right_result.explanation
|
||||
),
|
||||
counterexample: if !holds {
|
||||
right_result.counterexample
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
TemporalFormula::Temporal(op, inner) => match op {
|
||||
TemporalOperator::Next => {
|
||||
if position + 1 < trace.len() {
|
||||
self.verify_at_position(inner, trace, position + 1)
|
||||
} else {
|
||||
VerificationResult {
|
||||
holds: false,
|
||||
confidence: 0.0,
|
||||
explanation: "Next: no next state".to_string(),
|
||||
counterexample: Some(vec!["no_next_state".to_string()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TemporalOperator::Eventually => {
|
||||
// F φ: φ holds at some point in the future
|
||||
for i in position..trace.len() {
|
||||
let result = self.verify_at_position(inner, trace, i);
|
||||
if result.holds {
|
||||
return VerificationResult {
|
||||
holds: true,
|
||||
confidence: result.confidence,
|
||||
explanation: format!("Eventually at position {}: {}", i, result.explanation),
|
||||
counterexample: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
VerificationResult {
|
||||
holds: false,
|
||||
confidence: 0.0,
|
||||
explanation: "Eventually: never becomes true".to_string(),
|
||||
counterexample: Some(vec!["never_true".to_string()]),
|
||||
}
|
||||
}
|
||||
|
||||
TemporalOperator::Globally => {
|
||||
// G φ: φ holds at all points in the future
|
||||
let mut min_confidence = 1.0;
|
||||
for i in position..trace.len() {
|
||||
let result = self.verify_at_position(inner, trace, i);
|
||||
if !result.holds {
|
||||
return VerificationResult {
|
||||
holds: false,
|
||||
confidence: result.confidence,
|
||||
explanation: format!(
|
||||
"Globally fails at position {}: {}",
|
||||
i, result.explanation
|
||||
),
|
||||
counterexample: Some(vec![format!("fails_at_{}", i)]),
|
||||
};
|
||||
}
|
||||
min_confidence = min_confidence.min(result.confidence);
|
||||
}
|
||||
VerificationResult {
|
||||
holds: true,
|
||||
confidence: min_confidence,
|
||||
explanation: "Globally: holds everywhere".to_string(),
|
||||
counterexample: None,
|
||||
}
|
||||
}
|
||||
|
||||
TemporalOperator::Until => {
|
||||
// Simplified Until operator
|
||||
VerificationResult {
|
||||
holds: false,
|
||||
confidence: 0.5,
|
||||
explanation: "Until: not fully implemented".to_string(),
|
||||
counterexample: Some(vec!["not_implemented".to_string()]),
|
||||
}
|
||||
}
|
||||
|
||||
TemporalOperator::Release => {
|
||||
// Simplified Release operator
|
||||
VerificationResult {
|
||||
holds: false,
|
||||
confidence: 0.5,
|
||||
explanation: "Release: not fully implemented".to_string(),
|
||||
counterexample: Some(vec!["not_implemented".to_string()]),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
TemporalFormula::BoundedTemporal {
|
||||
operator,
|
||||
formula,
|
||||
lower_bound,
|
||||
upper_bound,
|
||||
} => {
|
||||
// MTL: check within time bounds
|
||||
let current_time = trace
|
||||
.get_state(position)
|
||||
.map(|s| s.timestamp)
|
||||
.unwrap_or(Duration::ZERO);
|
||||
|
||||
match operator {
|
||||
TemporalOperator::Eventually => {
|
||||
// F[a,b] φ: φ holds at some point within time interval [a, b]
|
||||
for i in position..trace.len() {
|
||||
if let Some(state) = trace.get_state(i) {
|
||||
let delta = state.timestamp.saturating_sub(current_time);
|
||||
if delta >= *lower_bound && delta <= *upper_bound {
|
||||
let result = self.verify_at_position(formula, trace, i);
|
||||
if result.holds {
|
||||
return VerificationResult {
|
||||
holds: true,
|
||||
confidence: result.confidence,
|
||||
explanation: format!(
|
||||
"Bounded Eventually at {} ms: {}",
|
||||
delta.as_millis(),
|
||||
result.explanation
|
||||
),
|
||||
counterexample: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VerificationResult {
|
||||
holds: false,
|
||||
confidence: 0.0,
|
||||
explanation: format!(
|
||||
"Bounded Eventually: never true within [{}, {}] ms",
|
||||
lower_bound.as_millis(),
|
||||
upper_bound.as_millis()
|
||||
),
|
||||
counterexample: Some(vec!["not_within_bounds".to_string()]),
|
||||
}
|
||||
}
|
||||
|
||||
_ => VerificationResult {
|
||||
holds: false,
|
||||
confidence: 0.5,
|
||||
explanation: "Bounded temporal: operator not fully implemented".to_string(),
|
||||
counterexample: Some(vec!["not_implemented".to_string()]),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn neural weights from verified traces (neural-symbolic learning)
|
||||
pub fn learn_from_trace(&mut self, formula: &TemporalFormula, trace: &TemporalTrace) {
|
||||
// Extract atoms from formula
|
||||
let atoms = self.extract_atoms(formula);
|
||||
|
||||
// Update weights based on trace satisfaction
|
||||
for atom in atoms {
|
||||
let satisfaction_rate = self.calculate_satisfaction_rate(&atom, trace);
|
||||
self.neural_weights.insert(atom, satisfaction_rate);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract all atoms from a formula
|
||||
fn extract_atoms(&self, formula: &TemporalFormula) -> Vec<String> {
|
||||
let mut atoms = Vec::new();
|
||||
self.extract_atoms_recursive(formula, &mut atoms);
|
||||
atoms.sort();
|
||||
atoms.dedup();
|
||||
atoms
|
||||
}
|
||||
|
||||
fn extract_atoms_recursive(&self, formula: &TemporalFormula, atoms: &mut Vec<String>) {
|
||||
match formula {
|
||||
TemporalFormula::Atom(name) => atoms.push(name.clone()),
|
||||
TemporalFormula::Not(inner) => self.extract_atoms_recursive(inner, atoms),
|
||||
TemporalFormula::And(left, right)
|
||||
| TemporalFormula::Or(left, right)
|
||||
| TemporalFormula::Implies(left, right) => {
|
||||
self.extract_atoms_recursive(left, atoms);
|
||||
self.extract_atoms_recursive(right, atoms);
|
||||
}
|
||||
TemporalFormula::Temporal(_, inner) => self.extract_atoms_recursive(inner, atoms),
|
||||
TemporalFormula::BoundedTemporal { formula, .. } => {
|
||||
self.extract_atoms_recursive(formula, atoms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate how often an atom is satisfied in a trace
|
||||
fn calculate_satisfaction_rate(&self, atom: &str, trace: &TemporalTrace) -> f64 {
|
||||
if trace.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut true_count = 0;
|
||||
for i in 0..trace.len() {
|
||||
if let Some(state) = trace.get_state(i) {
|
||||
if state.is_true(atom) {
|
||||
true_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true_count as f64 / trace.len() as f64
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TemporalNeuralSolver {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_atom_verification() {
|
||||
let mut solver = TemporalNeuralSolver::new();
|
||||
let mut trace = TemporalTrace::new();
|
||||
|
||||
let mut state = TemporalState::new(Duration::from_secs(0));
|
||||
state.set("safe".to_string(), true);
|
||||
trace.add_state(state);
|
||||
|
||||
let formula = TemporalFormula::atom("safe");
|
||||
let result = solver.verify(&formula, &trace);
|
||||
|
||||
assert!(result.holds);
|
||||
assert!(result.confidence > 0.9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eventually_operator() {
|
||||
let mut solver = TemporalNeuralSolver::new();
|
||||
let mut trace = TemporalTrace::new();
|
||||
|
||||
// Add states where "goal" becomes true at position 2
|
||||
for i in 0..3 {
|
||||
let mut state = TemporalState::new(Duration::from_secs(i));
|
||||
state.set("goal".to_string(), i == 2);
|
||||
trace.add_state(state);
|
||||
}
|
||||
|
||||
let formula = TemporalFormula::eventually(TemporalFormula::atom("goal"));
|
||||
let result = solver.verify(&formula, &trace);
|
||||
|
||||
assert!(result.holds);
|
||||
println!("Eventually result: {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_globally_operator() {
|
||||
let mut solver = TemporalNeuralSolver::new();
|
||||
let mut trace = TemporalTrace::new();
|
||||
|
||||
// Add states where "invariant" is always true
|
||||
for i in 0..5 {
|
||||
let mut state = TemporalState::new(Duration::from_secs(i));
|
||||
state.set("invariant".to_string(), true);
|
||||
trace.add_state(state);
|
||||
}
|
||||
|
||||
let formula = TemporalFormula::globally(TemporalFormula::atom("invariant"));
|
||||
let result = solver.verify(&formula, &trace);
|
||||
|
||||
assert!(result.holds);
|
||||
println!("Globally result: {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bounded_eventually() {
|
||||
let mut solver = TemporalNeuralSolver::new();
|
||||
let mut trace = TemporalTrace::new();
|
||||
|
||||
// Add states with timestamps
|
||||
for i in 0..10 {
|
||||
let mut state = TemporalState::new(Duration::from_millis(i * 100));
|
||||
state.set("event".to_string(), i == 5); // Event occurs at 500ms
|
||||
trace.add_state(state);
|
||||
}
|
||||
|
||||
// Check if event occurs within [400ms, 600ms]
|
||||
let formula = TemporalFormula::eventually_bounded(
|
||||
TemporalFormula::atom("event"),
|
||||
Duration::from_millis(400),
|
||||
Duration::from_millis(600),
|
||||
);
|
||||
|
||||
let result = solver.verify(&formula, &trace);
|
||||
assert!(result.holds);
|
||||
println!("Bounded Eventually result: {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_formula() {
|
||||
let mut solver = TemporalNeuralSolver::new();
|
||||
let mut trace = TemporalTrace::new();
|
||||
|
||||
// G(request -> F response)
|
||||
// "If request happens, response must eventually happen"
|
||||
|
||||
for i in 0..10 {
|
||||
let mut state = TemporalState::new(Duration::from_secs(i));
|
||||
state.set("request".to_string(), i == 2);
|
||||
state.set("response".to_string(), i >= 5);
|
||||
trace.add_state(state);
|
||||
}
|
||||
|
||||
let formula = TemporalFormula::globally(TemporalFormula::implies(
|
||||
TemporalFormula::atom("request"),
|
||||
TemporalFormula::eventually(TemporalFormula::atom("response")),
|
||||
));
|
||||
|
||||
let result = solver.verify(&formula, &trace);
|
||||
println!("Complex formula result: {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_learning() {
|
||||
let mut solver = TemporalNeuralSolver::new();
|
||||
let mut trace = TemporalTrace::new();
|
||||
|
||||
for i in 0..10 {
|
||||
let mut state = TemporalState::new(Duration::from_secs(i));
|
||||
state.set("pattern".to_string(), i % 2 == 0);
|
||||
trace.add_state(state);
|
||||
}
|
||||
|
||||
let formula = TemporalFormula::atom("pattern");
|
||||
solver.learn_from_trace(&formula, &trace);
|
||||
|
||||
// Check learned weight
|
||||
if let Some(&weight) = solver.neural_weights.get("pattern") {
|
||||
println!("Learned weight for 'pattern': {}", weight);
|
||||
assert!((weight - 0.5).abs() < 0.1); // Should be ~0.5 (true half the time)
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
//! Core types for the lean agentic system
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Context for agent decision-making
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Context {
|
||||
/// Current conversation history
|
||||
pub history: Vec<String>,
|
||||
|
||||
/// User preferences learned over time
|
||||
pub preferences: HashMap<String, f64>,
|
||||
|
||||
/// Session metadata
|
||||
pub session_id: String,
|
||||
|
||||
/// Environment state
|
||||
pub environment: HashMap<String, serde_json::Value>,
|
||||
|
||||
/// Timestamp
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(session_id: String) -> Self {
|
||||
Self {
|
||||
session_id,
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_message(&mut self, message: String) {
|
||||
self.history.push(message);
|
||||
self.timestamp = chrono::Utc::now().timestamp();
|
||||
}
|
||||
|
||||
pub fn set_preference(&mut self, key: String, value: f64) {
|
||||
self.preferences.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Agent state representation
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AgentState {
|
||||
/// Current goals
|
||||
pub goals: Vec<Goal>,
|
||||
|
||||
/// Beliefs about the world
|
||||
pub beliefs: HashMap<String, Belief>,
|
||||
|
||||
/// Current intentions
|
||||
pub intentions: Vec<Intention>,
|
||||
|
||||
/// Learned policies
|
||||
pub policies: Vec<Policy>,
|
||||
|
||||
/// Confidence scores
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
impl Default for AgentState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
goals: Vec::new(),
|
||||
beliefs: HashMap::new(),
|
||||
intentions: Vec::new(),
|
||||
policies: Vec::new(),
|
||||
confidence: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A goal the agent is trying to achieve
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Goal {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub priority: f64,
|
||||
pub achieved: bool,
|
||||
}
|
||||
|
||||
/// A belief about the world state
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Belief {
|
||||
pub proposition: String,
|
||||
pub confidence: f64,
|
||||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
/// An intention to perform actions
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Intention {
|
||||
pub goal_id: String,
|
||||
pub action_sequence: Vec<String>,
|
||||
pub committed: bool,
|
||||
}
|
||||
|
||||
/// A learned policy for decision-making
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Policy {
|
||||
pub condition: String,
|
||||
pub action: String,
|
||||
pub expected_reward: f64,
|
||||
pub usage_count: u64,
|
||||
}
|
||||
|
||||
/// Reward signal for learning
|
||||
pub type Reward = f64;
|
||||
|
||||
/// Stream message with metadata
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StreamMessage {
|
||||
pub content: String,
|
||||
pub metadata: HashMap<String, serde_json::Value>,
|
||||
pub timestamp: i64,
|
||||
pub sender: String,
|
||||
}
|
||||
|
||||
impl StreamMessage {
|
||||
pub fn new(content: String, sender: String) -> Self {
|
||||
Self {
|
||||
content,
|
||||
sender,
|
||||
timestamp: chrono::Utc::now().timestamp(),
|
||||
metadata: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+103
@@ -0,0 +1,103 @@
|
||||
//! MidStream: Real-Time Large Language Model Streaming Platform
|
||||
//!
|
||||
//! This library provides functionality for real-time LLM response streaming,
|
||||
//! inflight data analysis, and integration with external tools.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use midstream::{Midstream, HyprSettings, HyprServiceImpl, StreamProcessor, LLMClient};
|
||||
//! use futures::stream::BoxStream;
|
||||
//! use futures::stream::iter;
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! // Example LLM client implementation
|
||||
//! struct ExampleLLMClient;
|
||||
//!
|
||||
//! impl LLMClient for ExampleLLMClient {
|
||||
//! fn stream(&self) -> BoxStream<'static, String> {
|
||||
//! Box::pin(iter(vec![
|
||||
//! "Processing".to_string(),
|
||||
//! "the".to_string(),
|
||||
//! "stream".to_string(),
|
||||
//! ]))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! // Initialize settings
|
||||
//! let settings = HyprSettings::new()?;
|
||||
//!
|
||||
//! // Create hyprstream service
|
||||
//! let hypr_service = HyprServiceImpl::new(&settings).await?;
|
||||
//!
|
||||
//! // Create LLM client
|
||||
//! let llm_client = ExampleLLMClient;
|
||||
//!
|
||||
//! // Initialize Midstream
|
||||
//! let midstream = Midstream::new(
|
||||
//! Box::new(llm_client),
|
||||
//! Box::new(hypr_service),
|
||||
//! );
|
||||
//!
|
||||
//! // Process stream
|
||||
//! let messages = midstream.process_stream().await?;
|
||||
//! println!("Processed messages: {:?}", messages);
|
||||
//!
|
||||
//! // Get metrics
|
||||
//! let metrics = midstream.get_metrics().await;
|
||||
//! println!("Collected metrics: {:?}", metrics);
|
||||
//!
|
||||
//! // Get average sentiment for last 5 minutes
|
||||
//! let avg = midstream.get_average_sentiment(Duration::from_secs(300)).await?;
|
||||
//! println!("Average sentiment: {}", avg);
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub mod config;
|
||||
pub mod midstream;
|
||||
pub mod hypr_service;
|
||||
pub mod tests;
|
||||
pub mod lean_agentic;
|
||||
|
||||
pub use config::HyprSettings;
|
||||
pub use midstream::{
|
||||
Midstream,
|
||||
StreamProcessor,
|
||||
LLMMessage,
|
||||
LLMClient,
|
||||
HyprService,
|
||||
ToolIntegration,
|
||||
Intent,
|
||||
MetricRecord,
|
||||
TimeWindow,
|
||||
AggregateFunction,
|
||||
};
|
||||
pub use hypr_service::HyprServiceImpl;
|
||||
|
||||
// Lean Agentic Learning System exports
|
||||
pub use lean_agentic::{
|
||||
LeanAgenticSystem,
|
||||
LeanAgenticConfig,
|
||||
FormalReasoner,
|
||||
Theorem,
|
||||
Proof,
|
||||
ProofStep,
|
||||
AgenticLoop,
|
||||
Action,
|
||||
Observation,
|
||||
Plan,
|
||||
LearningSignal,
|
||||
KnowledgeGraph,
|
||||
Entity,
|
||||
Relation,
|
||||
StreamLearner,
|
||||
OnlineModel,
|
||||
AdaptationStrategy,
|
||||
AgentState,
|
||||
Context as AgentContext,
|
||||
Reward,
|
||||
};
|
||||
Vendored
+196
@@ -0,0 +1,196 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use async_trait::async_trait;
|
||||
use futures::stream::BoxStream;
|
||||
use tokio::sync::Mutex;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MetricRecord {
|
||||
pub timestamp: u64,
|
||||
pub name: String,
|
||||
pub value: f64,
|
||||
pub labels: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TimeWindow {
|
||||
Minutes(u32),
|
||||
Hours(u32),
|
||||
Days(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AggregateFunction {
|
||||
Average,
|
||||
Sum,
|
||||
Count,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Intent {
|
||||
Weather,
|
||||
Calendar,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LLMMessage {
|
||||
pub content: String,
|
||||
pub timestamp: chrono::DateTime<chrono::Utc>,
|
||||
pub intent: Option<Intent>,
|
||||
pub tool_response: Option<String>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait StreamProcessor {
|
||||
async fn process_stream(&self) -> Result<Vec<LLMMessage>, Box<dyn std::error::Error>>;
|
||||
async fn get_metrics(&self) -> Vec<MetricRecord>;
|
||||
async fn get_average_sentiment(&self, window: Duration) -> Result<f64, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
pub trait LLMClient: Send + Sync {
|
||||
fn stream(&self) -> BoxStream<'static, String>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait HyprService: Send + Sync {
|
||||
async fn ingest_metric(&self, metric: MetricRecord) -> Result<(), Box<dyn std::error::Error>>;
|
||||
async fn query_aggregate(&self, window: TimeWindow, func: AggregateFunction) -> Result<f64, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
pub trait ToolIntegration: Send + Sync {
|
||||
fn handle_weather_intent(&self, content: &str) -> Result<String, Box<dyn std::error::Error>>;
|
||||
fn handle_calendar_intent(&self, content: &str) -> Result<String, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
pub struct Midstream {
|
||||
llm_client: Box<dyn LLMClient>,
|
||||
hypr_service: Box<dyn HyprService>,
|
||||
tool_integration: Option<Box<dyn ToolIntegration>>,
|
||||
metrics: Arc<Mutex<Vec<MetricRecord>>>,
|
||||
}
|
||||
|
||||
impl Midstream {
|
||||
pub fn new(
|
||||
llm_client: Box<dyn LLMClient>,
|
||||
hypr_service: Box<dyn HyprService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
llm_client,
|
||||
hypr_service,
|
||||
tool_integration: None,
|
||||
metrics: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_tool_integration(
|
||||
llm_client: Box<dyn LLMClient>,
|
||||
hypr_service: Box<dyn HyprService>,
|
||||
tool_integration: Box<dyn ToolIntegration>,
|
||||
) -> Self {
|
||||
Self {
|
||||
llm_client,
|
||||
hypr_service,
|
||||
tool_integration: Some(tool_integration),
|
||||
metrics: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_intent(&self, content: &str) -> Intent {
|
||||
let content_lower = content.to_lowercase();
|
||||
if content_lower.contains("weather") {
|
||||
Intent::Weather
|
||||
} else if content_lower.contains("schedule") || content_lower.contains("meeting") {
|
||||
Intent::Calendar
|
||||
} else {
|
||||
Intent::None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_urgent(&self, content: &str) -> bool {
|
||||
content.to_uppercase().starts_with("URGENT")
|
||||
}
|
||||
|
||||
async fn process_message(&self, content: String) -> Result<LLMMessage, Box<dyn std::error::Error>> {
|
||||
// Validate content
|
||||
if content.is_empty() {
|
||||
return Err("Empty message content".into());
|
||||
}
|
||||
|
||||
let timestamp = chrono::Utc::now();
|
||||
let intent = self.detect_intent(&content);
|
||||
let mut tool_response = None;
|
||||
|
||||
// Handle urgent requests immediately
|
||||
if self.is_urgent(&content) && intent != Intent::None {
|
||||
if let Some(tool) = &self.tool_integration {
|
||||
tool_response = match intent {
|
||||
Intent::Weather => Some(tool.handle_weather_intent(&content)?),
|
||||
Intent::Calendar => Some(tool.handle_calendar_intent(&content)?),
|
||||
Intent::None => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let message = LLMMessage {
|
||||
content,
|
||||
timestamp,
|
||||
intent: Some(intent),
|
||||
tool_response,
|
||||
};
|
||||
|
||||
// Create and ingest metric
|
||||
let metric = MetricRecord {
|
||||
timestamp: timestamp.timestamp() as u64,
|
||||
name: "llm_stream".to_string(),
|
||||
value: message.content.len() as f64,
|
||||
labels: vec![
|
||||
("type".to_string(), "message".to_string()),
|
||||
("size".to_string(), message.content.len().to_string()),
|
||||
("intent".to_string(), format!("{:?}", message.intent)),
|
||||
("urgent".to_string(), self.is_urgent(&message.content).to_string()),
|
||||
],
|
||||
};
|
||||
|
||||
// Attempt to ingest metric and handle errors
|
||||
if let Err(e) = self.hypr_service.ingest_metric(metric.clone()).await {
|
||||
return Err(format!("Failed to ingest metric: {}", e).into());
|
||||
}
|
||||
|
||||
// Update internal metrics
|
||||
let mut metrics = self.metrics.lock().await;
|
||||
metrics.push(metric);
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StreamProcessor for Midstream {
|
||||
async fn process_stream(&self) -> Result<Vec<LLMMessage>, Box<dyn std::error::Error>> {
|
||||
use futures::StreamExt;
|
||||
|
||||
let mut messages = Vec::new();
|
||||
let mut stream = self.llm_client.stream();
|
||||
|
||||
while let Some(content) = stream.next().await {
|
||||
let message = self.process_message(content).await?;
|
||||
messages.push(message);
|
||||
}
|
||||
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
async fn get_metrics(&self) -> Vec<MetricRecord> {
|
||||
self.metrics.lock().await.clone()
|
||||
}
|
||||
|
||||
async fn get_average_sentiment(&self, window: Duration) -> Result<f64, Box<dyn std::error::Error>> {
|
||||
let minutes = window.as_secs() / 60;
|
||||
self.hypr_service.query_aggregate(
|
||||
TimeWindow::Minutes(minutes as u32),
|
||||
AggregateFunction::Average,
|
||||
).await
|
||||
}
|
||||
}
|
||||
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::midstream::{Midstream, StreamProcessor, Intent, LLMClient, HyprService, ToolIntegration, MetricRecord, TimeWindow, AggregateFunction};
|
||||
use std::time::Duration;
|
||||
use mockall::*;
|
||||
use futures::stream::{self, BoxStream};
|
||||
|
||||
type BoxError = Box<dyn std::error::Error>;
|
||||
|
||||
mock! {
|
||||
pub LLMClient {}
|
||||
impl LLMClient for LLMClient {
|
||||
fn stream(&self) -> BoxStream<'static, String>;
|
||||
}
|
||||
}
|
||||
|
||||
mock! {
|
||||
pub HyprService {}
|
||||
impl HyprService for HyprService {
|
||||
fn ingest_metric(&self, metric: MetricRecord) -> Result<(), BoxError>;
|
||||
fn query_aggregate(&self, window: TimeWindow, func: AggregateFunction) -> Result<f64, BoxError>;
|
||||
}
|
||||
}
|
||||
|
||||
mock! {
|
||||
pub ToolClient {}
|
||||
impl ToolIntegration for ToolClient {
|
||||
fn handle_weather_intent(&self, content: &str) -> Result<String, BoxError>;
|
||||
fn handle_calendar_intent(&self, content: &str) -> Result<String, BoxError>;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_processing_with_metrics() {
|
||||
let mut mock_llm = MockLLMClient::new();
|
||||
let mut mock_hypr = MockHyprService::new();
|
||||
|
||||
mock_llm.expect_stream()
|
||||
.times(1)
|
||||
.return_once(move || {
|
||||
Box::pin(stream::iter(vec![
|
||||
"Process".to_string(),
|
||||
"this".to_string(),
|
||||
"stream".to_string(),
|
||||
]))
|
||||
});
|
||||
|
||||
mock_hypr.expect_ingest_metric()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
let midstream = Midstream::new(
|
||||
Box::new(mock_llm),
|
||||
Box::new(mock_hypr),
|
||||
);
|
||||
|
||||
let result = midstream.process_stream().await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let metrics = midstream.get_metrics().await;
|
||||
assert!(!metrics.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_real_time_aggregation() {
|
||||
let mut mock_llm = MockLLMClient::new();
|
||||
let mut mock_hypr = MockHyprService::new();
|
||||
|
||||
mock_hypr.expect_query_aggregate()
|
||||
.times(1)
|
||||
.return_once(|_, _| Ok(0.75));
|
||||
|
||||
let midstream = Midstream::new(
|
||||
Box::new(mock_llm),
|
||||
Box::new(mock_hypr),
|
||||
);
|
||||
|
||||
let avg = midstream.get_average_sentiment(Duration::from_secs(300)).await;
|
||||
assert!(avg.is_ok());
|
||||
assert_eq!(avg.unwrap(), 0.75);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_error_handling() {
|
||||
let mut mock_llm = MockLLMClient::new();
|
||||
let mut mock_hypr = MockHyprService::new();
|
||||
|
||||
mock_hypr.expect_ingest_metric()
|
||||
.times(1)
|
||||
.return_once(|_| Err("Ingestion error".into()));
|
||||
|
||||
mock_llm.expect_stream()
|
||||
.times(1)
|
||||
.return_once(|| {
|
||||
Box::pin(stream::iter(vec!["test message".to_string()]))
|
||||
});
|
||||
|
||||
let midstream = Midstream::new(
|
||||
Box::new(mock_llm),
|
||||
Box::new(mock_hypr),
|
||||
);
|
||||
|
||||
let result = midstream.process_stream().await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("Failed to ingest metric"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_empty_stream() {
|
||||
let mut mock_llm = MockLLMClient::new();
|
||||
let mut mock_hypr = MockHyprService::new();
|
||||
|
||||
mock_llm.expect_stream()
|
||||
.times(1)
|
||||
.return_once(|| {
|
||||
Box::pin(stream::iter(Vec::<String>::new()))
|
||||
});
|
||||
|
||||
let midstream = Midstream::new(
|
||||
Box::new(mock_llm),
|
||||
Box::new(mock_hypr),
|
||||
);
|
||||
|
||||
let result = midstream.process_stream().await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_large_message_processing() {
|
||||
let mut mock_llm = MockLLMClient::new();
|
||||
let mut mock_hypr = MockHyprService::new();
|
||||
|
||||
let large_message = "x".repeat(1_000_000);
|
||||
mock_llm.expect_stream()
|
||||
.times(1)
|
||||
.return_once(move || {
|
||||
Box::pin(stream::iter(vec![large_message.clone()]))
|
||||
});
|
||||
|
||||
mock_hypr.expect_ingest_metric()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
let midstream = Midstream::new(
|
||||
Box::new(mock_llm),
|
||||
Box::new(mock_hypr),
|
||||
);
|
||||
|
||||
let result = midstream.process_stream().await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let messages = result.unwrap();
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0].content.len(), 1_000_000);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_inflight_decision_making() {
|
||||
let mut mock_llm = MockLLMClient::new();
|
||||
let mut mock_hypr = MockHyprService::new();
|
||||
let mut mock_tool = MockToolClient::new();
|
||||
|
||||
mock_llm.expect_stream()
|
||||
.times(1)
|
||||
.return_once(|| {
|
||||
Box::pin(stream::iter(vec![
|
||||
"URGENT: What's the weather".to_string(),
|
||||
]))
|
||||
});
|
||||
|
||||
mock_tool.expect_handle_weather_intent()
|
||||
.times(1)
|
||||
.return_once(|_| Ok("Weather info (urgent response)".to_string()));
|
||||
|
||||
mock_hypr.expect_ingest_metric()
|
||||
.returning(|_| Ok(()));
|
||||
|
||||
let midstream = Midstream::with_tool_integration(
|
||||
Box::new(mock_llm),
|
||||
Box::new(mock_hypr),
|
||||
Box::new(mock_tool),
|
||||
);
|
||||
|
||||
let result = midstream.process_stream().await;
|
||||
assert!(result.is_ok());
|
||||
let messages = result.unwrap();
|
||||
|
||||
assert_eq!(messages[0].intent, Some(Intent::Weather));
|
||||
assert!(messages[0].tool_response.as_ref().unwrap().contains("urgent response"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_empty_message_handling() {
|
||||
let mut mock_llm = MockLLMClient::new();
|
||||
let mut mock_hypr = MockHyprService::new();
|
||||
|
||||
mock_llm.expect_stream()
|
||||
.times(1)
|
||||
.return_once(|| {
|
||||
Box::pin(stream::iter(vec!["".to_string()]))
|
||||
});
|
||||
|
||||
let midstream = Midstream::new(
|
||||
Box::new(mock_llm),
|
||||
Box::new(mock_hypr),
|
||||
);
|
||||
|
||||
let result = midstream.process_stream().await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("Empty message content"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user