feat: 更新调度配置以每4小时执行内存维护,并添加根据更新时间过滤内存范围的功能
This commit is contained in:
parent
52c94f274a
commit
4fb102644e
@ -311,7 +311,7 @@ impl SchedulerConfig {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
kind: SchedulerJobKind::InternalEvent,
|
kind: SchedulerJobKind::InternalEvent,
|
||||||
schedule: Some(SchedulerSchedule::Cron {
|
schedule: Some(SchedulerSchedule::Cron {
|
||||||
expression: "0 3 * * *".to_string(),
|
expression: "0 */4 * * *".to_string(),
|
||||||
}),
|
}),
|
||||||
startup_delay_secs: 0,
|
startup_delay_secs: 0,
|
||||||
interval_secs: 0,
|
interval_secs: 0,
|
||||||
@ -319,7 +319,7 @@ impl SchedulerConfig {
|
|||||||
payload: serde_json::json!({
|
payload: serde_json::json!({
|
||||||
"event": "memory_maintenance",
|
"event": "memory_maintenance",
|
||||||
"time_zone": time.timezone,
|
"time_zone": time.timezone,
|
||||||
"local_time": "03:00"
|
"local_time": "every_4_hours"
|
||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@ -1146,7 +1146,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
effective_jobs[0].resolved_schedule().unwrap(),
|
effective_jobs[0].resolved_schedule().unwrap(),
|
||||||
SchedulerSchedule::Cron {
|
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(
|
pub(crate) async fn run_memory_maintenance_for_all_scopes(
|
||||||
&self,
|
&self,
|
||||||
|
updated_since: Option<i64>,
|
||||||
) -> Result<Vec<MemoryMaintenanceScopeResult>, AgentError> {
|
) -> Result<Vec<MemoryMaintenanceScopeResult>, AgentError> {
|
||||||
let scope_keys = self
|
let scope_keys = if let Some(cutoff) = updated_since {
|
||||||
.store
|
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")
|
.list_memory_scope_keys("user")
|
||||||
.map_err(|err| AgentError::Other(format!("list memory scope keys error: {}", err)))?;
|
.map_err(|err| AgentError::Other(format!("list memory scope keys error: {}", err)))?
|
||||||
|
};
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
for scope_key in scope_keys {
|
for scope_key in scope_keys {
|
||||||
@ -2250,6 +2256,60 @@ mod tests {
|
|||||||
assert!(output.managed_markdown.contains("### 用户事实"));
|
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]
|
#[test]
|
||||||
fn test_apply_memory_maintenance_output_merges_and_deletes_low_value_records() {
|
fn test_apply_memory_maintenance_output_merges_and_deletes_low_value_records() {
|
||||||
let store = SessionStore::in_memory().unwrap();
|
let store = SessionStore::in_memory().unwrap();
|
||||||
|
|||||||
@ -501,7 +501,9 @@ async fn execute_internal_event(session_manager: &SessionManager, job: &RuntimeJ
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
"memory_maintenance" => {
|
"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 {
|
for result in &results {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
job_id = %job.id,
|
job_id = %job.id,
|
||||||
@ -937,9 +939,10 @@ mod tests {
|
|||||||
saved.schedule,
|
saved.schedule,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"type": "cron",
|
"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());
|
assert!(saved.next_fire_at.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -925,6 +925,29 @@ impl SessionStore {
|
|||||||
Ok(scope_keys)
|
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(
|
pub fn list_memories_for_scope(
|
||||||
&self,
|
&self,
|
||||||
scope_kind: &str,
|
scope_kind: &str,
|
||||||
@ -2429,4 +2452,51 @@ mod tests {
|
|||||||
assert_eq!(fetched.run_count, 1);
|
assert_eq!(fetched.run_count, 1);
|
||||||
assert_eq!(fetched.completed_at, Some(1_700_000_000_100));
|
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