use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::mimicry::analyzer::BehaviorSignature;
use crate::mimicry::capability::Modality;
use crate::mimicry::profile::AiProfileStore;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseTemplate {
pub trigger_patterns: Vec<String>,
pub skeleton: String,
pub confidence: f64,
pub times_used: u64,
pub persona_id: String,
}
impl ResponseTemplate {
pub fn new(persona_id: &str, triggers: Vec<String>, skeleton: &str) -> Self {
ResponseTemplate {
trigger_patterns: triggers,
skeleton: skeleton.to_string(),
confidence: 0.5,
times_used: 0,
persona_id: persona_id.to_string(),
}
}
pub fn matches(&self, input: &str) -> bool {
let lower = input.to_lowercase();
self.trigger_patterns
.iter()
.any(|t| lower.contains(&t.to_lowercase()))
}
pub fn record_use(&mut self) {
self.times_used += 1;
self.confidence = (0.5 + (self.times_used as f64).ln() * 0.1).min(0.95);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToneProfile {
pub warmth: f64,
pub enthusiasm: f64,
pub formality: f64,
}
impl Default for ToneProfile {
fn default() -> Self {
ToneProfile {
warmth: 0.5,
enthusiasm: 0.5,
formality: 0.5,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StructurePrefs {
pub uses_lists: bool,
pub uses_code_blocks: bool,
pub uses_headers: bool,
pub preferred_list_marker: String,
pub avg_paragraph_sentences: usize,
}
impl Default for StructurePrefs {
fn default() -> Self {
StructurePrefs {
uses_lists: true,
uses_code_blocks: true,
uses_headers: false,
preferred_list_marker: "- ".to_string(),
avg_paragraph_sentences: 3,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CachedSignature {
pub model_id: String,
pub opening_phrases: Vec<(String, f64)>,
pub hedging_level: f64,
pub tone: ToneProfile,
pub structure: StructurePrefs,
pub source_samples: usize,
pub hit_count: u64,
pub confidence: f64,
}
impl CachedSignature {
pub fn compile_from(sig: &BehaviorSignature) -> Self {
use crate::mimicry::analyzer::PatternType;
let opening_phrases: Vec<(String, f64)> = sig
.patterns_of_type(&PatternType::Opening)
.iter()
.flat_map(|p| {
p.examples
.iter()
.map(|ex| (ex.clone(), p.frequency))
.collect::<Vec<_>>()
})
.collect();
let hedging_level = sig.hedging_level();
let tone_patterns = sig.patterns_of_type(&PatternType::Tone);
let enthusiasm = tone_patterns.iter().map(|p| p.frequency).sum::<f64>()
/ tone_patterns.len().max(1) as f64;
let tone = ToneProfile {
warmth: if enthusiasm > 0.3 { 0.7 } else { 0.4 },
enthusiasm,
formality: sig.vocabulary_complexity,
};
let structure_patterns = sig.patterns_of_type(&PatternType::Structure);
let uses_code = structure_patterns
.iter()
.any(|p| p.description.contains("code"));
let uses_lists = structure_patterns
.iter()
.any(|p| p.description.contains("list"));
let uses_numbered = structure_patterns
.iter()
.any(|p| p.description.contains("numbered"));
let structure = StructurePrefs {
uses_lists,
uses_code_blocks: uses_code,
uses_headers: false,
preferred_list_marker: if uses_numbered {
"1. ".to_string()
} else {
"- ".to_string()
},
avg_paragraph_sentences: if sig.avg_response_length > 500.0 {
4
} else {
2
},
};
CachedSignature {
model_id: sig.model_id.clone(),
opening_phrases,
hedging_level,
tone,
structure,
source_samples: sig.samples_analyzed,
hit_count: 0,
confidence: 0.5,
}
}
pub fn record_hit(&mut self) {
self.hit_count += 1;
self.confidence = (0.5 + (self.hit_count as f64).ln() * 0.08).min(0.95);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignatureCache {
cache: HashMap<String, CachedSignature>,
pub total_lookups: u64,
pub total_hits: u64,
}
impl SignatureCache {
pub fn new() -> Self {
SignatureCache {
cache: HashMap::new(),
total_lookups: 0,
total_hits: 0,
}
}
pub fn lookup(&mut self, model_id: &str) -> Option<&CachedSignature> {
self.total_lookups += 1;
if self.cache.contains_key(model_id) {
self.total_hits += 1;
if let Some(cached) = self.cache.get_mut(model_id) {
cached.record_hit();
}
self.cache.get(model_id)
} else {
None
}
}
pub fn compile_from(&mut self, sig: &BehaviorSignature) {
let cached = CachedSignature::compile_from(sig);
self.cache.insert(sig.model_id.clone(), cached);
}
pub fn warm_up(&mut self, store: &AiProfileStore) {
for id in store.ids() {
if let Some(profile) = store.get(&id) {
let mut sig = BehaviorSignature::new(&id);
sig.avg_response_length = profile.response_style.verbosity * 1000.0;
sig.vocabulary_complexity = profile.response_style.formality;
sig.question_asking_rate = profile.personality_value("autonomy").unwrap_or(0.3);
sig.samples_analyzed = 0;
for phrase in &profile.signature_phrases {
sig.patterns
.push(crate::mimicry::analyzer::ResponsePattern {
pattern_type: crate::mimicry::analyzer::PatternType::Opening,
frequency: 0.7,
examples: vec![phrase.clone()],
description: format!("Signature phrase: {}", phrase),
});
}
self.compile_from(&sig);
}
}
}
pub fn hit_rate(&self) -> f64 {
if self.total_lookups > 0 {
self.total_hits as f64 / self.total_lookups as f64
} else {
0.0
}
}
pub fn size(&self) -> usize {
self.cache.len()
}
pub fn contains(&self, model_id: &str) -> bool {
self.cache.contains_key(model_id)
}
}
impl Default for SignatureCache {
fn default() -> Self {
SignatureCache::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HotSwap {
preloaded: HashMap<String, HotSwapEntry>,
current_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HotSwapEntry {
pub persona_id: String,
pub snapshot_json: String,
pub preloaded_at: u64,
pub switch_count: u64,
}
impl HotSwap {
pub fn new() -> Self {
HotSwap {
preloaded: HashMap::new(),
current_id: None,
}
}
pub fn preload(&mut self, persona_id: &str, snapshot_json: String, iteration: u64) {
self.preloaded.insert(
persona_id.to_string(),
HotSwapEntry {
persona_id: persona_id.to_string(),
snapshot_json,
preloaded_at: iteration,
switch_count: 0,
},
);
}
pub fn switch_to(&mut self, persona_id: &str) -> Option<&str> {
if let Some(entry) = self.preloaded.get_mut(persona_id) {
entry.switch_count += 1;
self.current_id = Some(persona_id.to_string());
Some(&entry.snapshot_json)
} else {
None
}
}
pub fn current(&self) -> Option<&str> {
self.current_id.as_deref()
}
pub fn preloaded_ids(&self) -> Vec<String> {
self.preloaded.keys().cloned().collect()
}
pub fn is_preloaded(&self, persona_id: &str) -> bool {
self.preloaded.contains_key(persona_id)
}
}
impl Default for HotSwap {
fn default() -> Self {
HotSwap::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstinctiveRouter {
keyword_map: Vec<(Vec<String>, Modality, f64)>, pub confidence_threshold: f64,
}
impl InstinctiveRouter {
pub fn new() -> Self {
InstinctiveRouter {
keyword_map: vec![
(
vec![
"code".to_string(),
"function".to_string(),
"implement".to_string(),
"debug".to_string(),
"compile".to_string(),
"rust".to_string(),
"python".to_string(),
"javascript".to_string(),
"error".to_string(),
"bug".to_string(),
"```".to_string(),
"fn ".to_string(),
"def ".to_string(),
"class ".to_string(),
],
Modality::Code,
0.7,
),
(
vec![
"image".to_string(),
"picture".to_string(),
"photo".to_string(),
"screenshot".to_string(),
"visual".to_string(),
"see".to_string(),
"look at".to_string(),
"diagram".to_string(),
],
Modality::Vision,
0.6,
),
(
vec![
"think".to_string(),
"reason".to_string(),
"prove".to_string(),
"logic".to_string(),
"analyze".to_string(),
"step by step".to_string(),
"why".to_string(),
"explain".to_string(),
"derive".to_string(),
],
Modality::Reasoning,
0.5,
),
(
vec![
"audio".to_string(),
"sound".to_string(),
"music".to_string(),
"voice".to_string(),
"speech".to_string(),
"listen".to_string(),
"hear".to_string(),
"podcast".to_string(),
],
Modality::Audio,
0.6,
),
(
vec![
"call".to_string(),
"execute".to_string(),
"tool".to_string(),
"api".to_string(),
"endpoint".to_string(),
"invoke".to_string(),
],
Modality::FunctionCall,
0.5,
),
],
confidence_threshold: 0.4,
}
}
pub fn classify(&self, input: &str) -> (Modality, f64) {
let lower = input.to_lowercase();
let mut best_modality = Modality::Text;
let mut best_score = 0.0;
for (keywords, modality, base_confidence) in &self.keyword_map {
let hits = keywords
.iter()
.filter(|k| lower.contains(k.as_str()))
.count();
if hits > 0 {
let score = base_confidence * (hits as f64).sqrt() * 0.4;
if score > best_score {
best_score = score;
best_modality = modality.clone();
}
}
}
if best_score < self.confidence_threshold {
(Modality::Text, 0.8) } else {
(best_modality, best_score.min(0.95))
}
}
}
impl Default for InstinctiveRouter {
fn default() -> Self {
InstinctiveRouter::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_template_match() {
let template = ResponseTemplate::new(
"gpt4o",
vec!["hello".to_string(), "hi".to_string()],
"Certainly! {response}",
);
assert!(template.matches("Hello there!"));
assert!(template.matches("hi how are you"));
assert!(!template.matches("goodbye"));
}
#[test]
fn test_response_template_compound_confidence() {
let mut template = ResponseTemplate::new("test", vec![], "test");
assert_eq!(template.confidence, 0.5);
for _ in 0..10 {
template.record_use();
}
assert!(
template.confidence > 0.5,
"Confidence should increase with usage"
);
assert!(template.confidence <= 0.95, "Confidence should be capped");
}
#[test]
fn test_cached_signature_compile() {
let mut sig = BehaviorSignature::new("test-model");
sig.avg_response_length = 500.0;
sig.vocabulary_complexity = 0.7;
sig.samples_analyzed = 5;
let cached = CachedSignature::compile_from(&sig);
assert_eq!(cached.model_id, "test-model");
assert_eq!(cached.source_samples, 5);
assert!(cached.confidence == 0.5); }
#[test]
fn test_cached_signature_compound_hits() {
let sig = BehaviorSignature::new("test");
let mut cached = CachedSignature::compile_from(&sig);
for _ in 0..20 {
cached.record_hit();
}
assert!(
cached.confidence > 0.5,
"Confidence should compound with hits"
);
assert!(cached.hit_count == 20);
}
#[test]
fn test_signature_cache_lookup() {
let mut cache = SignatureCache::new();
let sig = BehaviorSignature::new("gpt4o");
cache.compile_from(&sig);
assert!(cache.contains("gpt4o"));
assert!(!cache.contains("unknown"));
let result = cache.lookup("gpt4o");
assert!(result.is_some());
assert_eq!(cache.total_hits, 1);
assert_eq!(cache.total_lookups, 1);
let _ = cache.lookup("unknown");
assert_eq!(cache.total_hits, 1);
assert_eq!(cache.total_lookups, 2);
}
#[test]
fn test_signature_cache_warm_up() {
let store = AiProfileStore::default();
let mut cache = SignatureCache::new();
cache.warm_up(&store);
assert!(cache.size() > 0);
assert!(cache.contains("gpt4o"));
assert!(cache.contains("claude"));
assert!(cache.contains("rustyworm"));
}
#[test]
fn test_hot_swap() {
let mut hot_swap = HotSwap::new();
hot_swap.preload("gpt4o", r#"{"profile":"gpt4o"}"#.to_string(), 0);
hot_swap.preload("claude", r#"{"profile":"claude"}"#.to_string(), 0);
assert!(hot_swap.is_preloaded("gpt4o"));
assert!(!hot_swap.is_preloaded("unknown"));
let json = hot_swap.switch_to("gpt4o");
assert!(json.is_some());
assert_eq!(hot_swap.current(), Some("gpt4o"));
let json = hot_swap.switch_to("claude");
assert!(json.is_some());
assert_eq!(hot_swap.current(), Some("claude"));
}
#[test]
fn test_instinctive_router_code() {
let router = InstinctiveRouter::new();
let (modality, confidence) = router.classify("Can you help me debug this rust function?");
assert_eq!(modality, Modality::Code);
assert!(confidence > 0.0);
}
#[test]
fn test_instinctive_router_text_default() {
let router = InstinctiveRouter::new();
let (modality, _confidence) = router.classify("Tell me about the weather today");
assert_eq!(modality, Modality::Text);
}
#[test]
fn test_instinctive_router_reasoning() {
let router = InstinctiveRouter::new();
let (modality, _) =
router.classify("Can you prove this theorem step by step and explain the logic?");
assert_eq!(modality, Modality::Reasoning);
}
#[test]
fn test_cache_serialization() {
let mut cache = SignatureCache::new();
let sig = BehaviorSignature::new("test");
cache.compile_from(&sig);
let json = serde_json::to_string(&cache).unwrap();
let restored: SignatureCache = serde_json::from_str(&json).unwrap();
assert_eq!(restored.size(), 1);
assert!(restored.contains("test"));
}
#[test]
fn test_hot_swap_serialization() {
let mut hs = HotSwap::new();
hs.preload("test", "{}".to_string(), 0);
let json = serde_json::to_string(&hs).unwrap();
let restored: HotSwap = serde_json::from_str(&json).unwrap();
assert!(restored.is_preloaded("test"));
}
}