Files
sol/src/brain/personality.rs
Sienna Meridian Satterwhite 4dc20bee23 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.
2026-03-20 21:40:13 +00:00

90 lines
2.8 KiB
Rust

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.");
}
}