use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tinyagents::graph::command::NodeResult;
use tinyagents::graph::{NodeContext, NodeFuture};
use tinyagents::harness::store::{InMemoryStore, Store};
use tinyagents::{GoalProgress, GraphBuilder, ThreadGoalStatus, goal_gate_node, goal_store};
#[derive(Clone, Debug, Default, PartialEq)]
struct LoopState {
iters: usize,
}
#[tokio::test]
async fn self_driving_goal_loop_runs_until_complete() {
let store: Arc<dyn Store> = Arc::new(InMemoryStore::default());
goal_store::set(&store, "thread-goal", "process every item", None)
.await
.expect("set goal");
let counter = Arc::new(AtomicUsize::new(0));
let work_store = store.clone();
let work_node = move |_state: LoopState, _ctx: NodeContext| {
let counter = counter.clone();
let work_store = work_store.clone();
Box::pin(async move {
let n = counter.fetch_add(1, Ordering::SeqCst) + 1;
if n >= 3 {
goal_store::complete(&work_store, "thread-goal")
.await
.expect("complete goal");
}
Ok(NodeResult::Update(LoopState { iters: n }))
}) as NodeFuture<LoopState>
};
let gate = goal_gate_node::<LoopState, LoopState>(store.clone(), "work", |_s: &LoopState| {
GoalProgress {
tokens_used: 10,
elapsed_secs: 1,
made_progress: true,
}
});
let graph = GraphBuilder::<LoopState, LoopState>::overwrite()
.with_recursion_limit(64)
.add_node("work", work_node)
.add_node("gate", gate)
.set_entry("work")
.add_edge("work", "gate")
.with_command_destinations("gate", ["work", tinyagents::END])
.compile()
.expect("graph compiles");
let exec = graph
.run_with_thread("thread-goal", LoopState::default())
.await
.expect("graph runs to completion");
assert_eq!(exec.state.iters, 3, "loops until the goal is completed");
let goal = goal_store::get(&store, "thread-goal")
.await
.expect("load goal")
.expect("goal exists");
assert_eq!(goal.status, ThreadGoalStatus::Complete);
assert!(
goal.tokens_used >= 20,
"usage accrued: {}",
goal.tokens_used
);
}