use llm_toolkit::agent::{Agent, AgentError, Payload};
use llm_toolkit::intent::expandable::{
ReActConfig, SelectionRegistry, react_loop, simple_tag_selector,
};
use std::sync::{Arc, Mutex};
use llm_toolkit::intent::expandable::{Expandable, Selectable};
#[derive(Debug, Clone, PartialEq)]
pub enum CalculatorAction {
Add { a: String, b: String },
Multiply { a: String, b: String },
GetResult,
}
impl Expandable for CalculatorAction {
fn expand(&self) -> Payload {
match self {
CalculatorAction::Add { a, b } => Payload::from(format!(
"Calculate the sum: {} + {}\nReturn only the numeric result.",
a, b
)),
CalculatorAction::Multiply { a, b } => Payload::from(format!(
"Calculate the product: {} * {}\nReturn only the numeric result.",
a, b
)),
CalculatorAction::GetResult => Payload::from("Return the final result."),
}
}
}
impl Selectable for CalculatorAction {
fn selection_id(&self) -> &str {
match self {
CalculatorAction::Add { .. } => "add",
CalculatorAction::Multiply { .. } => "multiply",
CalculatorAction::GetResult => "get_result",
}
}
fn description(&self) -> &str {
match self {
CalculatorAction::Add { .. } => "Add two numbers",
CalculatorAction::Multiply { .. } => "Multiply two numbers",
CalculatorAction::GetResult => "Get the result",
}
}
}
struct MockCalculatorAgent {
iteration: Arc<Mutex<usize>>,
}
impl MockCalculatorAgent {
fn new() -> Self {
Self {
iteration: Arc::new(Mutex::new(0)),
}
}
}
#[async_trait::async_trait]
impl Agent for MockCalculatorAgent {
type Output = String;
type Expertise = &'static str;
fn expertise(&self) -> &&'static str {
const EXPERTISE: &str = "Mock calculator agent for demonstration";
&EXPERTISE
}
async fn execute(&self, payload: Payload) -> Result<Self::Output, AgentError> {
let prompt = payload.to_text();
let mut iter = self.iteration.lock().unwrap();
*iter += 1;
let iteration = *iter;
println!("\n=== Agent Execution (iteration {}) ===", iteration);
println!("Received prompt:\n{}\n", prompt);
let response = match iteration {
1 => {
"<action>add</action>\nI'll start by adding 5 and 3.".to_string()
}
2 => {
"The result of 5 + 3 is: 8".to_string()
}
3 => {
"<action>multiply</action>\nNow I'll multiply the result by 2.".to_string()
}
4 => {
"The result of 8 * 2 is: 16".to_string()
}
5 => {
"DONE\nThe final result is 16. Task complete!".to_string()
}
_ => {
"DONE\nTask complete".to_string()
}
};
println!("Agent response:\n{}\n", response);
Ok(response)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== ReAct Loop Execution Example ===\n");
let mut registry = SelectionRegistry::new();
registry.register(CalculatorAction::Add {
a: "5".to_string(),
b: "3".to_string(),
});
registry.register(CalculatorAction::Multiply {
a: "8".to_string(),
b: "2".to_string(),
});
registry.register(CalculatorAction::GetResult);
println!("Registered {} actions:", registry.len());
println!("{}\n", registry.to_prompt_section());
let agent = MockCalculatorAgent::new();
let selector = simple_tag_selector("action", "DONE");
let config = ReActConfig::new()
.with_max_iterations(10)
.with_completion_marker("DONE")
.with_accumulate_results(true);
println!("=== Starting ReAct Loop ===\n");
let result = react_loop(&agent, ®istry, "Calculate (5 + 3) * 2", selector, config).await?;
println!("\n=== ReAct Loop Complete ===");
println!("Final result:\n{}", result);
println!("\n=== Summary ===");
println!("The ReAct loop successfully:");
println!("1. Selected the 'add' action");
println!("2. Expanded it into a calculation prompt");
println!("3. Executed and got result: 8");
println!("4. Selected the 'multiply' action");
println!("5. Expanded it into another calculation prompt");
println!("6. Executed and got result: 16");
println!("7. Completed with final result");
Ok(())
}