use std::collections::HashMap;
use chrono::Utc;
use cupel::{
ChronologicalPlacer, CompositeScorer, ContextBudget, ContextItemBuilder, ContextKind,
ContextSource, GreedySlice, KindScorer, Pipeline, RecencyScorer,
};
fn main() -> Result<(), cupel::CupelError> {
let now = Utc::now();
let items = vec![
ContextItemBuilder::new(
"You are a senior Rust engineer. Answer concisely with code examples.",
18,
)
.kind(ContextKind::new("SystemPrompt")?)
.source(ContextSource::new("Chat")?)
.timestamp(now - chrono::Duration::minutes(5))
.pinned(true)
.build()?,
ContextItemBuilder::new(
"How do I implement the Iterator trait for a custom type?",
14,
)
.kind(ContextKind::new("Message")?)
.source(ContextSource::new("Chat")?)
.timestamp(now - chrono::Duration::minutes(4))
.build()?,
ContextItemBuilder::new(
"Implement `fn next(&mut self) -> Option<Self::Item>` on your type...",
120,
)
.kind(ContextKind::new("Message")?)
.source(ContextSource::new("Chat")?)
.timestamp(now - chrono::Duration::minutes(3))
.build()?,
ContextItemBuilder::new(
"std::iter::Iterator trait — 76 provided methods, 1 required method: next()",
200,
)
.kind(ContextKind::new("ToolOutput")?)
.source(ContextSource::new("Tool")?)
.timestamp(now - chrono::Duration::minutes(2))
.tags(vec!["docs".to_string(), "stdlib".to_string()])
.build()?,
ContextItemBuilder::new("pub struct Fibonacci { curr: u64, next: u64 }", 350)
.kind(ContextKind::new("Document")?)
.source(ContextSource::new("Rag")?)
.timestamp(now - chrono::Duration::minutes(1))
.build()?,
ContextItemBuilder::new(
"Can you show how to make Fibonacci implement Iterator with a stop condition?",
16,
)
.kind(ContextKind::new("Message")?)
.source(ContextSource::new("Chat")?)
.timestamp(now)
.build()?,
ContextItemBuilder::new(
"User prefers examples using iterators over manual loops",
10,
)
.kind(ContextKind::new("Memory")?)
.timestamp(now - chrono::Duration::hours(1))
.build()?,
];
println!("Created {} candidate context items\n", items.len());
let budget = ContextBudget::new(
4096, 3000, 1024, HashMap::new(),
0.0,
)?;
println!(
"Budget: max={}, target={}, output_reserve={}\n",
budget.max_tokens(),
budget.target_tokens(),
budget.output_reserve(),
);
let scorer = CompositeScorer::new(vec![
(Box::new(RecencyScorer), 2.0),
(Box::new(KindScorer::with_default_weights()), 1.0),
])?;
let pipeline = Pipeline::builder()
.scorer(Box::new(scorer))
.slicer(Box::new(GreedySlice))
.placer(Box::new(ChronologicalPlacer))
.build()?;
let result = pipeline.run(&items, &budget)?;
println!("Pipeline selected {} items:\n", result.len());
let total_tokens: i64 = result.iter().map(|item| item.tokens()).sum();
for (i, item) in result.iter().enumerate() {
println!(
" [{}] kind={:<13} tokens={:>4} pinned={} {}",
i + 1,
item.kind(),
item.tokens(),
if item.pinned() { "yes" } else { "no " },
item.content().chars().take(60).collect::<String>(),
);
}
println!(
"\nTotal tokens used: {} / {} target",
total_tokens,
budget.target_tokens()
);
Ok(())
}