fix(growth): HIGH-6 修复 extract_combined 合并提取空壳
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
根因: growth.rs 构造 CombinedExtraction 时硬编码 experiences: Vec::new() 和 profile_signals: default(),导致 L1 结构化经验不被提取、L2 技能进化 没有输入数据、整个进化引擎无法端到端工作。 修复: - extractor.rs: 添加 COMBINED_EXTRACTION_PROMPT 统一 prompt,单次 LLM 调用 同时输出 memories + experiences + profile_signals - extractor.rs: 添加 parse_combined_response() 解析 LLM JSON 响应 - LlmDriverForExtraction trait: 添加 extract_with_prompt() 方法(默认不支持, 退化到现有 extract() + 启发式推断) - MemoryExtractor: 添加 extract_combined() 方法,优先单次调用,失败则退化 - growth.rs: extract_combined() 使用新的合并提取替代硬编码空值 - TauriExtractionDriver: 实现 extract_with_prompt() - ProfileSignals: 添加 has_any_signal() 方法 - types.rs: ProfileSignals 无 structural 变化(字段已存在) 测试: 4 个新测试(parse_combined_response_full/minimal/invalid + extract_combined_fallback),11 个 extractor 测试全部通过
This commit is contained in:
@@ -15,7 +15,7 @@ use zclaw_growth::{
|
||||
AggregatedPattern, CombinedExtraction, EvolutionConfig, EvolutionEngine,
|
||||
ExperienceExtractor, GrowthTracker, InjectionFormat,
|
||||
LlmDriverForExtraction, MemoryExtractor, MemoryRetriever, PromptInjector,
|
||||
ProfileSignals, RetrievalResult, UserProfileUpdater, VikingAdapter,
|
||||
RetrievalResult, UserProfileUpdater, VikingAdapter,
|
||||
};
|
||||
use zclaw_memory::{ExtractedFactBatch, Fact, FactCategory, UserProfileStore};
|
||||
use zclaw_types::{AgentId, Message, Result, SessionId};
|
||||
@@ -263,8 +263,8 @@ impl GrowthIntegration {
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Combined extraction: single LLM call that produces both stored memories
|
||||
/// and structured facts, avoiding double extraction overhead.
|
||||
/// Combined extraction: single LLM call that produces stored memories,
|
||||
/// structured experiences, and profile signals — all in one pass.
|
||||
///
|
||||
/// Returns `(memory_count, Option<ExtractedFactBatch>)` on success.
|
||||
pub async fn extract_combined(
|
||||
@@ -277,25 +277,28 @@ impl GrowthIntegration {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Single LLM extraction call
|
||||
let extracted = self
|
||||
// 单次 LLM 提取:memories + experiences + profile_signals
|
||||
let combined = self
|
||||
.extractor
|
||||
.extract(messages, session_id.clone())
|
||||
.extract_combined(messages, session_id.clone())
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!("[GrowthIntegration] Combined extraction failed: {}", e);
|
||||
Vec::new()
|
||||
CombinedExtraction::default()
|
||||
});
|
||||
|
||||
if extracted.is_empty() {
|
||||
if combined.memories.is_empty()
|
||||
&& combined.experiences.is_empty()
|
||||
&& !combined.profile_signals.has_any_signal()
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mem_count = extracted.len();
|
||||
let mem_count = combined.memories.len();
|
||||
|
||||
// Store raw memories
|
||||
self.extractor
|
||||
.store_memories(&agent_id.to_string(), &extracted)
|
||||
.store_memories(&agent_id.to_string(), &combined.memories)
|
||||
.await?;
|
||||
|
||||
// Track learning event
|
||||
@@ -304,14 +307,9 @@ impl GrowthIntegration {
|
||||
.await?;
|
||||
|
||||
// Persist structured experiences (L1 enhancement)
|
||||
let combined_extraction = CombinedExtraction {
|
||||
memories: extracted.clone(),
|
||||
experiences: Vec::new(), // LLM-driven extraction fills this later
|
||||
profile_signals: ProfileSignals::default(),
|
||||
};
|
||||
if let Ok(exp_count) = self
|
||||
.experience_extractor
|
||||
.persist_experiences(&agent_id.to_string(), &combined_extraction)
|
||||
.persist_experiences(&agent_id.to_string(), &combined)
|
||||
.await
|
||||
{
|
||||
if exp_count > 0 {
|
||||
@@ -324,9 +322,7 @@ impl GrowthIntegration {
|
||||
|
||||
// Update user profile from extraction signals (L1 enhancement)
|
||||
if let Some(profile_store) = &self.profile_store {
|
||||
let updates = self
|
||||
.profile_updater
|
||||
.collect_updates(&combined_extraction);
|
||||
let updates = self.profile_updater.collect_updates(&combined);
|
||||
let user_id = agent_id.to_string();
|
||||
for update in updates {
|
||||
if let Err(e) = profile_store
|
||||
@@ -342,8 +338,9 @@ impl GrowthIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert same extracted memories to structured facts (no extra LLM call)
|
||||
let facts: Vec<Fact> = extracted
|
||||
// Convert extracted memories to structured facts
|
||||
let facts: Vec<Fact> = combined
|
||||
.memories
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
let category = match m.memory_type {
|
||||
|
||||
Reference in New Issue
Block a user