feat: initial Sol virtual librarian implementation
Matrix bot with E2EE (matrix-sdk 0.9) that passively archives all messages to OpenSearch and responds to queries via Mistral AI with function calling tools. Core systems: - Archive: bulk OpenSearch indexer with batch/flush, edit/redaction handling, embedding pipeline passthrough - Brain: rule-based engagement evaluator (mentions, DMs, name invocations), LLM-powered spontaneous engagement, per-room conversation context windows, response delay simulation - Tools: search_archive, get_room_context, list_rooms, get_room_members registered as Mistral function calling tools with iterative tool loop - Personality: templated system prompt with Sol's librarian persona 47 unit tests covering config, evaluator, conversation windowing, personality templates, schema serialization, and search query building.
This commit is contained in:
89
src/brain/personality.rs
Normal file
89
src/brain/personality.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use chrono::Utc;
|
||||
|
||||
pub struct Personality {
|
||||
template: String,
|
||||
}
|
||||
|
||||
impl Personality {
|
||||
pub fn new(system_prompt: String) -> Self {
|
||||
Self {
|
||||
template: system_prompt,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_system_prompt(
|
||||
&self,
|
||||
room_name: &str,
|
||||
members: &[String],
|
||||
) -> String {
|
||||
let date = Utc::now().format("%Y-%m-%d").to_string();
|
||||
let members_str = members.join(", ");
|
||||
|
||||
self.template
|
||||
.replace("{date}", &date)
|
||||
.replace("{room_name}", room_name)
|
||||
.replace("{members}", &members_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_date_substitution() {
|
||||
let p = Personality::new("Today is {date}.".to_string());
|
||||
let result = p.build_system_prompt("general", &[]);
|
||||
let today = Utc::now().format("%Y-%m-%d").to_string();
|
||||
assert_eq!(result, format!("Today is {today}."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_room_name_substitution() {
|
||||
let p = Personality::new("You are in {room_name}.".to_string());
|
||||
let result = p.build_system_prompt("design-chat", &[]);
|
||||
assert!(result.contains("design-chat"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_members_substitution() {
|
||||
let p = Personality::new("Members: {members}".to_string());
|
||||
let members = vec!["Alice".to_string(), "Bob".to_string(), "Carol".to_string()];
|
||||
let result = p.build_system_prompt("room", &members);
|
||||
assert_eq!(result, "Members: Alice, Bob, Carol");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_members() {
|
||||
let p = Personality::new("Members: {members}".to_string());
|
||||
let result = p.build_system_prompt("room", &[]);
|
||||
assert_eq!(result, "Members: ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_placeholders() {
|
||||
let template = "Date: {date}, Room: {room_name}, People: {members}".to_string();
|
||||
let p = Personality::new(template);
|
||||
let members = vec!["Sienna".to_string(), "Lonni".to_string()];
|
||||
let result = p.build_system_prompt("studio", &members);
|
||||
|
||||
let today = Utc::now().format("%Y-%m-%d").to_string();
|
||||
assert!(result.starts_with(&format!("Date: {today}")));
|
||||
assert!(result.contains("Room: studio"));
|
||||
assert!(result.contains("People: Sienna, Lonni"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_placeholders_passthrough() {
|
||||
let p = Personality::new("Static prompt with no variables.".to_string());
|
||||
let result = p.build_system_prompt("room", &["Alice".to_string()]);
|
||||
assert_eq!(result, "Static prompt with no variables.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_same_placeholder() {
|
||||
let p = Personality::new("{room_name} is great. I love {room_name}.".to_string());
|
||||
let result = p.build_system_prompt("lounge", &[]);
|
||||
assert_eq!(result, "lounge is great. I love lounge.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user