agent_base/types/
finish_reason.rs1#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum FinishReason {
8 Stop,
10 ToolUse,
15 Truncated {
21 reason: Option<String>,
23 },
24 Other(String),
26}
27
28impl FinishReason {
29 pub fn from_openai(s: Option<&str>) -> Self {
31 match s {
32 Some("stop") => Self::Stop,
33 Some("length") => Self::Truncated {
34 reason: Some("length".into()),
35 },
36 Some(other) => Self::Other(other.to_string()),
38 None => Self::Stop, }
40 }
41
42 pub fn from_anthropic(s: Option<&str>) -> Self {
44 match s {
45 Some("end_turn") => Self::Stop,
46 Some("tool_use") => Self::ToolUse,
47 Some("max_tokens") => Self::Truncated {
48 reason: Some("max_tokens".into()),
49 },
50 Some(other) => Self::Other(other.to_string()),
51 None => Self::Stop,
52 }
53 }
54
55 pub fn from_responses(reason: Option<&str>) -> Self {
57 match reason {
58 None => Self::Stop,
59 Some("incomplete") => Self::Truncated { reason: None },
60 Some(other) => Self::Other(other.to_string()),
61 }
62 }
63
64 pub fn from_raw(s: Option<&str>) -> Self {
71 match s {
72 Some("stop") | Some("end_turn") => Self::Stop,
73 Some("tool_use") => Self::ToolUse,
74 Some("length") => Self::Truncated {
75 reason: Some("length".into()),
76 },
77 Some("max_tokens") => Self::Truncated {
78 reason: Some("max_tokens".into()),
79 },
80 Some("incomplete:max_output_tokens") => Self::Truncated {
83 reason: Some("incomplete:max_output_tokens".into()),
84 },
85 Some(s) if s.starts_with("incomplete:") => Self::Other(s.to_string()),
86 Some(other) => Self::Other(other.to_string()),
87 None => Self::Stop,
88 }
89 }
90
91 pub fn is_truncated(&self) -> bool {
93 matches!(self, Self::Truncated { .. })
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn openai_stop() {
103 assert_eq!(FinishReason::from_openai(Some("stop")), FinishReason::Stop);
104 }
105
106 #[test]
107 fn openai_length() {
108 assert_eq!(
109 FinishReason::from_openai(Some("length")),
110 FinishReason::Truncated {
111 reason: Some("length".into())
112 }
113 );
114 }
115
116 #[test]
117 fn openai_none() {
118 assert_eq!(FinishReason::from_openai(None), FinishReason::Stop);
119 }
120
121 #[test]
122 fn openai_other() {
123 assert_eq!(
124 FinishReason::from_openai(Some("content_filter")),
125 FinishReason::Other("content_filter".into())
126 );
127 }
128
129 #[test]
130 fn anthropic_end_turn() {
131 assert_eq!(
132 FinishReason::from_anthropic(Some("end_turn")),
133 FinishReason::Stop
134 );
135 }
136
137 #[test]
138 fn anthropic_tool_use() {
139 assert_eq!(
140 FinishReason::from_anthropic(Some("tool_use")),
141 FinishReason::ToolUse
142 );
143 }
144
145 #[test]
146 fn anthropic_max_tokens() {
147 assert_eq!(
148 FinishReason::from_anthropic(Some("max_tokens")),
149 FinishReason::Truncated {
150 reason: Some("max_tokens".into())
151 }
152 );
153 }
154
155 #[test]
156 fn responses_incomplete() {
157 assert_eq!(
158 FinishReason::from_responses(Some("incomplete")),
159 FinishReason::Truncated { reason: None }
160 );
161 }
162
163 #[test]
164 fn responses_none() {
165 assert_eq!(FinishReason::from_responses(None), FinishReason::Stop);
166 }
167
168 #[test]
169 fn is_truncated_true() {
170 assert!(FinishReason::Truncated { reason: None }.is_truncated());
171 assert!(
172 FinishReason::Truncated {
173 reason: Some("length".into())
174 }
175 .is_truncated()
176 );
177 }
178
179 #[test]
180 fn is_truncated_false() {
181 assert!(!FinishReason::Stop.is_truncated());
182 assert!(!FinishReason::ToolUse.is_truncated());
183 assert!(!FinishReason::Other("x".into()).is_truncated());
184 }
185
186 #[test]
187 fn from_raw_incomplete_prefix() {
188 assert_eq!(
189 FinishReason::from_raw(Some("incomplete:max_output_tokens")),
190 FinishReason::Truncated {
191 reason: Some("incomplete:max_output_tokens".into())
192 }
193 );
194 assert_eq!(
196 FinishReason::from_raw(Some("incomplete:content_filter")),
197 FinishReason::Other("incomplete:content_filter".into())
198 );
199 }
200}