fix(growth): 记忆召回跨 agent fallback — IdentityRecall 全局 scope 检索
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
B14 根因: 记忆按 agent_id 隔离存储,用户换对话/Agent 后 新 agent_id scope 下无记忆可检索,导致"我叫什么"无法召回。 修复: retrieve_broad_identity 在当前 agent 无结果时 fallback 到 retrieve_by_scope_any_agent,跨所有 agent 检索身份相关 的 preference/knowledge 记忆(用户名、工作单位等)。 影响范围: 仅 IdentityRecall 路径("我是谁"/"我叫什么"类查询), 普通 keyword 检索仍按 agent_id scope 隔离。
This commit is contained in:
@@ -331,6 +331,36 @@ impl MemoryRetriever {
|
|||||||
self.config.experience_budget,
|
self.config.experience_budget,
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
|
// Fallback: if no results for this agent, search across ALL agents
|
||||||
|
// for identity-critical info (user name, workplace, preferences)
|
||||||
|
if preferences.is_empty() && knowledge.is_empty() && experience.is_empty() {
|
||||||
|
tracing::info!(
|
||||||
|
"[MemoryRetriever] No memories for agent {}, falling back to global scope",
|
||||||
|
agent_str
|
||||||
|
);
|
||||||
|
let global_prefs = self.retrieve_by_scope_any_agent(
|
||||||
|
MemoryType::Preference,
|
||||||
|
self.config.max_results_per_type,
|
||||||
|
self.config.preference_budget,
|
||||||
|
).await?;
|
||||||
|
let global_knowledge = self.retrieve_by_scope_any_agent(
|
||||||
|
MemoryType::Knowledge,
|
||||||
|
self.config.max_results_per_type,
|
||||||
|
self.config.knowledge_budget,
|
||||||
|
).await?;
|
||||||
|
let total: usize = global_prefs.iter()
|
||||||
|
.chain(global_knowledge.iter())
|
||||||
|
.map(|m| m.estimated_tokens())
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
return Ok(RetrievalResult {
|
||||||
|
preferences: global_prefs,
|
||||||
|
knowledge: global_knowledge,
|
||||||
|
experience,
|
||||||
|
total_tokens: total,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let total_tokens = preferences.iter()
|
let total_tokens = preferences.iter()
|
||||||
.chain(knowledge.iter())
|
.chain(knowledge.iter())
|
||||||
.chain(experience.iter())
|
.chain(experience.iter())
|
||||||
@@ -352,6 +382,43 @@ impl MemoryRetriever {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve memories across ALL agents for a given type.
|
||||||
|
/// Used as fallback when agent-scoped retrieval returns nothing for identity recall.
|
||||||
|
async fn retrieve_by_scope_any_agent(
|
||||||
|
&self,
|
||||||
|
memory_type: MemoryType,
|
||||||
|
max_results: usize,
|
||||||
|
token_budget: usize,
|
||||||
|
) -> Result<Vec<MemoryEntry>> {
|
||||||
|
// Match any agent by using only the type suffix as scope pattern
|
||||||
|
let scope_pattern = format!("/{}", memory_type);
|
||||||
|
let options = FindOptions {
|
||||||
|
scope: None, // No scope filter — search all agents
|
||||||
|
limit: Some(max_results * 3),
|
||||||
|
min_similarity: None,
|
||||||
|
};
|
||||||
|
let entries = self.viking.find("", options).await?;
|
||||||
|
// Filter to only matching memory type
|
||||||
|
let mut filtered: Vec<MemoryEntry> = entries
|
||||||
|
.into_iter()
|
||||||
|
.filter(|e| e.uri.contains(&scope_pattern) || e.memory_type == memory_type)
|
||||||
|
.collect();
|
||||||
|
filtered.sort_by(|a, b| {
|
||||||
|
b.importance.cmp(&a.importance)
|
||||||
|
.then_with(|| b.access_count.cmp(&a.access_count))
|
||||||
|
});
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut used_tokens = 0;
|
||||||
|
for entry in filtered {
|
||||||
|
let tokens = entry.estimated_tokens();
|
||||||
|
if used_tokens + tokens > token_budget { break; }
|
||||||
|
used_tokens += tokens;
|
||||||
|
result.push(entry);
|
||||||
|
if result.len() >= max_results { break; }
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve memories by scope only (no text search).
|
/// Retrieve memories by scope only (no text search).
|
||||||
/// Returns entries sorted by importance and recency, limited by budget.
|
/// Returns entries sorted by importance and recency, limited by budget.
|
||||||
async fn retrieve_by_scope(
|
async fn retrieve_by_scope(
|
||||||
|
|||||||
Reference in New Issue
Block a user