fix(growth): Evolution Engine 穷尽审计 3CRITICAL + 3HIGH 全部修复
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
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
C-01: ExperienceExtractor 接入 ExperienceStore - GrowthIntegration.new() 创建 ExperienceExtractor 时注入 ExperienceStore - 经验持久化路径打通:extract_combined → persist_experiences → ExperienceStore C-02+C-03: 进化触发链路全链路接通 - create_middleware_chain() 注册 EvolutionMiddleware (priority 78) - MemoryMiddleware 持有 Arc<EvolutionMiddleware> 共享引用 - after_completion 中调用 check_evolution() → 推送 PendingEvolution - EvolutionMiddleware 在下次对话前注入进化建议到 system prompt H-01: FeedbackCollector loaded 标志修复 - load() 失败时保留 loaded=false,下次 save 重试 - 日志级别 debug → warn H-03: FeedbackCollector 内部可变性 - EvolutionEngine.feedback 改为 Arc<Mutex<FeedbackCollector>> - submit_feedback() 从 &mut self → &self,支持中间件 &self 调用路径 - GrowthIntegration.initialize() 从 &mut self → &self H-05: 删除空测试 test_parse_empty_response (无 assert) H-06: infer_experiences_from_memories() fallback - Outcome::Success → Outcome::Partial (反映推断不确定性)
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
use std::sync::Arc;
|
||||
use zclaw_growth::{
|
||||
AggregatedPattern, CombinedExtraction, EvolutionConfig, EvolutionEngine,
|
||||
ExperienceExtractor, GrowthTracker, InjectionFormat,
|
||||
ExperienceExtractor, ExperienceStore, GrowthTracker, InjectionFormat,
|
||||
LlmDriverForExtraction, MemoryExtractor, MemoryRetriever, PromptInjector,
|
||||
RetrievalResult, UserProfileUpdater, VikingAdapter,
|
||||
};
|
||||
@@ -79,14 +79,15 @@ impl GrowthIntegration {
|
||||
let retriever = MemoryRetriever::new(viking.clone());
|
||||
let injector = PromptInjector::new();
|
||||
let tracker = GrowthTracker::new(viking.clone());
|
||||
let evolution_engine = Some(EvolutionEngine::new(viking));
|
||||
let evolution_engine = Some(EvolutionEngine::new(viking.clone()));
|
||||
|
||||
Self {
|
||||
retriever,
|
||||
extractor,
|
||||
injector,
|
||||
tracker,
|
||||
experience_extractor: ExperienceExtractor::new(),
|
||||
experience_extractor: ExperienceExtractor::new()
|
||||
.with_store(Arc::new(ExperienceStore::new(viking))),
|
||||
profile_updater: UserProfileUpdater::new(),
|
||||
profile_store: None,
|
||||
evolution_engine,
|
||||
@@ -120,10 +121,8 @@ impl GrowthIntegration {
|
||||
///
|
||||
/// **注意**:FeedbackCollector 内部已实现 lazy-load(首次 save() 时自动加载),
|
||||
/// 所以此方法为可选优化 — 提前加载可避免首次反馈提交时的延迟。
|
||||
/// 在中间件持有 GrowthIntegration 的场景中,由于 `&self` 限制无法调用此方法,
|
||||
/// lazy-load 机制会兜底处理。
|
||||
pub async fn initialize(&mut self) -> Result<()> {
|
||||
if let Some(ref mut engine) = self.evolution_engine {
|
||||
pub async fn initialize(&self) -> Result<()> {
|
||||
if let Some(ref engine) = self.evolution_engine {
|
||||
match engine.load_feedback().await {
|
||||
Ok(count) => {
|
||||
if count > 0 {
|
||||
|
||||
@@ -11,14 +11,17 @@ use async_trait::async_trait;
|
||||
use zclaw_types::Result;
|
||||
use crate::growth::GrowthIntegration;
|
||||
use crate::middleware::{AgentMiddleware, MiddlewareContext, MiddlewareDecision};
|
||||
use crate::middleware::evolution::EvolutionMiddleware;
|
||||
|
||||
/// Middleware that handles memory retrieval (pre-completion) and extraction (post-completion).
|
||||
///
|
||||
/// Wraps `GrowthIntegration` and delegates:
|
||||
/// - `before_completion` → `enhance_prompt()` for memory injection
|
||||
/// - `after_completion` → `process_conversation()` for memory extraction
|
||||
/// - `after_completion` → `extract_combined()` for memory extraction + evolution check
|
||||
pub struct MemoryMiddleware {
|
||||
growth: GrowthIntegration,
|
||||
/// Shared EvolutionMiddleware for pushing evolution suggestions
|
||||
evolution_mw: Option<std::sync::Arc<EvolutionMiddleware>>,
|
||||
/// Minimum seconds between extractions for the same agent (debounce).
|
||||
debounce_secs: u64,
|
||||
/// Timestamp of last extraction per agent (for debouncing).
|
||||
@@ -29,11 +32,18 @@ impl MemoryMiddleware {
|
||||
pub fn new(growth: GrowthIntegration) -> Self {
|
||||
Self {
|
||||
growth,
|
||||
evolution_mw: None,
|
||||
debounce_secs: 30,
|
||||
last_extraction: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a shared EvolutionMiddleware for pushing evolution suggestions.
|
||||
pub fn with_evolution(mut self, mw: std::sync::Arc<EvolutionMiddleware>) -> Self {
|
||||
self.evolution_mw = Some(mw);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the debounce interval in seconds.
|
||||
pub fn with_debounce_secs(mut self, secs: u64) -> Self {
|
||||
self.debounce_secs = secs;
|
||||
@@ -52,6 +62,49 @@ impl MemoryMiddleware {
|
||||
map.insert(agent_id.to_string(), now);
|
||||
true
|
||||
}
|
||||
|
||||
/// Check for evolvable patterns and push suggestions to EvolutionMiddleware.
|
||||
async fn check_and_push_evolution(&self, agent_id: &zclaw_types::AgentId) {
|
||||
let evolution_mw = match &self.evolution_mw {
|
||||
Some(mw) => mw,
|
||||
None => return,
|
||||
};
|
||||
|
||||
match self.growth.check_evolution(agent_id).await {
|
||||
Ok(patterns) if !patterns.is_empty() => {
|
||||
for pattern in &patterns {
|
||||
let trigger = pattern
|
||||
.common_steps
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| pattern.pain_pattern.clone());
|
||||
evolution_mw.add_pending(
|
||||
crate::middleware::evolution::PendingEvolution {
|
||||
pattern_name: pattern.pain_pattern.clone(),
|
||||
trigger_suggestion: trigger,
|
||||
description: format!(
|
||||
"基于 {} 次重复经验,自动固化技能",
|
||||
pattern.total_reuse
|
||||
),
|
||||
},
|
||||
).await;
|
||||
}
|
||||
tracing::info!(
|
||||
"[MemoryMiddleware] Pushed {} evolution candidates for agent {}",
|
||||
patterns.len(),
|
||||
agent_id
|
||||
);
|
||||
}
|
||||
Ok(_) => {
|
||||
tracing::debug!("[MemoryMiddleware] No evolvable patterns found");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
"[MemoryMiddleware] Evolution check failed (non-fatal): {}", e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -65,11 +118,6 @@ impl AgentMiddleware for MemoryMiddleware {
|
||||
ctx.user_input.chars().take(50).collect::<String>()
|
||||
);
|
||||
|
||||
// Retrieve relevant memories and inject into system prompt.
|
||||
// The SqliteStorage retriever now uses FTS5-only matching — if FTS5 finds
|
||||
// no relevant results, no memories are returned (no scope-based fallback).
|
||||
// This prevents irrelevant high-importance memories from leaking into
|
||||
// unrelated conversations.
|
||||
let base = &ctx.system_prompt;
|
||||
match self.growth.enhance_prompt(&ctx.agent_id, base, &ctx.user_input).await {
|
||||
Ok(enhanced) => {
|
||||
@@ -88,7 +136,6 @@ impl AgentMiddleware for MemoryMiddleware {
|
||||
Ok(MiddlewareDecision::Continue)
|
||||
}
|
||||
Err(e) => {
|
||||
// Non-fatal: retrieval failure should not block the conversation
|
||||
tracing::warn!(
|
||||
"[MemoryMiddleware] Memory retrieval failed (non-fatal): {}",
|
||||
e
|
||||
@@ -99,7 +146,6 @@ impl AgentMiddleware for MemoryMiddleware {
|
||||
}
|
||||
|
||||
async fn after_completion(&self, ctx: &MiddlewareContext) -> Result<()> {
|
||||
// Debounce: skip extraction if called too recently for this agent
|
||||
let agent_key = ctx.agent_id.to_string();
|
||||
if !self.should_extract(&agent_key) {
|
||||
tracing::debug!(
|
||||
@@ -113,8 +159,6 @@ impl AgentMiddleware for MemoryMiddleware {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Combined extraction: single LLM call produces both memories and structured facts.
|
||||
// Avoids double LLM extraction ( process_conversation + extract_structured_facts).
|
||||
match self.growth.extract_combined(
|
||||
&ctx.agent_id,
|
||||
&ctx.messages,
|
||||
@@ -127,12 +171,14 @@ impl AgentMiddleware for MemoryMiddleware {
|
||||
facts.len(),
|
||||
agent_key
|
||||
);
|
||||
|
||||
// Check for evolvable patterns after successful extraction
|
||||
self.check_and_push_evolution(&ctx.agent_id).await;
|
||||
}
|
||||
Ok(None) => {
|
||||
tracing::debug!("[MemoryMiddleware] No memories or facts extracted");
|
||||
}
|
||||
Err(e) => {
|
||||
// Non-fatal: extraction failure should not affect the response
|
||||
tracing::warn!("[MemoryMiddleware] Combined extraction failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user