Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- 创建 types.ts 定义完整的类型系统 - 重写 DocumentRenderer.tsx 修复语法错误 - 重写 QuizRenderer.tsx 修复语法错误 - 重写 PresentationContainer.tsx 添加类型守卫 - 重写 TypeSwitcher.tsx 修复类型引用 - 更新 index.ts 移除不存在的 ChartRenderer 导出 审计结果: - 类型检查: 通过 - 单元测试: 222 passed - 构建: 成功
213 lines
6.5 KiB
Rust
213 lines
6.5 KiB
Rust
//! Growth Tracker - Tracks agent growth metrics and evolution
|
|
//!
|
|
//! This module provides the `GrowthTracker` which monitors and records
|
|
//! the evolution of an agent's capabilities and knowledge over time.
|
|
|
|
use crate::types::{GrowthStats, MemoryType};
|
|
use crate::viking_adapter::VikingAdapter;
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use zclaw_types::{AgentId, Result};
|
|
|
|
/// Growth Tracker - tracks agent growth metrics
|
|
pub struct GrowthTracker {
|
|
/// OpenViking adapter for storage
|
|
viking: Arc<VikingAdapter>,
|
|
}
|
|
|
|
impl GrowthTracker {
|
|
/// Create a new growth tracker
|
|
pub fn new(viking: Arc<VikingAdapter>) -> Self {
|
|
Self { viking }
|
|
}
|
|
|
|
/// Get current growth statistics for an agent
|
|
pub async fn get_stats(&self, agent_id: &AgentId) -> Result<GrowthStats> {
|
|
// Query all memories for the agent
|
|
let memories = self.viking.find_by_prefix(&format!("agent://{}", agent_id)).await?;
|
|
|
|
let mut stats = GrowthStats::default();
|
|
stats.total_memories = memories.len();
|
|
|
|
for memory in &memories {
|
|
match memory.memory_type {
|
|
MemoryType::Preference => stats.preference_count += 1,
|
|
MemoryType::Knowledge => stats.knowledge_count += 1,
|
|
MemoryType::Experience => stats.experience_count += 1,
|
|
MemoryType::Session => stats.sessions_processed += 1,
|
|
}
|
|
}
|
|
|
|
// Get last learning time from metadata
|
|
let meta: Option<AgentMetadata> = self.viking
|
|
.get_metadata(&format!("agent://{}", agent_id))
|
|
.await?;
|
|
|
|
if let Some(meta) = meta {
|
|
stats.last_learning_time = meta.last_learning_time;
|
|
}
|
|
|
|
Ok(stats)
|
|
}
|
|
|
|
/// Record a learning event
|
|
pub async fn record_learning(
|
|
&self,
|
|
agent_id: &AgentId,
|
|
session_id: &str,
|
|
memories_extracted: usize,
|
|
) -> Result<()> {
|
|
let event = LearningEvent {
|
|
agent_id: agent_id.to_string(),
|
|
session_id: session_id.to_string(),
|
|
memories_extracted,
|
|
timestamp: Utc::now(),
|
|
};
|
|
|
|
// Store learning event
|
|
self.viking
|
|
.store_metadata(
|
|
&format!("agent://{}/events/{}", agent_id, session_id),
|
|
&event,
|
|
)
|
|
.await?;
|
|
|
|
// Update last learning time
|
|
self.viking
|
|
.store_metadata(
|
|
&format!("agent://{}", agent_id),
|
|
&AgentMetadata {
|
|
last_learning_time: Some(Utc::now()),
|
|
total_learning_events: None, // Will be computed
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
tracing::info!(
|
|
"[GrowthTracker] Recorded learning event: agent={}, session={}, memories={}",
|
|
agent_id,
|
|
session_id,
|
|
memories_extracted
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get growth timeline for an agent
|
|
pub async fn get_timeline(&self, agent_id: &AgentId) -> Result<Vec<LearningEvent>> {
|
|
let memories = self
|
|
.viking
|
|
.find_by_prefix(&format!("agent://{}/events/", agent_id))
|
|
.await?;
|
|
|
|
// Parse events from stored memory content
|
|
let mut timeline = Vec::new();
|
|
for memory in memories {
|
|
if let Ok(event) = serde_json::from_str::<LearningEvent>(&memory.content) {
|
|
timeline.push(event);
|
|
}
|
|
}
|
|
|
|
// Sort by timestamp descending
|
|
timeline.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
|
|
|
|
Ok(timeline)
|
|
}
|
|
|
|
/// Calculate growth velocity (memories per day)
|
|
pub async fn get_growth_velocity(&self, agent_id: &AgentId) -> Result<f64> {
|
|
let timeline = self.get_timeline(agent_id).await?;
|
|
|
|
if timeline.is_empty() {
|
|
return Ok(0.0);
|
|
}
|
|
|
|
// Get first and last event
|
|
let first = timeline.iter().min_by_key(|e| e.timestamp);
|
|
let last = timeline.iter().max_by_key(|e| e.timestamp);
|
|
|
|
match (first, last) {
|
|
(Some(first), Some(last)) => {
|
|
let days = (last.timestamp - first.timestamp).num_days().max(1) as f64;
|
|
let total_memories: usize = timeline.iter().map(|e| e.memories_extracted).sum();
|
|
Ok(total_memories as f64 / days)
|
|
}
|
|
_ => Ok(0.0),
|
|
}
|
|
}
|
|
|
|
/// Get memory distribution by category
|
|
pub async fn get_memory_distribution(
|
|
&self,
|
|
agent_id: &AgentId,
|
|
) -> Result<HashMap<String, usize>> {
|
|
let memories = self.viking.find_by_prefix(&format!("agent://{}", agent_id)).await?;
|
|
|
|
let mut distribution = HashMap::new();
|
|
for memory in memories {
|
|
*distribution.entry(memory.memory_type.to_string()).or_insert(0) += 1;
|
|
}
|
|
|
|
Ok(distribution)
|
|
}
|
|
}
|
|
|
|
/// Learning event record
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LearningEvent {
|
|
/// Agent ID
|
|
pub agent_id: String,
|
|
/// Session ID where learning occurred
|
|
pub session_id: String,
|
|
/// Number of memories extracted
|
|
pub memories_extracted: usize,
|
|
/// Event timestamp
|
|
pub timestamp: DateTime<Utc>,
|
|
}
|
|
|
|
/// Agent metadata stored in OpenViking
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AgentMetadata {
|
|
/// Last learning time
|
|
pub last_learning_time: Option<DateTime<Utc>>,
|
|
/// Total learning events (computed)
|
|
pub total_learning_events: Option<usize>,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_learning_event_serialization() {
|
|
let event = LearningEvent {
|
|
agent_id: "test-agent".to_string(),
|
|
session_id: "test-session".to_string(),
|
|
memories_extracted: 5,
|
|
timestamp: Utc::now(),
|
|
};
|
|
|
|
let json = serde_json::to_string(&event).unwrap();
|
|
let parsed: LearningEvent = serde_json::from_str(&json).unwrap();
|
|
|
|
assert_eq!(parsed.agent_id, event.agent_id);
|
|
assert_eq!(parsed.memories_extracted, event.memories_extracted);
|
|
}
|
|
|
|
#[test]
|
|
fn test_agent_metadata_serialization() {
|
|
let meta = AgentMetadata {
|
|
last_learning_time: Some(Utc::now()),
|
|
total_learning_events: Some(10),
|
|
};
|
|
|
|
let json = serde_json::to_string(&meta).unwrap();
|
|
let parsed: AgentMetadata = serde_json::from_str(&json).unwrap();
|
|
|
|
assert!(parsed.last_learning_time.is_some());
|
|
assert_eq!(parsed.total_learning_events, Some(10));
|
|
}
|
|
}
|