initial prompts package

This commit is contained in:
Mark Pollack
2023-07-26 20:05:12 -04:00
parent d5c1044aee
commit 6325fd361c
21 changed files with 821 additions and 3 deletions

View File

@@ -54,7 +54,11 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<!-- prodution dependencies -->
<spring-boot.version>3.1.2</spring-boot.version>
<stringtemplate.version>4.0.2</stringtemplate.version>
<!-- documentation dependencies -->
<asciidoctorj-pdf.version>1.6.2</asciidoctorj-pdf.version> <!-- FIXME build failure with version 2.3.9 -->
@@ -239,7 +243,7 @@
</plugins>
</build>
<profiles>
<profiles>
<profile>
<id>test-coverage</id>
<build>
@@ -292,7 +296,6 @@
</profile>
</profiles>
<dependencyManagement>
<dependencies>
<dependency>

View File

@@ -19,8 +19,20 @@
</scm>
<dependencies>
<!-- production dependencies -->
<dependency>
<groupId>org.antlr</groupId>
<artifactId>stringtemplate</artifactId>
<version>${stringtemplate.version}</version>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.Map;
import java.util.Optional;
public abstract class AbstractPromptTemplate implements PromptInput {
private Optional<OutputParser> outputParser = Optional.empty();
public AbstractPromptTemplate(Optional<OutputParser> outputParser) {
this.outputParser = outputParser;
}
@Override
public Optional<OutputParser> getOutputParser() {
return this.outputParser;
}
public abstract String formatAsString(Map<String, Object> inputVariables);
public abstract PromptValue formatAsPrompt(Map<String, Object> inputVariables);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.Map;
import java.util.Optional;
public abstract class AbstractStringPromptTemplate extends AbstractPromptTemplate implements PromptTemplateInput {
public AbstractStringPromptTemplate(Optional<OutputParser> outputParser) {
super(outputParser);
}
@Override
public PromptValue formatAsPrompt(Map<String, Object> inputVariables) {
String formattedPrompt = formatAsString(inputVariables);
return new StringPromptValue(formattedPrompt);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.Map;
public class Generation {
private final String text;
private Map<String, Object> info;
public Generation(String text) {
this.text = text;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.List;
public interface OutputParser {
Object parseResut(List<Generation> generations);
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.Optional;
import java.util.Set;
public interface PromptInput {
Set<String> getInputVariables();
Optional<OutputParser> getOutputParser();
// Leave out Partial Input Variables for now
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenStream;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.compiler.STLexer;
public class PromptTemplate extends AbstractStringPromptTemplate implements PromptTemplateInput {
private String template;
private TemplateFormat templateFormat = TemplateFormat.FSTRING;
public PromptTemplate(String template) {
super(Optional.empty());
this.template = template;
}
public PromptTemplate(String template, boolean validate) {
super(Optional.empty());
validateTemplate(validate);
this.template = template;
}
public PromptTemplate(String template, TemplateFormat templateFormat) {
super(Optional.empty());
this.template = template;
this.templateFormat = templateFormat;
}
public PromptTemplate(String template, TemplateFormat templateFormat, boolean validate) {
super(Optional.empty());
validateTemplate(validate);
this.template = template;
this.templateFormat = templateFormat;
}
public PromptTemplate(String template, Optional<OutputParser> outputParser) {
super(outputParser);
this.template = template;
}
public PromptTemplate(String template, Optional<OutputParser> outputParser, boolean validate) {
super(outputParser);
validateTemplate(validate);
this.template = template;
}
public PromptTemplate(String template, Optional<OutputParser> outputParser, TemplateFormat templateFormat) {
super(outputParser);
this.template = template;
this.templateFormat = templateFormat;
}
public PromptTemplate(String template, Optional<OutputParser> outputParser, TemplateFormat templateFormat,
boolean validate) {
super(outputParser);
validateTemplate(validate);
this.template = template;
this.templateFormat = templateFormat;
}
@Override
public String formatAsString(Map<String, Object> inputVariables) {
// Only "F-String" for now
ST st = new ST(this.template, '{', '}');
for (Entry<String, Object> stringObjectEntry : inputVariables.entrySet()) {
st.add(stringObjectEntry.getKey(), stringObjectEntry.getValue());
}
return st.render();
}
@Override
public String getTemplate() {
return this.template;
}
@Override
public TemplateFormat getTemplateFormat() {
return this.templateFormat;
}
@Override
public Set<String> getInputVariables() {
ST st = new ST(this.template, '{', '}');
TokenStream tokens = st.impl.tokens;
return IntStream.range(0, tokens.range())
.mapToObj(tokens::get)
.filter(token -> token.getType() == STLexer.ID)
.map(Token::getText)
.collect(Collectors.toSet());
}
private void validateTemplate(boolean validate) {
if (!validate) {
return;
}
try {
ST st = new ST(this.template, '{', '}');
Set<String> inputVariables = getInputVariables();
for (String inputVariable : inputVariables) {
st.add(inputVariable, "foo");
}
st.render();
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
public interface PromptTemplateInput extends PromptInput {
String getTemplate();
TemplateFormat getTemplateFormat();
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.List;
import org.springframework.ai.core.prompts.messages.Message;
public interface PromptValue {
String toStringValue();
List<Message> toMessages();
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.Collections;
import java.util.List;
import org.springframework.ai.core.prompts.messages.HumanMessage;
import org.springframework.ai.core.prompts.messages.Message;
public class StringPromptValue implements PromptValue {
private String value;
public StringPromptValue(String formattedPrompt) {
this.value = formattedPrompt;
}
@Override
public String toStringValue() {
return this.value;
}
@Override
public List<Message> toMessages() {
return Collections.singletonList(new HumanMessage(this.value));
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
public enum TemplateFormat {
FSTRING("f-string");
private final String value;
TemplateFormat(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static TemplateFormat fromValue(String value) {
for (TemplateFormat templateFormat : TemplateFormat.values()) {
if (templateFormat.getValue().equals(value)) {
return templateFormat;
}
}
throw new IllegalArgumentException("Invalid TemplateFormat value: " + value);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts.messages;
import java.util.HashMap;
import java.util.Map;
public abstract class AbstractMessage implements Message {
private String content;
private Map<String, Object> properties = new HashMap<>();
private MessageType messageType;
protected AbstractMessage(MessageType messageType, String content) {
this.messageType = messageType;
this.content = content;
}
protected AbstractMessage(MessageType messageType, String content, Map<String, Object> properties) {
this.messageType = messageType;
this.content = content;
this.properties = properties;
}
@Override
public String getContent() {
return this.content;
}
@Override
public Map<String, Object> getProperties() {
return this.properties;
}
@Override
public MessageType getMessageType() {
return this.messageType;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts.messages;
import java.util.Map;
public class AiMessage extends AbstractMessage {
public AiMessage(String content) {
super(MessageType.AI, content);
}
public AiMessage(String content, Map<String, Object> properties) {
super(MessageType.AI, content, properties);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts.messages;
import java.util.Map;
public class ChatMessage extends AbstractMessage {
private String role;
public ChatMessage(String content, String role) {
super(MessageType.SYSTEM, content);
this.role = role;
}
public ChatMessage(String content, Map<String, Object> properties, String role) {
super(MessageType.SYSTEM, content, properties);
this.role = role;
}
public String getRole() {
return role;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts.messages;
import java.util.Map;
public class FunctionMessage extends AbstractMessage {
private String functionName;
public FunctionMessage(String content, String functionName) {
super(MessageType.SYSTEM, content);
this.functionName = functionName;
}
public FunctionMessage(String content, Map<String, Object> properties, String functionName) {
super(MessageType.SYSTEM, content, properties);
this.functionName = functionName;
}
public String getFunctionName() {
return functionName;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts.messages;
import java.util.Map;
public class HumanMessage extends AbstractMessage {
public HumanMessage(String content) {
super(MessageType.HUMAN, content);
}
public HumanMessage(String content, Map<String, Object> properties) {
super(MessageType.HUMAN, content, properties);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts.messages;
import java.util.Map;
public interface Message {
String getContent();
Map<String, Object> getProperties();
MessageType getMessageType();
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts.messages;
public enum MessageType {
HUMAN("human"),
AI("ai"),
GENERIC("generic"),
SYSTEM("system"),
FUNCTION("function");
private final String value;
MessageType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MessageType fromValue(String value) {
for (MessageType messageType : MessageType.values()) {
if (messageType.getValue().equals(value)) {
return messageType;
}
}
throw new IllegalArgumentException("Invalid MessageType value: " + value);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts.messages;
import java.util.Map;
public class SystemMessage extends AbstractMessage {
public SystemMessage(String content) {
super(MessageType.SYSTEM, content);
}
public SystemMessage(String content, Map<String, Object> properties) {
super(MessageType.SYSTEM, content, properties);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.core.prompts;
import java.util.Set;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class PromptTests {
@Test
void testSingleInputVariable() {
String template = "This is a {foo} test";
PromptTemplate promptTemplate = new PromptTemplate(template);
Set<String> inputVariables = promptTemplate.getInputVariables();
assertThat(inputVariables).isNotEmpty();
assertThat(inputVariables).hasSize(1);
assertThat(inputVariables).contains("foo");
}
@Test
void testMultipleInputVariables() {
String template = "This {bar} is a {foo} test";
PromptTemplate promptTemplate = new PromptTemplate(template);
Set<String> inputVariables = promptTemplate.getInputVariables();
assertThat(inputVariables).isNotEmpty();
assertThat(inputVariables).hasSize(2);
assertThat(inputVariables).contains("foo", "bar");
}
@Test
void testMultipleInputVariablesWithRepeats() {
String template = "This {bar} is a {foo} test {foo}.";
PromptTemplate promptTemplate = new PromptTemplate(template);
Set<String> inputVariables = promptTemplate.getInputVariables();
assertThat(inputVariables).isNotEmpty();
assertThat(inputVariables).hasSize(2);
assertThat(inputVariables).contains("foo", "bar");
}
@Test
void testBadTemplateString() {
String template = "This is a {foo test";
Assertions.assertThatThrownBy(() -> {
PromptTemplate promptTemplate = new PromptTemplate(template, true);
}).isInstanceOf(IllegalArgumentException.class).hasMessage("The template string is not valid.");
}
}