use tinyagents::{Command, END, GraphBuilder, NodeContext, NodeResult, assert_graph, run_recorded};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct Doc {
n: i64,
path: Vec<String>,
}
impl Doc {
fn seed(n: i64) -> Self {
Doc {
n,
path: Vec::new(),
}
}
fn stamped(mut self, label: &str) -> Self {
self.path.push(label.to_string());
self
}
}
fn parity_graph() -> tinyagents::CompiledGraph<Doc, Doc> {
GraphBuilder::<Doc, Doc>::overwrite()
.add_node("classify", |s: Doc, _c: NodeContext| async move {
Ok(NodeResult::Update(s.stamped("classify")))
})
.add_node("even", |s: Doc, _c: NodeContext| async move {
Ok(NodeResult::Update(s.stamped("even")))
})
.add_node("odd", |s: Doc, _c: NodeContext| async move {
Ok(NodeResult::Update(s.stamped("odd")))
})
.set_entry("classify")
.add_conditional_edges(
"classify",
|d: &Doc| {
if d.n == 0 {
"zero"
} else if d.n % 2 == 0 {
"even"
} else {
"odd"
}
},
[("even", "even"), ("odd", "odd"), ("zero", END)],
)
.set_finish("even")
.set_finish("odd")
.compile()
.expect("parity graph compiles")
}
#[tokio::test]
async fn conditional_router_selects_even_branch_by_committed_state() {
let run = run_recorded(&parity_graph(), None, Doc::seed(4))
.await
.expect("run succeeds");
assert_graph(&run)
.visited(["classify", "even"])
.routed("classify", "even")
.completed();
assert_eq!(run.execution.state.path, vec!["classify", "even"]);
}
#[tokio::test]
async fn conditional_router_selects_odd_branch_by_committed_state() {
let run = run_recorded(&parity_graph(), None, Doc::seed(7))
.await
.expect("run succeeds");
assert_graph(&run)
.visited(["classify", "odd"])
.routed("classify", "odd")
.completed();
assert_eq!(run.execution.state.path, vec!["classify", "odd"]);
}
#[tokio::test]
async fn conditional_router_can_route_directly_to_end() {
let run = run_recorded(&parity_graph(), None, Doc::seed(0))
.await
.expect("run succeeds");
assert_graph(&run).visited(["classify"]).completed();
assert_eq!(run.execution.state.path, vec!["classify"]);
}
#[tokio::test]
async fn conditional_router_is_re_evaluated_each_loop_iteration() {
let graph = GraphBuilder::<i64, i64>::overwrite()
.with_recursion_limit(16)
.add_node("inc", |s: i64, _c: NodeContext| async move {
Ok(NodeResult::Update(s + 1))
})
.set_entry("inc")
.add_conditional_edges(
"inc",
|n: &i64| if *n >= 3 { "done" } else { "again" },
[("again", "inc"), ("done", END)],
)
.compile()
.expect("loop graph compiles");
let run = run_recorded(&graph, None, 0).await.expect("run succeeds");
assert_graph(&run)
.visited(["inc", "inc", "inc"])
.completed();
assert_eq!(run.execution.state, 3);
assert_eq!(run.execution.steps, 3);
}
#[tokio::test]
async fn command_goto_routes_dynamically_to_the_chosen_target() {
fn threshold_graph() -> tinyagents::CompiledGraph<Doc, Doc> {
GraphBuilder::<Doc, Doc>::overwrite()
.add_node("route", |s: Doc, _c: NodeContext| async move {
let target = if s.n >= 10 { "high" } else { "low" };
Ok(NodeResult::Command(
Command::goto([target]).with_update(s.stamped("route")),
))
})
.add_node("low", |s: Doc, _c: NodeContext| async move {
Ok(NodeResult::Update(s.stamped("low")))
})
.add_node("high", |s: Doc, _c: NodeContext| async move {
Ok(NodeResult::Update(s.stamped("high")))
})
.set_entry("route")
.with_command_destinations("route", ["low", "high"])
.set_finish("low")
.set_finish("high")
.compile()
.expect("threshold graph compiles")
}
let graph = threshold_graph();
let low_run = run_recorded(&graph, None, Doc::seed(3))
.await
.expect("low run succeeds");
assert_graph(&low_run)
.visited(["route", "low"])
.routed("route", "low")
.completed();
let high_run = run_recorded(&graph, None, Doc::seed(42))
.await
.expect("high run succeeds");
assert_graph(&high_run)
.visited(["route", "high"])
.routed("route", "high")
.completed();
assert_eq!(high_run.execution.state.path, vec!["route", "high"]);
}