feat: 更新调度配置以每4小时执行内存维护,并添加根据更新时间过滤内存范围的功能
This commit is contained in:
parent
52c94f274a
commit
4fb102644e
@ -311,7 +311,7 @@ impl SchedulerConfig {
|
||||
enabled: true,
|
||||
kind: SchedulerJobKind::InternalEvent,
|
||||
schedule: Some(SchedulerSchedule::Cron {
|
||||
expression: "0 3 * * *".to_string(),
|
||||
expression: "0 */4 * * *".to_string(),
|
||||
}),
|
||||
startup_delay_secs: 0,
|
||||
interval_secs: 0,
|
||||
@ -319,7 +319,7 @@ impl SchedulerConfig {
|
||||
payload: serde_json::json!({
|
||||
"event": "memory_maintenance",
|
||||
"time_zone": time.timezone,
|
||||
"local_time": "03:00"
|
||||
"local_time": "every_4_hours"
|
||||
}),
|
||||
}]
|
||||
}
|
||||
@ -1146,7 +1146,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
effective_jobs[0].resolved_schedule().unwrap(),
|
||||
SchedulerSchedule::Cron {
|
||||
expression: "0 3 * * *".to_string(),
|
||||
expression: "0 */4 * * *".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1168,11 +1168,17 @@ impl SessionManager {
|
||||
|
||||
pub(crate) async fn run_memory_maintenance_for_all_scopes(
|
||||
&self,
|
||||
updated_since: Option<i64>,
|
||||
) -> Result<Vec<MemoryMaintenanceScopeResult>, AgentError> {
|
||||
let scope_keys = self
|
||||
.store
|
||||
.list_memory_scope_keys("user")
|
||||
.map_err(|err| AgentError::Other(format!("list memory scope keys error: {}", err)))?;
|
||||
let scope_keys = if let Some(cutoff) = updated_since {
|
||||
self.store
|
||||
.list_memory_scope_keys_updated_since("user", cutoff)
|
||||
.map_err(|err| AgentError::Other(format!("list memory scope keys updated since error: {}", err)))?
|
||||
} else {
|
||||
self.store
|
||||
.list_memory_scope_keys("user")
|
||||
.map_err(|err| AgentError::Other(format!("list memory scope keys error: {}", err)))?
|
||||
};
|
||||
let mut results = Vec::new();
|
||||
|
||||
for scope_key in scope_keys {
|
||||
@ -2250,6 +2256,60 @@ mod tests {
|
||||
assert!(output.managed_markdown.contains("### 用户事实"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_run_memory_maintenance_for_all_scopes_returns_empty_when_no_recent_updates() {
|
||||
let provider_config = LLMProviderConfig {
|
||||
provider_type: "openai".to_string(),
|
||||
name: "maintenance-provider".to_string(),
|
||||
base_url: "http://localhost".to_string(),
|
||||
api_key: "test-key".to_string(),
|
||||
extra_headers: HashMap::new(),
|
||||
model_id: "maintenance-model".to_string(),
|
||||
temperature: Some(0.0),
|
||||
max_tokens: Some(256),
|
||||
model_extra: HashMap::new(),
|
||||
max_tool_iterations: 1,
|
||||
llm_timeout_secs: 30,
|
||||
tool_result_max_chars: 20_000,
|
||||
context_tool_result_trim_chars: 20_000,
|
||||
};
|
||||
|
||||
let session_manager = SessionManager::new(
|
||||
4,
|
||||
100,
|
||||
false,
|
||||
"Asia/Shanghai".to_string(),
|
||||
provider_config.clone(),
|
||||
HashMap::from([("default".to_string(), provider_config)]),
|
||||
Arc::new(SkillRuntime::default()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let memory = session_manager
|
||||
.store()
|
||||
.put_memory(&crate::storage::MemoryUpsert {
|
||||
scope_kind: "user".to_string(),
|
||||
scope_key: "feishu:user-1".to_string(),
|
||||
namespace: "profile".to_string(),
|
||||
memory_key: "work".to_string(),
|
||||
content: "用户在做AI产品".to_string(),
|
||||
source_type: "message".to_string(),
|
||||
source_session_id: None,
|
||||
source_message_id: None,
|
||||
source_message_seq: None,
|
||||
source_channel_name: None,
|
||||
source_chat_id: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let results = session_manager
|
||||
.run_memory_maintenance_for_all_scopes(Some(memory.updated_at + 1))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(results.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_memory_maintenance_output_merges_and_deletes_low_value_records() {
|
||||
let store = SessionStore::in_memory().unwrap();
|
||||
|
||||
@ -501,7 +501,9 @@ async fn execute_internal_event(session_manager: &SessionManager, job: &RuntimeJ
|
||||
Ok(())
|
||||
}
|
||||
"memory_maintenance" => {
|
||||
let results = session_manager.run_memory_maintenance_for_all_scopes().await?;
|
||||
let results = session_manager
|
||||
.run_memory_maintenance_for_all_scopes(job.last_fired_at)
|
||||
.await?;
|
||||
for result in &results {
|
||||
tracing::info!(
|
||||
job_id = %job.id,
|
||||
@ -937,9 +939,10 @@ mod tests {
|
||||
saved.schedule,
|
||||
serde_json::json!({
|
||||
"type": "cron",
|
||||
"expression": "0 3 * * *"
|
||||
"expression": "0 */4 * * *"
|
||||
})
|
||||
);
|
||||
assert_eq!(saved.payload.get("local_time").and_then(|value| value.as_str()), Some("every_4_hours"));
|
||||
assert!(saved.next_fire_at.is_some());
|
||||
}
|
||||
|
||||
|
||||
@ -925,6 +925,29 @@ impl SessionStore {
|
||||
Ok(scope_keys)
|
||||
}
|
||||
|
||||
pub fn list_memory_scope_keys_updated_since(
|
||||
&self,
|
||||
scope_kind: &str,
|
||||
since_timestamp: i64,
|
||||
) -> Result<Vec<String>, StorageError> {
|
||||
let conn = self.conn.lock().expect("session db mutex poisoned");
|
||||
let mut stmt = conn.prepare(
|
||||
"
|
||||
SELECT DISTINCT scope_key
|
||||
FROM memories
|
||||
WHERE scope_kind = ?1 AND updated_at > ?2
|
||||
ORDER BY scope_key ASC
|
||||
",
|
||||
)?;
|
||||
|
||||
let rows = stmt.query_map(params![scope_kind, since_timestamp], |row| row.get::<_, String>(0))?;
|
||||
let mut scope_keys = Vec::new();
|
||||
for row in rows {
|
||||
scope_keys.push(row?);
|
||||
}
|
||||
Ok(scope_keys)
|
||||
}
|
||||
|
||||
pub fn list_memories_for_scope(
|
||||
&self,
|
||||
scope_kind: &str,
|
||||
@ -2429,4 +2452,51 @@ mod tests {
|
||||
assert_eq!(fetched.run_count, 1);
|
||||
assert_eq!(fetched.completed_at, Some(1_700_000_000_100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_memory_scope_keys_updated_since_filters_recent_scopes() {
|
||||
let store = SessionStore::in_memory().unwrap();
|
||||
|
||||
let first = store
|
||||
.put_memory(&MemoryUpsert {
|
||||
scope_kind: "user".to_string(),
|
||||
scope_key: "feishu:user-1".to_string(),
|
||||
namespace: "profile".to_string(),
|
||||
memory_key: "work".to_string(),
|
||||
content: "用户在做AI产品".to_string(),
|
||||
source_type: "message".to_string(),
|
||||
source_session_id: None,
|
||||
source_message_id: None,
|
||||
source_message_seq: None,
|
||||
source_channel_name: None,
|
||||
source_chat_id: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let cutoff = first.updated_at;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
|
||||
store
|
||||
.put_memory(&MemoryUpsert {
|
||||
scope_kind: "user".to_string(),
|
||||
scope_key: "feishu:user-2".to_string(),
|
||||
namespace: "preferences".to_string(),
|
||||
memory_key: "style".to_string(),
|
||||
content: "偏好简洁表达".to_string(),
|
||||
source_type: "message".to_string(),
|
||||
source_session_id: None,
|
||||
source_message_id: None,
|
||||
source_message_seq: None,
|
||||
source_channel_name: None,
|
||||
source_chat_id: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let recent_scope_keys = store
|
||||
.list_memory_scope_keys_updated_since("user", cutoff)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(recent_scope_keys, vec!["feishu:user-2".to_string()]);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user