use std::sync::Arc; use crate::agent::context_compressor::ContextCompressor; use crate::agent::{AgentError, AgentLoop, AgentRuntimeConfig, CompositeSystemPromptProvider, SystemPromptProvider}; use crate::config::{CompactionConfig, LLMProviderConfig, ModelResolver}; use crate::domain::CapabilityPolicy; use crate::experts::ExpertPromptProvider; use crate::experts::ExpertRuntime; use crate::gateway::agent_prompt_provider::AgentPromptProvider; use crate::gateway::model_selection::ModelSelectionStore; use crate::gateway::tool_prompt_provider::ToolPromptProvider; use crate::skills::{SkillPromptProvider, SkillRuntime}; use crate::storage::PromptInjectionRepository; use crate::storage::persistent_session_id; use crate::tools::task::runtime::{SubagentPromptProvider, SubagentRuntime}; use crate::tools::{ToolContext, ToolRegistry}; /// 构建与 Agent 实际使用的完全一致的组合系统提示词 Provider。 /// /// 单一来源:AgentFactory::create 与命令侧(/save、/save-session、/current) /// 都调用此函数,确保保存到文件的系统提示词与 LLM 实际接收的提示词一致。 /// /// Provider 顺序:AgentPrompt → SkillPrompt → ExpertPrompt → SubagentPrompt → TodoPrompt pub(crate) fn build_system_prompt_provider( reinject_every: usize, provider_config: LLMProviderConfig, prompt_repository: Arc, skills: Arc, experts: Arc, subagent_runtime: Arc, ) -> Arc { Arc::new(CompositeSystemPromptProvider::new(vec![ Box::new(AgentPromptProvider::new( reinject_every, provider_config, prompt_repository, )), Box::new(SkillPromptProvider::new(skills, experts.clone())), Box::new(ExpertPromptProvider::new(experts.clone())), Box::new(SubagentPromptProvider::new(subagent_runtime, experts)), Box::new(ToolPromptProvider::new()), ])) } #[derive(Clone)] pub(crate) struct AgentFactory { tools: Arc, skills: Arc, experts: Arc, subagent_runtime: Arc, reinject_every: usize, prompt_repository: Arc, /// Provider/Model 解析器:按专家 frontmatter 中的 provider/model 字段覆盖基础配置 model_resolver: Arc, /// per-session 的用户模型选择(最高优先级,覆盖专家配置) model_selections: Arc, /// 上下文压缩算法配置(所有 agent 共享) compaction_config: CompactionConfig, /// 实例创建时间戳(用于区分新旧 AgentFactory 实例) instance_id: u64, } pub(crate) struct AgentBuildRequest<'a> { pub(crate) channel_name: &'a str, pub(crate) session_chat_id: &'a str, pub(crate) notification_chat_id: Option<&'a str>, pub(crate) sender_id: Option<&'a str>, pub(crate) message_id: Option<&'a str>, pub(crate) provider_config: LLMProviderConfig, /// 当前话题 ID(可选):用于 todo 等按 topic 隔离的工具 pub(crate) topic_id: Option, /// 取消信号接收端(可选):Agent 在每次迭代时检查是否被取消 pub(crate) cancel_token: Option>, } impl AgentFactory { pub(crate) fn new( tools: Arc, skills: Arc, experts: Arc, subagent_runtime: Arc, reinject_every: usize, prompt_repository: Arc, model_resolver: Arc, model_selections: Arc, compaction_config: CompactionConfig, ) -> Self { // 使用 Arc 指针地址作为实例标识符,用于区分新旧 AgentFactory 实例 let instance_id = Arc::as_ptr(&tools) as u64; tracing::info!( instance_id = instance_id, tool_count = tools.tool_names().len(), "AgentFactory::new created" ); Self { tools, skills, experts, subagent_runtime, reinject_every, prompt_repository, model_resolver, model_selections, compaction_config, instance_id, } } /// 构造 ContextCompressor(参数内聚到 ContextCompressor,CompactionConfig 注入)。 /// AgentLoop(in-loop 压缩)和 Session(sync 兜底压缩)共用此方法, /// 确保两条压缩路径使用同一套用户配置的压缩参数。 pub(crate) fn build_compressor(&self, runtime_config: &AgentRuntimeConfig) -> ContextCompressor { ContextCompressor::with_compaction_config( runtime_config.context_window_tokens, runtime_config.context_summary_char_budget, &self.compaction_config, ) } pub(crate) fn create(&self, request: AgentBuildRequest<'_>) -> Result { let session_id = persistent_session_id(request.channel_name, request.session_chat_id); // 读取所选专家(用于工具过滤 + 子代理策略 + 模型覆盖) let expert = self.experts.selected_expert_for(&session_id); let expert_capability = expert.as_ref().map(|e| e.capability.clone()); // 按专家 frontmatter 中的 provider/model 字段解析覆盖基础 provider_config。 // 引用不存在的 provider/model 名时报错并阻止会话(用户主动选择的角色,配置错误应明确反馈)。 let expert_provider_config = match &expert { Some(e) if e.provider.is_some() || e.model.is_some() => { let resolved = self .model_resolver .resolve( e.provider.as_deref(), e.model.as_deref(), &request.provider_config, ) .map_err(|e| AgentError::Other(e.to_string()))?; tracing::info!( instance_id = self.instance_id, session_id = %session_id, expert = %e.name, provider = %resolved.name, model_id = %resolved.model_id, "AgentFactory: applied expert model override" ); resolved } _ => request.provider_config.clone(), }; // 按用户手动选择的 provider/model 覆盖(最高优先级,覆盖专家配置)。 // 引用不存在的 provider/model 名时报错并阻止会话(用户主动选择,配置错误应明确反馈)。 let effective_provider_config = match self.model_selections.get(&session_id) { Some((user_provider, user_model)) if user_provider.is_some() || user_model.is_some() => { let resolved = self .model_resolver .resolve( user_provider.as_deref(), user_model.as_deref(), &expert_provider_config, ) .map_err(|e| AgentError::Other(e.to_string()))?; tracing::info!( instance_id = self.instance_id, session_id = %session_id, provider = %resolved.name, model_id = %resolved.model_id, "AgentFactory: applied user model override" ); resolved } _ => expert_provider_config, }; // 诊断日志:记录 agent 实际使用的配置和实例 ID tracing::info!( instance_id = self.instance_id, channel = %request.channel_name, session_id = %session_id, provider = %effective_provider_config.name, model_id = %effective_provider_config.model_id, tool_count = self.tools.tool_names().len(), "AgentFactory: creating agent with config" ); // 创建组合的系统提示词提供者(与命令侧 /save 等共享同一构建逻辑) let system_prompt_provider = build_system_prompt_provider( self.reinject_every, effective_provider_config.clone(), self.prompt_repository.clone(), self.skills.clone(), self.experts.clone(), self.subagent_runtime.clone(), ); // 按所选专家的工具策略过滤工具集(含内置 + MCP 工具)。 // 无专家或专家未声明工具策略时,复用共享的 Arc(零拷贝)。 let base_tool_count = self.tools.tool_names().len(); let tools: Arc = match &expert_capability { Some(cap) if cap.has_tool_policy() => { let filtered = self.build_filtered_registry(cap); let filtered_count = filtered.tool_names().len(); tracing::info!( instance_id = self.instance_id, session_id = %session_id, base_tool_count, filtered_tool_count = filtered_count, "AgentFactory: applied expert tool policy" ); Arc::new(filtered) } _ => self.tools.clone(), }; AgentLoop::with_tools_and_system_prompt_provider( effective_provider_config.clone(), tools, system_prompt_provider, Some(self.skills.clone()), ) .map(|agent| { // notification_chat_id 优先,否则使用 session_chat_id let tool_chat_id = request .notification_chat_id .unwrap_or(request.session_chat_id); // 构建上下文压缩器(参数内聚到 ContextCompressor,CompactionConfig 注入) let runtime_config = AgentRuntimeConfig::from(effective_provider_config.clone()); let compressor = Arc::new(self.build_compressor(&runtime_config)); let mut agent = agent .with_tool_context(ToolContext { channel_name: Some(request.channel_name.to_string()), sender_id: request.sender_id.map(str::to_string), chat_id: Some(tool_chat_id.to_string()), session_id: Some(session_id), topic_id: request.topic_id.clone(), message_id: request.message_id.map(str::to_string), message_seq: None, subagent_description: None, nesting_depth: 0, task_id: None, parent_task_id: None, tool_call_id: None, // 注入专家 capability,TaskTool 据此强制校验子代理白/黑名单 parent_capability: expert_capability.clone(), }) .with_compressor(Some(compressor)); // 如果有取消信号接收端,注入 Agent if let Some(token) = request.cancel_token { agent = agent.with_cancel_token(token); } agent }) } /// 按专家 CapabilityPolicy 构建过滤后的 ToolRegistry 副本。 /// 生效顺序:先白名单取交集,再黑名单扣除(与 subagent filter_tool_registry 语义一致)。 /// 底层工具为 Arc,克隆廉价。 fn build_filtered_registry(&self, policy: &CapabilityPolicy) -> ToolRegistry { // 1. 白名单(取交集);None 表示不限,复制一份以便后续黑名单过滤 let after_allow: ToolRegistry = match &policy.allowed_tools { Some(allowed) => { let refs: Vec<&str> = allowed.iter().map(|s| s.as_str()).collect(); self.tools.only(&refs) } None => self.tools.without(&[]), }; // 2. 黑名单(扣除) if policy.denied_tools.is_empty() { after_allow } else { let refs: Vec<&str> = policy.denied_tools.iter().map(|s| s.as_str()).collect(); after_allow.without(&refs) } } }