fix(memory): CJK-aware short query threshold + Chinese synonym expansion
1. MemoryMiddleware: replace byte-length check (query.len() < 4) with char-count check (query.chars().count() < 2). Single CJK characters are 3 UTF-8 bytes but 1 meaningful character — the old threshold incorrectly skipped 1-2 char Chinese queries like "你好". 2. QueryAnalyzer: add Chinese synonym mappings for 13 common technical terms (错误→bug, 优化→improve, 配置→config, etc.) so CJK queries can find relevant English-keyword memories and vice versa. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -216,9 +216,10 @@ impl QueryAnalyzer {
|
|||||||
expansions
|
expansions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get synonyms for a keyword (simplified)
|
/// Get synonyms for a keyword (simplified, English + Chinese)
|
||||||
fn get_synonyms(&self, keyword: &str) -> Option<Vec<String>> {
|
fn get_synonyms(&self, keyword: &str) -> Option<Vec<String>> {
|
||||||
let synonyms: &[&str] = match keyword {
|
let synonyms: &[&str] = match keyword {
|
||||||
|
// English synonyms
|
||||||
"code" => &["program", "script", "source"],
|
"code" => &["program", "script", "source"],
|
||||||
"error" => &["bug", "issue", "problem", "exception"],
|
"error" => &["bug", "issue", "problem", "exception"],
|
||||||
"fix" => &["solve", "resolve", "repair", "patch"],
|
"fix" => &["solve", "resolve", "repair", "patch"],
|
||||||
@@ -226,6 +227,20 @@ impl QueryAnalyzer {
|
|||||||
"slow" => &["performance", "optimize", "speed"],
|
"slow" => &["performance", "optimize", "speed"],
|
||||||
"help" => &["assist", "support", "guide", "aid"],
|
"help" => &["assist", "support", "guide", "aid"],
|
||||||
"learn" => &["study", "understand", "know", "grasp"],
|
"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,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,22 @@ impl AgentMiddleware for MemoryMiddleware {
|
|||||||
fn priority(&self) -> i32 { 150 }
|
fn priority(&self) -> i32 { 150 }
|
||||||
|
|
||||||
async fn before_completion(&self, ctx: &mut MiddlewareContext) -> Result<MiddlewareDecision> {
|
async fn before_completion(&self, ctx: &mut MiddlewareContext) -> Result<MiddlewareDecision> {
|
||||||
|
// 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(
|
match self.growth.enhance_prompt(
|
||||||
&ctx.agent_id,
|
&ctx.agent_id,
|
||||||
&ctx.system_prompt,
|
&ctx.system_prompt,
|
||||||
@@ -92,21 +108,27 @@ impl AgentMiddleware for MemoryMiddleware {
|
|||||||
return Ok(());
|
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.agent_id,
|
||||||
&ctx.messages,
|
&ctx.messages,
|
||||||
ctx.session_id.clone(),
|
&ctx.session_id,
|
||||||
).await {
|
).await {
|
||||||
Ok(count) => {
|
Ok(Some((mem_count, facts))) => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"[MemoryMiddleware] Extracted {} memories for agent {}",
|
"[MemoryMiddleware] Extracted {} memories + {} structured facts for agent {}",
|
||||||
count,
|
mem_count,
|
||||||
|
facts.len(),
|
||||||
agent_key
|
agent_key
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
tracing::debug!("[MemoryMiddleware] No memories or facts extracted");
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Non-fatal: extraction failure should not affect the response
|
// Non-fatal: extraction failure should not affect the response
|
||||||
tracing::warn!("[MemoryMiddleware] Memory extraction failed: {}", e);
|
tracing::warn!("[MemoryMiddleware] Combined extraction failed: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user