Chat Templates and Parsing
On this page
llamadart routes chat rendering/parsing through template handlers aligned to
llama.cpp behavior.
Parity model#
llamadart reimplements llama.cpp-style template detection, rendering,
workarounds, grammar wiring, and parse behavior in Dart. This is why
engine.create(...) and engine.chatTemplate(...) can keep consistent behavior
across native and web backends.
Template rendering is powered by dinja, the
Dart Jinja runtime used by llamadart for llama.cpp-compatible template
execution.
For internals and pipeline details, see Template Engine Internals.
Core API#
Use engine.chatTemplate(...) when you need:
- prompt preview,
- grammar and stop-sequence inspection,
- format-aware rendering diagnostics.
final result = await engine.chatTemplate(
messages,
tools: tools,
toolChoice: ToolChoice.auto,
parallelToolCalls: false,
customTemplate: null,
chatTemplateKwargs: const {'use_builtin_tools': true},
);
print(result.prompt);
print(result.format);
Useful parameters#
customTemplate: per-call template override.chatTemplateKwargs: additional template globals.templateNow: deterministic time injection for tests.sourceLangCode/targetLangCode: TranslateGemma style metadata.responseFormat: structured-output schema hints.
engine.create(...) accepts responseFormat for strict structured output.
Use {'type': 'json_object'} or
{'type': 'json_schema', 'json_schema': {'schema': <JSON schema>}}.
Application code can build those maps with LlamaStructuredOutput, or call
engine.createStructuredJson(...) to collect streamed content and validate the
final JSON before decoding it into an app type. Streaming UI code can still pass
responseFormat: output.responseFormat and finish with
await stream.parseStructuredJson(output) after the stream completes.
Grammar-capable backends use those hints for strict output. LiteRT-LM native
and web fail early for strict response formats because the current public
runtime APIs do not expose JSON-schema/Lark constraint wiring.
chatTemplate(...) still accepts the deprecated jsonSchema shortcut for
template inspection. Prefer responseFormat for new code; if both are passed,
responseFormat wins.
When to inspect template output#
Inspect template output when debugging:
- tool-call shape mismatches,
- stop-sequence behavior,
- model-specific reasoning/content boundaries,
- template routing differences after upgrades.
Built-in format coverage#
Built-in handlers include newer formats such as Gemma 4. In practice that means
llamadart can detect and parse:
<|turn> ... <turn|>turn framing,<|think|>thinking enablement in the system prompt,<|channel>thought ... <channel|>reasoning output,<|tool_call>call:name{args}<tool_call|>tool-call envelopes.
Gemma 4 thought-channel output is parsed incrementally during streaming, so
chunk.choices.first.delta.thinking carries reasoning text while
chunk.choices.first.delta.content remains reserved for final answer content.
Tencent Hunyuan V3 templates are also detected directly, including their
namespaced reasoning tags and parallel <tool_call:opensource> envelopes.
LiteRT-LM template registry#
GGUF models expose tokenizer.chat_template metadata directly through the
llama.cpp backend. Native .litertlm bundles do not currently expose their
embedded template through the LiteRT-LM FFI, so llamadart uses a
filename-keyed registry for supported Gemma and Qwen LiteRT-LM families.
Native LiteRT-LM engine.create(...) uses LiteRT-LM's Conversation APIs for
eligible text-only chat requests so system messages, history, tools, and extra
context stay structured inside the runtime. The Dart template registry is still
used for template metadata, streamed output parsing, engine.chatTemplate(...),
web LiteRT-LM, and fallback prompt rendering when a request cannot use the
native conversation path.
Use ModelParams.chatTemplate when loading a .litertlm bundle whose family is
not in the registry or whose filename has been changed. The maintained registry
coverage, smoke commands, and contribution notes live in
doc/litert_lm_templates.md.
Custom template overrides#
For application code, the supported customization path is customTemplate on
engine.chatTemplate(...).
import 'package:llamadart/llamadart.dart';
const String customTemplate = '''
{% for message in messages %}
{{ message['role'] }}: {{ message['content'] }}
{% endfor %}
Assistant:
''';
Future<void> main() async {
final LlamaEngine engine = LlamaEngine(LlamaBackend());
try {
await engine.loadModel('model.gguf');
final messages = [
LlamaChatMessage.fromText(
role: LlamaChatRole.user,
text: 'Explain local inference in one sentence.',
),
];
final rendered = await engine.chatTemplate(
messages,
customTemplate: customTemplate,
addAssistant: true,
);
print(rendered.prompt);
print(rendered.stopSequences);
} finally {
await engine.dispose();
}
}
About custom handlers#
ChatTemplateHandler is an internal extension point used by built-in format
implementations.
There is currently no public API to register custom handlers globally from application code. If you need first-class support for a new template format, open an issue with a minimal reproducible template and sample outputs.