重构:架构重组 + 6轮缺陷修复
Phase 1: 工具模块统一 - 新建 tools/executor.rs — 统一工具执行器,消除 main/worker/daemon 三处重复 - tools/builtin.rs → tools/registry.rs - tools/mod.rs 新增 build_tools_list() / build_env_map(),daemon/main 共用 Phase 2: 主模块重组 context/ → core/ - core/pipeline.rs — 消息处理流水线 (从 main.rs 抽取 listen_loop) - core/session.rs — ChatSession 会话管理 - core/context.rs — 上下文构建 + token 估算 - core/summary.rs — 摘要管理 (溢出/空闲触发) - core/memory.rs — MemoryStore 长期记忆 - main.rs 精简至 ~200 行 Phase 3: listen 模式全局会话锁 + Pipeline 收尾 - PipelineContext 持有全局 conversation_lock,所有消息串行处理防串话 Phase 4: wechat/ → channel/ 重命名 缺陷修复: - daemon send_frame 失败后消息重回 waiting 队列 - daemon 审批通过后按 code_hash 精确执行,不重跑 LLM - listen 模式执行器从 session.current_user_id 动态获取用户 - listen 模式 LLM 用量统计传入实际 user_id - send_text 响应校验改用 serde_json::Value 宽松解析 - pi_subagent 工具 (JSON 事件流模式, RiskLevel::High, timeout clamp 1-300) - listen 审批超时 300s→60s,cancel_by_code 精确保护 - approval.rs 新增 cancel_by_code 方法同步 DB
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
use crate::core::session::{ChatSession, SummaryEntry, SummaryReason};
|
||||
use crate::llm::conversation::Summarizer;
|
||||
use crate::llm::types::Message;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// 触发溢出摘要:压缩 checkpoint 到当前位置之间的新消息
|
||||
pub async fn trigger_overflow_summary(
|
||||
session: &Arc<Mutex<ChatSession>>,
|
||||
summarizer: Option<&Summarizer>,
|
||||
) -> bool {
|
||||
let mut s = session.lock().await;
|
||||
if s.messages.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let prev_checkpoint = s.checkpoint;
|
||||
let end = s.messages.len();
|
||||
|
||||
// checkpoint 始终在 user+assistant 对边界,直接用它作为摘要起点
|
||||
let start = prev_checkpoint;
|
||||
if start >= end {
|
||||
return false;
|
||||
}
|
||||
|
||||
let to_summarize: Vec<_> = s.messages[start..end]
|
||||
.iter()
|
||||
.take(40) // 最多 40 条
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if to_summarize.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let summary_text = generate_summary(&to_summarize, summarizer).await;
|
||||
|
||||
s.summaries.push(SummaryEntry {
|
||||
checkpoint: prev_checkpoint,
|
||||
text: summary_text.clone(),
|
||||
reason: SummaryReason::Overflow,
|
||||
created_at: chrono::Utc::now(),
|
||||
});
|
||||
|
||||
// 持久化到数据库
|
||||
s.save_summary_to_db(&summary_text, "overflow", to_summarize.len() as i32)
|
||||
.await;
|
||||
|
||||
s.checkpoint = end;
|
||||
|
||||
// 清理太旧的消息(checkpoint 之前的保留最近 100 条用于调试)
|
||||
if s.checkpoint > 200 {
|
||||
let keep = s.checkpoint - 100;
|
||||
s.messages.drain(0..keep);
|
||||
// 调整 checkpoint 和 summary checkpoint
|
||||
let shift = keep;
|
||||
s.checkpoint -= shift;
|
||||
for sum in &mut s.summaries {
|
||||
if sum.checkpoint >= shift {
|
||||
sum.checkpoint -= shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// 触发空闲摘要(不注入,保存到 summaries 供 LLM 工具查询)
|
||||
pub async fn trigger_idle_summary(
|
||||
session: &Arc<Mutex<ChatSession>>,
|
||||
summarizer: Option<&Summarizer>,
|
||||
) {
|
||||
let mut s = session.lock().await;
|
||||
if s.messages.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let summary_text = generate_summary(&s.messages, summarizer).await;
|
||||
let cp = s.messages.len();
|
||||
|
||||
s.summaries.push(SummaryEntry {
|
||||
checkpoint: cp,
|
||||
text: summary_text.clone(),
|
||||
reason: SummaryReason::Timeout,
|
||||
created_at: chrono::Utc::now(),
|
||||
});
|
||||
|
||||
let msg_count = cp as i32;
|
||||
s.save_summary_to_db(&summary_text, "timeout", msg_count)
|
||||
.await;
|
||||
|
||||
s.checkpoint = s.messages.len();
|
||||
|
||||
// 清理旧消息(checkpoint 之前的全部清除)
|
||||
if s.checkpoint > 0 {
|
||||
let shift = s.checkpoint;
|
||||
s.messages.drain(0..shift);
|
||||
s.checkpoint = 0;
|
||||
for sum in &mut s.summaries {
|
||||
if sum.checkpoint >= shift {
|
||||
sum.checkpoint -= shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成摘要:优先使用 LLM,不可用时回退到简单截断
|
||||
async fn generate_summary(messages: &[Message], summarizer: Option<&Summarizer>) -> String {
|
||||
if let Some(summarizer) = summarizer {
|
||||
if messages.len() >= 3 {
|
||||
let prompt = build_summary_prompt(messages);
|
||||
match summarizer(prompt).await {
|
||||
Ok(text) if !text.is_empty() => {
|
||||
tracing::info!("LLM 摘要: {:.100}...", text);
|
||||
return text;
|
||||
}
|
||||
_ => tracing::warn!("LLM 摘要失败,回退到简单截断"),
|
||||
}
|
||||
}
|
||||
}
|
||||
summarize_messages(messages)
|
||||
}
|
||||
|
||||
/// 构建给 LLM 的摘要 prompt
|
||||
fn build_summary_prompt(messages: &[Message]) -> String {
|
||||
let convo: String = messages
|
||||
.iter()
|
||||
.filter(|m| {
|
||||
m.role == crate::llm::types::Role::User
|
||||
|| m.role == crate::llm::types::Role::Assistant
|
||||
})
|
||||
.map(|m| {
|
||||
let role = if m.role == crate::llm::types::Role::User {
|
||||
"用户"
|
||||
} else {
|
||||
"助手"
|
||||
};
|
||||
let content: String = m.content.chars().take(200).collect();
|
||||
format!("{}: {}", role, content)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
format!(
|
||||
"请将以下对话压缩为简短摘要,保留关键事实、用户偏好、决定和待办事项。只输出摘要,不要其他内容。\n\n对话:\n{}",
|
||||
convo
|
||||
)
|
||||
}
|
||||
|
||||
/// 简单的摘要生成(提取最近 N 条消息的关键内容)
|
||||
fn summarize_messages(messages: &[Message]) -> String {
|
||||
// 简单实现:提取用户消息的前 80 个字符作为摘要
|
||||
let lines: Vec<String> = messages
|
||||
.iter()
|
||||
.filter(|m| {
|
||||
m.role == crate::llm::types::Role::User
|
||||
|| m.role == crate::llm::types::Role::Assistant
|
||||
})
|
||||
.map(|m| {
|
||||
let role = match m.role {
|
||||
crate::llm::types::Role::User => "用户",
|
||||
crate::llm::types::Role::Assistant => "助手",
|
||||
_ => "",
|
||||
};
|
||||
let preview: String = m.content.chars().take(80).collect();
|
||||
format!("{}: {}", role, preview)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if lines.is_empty() {
|
||||
"暂无历史对话".to_string()
|
||||
} else {
|
||||
let result = format!(
|
||||
"历史对话摘要({} 条消息):\n{}",
|
||||
lines.len(),
|
||||
lines.join("\n")
|
||||
);
|
||||
if result.chars().count() > 2000 {
|
||||
format!(
|
||||
"{}...(已截断)",
|
||||
result.chars().take(2000).collect::<String>()
|
||||
)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user