Skip to content

Commit 25e46c8

Browse files
authored
Merge pull request #169 from johnoliver/fix-handlebars
Allow Handlebars templates to extract fields from Java beans
2 parents 7a6a538 + b1cb057 commit 25e46c8

File tree

5 files changed

+67
-14
lines changed

5 files changed

+67
-14
lines changed

semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService;
2121
import com.microsoft.semantickernel.services.textcompletion.TextGenerationService;
2222
import java.util.ArrayList;
23+
import java.util.Collections;
2324
import java.util.HashMap;
2425
import java.util.List;
2526
import java.util.Map;
@@ -439,6 +440,7 @@ public KernelFunction<T> build() {
439440
name,
440441
template,
441442
templateFormat,
443+
Collections.emptySet(),
442444
description,
443445
inputVariables,
444446
outputVariable,

semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java

+37-8
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
import java.util.ArrayList;
1212
import java.util.Collections;
1313
import java.util.HashMap;
14+
import java.util.HashSet;
1415
import java.util.List;
1516
import java.util.Map;
1617
import java.util.Objects;
18+
import java.util.Set;
1719
import javax.annotation.Nullable;
1820

1921
/**
@@ -43,6 +45,7 @@ public class PromptTemplateConfig {
4345
@Nullable
4446
private final String template;
4547
private final String templateFormat;
48+
private final Set<PromptTemplateOption> promptTemplateOptions;
4649
@Nullable
4750
private final String description;
4851
private final List<InputVariable> inputVariables;
@@ -61,6 +64,7 @@ protected PromptTemplateConfig(String template) {
6164
DEFAULT_CONFIG_NAME,
6265
template,
6366
SEMANTIC_KERNEL_TEMPLATE_FORMAT,
67+
Collections.emptySet(),
6468
"",
6569
Collections.emptyList(),
6670
new OutputVariable(String.class.getName(), "out"),
@@ -70,21 +74,23 @@ protected PromptTemplateConfig(String template) {
7074
/**
7175
* Constructor for a prompt template config
7276
*
73-
* @param schema Schema version
74-
* @param name Name of the template
75-
* @param template Template string
76-
* @param templateFormat Template format
77-
* @param description Description of the template
78-
* @param inputVariables Input variables
79-
* @param outputVariable Output variable
80-
* @param executionSettings Execution settings
77+
* @param schema Schema version
78+
* @param name Name of the template
79+
* @param template Template string
80+
* @param templateFormat Template format
81+
* @param promptTemplateOptions Prompt template options
82+
* @param description Description of the template
83+
* @param inputVariables Input variables
84+
* @param outputVariable Output variable
85+
* @param executionSettings Execution settings
8186
*/
8287
@JsonCreator
8388
public PromptTemplateConfig(
8489
@JsonProperty("schema") int schema,
8590
@Nullable @JsonProperty("name") String name,
8691
@Nullable @JsonProperty("template") String template,
8792
@Nullable @JsonProperty(value = "template_format", defaultValue = SEMANTIC_KERNEL_TEMPLATE_FORMAT) String templateFormat,
93+
@Nullable @JsonProperty(value = "prompt_template_options") Set<PromptTemplateOption> promptTemplateOptions,
8894
@Nullable @JsonProperty("description") String description,
8995
@Nullable @JsonProperty("input_variables") List<InputVariable> inputVariables,
9096
@Nullable @JsonProperty("output_variable") OutputVariable outputVariable,
@@ -96,6 +102,10 @@ public PromptTemplateConfig(
96102
templateFormat = SEMANTIC_KERNEL_TEMPLATE_FORMAT;
97103
}
98104
this.templateFormat = templateFormat;
105+
if (promptTemplateOptions == null) {
106+
promptTemplateOptions = new HashSet<>();
107+
}
108+
this.promptTemplateOptions = promptTemplateOptions;
99109
this.description = description;
100110
if (inputVariables == null) {
101111
this.inputVariables = new ArrayList<>();
@@ -127,6 +137,7 @@ protected PromptTemplateConfig(
127137
@Nullable String name,
128138
@Nullable String template,
129139
@Nullable String templateFormat,
140+
@Nullable Set<PromptTemplateOption> promptTemplateOptions,
130141
@Nullable String description,
131142
@Nullable List<InputVariable> inputVariables,
132143
@Nullable OutputVariable outputVariable,
@@ -136,6 +147,7 @@ protected PromptTemplateConfig(
136147
name,
137148
template,
138149
templateFormat,
150+
promptTemplateOptions,
139151
description,
140152
inputVariables,
141153
outputVariable,
@@ -152,6 +164,7 @@ public PromptTemplateConfig(PromptTemplateConfig promptTemplate) {
152164
promptTemplate.name,
153165
promptTemplate.template,
154166
promptTemplate.templateFormat,
167+
promptTemplate.promptTemplateOptions,
155168
promptTemplate.description,
156169
promptTemplate.inputVariables,
157170
promptTemplate.outputVariable,
@@ -300,6 +313,15 @@ public int getSchema() {
300313
return schema;
301314
}
302315

316+
/**
317+
* Get the prompt template options of the prompt template config.
318+
*
319+
* @return The prompt template options of the prompt template config.
320+
*/
321+
public Set<PromptTemplateOption> getPromptTemplateOptions() {
322+
return Collections.unmodifiableSet(promptTemplateOptions);
323+
}
324+
303325
/**
304326
* Create a builder for a prompt template config which is a clone of the current object.
305327
*
@@ -358,6 +380,7 @@ public static class Builder {
358380
@Nullable
359381
private String template;
360382
private String templateFormat = SEMANTIC_KERNEL_TEMPLATE_FORMAT;
383+
private final Set<PromptTemplateOption> promptTemplateOptions = new HashSet<>();
361384
@Nullable
362385
private String description = null;
363386
private List<InputVariable> inputVariables = new ArrayList<>();
@@ -433,6 +456,11 @@ public Builder withTemplateFormat(String templateFormat) {
433456
return this;
434457
}
435458

459+
public Builder addPromptTemplateOption(PromptTemplateOption option) {
460+
promptTemplateOptions.add(option);
461+
return this;
462+
}
463+
436464
/**
437465
* Set the inputVariables of the prompt template config.
438466
*
@@ -477,6 +505,7 @@ public PromptTemplateConfig build() {
477505
name,
478506
template,
479507
templateFormat,
508+
promptTemplateOptions,
480509
description,
481510
inputVariables,
482511
outputVariable,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
package com.microsoft.semantickernel.semanticfunctions;
3+
4+
public enum PromptTemplateOption {
5+
/**
6+
* Allow methods on objects provided as arguments to an invocation, to be invoked when rendering
7+
* a template and its return value used. Typically, this would be used to call a getter on an
8+
* object i.e. {@code {{#each users}} {{userName}} {{/each}} } on a handlebars template will
9+
* call the method {@code getUserName()} on each object in {@code users}.
10+
* <p>
11+
* WARNING: If this option is used, ensure that your template is trusted, and that objects added
12+
* as arguments to an invocation, do not contain methods that are unsafe to be invoked when
13+
* rendering a template.
14+
*/
15+
ALLOW_CONTEXT_VARIABLE_METHOD_CALLS_UNSAFE
16+
}

semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.github.jknack.handlebars.Helper;
1010
import com.github.jknack.handlebars.Options;
1111
import com.github.jknack.handlebars.ValueResolver;
12+
import com.github.jknack.handlebars.context.JavaBeanValueResolver;
1213
import com.microsoft.semantickernel.Kernel;
1314
import com.microsoft.semantickernel.contextvariables.ContextVariable;
1415
import com.microsoft.semantickernel.contextvariables.ContextVariableType;
@@ -21,6 +22,7 @@
2122
import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments;
2223
import com.microsoft.semantickernel.semanticfunctions.PromptTemplate;
2324
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig;
25+
import com.microsoft.semantickernel.semanticfunctions.PromptTemplateOption;
2426
import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent;
2527
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2628
import java.io.IOException;
@@ -35,7 +37,6 @@
3537
import java.util.stream.Collectors;
3638
import javax.annotation.Nonnull;
3739
import javax.annotation.Nullable;
38-
import org.apache.commons.text.StringEscapeUtils;
3940
import reactor.core.publisher.Mono;
4041

4142
/**
@@ -168,7 +169,7 @@ public Set<Entry<String, Object>> propertySet(Object context) {
168169
}
169170
}
170171

171-
private static class HandleBarsPromptTemplateHandler {
172+
private class HandleBarsPromptTemplateHandler {
172173

173174
private final String template;
174175
private final Handlebars handlebars;
@@ -181,7 +182,7 @@ public HandleBarsPromptTemplateHandler(
181182
this.template = template;
182183
this.handlebars = new Handlebars();
183184
this.handlebars
184-
.registerHelper("message", HandleBarsPromptTemplateHandler::handleMessage)
185+
.registerHelper("message", this::handleMessage)
185186
.registerHelper("each", handleEach(context))
186187
.with(EscapingStrategy.XML);
187188

@@ -190,7 +191,7 @@ public HandleBarsPromptTemplateHandler(
190191
// TODO: 1.0 Add more helpers
191192
}
192193

193-
private static Helper<Object> handleEach(InvocationContext invocationContext) {
194+
private Helper<Object> handleEach(InvocationContext invocationContext) {
194195
return (context, options) -> {
195196
if (context instanceof ContextVariable) {
196197
return ((ContextVariable<?>) context)
@@ -227,7 +228,7 @@ private static Helper<Object> handleEach(InvocationContext invocationContext) {
227228
}
228229

229230
@Nullable
230-
private static CharSequence handleMessage(Object context, Options options)
231+
private CharSequence handleMessage(Object context, Options options)
231232
throws IOException {
232233
String role = options.hash("role");
233234
String content = (String) options.fn(context);
@@ -258,7 +259,10 @@ public Mono<String> render(KernelFunctionArguments variables) {
258259
resolvers.add(new MessageResolver());
259260
resolvers.add(new ContextVariableResolver());
260261

261-
// resolvers.addAll(ValueResolver.defaultValueResolvers());
262+
if (promptTemplate.getPromptTemplateOptions()
263+
.contains(PromptTemplateOption.ALLOW_CONTEXT_VARIABLE_METHOD_CALLS_UNSAFE)) {
264+
resolvers.add(JavaBeanValueResolver.INSTANCE);
265+
}
262266

263267
Context context = Context
264268
.newBuilder(variables)

semantickernel-api/src/test/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfigTest.java

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import com.microsoft.semantickernel.orchestration.PromptExecutionSettings;
77
import java.util.Arrays;
8+
import java.util.Collections;
89
import java.util.HashMap;
910
import java.util.List;
1011
import org.junit.jupiter.api.Test;
@@ -28,6 +29,7 @@ void testInstanceMadeWithBuilderEqualsInstanceMadeWithConstructor() {
2829
name,
2930
template,
3031
"semantic-kernel",
32+
Collections.emptySet(),
3133
description,
3234
inputVariables,
3335
outputVariable,

0 commit comments

Comments
 (0)