#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ToolBudgetDecision {
Continue,
ContinueAfterBudgetMessage,
Exhausted {
segment_iterations: usize,
total_batches: usize,
continuations_used: usize,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ToolIterationBudget {
segment_limit: usize,
continuation_limit: usize,
segment_iterations: usize,
total_batches: usize,
continuations_used: usize,
}
impl ToolIterationBudget {
pub fn new(segment_limit: usize, continuation_limit: usize) -> Self {
Self { segment_limit, continuation_limit, segment_iterations: 0, total_batches: 0, continuations_used: 0 }
}
pub fn record_tool_batch(&mut self) {
self.segment_iterations = self.segment_iterations.saturating_add(1);
self.total_batches = self.total_batches.saturating_add(1);
}
pub fn before_provider_request(&mut self) -> ToolBudgetDecision {
if self.segment_iterations < self.segment_limit {
return ToolBudgetDecision::Continue;
}
if self.continuations_used < self.continuation_limit {
self.segment_iterations = 0;
self.continuations_used += 1;
return ToolBudgetDecision::ContinueAfterBudgetMessage;
}
ToolBudgetDecision::Exhausted {
segment_iterations: self.segment_iterations,
total_batches: self.total_batches,
continuations_used: self.continuations_used,
}
}
pub const fn total_batches(&self) -> usize {
self.total_batches
}
pub const fn continuations_used(&self) -> usize {
self.continuations_used
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn budget_allows_bounded_continuations_before_exhausting() {
let mut budget = ToolIterationBudget::new(2, 1);
assert_eq!(budget.before_provider_request(), ToolBudgetDecision::Continue);
budget.record_tool_batch();
budget.record_tool_batch();
assert_eq!(
budget.before_provider_request(),
ToolBudgetDecision::ContinueAfterBudgetMessage
);
budget.record_tool_batch();
budget.record_tool_batch();
assert_eq!(
budget.before_provider_request(),
ToolBudgetDecision::Exhausted { segment_iterations: 2, total_batches: 4, continuations_used: 1 }
);
}
}