synwire_core/output_parsers/
structured.rs1use std::marker::PhantomData;
4
5use serde::de::DeserializeOwned;
6
7use crate::error::{ParseError, SynwireError};
8use crate::output_parsers::OutputParser;
9
10pub struct StructuredOutputParser<T: DeserializeOwned> {
33 _marker: PhantomData<T>,
34}
35
36impl<T: DeserializeOwned> StructuredOutputParser<T> {
37 pub const fn new() -> Self {
39 Self {
40 _marker: PhantomData,
41 }
42 }
43}
44
45impl<T: DeserializeOwned + Send + Sync> StructuredOutputParser<T> {
46 pub fn parse_with_retry_context(&self, text: &str) -> Result<T, (SynwireError, String)> {
57 match self.parse(text) {
58 Ok(v) => Ok(v),
59 Err(e) => {
60 let context = format!(
61 "Previous attempt failed with error: {e}\nPlease fix the output and try again."
62 );
63 Err((e, context))
64 }
65 }
66 }
67}
68
69impl<T: DeserializeOwned> Default for StructuredOutputParser<T> {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl<T: DeserializeOwned + Send + Sync> OutputParser for StructuredOutputParser<T> {
76 type Output = T;
77
78 fn parse(&self, text: &str) -> Result<T, SynwireError> {
79 serde_json::from_str(text).map_err(|e| {
80 SynwireError::from(ParseError::ParseFailed {
81 message: format!("Failed to parse structured output: {e}"),
82 })
83 })
84 }
85
86 fn get_format_instructions(&self) -> String {
87 "Respond with valid JSON matching the expected schema.".to_string()
88 }
89}
90
91#[cfg(test)]
92#[allow(clippy::unwrap_used)]
93mod tests {
94 use serde::Deserialize;
95
96 use super::*;
97
98 #[derive(Debug, Deserialize, PartialEq)]
99 struct TestPerson {
100 name: String,
101 age: u32,
102 }
103
104 #[test]
105 fn test_structured_parser() {
106 let parser = StructuredOutputParser::<TestPerson>::new();
107 let result = parser.parse(r#"{"name": "Alice", "age": 30}"#).unwrap();
108 assert_eq!(
109 result,
110 TestPerson {
111 name: "Alice".to_string(),
112 age: 30,
113 }
114 );
115 }
116
117 #[test]
118 fn test_structured_parser_invalid() {
119 let parser = StructuredOutputParser::<TestPerson>::new();
120 let result = parser.parse(r#"{"name": "Alice"}"#);
121 assert!(result.is_err());
122 }
123
124 #[test]
125 fn test_structured_parser_format_instructions() {
126 let parser = StructuredOutputParser::<TestPerson>::new();
127 assert_eq!(
128 parser.get_format_instructions(),
129 "Respond with valid JSON matching the expected schema."
130 );
131 }
132
133 #[test]
134 fn test_parse_with_retry_context_success() {
135 let parser = StructuredOutputParser::<TestPerson>::new();
136 let result = parser
137 .parse_with_retry_context(r#"{"name": "Alice", "age": 30}"#)
138 .unwrap();
139 assert_eq!(result.name, "Alice");
140 assert_eq!(result.age, 30);
141 }
142
143 #[test]
144 fn test_parse_with_retry_context_failure() {
145 let parser = StructuredOutputParser::<TestPerson>::new();
146 let result = parser.parse_with_retry_context(r#"{"name": "Alice"}"#);
147 assert!(result.is_err());
148 let (err, context) = result.unwrap_err();
149 assert!(err.to_string().contains("Failed to parse"));
150 assert!(context.contains("Previous attempt failed"));
151 assert!(context.contains("Please fix the output"));
152 }
153
154 #[test]
155 fn test_structured_parser_default() {
156 let parser = StructuredOutputParser::<TestPerson>::default();
157 let result = parser.parse(r#"{"name": "Bob", "age": 25}"#).unwrap();
158 assert_eq!(result.name, "Bob");
159 }
160}