llm/agent/
builder.rs

1//! Agent builder for creating reactive LLM agents with memory and role-based triggers.
2
3use crate::{
4    builder::LLMBuilder,
5    error::LLMError,
6    memory::{ChatWithMemory, MemoryProvider, MessageCondition},
7    LLMProvider,
8};
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12/// Builder for creating reactive LLM agents.
13///
14/// AgentBuilder provides a clean interface for creating agents that can:
15/// - React to messages from other agents based on role and conditions
16/// - Share memory across multiple agents
17/// - Control reactive cycles to prevent infinite loops
18/// - Maintain conversation context
19/// - Handle speech-to-text and text-to-speech capabilities
20pub struct AgentBuilder {
21    llm_builder: LLMBuilder,
22    role: Option<String>,
23    role_triggers: Vec<(String, MessageCondition)>,
24    max_cycles: Option<u32>,
25    single_reply_per_turn: bool,
26    debounce_ms: Option<u64>,
27    stt_builder: Option<LLMBuilder>,
28    tts_builder: Option<LLMBuilder>,
29    memory: Option<Box<dyn MemoryProvider>>,
30}
31
32impl AgentBuilder {
33    /// Creates a new AgentBuilder instance.
34    pub fn new() -> Self {
35        Self {
36            llm_builder: LLMBuilder::new(),
37            role: None,
38            role_triggers: Vec::new(),
39            max_cycles: None,
40            single_reply_per_turn: false,
41            debounce_ms: None,
42            stt_builder: None,
43            tts_builder: None,
44            memory: None,
45        }
46    }
47
48    /// Sets the role name for this agent.
49    ///
50    /// The role is used to identify messages from this agent in shared memory
51    /// and for reactive message filtering.
52    pub fn role(mut self, role: impl Into<String>) -> Self {
53        self.role = Some(role.into());
54        self
55    }
56
57    /// Configures the agent to react to messages from a specific role with a condition.
58    ///
59    /// The agent will only trigger when messages from the specified role match the condition.
60    pub fn on(mut self, role: impl Into<String>, condition: MessageCondition) -> Self {
61        self.role_triggers.push((role.into(), condition));
62        self
63    }
64
65    /// Sets the maximum number of reactive cycles this agent can perform.
66    ///
67    /// This prevents infinite loops in multi-agent conversations.
68    pub fn max_cycles(mut self, max: u32) -> Self {
69        self.max_cycles = Some(max);
70        self
71    }
72
73    /// Configures the agent to send only one reply per conversational turn.
74    pub fn single_reply_per_turn(mut self, enabled: bool) -> Self {
75        self.single_reply_per_turn = enabled;
76        self
77    }
78
79    /// Sets a debounce delay in milliseconds before reacting to messages.
80    pub fn debounce(mut self, ms: u64) -> Self {
81        self.debounce_ms = Some(ms);
82        self
83    }
84
85    /// Sets the underlying LLM configuration.
86    pub fn llm(mut self, llm_builder: LLMBuilder) -> Self {
87        self.llm_builder = llm_builder;
88        self
89    }
90
91    /// Sets the Speech-to-Text LLM configuration.
92    pub fn stt(mut self, stt_builder: LLMBuilder) -> Self {
93        self.stt_builder = Some(stt_builder);
94        self
95    }
96
97    /// Sets the Text-to-Speech LLM configuration.
98    pub fn tts(mut self, tts_builder: LLMBuilder) -> Self {
99        self.tts_builder = Some(tts_builder);
100        self
101    }
102
103    /// Sets a memory provider for the agent.
104    pub fn memory(mut self, memory: impl MemoryProvider + 'static) -> Self {
105        self.memory = Some(Box::new(memory));
106        self
107    }
108
109    /// Builds the agent and returns an LLM provider with agent capabilities.
110    ///
111    /// # Errors
112    ///
113    /// Returns an error if the underlying LLM configuration is invalid.
114    pub fn build(self) -> Result<Box<dyn LLMProvider>, LLMError> {
115        // Build the base LLM provider
116        let base_provider = self.llm_builder.build()?;
117
118        // If memory is configured, wrap with ChatWithMemory including agent capabilities
119        if let Some(memory) = self.memory {
120            let memory_arc = Arc::new(RwLock::new(memory));
121            let provider_arc = Arc::from(base_provider);
122            let agent_provider = ChatWithMemory::new(
123                provider_arc,
124                memory_arc,
125                self.role,
126                self.role_triggers,
127                self.max_cycles,
128            );
129            Ok(Box::new(agent_provider))
130        } else {
131            // No memory, return base provider
132            Ok(base_provider)
133        }
134    }
135}
136
137impl Default for AgentBuilder {
138    fn default() -> Self {
139        Self::new()
140    }
141}