dinja 1.3.0
dinja: ^1.3.0 copied to clipboard
Jinja engine for Hugging Face and GGUF LLM chat templates: messages, tools, generation prompts. Parity-tested against llama.cpp and Jinja2. All platforms, including web and Wasm.
1.3.0 #
- Fixed a
{% filter %}block, and a filter on the output of a blockset, a macro orcaller(), gettingJinjaString.usertext already escaped: withx = JinjaString.user('<b>'),{% filter upper %}{{ x }}{% endfilter %}is now<B>, not<B>, and{% filter length %}{{ x }}{% endfilter %}is3, not9, as in llama.cpp. Input text is now escaped once, when the template outputs it.| safeon such output,{% filter safe %}, and| safeon text made from it or from other safe text escape the input in it, as Jinja2 with autoescape does;| safeon a value passed torender, or returned by a function passed to it, still outputs it unescaped. A block whose output also contains| safeinput text is still escaped before the filter runs, as~does. - Fixed
tojsonandjoindropping the input marking, soJinjaString.usertext in their output was not escaped:{{ {"k": x} | tojson }}is now{"k": "<b>"}and{{ [x, "<i>"] | join }}is<b><i>. Only input text is escaped, after JSON escaping, in values, dict keys and separators; the JSON's own quotes and template text are not.tojsonoutput with input text in it is no longer safe (is escaped). - Fixed input text being output unescaped by string indexing,
first,last,reverse,listandforon a string,splitandrsplitpieces,strip,lstripandrstripwith characters,truncate,str(),~,+andformatwith a list or dict,string,upper,lower,title,capitalize,trim,replaceandindenton a list or dict,replace,indentandformaton text that is only partly input, input passed as areplacereplacement, input dict keys in aforloop, and an inputstrftime_nowformat. - Changed
replaceandindenton input text to leave the template text they add unescaped:x | replace('b', '<i>')is now<<i>>, not<<i>>, andx | indent('> ', true)no longer escapes>. Each character keeps the marking of the text it came from; llama.cpp marks all of the result as input when all of the source is. - Fixed
| safetext being escaped when passed toformat, or toreplaceorindenton input text, sliced with a step or split byunique, and escaped twice when it held escaped input:'{}'.format(x ~ (y | safe))is now<b><i>, not&lt;b&gt;<i>. - Fixed member access on an undefined value giving a function, which printed as
<function NAME>and wasdefined: for an undefinedy,{{ y.text }}and{{ y[0]['text'] }}now print nothing and are notdefined, as in llama.cpp. As in llama.cpp,y.upper,y.lengthand the other filters that llama.cpp gives undefined are functions returning an empty value (y.length()is0,y.default(1)is1) that print nothing, and calling any other member, such asy.get(), throws. Devstral Small 2507 no longer prints<function text>for an assistant turn withoutcontent. Also as in llama.cpp, Command R+ tool use renders anobjectparameter withoutadditionalPropertiesasDict[str, Union[]], notDict[str, Any], and QwQ-32B throws for an assistant tool-call turn withoutcontentbefore the last turn. - Changed a number subscript of none, as in
x[0]orx[0]['text']forx = none, to throwCannot access property with non-string: got Integer, as llama.cpp does; it gave undefined. Devstral Small 2507 with an assistant turn whose content is none now throws, as in llama.cpp.
1.2.0 #
- Rewrote the README and example around rendering a chat template, and updated the pubspec description and topics.
- Fixed
lstrip_blockskeeping the indentation before a{%or{#on the line after a block tag, as in{% if x %}\n {% set y = 1 %}. It is now removed, as in llama.cpp and Jinja2. Qwen3-Coder, GLM-4.6, GLM-4.7-Flash, MiniMax-M2, Nemotron-3-Nano and Command R7B prompts no longer contain stray spaces. - Fixed
strftime_nowsupporting only%Y %m %d %H %M %S. It now formats as llama.cpp does, with Cstrftimein the C locale as glibc implements it: every conversion (%b,%B,%a,%c,%%and the rest), flags and widths. Like llama.cpp, it throws without a string argument and when the result is empty or 100 bytes or longer. - Fixed
continueandbreakdropping what the loop pass had already rendered:{% for i in [1, 2] %}{{ i }}{% continue %}{% endfor %}is now12. Aforloop'selseblock now runs when no pass finishes withoutcontinueorbreak, as in llama.cpp and Jinja2. Output rendered in a nested block before the signal is kept, as in Jinja2; llama.cpp drops it. Output captured by a blockset, afilterblock, a macro or acaller()body that the signal leaves is discarded, as in llama.cpp. - Fixed input-marked values in a
forloop, including itselseblock, being escaped twice:{% for i in [1] %}{{ x }}{% endfor %}withx = JinjaString.user('<b>')is now<b>. - Fixed
caller()renderingNonefor a body statement without output, such as{% set %}or a comment, and not escapingJinjaString.uservalues in the body. - Fixed
select,reject,selectattr,rejectattranduniqueon none returning none, sonone | selectattr(...) | listwas[None]. They now return an empty list, as in llama.cpp. This fixes the functionary v3.1 prompt without tools.defaultnow also replaces none, as in llama.cpp. - Fixed macro keyword arguments binding by position, which rendered
greet(last='Smith', first='John')with empty values and madeadd(1, b=10)throw. Arguments now bind positionally, then by name, then from defaults, as in llama.cpp and Jinja2, includingcaller(...)arguments in a{% call %}block. A missing required argument throwsNot enough arguments provided, as in llama.cpp; an unknown keyword or a keyword repeating a positional argument throws, as in Jinja2. - Fixed unary
-and+failing to parse before anything but a number literal, as in{{ -n }},{{ items[:-n] }}and{{ not -n }}. As in llama.cpp and Jinja2, they bind tighter than filters, tests and**:-n|absis(-n)|abs. - Fixed
minandmaxwithattributereturning the attribute value instead of the item, as Jinja2 does.attributecan also be the second positional argument. - Added llama.cpp's numeric member access (
{{ items.0 }},{{ {10: 'Bob'}.10 }}), empty subscript (a[]is undefined),int * strrepetition, stringindentwidth (indent('> ')) andstr.formatwith{}placeholders. Otherformatfields, such as{0},{name},{{and{:>5}, throw, as in llama.cpp. - Added
BlankExpressiontopackage:dinja/ast.dartfor thea[]subscript. - Fixed the README and
example/security_example.dartclaiming that plain strings passed torenderare escaped; only values wrapped inJinjaString.userare. - Fixed template text containing
" "(eight spaces in double quotes) being deleted from the output. - Fixed
indentadding a newline to input that ends with one:'foo\n'|indentis nowfoo\n, as in llama.cpp and Jinja2. An empty string stays empty withfirst=true, as in llama.cpp. - Changed tests that take an argument, such as
divisibleby,eqandin, to throw when called without one, as llama.cpp and Jinja2 do.a is divisibleby -ais(a is divisibleby) - ain both, so it now throws instead of rendering-2. - Changed
*argument unpacking, as inf(*items), to throw, as llama.cpp does. It used to pass the list as a single argument. - Added the
formatfilter as llama.cpp has it: it callsstr.format, so'{}-{}'|format(1, 2)is1-2. Jinja2's%-style formatting is not supported:'%s'|format(x)returns%s.
1.1.1 #
- Fixed
tojsonto match llama.cpp:json.dumpsspacing ({"a": 1, "b": [1, 2]}), non-ASCII kept unlessensure_ascii=true, floats formatted as C++%gwith 6 significant digits (1.0is1,3.14159265is3.14159), non-string keys converted to strings, and positional arguments read as(ensure_ascii, indent, separators, sort_keys). A negative or non-integerindentnow gives one-line output, and one separator sets only the item separator. - Fixed printing a list, dict or tuple HTML-escaping its quotes:
{{ [1, '1'] }}now prints[1, '1']. Input-marked items are still escaped. - Fixed
-0.0and numbers outside the 64-bit integer range passed torenderbecoming integers on the web; they are now floats, as on the VM. Other whole-number doubles are still integers on the web, where they are indistinguishable fromint. - Changed list, dict and tuple string conversion to match llama.cpp: a string item containing
'is written in JSON form, as in["it's"].
1.1.0 #
- Added
package:dinja/ast.dartfor template analysis without importingsrc/:parseTemplate, which parses a template into aProgram; the AST node types; andLexerExceptionandParserException.package:dinja/dinja.dartis unchanged. - Fixed string equality to compare content only:
''.strip() == ''is now true, andsafeor input-marked strings equal plain ones. - Removed a debug
printto stdout when aforloop iterates a function; the error now names the function. - Fixed tests with one unparenthesized argument, such as
1 is eq 1and'a' is in ['a'], which failed to parse. As in Jinja2, a value after a test without a comma is now its argument:[x is odd 1]means[x is odd(1)]. - Fixed
uniqueto compare items with Python equality:[1, '1']keeps both,[1, 1.0, true]keeps one. It now ignores string case by default, as Jinja2 does. - Fixed a
ParserExceptionat the end of the template reporting position-1; it now reports the end of the template. - Fixed an incomplete expression at the end of the template, such as
{{ 1 +, throwingRangeErrorinstead ofParserException. - Fixed
ParserExceptionfromTemplateon templates with\r\nor a trailing newline: itssource,pos,lineandcolnow all refer to the normalized text, as withparseTemplate. - Changed
uniqueto follow Jinja2's signature,unique(case_sensitive=false, attribute=none): the first positional argument is nowcase_sensitive, notattribute.uniquenow also deduplicates the characters of a string and the keys of a dict. - Fixed input marking being dropped by
~, string repetition (*),replace,capitalize,title,string,indentand the last piece ofsplit(first ofrsplit), matching llama.cpp. Input-marked text from these is now escaped on output like{{ value }}.joinstill drops the marking, as in llama.cpp. - Fixed double escaping when an escaped block, such as a
{% set %}block or macro output, is joined with+to plain text.
1.0.0 #
- Initial release of Dinja.
- Minimal Jinja2 implementation ported from llama.cpp.
- Support for LLM chat templates.
- Secure input marking system.
- Fixed unused declaration in
nodes.dart. - Fixed
not inoperator logic for Maps. - Fixed duplicate key in
builtins.dart. - Added cross-compatibility generation script (
script/generate_cross_test.py).