mod common;
use serde::{Deserialize, Serialize};
use sombrax_agentic_core::providers::build_agent;
use sombrax_agentic_core::tool::{into_arc_dyn, Tool, ToolDefinition};
#[derive(Debug, Deserialize)]
struct CalcArgs {
op: String,
a: f64,
b: f64,
}
#[derive(Debug, Serialize)]
struct CalcResult {
result: f64,
}
#[derive(Debug)]
struct CalcError(String);
impl std::fmt::Display for CalcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for CalcError {}
#[derive(Clone)]
struct Calculator;
impl Tool for Calculator {
const NAME: &'static str = "calculator";
type Args = CalcArgs;
type Output = CalcResult;
type Error = CalcError;
async fn definition(&self, _prompt: String) -> ToolDefinition {
ToolDefinition::new(
Self::NAME,
"Perform a single arithmetic operation on two numbers. \
Call this once per operation; chain calls for multi-step math.",
serde_json::json!({
"type": "object",
"properties": {
"op": {
"type": "string",
"enum": ["add", "sub", "mul", "div"],
"description": "The operation to perform"
},
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["op", "a", "b"]
}),
)
}
async fn call(&self, args: CalcArgs) -> Result<CalcResult, CalcError> {
let result = match args.op.as_str() {
"add" => args.a + args.b,
"sub" => args.a - args.b,
"mul" => args.a * args.b,
"div" => {
if args.b == 0.0 {
return Err(CalcError("division by zero".to_string()));
}
args.a / args.b
}
other => return Err(CalcError(format!("unknown op: {other}"))),
};
tracing::info!("calculator: {} {} {} = {result}", args.a, args.op, args.b);
Ok(CalcResult { result })
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
common::init_tracing();
let cfg = common::config_from_env();
common::banner("ReAct agent with a custom Calculator tool", &cfg);
let system = "You are a precise math assistant. You CANNOT do arithmetic yourself — \
you must use the `calculator` tool for every individual operation, one \
operation per call. After computing, state the final numeric answer clearly.";
let tools = vec![into_arc_dyn(Calculator)];
let agent = build_agent(&cfg, system, 600, tools).await?;
let question = std::env::var("SAC_QUESTION").unwrap_or_else(|_| {
"Compute (12.5 * 8) + 100, then divide that total by 3. Show the final number.".to_string()
});
println!("Question: {question}\n");
let (answer, stats) = agent.execute(&question, &[]).await?;
println!("Answer:\n {}\n", answer.trim().replace('\n', "\n "));
println!(
"Tool calls: {} · completions: {} · tokens: {} · tool errors: {}",
stats.tool_calls,
stats.message_count,
stats.total_tokens(),
stats.tool_error_count,
);
Ok(())
}