Skip to content

Commit 5707ab5

Browse files
Issue #459: Add Mustache Scripting Support for knn_vector Fields
Signed-off-by: Abhinav Tripathi <[email protected]>
1 parent 49ff9a8 commit 5707ab5

File tree

5 files changed

+372
-0
lines changed

5 files changed

+372
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
77
## [Unreleased 3.3](https://github.com/opensearch-project/k-NN/compare/main...HEAD)
88
### Refactoring
99
* Refactored the KNN Stat files for better readability.
10+
### Enhancements
11+
* Added mustache template support for knn [#2807](https://github.com/opensearch-project/k-NN/pull/2807)

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ dependencies {
349349
compileOnly "org.opensearch:protobufs:0.6.0"
350350
compileOnly group: 'com.google.guava', name: 'failureaccess', version:'1.0.2'
351351
compileOnly group: 'com.google.guava', name: 'guava', version:'33.2.1-jre'
352+
// Mustache support - only core mustache library, OpenSearch classes available at runtime
353+
compileOnly "com.github.spullara.mustache.java:compiler:0.9.14"
352354
api group: 'commons-lang', name: 'commons-lang', version: '2.6'
353355
testFixturesImplementation "org.opensearch.test:framework:${opensearch_version}"
354356
// Add Guava for test configurations since it's needed for testing but excluded from runtime

src/main/java/org/opensearch/knn/plugin/KNNPlugin.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import org.opensearch.knn.plugin.rest.RestSearchModelHandler;
6060
import org.opensearch.knn.plugin.rest.RestTrainModelHandler;
6161
import org.opensearch.knn.plugin.script.KNNScoringScriptEngine;
62+
import org.opensearch.knn.plugin.script.KNNMustacheEngine;
6263
import org.opensearch.knn.plugin.search.KNNConcurrentSearchRequestDecider;
6364
import org.opensearch.knn.plugin.stats.KNNStats;
6465
import org.opensearch.knn.plugin.transport.ClearCacheAction;
@@ -107,6 +108,7 @@
107108
import org.opensearch.rest.RestHandler;
108109
import org.opensearch.script.ScriptContext;
109110
import org.opensearch.script.ScriptEngine;
111+
import org.opensearch.script.TemplateScript;
110112
import org.opensearch.script.ScriptService;
111113
import org.opensearch.search.deciders.ConcurrentSearchRequestDecider;
112114
import org.opensearch.threadpool.ExecutorBuilder;
@@ -380,6 +382,11 @@ public void onIndexModule(IndexModule indexModule) {
380382
*/
381383
@Override
382384
public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts) {
385+
// Check if this is specifically a template script request
386+
if (contexts.size() == 1 && contexts.contains(TemplateScript.CONTEXT)) {
387+
return new KNNMustacheEngine();
388+
}
389+
// Default to scoring engine for other contexts
383390
return new KNNScoringScriptEngine();
384391
}
385392

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.knn.plugin.script;
7+
8+
import com.github.mustachejava.DefaultMustacheFactory;
9+
import com.github.mustachejava.Mustache;
10+
import com.github.mustachejava.MustacheException;
11+
import org.opensearch.script.GeneralScriptException;
12+
import org.opensearch.script.ScriptContext;
13+
import org.opensearch.script.ScriptEngine;
14+
import org.opensearch.script.ScriptException;
15+
import org.opensearch.script.TemplateScript;
16+
import org.opensearch.knn.plugin.stats.KNNCounter;
17+
import org.apache.logging.log4j.LogManager;
18+
import org.apache.logging.log4j.Logger;
19+
20+
import java.io.StringReader;
21+
import java.io.StringWriter;
22+
import java.util.Collections;
23+
import java.util.Map;
24+
import java.util.Set;
25+
26+
/**
27+
* Mustache script engine for k-NN query templating.
28+
*/
29+
public class KNNMustacheEngine implements ScriptEngine {
30+
31+
public static final String NAME = "knn-mustache";
32+
private static final Logger logger = LogManager.getLogger(KNNMustacheEngine.class);
33+
34+
@Override
35+
public String getType() {
36+
return NAME;
37+
}
38+
39+
@Override
40+
public <T> T compile(String templateName, String templateSource, ScriptContext<T> context, Map<String, String> options) {
41+
KNNCounter.SCRIPT_COMPILATIONS.increment();
42+
43+
if (!context.instanceClazz.equals(TemplateScript.class)) {
44+
KNNCounter.SCRIPT_COMPILATION_ERRORS.increment();
45+
throw new IllegalArgumentException("knn-mustache engine only supports TemplateScript context, got [" + context.name + "]");
46+
}
47+
48+
try (StringReader reader = new StringReader(templateSource)) {
49+
DefaultMustacheFactory factory = new DefaultMustacheFactory();
50+
Mustache template = factory.compile(reader, templateName != null ? templateName : "knn-template");
51+
52+
TemplateScript.Factory compiled = params -> new KNNMustacheTemplateScript(template, params);
53+
return context.factoryClazz.cast(compiled);
54+
} catch (MustacheException ex) {
55+
KNNCounter.SCRIPT_COMPILATION_ERRORS.increment();
56+
logger.error("Failed to compile k-NN Mustache template [{}]: {}", templateName, ex.getMessage());
57+
throw new ScriptException(ex.getMessage(), ex, Collections.emptyList(), templateSource, NAME);
58+
}
59+
}
60+
61+
@Override
62+
public Set<ScriptContext<?>> getSupportedContexts() {
63+
return Collections.singleton(TemplateScript.CONTEXT);
64+
}
65+
66+
/**
67+
* Template script implementation for k-NN query generation.
68+
*/
69+
private static class KNNMustacheTemplateScript extends TemplateScript {
70+
private final Mustache template;
71+
private final Map<String, Object> params;
72+
73+
KNNMustacheTemplateScript(Mustache template, Map<String, Object> params) {
74+
super(params);
75+
this.template = template;
76+
this.params = params;
77+
}
78+
79+
@Override
80+
public String execute() {
81+
StringWriter writer = new StringWriter();
82+
try {
83+
template.execute(writer, params);
84+
return writer.toString();
85+
} catch (Exception e) {
86+
logger.error("Error executing k-NN mustache template: {}", e.getMessage());
87+
throw new GeneralScriptException("Error executing k-NN mustache template: " + e.getMessage(), e);
88+
}
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)