use std::sync::Arc;
use cel_brief::{
Brief, BriefContext, BriefMessage, BriefReceipt, Contribution, ContributionContent,
MemoryQueryStrategy, MemorySource, Source, SourceError, SourceId, SystemPromptSource,
TokenBudget, UserMessageSource,
};
use cel_memory::{
BasicMemoryProvider, ChunkKind, ChunkSource, MemoryProvider, NewMemoryChunk, NewMemorySession,
};
use serde_json::json;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let memory = Arc::new(BasicMemoryProvider::new());
let session = memory
.open_session(NewMemorySession {
caller_id: "example".into(),
title: Some("with_memory".into()),
metadata: json!(null),
})
.await?;
for content in [
"User prefers dry-run mode for any destructive file operation.",
"Q4 expense report is filed under ~/Workspace/q4-expenses.md",
"Last user-confirmed action: copy ~/Documents/draft.md to ~/Workspace/",
] {
memory
.write(NewMemoryChunk {
kind: ChunkKind::Chat,
source: ChunkSource::Embedded,
session_id: Some(session.id.clone()),
project_root: None,
caller_id: "example".into(),
content: content.into(),
metadata: json!(null),
importance: Some(0.6),
shareable: false,
pinned: false,
})
.await?;
}
let ctx = BriefContext::new(TokenBudget::default())
.with_turn(1)
.with_goal("answer the user's question")
.with_user_message("Q4 expense report");
let sources: Vec<Box<dyn Source>> = vec![
Box::new(SystemPromptSource::new(
"You are an assistant grounded in the user's local memory.",
)),
Box::new(UserMessageSource::new()),
Box::new(
MemorySource::new(memory.clone(), "example", 5)
.with_strategy(MemoryQueryStrategy::UserMessageThenGoal),
),
];
let mut system_text = String::new();
let mut messages = Vec::<BriefMessage>::new();
let mut total_contributions = 0usize;
let mut total_skipped = 0usize;
for source in &sources {
let id = source.id();
match source.contribute(&ctx).await {
Ok(contributions) => {
total_contributions += contributions.len();
println!(
"source `{id}` (priority={:?}) returned {} contribution(s)",
source.priority(),
contributions.len()
);
for c in contributions {
push_contribution(&id, c, &mut system_text, &mut messages);
}
}
Err(SourceError::Skipped(reason)) => {
total_skipped += 1;
println!("source `{id}` skipped: {reason}");
}
Err(err) => {
eprintln!("source `{id}` failed: {err}");
}
}
}
let brief = Brief {
system: if system_text.is_empty() {
None
} else {
Some(system_text)
},
messages,
tools: Vec::new(),
receipt: BriefReceipt::empty(),
};
println!();
println!(
"--- assembled Brief ({total_contributions} contributions, {total_skipped} skipped) ---"
);
if let Some(system) = &brief.system {
println!("system: {system}");
println!();
}
for (idx, msg) in brief.messages.iter().enumerate() {
match msg {
BriefMessage::Text {
role,
content,
source,
} => {
println!("[{idx}] role={role:?} src={source}");
println!(" {content}");
}
BriefMessage::Image { role, source, .. } => {
println!("[{idx}] image role={role:?} src={source}");
}
BriefMessage::ToolCall {
id, name, source, ..
} => {
println!("[{idx}] tool_call id={id} name={name} src={source}");
}
BriefMessage::ToolResult { id, source, .. } => {
println!("[{idx}] tool_result id={id} src={source}");
}
}
}
println!();
println!(
"receipt: total_tokens={} dropped={} redactions={}",
brief.receipt.total_tokens,
brief.receipt.dropped.len(),
brief.receipt.redactions.len()
);
Ok(())
}
fn push_contribution(
source_id: &SourceId,
contribution: Contribution,
system_text: &mut String,
messages: &mut Vec<BriefMessage>,
) {
match contribution.content {
ContributionContent::System { text } => {
if !system_text.is_empty() {
system_text.push_str("\n\n");
}
system_text.push_str(&text);
}
ContributionContent::Text { role, content } => {
messages.push(BriefMessage::Text {
role,
content,
source: source_id.clone(),
});
}
ContributionContent::Image { role, data, alt } => {
messages.push(BriefMessage::Image {
role,
data,
alt,
source: source_id.clone(),
});
}
ContributionContent::ToolCall {
id: call_id,
name,
args,
} => {
messages.push(BriefMessage::ToolCall {
id: call_id,
name,
args,
source: source_id.clone(),
});
}
ContributionContent::ToolResult {
id: call_id,
content,
} => {
messages.push(BriefMessage::ToolResult {
id: call_id,
content,
source: source_id.clone(),
});
}
ContributionContent::Tool { schema: _ } => {
}
}
}