Files
zclaw_openfang/crates/zclaw-growth/src/tracker.rs
iven b7f3d94950
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
fix(presentation): 修复 presentation 模块类型错误和语法问题
- 创建 types.ts 定义完整的类型系统
- 重写 DocumentRenderer.tsx 修复语法错误
- 重写 QuizRenderer.tsx 修复语法错误
- 重写 PresentationContainer.tsx 添加类型守卫
- 重写 TypeSwitcher.tsx 修复类型引用
- 更新 index.ts 移除不存在的 ChartRenderer 导出

审计结果:
- 类型检查: 通过
- 单元测试: 222 passed
- 构建: 成功
2026-03-26 17:19:28 +08:00

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));
}
}