<!-- llms-explorer concept facts · https://llms-explorer.com/tree/llama-cpp-jinja-capability-probes-chat-template/ · pack 2026-10-05 · ~2577 tokens -->

# llama.cpp jinja capability probes chat_template_caps

> Probes run with `ctx.is_get_stats = true`. Every value the template reads is tagged with `stats.used` and the list of operations applied to it (`test_is_string`, `selectattr`, `array_access`). Most probes infer a capability from "was this field read", not from the rendered text. Exceptions during...

Parent: [Mac local LLMs: llama.cpp internals](https://llms-explorer.com/tree/mac-local-llms-llama-cpp-internals/) · 1 facets · 42 facts · page: https://llms-explorer.com/tree/llama-cpp-jinja-capability-probes-chat-template/

## Facts

- Probes run with `ctx.is_get_stats = true`. Every value the template reads is tagged with `stats.used` and the list of operations applied to it (`test_is_string`, `selectattr`, `array_access`). Most probes infer a capability from "was this field read", not from the rendered text. Exceptions during a probe are swallowed. — source: `asserted`
- Probe order: typed content, system prompt, single tool call with object arguments, (string arguments, only if object arguments failed), parallel tool calls, preserve reasoning, reasoning effort. — source: `asserted`
- Typed-content probe: one user message with the marker `STRING_MARKER`. Array-style access (`selectattr` or `array_access`) means `supports_typed_content`. A render failure, or array access whose output lacks the marker, clears `supports_string_content`. A template that tests `is string` triggers a second probe with an empty content array. — source: `asserted`
- System probe: a system plus a user message. If the system content value was never read, `supports_system_role` is false. — source: `asserted`
- Tool probe: user, assistant with one tool call (object arguments), tool result, assistant, user, with one tool definition. Unread `tools[0].function.name` clears `supports_tools`; unread `tool_calls` clears `supports_tool_calls`; a read of `arguments.arg` sets `supports_object_arguments`. If that fails, a string-arguments variant runs, and a render failure there clears both `supports_tool_calls` and `supports_tools`. — source: `asserted`
- Parallel probe: two tool calls in one assistant message; unread `tool_calls[1].function` clears `supports_parallel_tool_calls`. — source: `asserted`
- Reasoning-effort probe: sets `enable_thinking` true and `reasoning_effort` "low", then reads the stats of `reasoning_effort`. Unlike the preserve-reasoning probe, it does not look at the output, so any read of the variable counts. — source: `asserted`
- `caps_apply_reasoning_effort` binds `reasoning_effort` and `reasoning_strength` to the same string value, so templates written for either name see the request value. — source: `asserted`
- The preserve-reasoning probe is the only one judged on output text, because a template may read `reasoning_content` in an `if` test without printing it. — source: `asserted`
- Workarounds keyed on the original (probed) caps, in `common_chat_templates_apply`: developer role mapped to system unless the source contains `<|channel|>`; system message merged into the next message with a single "\n" (or dropped, with a warning, when it is the only message) when `supports_system_role` is false; empty-string `content` added to tool-call messages when the template supports tool calls; JSON-string tool arguments parsed to objects when `supports_object_arguments`. — source: `asserted`
- Content normaliser: a string-only template gets array content joined with "\n" between text parts and no newline around media markers; a typed-only template gets each string wrapped as `[{"type":"text","text":...}]`. — source: `asserted`
- Per-request `chat_template_kwargs` are stored as JSON strings and parsed into `extra_context`; a non-empty string `reasoning_effort` in the merged input triggers `caps_apply_reasoning_effort` before `global_from_json`. — source: `asserted`
- `common_json` (the wrapper llama.cpp now uses) keeps object keys in insertion order. A tool-argument string parsed by `func_args_not_string` therefore keeps the model's key order when a template iterates it. — source: `asserted`
- The older minja-based `chat_template_caps` carried a narrower set; the nine-flag set with `supports_preserve_reasoning` and `supports_reasoning_effort` is on master as of 2026-10-04. Dates of each flag's introduction were not found in the sources read. — source: `asserted`
- No flag covers acceptance of a non-leading system message. A template can pass the system probe (leading system is read) and still call `raise_exception('System message must be at the beginning.')` for a later one, as the Qwen 3.8 template does. — source: `asserted`
- Qwen 3.8's template passes the reasoning-effort probe (effort "low" is valid) but rejects values such as "high" at render time; the probe cannot see the accepted vocabulary. — source: `asserted`
- A template that supports tool calls but never prints tool definitions triggers the warning "Template supports tool calls but does not natively describe tools". — source: `chat.cpp`
- Because the typed/string normaliser rewrites content before rendering, a template that handles only arrays receives "\n"-joined text for multi-part string input; this differs from what a Python `apply_chat_template` call would render. Prefix tests done in Python do not see it. — source: `asserted`
- Probes use synthetic messages with a user-only or short history, so history-dependent behaviour (empty think wrappers, whitespace) is not probed except for preserve reasoning. — source: `asserted`
- Which release introduced `supports_reasoning_effort` and `supports_object_arguments` in the property output. — source: `asserted`
- Whether `/props` `chat_template_caps` reflects the `--chat-template-file` override or the embedded template when both exist (chat.cpp returns the caps of `template_tool_use` if present, else `template_default`). — source: `asserted`
- caps.cpp exposes nine booleans: supports_string_content, supports_typed_content, supports_tools, supports_tool_calls, supports_parallel_tool_calls, supports_system_role, supports_preserve_reasoning, supports_reasoning_effort, supports_object_arguments — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- Capability probes render the template with is_get_stats set and read per-value `stats.used` and applied-operation lists rather than only the output — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- Exceptions raised while a probe renders are caught and ignored — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- The typed-content probe uses the marker STRING_MARKER and treats selectattr or array_access on content as typed-content support — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- supports_system_role is false when a system message's content value is never read during the system probe — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- The tool probe runs a single call with object arguments first and a string-arguments variant only when object arguments are unsupported — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- supports_parallel_tool_calls is false when the second tool call in an assistant message is never read — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- supports_reasoning_effort is set from whether reasoning_effort (set to "low" with enable_thinking true) was read, without checking output — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- caps_apply_reasoning_effort sets both reasoning_effort and reasoning_strength to the same string — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- Only the preserve-reasoning probe is judged on rendered output, because reasoning_content may be read in an if test without being printed — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/jinja/caps.cpp)
- chat.cpp merges a leading system message into the next message with "\n" when supports_system_role is false, and drops it with a warning when it is the only message — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/chat.cpp)
- chat.cpp maps the developer role to system for every template whose source lacks "<|channel|>" — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/chat.cpp)
- chat.cpp parses JSON-string tool arguments into objects when the template supports object arguments, and throws "Failed to parse tool call arguments as JSON" on invalid JSON — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/chat.cpp)
- chat.cpp adds an empty-string content to tool-call messages that lack it when the template supports tool calls — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/chat.cpp)
- chat.cpp converts content to string-only or typed-only form to match the probed caps, joining string-only text parts with "\n" — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/chat.cpp)
- chat.cpp warns that a template supporting tool calls but not tool descriptions may produce bad results and suggests overriding the template — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/chat.cpp)
- chat.cpp applies caps_apply_reasoning_effort when the merged input holds a non-empty string reasoning_effort — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/chat.cpp)
- chat.cpp returns the caps map of the tool-use template when one exists, otherwise of the default template — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/chat.cpp)
- common_json keeps object keys in the order they were added — [source](https://raw.githubusercontent.com/ggml-org/llama.cpp/master/common/json.h)
- No capability flag covers non-leading system messages, so a template that raises on them can still report supports_system_role true — source: `asserted`
- The reasoning-effort probe cannot detect which effort names a template accepts — source: `asserted`
