Repository navigation
Give each Minja macro call its own variable scope - #1130
Open
Joel Stidley (joelst) wants to merge 4 commits into
Open
Joel Stidley (joelst) wants to merge 4 commits into
Joel Stidley (joelst) wants to merge 4 commits into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
This was referenced Oct 4, 2026
Macro calls rendered directly into the defining context, so parameters and `set` statements inside a macro overwrote same-named outer variables and recursive calls clobbered their callers' arguments. Gemma 4's chat template hits this: `strip_thinking` reassigns `ns`, hiding the outer tool-response state and producing an extra `<|turn>model` generation prompt. Create a child context per call, matching Jinja2 and upstream google/minja. Namespace attribute writes still reach outer namespaces via parent lookup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4ca47b75-18b7-4168-b67d-aae8529ba961
Joel Stidley (joelst)
force-pushed
the
fix/minja-macro-scope
branch
from
October 4, 2026 07:16
e1c42bc to
8aeadf3
Compare
Joel Stidley (joelst)
marked this pull request as ready for review
October 4, 2026 07:23
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The focused implementation matches the intended semantics and is covered by relevant regressions.
Review effort: Balanced
Findings: None
What changed in this PR
Fixes Gemma 4’s extra generation prompt by aligning Minja macro scoping with Jinja2 and upstream Minja.
Changes:
- Creates an isolated child context for each macro invocation.
- Adds regression coverage for macro scope, recursion, namespace mutation, defaults, and Gemma 4 tool responses.
| File | Description |
|---|---|
shared/api/minja.hpp |
Isolates macro-call variables from their defining context. |
test/pp_api_test/test_tokenizer_chat.cc |
Tests macro semantics and Gemma 4 output. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Collaborator
|
/azp run onnxruntime-extensions.ci |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Evaluate defaults in parameter order within the macro call context, allowing later defaults to resolve bound parameters without seeing unrelated caller locals. This follows Jinja macro semantics and intentionally diverges from upstream google/minja's caller-context evaluation; regressions cover positional, keyword, defaulted, and expression arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f7c802aa-8c9c-490d-a569-b6e4cda98adf
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.

Independent of #1129. The two PRs can merge in either order and merge cleanly together. This PR fixes the second Gemma 4 symptom from #1128: an extra
<|turn>modelafter tool responses.Minimal repro (Jinja2 vs. ORT Extensions before this fix)
{% set ns = namespace(v='outer') %}{% macro m() %}{% set ns = namespace(v='inner') %}{{ ns.v }}{% endmacro %}{{ m() }}|{{ ns.v }}inner|outerinner|inner{% set x = 'outer' %}{% macro m(x) %}{{ x }}{% endmacro %}{{ m('arg') }}|{{ x }}arg|outerarg|arg{% macro r(n, tag) %}{% if n > 0 %}{{ r(n - 1, 'inner') }}{% endif %}{{ tag }}{% endmacro %}{{ r(2, 'outer') }}innerinnerouterinnerinnerinnerChange
One line in
shared/api/minja.hpp: each macro call renders in a child context of the defining context instead of the defining context itself. This matches Jinja2 semantics and upstreamgoogle/minja(execution_context = Context::make(Value::object(), macro_context)).Scope / behavior
{% set %}inside a macro no longer overwrite outer variables. Recursive macros no longer overwrite their callers' arguments.{% set ns.attr = ... %}inside a macro still mutates an outer namespace, becausensis resolved through parent lookup. Default parameter values are still evaluated per call. Templates that don't use macros are unaffected.Validation
OrtxTokenizerTest.MinjaMacroCallsUseLocalScope: the 3 scope assertions fail before the fix (inner|inner,arg|arg,innerinnerinner) and pass after. The namespace-mutation and default-argument assertions pass both before and after (guarding against over-isolation).OrtxTokenizerTest.Gemma4ToolResponseSuppressesGenerationPrompt, which uses the Gemma 4 template already onmain, with the expected string taken from Jinja2. It fails before the fix (trailing<|turn>model) and passes after.OrtxTokenizerTest.*tests pass (89/89) on macOS arm64.rel-0.17.1built against this change (viaFETCHCONTENT_SOURCE_DIR_ONNXRUNTIME_EXTENSIONS) renders the Gemma 4 E2B template byte-identical to Jinja2 for user+tools, no-tools, and tool-call history (when combined with Preserve Gemma 4 tool definitions and add Minja upper filter #1129).rel-0.17.1. 356 of the 360 renders were byte-identical. The only 4 differences are Gemma 4 tool-call-history cases, which now match Jinja2. Only 7 of the 45 templates use{% macro %}at all.Reviewer questions
MacroNodecaptures[&]while upstream captures[this, macro_context]by value. Fold that hardening in here or track it separately?