use std::sync::OnceLock;
pub use alice_llm::gguf::GgufTokenizer;
pub use alice_llm::grammar::{parse_gbnf, CharSet, Fsm, FsmError, Grammar};
pub use alice_llm::llama3::{GenerateResult, GrammarGenError, Llama3Model};
pub use alice_llm::sampling::{advance_fsm_on_emit, mask_logits_by_grammar, GrammarTokenizer};
use crate::runtime_parser::{parse_lol, ParseError};
use crate::SdfNode;
pub const LOL_FSM_MAX_DEPTH: usize = 4096;
const LOL_GBNF: &str = include_str!("../../../lol.gbnf");
static LOL_GRAMMAR: OnceLock<Grammar> = OnceLock::new();
#[must_use]
pub fn lol_grammar() -> &'static Grammar {
LOL_GRAMMAR.get_or_init(|| {
parse_gbnf(LOL_GBNF).unwrap_or_else(|e| panic!("bundled lol.gbnf failed to parse: {e}"))
})
}
#[derive(Debug)]
pub enum BridgeError {
GrammarGen(GrammarGenError),
Parse(ParseError),
}
impl std::fmt::Display for BridgeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::GrammarGen(e) => write!(f, "grammar-constrained generation failed: {e}"),
Self::Parse(e) => write!(f, "generated text failed to parse as LOL: {e}"),
}
}
}
impl std::error::Error for BridgeError {}
impl From<GrammarGenError> for BridgeError {
fn from(e: GrammarGenError) -> Self {
Self::GrammarGen(e)
}
}
impl From<ParseError> for BridgeError {
fn from(e: ParseError) -> Self {
Self::Parse(e)
}
}
pub fn generate_sdf_from_prompt(
model: &mut Llama3Model<'_>,
tokenizer: &GgufTokenizer,
prompt: &str,
max_new_tokens: usize,
) -> Result<SdfNode, BridgeError> {
let result = model.generate_grammar(
tokenizer,
prompt,
max_new_tokens,
lol_grammar(),
1.0, 1, )?;
let node = parse_lol(result.text.trim())?;
Ok(node)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grammar_parses_and_is_cached() {
let g1 = lol_grammar();
let g2 = lol_grammar();
assert!(std::ptr::eq(g1, g2));
assert!(g1.rule("expr").is_some(), "expected root `expr` rule");
}
#[test]
fn grammar_accepts_a_trivial_snippet() {
let g = lol_grammar();
let fsm = Fsm::start(g).unwrap().with_max_depth(LOL_FSM_MAX_DEPTH);
assert!(fsm.accepts_str("sphere(1.0)"));
}
#[test]
fn bridge_error_display_wraps_grammar_gen() {
let e = BridgeError::GrammarGen(GrammarGenError::NoValidToken { step: 7 });
let msg = format!("{e}");
assert!(msg.contains("grammar-constrained"));
assert!(msg.contains("step 7"));
}
#[test]
fn bridge_error_display_wraps_parse_error() {
let inner = ParseError {
message: "unexpected token".to_string(),
position: 42,
};
let e = BridgeError::Parse(inner);
let msg = format!("{e}");
assert!(msg.contains("failed to parse as LOL"));
assert!(msg.contains("pos 42"));
}
#[test]
fn bridge_error_from_grammar_gen() {
let src = GrammarGenError::NoValidToken { step: 0 };
let converted: BridgeError = src.clone().into();
match converted {
BridgeError::GrammarGen(inner) => assert_eq!(inner, src),
BridgeError::Parse(_) => panic!("wrong variant"),
}
}
#[test]
fn bridge_error_from_parse_error() {
let src = ParseError {
message: "boom".to_string(),
position: 3,
};
let converted: BridgeError = src.clone().into();
match converted {
BridgeError::Parse(inner) => {
assert_eq!(inner.message, src.message);
assert_eq!(inner.position, src.position);
}
BridgeError::GrammarGen(_) => panic!("wrong variant"),
}
}
}