1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct StepResult {
9 pub step_id: String,
11
12 pub success: bool,
14
15 pub output: StepOutput,
17
18 pub confidence: f64,
20
21 pub duration_ms: u64,
23
24 pub tokens: TokenUsage,
26
27 pub error: Option<String>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33#[serde(tag = "type", rename_all = "snake_case")]
34#[derive(Default)]
35pub enum StepOutput {
36 Text {
38 content: String,
40 },
41
42 List {
44 items: Vec<ListItem>,
46 },
47
48 Structured {
50 data: HashMap<String, serde_json::Value>,
52 },
53
54 Score {
56 value: f64,
58 },
59
60 Boolean {
62 value: bool,
64 reason: Option<String>,
66 },
67
68 #[default]
70 Empty,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct ListItem {
76 pub content: String,
78
79 #[serde(default)]
81 pub confidence: Option<f64>,
82
83 #[serde(default)]
85 pub metadata: HashMap<String, serde_json::Value>,
86}
87
88impl ListItem {
89 pub fn new(content: impl Into<String>) -> Self {
91 Self {
92 content: content.into(),
93 confidence: None,
94 metadata: HashMap::new(),
95 }
96 }
97
98 pub fn with_confidence(content: impl Into<String>, confidence: f64) -> Self {
100 Self {
101 content: content.into(),
102 confidence: Some(confidence),
103 metadata: HashMap::new(),
104 }
105 }
106}
107
108#[derive(Debug, Clone, Default, Serialize, Deserialize)]
110pub struct TokenUsage {
111 pub input_tokens: u32,
113
114 pub output_tokens: u32,
116
117 pub total_tokens: u32,
119
120 pub cost_usd: f64,
122}
123
124impl TokenUsage {
125 pub fn new(input: u32, output: u32, cost: f64) -> Self {
127 Self {
128 input_tokens: input,
129 output_tokens: output,
130 total_tokens: input + output,
131 cost_usd: cost,
132 }
133 }
134
135 pub fn add(&mut self, other: &TokenUsage) {
137 self.input_tokens += other.input_tokens;
138 self.output_tokens += other.output_tokens;
139 self.total_tokens += other.total_tokens;
140 self.cost_usd += other.cost_usd;
141 }
142}
143
144#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
146pub enum OutputFormat {
147 #[default]
149 Raw,
150 Json,
152 List,
154 KeyValue,
156 Numeric,
158}
159
160impl StepResult {
161 pub fn success(step_id: impl Into<String>, output: StepOutput, confidence: f64) -> Self {
163 Self {
164 step_id: step_id.into(),
165 success: true,
166 output,
167 confidence,
168 duration_ms: 0,
169 tokens: TokenUsage::default(),
170 error: None,
171 }
172 }
173
174 pub fn failure(step_id: impl Into<String>, error: impl Into<String>) -> Self {
176 Self {
177 step_id: step_id.into(),
178 success: false,
179 output: StepOutput::Empty,
180 confidence: 0.0,
181 duration_ms: 0,
182 tokens: TokenUsage::default(),
183 error: Some(error.into()),
184 }
185 }
186
187 pub fn with_duration(mut self, ms: u64) -> Self {
189 self.duration_ms = ms;
190 self
191 }
192
193 pub fn with_tokens(mut self, tokens: TokenUsage) -> Self {
195 self.tokens = tokens;
196 self
197 }
198
199 pub fn meets_threshold(&self, threshold: f64) -> bool {
201 self.success && self.confidence >= threshold
202 }
203
204 pub fn as_text(&self) -> Option<&str> {
206 match &self.output {
207 StepOutput::Text { content } => Some(content),
208 _ => None,
209 }
210 }
211
212 pub fn as_list(&self) -> Option<&[ListItem]> {
214 match &self.output {
215 StepOutput::List { items } => Some(items),
216 _ => None,
217 }
218 }
219
220 pub fn as_score(&self) -> Option<f64> {
222 match &self.output {
223 StepOutput::Score { value } => Some(*value),
224 _ => None,
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn test_step_result_success() {
235 let result = StepResult::success(
236 "test_step",
237 StepOutput::Text {
238 content: "Hello world".to_string(),
239 },
240 0.85,
241 );
242
243 assert!(result.success);
244 assert_eq!(result.confidence, 0.85);
245 assert_eq!(result.as_text(), Some("Hello world"));
246 }
247
248 #[test]
249 fn test_step_result_failure() {
250 let result = StepResult::failure("test_step", "Something went wrong");
251
252 assert!(!result.success);
253 assert_eq!(result.confidence, 0.0);
254 assert_eq!(result.error, Some("Something went wrong".to_string()));
255 }
256
257 #[test]
258 fn test_token_usage_add() {
259 let mut usage1 = TokenUsage::new(100, 50, 0.001);
260 let usage2 = TokenUsage::new(200, 100, 0.002);
261
262 usage1.add(&usage2);
263
264 assert_eq!(usage1.input_tokens, 300);
265 assert_eq!(usage1.output_tokens, 150);
266 assert_eq!(usage1.total_tokens, 450);
267 }
268
269 #[test]
270 fn test_list_item() {
271 let item = ListItem::with_confidence("Test item", 0.9);
272 assert_eq!(item.content, "Test item");
273 assert_eq!(item.confidence, Some(0.9));
274 }
275}