From fb599c43bb1aed708984b33e312cd1c4d8cf4989 Mon Sep 17 00:00:00 2001 From: pherklotz Date: Thu, 14 Mar 2024 08:54:25 +0100 Subject: [PATCH] Use default stringtemplate delimiters for prompt templates to avoid escaping json --- .../ai/chat/prompt/PromptTemplate.java | 8 ++--- .../ai/prompt/PromptTemplateTest.java | 33 +++++++++++++++++-- .../ai/prompt/PromptTests.java | 10 +++--- .../antora/modules/ROOT/pages/api/prompt.adoc | 8 ++--- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java index db74d9aaaad..d5b99e56fb8 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java @@ -53,7 +53,7 @@ public PromptTemplate(Resource resource) { throw new RuntimeException("Failed to read resource", ex); } try { - this.st = new ST(this.template, '{', '}'); + this.st = new ST(this.template); } catch (Exception ex) { throw new IllegalArgumentException("The template string is not valid.", ex); @@ -64,7 +64,7 @@ public PromptTemplate(String template) { this.template = template; // If the template string is not valid, an exception will be thrown try { - this.st = new ST(this.template, '{', '}'); + this.st = new ST(this.template); } catch (Exception ex) { throw new IllegalArgumentException("The template string is not valid.", ex); @@ -75,7 +75,7 @@ public PromptTemplate(String template, Map model) { this.template = template; // If the template string is not valid, an exception will be thrown try { - this.st = new ST(this.template, '{', '}'); + this.st = new ST(this.template); for (Entry entry : model.entrySet()) { add(entry.getKey(), entry.getValue()); dynamicModel.put(entry.getKey(), entry.getValue()); @@ -95,7 +95,7 @@ public PromptTemplate(Resource resource, Map model) { } // If the template string is not valid, an exception will be thrown try { - this.st = new ST(this.template, '{', '}'); + this.st = new ST(this.template); for (Entry entry : model.entrySet()) { add(entry.getKey(), entry.getValue()); dynamicModel.put(entry.getKey(), entry.getValue()); diff --git a/spring-ai-core/src/test/java/org/springframework/ai/prompt/PromptTemplateTest.java b/spring-ai-core/src/test/java/org/springframework/ai/prompt/PromptTemplateTest.java index b60fb46a98d..c949021cc71 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/prompt/PromptTemplateTest.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/prompt/PromptTemplateTest.java @@ -42,7 +42,7 @@ public void testRender() { model.put("key3", 100); // Create a simple template with placeholders for keys in the generative - String template = "This is a {key1}, it is {key2}, and it costs {key3}"; + String template = "This is a , it is , and it costs "; PromptTemplate promptTemplate = new PromptTemplate(template, model); // The expected result after rendering the template with the generative @@ -57,6 +57,33 @@ public void testRender() { result = promptTemplate.render(model); assertEquals(expected, result); } + @Test + public void testRenderJson() { + Map model = new HashMap<>(); + model.put("jsonValue", "bar"); + + String template = "{'foo': ''}"; + PromptTemplate promptTemplate = new PromptTemplate(template, model); + + String expected = "{'foo': 'bar'}"; + String result = promptTemplate.render(); + + assertEquals(expected, result); + } + + @Test + public void testRenderEscapedString() { + Map model = new HashMap<>(); + model.put("xmlValue", "bar"); + + String template = "\\\\"; + PromptTemplate promptTemplate = new PromptTemplate(template, model); + + String expected = "bar"; + String result = promptTemplate.render(); + + assertEquals(expected, result); + } @Disabled("Need to improve PromptTemplate to better handle Resource toString and tracking with 'dynamicModel' for underlying StringTemplate") @Test @@ -74,7 +101,7 @@ public void testRenderResource() throws Exception { model.put("key3", resource); // Create a simple template with placeholders for keys in the generative - String template = "{key1}, {key2}, {key3}"; + String template = ", , "; PromptTemplate promptTemplate = new PromptTemplate(template, model); // The expected result after rendering the template with the generative @@ -93,7 +120,7 @@ public void testRenderFailure() { model.put("key1", "value1"); // Create a simple template that includes a key not present in the generative - String template = "This is a {key2}!"; + String template = "This is a !"; PromptTemplate promptTemplate = new PromptTemplate(template, model); // Rendering the template with a missing key should throw an exception diff --git a/spring-ai-core/src/test/java/org/springframework/ai/prompt/PromptTests.java b/spring-ai-core/src/test/java/org/springframework/ai/prompt/PromptTests.java index 4d3ff521757..9bc15719c90 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/prompt/PromptTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/prompt/PromptTests.java @@ -33,7 +33,7 @@ class PromptTests { @Test void newApiPlaygroundTests() { // Create a String, a PromptValue or Messages - String templateText = "Hello '{firstName}' '{lastName}' from Unix"; + String templateText = "Hello '' '' from Unix"; PromptTemplate pt = new PromptTemplate(templateText); final Map model = new HashMap<>(); @@ -94,7 +94,7 @@ void newApiPlaygroundTests() { @Test void testSingleInputVariable() { - String template = "This is a {foo} test"; + String template = "This is a test"; PromptTemplate promptTemplate = new PromptTemplate(template); Set inputVariables = promptTemplate.getInputVariables(); assertThat(inputVariables).isNotEmpty(); @@ -104,7 +104,7 @@ void testSingleInputVariable() { @Test void testMultipleInputVariables() { - String template = "This {bar} is a {foo} test"; + String template = "This is a test"; PromptTemplate promptTemplate = new PromptTemplate(template); Set inputVariables = promptTemplate.getInputVariables(); assertThat(inputVariables).isNotEmpty(); @@ -114,7 +114,7 @@ void testMultipleInputVariables() { @Test void testMultipleInputVariablesWithRepeats() { - String template = "This {bar} is a {foo} test {foo}."; + String template = "This is a test ."; PromptTemplate promptTemplate = new PromptTemplate(template); Set inputVariables = promptTemplate.getInputVariables(); assertThat(inputVariables).isNotEmpty(); @@ -124,7 +124,7 @@ void testMultipleInputVariablesWithRepeats() { @Test void testBadFormatOfTemplateString() { - String template = "This is a {foo test"; + String template = "This is a { new PromptTemplate(template); }).isInstanceOf(IllegalArgumentException.class).hasMessage("The template string is not valid."); diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/prompt.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/prompt.adoc index 054d829bdce..2ea60d21822 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/prompt.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/prompt.adoc @@ -194,12 +194,12 @@ The method `Prompt create(Map model)`: Expands prompt creation c == Example Usage -A simple example taken from the https://github.com/Azure-Samples/spring-ai-azure-workshop/blob/main/2-README-prompt-templating.md[AI Workshop on PromptTemplates] is shown below. +A simple example taken from the https://github.com/Azure-Samples/spring-ai-azure-workshop/blob/main/2-README-prompt-templating.md[AI Workshop on PromptTemplates] is shown below. Context variable will be delimited by `<` and `>`. The delimiters can be escaped with `\` as stated in the https://github.com/antlr/stringtemplate4/blob/master/doc/cheatsheet.md#stringtemplate-cheat-sheet[StringTemplate documentation]. ```java -PromptTemplate promptTemplate = new PromptTemplate("Tell me a {adjective} joke about {topic}"); +PromptTemplate promptTemplate = new PromptTemplate("Tell me a joke about "); Prompt prompt = promptTemplate.create(Map.of("adjective", adjective, "topic", topic)); @@ -218,8 +218,8 @@ Message userMessage = new UserMessage(userText); String systemText = """ You are a helpful AI assistant that helps people find information. - Your name is {name} - You should reply to the user's request with your name and also in the style of a {voice}. + Your name is + You should reply to the user's request with your name and also in the style of a . """; SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText);