ferrin_core/generate_text/
stop_condition.rs1use std::sync::Arc;
4
5use ferrin_spec::BoxFuture;
6use ferrin_spec::ToolName;
7
8use super::StepResult;
9
10pub trait StopCondition: Send + Sync {
15 fn should_stop<'a>(&'a self, steps: &'a [StepResult]) -> BoxFuture<'a, bool>;
17}
18
19impl<F> StopCondition for F
20where
21 F: Fn(&[StepResult]) -> bool + Send + Sync,
22{
23 fn should_stop<'a>(&'a self, steps: &'a [StepResult]) -> BoxFuture<'a, bool> {
24 let result = self(steps);
25 Box::pin(async move { result })
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct StepCount(u32);
32
33#[must_use]
35pub fn step_count(n: u32) -> StepCount {
36 StepCount(n)
37}
38
39impl StopCondition for StepCount {
40 fn should_stop<'a>(&'a self, steps: &'a [StepResult]) -> BoxFuture<'a, bool> {
41 let stop = u32::try_from(steps.len()).unwrap_or(u32::MAX) >= self.0;
42 Box::pin(async move { stop })
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct HasToolCall(Vec<ToolName>);
49
50#[must_use]
52pub fn has_tool_call(tool_name: impl Into<ToolName>) -> HasToolCall {
53 HasToolCall(vec![tool_name.into()])
54}
55
56#[must_use]
58pub fn has_any_tool_call(tool_names: impl IntoIterator<Item = impl Into<ToolName>>) -> HasToolCall {
59 HasToolCall(tool_names.into_iter().map(Into::into).collect())
60}
61
62impl StopCondition for HasToolCall {
63 fn should_stop<'a>(&'a self, steps: &'a [StepResult]) -> BoxFuture<'a, bool> {
64 let stop = steps.last().is_some_and(|step| {
65 step.tool_calls()
66 .any(|call| self.0.contains(&call.tool_name))
67 });
68 Box::pin(async move { stop })
69 }
70}
71
72#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
74pub struct Never;
75
76#[must_use]
78pub fn never() -> Never {
79 Never
80}
81
82impl StopCondition for Never {
83 fn should_stop<'a>(&'a self, _steps: &'a [StepResult]) -> BoxFuture<'a, bool> {
84 Box::pin(async { false })
85 }
86}
87
88pub(crate) async fn is_stop_condition_met(
90 conditions: &[Arc<dyn StopCondition>],
91 steps: &[StepResult],
92) -> bool {
93 for condition in conditions {
94 if condition.should_stop(steps).await {
95 return true;
96 }
97 }
98 false
99}