metrale_grammar/matcher/
completion.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// GrammarMatcher — grammar-legal completion to a stop-legal state.
4//
5// Powers budget-aware graceful close of structured outputs (Metrale Engine #144):
6// when a length-limited response would otherwise stop with the stop token
7// forbidden mid-structure (e.g. inside an open JSON string), this finds a
8// byte sequence that drives the grammar to a state where the root rule can
9// complete — so a truncated `finish_reason="length"` response is still
10// parseable.
11//
12// The search is a depth-first, CLOSURE-PREFERRING walk: at every choice
13// point it tries structural closers (`"` `}` `]` `,` `:` …) before content
14// bytes, so it commits to closing rather than exploring the exponential
15// space of string/number content continuations. That is what lets it reach
16// DEEP closes — e.g. an unemitted required field needing `,"tags":[]}` —
17// which a breadth-first search would never reach before exhausting its
18// budget on content branches.
19//
20// There is no C++ upstream for this; it is a Metrale Engine addition built on the
21// same byte-level Earley primitives as `find_jump_forward_string`
22// (`advance` / `pop_last_states` / `is_completed`).
23
24use super::matcher::GrammarMatcher;
25
26/// Hard cap on parser byte-advances explored. A close sequence is short in
27/// practice; if none is found under this budget the caller falls back to a
28/// plain finish — strictly no worse than the prior behavior.
29const COMPLETION_NODE_BUDGET: usize = 8192;
30
31/// Priority-ordered "closing" bytes tried before any content byte: string /
32/// container closers and JSON structural separators. Ordering the search to
33/// take these first makes it commit to closing the current structure instead
34/// of extending it.
35const CLOSE_PRIORITY: &[u8] = b"\"}],: \n\t";
36
37/// Max distinct CONTENT (non-priority) bytes explored per node. Forced
38/// positions — e.g. each character of a required key — expose exactly one
39/// acceptable byte, so a small fan-out preserves forced-run closes while
40/// bounding branching at genuine content choice points.
41const CONTENT_FANOUT: usize = 3;
42
43impl GrammarMatcher {
44    /// A grammar-legal byte sequence that advances the grammar from its
45    /// CURRENT state to one where the root rule can complete (a stop token
46    /// is legal), or `None` if none is found within `max_bytes` and the node
47    /// budget. The matcher state is left UNCHANGED.
48    ///
49    /// Returns `Some(empty)` when the grammar can already stop here.
50    ///
51    /// Soundness: every byte of a returned path is applied via
52    /// `parser.advance`, and a path is only returned once
53    /// `parser.is_completed()` holds — so the result is always a grammar-legal
54    /// completion. Closure-first ordering means it is not guaranteed to be the
55    /// globally shortest close, but it is a valid one and is found without
56    /// exploring content breadth.
57    #[must_use]
58    pub fn find_completion_to_accept(&mut self, max_bytes: usize) -> Option<Vec<u8>> {
59        if self.is_terminated() {
60            return None;
61        }
62        if self.parser.is_completed() {
63            return Some(Vec::new());
64        }
65        let mut out = Vec::new();
66        let mut budget = COMPLETION_NODE_BUDGET;
67        if self.close_dfs(max_bytes, &mut out, &mut budget) {
68            // `close_dfs` left the parser advanced through `out`; restore it so
69            // the search is side-effect-free for the caller.
70            if !out.is_empty() {
71                self.parser.pop_last_states(out.len());
72            }
73            Some(out)
74        } else {
75            None
76        }
77    }
78
79    /// Depth-first, closure-preferring search. Appends accepted bytes to
80    /// `out`; returns `true` with the parser left advanced through `out` once
81    /// the grammar can stop. On `false` it has restored the parser and left
82    /// `out` unchanged.
83    fn close_dfs(&mut self, remaining: usize, out: &mut Vec<u8>, budget: &mut usize) -> bool {
84        if self.parser.is_completed() {
85            return true;
86        }
87        if remaining == 0 || *budget == 0 {
88            return false;
89        }
90        let mask = self.parser.acceptable_byte_mask();
91        // 1) Structural closers first — commit to closing the open structure.
92        for &b in CLOSE_PRIORITY {
93            if mask[b as usize] && self.try_byte(b, remaining, out, budget) {
94                return true;
95            }
96            if *budget == 0 {
97                return false;
98            }
99        }
100        // 2) Bounded content fall-back — covers forced required-field bytes
101        //    (where exactly one content byte is acceptable).
102        let mut content = 0;
103        for b in 0u16..256 {
104            let byte = b as u8;
105            if !mask[b as usize] || CLOSE_PRIORITY.contains(&byte) {
106                continue;
107            }
108            if content >= CONTENT_FANOUT || *budget == 0 {
109                break;
110            }
111            content += 1;
112            if self.try_byte(byte, remaining, out, budget) {
113                return true;
114            }
115        }
116        false
117    }
118
119    /// Advance by `byte` and recurse. Keeps the byte in `out` (parser left
120    /// advanced) iff the recursion reaches a completion; otherwise rolls the
121    /// byte back. Returns whether a completion was found through `byte`.
122    fn try_byte(
123        &mut self,
124        byte: u8,
125        remaining: usize,
126        out: &mut Vec<u8>,
127        budget: &mut usize,
128    ) -> bool {
129        *budget = budget.saturating_sub(1);
130        if !self.parser.advance(byte) {
131            return false;
132        }
133        out.push(byte);
134        if self.close_dfs(remaining - 1, out, budget) {
135            return true;
136        }
137        out.pop();
138        self.parser.pop_last_states(1);
139        false
140    }
141
142    /// Like [`Self::find_completion_to_accept`], but returns the close as
143    /// content **token ids**, greedily encoded against this matcher's vocab
144    /// (`sorted_decoded_vocab`, which excludes stop/special tokens — so a
145    /// close never emits a control token). Returns `Some(empty)` when the
146    /// grammar can already stop, and `None` when no bounded close exists or
147    /// a close byte is not representable as a content token.
148    #[must_use]
149    pub fn find_completion_token_ids(&mut self, max_bytes: usize) -> Option<Vec<i32>> {
150        let bytes = self.find_completion_to_accept(max_bytes)?;
151        if bytes.is_empty() {
152            return Some(Vec::new());
153        }
154        self.encode_bytes_greedy(&bytes)
155    }
156
157    /// Greedy longest-match encode of `bytes` into content token ids. The
158    /// concatenated token bytes equal `bytes` by construction, so detokenizing
159    /// the result reproduces the close exactly. `None` if any position has no
160    /// covering token.
161    fn encode_bytes_greedy(&self, bytes: &[u8]) -> Option<Vec<i32>> {
162        let vocab = self.tokenizer_info().sorted_decoded_vocab();
163        let mut out = Vec::new();
164        let mut i = 0;
165        while i < bytes.len() {
166            let rem = &bytes[i..];
167            let mut best: Option<(i32, usize)> = None;
168            for (id, tok) in vocab {
169                let len = tok.len();
170                if len == 0 || len > rem.len() || !rem.starts_with(tok.as_slice()) {
171                    continue;
172                }
173                if best.is_none_or(|(_, bl)| len > bl) {
174                    best = Some((*id, len));
175                }
176            }
177            let (id, len) = best?;
178            out.push(id);
179            i += len;
180        }
181        Some(out)
182    }
183}