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:
iven
2026-04-02 01:21:29 +08:00
parent 07099e3ef0
commit 1bf0d3a73d
2 changed files with 44 additions and 7 deletions

View File

@@ -60,6 +60,22 @@ impl AgentMiddleware for MemoryMiddleware {
fn priority(&self) -> i32 { 150 }
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(
&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);
}
}