Guides

Tools and schemas

A server that accepts tools and ignores them fails in the worst available way: it returns a well-formed assistant message containing prose about the function it would have called, the framework parses it as an answer, and nothing anywhere reports a problem. llamay implements tool calling on top of constrained decoding rather than on top of hope.

The constraint is the point

pkg/constrain compiles a JSON Schema to a byte-level pushdown automaton. A token is allowed only if every byte it contributes keeps the automaton alive, so the model can produce the wrong value but not a syntax error — and, because the automaton carries a richer state than a JSON grammar does, it also cannot produce a missing key, a string where a number belongs, or a function nobody offered.

the tool's schema exactly as the caller sent it CompileSchema → pushdown automaton Mask over the whole vocabulary the sampler chooses only from tokens that keep it alive every token, both ways Two tables make it affordable Naively, running 150k tokens through a nondeterministic stack machine costs 263 ms per token — a stall, not a constraint. Transitions are cheap and enormously numerous; masks are expensive and few. So they are cached separately. What it costs, measured unconstrained 10.7 tok/s json_schema 9.5 (89%) json_object 8.7 (81%) one tool 8.5 (79%) three tools (AnyOf) 7.3 (68%) QWEN2.5, 151,936-TOKEN VOCABULARY, 8 x86 CORES, NO GPU
Three automata cost ten to fifteen per cent more than one, not three times as much, because AnyOf drops an alternative as soon as a token rules it out — and the function name in the first few tokens rules out all but one.

Tool calling

tools and tool_choice work in all three API shapes: OpenAI's tool_calls, Anthropic's tool_use blocks, and Ollama's tools. When tool_choice names a function or is "required", the tool's own schema is compiled and the call cannot be malformed. Several tools become a union of automata rather than a wish.

curl localhost:11435/v1/chat/completions -d '{
  "messages": [{"role":"user","content":"weather in pune?"}],
  "tools": [{"type":"function","function":{"name":"get_weather",
    "parameters":{"type":"object",
      "properties":{"city":{"type":"string"},"unit":{"enum":["c","f"]}},
      "required":["city","unit"]}}}],
  "tool_choice": "required"}'

Streaming holds the call back: a partial tool call is not a thing a caller can do anything with, so the deltas carry text and the call arrives complete.

Structured output

Request fieldGuarantee
response_format: {"type":"json_schema", …}The full schema constraint.
llamay_schemaThe same, under llamay's own namespace.
Ollama's format, carrying a schemaThe same.
llamay_format: "json"The weaker guarantee: any valid JSON.
llamay_lexicon: trueDecoding constrained to a word list, with skeleton indexing.

What is enforced, and what is not

Keywords
Enforced Object properties and their types, required, closed objects, nested objects, arrays with items and minItems/maxItems, the six scalar types, enum, const, and minLength/maxLength on strings. A number has to be a JSON number — 1., 1e and 01 are refused, and generation cannot stop in the middle of one.
Ignored, and it says so minimum, maximum, multipleOf and string pattern. A value can still come out of range; it will be the right type under the right key.
Refused at compile time $ref, anyOf, oneOf, allOf, not, patternProperties, if/then — with a message naming the keyword. Ignoring one of these changes which documents are legal by so much that the caller would be getting a different schema than the one they sent, with nothing saying so.
Two deliberate departures from the JSON Schema defaults
  • An object that declares properties and says nothing about additionalProperties is closed. The spec's default is the opposite, and following it would mean a schema with three named properties constrains almost nothing.
  • Structural whitespace is budgeted, sixty-four characters per document. Whitespace is the only byte that returns the automaton to the state it came from, so with no bound a greedy sampler whose best legal token is a space emits spaces until the token budget runs out and the document is never closed. This is measured, not feared: the fixture model did exactly that.

GBNF grammars

llama.cpp's grammar files are read by a hand-written parser and compiled onto the same byte-level automaton the schemas use; its own grammars/ are in testdata/grammars as tests. Three things are refused, at load, by name:

What is still unverified