use crate::structs::persistence;
use crate::structs::tool::ToolError;
use colored::*;
use futures::future::BoxFuture;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use thiserror::Error;
use tokio::sync::broadcast;
#[derive(Debug, Error)]
pub enum AgentError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serde json error: {0}")]
SerdeError(#[from] serde_json::Error),
#[error("Broadcast error: {0}")]
BroadcastError(#[from] broadcast::error::SendError<Result<String, String>>),
#[error("Persistence error: {0}")]
PersistenceError(#[from] persistence::PersistenceError),
#[error("Invalid save state path: {0}")]
InvalidSaveStatePath(String),
#[error("Completion error: {0}")]
CompletionError(#[from] crate::llm::CompletionError),
#[error("No choice found")]
NoChoiceFound,
#[error("Tool {0} not found")]
ToolNotFound(String),
#[error("Tool error: {0}")]
ToolError(#[from] ToolError),
#[cfg(test)]
#[error("Test error")]
TestError(String),
}
#[derive(Clone)]
pub struct AgentConfigBuilder {
config: Arc<AgentConfig>,
}
impl AgentConfigBuilder {
pub fn agent_name(mut self, name: impl Into<String>) -> Self {
let name = name.into();
log::debug!("🏷️ Setting agent name: {}", name.bright_cyan().bold());
Arc::make_mut(&mut self.config).name = name;
self
}
pub fn user_name(mut self, name: impl Into<String>) -> Self {
Arc::make_mut(&mut self.config).user_name = name.into();
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
Arc::make_mut(&mut self.config).description = Some(description.into());
self
}
pub fn temperature(mut self, temperature: f64) -> Self {
Arc::make_mut(&mut self.config).temperature = temperature;
self
}
pub fn max_loops(mut self, max_loops: u32) -> Self {
Arc::make_mut(&mut self.config).max_loops = max_loops;
self
}
pub fn max_tokens(mut self, max_tokens: u64) -> Self {
Arc::make_mut(&mut self.config).max_tokens = max_tokens;
self
}
pub fn enable_plan(mut self, planning_prompt: impl Into<Option<String>>) -> Self {
let config = Arc::make_mut(&mut self.config);
config.plan_enabled = true;
config.planning_prompt = planning_prompt.into();
self
}
pub fn enable_autosave(mut self) -> Self {
Arc::make_mut(&mut self.config).autosave = true;
self
}
pub fn retry_attempts(mut self, retry_attempts: u32) -> Self {
Arc::make_mut(&mut self.config).retry_attempts = retry_attempts;
self
}
pub fn enable_rag_every_loop(mut self) -> Self {
Arc::make_mut(&mut self.config).rag_every_loop = true;
self
}
pub fn save_sate_path(mut self, path: impl Into<String>) -> Self {
Arc::make_mut(&mut self.config).save_state_dir = Some(path.into());
self
}
pub fn add_stop_word(mut self, stop_word: impl Into<String>) -> Self {
Arc::make_mut(&mut self.config)
.stop_words
.insert(stop_word.into());
self
}
pub fn stop_words(mut self, stop_words: Vec<String>) -> Self {
let config = Arc::make_mut(&mut self.config);
config.stop_words = stop_words.into_iter().collect();
self
}
pub fn build(self) -> Arc<AgentConfig> {
let config = &self.config;
log::info!(
"🎯 Agent configuration built: {} (ID: {}) - Max loops: {}, Temperature: {}, Max tokens: {}",
config.name.bright_cyan().bold(),
config.id.bright_yellow(),
config.max_loops.to_string().bright_green(),
config.temperature.to_string().bright_blue(),
config.max_tokens.to_string().bright_purple()
);
self.config
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
pub id: String,
pub name: String,
pub user_name: String,
pub description: Option<String>,
pub temperature: f64,
pub max_loops: u32,
pub max_tokens: u64,
pub plan_enabled: bool,
pub planning_prompt: Option<String>,
pub autosave: bool,
pub retry_attempts: u32,
pub rag_every_loop: bool,
pub save_state_dir: Option<String>,
#[serde(with = "hashset_serde")]
pub stop_words: HashSet<String>,
pub task_evaluator_tool_enabled: bool,
pub concurrent_tool_call_enabled: bool,
#[serde(skip)]
pub response_cache: HashMap<String, String>,
}
mod hashset_serde {
use super::*;
use serde::{Deserializer, Serializer};
use std::collections::HashSet;
pub fn serialize<S>(set: &HashSet<String>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let vec: Vec<_> = set.iter().collect();
vec.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<HashSet<String>, D::Error>
where
D: Deserializer<'de>,
{
let vec: Vec<String> = Vec::deserialize(deserializer)?;
Ok(vec.into_iter().collect())
}
}
impl AgentConfig {
pub fn builder() -> AgentConfigBuilder {
AgentConfigBuilder {
config: Arc::new(AgentConfig::default()),
}
}
pub fn compute_hash(&self, input: &str) -> u64 {
let mut hasher = DefaultHasher::new();
input.hash(&mut hasher);
hasher.finish()
}
pub fn get_cached_response(&self, input: &str) -> Option<&String> {
self.response_cache.get(input)
}
pub fn cache_response(&mut self, input: String, response: String) {
self.response_cache.insert(input, response);
}
}
impl Default for AgentConfig {
fn default() -> Self {
let id = uuid::Uuid::new_v4().to_string();
log::debug!(
"🆕 Creating default agent configuration with ID: {}",
id.bright_yellow()
);
Self {
id,
name: "Agent".to_owned(),
user_name: "User".to_owned(),
description: None,
temperature: 0.7,
max_loops: 1,
max_tokens: 8192,
plan_enabled: false,
planning_prompt: None,
autosave: false,
retry_attempts: 3,
rag_every_loop: false,
save_state_dir: None,
stop_words: HashSet::with_capacity(16), task_evaluator_tool_enabled: true,
concurrent_tool_call_enabled: true,
response_cache: HashMap::with_capacity(100), }
}
}
pub trait Agent: Send + Sync {
fn run(&self, task: String) -> BoxFuture<Result<String, AgentError>>;
fn run_multiple_tasks(
&mut self,
tasks: Vec<String>,
) -> BoxFuture<Result<Vec<String>, AgentError>>;
fn plan(&self, task: String) -> BoxFuture<Result<(), AgentError>>;
fn query_long_term_memory(&self, task: String) -> BoxFuture<Result<(), AgentError>>;
fn save_task_state(&self, task: String) -> BoxFuture<Result<(), AgentError>>;
fn is_response_complete(&self, response: String) -> bool;
fn id(&self) -> String;
fn name(&self) -> String;
fn description(&self) -> String;
fn clone_box(&self) -> Box<dyn Agent>;
}
impl Clone for Box<dyn Agent> {
fn clone(&self) -> Self {
self.clone_box()
}
}