Compare commits
3 Commits
e712fd7645
...
23b7497b12
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23b7497b12 | ||
|
|
bad36aa412 | ||
|
|
86ba3b447e |
@ -4,8 +4,6 @@ use crate::command::context::CommandContext;
|
|||||||
use crate::command::handler::{CommandHandler, InChatCommandHandler};
|
use crate::command::handler::{CommandHandler, InChatCommandHandler};
|
||||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||||
use crate::command::Command;
|
use crate::command::Command;
|
||||||
use crate::config::LLMProviderConfig;
|
|
||||||
use crate::gateway::agent_prompt_provider::SimpleAgentPromptProvider;
|
|
||||||
use crate::storage::{SessionRecord, SessionStore};
|
use crate::storage::{SessionRecord, SessionStore};
|
||||||
use crate::agent::AgentError;
|
use crate::agent::AgentError;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@ -29,7 +27,7 @@ pub async fn save_session_to_file(
|
|||||||
filepath: Option<String>,
|
filepath: Option<String>,
|
||||||
include_all: bool,
|
include_all: bool,
|
||||||
store: &SessionStore,
|
store: &SessionStore,
|
||||||
provider_config: &LLMProviderConfig,
|
system_prompt_provider: &dyn SystemPromptProvider,
|
||||||
) -> Result<PathBuf, String> {
|
) -> Result<PathBuf, String> {
|
||||||
// 获取会话记录
|
// 获取会话记录
|
||||||
let record = store
|
let record = store
|
||||||
@ -51,8 +49,8 @@ pub async fn save_session_to_file(
|
|||||||
// 计算用户消息数(用于系统提示词构建)
|
// 计算用户消息数(用于系统提示词构建)
|
||||||
let user_message_count = messages.iter().filter(|m| m.role == "user").count();
|
let user_message_count = messages.iter().filter(|m| m.role == "user").count();
|
||||||
|
|
||||||
// 构建系统提示词
|
// 构建系统提示词(使用外部传入的提供者)
|
||||||
let system_prompt = build_system_prompt(provider_config, &record, user_message_count);
|
let system_prompt = build_system_prompt(system_prompt_provider, &record, user_message_count);
|
||||||
|
|
||||||
// 生成 Markdown 内容
|
// 生成 Markdown 内容
|
||||||
let markdown = generate_markdown(&record, &system_prompt, &messages);
|
let markdown = generate_markdown(&record, &system_prompt, &messages);
|
||||||
@ -80,7 +78,7 @@ pub async fn save_session_to_file(
|
|||||||
/// 将当前会话内容(系统提示词和消息历史)保存到 Markdown 文件
|
/// 将当前会话内容(系统提示词和消息历史)保存到 Markdown 文件
|
||||||
pub struct SaveSessionCommandHandler {
|
pub struct SaveSessionCommandHandler {
|
||||||
store: Arc<SessionStore>,
|
store: Arc<SessionStore>,
|
||||||
provider_config: LLMProviderConfig,
|
system_prompt_provider: Arc<dyn SystemPromptProvider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SaveSessionCommandHandler {
|
impl SaveSessionCommandHandler {
|
||||||
@ -88,11 +86,11 @@ impl SaveSessionCommandHandler {
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `store` - 会话存储
|
/// * `store` - 会话存储
|
||||||
/// * `provider_config` - LLM 提供者配置(用于构建系统提示词)
|
/// * `system_prompt_provider` - 系统提示词提供者(负责构建完整的系统提示词)
|
||||||
pub fn new(store: Arc<SessionStore>, provider_config: LLMProviderConfig) -> Self {
|
pub fn new(store: Arc<SessionStore>, system_prompt_provider: Arc<dyn SystemPromptProvider>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
store,
|
store,
|
||||||
provider_config,
|
system_prompt_provider,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +139,7 @@ async fn handle_save_session(
|
|||||||
filepath,
|
filepath,
|
||||||
include_all,
|
include_all,
|
||||||
&*handler.store,
|
&*handler.store,
|
||||||
&handler.provider_config,
|
&*handler.system_prompt_provider,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CommandError::new("SAVE_ERROR", e))?;
|
.map_err(|e| CommandError::new("SAVE_ERROR", e))?;
|
||||||
@ -170,11 +168,10 @@ async fn handle_save_session(
|
|||||||
|
|
||||||
/// 构建系统提示词
|
/// 构建系统提示词
|
||||||
fn build_system_prompt(
|
fn build_system_prompt(
|
||||||
provider_config: &LLMProviderConfig,
|
provider: &dyn SystemPromptProvider,
|
||||||
record: &SessionRecord,
|
record: &SessionRecord,
|
||||||
user_message_count: usize,
|
user_message_count: usize,
|
||||||
) -> Option<SystemPrompt> {
|
) -> Option<SystemPrompt> {
|
||||||
let provider = SimpleAgentPromptProvider::new(provider_config.clone());
|
|
||||||
let context = SystemPromptContext {
|
let context = SystemPromptContext {
|
||||||
session_id: Some(record.id.clone()),
|
session_id: Some(record.id.clone()),
|
||||||
chat_id: record.chat_id.clone(),
|
chat_id: record.chat_id.clone(),
|
||||||
@ -373,15 +370,15 @@ pub fn resolve_filepath(filepath: Option<String>, record: &SessionRecord) -> Pat
|
|||||||
/// 用于处理 Feishu/WeChat 等通道中直接输入的 /save 命令
|
/// 用于处理 Feishu/WeChat 等通道中直接输入的 /save 命令
|
||||||
pub struct SaveSessionInChatHandler {
|
pub struct SaveSessionInChatHandler {
|
||||||
store: Arc<SessionStore>,
|
store: Arc<SessionStore>,
|
||||||
provider_config: LLMProviderConfig,
|
system_prompt_provider: Arc<dyn SystemPromptProvider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SaveSessionInChatHandler {
|
impl SaveSessionInChatHandler {
|
||||||
/// 创建新的 InChat 保存会话命令处理器
|
/// 创建新的 InChat 保存会话命令处理器
|
||||||
pub fn new(store: Arc<SessionStore>, provider_config: LLMProviderConfig) -> Self {
|
pub fn new(store: Arc<SessionStore>, system_prompt_provider: Arc<dyn SystemPromptProvider>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
store,
|
store,
|
||||||
provider_config,
|
system_prompt_provider,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,7 +417,7 @@ impl InChatCommandHandler for SaveSessionInChatHandler {
|
|||||||
filepath,
|
filepath,
|
||||||
include_all,
|
include_all,
|
||||||
&*self.store,
|
&*self.store,
|
||||||
&self.provider_config,
|
&*self.system_prompt_provider,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -444,27 +441,6 @@ impl InChatCommandHandler for SaveSessionInChatHandler {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::storage::{SessionRecord, SessionStore};
|
use crate::storage::{SessionRecord, SessionStore};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
fn test_config() -> LLMProviderConfig {
|
|
||||||
LLMProviderConfig {
|
|
||||||
provider_type: "openai".to_string(),
|
|
||||||
name: "test".to_string(),
|
|
||||||
base_url: "http://localhost".to_string(),
|
|
||||||
api_key: "test-key".to_string(),
|
|
||||||
extra_headers: HashMap::new(),
|
|
||||||
llm_timeout_secs: 120,
|
|
||||||
memory_maintenance_timeout_secs: 600,
|
|
||||||
model_id: "test-model".to_string(),
|
|
||||||
temperature: Some(0.0),
|
|
||||||
max_tokens: Some(32),
|
|
||||||
context_window_tokens: None,
|
|
||||||
tool_result_max_chars: 20_000,
|
|
||||||
context_tool_result_trim_chars: 20_000,
|
|
||||||
model_extra: HashMap::new(),
|
|
||||||
max_tool_iterations: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_test_record(id: &str, title: &str) -> SessionRecord {
|
fn create_test_record(id: &str, title: &str) -> SessionRecord {
|
||||||
SessionRecord {
|
SessionRecord {
|
||||||
@ -547,10 +523,23 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_can_handle() {
|
fn test_can_handle() {
|
||||||
let store = Arc::new(SessionStore::in_memory().unwrap());
|
let store = Arc::new(SessionStore::in_memory().unwrap());
|
||||||
let handler = SaveSessionCommandHandler::new(store, test_config());
|
let provider = Arc::new(TestSystemPromptProvider);
|
||||||
|
let handler = SaveSessionCommandHandler::new(store, provider);
|
||||||
|
|
||||||
assert!(handler.can_handle(&Command::SaveSession { filepath: None, include_all: false }));
|
assert!(handler.can_handle(&Command::SaveSession { filepath: None, include_all: false }));
|
||||||
assert!(handler.can_handle(&Command::SaveSession { filepath: None, include_all: true }));
|
assert!(handler.can_handle(&Command::SaveSession { filepath: None, include_all: true }));
|
||||||
assert!(!handler.can_handle(&Command::CreateSession { title: None }));
|
assert!(!handler.can_handle(&Command::CreateSession { title: None }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 测试用的系统提示词提供者
|
||||||
|
struct TestSystemPromptProvider;
|
||||||
|
|
||||||
|
impl SystemPromptProvider for TestSystemPromptProvider {
|
||||||
|
fn build(&self, _context: &SystemPromptContext) -> Option<SystemPrompt> {
|
||||||
|
Some(SystemPrompt {
|
||||||
|
content: "Test system prompt".to_string(),
|
||||||
|
context: Some("test".to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -314,6 +314,8 @@ pub struct GatewayConfig {
|
|||||||
pub agent_prompt_reinject_every: u64,
|
pub agent_prompt_reinject_every: u64,
|
||||||
#[serde(default = "default_max_concurrent_requests")]
|
#[serde(default = "default_max_concurrent_requests")]
|
||||||
pub max_concurrent_requests: usize,
|
pub max_concurrent_requests: usize,
|
||||||
|
#[serde(default, rename = "session_ttl_hours")]
|
||||||
|
pub session_ttl_hours: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
@ -337,6 +339,7 @@ pub struct SchedulerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const BUILTIN_MEMORY_MAINTENANCE_JOB_ID: &str = "builtin.memory_maintenance_daily";
|
pub const BUILTIN_MEMORY_MAINTENANCE_JOB_ID: &str = "builtin.memory_maintenance_daily";
|
||||||
|
pub const BUILTIN_SESSION_CLEANUP_JOB_ID: &str = "builtin.session_cleanup_hourly";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
@ -427,22 +430,40 @@ impl SchedulerJobConfig {
|
|||||||
|
|
||||||
impl SchedulerConfig {
|
impl SchedulerConfig {
|
||||||
pub fn builtin_jobs(time: &TimeConfig) -> Vec<SchedulerJobConfig> {
|
pub fn builtin_jobs(time: &TimeConfig) -> Vec<SchedulerJobConfig> {
|
||||||
vec![SchedulerJobConfig {
|
vec![
|
||||||
id: BUILTIN_MEMORY_MAINTENANCE_JOB_ID.to_string(),
|
SchedulerJobConfig {
|
||||||
enabled: true,
|
id: BUILTIN_MEMORY_MAINTENANCE_JOB_ID.to_string(),
|
||||||
kind: SchedulerJobKind::InternalEvent,
|
enabled: true,
|
||||||
schedule: Some(SchedulerSchedule::Cron {
|
kind: SchedulerJobKind::InternalEvent,
|
||||||
expression: "0 */4 * * *".to_string(),
|
schedule: Some(SchedulerSchedule::Cron {
|
||||||
}),
|
expression: "0 */4 * * *".to_string(),
|
||||||
startup_delay_secs: 0,
|
}),
|
||||||
interval_secs: 0,
|
startup_delay_secs: 0,
|
||||||
target: SchedulerJobTarget::default(),
|
interval_secs: 0,
|
||||||
payload: serde_json::json!({
|
target: SchedulerJobTarget::default(),
|
||||||
"event": "memory_maintenance",
|
payload: serde_json::json!({
|
||||||
"time_zone": time.timezone,
|
"event": "memory_maintenance",
|
||||||
"local_time": "every_4_hours"
|
"time_zone": time.timezone,
|
||||||
}),
|
"local_time": "every_4_hours"
|
||||||
}]
|
}),
|
||||||
|
},
|
||||||
|
SchedulerJobConfig {
|
||||||
|
id: BUILTIN_SESSION_CLEANUP_JOB_ID.to_string(),
|
||||||
|
enabled: true,
|
||||||
|
kind: SchedulerJobKind::InternalEvent,
|
||||||
|
schedule: Some(SchedulerSchedule::Cron {
|
||||||
|
expression: "0 * * * *".to_string(),
|
||||||
|
}),
|
||||||
|
startup_delay_secs: 0,
|
||||||
|
interval_secs: 0,
|
||||||
|
target: SchedulerJobTarget::default(),
|
||||||
|
payload: serde_json::json!({
|
||||||
|
"event": "session_cleanup",
|
||||||
|
"time_zone": time.timezone,
|
||||||
|
"local_time": "every_hour"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn effective_jobs(&self, time: &TimeConfig) -> Vec<SchedulerJobConfig> {
|
pub fn effective_jobs(&self, time: &TimeConfig) -> Vec<SchedulerJobConfig> {
|
||||||
@ -604,6 +625,7 @@ impl Default for GatewayConfig {
|
|||||||
chat_history_ttl_hours: Some(4),
|
chat_history_ttl_hours: Some(4),
|
||||||
agent_prompt_reinject_every: default_agent_prompt_reinject_every(),
|
agent_prompt_reinject_every: default_agent_prompt_reinject_every(),
|
||||||
max_concurrent_requests: default_max_concurrent_requests(),
|
max_concurrent_requests: default_max_concurrent_requests(),
|
||||||
|
session_ttl_hours: Some(24),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use crate::storage::PromptInjectionRepository;
|
|||||||
/// 以及动态生成的系统环境信息。
|
/// 以及动态生成的系统环境信息。
|
||||||
pub struct AgentPromptProvider {
|
pub struct AgentPromptProvider {
|
||||||
/// 重新注入间隔(用户消息数)
|
/// 重新注入间隔(用户消息数)
|
||||||
reinject_every: usize,
|
_reinject_every: usize,
|
||||||
/// LLM 提供者配置(用于生成系统环境信息)
|
/// LLM 提供者配置(用于生成系统环境信息)
|
||||||
provider_config: LLMProviderConfig,
|
provider_config: LLMProviderConfig,
|
||||||
/// 会话持久化仓库(用于记录注入状态)
|
/// 会话持久化仓库(用于记录注入状态)
|
||||||
@ -26,7 +26,7 @@ impl AgentPromptProvider {
|
|||||||
repository: Arc<dyn PromptInjectionRepository>,
|
repository: Arc<dyn PromptInjectionRepository>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
reinject_every,
|
_reinject_every: reinject_every,
|
||||||
provider_config,
|
provider_config,
|
||||||
repository,
|
repository,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,8 @@ impl GatewayState {
|
|||||||
let agent_prompt_reinject_every = config.gateway.agent_prompt_reinject_every;
|
let agent_prompt_reinject_every = config.gateway.agent_prompt_reinject_every;
|
||||||
let show_tool_results = config.gateway.show_tool_results;
|
let show_tool_results = config.gateway.show_tool_results;
|
||||||
|
|
||||||
|
let session_ttl_hours = config.gateway.session_ttl_hours;
|
||||||
|
|
||||||
let skills = Arc::new(SkillRuntime::from_config(config.skills.clone()));
|
let skills = Arc::new(SkillRuntime::from_config(config.skills.clone()));
|
||||||
let channel_manager = ChannelManager::new();
|
let channel_manager = ChannelManager::new();
|
||||||
let bus = channel_manager.bus();
|
let bus = channel_manager.bus();
|
||||||
@ -80,6 +82,7 @@ impl GatewayState {
|
|||||||
Arc::new(BusSessionMessageSender::new(bus.clone())),
|
Arc::new(BusSessionMessageSender::new(bus.clone())),
|
||||||
std::collections::HashSet::new(),
|
std::collections::HashSet::new(),
|
||||||
chat_history_ttl_hours,
|
chat_history_ttl_hours,
|
||||||
|
session_ttl_hours,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|||||||
@ -2,11 +2,13 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
use crate::agent::AgentError;
|
use crate::agent::{AgentError, CompositeSystemPromptProvider};
|
||||||
use crate::bus::{InboundMessage, MessageBus, OutboundMessage};
|
use crate::bus::{InboundMessage, MessageBus, OutboundMessage};
|
||||||
use crate::command::handler::InChatCommandRouter;
|
use crate::command::handler::InChatCommandRouter;
|
||||||
use crate::command::Command;
|
use crate::command::Command;
|
||||||
use crate::config::LLMProviderConfig;
|
use crate::config::LLMProviderConfig;
|
||||||
|
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
||||||
|
use crate::skills::SkillPromptProvider;
|
||||||
|
|
||||||
use super::session::{BusToolCallEmitter, SessionManager};
|
use super::session::{BusToolCallEmitter, SessionManager};
|
||||||
|
|
||||||
@ -31,9 +33,19 @@ impl InboundProcessor {
|
|||||||
|
|
||||||
// 注册 save_session 处理器
|
// 注册 save_session 处理器
|
||||||
let store = session_manager.store();
|
let store = session_manager.store();
|
||||||
|
let skills = session_manager.skills();
|
||||||
|
let prompt_repository = session_manager.store().clone();
|
||||||
|
let system_prompt_provider: Arc<dyn crate::agent::SystemPromptProvider> = Arc::new(CompositeSystemPromptProvider::new(vec![
|
||||||
|
Box::new(AgentPromptProvider::new(
|
||||||
|
0, // save_session 不需要 reinject 逻辑
|
||||||
|
provider_config.clone(),
|
||||||
|
prompt_repository,
|
||||||
|
)),
|
||||||
|
Box::new(SkillPromptProvider::new(skills)),
|
||||||
|
]));
|
||||||
command_router.register(Box::new(crate::command::handlers::save_session::SaveSessionInChatHandler::new(
|
command_router.register(Box::new(crate::command::handlers::save_session::SaveSessionInChatHandler::new(
|
||||||
store,
|
store,
|
||||||
provider_config.clone(),
|
system_prompt_provider,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@ -30,6 +30,7 @@ pub(crate) fn build_session_manager(
|
|||||||
skills: Arc<SkillRuntime>,
|
skills: Arc<SkillRuntime>,
|
||||||
disabled_tools: HashSet<String>,
|
disabled_tools: HashSet<String>,
|
||||||
chat_history_ttl_hours: Option<u64>,
|
chat_history_ttl_hours: Option<u64>,
|
||||||
|
session_ttl_hours: Option<u64>,
|
||||||
) -> Result<SessionManager, AgentError> {
|
) -> Result<SessionManager, AgentError> {
|
||||||
build_session_manager_with_sender(
|
build_session_manager_with_sender(
|
||||||
agent_prompt_reinject_every,
|
agent_prompt_reinject_every,
|
||||||
@ -41,6 +42,7 @@ pub(crate) fn build_session_manager(
|
|||||||
Arc::new(NoopSessionMessageSender),
|
Arc::new(NoopSessionMessageSender),
|
||||||
disabled_tools,
|
disabled_tools,
|
||||||
chat_history_ttl_hours,
|
chat_history_ttl_hours,
|
||||||
|
session_ttl_hours,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +56,7 @@ pub(crate) fn build_session_manager_with_sender(
|
|||||||
session_message_sender: Arc<dyn SessionMessageSender>,
|
session_message_sender: Arc<dyn SessionMessageSender>,
|
||||||
disabled_tools: HashSet<String>,
|
disabled_tools: HashSet<String>,
|
||||||
chat_history_ttl_hours: Option<u64>,
|
chat_history_ttl_hours: Option<u64>,
|
||||||
|
session_ttl_hours: Option<u64>,
|
||||||
) -> Result<SessionManager, AgentError> {
|
) -> Result<SessionManager, AgentError> {
|
||||||
let store = Arc::new(
|
let store = Arc::new(
|
||||||
SessionStore::new()
|
SessionStore::new()
|
||||||
@ -101,7 +104,7 @@ pub(crate) fn build_session_manager_with_sender(
|
|||||||
skill_events,
|
skill_events,
|
||||||
chat_history_ttl_hours,
|
chat_history_ttl_hours,
|
||||||
);
|
);
|
||||||
let lifecycle = SessionLifecycleService::new(session_factory);
|
let lifecycle = SessionLifecycleService::new(session_factory, session_ttl_hours);
|
||||||
let cli_sessions = CliSessionService::new(store.clone());
|
let cli_sessions = CliSessionService::new(store.clone());
|
||||||
let messages = SessionMessageService::new(lifecycle.clone(), show_tool_results);
|
let messages = SessionMessageService::new(lifecycle.clone(), show_tool_results);
|
||||||
let scheduled_tasks = ScheduledAgentTaskService::new(
|
let scheduled_tasks = ScheduledAgentTaskService::new(
|
||||||
|
|||||||
@ -387,6 +387,7 @@ impl SessionManager {
|
|||||||
skills: Arc<SkillRuntime>,
|
skills: Arc<SkillRuntime>,
|
||||||
disabled_tools: std::collections::HashSet<String>,
|
disabled_tools: std::collections::HashSet<String>,
|
||||||
chat_history_ttl_hours: Option<u64>,
|
chat_history_ttl_hours: Option<u64>,
|
||||||
|
session_ttl_hours: Option<u64>,
|
||||||
) -> Result<Self, AgentError> {
|
) -> Result<Self, AgentError> {
|
||||||
super::runtime::build_session_manager(
|
super::runtime::build_session_manager(
|
||||||
agent_prompt_reinject_every,
|
agent_prompt_reinject_every,
|
||||||
@ -397,6 +398,7 @@ impl SessionManager {
|
|||||||
skills,
|
skills,
|
||||||
disabled_tools,
|
disabled_tools,
|
||||||
chat_history_ttl_hours,
|
chat_history_ttl_hours,
|
||||||
|
session_ttl_hours,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,6 +820,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -868,6 +871,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -934,6 +938,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1017,6 +1022,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1101,6 +1107,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1184,6 +1191,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1249,6 +1257,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1323,6 +1332,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1384,6 +1394,7 @@ mod tests {
|
|||||||
Arc::new(SkillRuntime::default()),
|
Arc::new(SkillRuntime::default()),
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
|
Some(24),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1789,3 +1800,25 @@ mod tests {
|
|||||||
assert!(contents.contains(&"习惯先问方案再要代码".to_string()));
|
assert!(contents.contains(&"习惯先问方案再要代码".to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl crate::scheduler::MaintenanceExecutor for SessionManager {
|
||||||
|
async fn cleanup_expired_sessions(&self) -> usize {
|
||||||
|
self.cleanup_expired_sessions().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_memory_maintenance_for_all_scopes(
|
||||||
|
&self,
|
||||||
|
) -> anyhow::Result<Vec<crate::scheduler::MaintenanceRunSummary>> {
|
||||||
|
match self.run_memory_maintenance_for_all_scopes().await {
|
||||||
|
Ok(Some(result)) => Ok(vec![crate::scheduler::MaintenanceRunSummary {
|
||||||
|
scope_key: result.scope_key,
|
||||||
|
merges: result.output.merges.len(),
|
||||||
|
conflicts: result.output.conflicts.len(),
|
||||||
|
low_value: result.output.low_value_ids.len(),
|
||||||
|
}]),
|
||||||
|
Ok(None) => Ok(vec![]),
|
||||||
|
Err(error) => Err(anyhow::anyhow!(error.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -14,9 +14,9 @@ pub(crate) struct SessionLifecycleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SessionLifecycleService {
|
impl SessionLifecycleService {
|
||||||
pub(crate) fn new(session_factory: SessionFactory) -> Self {
|
pub(crate) fn new(session_factory: SessionFactory, session_ttl_hours: Option<u64>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
session_pool: SessionPool::new(session_factory),
|
session_pool: SessionPool::new(session_factory, session_ttl_hours),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ use super::session_factory::SessionFactory;
|
|||||||
pub(crate) struct SessionPool {
|
pub(crate) struct SessionPool {
|
||||||
inner: Arc<Mutex<SessionPoolInner>>,
|
inner: Arc<Mutex<SessionPoolInner>>,
|
||||||
session_factory: SessionFactory,
|
session_factory: SessionFactory,
|
||||||
|
session_ttl_hours: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SessionPoolInner {
|
struct SessionPoolInner {
|
||||||
@ -22,13 +23,14 @@ struct SessionPoolInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SessionPool {
|
impl SessionPool {
|
||||||
pub(crate) fn new(session_factory: SessionFactory) -> Self {
|
pub(crate) fn new(session_factory: SessionFactory, session_ttl_hours: Option<u64>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(Mutex::new(SessionPoolInner {
|
inner: Arc::new(Mutex::new(SessionPoolInner {
|
||||||
sessions: HashMap::new(),
|
sessions: HashMap::new(),
|
||||||
session_timestamps: HashMap::new(),
|
session_timestamps: HashMap::new(),
|
||||||
})),
|
})),
|
||||||
session_factory,
|
session_factory,
|
||||||
|
session_ttl_hours,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +72,39 @@ impl SessionPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn cleanup_expired_sessions(&self) -> usize {
|
pub(crate) async fn cleanup_expired_sessions(&self) -> usize {
|
||||||
// Session 级别不再自动清理,返回 0
|
let ttl_hours = match self.session_ttl_hours {
|
||||||
0
|
Some(hours) if hours > 0 => hours,
|
||||||
|
_ => return 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let ttl_duration = std::time::Duration::from_secs(ttl_hours * 3600);
|
||||||
|
let mut inner = self.inner.lock().await;
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
let expired_channels: Vec<String> = inner
|
||||||
|
.session_timestamps
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(channel_name, last_active)| {
|
||||||
|
let elapsed = now.duration_since(*last_active);
|
||||||
|
if elapsed >= ttl_duration {
|
||||||
|
tracing::info!(
|
||||||
|
channel = %channel_name,
|
||||||
|
elapsed_hours = elapsed.as_secs() / 3600,
|
||||||
|
ttl_hours = ttl_hours,
|
||||||
|
"Session expired, removing from memory pool"
|
||||||
|
);
|
||||||
|
Some(channel_name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for channel_name in &expired_channels {
|
||||||
|
inner.sessions.remove(channel_name);
|
||||||
|
inner.session_timestamps.remove(channel_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
expired_channels.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use super::GatewayState;
|
use super::GatewayState;
|
||||||
use crate::agent::AgentError;
|
use crate::agent::{AgentError, CompositeSystemPromptProvider};
|
||||||
use crate::bus::InboundMessage;
|
use crate::bus::InboundMessage;
|
||||||
use crate::command::adapter::OutputAdapter;
|
use crate::command::adapter::OutputAdapter;
|
||||||
use crate::command::adapters::websocket::{WebSocketInputAdapter, WebSocketOutputAdapter};
|
use crate::command::adapters::websocket::{WebSocketInputAdapter, WebSocketOutputAdapter};
|
||||||
@ -7,7 +7,9 @@ use crate::command::context::CommandContext;
|
|||||||
use crate::command::handler::CommandRouter;
|
use crate::command::handler::CommandRouter;
|
||||||
use crate::command::handlers::save_session::SaveSessionCommandHandler;
|
use crate::command::handlers::save_session::SaveSessionCommandHandler;
|
||||||
use crate::command::handlers::session::SessionCommandHandler;
|
use crate::command::handlers::session::SessionCommandHandler;
|
||||||
|
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
||||||
use crate::protocol::{SessionSummary, WsInbound, WsOutbound, parse_inbound, serialize_outbound};
|
use crate::protocol::{SessionSummary, WsInbound, WsOutbound, parse_inbound, serialize_outbound};
|
||||||
|
use crate::skills::SkillPromptProvider;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::extract::ws::{Message as WsMessage, WebSocket, WebSocketUpgrade};
|
use axum::extract::ws::{Message as WsMessage, WebSocket, WebSocketUpgrade};
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
@ -354,11 +356,23 @@ async fn handle_inbound(
|
|||||||
|
|
||||||
// 获取所需依赖
|
// 获取所需依赖
|
||||||
let store = state.session_manager.store();
|
let store = state.session_manager.store();
|
||||||
|
let skills = state.session_manager.skills();
|
||||||
let provider_config = state.config.get_provider_config("default")
|
let provider_config = state.config.get_provider_config("default")
|
||||||
.map_err(|e| AgentError::Other(e.to_string()))?;
|
.map_err(|e| AgentError::Other(e.to_string()))?;
|
||||||
|
let prompt_repository = state.session_manager.store().clone();
|
||||||
|
|
||||||
|
// 构建组合系统提示词提供者(与运行时一致)
|
||||||
|
let system_prompt_provider: Arc<dyn crate::agent::SystemPromptProvider> = Arc::new(CompositeSystemPromptProvider::new(vec![
|
||||||
|
Box::new(AgentPromptProvider::new(
|
||||||
|
0, // save_session 不需要 reinject 逻辑
|
||||||
|
provider_config.clone(),
|
||||||
|
prompt_repository,
|
||||||
|
)),
|
||||||
|
Box::new(SkillPromptProvider::new(skills)),
|
||||||
|
]));
|
||||||
|
|
||||||
// 构建处理器
|
// 构建处理器
|
||||||
let handler = SaveSessionCommandHandler::new(store, provider_config);
|
let handler = SaveSessionCommandHandler::new(store, system_prompt_provider);
|
||||||
let router = {
|
let router = {
|
||||||
let mut r = CommandRouter::new();
|
let mut r = CommandRouter::new();
|
||||||
r.register(Box::new(handler));
|
r.register(Box::new(handler));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user