From 716d92a618545764d31a81bad14131f41741d7f5 Mon Sep 17 00:00:00 2001 From: ooodc <549496103@qq.com> Date: Thu, 30 Apr 2026 22:34:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5=20AgentRuntimeConfig?= =?UTF-8?q?=EF=BC=8C=E9=87=8D=E6=9E=84=E7=9B=B8=E5=85=B3=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E8=BF=90=E8=A1=8C=E6=97=B6=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- src/agent/agent_loop.rs | 37 +++++------ src/agent/context_compressor.rs | 13 ++-- src/agent/mod.rs | 2 + src/agent/runtime_config.rs | 39 +++++++++++ src/gateway/memory_maintenance.rs | 5 +- src/gateway/mod.rs | 4 +- src/gateway/runtime.rs | 96 ++++++++++++++++++++++++++++ src/gateway/session.rs | 93 ++++++++++----------------- src/gateway/tool_registry_factory.rs | 22 ++++--- src/providers/mod.rs | 10 ++- src/providers/traits.rs | 15 +++++ 11 files changed, 241 insertions(+), 95 deletions(-) create mode 100644 src/agent/runtime_config.rs create mode 100644 src/gateway/runtime.rs diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index dc2ecfe..b154408 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -1,6 +1,6 @@ +use crate::agent::AgentRuntimeConfig; use crate::bus::ChatMessage; use crate::bus::message::ToolMessageState; -use crate::config::LLMProviderConfig; use crate::domain::messages::{ContentBlock, ToolCall}; use crate::observability::{ Observer, ObserverEvent, ToolExecutionOutcome, ToolExecutionState, truncate_args, @@ -292,7 +292,7 @@ fn chat_message_to_llm_message(m: &ChatMessage) -> Message { /// AgentLoop - Stateless agent that processes messages with tool calling support. /// History is managed externally by SessionManager. pub struct AgentLoop { - provider_config: LLMProviderConfig, + runtime_config: AgentRuntimeConfig, provider: Box, tools: Arc, skills: Arc, @@ -327,13 +327,14 @@ impl SkillProvider for EmptySkillProvider { } impl AgentLoop { - pub fn new(provider_config: LLMProviderConfig) -> Result { - let max_iterations = provider_config.max_tool_iterations; - let provider = create_provider(provider_config.clone()) + pub fn new(config: impl Into) -> Result { + let runtime_config = config.into(); + let max_iterations = runtime_config.max_tool_iterations; + let provider = create_provider(runtime_config.provider.clone()) .map_err(|e| AgentError::ProviderCreation(e.to_string()))?; Ok(Self { - provider_config, + runtime_config, provider, tools: Arc::new(ToolRegistry::new()), skills: Arc::new(EmptySkillProvider), @@ -345,15 +346,16 @@ impl AgentLoop { } pub fn with_tools( - provider_config: LLMProviderConfig, + config: impl Into, tools: Arc, ) -> Result { - let max_iterations = provider_config.max_tool_iterations; - let provider = create_provider(provider_config.clone()) + let runtime_config = config.into(); + let max_iterations = runtime_config.max_tool_iterations; + let provider = create_provider(runtime_config.provider.clone()) .map_err(|e| AgentError::ProviderCreation(e.to_string()))?; Ok(Self { - provider_config, + runtime_config, provider, tools, skills: Arc::new(EmptySkillProvider), @@ -365,16 +367,17 @@ impl AgentLoop { } pub fn with_tools_and_skill_provider( - provider_config: LLMProviderConfig, + config: impl Into, tools: Arc, skills: Arc, ) -> Result { - let max_iterations = provider_config.max_tool_iterations; - let provider = create_provider(provider_config.clone()) + let runtime_config = config.into(); + let max_iterations = runtime_config.max_tool_iterations; + let provider = create_provider(runtime_config.provider.clone()) .map_err(|e| AgentError::ProviderCreation(e.to_string()))?; Ok(Self { - provider_config, + runtime_config, provider, tools, skills, @@ -543,10 +546,8 @@ impl AgentLoop { tracing::info!(tool = %tool_call.name, args = %args_str, "Calling tool"); // Truncate tool result if too large - let truncated_output = truncate_tool_result( - &result.output, - self.provider_config.tool_result_max_chars, - ); + let truncated_output = + truncate_tool_result(&result.output, self.runtime_config.tool_result_max_chars); // Record tool call and check for loops let loop_result = loop_detector.record(&tool_call.name, &tool_call.arguments); diff --git a/src/agent/context_compressor.rs b/src/agent/context_compressor.rs index 4a40596..c70cbf6 100644 --- a/src/agent/context_compressor.rs +++ b/src/agent/context_compressor.rs @@ -6,7 +6,7 @@ use crate::config::LLMProviderConfig; use crate::providers::{ChatCompletionRequest, LLMProvider, Message, create_provider}; use crate::text::{char_count, take_prefix_chars}; -use crate::agent::AgentError; +use crate::agent::{AgentError, AgentRuntimeConfig}; /// Token estimation using ~4 chars/token heuristic with 1.2x safety margin. pub fn estimate_tokens(messages: &[ChatMessage]) -> usize { @@ -263,10 +263,14 @@ Be concise, aim for {} characters or less. } pub fn from_provider_config(provider_config: &LLMProviderConfig) -> Self { + Self::from_runtime_config(&AgentRuntimeConfig::from(provider_config.clone())) + } + + pub fn from_runtime_config(config: &AgentRuntimeConfig) -> Self { Self::with_config( - provider_config.context_window_tokens(), + config.context_window_tokens, ContextCompressionConfig { - summary_max_chars: provider_config.context_summary_char_budget(), + summary_max_chars: config.context_summary_char_budget, ..ContextCompressionConfig::default() }, ) @@ -434,7 +438,8 @@ Be concise, aim for {} characters or less. return Ok(String::new()); } - let provider = create_provider(provider_config.clone()) + let runtime_config = AgentRuntimeConfig::from(provider_config.clone()); + let provider = create_provider(runtime_config.provider) .map_err(|e| AgentError::ProviderCreation(e.to_string()))?; let transcript = Self::build_transcript(messages); diff --git a/src/agent/mod.rs b/src/agent/mod.rs index d4b57ac..686bb55 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -1,7 +1,9 @@ pub mod agent_loop; pub mod context_compressor; +pub mod runtime_config; pub use agent_loop::{ AgentError, AgentLoop, AgentProcessResult, EmittedMessageHandler, SkillProvider, }; pub use context_compressor::ContextCompressor; +pub use runtime_config::AgentRuntimeConfig; diff --git a/src/agent/runtime_config.rs b/src/agent/runtime_config.rs new file mode 100644 index 0000000..e38d1c2 --- /dev/null +++ b/src/agent/runtime_config.rs @@ -0,0 +1,39 @@ +use crate::config::LLMProviderConfig; +use crate::providers::ProviderRuntimeConfig; + +#[derive(Debug, Clone)] +pub struct AgentRuntimeConfig { + pub provider: ProviderRuntimeConfig, + pub context_window_tokens: usize, + pub context_summary_char_budget: usize, + pub max_tool_iterations: usize, + pub tool_result_max_chars: usize, + pub context_tool_result_trim_chars: usize, +} + +impl From for AgentRuntimeConfig { + fn from(config: LLMProviderConfig) -> Self { + let context_window_tokens = config.context_window_tokens(); + let context_summary_char_budget = config.context_summary_char_budget(); + + Self { + provider: ProviderRuntimeConfig { + provider_type: config.provider_type, + name: config.name, + base_url: config.base_url, + api_key: config.api_key, + extra_headers: config.extra_headers, + llm_timeout_secs: config.llm_timeout_secs, + model_id: config.model_id, + temperature: config.temperature, + max_tokens: config.max_tokens, + model_extra: config.model_extra, + }, + context_window_tokens, + context_summary_char_budget, + max_tool_iterations: config.max_tool_iterations, + tool_result_max_chars: config.tool_result_max_chars, + context_tool_result_trim_chars: config.context_tool_result_trim_chars, + } + } +} diff --git a/src/gateway/memory_maintenance.rs b/src/gateway/memory_maintenance.rs index 5c8b3d3..5653bb5 100644 --- a/src/gateway/memory_maintenance.rs +++ b/src/gateway/memory_maintenance.rs @@ -4,7 +4,7 @@ use std::time::Duration; use serde::{Deserialize, Serialize}; -use crate::agent::AgentError; +use crate::agent::{AgentError, AgentRuntimeConfig}; use crate::config::LLMProviderConfig; use crate::providers::{ChatCompletionRequest, Message, create_provider}; use crate::storage::{MemoryRecord, SessionStore}; @@ -114,7 +114,8 @@ impl MemoryMaintenanceService { scope_key: &str, plan: &MemoryMaintenancePlan, ) -> Result { - let provider = create_provider(self.provider_config.clone()).map_err(|err| { + let runtime_config = AgentRuntimeConfig::from(self.provider_config.clone()); + let provider = create_provider(runtime_config.provider).map_err(|err| { AgentError::Other(format!("create maintenance provider error: {}", err)) })?; diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 9cfa501..8de5c58 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -13,6 +13,7 @@ pub mod processor; pub mod prompt; pub mod prompt_injector; pub mod provider_config_service; +pub mod runtime; pub mod scheduled_agent_task_service; pub mod session; pub mod session_factory; @@ -38,6 +39,7 @@ use crate::skills::SkillRuntime; use agent_task_executor::{AgentTaskExecutor, SchedulerMaintenanceService}; use outbound_dispatcher::OutboundDispatcher; use processor::InboundProcessor; +use runtime::build_session_manager; use session::SessionManager; pub struct GatewayState { @@ -63,7 +65,7 @@ impl GatewayState { let skills = Arc::new(SkillRuntime::from_config(config.skills.clone())); - let session_manager = SessionManager::new( + let session_manager = build_session_manager( session_ttl_hours, agent_prompt_reinject_every, show_tool_results, diff --git a/src/gateway/runtime.rs b/src/gateway/runtime.rs new file mode 100644 index 0000000..b232f49 --- /dev/null +++ b/src/gateway/runtime.rs @@ -0,0 +1,96 @@ +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; + +use crate::agent::AgentError; +use crate::config::LLMProviderConfig; +use crate::skills::SkillRuntime; +use crate::storage::{ + ConversationRepository, MemoryRepository, PromptInjectionRepository, SchedulerJobRepository, + SessionStore, SkillEventRepository, +}; +use crate::tools::ToolRegistry; + +use super::agent_factory::AgentFactory; +use super::cli_session::CliSessionService; +use super::memory_maintenance_coordinator::MemoryMaintenanceCoordinator; +use super::prompt_injector::PromptInjector; +use super::provider_config_service::ProviderConfigService; +use super::scheduled_agent_task_service::ScheduledAgentTaskService; +use super::session::{SessionManager, SessionManagerServices}; +use super::session_factory::SessionFactory; +use super::session_lifecycle::SessionLifecycleService; +use super::session_message_service::SessionMessageService; +use super::tool_registry_factory::ToolRegistryFactory; + +pub(crate) fn build_session_manager( + session_ttl_hours: u64, + agent_prompt_reinject_every: u64, + show_tool_results: bool, + default_timezone: String, + provider_config: LLMProviderConfig, + provider_configs: HashMap, + skills: Arc, +) -> Result { + let store = Arc::new( + SessionStore::new() + .map_err(|err| AgentError::Other(format!("session store init error: {}", err)))?, + ); + let known_agents = provider_configs.keys().cloned().collect::>(); + let provider_configs = ProviderConfigService::new(provider_config.clone(), provider_configs); + + if let Err(err) = + store.append_skill_event(None, "discovered", None, &skills.discovery_event_payload()) + { + tracing::warn!(error = %err, "Failed to record skill discovery event"); + } + + let memories: Arc = store.clone(); + let scheduler_jobs: Arc = store.clone(); + let skill_events: Arc = store.clone(); + let tools = Arc::new( + ToolRegistryFactory::new( + skills.clone(), + memories, + scheduler_jobs, + skill_events.clone(), + known_agents, + default_timezone, + ) + .build(), + ); + + let agent_factory = AgentFactory::new(tools.clone(), skills.clone()); + let conversations: Arc = store.clone(); + let prompt_repository: Arc = store.clone(); + let prompt_injector = PromptInjector::new(prompt_repository, agent_prompt_reinject_every); + let session_factory = SessionFactory::new( + provider_config.clone(), + skills.clone(), + agent_factory, + prompt_injector, + conversations, + skill_events, + ); + let lifecycle = SessionLifecycleService::new(session_ttl_hours, session_factory); + let cli_sessions = CliSessionService::new(store.clone()); + let messages = SessionMessageService::new(lifecycle.clone(), show_tool_results); + let scheduled_tasks = ScheduledAgentTaskService::new( + lifecycle.clone(), + provider_configs.clone(), + show_tool_results, + ); + let memory_maintenance = + MemoryMaintenanceCoordinator::new(store.clone(), provider_configs.clone()); + + Ok(SessionManager::from_services(SessionManagerServices { + tools: tools as Arc, + skills, + store, + show_tool_results, + lifecycle, + cli_sessions, + messages, + scheduled_tasks, + memory_maintenance, + })) +} diff --git a/src/gateway/session.rs b/src/gateway/session.rs index e059ba1..e733120 100644 --- a/src/gateway/session.rs +++ b/src/gateway/session.rs @@ -9,7 +9,7 @@ use crate::skills::SkillRuntime; use crate::storage::{ConversationRepository, SessionRecord, SessionStore, SkillEventRepository}; use crate::tools::ToolRegistry; use async_trait::async_trait; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::sync::Arc; use tokio::sync::{Mutex, mpsc}; use uuid::Uuid; @@ -26,13 +26,10 @@ use super::memory_maintenance::{ use super::memory_maintenance::{MemoryMaintenanceModelOutput, MemoryMaintenanceScopeResult}; use super::memory_maintenance_coordinator::MemoryMaintenanceCoordinator; use super::prompt_injector::PromptInjector; -use super::provider_config_service::ProviderConfigService; use super::scheduled_agent_task_service::ScheduledAgentTaskService; -use super::session_factory::SessionFactory; use super::session_history::SessionHistory; use super::session_lifecycle::SessionLifecycleService; use super::session_message_service::SessionMessageService; -use super::tool_registry_factory::ToolRegistryFactory; /// Session 按 channel 隔离,每个 channel 一个 Session /// History 按 chat_id 隔离,由 Session 统一管理 @@ -346,7 +343,33 @@ pub struct SessionManager { memory_maintenance: MemoryMaintenanceCoordinator, } +pub(crate) struct SessionManagerServices { + pub(crate) tools: Arc, + pub(crate) skills: Arc, + pub(crate) store: Arc, + pub(crate) show_tool_results: bool, + pub(crate) lifecycle: SessionLifecycleService, + pub(crate) cli_sessions: CliSessionService, + pub(crate) messages: SessionMessageService, + pub(crate) scheduled_tasks: ScheduledAgentTaskService, + pub(crate) memory_maintenance: MemoryMaintenanceCoordinator, +} + impl SessionManager { + pub(crate) fn from_services(services: SessionManagerServices) -> Self { + Self { + tools: services.tools, + skills: services.skills, + store: services.store, + show_tool_results: services.show_tool_results, + lifecycle: services.lifecycle, + cli_sessions: services.cli_sessions, + messages: services.messages, + scheduled_tasks: services.scheduled_tasks, + memory_maintenance: services.memory_maintenance, + } + } + pub fn new( session_ttl_hours: u64, agent_prompt_reinject_every: u64, @@ -356,63 +379,15 @@ impl SessionManager { provider_configs: HashMap, skills: Arc, ) -> Result { - let store = Arc::new( - SessionStore::new() - .map_err(|err| AgentError::Other(format!("session store init error: {}", err)))?, - ); - let known_agents = provider_configs.keys().cloned().collect::>(); - let provider_configs = - ProviderConfigService::new(provider_config.clone(), provider_configs); - - if let Err(err) = - store.append_skill_event(None, "discovered", None, &skills.discovery_event_payload()) - { - tracing::warn!(error = %err, "Failed to record skill discovery event"); - } - - let tools = Arc::new( - ToolRegistryFactory::new( - skills.clone(), - store.clone(), - known_agents, - default_timezone, - ) - .build(), - ); - let agent_factory = AgentFactory::new(tools.clone(), skills.clone()); - let conversations: Arc = store.clone(); - let skill_events: Arc = store.clone(); - let prompt_injector = PromptInjector::new(store.clone(), agent_prompt_reinject_every); - let session_factory = SessionFactory::new( - provider_config.clone(), - skills.clone(), - agent_factory, - prompt_injector, - conversations, - skill_events, - ); - let lifecycle = SessionLifecycleService::new(session_ttl_hours, session_factory); - let cli_sessions = CliSessionService::new(store.clone()); - let messages = SessionMessageService::new(lifecycle.clone(), show_tool_results); - let scheduled_tasks = ScheduledAgentTaskService::new( - lifecycle.clone(), - provider_configs.clone(), + super::runtime::build_session_manager( + session_ttl_hours, + agent_prompt_reinject_every, show_tool_results, - ); - let memory_maintenance = - MemoryMaintenanceCoordinator::new(store.clone(), provider_configs.clone()); - - Ok(Self { - tools, + default_timezone, + provider_config, + provider_configs, skills, - store, - show_tool_results, - lifecycle, - cli_sessions, - messages, - scheduled_tasks, - memory_maintenance, - }) + ) } pub fn tools(&self) -> Arc { diff --git a/src/gateway/tool_registry_factory.rs b/src/gateway/tool_registry_factory.rs index 035cf3d..4bb78b1 100644 --- a/src/gateway/tool_registry_factory.rs +++ b/src/gateway/tool_registry_factory.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::sync::Arc; use crate::skills::SkillRuntime; -use crate::storage::SessionStore; +use crate::storage::{MemoryRepository, SchedulerJobRepository, SkillEventRepository}; use crate::tools::{ BashTool, CalculatorTool, FileEditTool, FileReadTool, FileWriteTool, HttpRequestTool, MemoryManageTool, MemorySearchTool, SchedulerManageTool, SkillActivateTool, SkillListTool, @@ -11,7 +11,9 @@ use crate::tools::{ pub(crate) struct ToolRegistryFactory { skills: Arc, - store: Arc, + memories: Arc, + scheduler_jobs: Arc, + skill_events: Arc, known_agents: HashSet, default_timezone: String, } @@ -19,13 +21,17 @@ pub(crate) struct ToolRegistryFactory { impl ToolRegistryFactory { pub(crate) fn new( skills: Arc, - store: Arc, + memories: Arc, + scheduler_jobs: Arc, + skill_events: Arc, known_agents: HashSet, default_timezone: String, ) -> Self { Self { skills, - store, + memories, + scheduler_jobs, + skill_events, known_agents, default_timezone, } @@ -38,15 +44,15 @@ impl ToolRegistryFactory { registry.register(FileReadTool::new()); registry.register(FileWriteTool::new()); registry.register(FileEditTool::new()); - registry.register(MemorySearchTool::new(self.store.clone())); - registry.register(MemoryManageTool::new(self.store.clone())); + registry.register(MemorySearchTool::new(self.memories.clone())); + registry.register(MemoryManageTool::new(self.memories.clone())); registry.register(SchedulerManageTool::new( - self.store.clone(), + self.scheduler_jobs.clone(), self.known_agents.clone(), )); registry.register(SkillActivateTool::new( self.skills.clone(), - self.store.clone(), + self.skill_events.clone(), )); registry.register(SkillListTool::new(self.skills.clone())); registry.register(SkillManageTool::new(self.skills.clone())); diff --git a/src/providers/mod.rs b/src/providers/mod.rs index fdf3804..8436e7e 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -5,12 +5,16 @@ pub mod traits; pub use self::anthropic::AnthropicProvider; pub use self::openai::OpenAIProvider; -use crate::config::LLMProviderConfig; pub use crate::domain::messages::ToolCall; pub use crate::domain::tools::{Tool, ToolFunction}; -pub use traits::{ChatCompletionRequest, ChatCompletionResponse, LLMProvider, Message, Usage}; +pub use traits::{ + ChatCompletionRequest, ChatCompletionResponse, LLMProvider, Message, ProviderRuntimeConfig, + Usage, +}; -pub fn create_provider(config: LLMProviderConfig) -> Result, ProviderError> { +pub fn create_provider( + config: ProviderRuntimeConfig, +) -> Result, ProviderError> { match config.provider_type.as_str() { "openai" => Ok(Box::new(OpenAIProvider::new( config.name, diff --git a/src/providers/traits.rs b/src/providers/traits.rs index ab0bbc3..28b757c 100644 --- a/src/providers/traits.rs +++ b/src/providers/traits.rs @@ -2,6 +2,21 @@ use crate::domain::messages::{ContentBlock, ToolCall}; use crate::domain::tools::Tool; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub struct ProviderRuntimeConfig { + pub provider_type: String, + pub name: String, + pub base_url: String, + pub api_key: String, + pub extra_headers: HashMap, + pub llm_timeout_secs: u64, + pub model_id: String, + pub temperature: Option, + pub max_tokens: Option, + pub model_extra: HashMap, +} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message {