// 記憶、技能、範本 (the original: user bot memories, agent skills and template skills/bots). // - Memories belong to a bot: facts it was asked to keep across chats. // - Skills are shared, named procedures; the bot sees their names and loads one when it needs it. // - Templates start a new bot with a ready role and instructions. use serde::{Deserialize, Serialize}; /// Most memories a bot keeps; the oldest go first (estimated). pub const MAX_MEMORIES: usize = 200; /// Longest memory (estimated). pub const MAX_MEMORY_CHARS: usize = 500; #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Skill { pub name: String, pub description: String, pub instructions: String, } pub struct Template { pub name: &'static str, pub description: &'static str, pub instructions: &'static str, pub shape: &'static str, pub color: &'static str, } pub const TEMPLATES: [Template; 5] = [ Template { name: "研究助理", description: "上網找資料、整理重點、附上來源", instructions: "先釐清問題,再用 web_fetch 或瀏覽器找至少兩個可靠來源。回答先給結論,再列重點,每個重點附來源網址。不確定的地方直接說不確定。", shape: "circle", color: "green" }, Template { name: "寫作夥伴", description: "幫你構思、改寫和潤稿", instructions: "先問清楚讀者和語氣。改稿時保留作者的意思,列出改了什麼和為什麼。用繁體中文、台灣用語。", shape: "cloud", color: "purple" }, Template { name: "電腦助手", description: "在 Bot 的電腦上跑指令、整理檔案、操作網頁", instructions: "預設在「Bot 的電腦」上做事。動手前先看清楚(list_dir、read_file、截圖),每一步確認結果。要動到使用者的 Mac 時先說明要做什麼。", shape: "gem", color: "blue" }, Template { name: "客服助理", description: "在 Slack、Discord、Telegram 或 Teams 回覆客人", instructions: "回答簡短、有禮貌。不確定的事不要編,說會請同事確認。不要透露內部資料或使用者的私人資訊。", shape: "pill", color: "orange" }, Template { name: "開發助手", description: "看程式、跑測試、追 GitHub 上的事", instructions: "改程式前先讀相關檔案和測試。每次修改後跑測試,回報結果。PR 或 CI 的事件來時,先看紀錄再下結論。", shape: "hexagon", color: "black" }, ]; /// Add a memory (no duplicates; the oldest drop off past the limit). pub fn remember(list: &mut Vec, fact: &str) -> Result { let f: String = fact.trim().chars().take(MAX_MEMORY_CHARS).collect(); if f.is_empty() { return Err("要記住什麼?".into()); } if list.iter().any(|m| m == &f) { return Ok(format!("已經記得:{f}")); } list.push(f.clone()); let n = list.len(); if n > MAX_MEMORIES { list.drain(..n - MAX_MEMORIES); } Ok(format!("記住了:{f}")) } /// Forget by number (1-based) or by text it contains. pub fn forget(list: &mut Vec, what: &str) -> Result { let w = what.trim(); let idx = match w.parse::() { Ok(n) if n >= 1 && n <= list.len() => Some(n - 1), _ => list.iter().position(|m| !w.is_empty() && m.contains(w)), }; match idx { Some(i) => Ok(format!("忘記了:{}", list.remove(i))), None => Err(format!("沒有記得「{w}」這件事")), } } pub fn prompt_section(memories: &[String], skills: &[Skill]) -> String { let mut s = String::new(); if !memories.is_empty() { s.push_str("\n\n你記得的事(使用者要你記住的;用 remember 新增、forget 刪除):\n"); for (i, m) in memories.iter().enumerate() { s.push_str(&format!("{}. {m}\n", i + 1)); } } else { s.push_str("\n\n使用者說「記住…」「以後都…」時,用 remember 記下來;說「忘記…」時用 forget。"); } if !skills.is_empty() { s.push_str("\n\n可用的技能(做法說明;需要時先用 use_skill 讀取完整內容再照做):\n"); for k in skills { s.push_str(&format!("- {}:{}\n", k.name, k.description)); } } s.push_str("\n使用者教你一套可以重複用的做法時,可以用 save_skill 存成技能,所有 Bot 都能用。"); s } #[cfg(test)] mod tests { use super::*; #[test] fn remember_and_forget() { let mut m = vec![]; assert!(remember(&mut m, "我喜歡靠窗的座位").unwrap().starts_with("記住了")); assert!(remember(&mut m, "我喜歡靠窗的座位").unwrap().starts_with("已經記得")); remember(&mut m, "週五不開會").unwrap(); assert_eq!(forget(&mut m, "1").unwrap(), "忘記了:我喜歡靠窗的座位"); assert!(forget(&mut m, "不存在").is_err()); assert_eq!(forget(&mut m, "週五").unwrap(), "忘記了:週五不開會"); for i in 0..(MAX_MEMORIES + 5) { remember(&mut m, &format!("事 {i}")).unwrap(); } assert_eq!(m.len(), MAX_MEMORIES); assert_eq!(m[0], "事 5"); let p = prompt_section(&m[..1], &[Skill { name: "週報".into(), description: "整理每週進度".into(), instructions: "…".into() }]); assert!(p.contains("1. 事 5") && p.contains("- 週報:整理每週進度")); } }