use crate::activity::Activity;
use crate::config::RecallWeights;
use crate::experience::Experience;
use crate::insight::DerivedInsight;
use crate::relation::ExperienceRelation;
use crate::search::{SearchFilter, SearchResult};
use crate::types::CollectiveId;
#[derive(Clone, Debug)]
pub struct ContextRequest {
pub collective_id: CollectiveId,
pub query_embedding: Vec<f32>,
pub max_similar: usize,
pub max_recent: usize,
pub include_insights: bool,
pub include_relations: bool,
pub include_active_agents: bool,
pub filter: SearchFilter,
pub recall_weights: Option<RecallWeights>,
}
impl Default for ContextRequest {
fn default() -> Self {
Self {
collective_id: CollectiveId::nil(),
query_embedding: vec![],
max_similar: 20,
max_recent: 10,
include_insights: true,
include_relations: true,
include_active_agents: true,
filter: SearchFilter::default(),
recall_weights: None,
}
}
}
#[derive(Clone, Debug)]
pub struct ContextCandidates {
pub similar_experiences: Vec<SearchResult>,
pub recent_experiences: Vec<Experience>,
pub insights: Vec<DerivedInsight>,
pub relations: Vec<ExperienceRelation>,
pub active_agents: Vec<Activity>,
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
use crate::config::{Config, RecallWeights};
use crate::types::{InstanceId, Timestamp};
use crate::PulseDB;
fn embedding_with_query_cosine(cosine: f32) -> Vec<f32> {
let mut embedding = vec![0.0; 384];
embedding[0] = cosine;
embedding[1] = (1.0 - cosine.powi(2)).sqrt();
embedding
}
#[test]
fn test_context_request_default_values() {
let req = ContextRequest::default();
assert_eq!(req.collective_id, CollectiveId::nil());
assert!(req.query_embedding.is_empty());
assert_eq!(req.max_similar, 20);
assert_eq!(req.max_recent, 10);
assert!(req.include_insights);
assert!(req.include_relations);
assert!(req.include_active_agents);
assert!(req.filter.exclude_archived);
}
#[test]
fn test_context_request_clone_and_debug() {
let req = ContextRequest {
collective_id: CollectiveId::new(),
query_embedding: vec![0.1; 384],
max_similar: 5,
..ContextRequest::default()
};
let cloned = req.clone();
assert_eq!(cloned.max_similar, 5);
assert_eq!(cloned.query_embedding.len(), 384);
let debug = format!("{:?}", req);
assert!(debug.contains("ContextRequest"));
}
#[test]
fn test_context_candidates_clone_and_debug() {
let candidates = ContextCandidates {
similar_experiences: vec![],
recent_experiences: vec![],
insights: vec![],
relations: vec![],
active_agents: vec![],
};
let cloned = candidates.clone();
assert!(cloned.similar_experiences.is_empty());
let debug = format!("{:?}", candidates);
assert!(debug.contains("ContextCandidates"));
}
#[test]
fn context_candidates_energy_reranks_fresh_ahead() {
let dir = tempfile::tempdir().unwrap();
let db = PulseDB::open(dir.path().join("context-rerank.db"), Config::default()).unwrap();
let collective_id = db.create_collective("context-rerank").unwrap();
let query_embedding = embedding_with_query_cosine(1.0);
let now = Timestamp::now();
let stale_last_reinforced =
Timestamp::from_millis(now.as_millis() - 90 * 24 * 60 * 60 * 1000);
let fresh_last_reinforced = now;
let stale_id = db
.insert_experience_backdated(
collective_id,
"stale but most similar",
embedding_with_query_cosine(0.95),
0.8,
BTreeMap::new(),
stale_last_reinforced,
)
.unwrap();
let fresh_id = db
.insert_experience_backdated(
collective_id,
"fresh reinforced",
embedding_with_query_cosine(0.90),
0.8,
BTreeMap::from([(InstanceId::new(), 24)]),
fresh_last_reinforced,
)
.unwrap();
let weighted = db
.get_context_candidates(ContextRequest {
collective_id,
query_embedding: query_embedding.clone(),
max_similar: 2,
max_recent: 2,
include_insights: false,
include_relations: false,
include_active_agents: false,
recall_weights: Some(RecallWeights::new(0.5, 0.5)),
..ContextRequest::default()
})
.unwrap();
let legacy = db
.get_context_candidates(ContextRequest {
collective_id,
query_embedding,
max_similar: 2,
max_recent: 2,
include_insights: false,
include_relations: false,
include_active_agents: false,
recall_weights: None,
..ContextRequest::default()
})
.unwrap();
assert_eq!(weighted.similar_experiences[0].experience.id, fresh_id);
assert_eq!(legacy.similar_experiences[0].experience.id, stale_id);
}
}