Files
zclaw_openfang/crates/zclaw-runtime/src/middleware/evolution.rs
iven ee5611a2f8
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
perf(middleware): before_completion 分波并行执行
- 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+: 工具/安全中间件 → 串行
2026-04-23 23:37:57 +08:00

197 lines
5.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 进化引擎中间件
//! 在管家对话中检测并呈现"技能进化确认"提示
//! 优先级 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 仍保留
}
}