Add missing .replace, .upper, .lower string methods to vendored minja#1082
Closed
samuel100 wants to merge 4 commits into
Closed
Add missing .replace, .upper, .lower string methods to vendored minja#1082samuel100 wants to merge 4 commits into
samuel100 wants to merge 4 commits into
Conversation
The vendored copy of minja in `shared/api/minja.hpp` was missing handlers
for several standard Jinja string methods that current upstream
`google/minja` supports. The most impactful gap is `.replace()`, which
caused `OrtxApplyChatTemplate` to throw `Unknown method: replace` on
chat templates shipped with widely-used Hugging Face models (e.g.
`smollm3-3b`'s `chat_template.jinja` uses
`system_message.replace("/no_think", "").replace("/think", "").rstrip()`).
This adds the three missing string-method handlers — `.replace`,
`.upper`, `.lower` — to the string-method dispatch in
`MethodCallExpr::do_evaluate`, with semantics matching upstream minja
and Python's `str` methods:
- `.replace(before, after[, count])` performs left-to-right substring
replacement, defaulting to all occurrences when no count is given.
An empty `before` returns the original string (matches upstream).
- `.upper()` / `.lower()` apply `std::toupper` / `std::tolower` via
`std::transform` with an `unsigned char` lambda to avoid UB on
signed-char platforms.
Tests
-----
Adds `MinjaStringReplace` and `MinjaStringUpperLower` regression tests
under `test/pp_api_test/test_tokenizer_chat.cc`, following the same
inline-template pattern introduced in microsoft#1070
(`MinjaStringSliceOOBClamped`). Coverage includes:
- single and chained `.replace()`
- replace with optional `count` argument
- replace with empty `before` (no-op)
- replace with no match (unchanged)
- `.upper()` and `.lower()` on mixed-case input
Verified locally with a standalone harness that compiles `minja.hpp`
directly and renders the exact failing line from
`smollm3-3b`'s `chat_template.jinja` — previously thrown with
`Unknown method: replace`, now renders correctly.
Fixes microsoft#1081
Refs microsoft/Foundry-Local#800
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds missing string methods to the vendored minja parser to support common HF chat templates and introduces regression tests to prevent future breakage.
Changes:
- Implemented
upper(),lower(), andreplace()string methods in minja. - Added tokenizer chat-template regression tests covering these methods.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| test/pp_api_test/test_tokenizer_chat.cc | Adds regression tests ensuring templates using .replace()/.upper()/.lower() render successfully. |
| shared/api/minja.hpp | Implements the missing minja string methods used by popular chat templates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- replace(): treat count < 0 as "replace all" to match Python str.replace semantics (previously a negative count silently produced zero replacements via `count-- > 0`). - upper() / lower(): switch from std::toupper / std::tolower to an explicit ASCII-only mapping. This is deterministic across locales (std::toupper/tolower are locale-dependent) and avoids the trap of per-process locale changing template rendering. Non-ASCII bytes are passed through unchanged, matching upstream minja behavior on most locales. Full Unicode case folding is out of scope. - Test helper: assert OrtxTensorResultGetAt and OrtxGetTensorData return kOrtxOK and that the data pointer is non-null, so test failures point at the actual cause rather than a downstream EXPECT_STREQ on undefined memory. - New regression tests for replace count=-1 (replace all) and count=0 (no replacements) to lock in the Python semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kunal-vaishnavi
previously approved these changes
Jun 12, 2026
…tring-methods # Conflicts: # test/pp_api_test/test_tokenizer_chat.cc
kunal-vaishnavi
approved these changes
Jun 15, 2026
Contributor
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
baijumeswani
approved these changes
Jun 16, 2026
Collaborator
Author
|
Closing because this PR originates from a fork and the required pipeline credentials/ACR access are unavailable for fork-based runs. Please recreate from a branch in microsoft/onnxruntime-extensions once write access is available. |
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.
Summary
Adds the three missing string-method handlers —
.replace,.upper,.lower— to the vendored minja parser atshared/api/minja.hpp. These are standard Jinja2 string methods that Hugging Facetransformerschat templates rely on, and the most impactful gap (.replace) currently breaks any model whosechat_template.jinjauses it — most visiblysmollm3-3b, whereOrtxApplyChatTemplatethrows:Fixes #1081
Refs microsoft/Foundry-Local#800
Diff against upstream
Comparing
shared/api/minja.hppagainst current upstreamgoogle/minja:.strip/.rstrip/.lstrip.split.title.endswith/.startswith/.capitalize.replace.upper.lower(Note:
lowerwas already available as a global filter —{{ x \| lower }}— but not as the method formx.lower()that HF templates actually use.)Implementation notes
Handlers were added in
MethodCallExpr::do_evaluatenext to the existing string-method dispatch:.replace(before, after[, count])— left-to-right substring replacement matching Python'sstr.replaceand upstream minja. Defaults to all occurrences whencountis omitted. Emptybeforereturns the string unchanged (matches upstream behavior; avoids an infinite loop)..upper()/.lower()—std::transformwith anunsigned char-typed lambda aroundstd::toupper/std::tolowerto avoid undefined behavior on signed-char platforms.I deliberately limited the scope of this PR to the methods needed to unblock real HF templates today; a full re-sync of
minja.hppagainst current upstream would also be reasonable but is out of scope here.Tests
Adds two regression tests in
test/pp_api_test/test_tokenizer_chat.cc, following the inline-template_strpattern introduced in #1070 (MinjaStringSliceOOBClamped):MinjaStringReplace— single replace, chained replace + rstrip (the exact smollm3-3b pattern), three-argcount, emptybefore, no-match.MinjaStringUpperLower—.upper()and.lower()on mixed-case input, plus idempotent cases.Local verification
I verified the fix end-to-end with a small standalone harness that compiles
shared/api/minja.hppdirectly and renders the exact failing line fromsmollm3-3b'schat_template.jinja:I did not build the full
pp_api_testbinary locally (would require ORT + sentencepiece + onnxruntime-extensions deps setup on macOS); the new gtest cases are written to existing conventions and will run in CI.Follow-up (out of scope here)
It would be worth adding a periodic re-sync of
shared/api/minja.hppwith current upstreamgoogle/minja, and/or a regression suite that renders the chat templates of a handful of popular HF models (SmolLM3, Qwen3, Llama-3.x, Phi-4) againstOrtxApplyChatTemplate, to catch this kind of drift earlier. I'm happy to follow up with that in a separate PR if maintainers want.