use oxidizedgraph::prelude::*;
struct MockLLM;
impl MockLLM {
async fn generate(&self, messages: &[Message]) -> (String, Vec<ToolCall>) {
let last = messages.last();
match last {
Some(msg) if msg.role == MessageRole::User => {
let text = &msg.content;
if text.contains("weather") {
(
"I'll check the weather for you.".to_string(),
vec![ToolCall::new(
"call_001",
"get_weather",
serde_json::json!({"location": "San Francisco"}),
)],
)
} else if text.contains("calculate") || text.contains("math") {
(
"Let me calculate that.".to_string(),
vec![ToolCall::new(
"call_002",
"calculator",
serde_json::json!({"expression": "42 * 17"}),
)],
)
} else {
(
"I can help you with weather lookups and calculations. What would you like to know?".to_string(),
vec![],
)
}
}
Some(msg) if msg.role == MessageRole::Tool => {
(
format!(
"Based on the information I gathered: {}. Is there anything else you'd like to know?",
msg.content
),
vec![],
)
}
_ => (
"How can I help you today?".to_string(),
vec![],
),
}
}
}
struct WeatherTool;
#[async_trait]
impl Tool for WeatherTool {
fn name(&self) -> &str {
"get_weather"
}
fn description(&self) -> &str {
"Get the current weather for a location"
}
fn parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city to get weather for"
}
},
"required": ["location"]
})
}
async fn execute(&self, arguments: serde_json::Value) -> Result<String, NodeError> {
let location = arguments["location"]
.as_str()
.unwrap_or("Unknown");
Ok(format!("Weather in {}: Sunny, 72°F", location))
}
}
struct CalculatorTool;
#[async_trait]
impl Tool for CalculatorTool {
fn name(&self) -> &str {
"calculator"
}
fn description(&self) -> &str {
"Perform mathematical calculations"
}
fn parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The math expression to evaluate"
}
},
"required": ["expression"]
})
}
async fn execute(&self, arguments: serde_json::Value) -> Result<String, NodeError> {
let expr = arguments["expression"]
.as_str()
.unwrap_or("0");
if expr.contains("42 * 17") {
Ok("714".to_string())
} else {
Ok("Result: computed".to_string())
}
}
}
struct CallModelNode {
llm: MockLLM,
}
#[async_trait]
impl NodeExecutor for CallModelNode {
fn id(&self) -> &str {
"call_model"
}
async fn execute(&self, state: SharedState) -> Result<NodeOutput, NodeError> {
let messages = {
let guard = state
.read()
.map_err(|e| NodeError::execution_failed(e.to_string()))?;
guard.messages.clone()
};
println!("Calling LLM...");
let (response, tool_calls) = self.llm.generate(&messages).await;
{
let mut guard = state
.write()
.map_err(|e| NodeError::execution_failed(e.to_string()))?;
guard.add_assistant_message(&response);
guard.tool_calls = tool_calls.clone();
if tool_calls.is_empty() {
guard.mark_complete();
}
println!(" Response: {}", response);
if !tool_calls.is_empty() {
println!(" Tool calls: {}", tool_calls.len());
}
}
Ok(NodeOutput::cont())
}
fn description(&self) -> Option<&str> {
Some("Calls the LLM to get next action")
}
}
struct AgentRouter;
impl AgentRouter {
fn route(state: &AgentState) -> String {
if state.is_complete {
println!("Routing to END");
transitions::END.to_string()
} else if state.has_pending_tool_calls() {
println!("Routing to execute_tools");
"execute_tools".to_string()
} else {
println!("Routing to END (no tool calls)");
transitions::END.to_string()
}
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
println!("ReAct Agent Example");
println!("===================\n");
let tools = ToolRegistry::new()
.register(WeatherTool)
.register(CalculatorTool);
let graph = GraphBuilder::new()
.name("ReAct Agent")
.description("A ReAct agent that can use tools")
.add_node(CallModelNode { llm: MockLLM })
.add_node(ToolNode::new("execute_tools", tools))
.set_entry_point("call_model")
.add_conditional_edge("call_model", AgentRouter::route)
.add_edge("execute_tools", "call_model")
.compile()?;
println!("Graph Structure (Mermaid):");
println!("```mermaid");
println!("{}", graph.to_mermaid());
println!("```\n");
let runner = GraphRunner::new(
graph,
RunnerConfig::default()
.max_iterations(10)
.tag("react")
.tag("example"),
);
let mut initial_state = AgentState::new();
initial_state.messages.push(Message::system(
"You are a helpful assistant with access to weather and calculator tools.",
));
initial_state.messages.push(Message::user(
"What's the weather like in San Francisco?",
));
println!("User: What's the weather like in San Francisco?\n");
println!("---\n");
let final_state = runner.invoke(initial_state).await?;
println!("\n---\n");
println!("Agent Complete!");
println!("---------------");
println!("Total iterations: {}", final_state.iteration);
println!("Total messages: {}", final_state.messages.len());
println!("Is complete: {}", final_state.is_complete);
if let Some(last) = final_state.last_assistant_message() {
println!("\nFinal Response:");
println!(" {}", last.content);
}
Ok(())
}