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, GrammarGenResult, GrammarPrefix, Llama3Model,
};
pub use alice_llm::sampling::{advance_fsm_on_emit, mask_logits_by_grammar, GrammarTokenizer};
use crate::intent::Program;
use crate::runtime_parser::{parse_lol, parse_program, ParseError};
use crate::SdfNode;
pub const LOL_FSM_MAX_DEPTH: usize = 4096;
const LOL_GBNF: &str = crate::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)]
#[non_exhaustive]
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)
}
pub fn generate_program_from_prompt(
model: &mut Llama3Model<'_>,
tokenizer: &GgufTokenizer,
prompt: &str,
max_new_tokens: usize,
) -> Result<Program, BridgeError> {
let result = model.generate_grammar(
tokenizer,
prompt,
max_new_tokens,
lol_grammar(),
1.0, 1, )?;
let program = parse_program(result.text.trim())?;
Ok(program)
}
#[derive(Debug, Clone)]
pub struct ThinkingOutput {
pub program: Program,
pub prefix_text: String,
pub prefix_marker_hit: bool,
pub text: String,
pub prefix_ms: u64,
pub decode_ms: u64,
pub total_ms: u64,
}
impl From<(Program, GrammarGenResult)> for ThinkingOutput {
fn from((program, r): (Program, GrammarGenResult)) -> Self {
Self {
program,
prefix_text: r.prefix_text,
prefix_marker_hit: r.prefix_marker_hit,
text: r.text,
prefix_ms: r.prefix_ms,
decode_ms: r.decode_ms,
total_ms: r.total_ms,
}
}
}
pub fn generate_program_thinking(
model: &mut Llama3Model<'_>,
tokenizer: &GgufTokenizer,
prompt: &str,
stop_marker: &str,
max_prefix_tokens: usize,
max_new_tokens: usize,
) -> Result<ThinkingOutput, BridgeError> {
let prefix = GrammarPrefix {
stop_marker,
max_prefix_tokens,
};
let result = model.generate_grammar_prefixed(
tokenizer,
prompt,
&prefix,
max_new_tokens,
lol_grammar(),
1.0, 1, )?;
let program = parse_program(result.text.trim())?;
Ok(ThinkingOutput::from((program, result)))
}
#[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"),
}
}
}