Support Jinja2 implicit adjacent string-literal concatenation in minja - #1095
Merged
Conversation
Jinja2 (like Python) implicitly concatenates adjacent string literals,
e.g. `{{ "foo" "bar" }}` renders as `foobar`. minja's `parseConstant`
only consumed a single string literal, so it raised
"Expected closing parenthesis in call args" whenever a template placed
two string literals next to each other.
This breaks real, valid templates. For example google/gemma-4-E2B-it's
`chat_template.jinja` splits a long `raise_exception(...)` message across
adjacent string literals, which made the whole template fail to parse and
rendered `OrtxApplyChatTemplate` unusable for that model.
Merge any run of consecutive string literals into a single constant in
`parseConstant`, matching Jinja2/Python semantics. Adds a unit test
covering single-line, cross-newline, and mixed-quote concatenation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d58326c3-f1ef-497f-a288-3dfd4b0f05c6
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the minja template parser to match Jinja2/Python behavior by implicitly concatenating adjacent string literals (e.g., {{ "foo" "bar" }} → foobar). This fixes parsing of real-world Jinja templates that split long strings across multiple adjacent literals, enabling OrtxApplyChatTemplate to handle such templates successfully.
Changes:
- Extend
minja::Parser::parseConstant()to merge runs of consecutive string literals into a single constant value. - Add a tokenizer/chat-template regression test that validates same-line, newline-separated, and mixed-quote adjacent literal concatenation.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| test/pp_api_test/test_tokenizer_chat.cc | Adds a regression test covering implicit adjacent string-literal concatenation via OrtxApplyChatTemplate. |
| shared/api/minja.hpp | Implements Jinja2-style implicit concatenation by looping parseString() calls and appending consecutive literals. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
sayanshaw24
enabled auto-merge (squash)
July 23, 2026 21:04
sayanshaw24
approved these changes
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Jinja2 (like Python) implicitly concatenates adjacent string literals, e.g.
{{ "foo" "bar" }}renders asfoobar. minja'sparseConstantonly consumed a single string literal, so it threwExpected closing parenthesis in call argswhenever a template placed two string literals next to each other.This breaks real, valid templates. For example
google/gemma-4-E2B-it'schat_template.jinjasplits a longraise_exception(...)message across adjacent string literals:{{- raise_exception("chat_template: tool_calls[].function.arguments must be a " "JSON object (mapping), not a string. Deserialize arguments " "before passing to the template.") -}}minja::Parser::parse()fails at that line, so the entire template is rejected andOrtxApplyChatTemplate(used by onnxruntime-genai) becomes unusable for the model.Before (parsing the Gemma-4 template):
After:
PARSE OK.Fix
In
parseConstant, after reading a string literal, loop and merge any run of consecutive string literals into a single constant — matching Jinja2/Python semantics.parseString()already skips leading whitespace and returnsnullptrwhen the next token is not a string literal, so the loop terminates naturally and does not affect any other expression form.Test
Adds
OrtxTokenizerTest.AdjacentStringLiteralConcatenation, covering single-line ("Hello, " "world" '!'), cross-newline, and mixed-quote concatenation. Also verified existing behavior is unaffected (explicit~concat, numbers, conditionals) via a standalone render harness.