feat(memory_manage): 添加搜索功能以查找用户记忆,优化参数描述

This commit is contained in:
ooodc 2026-04-22 08:45:20 +08:00
parent 2f47a8a273
commit bc24a28275

View File

@ -23,7 +23,7 @@ impl Tool for MemoryManageTool {
} }
fn description(&self) -> &str { fn description(&self) -> &str {
"Manage user memories stored in SQLite. Supports actions: list, get, put, update, delete. Memories are scoped to the current channel and sender, and record the originating session/message when available." "Manage user memories stored in SQLite. Supports actions: list, search, get, put, update, delete. Use search first when looking for user preferences, historical facts, prior decisions, or previously stored information by keyword. Memories are scoped to the current channel and sender, and record the originating session/message when available."
} }
fn parameters_schema(&self) -> serde_json::Value { fn parameters_schema(&self) -> serde_json::Value {
@ -32,16 +32,20 @@ impl Tool for MemoryManageTool {
"properties": { "properties": {
"action": { "action": {
"type": "string", "type": "string",
"enum": ["list", "get", "put", "update", "delete"], "enum": ["list", "search", "get", "put", "update", "delete"],
"description": "Management action to perform" "description": "Management action to perform. Prefer 'search' for keyword lookup across stored memories, 'get' for an exact namespace/key lookup, and 'list' for browsing recent memories."
}, },
"namespace": { "namespace": {
"type": "string", "type": "string",
"description": "Memory namespace, such as profile, preferences, or tasks" "description": "Optional memory namespace filter, such as profile, preferences, or tasks"
},
"query": {
"type": "string",
"description": "Keyword query for full-text memory search, such as a preference, fact, name, topic, or prior decision"
}, },
"key": { "key": {
"type": "string", "type": "string",
"description": "Memory key within the namespace" "description": "Exact memory key within the namespace"
}, },
"content": { "content": {
"type": "string", "type": "string",
@ -49,7 +53,7 @@ impl Tool for MemoryManageTool {
}, },
"limit": { "limit": {
"type": "integer", "type": "integer",
"description": "Maximum number of memories to list", "description": "Maximum number of memories to return",
"minimum": 1, "minimum": 1,
"default": 20 "default": 20
} }
@ -78,6 +82,7 @@ impl Tool for MemoryManageTool {
}; };
let namespace = args.get("namespace").and_then(|value| value.as_str()); let namespace = args.get("namespace").and_then(|value| value.as_str());
let query = args.get("query").and_then(|value| value.as_str());
let key = args.get("key").and_then(|value| value.as_str()); let key = args.get("key").and_then(|value| value.as_str());
let payload = match action { let payload = match action {
@ -94,6 +99,24 @@ impl Tool for MemoryManageTool {
"memories": memories.into_iter().map(memory_to_json).collect::<Vec<_>>() "memories": memories.into_iter().map(memory_to_json).collect::<Vec<_>>()
}) })
} }
"search" => {
let query = match query {
Some(query) if !query.trim().is_empty() => query,
_ => return Ok(error_result("Missing required parameter: query")),
};
let limit = args
.get("limit")
.and_then(|value| value.as_u64())
.unwrap_or(20) as usize;
let memories = self
.store
.search_memories("user", &scope_key, query, namespace, limit)?;
json!({
"query": query,
"count": memories.len(),
"memories": memories.into_iter().map(memory_to_json).collect::<Vec<_>>()
})
}
"get" => { "get" => {
let namespace = match namespace { let namespace = match namespace {
Some(namespace) => namespace, Some(namespace) => namespace,
@ -292,6 +315,49 @@ mod tests {
assert!(get.output.contains("msg-1")); assert!(get.output.contains("msg-1"));
} }
#[tokio::test]
async fn test_memory_manage_search() {
let store = Arc::new(SessionStore::in_memory().unwrap());
let tool = MemoryManageTool::new(store);
let context = ToolContext {
channel_name: Some("feishu".to_string()),
sender_id: Some("user-1".to_string()),
chat_id: Some("chat-1".to_string()),
session_id: Some("feishu:chat-1".to_string()),
message_id: Some("msg-1".to_string()),
message_seq: Some(1),
};
let put = tool
.execute_with_context(
&context,
json!({
"action": "put",
"namespace": "profile",
"key": "editor",
"content": "Prefers rust-analyzer over clippy hints"
}),
)
.await
.unwrap();
assert!(put.success);
let search = tool
.execute_with_context(
&context,
json!({
"action": "search",
"query": "rust-analyzer",
"limit": 5
}),
)
.await
.unwrap();
assert!(search.success);
assert!(search.output.contains("rust-analyzer"));
assert!(search.output.contains("editor"));
}
#[tokio::test] #[tokio::test]
async fn test_memory_manage_requires_context() { async fn test_memory_manage_requires_context() {
let store = Arc::new(SessionStore::in_memory().unwrap()); let store = Arc::new(SessionStore::in_memory().unwrap());