use crate::errors::{Result, TrustformersError};
use std::collections::HashSet;
use super::config::GuidedGenerationConfig;
use super::grammar::Grammar;
use super::json_schema::JsonSchema;
use super::regex_constraint::RegexConstraint;
#[derive(Debug)]
pub struct ConstraintValidator {
regex: Option<RegexConstraint>,
choice_list: Option<HashSet<String>>,
json_schema: Option<JsonSchemaValidator>,
grammar: Option<GrammarValidator>,
}
impl ConstraintValidator {
pub fn new(config: &GuidedGenerationConfig) -> Result<Self> {
let regex = match &config.regex_pattern {
Some(pattern) => Some(RegexConstraint::new(pattern)?),
None => None,
};
let choice_list = config
.choice_list
.as_ref()
.map(|choices| choices.iter().cloned().collect::<HashSet<String>>());
let json_schema = if let Some(schema) = &config.json_schema {
Some(JsonSchemaValidator::new(schema)?)
} else {
None
};
let grammar = if let Some(grammar_config) = &config.grammar {
Some(GrammarValidator::new(grammar_config)?)
} else {
None
};
Ok(Self {
regex,
choice_list,
json_schema,
grammar,
})
}
pub fn validate_token(
&self,
current_text: &str,
new_token: &str,
_tokenizer_fn: Option<&dyn Fn(usize) -> String>,
) -> bool {
let potential_text = format!("{}{}", current_text, new_token);
if let Some(regex) = &self.regex {
if !regex.is_viable_prefix(&potential_text) {
return false;
}
}
if let Some(choices) = &self.choice_list {
if !choices.contains(&potential_text) && !self.is_valid_prefix(&potential_text, choices)
{
return false;
}
}
if let Some(json_validator) = &self.json_schema {
if !json_validator.validate_partial(&potential_text) {
return false;
}
}
if let Some(grammar_validator) = &self.grammar {
if !grammar_validator.validate_partial(&potential_text) {
return false;
}
}
true
}
pub fn is_complete(&self, text: &str) -> bool {
if let Some(regex) = &self.regex {
if !regex.is_full_match(text) {
return false;
}
}
if let Some(choices) = &self.choice_list {
if !choices.contains(text) {
return false;
}
}
if let Some(json_validator) = &self.json_schema {
if !json_validator.validate_complete(text) {
return false;
}
}
if let Some(grammar_validator) = &self.grammar {
if !grammar_validator.validate_complete(text) {
return false;
}
}
true
}
pub fn regex(&self) -> Option<&RegexConstraint> {
self.regex.as_ref()
}
fn is_valid_prefix(&self, text: &str, choices: &HashSet<String>) -> bool {
choices.iter().any(|choice| choice.starts_with(text))
}
pub fn filter_valid_tokens(
&self,
current_text: &str,
token_logits: &[(usize, f32)],
tokenizer_fn: &dyn Fn(usize) -> String,
) -> Vec<(usize, f32)> {
token_logits
.iter()
.filter(|(token_id, _)| {
let token_str = tokenizer_fn(*token_id);
self.validate_token(current_text, &token_str, Some(tokenizer_fn))
})
.cloned()
.collect()
}
}
#[derive(Debug)]
pub struct JsonSchemaValidator {
schema: JsonSchema,
}
impl JsonSchemaValidator {
pub fn new(schema: &str) -> Result<Self> {
let schema = JsonSchema::parse(schema).map_err(|error| {
TrustformersError::invalid_input(format!("invalid JSON schema: {error}"))
})?;
Ok(Self { schema })
}
pub fn schema(&self) -> &JsonSchema {
&self.schema
}
pub fn validate_partial(&self, text: &str) -> bool {
if !Self::structure_is_open(text) {
return false;
}
match serde_json::from_str::<serde_json::Value>(text) {
Ok(document) => self.schema.validate(&document).is_ok(),
Err(_) => true,
}
}
pub fn validate_complete(&self, text: &str) -> bool {
self.schema.validate_text(text).is_ok()
}
fn structure_is_open(text: &str) -> bool {
let mut stack = Vec::new();
let mut in_string = false;
let mut escape_next = false;
for ch in text.chars() {
if escape_next {
escape_next = false;
continue;
}
match ch {
'\\' if in_string => escape_next = true,
'"' => in_string = !in_string,
'{' | '[' if !in_string => stack.push(ch),
'}' if !in_string => {
if stack.last() == Some(&'{') {
stack.pop();
} else {
return false;
}
},
']' if !in_string => {
if stack.last() == Some(&'[') {
stack.pop();
} else {
return false;
}
},
_ => {},
}
}
true
}
}
#[derive(Debug)]
pub struct GrammarValidator {
grammar: Option<Grammar>,
}
impl GrammarValidator {
pub fn new(grammar: &str) -> Result<Self> {
let grammar = Grammar::parse(grammar).map_err(|error| {
TrustformersError::invalid_input(format!("invalid grammar: {error}"))
})?;
Ok(Self { grammar })
}
pub fn grammar(&self) -> Option<&Grammar> {
self.grammar.as_ref()
}
pub fn validate_partial(&self, text: &str) -> bool {
match &self.grammar {
Some(grammar) => grammar.recognize(text).viable,
None => true,
}
}
pub fn validate_complete(&self, text: &str) -> bool {
match &self.grammar {
Some(grammar) => grammar.recognize(text).complete,
None => true,
}
}
pub fn get_valid_next_tokens(&self, prefix: &str) -> Vec<String> {
match &self.grammar {
Some(grammar) => grammar.recognize(prefix).next_terminals,
None => Vec::new(),
}
}
}