Skip to main content

autoagents_core/agent/
output.rs

1use serde::{Serialize, de::DeserializeOwned};
2
3/// Trait for agent output types that can generate structured output schemas
4pub trait AgentOutputT: Serialize + DeserializeOwned + Send + Sync {
5    /// Get the JSON schema string for this output type
6    fn output_schema() -> &'static str;
7
8    /// Get the structured output format as a JSON value
9    fn structured_output_format() -> serde_json::Value;
10}
11
12/// Implementation of AgentOutputT for String type (when no output is specified)
13impl AgentOutputT for String {
14    fn output_schema() -> &'static str {
15        "{}"
16    }
17
18    fn structured_output_format() -> serde_json::Value {
19        serde_json::Value::Null
20    }
21}
22
23#[cfg(test)]
24mod tests {
25    use super::*;
26    use serde::{Deserialize, Serialize};
27    use serde_json::json;
28
29    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
30    struct TestOutput {
31        message: String,
32        count: u32,
33    }
34
35    impl AgentOutputT for TestOutput {
36        fn output_schema() -> &'static str {
37            r#"{"type":"object","properties":{"message":{"type":"string"},"count":{"type":"integer"}},"required":["message","count"]}"#
38        }
39
40        fn structured_output_format() -> serde_json::Value {
41            json!({
42                "type": "object",
43                "properties": {
44                    "message": {"type": "string"},
45                    "count": {"type": "integer"}
46                },
47                "required": ["message", "count"]
48            })
49        }
50    }
51
52    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53    struct SimpleOutput {
54        value: String,
55    }
56
57    impl AgentOutputT for SimpleOutput {
58        fn output_schema() -> &'static str {
59            r#"{"type":"object","properties":{"value":{"type":"string"}},"required":["value"]}"#
60        }
61
62        fn structured_output_format() -> serde_json::Value {
63            json!({
64                "type": "object",
65                "properties": {
66                    "value": {"type": "string"}
67                },
68                "required": ["value"]
69            })
70        }
71    }
72
73    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
74    struct ComplexOutput {
75        name: String,
76        age: u32,
77        active: bool,
78        tags: Vec<String>,
79    }
80
81    impl AgentOutputT for ComplexOutput {
82        fn output_schema() -> &'static str {
83            r#"{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"active":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}}},"required":["name","age","active","tags"]}"#
84        }
85
86        fn structured_output_format() -> serde_json::Value {
87            json!({
88                "type": "object",
89                "properties": {
90                    "name": {"type": "string"},
91                    "age": {"type": "integer"},
92                    "active": {"type": "boolean"},
93                    "tags": {
94                        "type": "array",
95                        "items": {"type": "string"}
96                    }
97                },
98                "required": ["name", "age", "active", "tags"]
99            })
100        }
101    }
102
103    #[test]
104    fn test_agent_output_trait_basic() {
105        // Test that the trait is defined correctly
106        fn assert_agent_output<T: AgentOutputT>() {}
107        assert_agent_output::<String>();
108        assert_agent_output::<TestOutput>();
109    }
110
111    #[test]
112    fn test_string_output_schema() {
113        let schema = String::output_schema();
114        assert_eq!(schema, "{}");
115    }
116
117    #[test]
118    fn test_string_structured_output_format() {
119        let format = String::structured_output_format();
120        assert_eq!(format, serde_json::Value::Null);
121    }
122
123    #[test]
124    fn test_test_output_schema() {
125        let schema = TestOutput::output_schema();
126        assert!(schema.contains("object"));
127        assert!(schema.contains("message"));
128        assert!(schema.contains("count"));
129        assert!(schema.contains("string"));
130        assert!(schema.contains("integer"));
131        assert!(schema.contains("required"));
132    }
133
134    #[test]
135    fn test_test_output_structured_format() {
136        let format = TestOutput::structured_output_format();
137        assert_eq!(format["type"], "object");
138        assert_eq!(format["properties"]["message"]["type"], "string");
139        assert_eq!(format["properties"]["count"]["type"], "integer");
140        assert_eq!(format["required"][0], "message");
141        assert_eq!(format["required"][1], "count");
142    }
143
144    #[test]
145    fn test_test_output_serialization() {
146        let output = TestOutput {
147            message: "Hello World".to_string(),
148            count: 42,
149        };
150        let serialized = serde_json::to_string(&output).unwrap();
151        assert!(serialized.contains("Hello World"));
152        assert!(serialized.contains("42"));
153    }
154
155    #[test]
156    fn test_test_output_deserialization() {
157        let json = r#"{"message":"Test Message","count":123}"#;
158        let output: TestOutput = serde_json::from_str(json).unwrap();
159        assert_eq!(output.message, "Test Message");
160        assert_eq!(output.count, 123);
161    }
162
163    #[test]
164    fn test_test_output_clone() {
165        let output = TestOutput {
166            message: "Clone Test".to_string(),
167            count: 999,
168        };
169        let cloned = output.clone();
170        assert_eq!(output, cloned);
171    }
172
173    #[test]
174    fn test_test_output_debug() {
175        let output = TestOutput {
176            message: "Debug Test".to_string(),
177            count: 456,
178        };
179        let debug_str = format!("{output:?}");
180        assert!(debug_str.contains("TestOutput"));
181        assert!(debug_str.contains("Debug Test"));
182        assert!(debug_str.contains("456"));
183    }
184
185    #[test]
186    fn test_simple_output_schema() {
187        let schema = SimpleOutput::output_schema();
188        assert!(schema.contains("object"));
189        assert!(schema.contains("value"));
190        assert!(schema.contains("string"));
191    }
192
193    #[test]
194    fn test_simple_output_structured_format() {
195        let format = SimpleOutput::structured_output_format();
196        assert_eq!(format["type"], "object");
197        assert_eq!(format["properties"]["value"]["type"], "string");
198        assert_eq!(format["required"][0], "value");
199    }
200
201    #[test]
202    fn test_simple_output_serialization() {
203        let output = SimpleOutput {
204            value: "simple value".to_string(),
205        };
206        let serialized = serde_json::to_string(&output).unwrap();
207        assert!(serialized.contains("simple value"));
208    }
209
210    #[test]
211    fn test_simple_output_deserialization() {
212        let json = r#"{"value":"deserialized value"}"#;
213        let output: SimpleOutput = serde_json::from_str(json).unwrap();
214        assert_eq!(output.value, "deserialized value");
215    }
216
217    #[test]
218    fn test_complex_output_schema() {
219        let schema = ComplexOutput::output_schema();
220        assert!(schema.contains("object"));
221        assert!(schema.contains("name"));
222        assert!(schema.contains("age"));
223        assert!(schema.contains("active"));
224        assert!(schema.contains("tags"));
225        assert!(schema.contains("array"));
226        assert!(schema.contains("boolean"));
227    }
228
229    #[test]
230    fn test_complex_output_structured_format() {
231        let format = ComplexOutput::structured_output_format();
232        assert_eq!(format["type"], "object");
233        assert_eq!(format["properties"]["name"]["type"], "string");
234        assert_eq!(format["properties"]["age"]["type"], "integer");
235        assert_eq!(format["properties"]["active"]["type"], "boolean");
236        assert_eq!(format["properties"]["tags"]["type"], "array");
237        assert_eq!(format["properties"]["tags"]["items"]["type"], "string");
238    }
239
240    #[test]
241    fn test_complex_output_serialization() {
242        let output = ComplexOutput {
243            name: "Test User".to_string(),
244            age: 25,
245            active: true,
246            tags: vec!["tag1".to_string(), "tag2".to_string()],
247        };
248        let serialized = serde_json::to_string(&output).unwrap();
249        assert!(serialized.contains("Test User"));
250        assert!(serialized.contains("25"));
251        assert!(serialized.contains("true"));
252        assert!(serialized.contains("tag1"));
253        assert!(serialized.contains("tag2"));
254    }
255
256    #[test]
257    fn test_complex_output_deserialization() {
258        let json = r#"{"name":"Complex User","age":30,"active":false,"tags":["work","personal"]}"#;
259        let output: ComplexOutput = serde_json::from_str(json).unwrap();
260        assert_eq!(output.name, "Complex User");
261        assert_eq!(output.age, 30);
262        assert!(!output.active);
263        assert_eq!(output.tags, vec!["work", "personal"]);
264    }
265
266    #[test]
267    fn test_complex_output_with_empty_tags() {
268        let output = ComplexOutput {
269            name: "Empty Tags".to_string(),
270            age: 35,
271            active: true,
272            tags: vec![],
273        };
274        let serialized = serde_json::to_string(&output).unwrap();
275        let deserialized: ComplexOutput = serde_json::from_str(&serialized).unwrap();
276        assert_eq!(deserialized.tags.len(), 0);
277    }
278
279    #[test]
280    fn test_output_with_special_characters() {
281        let output = TestOutput {
282            message: "Special chars: !@#$%^&*()".to_string(),
283            count: 0,
284        };
285        let serialized = serde_json::to_string(&output).unwrap();
286        let deserialized: TestOutput = serde_json::from_str(&serialized).unwrap();
287        assert_eq!(deserialized.message, "Special chars: !@#$%^&*()");
288    }
289
290    #[test]
291    fn test_output_with_unicode() {
292        let output = TestOutput {
293            message: "Unicode: 你好世界 🌍".to_string(),
294            count: 42,
295        };
296        let serialized = serde_json::to_string(&output).unwrap();
297        let deserialized: TestOutput = serde_json::from_str(&serialized).unwrap();
298        assert_eq!(deserialized.message, "Unicode: 你好世界 🌍");
299    }
300
301    #[test]
302    fn test_output_with_newlines() {
303        let output = TestOutput {
304            message: "Multi\nLine\nMessage".to_string(),
305            count: 3,
306        };
307        let serialized = serde_json::to_string(&output).unwrap();
308        let deserialized: TestOutput = serde_json::from_str(&serialized).unwrap();
309        assert_eq!(deserialized.message, "Multi\nLine\nMessage");
310    }
311
312    #[test]
313    fn test_output_with_large_count() {
314        let output = TestOutput {
315            message: "Large count".to_string(),
316            count: u32::MAX,
317        };
318        let serialized = serde_json::to_string(&output).unwrap();
319        let deserialized: TestOutput = serde_json::from_str(&serialized).unwrap();
320        assert_eq!(deserialized.count, u32::MAX);
321    }
322
323    #[test]
324    fn test_output_with_zero_count() {
325        let output = TestOutput {
326            message: "Zero count".to_string(),
327            count: 0,
328        };
329        let serialized = serde_json::to_string(&output).unwrap();
330        let deserialized: TestOutput = serde_json::from_str(&serialized).unwrap();
331        assert_eq!(deserialized.count, 0);
332    }
333
334    #[test]
335    fn test_output_with_empty_string() {
336        let output = TestOutput {
337            message: "".to_string(),
338            count: 100,
339        };
340        let serialized = serde_json::to_string(&output).unwrap();
341        let deserialized: TestOutput = serde_json::from_str(&serialized).unwrap();
342        assert_eq!(deserialized.message, "");
343        assert_eq!(deserialized.count, 100);
344    }
345
346    #[test]
347    fn test_output_equality() {
348        let output1 = TestOutput {
349            message: "Equal test".to_string(),
350            count: 50,
351        };
352        let output2 = TestOutput {
353            message: "Equal test".to_string(),
354            count: 50,
355        };
356        let output3 = TestOutput {
357            message: "Different test".to_string(),
358            count: 50,
359        };
360        assert_eq!(output1, output2);
361        assert_ne!(output1, output3);
362    }
363
364    #[test]
365    fn test_multiple_output_types() {
366        let _test_output = TestOutput {
367            message: "test".to_string(),
368            count: 1,
369        };
370        let _simple_output = SimpleOutput {
371            value: "simple".to_string(),
372        };
373        let _complex_output = ComplexOutput {
374            name: "complex".to_string(),
375            age: 25,
376            active: true,
377            tags: vec!["tag".to_string()],
378        };
379
380        // Test that all implement the trait
381        assert_ne!(TestOutput::output_schema(), SimpleOutput::output_schema());
382        assert_ne!(
383            SimpleOutput::output_schema(),
384            ComplexOutput::output_schema()
385        );
386        assert_ne!(TestOutput::output_schema(), ComplexOutput::output_schema());
387    }
388
389    #[test]
390    fn test_schema_json_validity() {
391        // Test that schemas are valid JSON
392        let schemas = vec![
393            String::output_schema(),
394            TestOutput::output_schema(),
395            SimpleOutput::output_schema(),
396            ComplexOutput::output_schema(),
397        ];
398
399        for schema in schemas {
400            if schema != "{}" {
401                let _: serde_json::Value = serde_json::from_str(schema).unwrap();
402            }
403        }
404    }
405
406    #[test]
407    fn test_structured_format_json_validity() {
408        // Test that structured formats are valid JSON
409        let formats = vec![
410            String::structured_output_format(),
411            TestOutput::structured_output_format(),
412            SimpleOutput::structured_output_format(),
413            ComplexOutput::structured_output_format(),
414        ];
415
416        for format in formats {
417            if format != serde_json::Value::Null {
418                assert!(format.is_object() || format.is_null());
419            }
420        }
421    }
422
423    #[test]
424    fn test_send_sync_traits() {
425        // Test that our types implement Send and Sync
426        fn assert_send_sync<T: Send + Sync>() {}
427        assert_send_sync::<TestOutput>();
428        assert_send_sync::<SimpleOutput>();
429        assert_send_sync::<ComplexOutput>();
430    }
431
432    #[test]
433    fn test_round_trip_serialization() {
434        let outputs = vec![
435            TestOutput {
436                message: "Round trip test".to_string(),
437                count: 789,
438            },
439            TestOutput {
440                message: "Another test".to_string(),
441                count: 0,
442            },
443        ];
444
445        for output in outputs {
446            let serialized = serde_json::to_string(&output).unwrap();
447            let deserialized: TestOutput = serde_json::from_str(&serialized).unwrap();
448            assert_eq!(output, deserialized);
449        }
450    }
451}