//! Agent management and multi-agent orchestration use anyhow::{Result, Context}; use rig::{ agent::Agent, completion::{Completion, Message}, providers::openai, }; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; use uuid::Uuid; use crate::tools::{Tool, ToolRegistry, ToolResult}; /// Agent configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentConfig { pub name: String, pub preamble: String, pub model: String, pub provider: String, pub temperature: f32, pub max_turns: u32, } /// Agent session #[derive(Debug, Clone)] pub struct AgentSession { pub id: String, pub config: AgentConfig, pub messages: Vec, } /// Agent Council for multi-agent orchestration #[derive(Debug, Clone)] pub struct AgentCouncil { pub id: String, pub name: String, pub agents: Vec, } /// Agent manager #[derive(Debug, Clone)] pub struct AgentManager { sessions: Arc>>, councils: Arc>>, tool_registry: ToolRegistry, } impl AgentManager { pub fn new(tool_registry: ToolRegistry) -> Self { Self { sessions: Arc::new(RwLock::new(Vec::new())), councils: Arc::new(RwLock::new(Vec::new())), tool_registry, } } /// Create a new agent session pub async fn create_session(&self, config: AgentConfig) -> Result { let session = AgentSession { id: Uuid::new_v4().to_string(), config, messages: Vec::new(), }; let id = session.id.clone(); let mut sessions = self.sessions.write().await; sessions.push(session); Ok(id) } /// Get a session by ID pub async fn get_session(&self, id: &str) -> Option { let sessions = self.sessions.read().await; sessions.iter().find(|s| s.id == id).cloned() } /// Execute agent prompt using Rig pub async fn execute_prompt( &self, session_id: &str, prompt: &str, ) -> Result { let session = self.get_session(session_id) .await .ok_or_else(|| anyhow::anyhow!("Session not found"))?; // Get provider config (API key + optional base URL) let (api_key, base_url) = self.get_provider_config(&session.config.provider)?; // Create Rig agent with OpenAI-compatible provider // Qwen uses OpenAI-compatible API, so we can use the OpenAI client let mut client_builder = openai::ClientBuilder::new(&api_key); // Use custom base URL if provided (for Qwen or other compatible APIs) if let Some(url) = base_url { client_builder = client_builder.base_url(&url); } let client = client_builder.build(); let agent = client .agent(&session.config.model) .preamble(&session.config.preamble) .build(); // Execute prompt let response = agent.prompt(prompt).await .map_err(|e| anyhow::anyhow!("Rig prompt execution failed: {}", e))?; // Store message let mut sessions = self.sessions.write().await; if let Some(session) = sessions.iter_mut().find(|s| s.id == session_id) { session.messages.push(Message { role: "user".to_string(), content: prompt.to_string(), }); session.messages.push(Message { role: "assistant".to_string(), content: response.clone(), }); } Ok(response) } /// Create agent council pub async fn create_council( &self, name: &str, agent_configs: Vec, ) -> Result { let mut agents = Vec::new(); for config in agent_configs { let session = AgentSession { id: Uuid::new_v4().to_string(), config, messages: Vec::new(), }; agents.push(session); } let council = AgentCouncil { id: Uuid::new_v4().to_string(), name: name.to_string(), agents, }; let council_id = council.id.clone(); let mut councils = self.councils.write().await; councils.push(council); Ok(council_id) } /// Execute council orchestration pub async fn execute_council( &self, council_id: &str, task: &str, ) -> Result { let council = self.councils.read() .await .iter() .find(|c| c.id == council_id) .cloned() .ok_or_else(|| anyhow::anyhow!("Council not found"))?; let mut results = Vec::new(); // Execute task with each agent for agent in &council.agents { match self.execute_prompt(&agent.id, task).await { Ok(result) => { results.push(format!("**{}**: {}", agent.config.name, result)); } Err(e) => { results.push(format!("**{}**: Error - {}", agent.config.name, e)); } } } // Synthesize results Ok(results.join("\n\n---\n\n")) } /// Get API key and base URL for provider fn get_provider_config(&self, provider: &str) -> Result<(String, Option)> { match provider.to_lowercase().as_str() { "qwen" | "qwen-plus" | "qwen-max" => { let api_key = std::env::var("QWEN_API_KEY") .map_err(|_| anyhow::anyhow!("QWEN_API_KEY not set. Get it from https://platform.qwen.ai"))?; let base_url = std::env::var("QWEN_BASE_URL").ok(); Ok((api_key, base_url)) } "openai" | "gpt-4" | "gpt-4o" | "gpt-3.5" => { let api_key = std::env::var("OPENAI_API_KEY") .map_err(|_| anyhow::anyhow!("OPENAI_API_KEY not set"))?; Ok((api_key, None)) } "anthropic" | "claude" | "claude-3" => { let api_key = std::env::var("ANTHROPIC_API_KEY") .map_err(|_| anyhow::anyhow!("ANTHROPIC_API_KEY not set"))?; Ok((api_key, None)) } "ollama" | "local" => { // Ollama doesn't need API key, uses localhost Ok(("".to_string(), Some("http://localhost:11434".to_string()))) } _ => { // Default to Qwen for QwenClaw let api_key = std::env::var("QWEN_API_KEY") .or_else(|_| std::env::var("OPENAI_API_KEY")) .map_err(|_| anyhow::anyhow!("No API key found. Set QWEN_API_KEY or OPENAI_API_KEY"))?; let base_url = std::env::var("QWEN_BASE_URL").ok(); Ok((api_key, base_url)) } } } /// List all sessions pub async fn list_sessions(&self) -> Vec { let sessions = self.sessions.read().await; sessions.clone() } /// List all councils pub async fn list_councils(&self) -> Vec { let councils = self.councils.read().await; councils.clone() } /// Delete a session pub async fn delete_session(&self, id: &str) -> Result<()> { let mut sessions = self.sessions.write().await; sessions.retain(|s| s.id != id); Ok(()) } }