Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- MiddlewareContext 加 Clone derive, 支持并行克隆上下文 - AgentMiddleware trait 新增 parallel_safe() 默认方法 (false) - MiddlewareChain::run_before_completion 改为分波执行: 连续 2+ 个 parallel_safe 中间件用 tokio::spawn 并发执行, 各自独立修改 system_prompt, 执行完成后合并贡献 - 5 个只修改 system_prompt 的中间件标记 parallel_safe: evolution(P78), butler_router(P80), memory(P150), title(P180), skill_index(P200) - 非 parallel_safe 中间件 (compaction, dangling_tool 等) 保持串行 分波效果: Wave 1: evolution + butler_router → 并行 (省 ~0.5-1s) Wave 2: compaction → 串行 (可能修改 messages) Wave 3: memory + title + skill_index → 并行 (省 ~0.5-2s) Wave 4+: 工具/安全中间件 → 串行
197 lines
5.7 KiB
Rust
197 lines
5.7 KiB
Rust
//! 进化引擎中间件
|
||
//! 在管家对话中检测并呈现"技能进化确认"提示
|
||
//! 优先级 78(在 ButlerRouter@80 之前运行)
|
||
|
||
use async_trait::async_trait;
|
||
use std::sync::Arc;
|
||
use tokio::sync::RwLock;
|
||
use crate::middleware::{
|
||
AgentMiddleware, MiddlewareContext, MiddlewareDecision,
|
||
};
|
||
use zclaw_types::Result;
|
||
|
||
/// 待确认的进化事件
|
||
#[derive(Debug, Clone)]
|
||
pub struct PendingEvolution {
|
||
pub pattern_name: String,
|
||
pub trigger_suggestion: String,
|
||
pub description: String,
|
||
}
|
||
|
||
/// 进化引擎中间件
|
||
/// 检查是否有待确认的进化事件,根据模式:
|
||
/// - suggest 模式(默认): 注入确认提示到 system prompt
|
||
/// - auto 模式: 不注入,仅排队等待 kernel 自动处理
|
||
pub struct EvolutionMiddleware {
|
||
pending: Arc<RwLock<Vec<PendingEvolution>>>,
|
||
auto_mode: bool,
|
||
}
|
||
|
||
impl EvolutionMiddleware {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
pending: Arc::new(RwLock::new(Vec::new())),
|
||
auto_mode: false,
|
||
}
|
||
}
|
||
|
||
/// Create with auto mode enabled
|
||
pub fn new_auto() -> Self {
|
||
Self {
|
||
pending: Arc::new(RwLock::new(Vec::new())),
|
||
auto_mode: true,
|
||
}
|
||
}
|
||
|
||
/// Check if auto mode is enabled
|
||
pub fn is_auto_mode(&self) -> bool {
|
||
self.auto_mode
|
||
}
|
||
|
||
/// 添加一个待确认的进化事件
|
||
pub async fn add_pending(&self, evolution: PendingEvolution) {
|
||
let mut pending = self.pending.write().await;
|
||
if pending.len() >= 100 {
|
||
tracing::warn!(
|
||
"[EvolutionMiddleware] Pending queue full (100), dropping oldest event"
|
||
);
|
||
pending.remove(0);
|
||
}
|
||
pending.push(evolution);
|
||
}
|
||
|
||
/// 获取并清除所有待确认事件
|
||
pub async fn drain_pending(&self) -> Vec<PendingEvolution> {
|
||
let mut pending = self.pending.write().await;
|
||
std::mem::take(&mut *pending)
|
||
}
|
||
|
||
/// 当前待确认事件数量
|
||
pub async fn pending_count(&self) -> usize {
|
||
self.pending.read().await.len()
|
||
}
|
||
}
|
||
|
||
impl Default for EvolutionMiddleware {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[async_trait]
|
||
impl AgentMiddleware for EvolutionMiddleware {
|
||
fn name(&self) -> &str {
|
||
"evolution"
|
||
}
|
||
|
||
fn priority(&self) -> i32 {
|
||
78 // 在 ButlerRouter(80) 之前
|
||
}
|
||
|
||
fn parallel_safe(&self) -> bool { true }
|
||
|
||
async fn before_completion(
|
||
&self,
|
||
ctx: &mut MiddlewareContext,
|
||
) -> Result<MiddlewareDecision> {
|
||
// 先用 read lock 快速判空,避免每次对话都获取写锁
|
||
if self.pending.read().await.is_empty() {
|
||
return Ok(MiddlewareDecision::Continue);
|
||
}
|
||
|
||
// Auto mode: don't inject into prompt, leave for kernel to process
|
||
if self.auto_mode {
|
||
return Ok(MiddlewareDecision::Continue);
|
||
}
|
||
|
||
// Suggest mode: 只移除第一个事件,保留后续事件留待下次注入
|
||
let to_inject = {
|
||
let mut pending = self.pending.write().await;
|
||
if pending.is_empty() {
|
||
return Ok(MiddlewareDecision::Continue);
|
||
}
|
||
pending.remove(0)
|
||
};
|
||
|
||
let injection = format!(
|
||
"\n\n<evolution-suggestion>\n\
|
||
我注意到你经常做「{pattern}」相关的事情。\n\
|
||
我可以帮你整理成一个技能,以后直接说「{trigger}」就能用了。\n\
|
||
技能描述:{desc}\n\
|
||
如果你同意,请回复 '确认保存技能'。如果你想调整,可以告诉我怎么改。\n\
|
||
</evolution-suggestion>",
|
||
pattern = to_inject.pattern_name,
|
||
trigger = to_inject.trigger_suggestion,
|
||
desc = to_inject.description,
|
||
);
|
||
ctx.system_prompt.push_str(&injection);
|
||
|
||
tracing::info!(
|
||
"[EvolutionMiddleware] Injected evolution suggestion for: {}",
|
||
to_inject.pattern_name
|
||
);
|
||
|
||
Ok(MiddlewareDecision::Continue)
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[tokio::test]
|
||
async fn test_no_pending_continues() {
|
||
let mw = EvolutionMiddleware::new();
|
||
assert_eq!(mw.pending_count().await, 0);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_add_and_drain() {
|
||
let mw = EvolutionMiddleware::new();
|
||
mw.add_pending(PendingEvolution {
|
||
pattern_name: "报表生成".to_string(),
|
||
trigger_suggestion: "生成报表".to_string(),
|
||
description: "自动生成每日报表".to_string(),
|
||
})
|
||
.await;
|
||
assert_eq!(mw.pending_count().await, 1);
|
||
|
||
let drained = mw.drain_pending().await;
|
||
assert_eq!(drained.len(), 1);
|
||
assert_eq!(drained[0].pattern_name, "报表生成");
|
||
assert_eq!(mw.pending_count().await, 0);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_name_and_priority() {
|
||
let mw = EvolutionMiddleware::new();
|
||
assert_eq!(mw.name(), "evolution");
|
||
assert_eq!(mw.priority(), 78);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_only_first_event_injected() {
|
||
let mw = EvolutionMiddleware::new();
|
||
mw.add_pending(PendingEvolution {
|
||
pattern_name: "事件A".to_string(),
|
||
trigger_suggestion: "触发A".to_string(),
|
||
description: "描述A".to_string(),
|
||
})
|
||
.await;
|
||
mw.add_pending(PendingEvolution {
|
||
pattern_name: "事件B".to_string(),
|
||
trigger_suggestion: "触发B".to_string(),
|
||
description: "描述B".to_string(),
|
||
})
|
||
.await;
|
||
|
||
// 模拟注入:用 read 判空 + write 取第一个
|
||
let first = {
|
||
let mut pending = mw.pending.write().await;
|
||
pending.remove(0)
|
||
};
|
||
assert_eq!(first.pattern_name, "事件A");
|
||
assert_eq!(mw.pending_count().await, 1); // 事件B 仍保留
|
||
}
|
||
}
|