restructuring

This commit is contained in:
Mark Pollack
2023-08-11 17:46:26 -04:00
parent 1a642043f4
commit 234de74508
14 changed files with 255 additions and 148 deletions

View File

@@ -22,7 +22,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.core.llm.LLMResult;
import org.springframework.ai.core.llm.LlmClient;
import org.springframework.ai.core.prompt.Generation;
import org.springframework.ai.core.llm.Generation;
import org.springframework.ai.core.prompt.Prompt;
import org.springframework.ai.core.prompt.messages.Message;
import org.springframework.util.Assert;

View File

@@ -2,43 +2,61 @@ package org.springframework.ai.core.chain;
import org.springframework.ai.core.memory.Memory;
import java.util.List;
import java.util.*;
public abstract class AbstractChain implements Chain {
private Memory memory;
private Optional<Memory> memory = Optional.empty();
private boolean returnOnlyOutputs;
private List<String> inputKeys;
private List<String> outputKeys;
/**
* @return A string that uniquely identifies the type of chain
*/
protected abstract String getType();
protected abstract Memory getMemory();
public void setReturnOnlyOutputs(boolean returnOnlyOutputs) {
this.returnOnlyOutputs = returnOnlyOutputs;
}
public boolean isReturnOnlyOutputs() {
return this.returnOnlyOutputs;
protected Optional<Memory> getMemory() {
return this.memory;
}
@Override
public List<String> getInputKeys() {
return this.inputKeys;
}
public abstract List<String> getInputKeys();
@Override
public List<String> getOutputKeys() {
return this.outputKeys;
}
public abstract List<String> getOutputKeys();
// TODO validation of input/outputs
@Override
public Map<String, Object> apply(Map<String, Object> inputMap) {
Map<String, Object> inputMapToUse = processBeforeApply(inputMap);
Map<String, Object> outputMap = doApply(inputMapToUse);
Map<String, Object> outputMapToUse = processAfterApply(inputMapToUse, outputMap);
return outputMapToUse;
}
protected Map<String, Object> processBeforeApply(Map<String, Object> inputMap) {
validateInputs(inputMap);
return inputMap;
}
protected abstract Map<String, Object> doApply(Map<String, Object> inputMap);
private Map<String, Object> processAfterApply(Map<String, Object> inputMap, Map<String, Object> outputMap) {
validateOutputs(outputMap);
Map<String, Object> combindedMap = new HashMap<>();
combindedMap.putAll(inputMap);
combindedMap.putAll(outputMap);
return combindedMap;
}
protected void validateOutputs(Map<String, Object> outputMap) {
Set<String> missingKeys = new HashSet<>(getOutputKeys());
missingKeys.removeAll(outputMap.keySet());
if (!missingKeys.isEmpty()) {
throw new IllegalArgumentException("Missing some output keys: " + missingKeys);
}
}
protected void validateInputs(Map<String, Object> inputMap) {
Set<String> missingKeys = new HashSet<>(getInputKeys());
missingKeys.removeAll(inputMap.keySet());
if (!missingKeys.isEmpty()) {
throw new IllegalArgumentException("Missing some input keys: " + missingKeys);
}
}
}

View File

@@ -1,9 +1,10 @@
package org.springframework.ai.core.chain;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public interface Chain extends Function<ChainValues, ChainValues> {
public interface Chain extends Function<Map<String, Object>, Map<String, Object>> {
List<String> getInputKeys();

View File

@@ -1,17 +0,0 @@
package org.springframework.ai.core.chain;
import java.util.Map;
public class ChainValues {
private final Map<String, Object> values;
public ChainValues(Map<String, Object> values) {
this.values = values;
}
public Map<String, Object> getValues() {
return values;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.core.prompt;
package org.springframework.ai.core.llm;
import java.util.HashMap;
import java.util.Map;

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.ai.core.llm;
import org.springframework.ai.core.prompt.Generation;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

View File

@@ -0,0 +1,11 @@
package org.springframework.ai.core.parser;
import org.springframework.ai.core.llm.Generation;
import java.util.List;
public interface OutputParser<T> {
T parse(List<Generation> output);
}

View File

@@ -1,39 +0,0 @@
/*
* 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.prompt;
public abstract class AbstractPromptTemplate implements PromptOperations {
protected String template;
protected TemplateFormat templateFormat = TemplateFormat.ST;
public AbstractPromptTemplate(String template) {
this.template = template;
}
@Override
public String getTemplate() {
return this.template;
}
@Override
public TemplateFormat getTemplateFormat() {
return this.templateFormat;
}
}

View File

@@ -17,31 +17,72 @@
package org.springframework.ai.core.prompt;
import org.springframework.ai.core.prompt.messages.ChatMessage;
import org.springframework.ai.core.prompt.messages.Message;
import org.springframework.ai.core.prompt.messages.MessageType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A PromptTemplate that lets you specify the role as a string should the current
* implementations and their roles not suffice for your needs.
*/
public class ChatPromptTemplate extends PromptTemplate {
public class ChatPromptTemplate implements PromptOperations {
private MessageType messageType;
private final List<PromptTemplate> promptTemplates;
public ChatPromptTemplate(MessageType messageType, String template) {
super(template);
this.messageType = messageType;
public ChatPromptTemplate(List<PromptTemplate> promptTemplates) {
this.promptTemplates = promptTemplates;
}
@Override
public String render() {
StringBuilder sb = new StringBuilder();
for (PromptTemplate promptTemplate : promptTemplates) {
sb.append(promptTemplate.render());
}
return sb.toString();
}
@Override
public String render(Map<String, Object> model) {
StringBuilder sb = new StringBuilder();
for (PromptTemplate promptTemplate : promptTemplates) {
sb.append(promptTemplate.render(model));
}
return sb.toString();
}
@Override
public List<Message> createMessages() {
List<Message> messages = new ArrayList<>();
for (PromptTemplate promptTemplate : promptTemplates) {
messages.addAll(promptTemplate.createMessages());
}
return messages;
}
@Override
public List<Message> createMessages(Map<String, Object> model) {
List<Message> messages = new ArrayList<>();
for (PromptTemplate promptTemplate : promptTemplates) {
messages.addAll(promptTemplate.createMessages(model));
}
return messages;
}
@Override
public Prompt create() {
return new Prompt(new ChatMessage(this.messageType, render()));
List<Message> messages = createMessages();
return new Prompt(messages);
}
@Override
public Prompt create(Map<String, Object> model) {
return new Prompt(new ChatMessage(this.messageType, render(model)));
List<Message> messages = createMessages(model);
return new Prompt(messages);
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.ai.core.prompt;
import org.springframework.ai.core.llm.Generation;
import java.util.List;
public interface OutputParser {

View File

@@ -16,20 +16,21 @@
package org.springframework.ai.core.prompt;
import org.springframework.ai.core.prompt.messages.Message;
import java.util.List;
import java.util.Map;
public interface PromptOperations {
String getTemplate();
TemplateFormat getTemplateFormat();
void add(String name, Object value);
String render();
String render(Map<String, Object> model);
List<Message> createMessages();
List<Message> createMessages(Map<String, Object> model);
Prompt create();
Prompt create(Map<String, Object> model);

View File

@@ -18,25 +18,30 @@ package org.springframework.ai.core.prompt;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenStream;
import org.springframework.ai.core.prompt.messages.Message;
import org.springframework.ai.core.prompt.messages.UserMessage;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.compiler.STLexer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class PromptTemplate extends AbstractPromptTemplate {
public class PromptTemplate implements PromptOperations {
private ST st;
private Map<String, Object> dynamicModel = new HashMap<>();
protected String template;
protected TemplateFormat templateFormat = TemplateFormat.ST;
private OutputParser outputParser;
public PromptTemplate(String template) {
super(template);
this.template = template;
// If the template string is not valid, an exception will be thrown
try {
this.st = new ST(this.template, '{', '}');
@@ -46,12 +51,42 @@ public class PromptTemplate extends AbstractPromptTemplate {
}
}
@Override
public PromptTemplate(String template, Map<String, Object> model) {
this.template = template;
// If the template string is not valid, an exception will be thrown
try {
this.st = new ST(this.template, '{', '}');
for (Entry<String, Object> entry : model.entrySet()) {
add(entry.getKey(), entry.getValue());
}
}
catch (Exception ex) {
throw new IllegalArgumentException("The template string is not valid.", ex);
}
}
public OutputParser getOutputParser() {
return outputParser;
}
public void setOutputParser(OutputParser outputParser) {
Objects.requireNonNull(outputParser, "Output Parser can not be null");
this.outputParser = outputParser;
}
public void add(String name, Object value) {
this.st.add(name, value);
this.dynamicModel.put(name, value);
}
public String getTemplate() {
return this.template;
}
public TemplateFormat getTemplateFormat() {
return this.templateFormat;
}
// Render Methods
public String render() {
return st.render();
@@ -68,6 +103,16 @@ public class PromptTemplate extends AbstractPromptTemplate {
return st.render().trim();
}
@Override
public List<Message> createMessages() {
return List.of(new UserMessage(render()));
}
@Override
public List<Message> createMessages(Map<String, Object> model) {
return List.of(new UserMessage(render(model)));
}
@Override
public Prompt create() {
return new Prompt(render(new HashMap<>()));

View File

@@ -1,40 +0,0 @@
/*
* 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.prompt;
import org.springframework.ai.core.prompt.messages.SystemMessage;
import org.springframework.ai.core.prompt.messages.UserMessage;
import java.util.Map;
public class UserPromptTemplate extends PromptTemplate {
public UserPromptTemplate(String template) {
super(template);
}
@Override
public Prompt create() {
return new Prompt(new SystemMessage(render()));
}
@Override
public Prompt create(Map<String, Object> model) {
return new Prompt(new UserMessage(render(model)));
}
}

View File

@@ -0,0 +1,86 @@
package org.springframework.ai.core.chain;
import org.junit.jupiter.api.Test;
import org.springframework.ai.core.memory.Memory;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
class ChainTests {
@Test
void badInputs() {
Chain chain = new FakeChain();
assertThatThrownBy(() -> chain.apply(Map.of("foobar", "baz"))).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Missing some input keys");
}
@Test
void correctInputs() {
Chain chain = new FakeChain();
Map<String, Object> output = chain.apply(Map.of("foo", "bar"));
assertThat(output).containsEntry("foo", "bar").containsEntry("bar", "baz");
}
class FakeChain extends AbstractChain {
private boolean beCorrect = true;
private List<String> inputKeys = List.of("foo");
public FakeChain() {
}
public FakeChain(boolean beCorrect) {
this.beCorrect = beCorrect;
}
public FakeChain(List<String> inputKeys) {
this.inputKeys = inputKeys;
}
@Override
public List<String> getInputKeys() {
return this.inputKeys;
}
@Override
public List<String> getOutputKeys() {
return List.of("bar");
}
@Override
protected Map<String, Object> doApply(Map<String, Object> inputMap) {
if (beCorrect) {
return Map.of("bar", "baz");
}
else {
return Map.of("baz", "bar");
}
}
}
class FakeMemory implements Memory {
@Override
public List<String> getKeys() {
return List.of("baz");
}
@Override
public Map<String, Object> load(Map<String, Object> inputs) {
return Map.of("baz", "foo");
}
@Override
public void save(Map<String, Object> inputs, Map<String, Object> outputs) {
}
}
}