Skip to content

Conversation

night-owl-1709
Copy link
Contributor

@night-owl-1709 night-owl-1709 commented Jul 28, 2025

Summary

This PR introduces Mustache templating support for k-NN queries by implementing a dedicated KNNMustacheEngine that enables dynamic query generation through the Search Template API. This enhancement allows users to create reusable k-NN query templates with parameterized field names, vectors, and search parameters.

Issues

Implements #459

Changes Made

1. New Script Engine Implementation

  • File: src/main/java/org/opensearch/knn/plugin/script/KNNMustacheEngine.java
  • Created a new script engine that implements ScriptEngine interface
  • Supports only TemplateScript.CONTEXT for query templating
  • Integrates with existing k-NN statistics system through KNNCounter
  • Uses standard Mustache Java library for template compilation and execution

2. Plugin Integration

  • Modified: Integration in KNNPlugin.getScriptEngine() method
  • Added logic to return KNNMustacheEngine when TemplateScript.CONTEXT is requested
  • Maintains backward compatibility with existing KNNScoringScriptEngine
  • Added import for the new engine class

3. Build Configuration

  • Modified: build.gradle
  • Added Mustache Java library dependency: com.github.spullara.mustache.java:compiler:0.9.10

How Mustache Integration Works

The integration follows OpenSearch's plugin architecture patterns and works through these components:

  1. Script Engine Registration: The engine is registered through KNNPlugin.getScriptEngine() and responds to TemplateScript.CONTEXT requests
  2. Template Compilation: Uses DefaultMustacheFactory to compile template strings into executable Mustache objects
  3. Parameter Substitution: Replaces template variables (e.g., {{field}}, {{vector}}, {{k}}) with actual values from the params map
  4. Query Generation: Produces complete k-NN queries that are processed by the standard k-NN query pipeline

Usage Examples

Basic k-NN Query Template

POST /products/_search/template
{
  "source": {
    "knn": {
      "{{field_name}}": {
        "vector": {{query_vector}},
        "k": {{k_value}}
      }
    }
  },
  "params": {
    "field_name": "product_embedding",
    "query_vector": [0.2, 0.8, 0.1, 0.9, 0.3],
    "k_value": 10
  }
}

k-NN Query with Conditional Parameters

POST /products/_search/template
{
  "source": {
    "knn": {
      "{{field_name}}": {
        "vector": {{query_vector}},
        "k": {{k_value}}{{#max_distance}},
        "max_distance": {{max_distance}}{{/max_distance}}
      }
    }
  },
  "params": {
    "field_name": "product_embedding",
    "query_vector": [0.2, 0.8, 0.1, 0.9, 0.3],
    "k_value": 10,
    "max_distance": 0.8
  }
}

k-NN Query with Filter Template

POST /products/_search/template
{
  "source": {
    "query": {
      "knn": {
        "{{vector_field}}": {
          "vector": {{query_vector}},
          "k": {{k_value}},
          "filter": {
            "term": {
              "{{filter_field}}": "{{filter_value}}"
            }
          }
        }
      }
    }
  },
  "params": {
    "vector_field": "product_embedding",
    "query_vector": [0.2, 0.8, 0.1, 0.9, 0.3],
    "k_value": 10,
    "filter_field": "category",
    "filter_value": "electronics"
  }
}

Benefits

  1. Dynamic Query Generation: Enables parameterized k-NN queries for different use cases
  2. Template Reusability: Single templates can be used with different parameters
  3. Integration with Existing Architecture: Leverages OpenSearch's Search Template API
  4. Statistics Tracking: Integrates with k-NN plugin's existing statistics system
  5. Error Handling: Provides comprehensive error handling for template compilation and execution

Testing

  • Unit tests for KNNMustacheEngine compilation and execution
  • Integration tests with actual k-NN queries using templates
  • Error handling tests for malformed templates and invalid parameters

Backward Compatibility

This change is fully backward compatible:

  • Existing k-NN queries continue to work unchanged
  • The KNNScoringScriptEngine remains the default for script scoring contexts
  • No changes to existing k-NN query processing pipeline

@jmazanec15
Copy link
Member

Thanks @night-owl-1709 - will take a look!

@jmazanec15
Copy link
Member

@night-owl-1709 One question: for painless, we extend the lang-painless plugin and use SPI to extend it. Is that not possible with expression and mustache?

@night-owl-1709 night-owl-1709 force-pushed the feature/knn-scripting-support branch from eb40c52 to 23d7779 Compare July 29, 2025 03:16
@jmazanec15
Copy link
Member

Instead of treating knn_l2_distance like a value (and the other distance methods as values), is it possible to treat them like functions in painless? For example:

GET my-knn-index-2/_search
{
  "size": 2,
  "query": {
    "script_score": {
      "query": {
        "bool": {
          "filter": {
            "term": {
              "color": "BLUE"
            }
          }
        }
      },
      "script": {
        "source": "1.0 + cosineSimilarity(params.query_value, doc[params.field])",
        "params": {
          "field": "my_vector",
          "query_value": [9.9, 9.9]
        }
      }
    }
  }
}

Im not sure if this is possible, but I feel the distance functions should be somewhat standardized, similar to operations like "+".

@night-owl-1709 night-owl-1709 force-pushed the feature/knn-scripting-support branch 4 times, most recently from fa08347 to ae3b35c Compare August 2, 2025 08:41
@night-owl-1709 night-owl-1709 changed the title Issue #459: Add Mustache and Expression scripting support for knn_vector fields Issue #459: Add Mustache and Function-Based Scripting Support for knn_vector Fields Aug 2, 2025
@night-owl-1709 night-owl-1709 changed the title Issue #459: Add Mustache and Function-Based Scripting Support for knn_vector Fields Issue #459: Add Mustache Scripting Support for knn_vector Fields Aug 2, 2025
@night-owl-1709 night-owl-1709 force-pushed the feature/knn-scripting-support branch 6 times, most recently from c2ecf16 to 046b25a Compare August 4, 2025 16:54
@night-owl-1709 night-owl-1709 force-pushed the feature/knn-scripting-support branch from 046b25a to 4601e68 Compare August 4, 2025 17:23
Copy link
Member

@jmazanec15 jmazanec15 left a comment

Choose a reason for hiding this comment

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

Just want to

@night-owl-1709 night-owl-1709 force-pushed the feature/knn-scripting-support branch 2 times, most recently from a67ba8b to 1cfa160 Compare August 5, 2025 08:59
@jmazanec15
Copy link
Member

@Vikasht34, @shatejas to review as well

@night-owl-1709 night-owl-1709 force-pushed the feature/knn-scripting-support branch 5 times, most recently from 58d8150 to 3080d8e Compare August 10, 2025 10:17
@night-owl-1709 night-owl-1709 force-pushed the feature/knn-scripting-support branch from 3080d8e to 5707ab5 Compare August 10, 2025 10:25
public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts) {
// Check if this is specifically a template script request
if (contexts.size() == 1 && contexts.contains(TemplateScript.CONTEXT)) {
return new KNNMustacheEngine();
Copy link
Collaborator

@shatejas shatejas Aug 14, 2025

Choose a reason for hiding this comment

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

I was wondering if plugin should reuse the existing implementation of mustache engine. The plugin will probably need a dependency on lang-mustache module. @night-owl-1709 have you explored the approach? were there any problems with it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I checked but we do not have lang-mustache support yet like we have for painless in opensearch.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure if I understand this but did you try adding lang-mustache here https://github.com/opensearch-project/k-NN/blob/main/build.gradle#L318

Copy link
Collaborator

Choose a reason for hiding this comment

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

So the implementation here is similar to https://github.com/opensearch-project/OpenSearch/blob/main/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/MustacheScriptEngine.java#L67 with minor changes.

  • This uses default factory compared to custom factory. The compile implementation of both is the same as custom factory inherits from default factory.
  • There are some node metrics that are being added, if core implementation is used, I don't think its needed.

so instead of returning knn mustache engine we can return core implementation of it.

I would prefer using core implementation to be consistent and less maintenance on the plugin. Let me know if I am missing something here

public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts) {
// Check if this is specifically a template script request
if (contexts.size() == 1 && contexts.contains(TemplateScript.CONTEXT)) {
return new KNNMustacheEngine();
Copy link
Collaborator

Choose a reason for hiding this comment

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

So the implementation here is similar to https://github.com/opensearch-project/OpenSearch/blob/main/modules/lang-mustache/src/main/java/org/opensearch/script/mustache/MustacheScriptEngine.java#L67 with minor changes.

  • This uses default factory compared to custom factory. The compile implementation of both is the same as custom factory inherits from default factory.
  • There are some node metrics that are being added, if core implementation is used, I don't think its needed.

so instead of returning knn mustache engine we can return core implementation of it.

I would prefer using core implementation to be consistent and less maintenance on the plugin. Let me know if I am missing something here

public String execute() {
StringWriter writer = new StringWriter();
try {
template.execute(writer, params);
Copy link
Collaborator

Choose a reason for hiding this comment

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

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