Skip to main content

Module grammar

Module grammar 

Source
Expand description

GBNF grammar-constrained decoding.

A port of llama.cpp’s src/llama-grammar.cpp and src/llama-grammar.h (MIT; see docs/THIRD_PARTY_NOTICES.md). The algorithm is transcribed from that source rather than reconstructed from how EBNF engines usually work, because the two differ in places that matter – the negation of a character class lives on its first element only, the repetition rewrites produce observable rule ids, and a token piece that ends mid-codepoint has to stay viable rather than be rejected.

What this replaces, and why it is not the same kind of thing: crates/ferrox-server/src/json_mode.rs masks logits by a fixed character class. It has no state, so it cannot know whether a } closes an object that was opened. This is a pushdown machine over a parsed grammar, so it can.

§Layout

ModuleRole
elementthe compiled element / rule / cursor types
errorevery refusal, naming what is missing
utf8llama.cpp’s two UTF-8 decoders, partial sequences included
parserGBNF text to a rule table
machinethe pushdown stack machine over that table
candidateswhich candidate tokens no viable stack accepts
lazytrigger tokens / patterns: a grammar that switches on mid-generation
json_schemaJSON Schema to GBNF text, for response_format

§Status

Steps 1 and 2 of the three-step spine in docs/plans/llama-cpp-gap-inventory.md §8 item 8: the parser and the stack machine, with the candidate-rejection core the sampler hook would sit on. Not wired into sampling or the server. The seam a caller needs is machine::Grammar::accept_token after each accepted token and candidates::reject_candidates before each sample; nothing calls either yet.

json_schema now ports common/json-schema-to-grammar.cpp, which is what response_format: json_schema needs on top of a grammar. It is stricter than upstream by design – a keyword it cannot honour is a typed refusal rather than a grammar that accepts too much – and its module docs list what is ported, what is refused by name, and where its output differs from llama.cpp’s. ferrox-server now answers response_format: {"type": "json_schema"} through it (see that crate’s grammar_request), so a schema and a hand-written grammar reach the same machine.

Lazy grammars (lazy) are ported: trigger_tokens and trigger_patterns, the accumulated trigger buffer, and the replay that feeds the grammar from the match onward. That is what tool_choice needs on top of a grammar – a model may talk before it calls a tool, and a grammar applied from token zero forbids the talking.

Re-exports§

pub use candidates::reject_candidates;
pub use candidates::Candidate;
pub use candidates::DecodedPiece;
pub use element::GrammarElement;
pub use element::GrammarRule;
pub use element::GrammarStack;
pub use element::GreType;
pub use element::RulePos;
pub use error::GrammarError;
pub use json_schema::json_schema_to_grammar;
pub use json_schema::json_schema_to_grammar_value;
pub use json_schema::SchemaError;
pub use lazy::LazyState;
pub use lazy::LazyTriggers;
pub use lazy::TriggerPattern;
pub use lazy::TriggerStep;
pub use machine::Grammar;
pub use parser::parse;
pub use parser::parse_with_vocab;
pub use parser::GrammarVocab;
pub use parser::ParsedGrammar;
pub use utf8::PartialUtf8;

Modules§

candidates
Which candidate tokens no viable parse stack accepts.
element
The compiled form of a GBNF grammar: elements, rules, and a cursor into them.
error
Errors this grammar engine returns.
json_schema
JSON Schema to GBNF, a port of llama.cpp’s common/json-schema-to-grammar.cpp (MIT; see docs/THIRD_PARTY_NOTICES.md).
lazy
Lazy grammars: the trigger half of llama.cpp’s llama_grammar.
machine
The pushdown stack machine, transcribed from the second half of llama.cpp’s src/llama-grammar.cpp.
parser
The GBNF parser, transcribed from llama_grammar_parser in llama.cpp’s src/llama-grammar.cpp.
utf8
UTF-8 decoding for the grammar engine, transcribed from the two decode_utf8 overloads in llama.cpp’s src/llama-grammar.cpp.