PicoBot/src/gateway/agent_factory.rs

151 lines
5.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
})
}
}