151 lines
5.8 KiB
Rust
151 lines
5.8 KiB
Rust
use std::sync::Arc;
|
||
|
||
use crate::agent::{AgentError, AgentLoop, CompositeSystemPromptProvider, SystemPromptProvider};
|
||
use crate::config::LLMProviderConfig;
|
||
use crate::experts::ExpertPromptProvider;
|
||
use crate::experts::ExpertRuntime;
|
||
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
||
use crate::gateway::tool_prompt_provider::ToolPromptProvider;
|
||
use crate::skills::{SkillPromptProvider, SkillRuntime};
|
||
use crate::storage::persistent_session_id;
|
||
use crate::storage::PromptInjectionRepository;
|
||
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<dyn PromptInjectionRepository>,
|
||
skills: Arc<SkillRuntime>,
|
||
experts: Arc<ExpertRuntime>,
|
||
subagent_runtime: Arc<SubagentRuntime>,
|
||
) -> Arc<dyn SystemPromptProvider> {
|
||
Arc::new(CompositeSystemPromptProvider::new(vec![
|
||
Box::new(AgentPromptProvider::new(
|
||
reinject_every,
|
||
provider_config,
|
||
prompt_repository,
|
||
)),
|
||
Box::new(SkillPromptProvider::new(skills)),
|
||
Box::new(ExpertPromptProvider::new(experts)),
|
||
Box::new(SubagentPromptProvider::new(subagent_runtime)),
|
||
Box::new(ToolPromptProvider::new()),
|
||
]))
|
||
}
|
||
|
||
#[derive(Clone)]
|
||
pub(crate) struct AgentFactory {
|
||
tools: Arc<ToolRegistry>,
|
||
skills: Arc<SkillRuntime>,
|
||
experts: Arc<ExpertRuntime>,
|
||
subagent_runtime: Arc<SubagentRuntime>,
|
||
reinject_every: usize,
|
||
prompt_repository: Arc<dyn PromptInjectionRepository>,
|
||
/// 实例创建时间戳(用于区分新旧 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<String>,
|
||
/// 取消信号接收端(可选):Agent 在每次迭代时检查是否被取消
|
||
pub(crate) cancel_token: Option<tokio::sync::watch::Receiver<()>>,
|
||
}
|
||
|
||
impl AgentFactory {
|
||
pub(crate) fn new(
|
||
tools: Arc<ToolRegistry>,
|
||
skills: Arc<SkillRuntime>,
|
||
experts: Arc<ExpertRuntime>,
|
||
subagent_runtime: Arc<SubagentRuntime>,
|
||
reinject_every: usize,
|
||
prompt_repository: Arc<dyn PromptInjectionRepository>,
|
||
) -> 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,
|
||
instance_id,
|
||
}
|
||
}
|
||
|
||
pub(crate) fn create(&self, request: AgentBuildRequest<'_>) -> Result<AgentLoop, AgentError> {
|
||
let session_id = persistent_session_id(request.channel_name, request.session_chat_id);
|
||
|
||
// 诊断日志:记录 agent 实际使用的配置和实例 ID
|
||
tracing::info!(
|
||
instance_id = self.instance_id,
|
||
channel = %request.channel_name,
|
||
session_id = %session_id,
|
||
provider = %request.provider_config.name,
|
||
model_id = %request.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,
|
||
request.provider_config.clone(),
|
||
self.prompt_repository.clone(),
|
||
self.skills.clone(),
|
||
self.experts.clone(),
|
||
self.subagent_runtime.clone(),
|
||
);
|
||
|
||
AgentLoop::with_tools_and_system_prompt_provider(
|
||
request.provider_config,
|
||
self.tools.clone(),
|
||
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);
|
||
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,
|
||
});
|
||
// 如果有取消信号接收端,注入 Agent
|
||
if let Some(token) = request.cancel_token {
|
||
agent = agent.with_cancel_token(token);
|
||
}
|
||
agent
|
||
})
|
||
}
|
||
}
|