Skip to content

feat: Implement Mustache template support for search queries - #342

Merged
heemin32 merged 11 commits into
opensearch-project:mainfrom
Bharathi-Kanna:feature/mustache-template-support
Jul 17, 2026
Merged

heemin32 merged 11 commits into
opensearch-project:mainfrom
Bharathi-Kanna:feature/mustache-template-support

Conversation

@Bharathi-Kanna

@Bharathi-Kanna Bharathi-Kanna commented Dec 11, 2025 •

Copy link
Copy Markdown
Contributor

Description

This PR implements backend support for Mustache templates in search queries using OpenSearch's native
ScriptService

Key Changes:

Integrated Mustache Templating

  • Modified SearchRelevancePlugin.javato capture and pass ScriptService
  • Updated SearchRequestBuilder.java to compile and execute Mustache templates
  • Added automatic detection: queries with {{ use Mustache, others use legacy %SearchText%

Maintained Backward Compatibility

  • All existing %SearchText% queries continue to work without changes
  • No data model modifications
  • No migration required

Example Usage

Mustache Template (New):

{
  "query": {
    "match": { "title": "{{queryText}}" }
  }
}

Legacy Placeholder (Still Works):

{
  "query": {
    "match": { "title": "%SearchText%" }
  }
}

Both syntaxes work side-by-side. The system auto-detects which one to use.

Current Limitation: Single-parameter queries only (matches existing QuerySetEntry as List)
Multi-parameter support (e.g., {{brand}}, {{category}}) requires data model changes and will be addressed in a future PR.

Issues Resolved

Relates to #41

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@epugh epugh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you are on the right path..

@epugh

epugh commented Dec 19, 2025

Copy link
Copy Markdown
Member

@Bharathi-Kanna could you set up signing of yoru commits? WHen you sign with git commit -s -S you will get a green "VERIFIED" mark here:
image

@epugh

epugh commented Dec 19, 2025

Copy link
Copy Markdown
Member

Looks like good progress. We still need some tests for handling it.. Let me know how I can help!

@codecov

codecov Bot commented Dec 21, 2025 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (2fcc381) to head (eeeebec).

Additional details and impacted files
@@     Coverage Diff     @@
##   main   #342   +/-   ##
===========================
===========================

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Bharathi-Kanna added a commit to Bharathi-Kanna/search-relevance that referenced this pull request Dec 28, 2025
- Added 4 test methods to SearchRequestBuilderTests.java
  - testMustacheWithNullScriptService: Validates error handling
  - testLegacyWildcardStillWorks: Confirms backward compatibility
  - testDetectionLogicUsesLegacyForNonMustache: Verifies detection logic
  - testHybridSearchMustacheDetection: Tests hybrid query support
- Updated CHANGELOG.md with feature entry
- All tests passing (13/13 SearchRequestBuilderTests)

Addresses maintainer feedback on PR opensearch-project#342

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
@Bharathi-Kanna
Bharathi-Kanna marked this pull request as ready for review December 28, 2025 10:54
@Bharathi-Kanna

Bharathi-Kanna commented Dec 28, 2025 •

Copy link
Copy Markdown
Contributor Author

Hi @epugh,

Thanks for the feedback! I've added:

  • Test methods
  • DCO sign-off on commits
  • CHANGELOG entry

This PR enables Mustache templating with {{query_string}} support.

Would you prefer this incremental approach, or include data model changes in this PR?

@Bharathi-Kanna
Bharathi-Kanna requested a review from epugh December 28, 2025 11:36
Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
- Added 4 test methods to SearchRequestBuilderTests.java
  - testMustacheWithNullScriptService: Validates error handling
  - testLegacyWildcardStillWorks: Confirms backward compatibility
  - testDetectionLogicUsesLegacyForNonMustache: Verifies detection logic
  - testHybridSearchMustacheDetection: Tests hybrid query support
- Updated CHANGELOG.md with feature entry
- All tests passing (13/13 SearchRequestBuilderTests)

Addresses maintainer feedback on PR opensearch-project#342

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
@Bharathi-Kanna
Bharathi-Kanna force-pushed the feature/mustache-template-support branch from 9edb7eb to 7039e8b Compare December 28, 2025 11:38
Signed-off-by: Bharathi Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
@heemin32

heemin32 commented Jun 10, 2026 •

Copy link
Copy Markdown
Collaborator

This is good but I think we could extend this further and support multiple custom variables instead of single fixed query text.

Example 1

{
    "query": {
      "bool": {
        "must": {
          "match": { "title": "{{query_string}}" }
        },
        "filter": [
          { "term": { "status": "{{status}}" } },
          { "term": { "category": "{{category}}" } }
        ]
      }
    }
  }

Example 2

{
    "query": {
      "bool": {
        "must": {
          "match": { "title": "{{query_string}}" }
        },
        "filter": [ {{filter}} ]
      }
    }
  }

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
…ields

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
@Bharathi-Kanna

Copy link
Copy Markdown
Contributor Author

Hi @heemin32 !

Thank you for the feedback, I completely agree.

I've just pushed a major update to this PR that fully implements what you described!
Users can now define their queries with arbitrary keys like this:

{
  "queryText": "laptop",
  "status": "active",
  "category": "electronics"
}

And those values will automatically hydrate the exact Mustache template configurations you provided in your examples (e.g., {{status}}, {{category}}).

To ensure a seamless implementation, I also:
Updated the LLM Judgment processor cache to include the custom fields in the deduplication key so we don't accidentally serve cached judgments for queries that share the same queryText but have different filters.

@heemin32 heemin32 added v.3.8.0 and removed v.3.8.0 labels Jun 26, 2026
@heemin32

heemin32 commented Jun 26, 2026 •

Copy link
Copy Markdown
Collaborator

@Bharathi-Kanna Thanks for the change! By the way, wouldn't it be sufficient to simply use string replacement instead of relying on the script service?

Or at least can we disable partial from mustache?

MustacheFactory factory = new DefaultMustacheFactory(name -> null);

@Bharathi-Kanna

Copy link
Copy Markdown
Contributor Author

Thanks @heemin32 !

On string replacement: I'd prefer to keep ScriptService. It uses OpenSearch's managed Mustache engine, which JSON-escapes values automatically. Plain string replacement would break on quotes and other special characters in the query text, and could also introduce a DSL injection risk.

On partials: You're right, I checked, and OpenSearch's CustomMustacheFactory doesn't disable them. However, the DefaultMustacheFactory(name -> null) approach can't be wired in directly because we go through ScriptService, not the raw Mustache library. Instead, I'll reject templates containing {{> during validation to close that vector while keeping the managed engine. Does that sound reasonable?

@heemin32

Copy link
Copy Markdown
Collaborator

Thanks @heemin32 !

On string replacement: I'd prefer to keep ScriptService. It uses OpenSearch's managed Mustache engine, which JSON-escapes values automatically. Plain string replacement would break on quotes and other special characters in the query text, and could also introduce a DSL injection risk.

On partials: You're right, I checked, and OpenSearch's CustomMustacheFactory doesn't disable them. However, the DefaultMustacheFactory(name -> null) approach can't be wired in directly because we go through ScriptService, not the raw Mustache library. Instead, I'll reject templates containing {{> during validation to close that vector while keeping the managed engine. Does that sound reasonable?

Simple validation won't handle several other edge cases. One example is {{ > partial}} space before >. I think we might delay the support of mustache until OpenSearch core disable partial unless we use Mustache library directly.

@heemin32

Copy link
Copy Markdown
Collaborator

We could use mustache now as OpenSearch core disabled partial template resolution.
opensearch-project/OpenSearch#22438

@heemin32

Copy link
Copy Markdown
Collaborator

@Bharathi-Kanna could you open the corresponding document update pr for this change?

…mplate-support

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>

# Conflicts:
#	CHANGELOG.md
buildCacheKey had been changed to a newline-delimited format
(queryText#\nkey:value), which silently invalidated existing judgment
cache entries on upgrade — the key is used for both writes and reads, so
entries stored in the released JSON format (queryText#{json}) would no
longer be found, forcing needless LLM recomputation. The change also
contradicted the method's own Javadoc and QuerySetEntry.parseLegacyQueryText,
which both expect the JSON format, and was unrelated to the Mustache
feature (SearchRequestBuilder never touches buildCacheKey).

Restore the upstream JSON serialization via OBJECT_MAPPER (already a
dependency in this file). No test changes.

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
Add MustacheTemplateRenderTests, which exercises the real SearchRequestBuilder
render path against a live ScriptService backed by the core MustacheScriptEngine
(the existing SearchRequestBuilderTests only cover the legacy %SearchText% and
null-ScriptService error paths). It deterministically asserts single-variable and
multi-variable substitution, JSON escaping of special characters, empty rendering
of missing variables, and rejection of Mustache partials ({{>...}}) — the latter
verifying OpenSearch core's partial-resolution guard is present.

Pull in lang-mustache-client as a test dependency so the engine is available to
unit tests (the node provides it at runtime).

Fix the multi-variable search-configuration fixture: it referenced an unprovided
{{category_filter}} variable and applied a term filter on the analyzed 'category'
field. Reference the supplied {{category}} custom field and target the
'category_filter' keyword field, using an optional should clause so results are
driven by the title match (the ESCI sample data is not category-filtered).

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
A search configuration whose query is an invalid Mustache template — most
notably one containing an unsupported partial ({{>...}}), which OpenSearch
core now rejects at compile time — caused SearchRequestBuilder to throw
synchronously inside ExperimentTaskManager.executeVariantAsync, before
client.search was invoked. That path never called completeVariantFailure(),
so ExperimentTaskContext.remainingVariants never reached zero and the
experiment hung in PROCESSING indefinitely.

Wrap the request build in a try/catch that routes the failure through the
same handleSearchFailure path used for search errors, so the variant is
counted, the failure is recorded, and the experiment reaches COMPLETED.

Add a regression IT (partial-template search config -> experiment COMPLETED)
plus the supporting fixture and helper.

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

PR Code Analyzer ❗

AI-powered 'Code-Diff-Analyzer' found issues on commit 0a9290b.

PathLineSeverityDescription
build.gradle248highNew dependency added: 'org.opensearch.plugin:lang-mustache-client:${opensearch_version}'. Per mandatory supply chain rule, all dependency additions must be flagged regardless of apparent legitimacy. Maintainers should verify the artifact resolves to the expected OpenSearch core module and that the version variable is pinned as intended.
src/test/resources/searchconfig/CreateSearchConfigurationQueryWithPartial.json4lowTest fixture contains a Mustache partial referencing '/etc/passwd' ({{>/etc/passwd}}). Context indicates this is a defensive security test verifying that partial templates are rejected by the engine. The corresponding test explicitly asserts a 'Partial templates are not supported' failure, making malicious intent implausible, but the path reference is anomalous and warrants maintainer awareness.

The table above displays the top 10 most important findings.

Total: 2 | Critical: 0 | High: 1 | Medium: 0 | Low: 1


Pull Requests Author(s): Please update your Pull Request according to the report above.

Repository Maintainer(s): You can bypass diff analyzer by adding label skip-diff-analyzer after reviewing the changes carefully, then re-run failed actions. To re-enable the analyzer, remove the label, then re-run all actions.


⚠️ Note: The Code-Diff-Analyzer helps protect against potentially harmful code patterns. Please ensure you have thoroughly reviewed the changes beforehand.

Thanks.

@Bharathi-Kanna

Copy link
Copy Markdown
Contributor Author

Hi @heemin32,
I've opened the corresponding documentation PR: opensearch-project/documentation-website#12810

@heemin32

Copy link
Copy Markdown
Collaborator

Hi @heemin32, I've opened the corresponding documentation PR: opensearch-project/documentation-website#12810

Thanks. While I reviewing the document, I found this is a little confusing.

Mustache Template (New):

{
  "query": {
    "match": { "title": "{{query_string}}" }
  }
}

Legacy Placeholder (Still Works):

{
  "query": {
    "match": { "title": "%SearchText%" }
  }
}

Shouldn't we just keep it consistent from query sets field name?

{
  "query": {
    "match": { "title": "{{queryText}}" }
  }
}

@heemin32 heemin32 added v3.8.0 Issues and PRs related to version v3.8.0 and removed v3.8.0 Issues and PRs related to version v3.8.0 labels Jul 17, 2026
The query text was exposed to Mustache templates as {{query_string}}, which did
not match the query set field name (queryText). Custom fields are already
referenced by their field names (for example, {{category}}), so the query text
now follows the same convention and is available as {{queryText}}. Also updates
the processMustacheTemplate Javadoc to document customFields.

Addresses review feedback on opensearch-project#342.

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
Bharathi-Kanna added a commit to Bharathi-Kanna/documentation-website that referenced this pull request Jul 17, 2026
Match the query set field name (queryText) instead of {{query_string}}, per
review feedback on opensearch-project/search-relevance#342.

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
@Bharathi-Kanna

Copy link
Copy Markdown
Contributor Author

Applied and updated all ,Thanks @heemin32 !

@heemin32
heemin32 merged commit bfcbfc4 into opensearch-project:main Jul 17, 2026
43 of 48 checks passed
kolchfa-aws added a commit to opensearch-project/documentation-website that referenced this pull request Jul 29, 2026
)

* Document Mustache template support in Search Relevance Workbench

Search configurations can now use Mustache template variables ({{query_string}}
and query set custom fields) in the query, in addition to the existing
%SearchText% placeholder. Document the templating behavior, variable sources,
automatic JSON escaping, and that Mustache partials are not supported, with an
example. Also document custom fields on query set entries and how they map to
template variables.

Corresponds to opensearch-project/search-relevance#342.

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>

* Use {{queryText}} for the Mustache query variable

Match the query set field name (queryText) instead of {{query_string}}, per
review feedback on opensearch-project/search-relevance#342.

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>

* Doc review

Signed-off-by: Fanit Kolchina <kolchfa@amazon.com>

* Apply suggestion from @kolchfa-aws

Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com>

---------

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
Signed-off-by: Fanit Kolchina <kolchfa@amazon.com>
Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com>
Co-authored-by: Fanit Kolchina <kolchfa@amazon.com>
Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com>
kush992 pushed a commit to kush992/documentation-website that referenced this pull request Aug 13, 2026
…nsearch-project#12810)

* Document Mustache template support in Search Relevance Workbench

Search configurations can now use Mustache template variables ({{query_string}}
and query set custom fields) in the query, in addition to the existing
%SearchText% placeholder. Document the templating behavior, variable sources,
automatic JSON escaping, and that Mustache partials are not supported, with an
example. Also document custom fields on query set entries and how they map to
template variables.

Corresponds to opensearch-project/search-relevance#342.

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>

* Use {{queryText}} for the Mustache query variable

Match the query set field name (queryText) instead of {{query_string}}, per
review feedback on opensearch-project/search-relevance#342.

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>

* Doc review

Signed-off-by: Fanit Kolchina <kolchfa@amazon.com>

* Apply suggestion from @kolchfa-aws

Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com>

---------

Signed-off-by: Bharathi-Kanna <99189546+Bharathi-Kanna@users.noreply.github.com>
Signed-off-by: Fanit Kolchina <kolchfa@amazon.com>
Signed-off-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com>
Co-authored-by: Fanit Kolchina <kolchfa@amazon.com>
Co-authored-by: kolchfa-aws <105444904+kolchfa-aws@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants