use std::collections::HashMap;
use regex::Regex;
use super::{
typed::{preview, string_output_parser, strip_reasoning},
OutputParserError, TypedOutputParser,
};
#[derive(Debug, Clone)]
pub struct RegexParser {
regex: Regex,
instructions: Option<String>,
}
impl RegexParser {
pub fn new(pattern: &str) -> Result<Self, OutputParserError> {
let regex = Regex::new(pattern)?;
if regex.capture_names().flatten().next().is_none() {
return Err(OutputParserError::ParsingError(format!(
"regex {pattern:?} has no named capture groups"
)));
}
Ok(Self {
regex,
instructions: None,
})
}
pub fn with_format_instructions<S: Into<String>>(mut self, instructions: S) -> Self {
self.instructions = Some(instructions.into());
self
}
}
impl TypedOutputParser for RegexParser {
type Output = HashMap<String, String>;
fn format_instructions(&self) -> String {
self.instructions.clone().unwrap_or_else(|| {
let fields: Vec<_> = self.regex.capture_names().flatten().collect();
format!(
"Your answer must match this regular expression: {}\nIt provides these fields: {}",
self.regex.as_str(),
fields.join(", ")
)
})
}
fn parse_output(&self, text: &str) -> Result<Self::Output, OutputParserError> {
let captures = self.regex.captures(strip_reasoning(text)).ok_or_else(|| {
OutputParserError::ParsingError(format!(
"output doesn't match {:?}: {}",
self.regex.as_str(),
preview(text)
))
})?;
Ok(self
.regex
.capture_names()
.flatten()
.filter_map(|name| {
captures
.name(name)
.map(|m| (name.to_string(), m.as_str().to_string()))
})
.collect())
}
}
string_output_parser!(RegexParser, |fields| {
let fields: std::collections::BTreeMap<_, _> = fields.into_iter().collect();
serde_json::to_string(&fields).expect("a map of strings serializes")
});