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
| Module | Role |
|---|---|
element | the compiled element / rule / cursor types |
error | every refusal, naming what is missing |
utf8 | llama.cpp’s two UTF-8 decoders, partial sequences included |
parser | GBNF text to a rule table |
machine | the pushdown stack machine over that table |
candidates | which candidate tokens no viable stack accepts |
lazy | trigger tokens / patterns: a grammar that switches on mid-generation |
json_schema | JSON 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; seedocs/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_parserin llama.cpp’ssrc/llama-grammar.cpp. - utf8
- UTF-8 decoding for the grammar engine, transcribed from the two
decode_utf8overloads in llama.cpp’ssrc/llama-grammar.cpp.