use std::sync::Arc;
use synwire_core::error::SynwireError;
use synwire_core::language_models::BaseChatModel;
use synwire_core::messages::Message;
use synwire_core::output_parsers::OutputParser;
pub struct RetryOutputParser<P: OutputParser> {
inner: Arc<P>,
model: Arc<dyn BaseChatModel>,
max_retries: usize,
}
impl<P: OutputParser> RetryOutputParser<P> {
pub fn new(inner: Arc<P>, model: Arc<dyn BaseChatModel>, max_retries: usize) -> Self {
Self {
inner,
model,
max_retries,
}
}
pub async fn parse_with_retry(&self, text: &str) -> Result<P::Output, SynwireError> {
let first_err = match self.inner.parse(text) {
Ok(v) => return Ok(v),
Err(e) => e,
};
let mut last_err = first_err;
let mut current_text = text.to_owned();
for _ in 0..self.max_retries {
let format_instructions = self.inner.get_format_instructions();
let retry_prompt = format!(
"The following output failed to parse:\n\n{current_text}\n\n\
Error: {last_err}\n\n\
Please produce corrected output.\n{format_instructions}"
);
let messages = vec![Message::human(retry_prompt)];
let result = self.model.invoke(&messages, None).await?;
current_text = result.message.content().as_text();
match self.inner.parse(¤t_text) {
Ok(v) => return Ok(v),
Err(e) => last_err = e,
}
}
Err(last_err)
}
}
impl<P: OutputParser> OutputParser for RetryOutputParser<P> {
type Output = P::Output;
fn parse(&self, text: &str) -> Result<P::Output, SynwireError> {
self.inner.parse(text)
}
fn get_format_instructions(&self) -> String {
self.inner.get_format_instructions()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use synwire_core::language_models::FakeChatModel;
use synwire_core::output_parsers::JsonOutputParser;
#[tokio::test]
async fn retry_succeeds_on_corrected_output() {
let model = Arc::new(FakeChatModel::new(vec![r#"{"name": "Alice"}"#.into()]));
let parser = Arc::new(JsonOutputParser);
let retry = RetryOutputParser::new(parser, model, 3);
let result = retry.parse_with_retry("not json").await.unwrap();
assert_eq!(result["name"], "Alice");
}
#[tokio::test]
async fn retry_exhausted_returns_error() {
let model = Arc::new(FakeChatModel::new(vec!["still not json".into()]));
let parser = Arc::new(JsonOutputParser);
let retry = RetryOutputParser::new(parser, model, 2);
let result = retry.parse_with_retry("bad input").await;
assert!(result.is_err());
}
#[test]
fn direct_parse_delegates_to_inner() {
let model = Arc::new(FakeChatModel::new(vec![]));
let parser = Arc::new(JsonOutputParser);
let retry = RetryOutputParser::new(parser, model, 1);
let result = retry.parse(r#"{"key": "value"}"#).unwrap();
assert_eq!(result["key"], "value");
}
#[tokio::test]
async fn no_retry_needed_on_success() {
let model = Arc::new(FakeChatModel::new(vec![]));
let parser = Arc::new(JsonOutputParser);
let retry = RetryOutputParser::new(parser, model, 3);
let result = retry.parse_with_retry(r#"{"ok": true}"#).await.unwrap();
assert_eq!(result["ok"], true);
}
}