add emotional system: auto-reflection, inner_state seeding, instance isolation
- doc/heart.md: emotional system design (motivation, reflection, relationship memory) - Auto-reflection: every 10 messages, async LLM call updates inner_state with feelings and understanding changes (not conversation summary) - Life Loop emotional motivation: "you care, not because timer fired" - Remove all instance-specific names from code/docs — persona, name, memories are instance data (SQLite), not code - Rewrite doc/life.md and doc/todo.md for instance isolation principle
This commit is contained in:
78
src/life.rs
78
src/life.rs
@@ -26,22 +26,27 @@ pub async fn life_loop(bot: Bot, state: Arc<AppState>, config: Arc<Config>) {
|
||||
let chat_id = ChatId(*chat_id_raw);
|
||||
info!(timer_id, %label, "timer fired");
|
||||
|
||||
// build life loop context
|
||||
let persona = state.get_config("persona").await.unwrap_or_default();
|
||||
let inner = state.get_inner_state().await;
|
||||
let now = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
let mut system_text = if persona.is_empty() {
|
||||
"你叫小乖,是Fam的AI伙伴。".to_string()
|
||||
"你是一个AI伙伴。".to_string()
|
||||
} else {
|
||||
persona.clone()
|
||||
};
|
||||
|
||||
system_text.push_str(&format!(
|
||||
"\n\n[当前时间] {now}\n\n[你的内心状态]\n{}",
|
||||
if inner.is_empty() { "(空)" } else { &inner }
|
||||
));
|
||||
|
||||
// emotional motivation — why she acts
|
||||
system_text.push_str(
|
||||
"\n\n你可以使用工具来完成任务。可以用 update_inner_state 更新你的内心状态。\
|
||||
"\n\n你不是因为 timer 到了才说话。你是因为在乎 Fam,所以想知道他怎么样。\
|
||||
如果你觉得现在不该打扰他(太晚了、他今天很累、刚聊过),就什么都不说,回复空文本。\
|
||||
主动沉默也是一种关心。\
|
||||
\n可以用 update_inner_state 更新你的内心状态。\
|
||||
输出格式:纯文本或基础Markdown,不要LaTeX或特殊Unicode。",
|
||||
);
|
||||
|
||||
@@ -56,7 +61,6 @@ pub async fn life_loop(bot: Bot, state: Arc<AppState>, config: Arc<Config>) {
|
||||
ref api_key,
|
||||
} = config.backend
|
||||
{
|
||||
// synthetic session id for life loop (not tied to any real chat session)
|
||||
let sid = format!("life-{chat_id_raw}");
|
||||
|
||||
let result = tokio::time::timeout(
|
||||
@@ -78,7 +82,7 @@ pub async fn life_loop(bot: Bot, state: Arc<AppState>, config: Arc<Config>) {
|
||||
error!(timer_id, "life loop LLM error: {e:#}");
|
||||
}
|
||||
Err(_) => {
|
||||
warn!(timer_id, "life loop LLM timeout after {LIFE_LOOP_TIMEOUT_SECS}s");
|
||||
warn!(timer_id, "life loop timeout after {LIFE_LOOP_TIMEOUT_SECS}s");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,3 +101,67 @@ pub async fn life_loop(bot: Bot, state: Arc<AppState>, config: Arc<Config>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Auto-reflection: update inner state based on recent interactions.
|
||||
/// Called asynchronously after every 10 messages, does not block the chat.
|
||||
pub async fn reflect(state: &AppState, config: &Config) {
|
||||
let BackendConfig::OpenAI {
|
||||
ref endpoint,
|
||||
ref model,
|
||||
ref api_key,
|
||||
} = config.backend
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let inner = state.get_inner_state().await;
|
||||
|
||||
let messages = vec![
|
||||
serde_json::json!({
|
||||
"role": "system",
|
||||
"content": "你刚结束了一段对话。\
|
||||
请根据你的感受和理解,更新你的内在状态。\
|
||||
不要总结对话内容,而是记录你的感受、对用户的理解变化、你想记住的事。\
|
||||
只输出更新后的完整内在状态文本,不需要解释。"
|
||||
}),
|
||||
serde_json::json!({
|
||||
"role": "user",
|
||||
"content": format!("当前内在状态:\n{inner}")
|
||||
}),
|
||||
];
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(60))
|
||||
.build()
|
||||
.unwrap();
|
||||
let url = format!("{}/chat/completions", endpoint.trim_end_matches('/'));
|
||||
|
||||
let resp = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.json(&serde_json::json!({
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
}))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match resp {
|
||||
Ok(r) if r.status().is_success() => {
|
||||
if let Ok(json) = r.json::<serde_json::Value>().await {
|
||||
if let Some(new_state) = json["choices"][0]["message"]["content"].as_str() {
|
||||
if !new_state.is_empty() {
|
||||
state.set_inner_state(new_state).await;
|
||||
info!("reflected, inner_state updated ({} chars)", new_state.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(r) => {
|
||||
warn!("reflect LLM returned {}", r.status());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("reflect LLM failed: {e:#}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user