diff --git a/crates/zclaw-growth/src/retrieval/query.rs b/crates/zclaw-growth/src/retrieval/query.rs index 1acf69a..f204aaf 100644 --- a/crates/zclaw-growth/src/retrieval/query.rs +++ b/crates/zclaw-growth/src/retrieval/query.rs @@ -216,9 +216,10 @@ impl QueryAnalyzer { expansions } - /// Get synonyms for a keyword (simplified) + /// Get synonyms for a keyword (simplified, English + Chinese) fn get_synonyms(&self, keyword: &str) -> Option> { let synonyms: &[&str] = match keyword { + // English synonyms "code" => &["program", "script", "source"], "error" => &["bug", "issue", "problem", "exception"], "fix" => &["solve", "resolve", "repair", "patch"], @@ -226,6 +227,20 @@ impl QueryAnalyzer { "slow" => &["performance", "optimize", "speed"], "help" => &["assist", "support", "guide", "aid"], "learn" => &["study", "understand", "know", "grasp"], + // Chinese synonyms — critical for Chinese-language queries + "错误" => &["问题", "bug", "异常", "故障"], + "修复" => &["解决", "修正", "处理", "fix"], + "优化" => &["改进", "提升", "加速", "improve"], + "配置" => &["设置", "参数", "选项", "config"], + "性能" => &["速度", "效率", "performance"], + "问题" => &["错误", "故障", "issue", "problem"], + "帮助" => &["协助", "支持", "help"], + "学习" => &["了解", "掌握", "learn"], + "代码" => &["程序", "脚本", "code"], + "数据库" => &["DB", "database", "存储"], + "部署" => &["发布", "上线", "deploy"], + "测试" => &["验证", "检验", "test"], + "安全" => &["防护", "加密", "security"], _ => return None, }; diff --git a/crates/zclaw-runtime/src/middleware/memory.rs b/crates/zclaw-runtime/src/middleware/memory.rs index 2cc2fea..a4fc13b 100644 --- a/crates/zclaw-runtime/src/middleware/memory.rs +++ b/crates/zclaw-runtime/src/middleware/memory.rs @@ -60,6 +60,22 @@ impl AgentMiddleware for MemoryMiddleware { fn priority(&self) -> i32 { 150 } async fn before_completion(&self, ctx: &mut MiddlewareContext) -> Result { + // Skip memory injection for very short queries. + // Short queries (e.g., "1+6", "hi", "好") don't benefit from memory context. + // Worse, the retriever's scope-based fallback may return high-importance but + // irrelevant old memories, causing the model to think about past conversations + // instead of answering the current question. + // Use char count (not byte count) so CJK queries are handled correctly: + // a single Chinese char is 3 UTF-8 bytes but 1 meaningful character. + let query = ctx.user_input.trim(); + if query.chars().count() < 2 { + tracing::debug!( + "[MemoryMiddleware] Skipping enhancement for short query ({:?}): no memory context needed", + query + ); + return Ok(MiddlewareDecision::Continue); + } + match self.growth.enhance_prompt( &ctx.agent_id, &ctx.system_prompt, @@ -92,21 +108,27 @@ impl AgentMiddleware for MemoryMiddleware { return Ok(()); } - match self.growth.process_conversation( + // 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, - ctx.session_id.clone(), + &ctx.session_id, ).await { - Ok(count) => { + Ok(Some((mem_count, facts))) => { tracing::info!( - "[MemoryMiddleware] Extracted {} memories for agent {}", - count, + "[MemoryMiddleware] Extracted {} memories + {} structured facts for agent {}", + mem_count, + facts.len(), agent_key ); } + Ok(None) => { + tracing::debug!("[MemoryMiddleware] No memories or facts extracted"); + } Err(e) => { // Non-fatal: extraction failure should not affect the response - tracing::warn!("[MemoryMiddleware] Memory extraction failed: {}", e); + tracing::warn!("[MemoryMiddleware] Combined extraction failed: {}", e); } }