use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
#[serde(default = "default_true")]
pub use_short_term: bool,
#[serde(default)]
pub use_long_term: bool,
#[serde(default = "default_memory_provider")]
pub provider: String,
#[serde(default = "default_max_messages")]
pub max_messages: usize,
}
fn default_true() -> bool {
true
}
fn default_memory_provider() -> String {
"memory".to_string()
}
fn default_max_messages() -> usize {
100
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
use_short_term: true,
use_long_term: false,
provider: "memory".to_string(),
max_messages: 100,
}
}
}
impl MemoryConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_long_term(mut self) -> Self {
self.use_long_term = true;
self
}
pub fn provider(mut self, provider: impl Into<String>) -> Self {
self.provider = provider.into();
self
}
pub fn max_messages(mut self, max: usize) -> Self {
self.max_messages = max;
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HooksConfig {
#[serde(default)]
pub enabled: bool,
}
impl HooksConfig {
pub fn new() -> Self {
Self::default()
}
pub fn enabled(mut self) -> Self {
self.enabled = true;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputConfig {
#[serde(default = "default_output_mode")]
pub mode: String,
#[serde(default)]
pub file: Option<String>,
}
fn default_output_mode() -> String {
"verbose".to_string()
}
impl Default for OutputConfig {
fn default() -> Self {
Self {
mode: default_output_mode(),
file: None,
}
}
}
impl OutputConfig {
pub fn new() -> Self {
Self::default()
}
pub fn silent(mut self) -> Self {
self.mode = "silent".to_string();
self
}
pub fn verbose(mut self) -> Self {
self.mode = "verbose".to_string();
self
}
pub fn json(mut self) -> Self {
self.mode = "json".to_string();
self
}
pub fn file(mut self, path: impl Into<String>) -> Self {
self.file = Some(path.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionConfig {
#[serde(default = "default_max_iterations")]
pub max_iterations: usize,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
#[serde(default = "default_true")]
pub stream: bool,
}
fn default_max_iterations() -> usize {
10
}
fn default_timeout() -> u64 {
300
}
impl Default for ExecutionConfig {
fn default() -> Self {
Self {
max_iterations: default_max_iterations(),
timeout_secs: default_timeout(),
stream: true,
}
}
}
impl ExecutionConfig {
pub fn new() -> Self {
Self::default()
}
pub fn max_iterations(mut self, max: usize) -> Self {
self.max_iterations = max;
self
}
pub fn timeout(mut self, secs: u64) -> Self {
self.timeout_secs = secs;
self
}
pub fn no_stream(mut self) -> Self {
self.stream = false;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GuardrailAction {
#[default]
Retry,
Skip,
Raise,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuardrailResult {
pub passed: bool,
pub message: Option<String>,
pub modified_output: Option<String>,
}
impl GuardrailResult {
pub fn pass() -> Self {
Self {
passed: true,
message: None,
modified_output: None,
}
}
pub fn fail(message: impl Into<String>) -> Self {
Self {
passed: false,
message: Some(message.into()),
modified_output: None,
}
}
pub fn with_modification(mut self, output: impl Into<String>) -> Self {
self.modified_output = Some(output.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GuardrailConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub llm_validator: Option<String>,
#[serde(default = "default_guardrail_retries")]
pub max_retries: usize,
#[serde(default)]
pub on_fail: GuardrailAction,
#[serde(default)]
pub policies: Vec<String>,
}
fn default_guardrail_retries() -> usize {
3
}
impl GuardrailConfig {
pub fn new() -> Self {
Self::default()
}
pub fn enabled(mut self) -> Self {
self.enabled = true;
self
}
pub fn llm_validator(mut self, prompt: impl Into<String>) -> Self {
self.llm_validator = Some(prompt.into());
self.enabled = true;
self
}
pub fn max_retries(mut self, retries: usize) -> Self {
self.max_retries = retries;
self
}
pub fn on_fail(mut self, action: GuardrailAction) -> Self {
self.on_fail = action;
self
}
pub fn policy(mut self, policy: impl Into<String>) -> Self {
self.policies.push(policy.into());
self.enabled = true;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ChunkingStrategy {
Fixed,
#[default]
Semantic,
Sentence,
Paragraph,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KnowledgeConfig {
#[serde(default)]
pub sources: Vec<String>,
#[serde(default = "default_embedder")]
pub embedder: String,
#[serde(default)]
pub chunking_strategy: ChunkingStrategy,
#[serde(default = "default_chunk_size")]
pub chunk_size: usize,
#[serde(default = "default_chunk_overlap")]
pub chunk_overlap: usize,
#[serde(default = "default_retrieval_k")]
pub retrieval_k: usize,
#[serde(default)]
pub retrieval_threshold: f32,
#[serde(default)]
pub rerank: bool,
#[serde(default)]
pub rerank_model: Option<String>,
#[serde(default = "default_true")]
pub auto_retrieve: bool,
}
fn default_embedder() -> String {
"openai".to_string()
}
fn default_chunk_size() -> usize {
1000
}
fn default_chunk_overlap() -> usize {
200
}
fn default_retrieval_k() -> usize {
5
}
impl KnowledgeConfig {
pub fn new() -> Self {
Self::default()
}
pub fn source(mut self, source: impl Into<String>) -> Self {
self.sources.push(source.into());
self
}
pub fn sources(mut self, sources: Vec<String>) -> Self {
self.sources = sources;
self
}
pub fn embedder(mut self, embedder: impl Into<String>) -> Self {
self.embedder = embedder.into();
self
}
pub fn chunking(mut self, strategy: ChunkingStrategy) -> Self {
self.chunking_strategy = strategy;
self
}
pub fn chunk_size(mut self, size: usize) -> Self {
self.chunk_size = size;
self
}
pub fn retrieval_k(mut self, k: usize) -> Self {
self.retrieval_k = k;
self
}
pub fn with_rerank(mut self) -> Self {
self.rerank = true;
self
}
pub fn rerank_model(mut self, model: impl Into<String>) -> Self {
self.rerank_model = Some(model.into());
self.rerank = true;
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PlanningConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub llm: Option<String>,
#[serde(default)]
pub reasoning: bool,
#[serde(default)]
pub auto_approve: bool,
#[serde(default)]
pub read_only: bool,
}
impl PlanningConfig {
pub fn new() -> Self {
Self::default()
}
pub fn enabled(mut self) -> Self {
self.enabled = true;
self
}
pub fn llm(mut self, llm: impl Into<String>) -> Self {
self.llm = Some(llm.into());
self.enabled = true;
self
}
pub fn with_reasoning(mut self) -> Self {
self.reasoning = true;
self
}
pub fn auto_approve(mut self) -> Self {
self.auto_approve = true;
self
}
pub fn read_only(mut self) -> Self {
self.read_only = true;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReflectionConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_min_iterations")]
pub min_iterations: usize,
#[serde(default = "default_max_reflect_iterations")]
pub max_iterations: usize,
#[serde(default)]
pub llm: Option<String>,
#[serde(default)]
pub prompt: Option<String>,
}
fn default_min_iterations() -> usize {
1
}
fn default_max_reflect_iterations() -> usize {
3
}
impl Default for ReflectionConfig {
fn default() -> Self {
Self {
enabled: false,
min_iterations: 1,
max_iterations: 3,
llm: None,
prompt: None,
}
}
}
impl ReflectionConfig {
pub fn new() -> Self {
Self::default()
}
pub fn enabled(mut self) -> Self {
self.enabled = true;
self
}
pub fn min_iterations(mut self, min: usize) -> Self {
self.min_iterations = min;
self.enabled = true;
self
}
pub fn max_iterations(mut self, max: usize) -> Self {
self.max_iterations = max;
self.enabled = true;
self
}
pub fn llm(mut self, llm: impl Into<String>) -> Self {
self.llm = Some(llm.into());
self.enabled = true;
self
}
pub fn prompt(mut self, prompt: impl Into<String>) -> Self {
self.prompt = Some(prompt.into());
self.enabled = true;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CachingConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default)]
pub prompt_caching: bool,
#[serde(default)]
pub ttl_secs: Option<u64>,
}
impl Default for CachingConfig {
fn default() -> Self {
Self {
enabled: true,
prompt_caching: false,
ttl_secs: None,
}
}
}
impl CachingConfig {
pub fn new() -> Self {
Self::default()
}
pub fn disabled(mut self) -> Self {
self.enabled = false;
self
}
pub fn with_prompt_caching(mut self) -> Self {
self.prompt_caching = true;
self
}
pub fn ttl(mut self, secs: u64) -> Self {
self.ttl_secs = Some(secs);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WebSearchProvider {
#[default]
DuckDuckGo,
Google,
Bing,
Tavily,
Serper,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebConfig {
#[serde(default = "default_true")]
pub search: bool,
#[serde(default = "default_true")]
pub fetch: bool,
#[serde(default)]
pub search_provider: WebSearchProvider,
#[serde(default = "default_max_results")]
pub max_results: usize,
}
fn default_max_results() -> usize {
5
}
impl Default for WebConfig {
fn default() -> Self {
Self {
search: true,
fetch: true,
search_provider: WebSearchProvider::default(),
max_results: 5,
}
}
}
impl WebConfig {
pub fn new() -> Self {
Self::default()
}
pub fn no_search(mut self) -> Self {
self.search = false;
self
}
pub fn no_fetch(mut self) -> Self {
self.fetch = false;
self
}
pub fn provider(mut self, provider: WebSearchProvider) -> Self {
self.search_provider = provider;
self
}
pub fn max_results(mut self, max: usize) -> Self {
self.max_results = max;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AutonomyLevel {
#[default]
Suggest,
AutoEdit,
FullAuto,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AutonomyConfig {
#[serde(default)]
pub level: AutonomyLevel,
#[serde(default = "default_true")]
pub require_approval: bool,
#[serde(default)]
pub max_actions: Option<usize>,
#[serde(default)]
pub allowed_tools: Vec<String>,
#[serde(default)]
pub blocked_tools: Vec<String>,
}
impl AutonomyConfig {
pub fn new() -> Self {
Self::default()
}
pub fn level(mut self, level: AutonomyLevel) -> Self {
self.level = level;
self
}
pub fn no_approval(mut self) -> Self {
self.require_approval = false;
self
}
pub fn max_actions(mut self, max: usize) -> Self {
self.max_actions = Some(max);
self
}
pub fn allow_tool(mut self, tool: impl Into<String>) -> Self {
self.allowed_tools.push(tool.into());
self
}
pub fn block_tool(mut self, tool: impl Into<String>) -> Self {
self.blocked_tools.push(tool.into());
self
}
pub fn full_auto(mut self) -> Self {
self.level = AutonomyLevel::FullAuto;
self.require_approval = false;
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SkillsConfig {
#[serde(default)]
pub paths: Vec<String>,
#[serde(default)]
pub dirs: Vec<String>,
#[serde(default)]
pub auto_discover: bool,
}
impl SkillsConfig {
pub fn new() -> Self {
Self::default()
}
pub fn path(mut self, path: impl Into<String>) -> Self {
self.paths.push(path.into());
self
}
pub fn dir(mut self, dir: impl Into<String>) -> Self {
self.dirs.push(dir.into());
self
}
pub fn auto_discover(mut self) -> Self {
self.auto_discover = true;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateConfig {
#[serde(default)]
pub system: Option<String>,
#[serde(default)]
pub prompt: Option<String>,
#[serde(default)]
pub response: Option<String>,
#[serde(default = "default_true")]
pub use_system_prompt: bool,
}
impl Default for TemplateConfig {
fn default() -> Self {
Self {
system: None,
prompt: None,
response: None,
use_system_prompt: true,
}
}
}
impl TemplateConfig {
pub fn new() -> Self {
Self::default()
}
pub fn system(mut self, template: impl Into<String>) -> Self {
self.system = Some(template.into());
self
}
pub fn prompt(mut self, template: impl Into<String>) -> Self {
self.prompt = Some(template.into());
self
}
pub fn response(mut self, template: impl Into<String>) -> Self {
self.response = Some(template.into());
self
}
pub fn no_system_prompt(mut self) -> Self {
self.use_system_prompt = false;
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MultiAgentHooksConfig {
#[serde(default)]
pub on_task_start: bool,
#[serde(default)]
pub on_task_complete: bool,
#[serde(default)]
pub completion_checker: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MultiAgentOutputConfig {
#[serde(default)]
pub verbose: u8,
#[serde(default = "default_true")]
pub stream: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiAgentExecutionConfig {
#[serde(default = "default_multi_agent_iter")]
pub max_iter: usize,
#[serde(default = "default_multi_agent_retries")]
pub max_retries: usize,
}
fn default_multi_agent_iter() -> usize {
10
}
fn default_multi_agent_retries() -> usize {
5
}
impl Default for MultiAgentExecutionConfig {
fn default() -> Self {
Self {
max_iter: 10,
max_retries: 5,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MultiAgentPlanningConfig {
#[serde(default)]
pub llm: Option<String>,
#[serde(default)]
pub auto_approve: bool,
#[serde(default)]
pub reasoning: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MultiAgentMemoryConfig {
#[serde(default)]
pub user_id: Option<String>,
#[serde(default)]
pub config: HashMap<String, serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_config_defaults() {
let config = MemoryConfig::new();
assert!(config.use_short_term);
assert!(!config.use_long_term);
assert_eq!(config.provider, "memory");
}
#[test]
fn test_guardrail_config() {
let config = GuardrailConfig::new()
.llm_validator("Ensure response is safe")
.max_retries(5)
.on_fail(GuardrailAction::Raise)
.policy("pii:redact");
assert!(config.enabled);
assert_eq!(
config.llm_validator,
Some("Ensure response is safe".to_string())
);
assert_eq!(config.max_retries, 5);
assert_eq!(config.on_fail, GuardrailAction::Raise);
assert!(config.policies.contains(&"pii:redact".to_string()));
}
#[test]
fn test_guardrail_result() {
let pass = GuardrailResult::pass();
assert!(pass.passed);
assert!(pass.message.is_none());
let fail = GuardrailResult::fail("Invalid content");
assert!(!fail.passed);
assert_eq!(fail.message, Some("Invalid content".to_string()));
let modified = GuardrailResult::pass().with_modification("Modified output");
assert!(modified.passed);
assert_eq!(
modified.modified_output,
Some("Modified output".to_string())
);
}
#[test]
fn test_knowledge_config() {
let config = KnowledgeConfig::new()
.source("docs/")
.source("data.pdf")
.embedder("openai")
.chunking(ChunkingStrategy::Semantic)
.retrieval_k(10)
.with_rerank();
assert_eq!(config.sources.len(), 2);
assert_eq!(config.embedder, "openai");
assert_eq!(config.chunking_strategy, ChunkingStrategy::Semantic);
assert_eq!(config.retrieval_k, 10);
assert!(config.rerank);
}
#[test]
fn test_planning_config() {
let config = PlanningConfig::new()
.llm("gpt-4o")
.with_reasoning()
.auto_approve();
assert!(config.enabled);
assert_eq!(config.llm, Some("gpt-4o".to_string()));
assert!(config.reasoning);
assert!(config.auto_approve);
}
#[test]
fn test_reflection_config() {
let config = ReflectionConfig::new()
.min_iterations(2)
.max_iterations(5)
.prompt("Evaluate accuracy");
assert!(config.enabled);
assert_eq!(config.min_iterations, 2);
assert_eq!(config.max_iterations, 5);
assert_eq!(config.prompt, Some("Evaluate accuracy".to_string()));
}
#[test]
fn test_caching_config() {
let config = CachingConfig::new().with_prompt_caching().ttl(3600);
assert!(config.enabled);
assert!(config.prompt_caching);
assert_eq!(config.ttl_secs, Some(3600));
}
#[test]
fn test_web_config() {
let config = WebConfig::new()
.provider(WebSearchProvider::Tavily)
.max_results(10);
assert!(config.search);
assert!(config.fetch);
assert_eq!(config.search_provider, WebSearchProvider::Tavily);
assert_eq!(config.max_results, 10);
}
#[test]
fn test_autonomy_config() {
let config = AutonomyConfig::new()
.level(AutonomyLevel::AutoEdit)
.max_actions(10)
.allow_tool("search")
.block_tool("delete");
assert_eq!(config.level, AutonomyLevel::AutoEdit);
assert_eq!(config.max_actions, Some(10));
assert!(config.allowed_tools.contains(&"search".to_string()));
assert!(config.blocked_tools.contains(&"delete".to_string()));
}
#[test]
fn test_autonomy_full_auto() {
let config = AutonomyConfig::new().full_auto();
assert_eq!(config.level, AutonomyLevel::FullAuto);
assert!(!config.require_approval);
}
#[test]
fn test_skills_config() {
let config = SkillsConfig::new()
.path("./my-skill")
.dir("~/.praisonai/skills/")
.auto_discover();
assert!(config.paths.contains(&"./my-skill".to_string()));
assert!(config.dirs.contains(&"~/.praisonai/skills/".to_string()));
assert!(config.auto_discover);
}
#[test]
fn test_template_config() {
let config = TemplateConfig::new()
.system("You are a helpful assistant")
.prompt("User: {input}")
.response("Response format");
assert_eq!(
config.system,
Some("You are a helpful assistant".to_string())
);
assert_eq!(config.prompt, Some("User: {input}".to_string()));
assert_eq!(config.response, Some("Response format".to_string()));
assert!(config.use_system_prompt);
}
#[test]
fn test_memory_config_builder() {
let config = MemoryConfig::new()
.with_long_term()
.provider("chroma")
.max_messages(50);
assert!(config.use_long_term);
assert_eq!(config.provider, "chroma");
assert_eq!(config.max_messages, 50);
}
#[test]
fn test_output_config() {
let config = OutputConfig::new().silent().file("output.txt");
assert_eq!(config.mode, "silent");
assert_eq!(config.file, Some("output.txt".to_string()));
}
}