aide-grammar-constrained-generation · git:20260908.e9ac98d · 2026-09-08 · sha256 883d3ffd7ea0298f
aide-grammar-constrained-generation git:20260908.e9ac98dC
Immutable. This exact content is served forever at /api/v1/blob/883d3ffd7ea0298f.
# Skill: aide-grammar-constrained-generation
# Grammar-Constrained Generation — GBNF Wiring for Structured Outputs
Wire GBNF grammars to llama.cpp model serving for deterministic structured output
(SESEARCH/REPLACE blocks, JSON, code). Eliminates format failures at the decoder level
instead of relying on prompt engineering.
## Architecture
```
User text → model-manager.chat → llama.cpp /completion with grammar →
structured output → parseSearchReplaceBlocks → apply
```
## Files
- `grammar/sr-proposal.gbnf` — SEARCH/REPLACE proposal grammar (already written)
- `daemon/server.mjs` — model-manager.chat endpoint (needs grammar parameter)
- `node/src/services/model-manager.mjs` — chat function (needs grammar passthrough)
## Wiring Steps
### 1. Add grammar parameter to model-manager.chat
In `daemon/server.mjs`, the `/api/model/chat` handler calls `modelManager.chat(messages, options)`.
Add `grammar` to the options:
```js
const { messages, temperature, max_tokens, grammar } = req.body;
const result = await modelManager.chat(messages, { temperature, max_tokens, grammar });
```
### 2. Pass grammar to llama.cpp endpoint
In `model-manager.mjs`, the `chat()` function calls the llama.cpp `/completion` endpoint.
Add grammar to the request body:
```js
const body = {
prompt: formattedPrompt,
n_predict: max_tokens || 2048,
temperature: temperature || 0.7,
stream: false,
};
if (grammar) body.grammar = grammar;
```
### 3. Load grammar files
Grammar files live in `grammar/` directory. Load them by name:
```js
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
function loadGrammar(name) {
return readFileSync(join(process.cwd(), 'grammar', `${name}.gbnf`), 'utf8');
}
```
### 4. Apply grammar per-request
In the chat handler, detect when grammar is needed:
```js
// For SEARCH/REPLACE proposals
if (req.body.format === 'search-replace') {
body.grammar = loadGrammar('sr-proposal');
}
```
## GBNF Grammar Format
```
root ::= block+
block ::= search-marker content replace-marker content
search-marker ::= "<<<<<<< SEARCH" "\n"
replace-marker ::= "=======" "\n" ">>>>>>> REPLACE" "\n"
content ::= char*
char ::= [^\n] | "\n"
```
## Verification
Battery: Run `node scripts/grammar-battery.mjs` (to be created).
Probes: grammar loads, grammar parses, chat accepts grammar param, output matches grammar.
## Pitfalls
- Grammar must be valid GBNF — test with `llama-cli --grammar` before wiring
- Some models don't support grammar约束 — test with the actual model
- Grammar adds latency — only use when structured output is required
- Fallback: if grammar fails, use prompt-based format enforcement