This commit is contained in:
Mark Pollack
2024-05-16 09:55:50 +02:00
parent 549c480489
commit 19bdaa1ab9
7 changed files with 609 additions and 1 deletions

View File

@@ -0,0 +1,86 @@
package org.springframework.ai.openai.chat;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.FluentChatClient;
import org.springframework.ai.openai.OpenAiTestConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = OpenAiTestConfiguration.class)
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
public class OpenAiChatClientSpecIT {
@Autowired
private ChatClient chatClient;
private static final Logger logger = LoggerFactory.getLogger(OpenAiChatClientSpecIT.class);
@Test
void simpleTest() {
FluentChatClient fluentChatClient = new FluentChatClient(chatClient);
ActorsFilmsRecord actorsFilms = fluentChatClient.chat()
.user()
.text("Generate the filmography of 5 movies for {actor}.")
.param("actor", "Tom Hanks")
.and()
.and()
.execute();
// ActorsFilmsRecord actorsFilms = new FluentChatClient().chat(
// chatSpec -> chatSpec
// .user(userSpec -> userSpec.text("Generate the filmography of 5 movies for {actor}.",
// textSpec -> textSpec.param("actor", "Tom Hanks"))
// )
// .execute());
// ActorsFilmsRecord actorsFilms = new FluentChatClient().chat(chatSpec ->
// chatSpec.user(userSpec ->
// userSpec.text("Generate the filmography of 5 movies for {actor}.",
// textSpec -> textSpec.param("actor", "Tom Hanks")
// )
// )
// ).execute();
// ActorsFilmsRecord actorsFilms = fluentChatClient.chat()
// .user()
// .text("Generate the filmography of 5 movies for {actor}.")
// .param("actor", "Tom Hanks")
// .and()
// .and()
// .execute();
//
//
//
// ActorsFilmsRecord actorsFilms = new FluentChatClient().chat()
// .user(userSpec -> userSpec
// .text("Generate the filmography of 5 movies for {actor}.",
// textSpec -> textSpec.param("actor", "Tom Hanks").params(Map.of()) ) })
// .media(mediaSpec -> {
// mediaSpec.param(myImage, MimeTypeUtils.IMAGE_PNG);
// })
// )
// .execute();
//
// System.out.println(actorsFilms);
}
record ActorsFilmsRecord(String actor, List<String> movies) {
}
}

View File

@@ -0,0 +1,86 @@
package org.springframework.ai.openai.chat;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.FluentChatClientLambda;
import org.springframework.ai.openai.OpenAiTestConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest(classes = OpenAiTestConfiguration.class)
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
public class OpenAiChatClientSpecLambdaIT {
@Autowired
private ChatClient chatClient;
private static final Logger logger = LoggerFactory.getLogger(OpenAiChatClientSpecLambdaIT.class);
@Test
void simpleTest() {
FluentChatClientLambda fluentChatClient = new FluentChatClientLambda(chatClient);
ActorsFilmsRecord2 actorsFilms =
fluentChatClient.chat(chatSpec ->
chatSpec.user(chatUserSpec ->
chatUserSpec.text("Generate the filmograph of 5 movies for {actor}",
chatParamSpec -> chatParamSpec.param("actor", "Tom")))
// .user()
// .text("Generate the filmography of 5 movies for {actor}.")
// .param("actor", "Tom Hanks")
// .and()
// .and()
// .execute();
// ActorsFilmsRecord actorsFilms = new FluentChatClient().chat(
// chatSpec -> chatSpec
// .user(userSpec -> userSpec.text("Generate the filmography of 5 movies for {actor}.",
// textSpec -> textSpec.param("actor", "Tom Hanks"))
// )
// .execute());
// ActorsFilmsRecord actorsFilms = new FluentChatClient().chat(chatSpec ->
// chatSpec.user(userSpec ->
// userSpec.text("Generate the filmography of 5 movies for {actor}.",
// textSpec -> textSpec.param("actor", "Tom Hanks")
// )
// )
// ).execute();
// ActorsFilmsRecord actorsFilms = fluentChatClient.chat()
// .user()
// .text("Generate the filmography of 5 movies for {actor}.")
// .param("actor", "Tom Hanks")
// .and()
// .and()
// .execute();
//
//
//
// ActorsFilmsRecord actorsFilms = new FluentChatClient().chat()
// .user(userSpec -> userSpec
// .text("Generate the filmography of 5 movies for {actor}.",
// textSpec -> textSpec.param("actor", "Tom Hanks").params(Map.of()) ) })
// .media(mediaSpec -> {
// mediaSpec.param(myImage, MimeTypeUtils.IMAGE_PNG);
// })
// )
// .execute();
//
// System.out.println(actorsFilms);
// }
record ActorsFilmsRecord2(String actor, List<String> movies) {
}
}

View File

@@ -15,15 +15,18 @@
*/
package org.springframework.ai.chat;
import org.springframework.ai.chat.messages.Media;
import org.springframework.ai.chat.prompt.Prompt;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.model.ModelClient;
import org.springframework.util.MimeType;
@FunctionalInterface
public interface ChatClient extends ModelClient<Prompt, ChatResponse> {
default String call(String message) {

View File

@@ -0,0 +1,159 @@
package org.springframework.ai.chat;
import org.springframework.ai.chat.messages.Media;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DefaultChatSpec implements FluentChatClient.ChatSpec {
private String userText;
private ChatClient chatClient;
private Map<String, Object> textParameters = new HashMap<>();
public DefaultChatSpec(ChatClient chatClient) {
this.chatClient = chatClient;
}
@Override
public FluentChatClient.ChatUserSpec user() {
return new DefaultChatUserSpec(this);
}
public void addUser(String userText, Map<String, Object> textParameters) {
this.userText = userText;
this.textParameters.putAll(textParameters);
}
public <T> T execute(T... varargsOfT) {
Assert.state(varargsOfT.length == 0, "you should not provide any values for T!");
Assert.state(varargsOfT.getClass().isArray(), "this needs to be an array parameter");
Class<?> componentClass = varargsOfT.getClass().getComponentType();
List<Message> messageList = new ArrayList<>();
String userTextToUse = userText + System.lineSeparator() + "{format}";
var converter = new BeanOutputConverter(componentClass);
this.textParameters.put("format", converter.getFormat());
doCreateUserMessage(messageList, userTextToUse, this.textParameters);
Prompt prompt = new Prompt(messageList);
ChatResponse chatResponse = this.chatClient.call(prompt);
String stringResponse = chatResponse.getResult().getOutput().getContent();
T convertedResponse = (T) converter.convert(stringResponse);
return convertedResponse;
}
private void doCreateUserMessage(List<Message> messageList, String userText, Map<String, Object> textParameters) {
PromptTemplate userPromptTemplate = new PromptTemplate(userText);
messageList.add(userPromptTemplate.createMessage(textParameters));
}
private class DefaultChatUserSpec implements FluentChatClient.ChatUserSpec {
private DefaultChatSpec chatSpec;
private String text;
private Map<String, Object> textParameters = new HashMap<>();
public DefaultChatUserSpec(DefaultChatSpec chatSpec) {
this.chatSpec = chatSpec;
}
@Override
public FluentChatClient.ChatParamSpec text(String text) {
this.text = text;
return new DefaultChatParamSpec(this);
}
@Override
public FluentChatClient.ChatMediaParamSpec media() {
return new DefaultChatMediaParamSpec(this);
}
public void addTextParams(Map<String, Object> textParameters) {
this.textParameters.putAll(textParameters);
}
public FluentChatClient.ChatSpec and() {
this.chatSpec.addUser(this.text, this.textParameters);
return this.chatSpec;
}
}
private class DefaultChatMediaParamSpec implements FluentChatClient.ChatMediaParamSpec {
private final DefaultChatUserSpec chatUserSpec;
public DefaultChatMediaParamSpec(DefaultChatUserSpec chatUserSpec) {
this.chatUserSpec = chatUserSpec;
}
@Override
public FluentChatClient.ChatMediaParamSpec param(List<Media> mediaList) {
return null;
}
@Override
public FluentChatClient.ChatMediaParamSpec param(Media media) {
return null;
}
@Override
public FluentChatClient.ChatMediaParamSpec param(MimeType mimeType, Object data) {
return null;
}
@Override
public FluentChatClient.ChatUserSpec and() {
return null;
}
}
private class DefaultChatParamSpec implements FluentChatClient.ChatParamSpec {
private final DefaultChatUserSpec chatUserSpec;
private Map<String, Object> parameters = new HashMap<>();
public DefaultChatParamSpec(DefaultChatUserSpec chatUserSpec) {
this.chatUserSpec = chatUserSpec;
}
@Override
public FluentChatClient.ChatParamSpec param(String name, Object value) {
this.parameters.put(name, value);
return this;
}
@Override
public FluentChatClient.ChatParamSpec params(Map<String, ?> paramMap) {
this.parameters.putAll(paramMap);
return this;
}
@Override
public FluentChatClient.ChatUserSpec and() {
chatUserSpec.addTextParams(this.parameters);
return this.chatUserSpec;
}
}
}

View File

@@ -0,0 +1,157 @@
package org.springframework.ai.chat;
import org.springframework.ai.chat.messages.Media;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class DefaultChatSpecLambda implements FluentChatClientLambda.ChatSpec {
private String userText;
private ChatClient chatClient;
private Map<String, Object> textParameters = new HashMap<>();
public DefaultChatSpecLambda(ChatClient chatClient) {
this.chatClient = chatClient;
}
@Override
public FluentChatClientLambda.ChatUserSpec user() {
return new DefaultChatUserSpec(this);
}
public void addUser(String userText, Map<String, Object> textParameters) {
this.userText = userText;
this.textParameters.putAll(textParameters);
}
public <T> T execute(T... varargsOfT) {
Assert.state(varargsOfT.length == 0, "you should not provide any values for T!");
Assert.state(varargsOfT.getClass().isArray(), "this needs to be an array parameter");
Class<?> componentClass = varargsOfT.getClass().getComponentType();
List<Message> messageList = new ArrayList<>();
String userTextToUse = userText + System.lineSeparator() + "{format}";
var converter = new BeanOutputConverter(componentClass);
this.textParameters.put("format", converter.getFormat());
doCreateUserMessage(messageList, userTextToUse, this.textParameters);
Prompt prompt = new Prompt(messageList);
ChatResponse chatResponse = this.chatClient.call(prompt);
String stringResponse = chatResponse.getResult().getOutput().getContent();
T convertedResponse = (T) converter.convert(stringResponse);
return convertedResponse;
}
private void doCreateUserMessage(List<Message> messageList, String userText, Map<String, Object> textParameters) {
PromptTemplate userPromptTemplate = new PromptTemplate(userText);
messageList.add(userPromptTemplate.createMessage(textParameters));
}
private class DefaultChatUserSpec implements FluentChatClientLambda.ChatUserSpec {
private DefaultChatSpecLambda chatSpec;
private String text;
private Map<String, Object> textParameters = new HashMap<>();
public DefaultChatUserSpec(DefaultChatSpecLambda chatSpec) {
this.chatSpec = chatSpec;
}
// @Override
// public FluentChatClientLambda.ChatParamSpec text(String text) {
// this.text = text;
// return new DefaultChatParamSpec(this);
// }
//
// @Override
// public FluentChatClientLambda.ChatMediaParamSpec media() {
// return new DefaultChatMediaParamSpec(this);
// }
public void addTextParams(Map<String, Object> textParameters) {
this.textParameters.putAll(textParameters);
}
@Override
public FluentChatClientLambda.ChatParamSpec text(String text,
Consumer<FluentChatClientLambda.ChatParamSpec> chatParamSpecConsumer) {
this.text = text;
return chatParamSpecConsumer;
}
@Override
public FluentChatClientLambda.ChatMediaParamSpec media(
Consumer<FluentChatClientLambda.ChatMediaParamSpec> chatMediaParamSpecConsumer) {
return null;
}
}
private class DefaultChatMediaParamSpec implements FluentChatClientLambda.ChatMediaParamSpec {
private final DefaultChatUserSpec chatUserSpec;
public DefaultChatMediaParamSpec(DefaultChatUserSpec chatUserSpec) {
this.chatUserSpec = chatUserSpec;
}
@Override
public FluentChatClientLambda.ChatMediaParamSpec param(List<Media> mediaList) {
return null;
}
@Override
public FluentChatClientLambda.ChatMediaParamSpec param(Media media) {
return null;
}
@Override
public FluentChatClientLambda.ChatMediaParamSpec param(MimeType mimeType, Object data) {
return null;
}
}
private class DefaultChatParamSpec implements FluentChatClientLambda.ChatParamSpec {
private final DefaultChatUserSpec chatUserSpec;
private Map<String, Object> parameters = new HashMap<>();
public DefaultChatParamSpec(DefaultChatUserSpec chatUserSpec) {
this.chatUserSpec = chatUserSpec;
}
@Override
public FluentChatClientLambda.ChatParamSpec param(String name, Object value) {
this.parameters.put(name, value);
return this;
}
@Override
public FluentChatClientLambda.ChatParamSpec params(Map<String, ?> paramMap) {
this.parameters.putAll(paramMap);
return this;
}
}
}

View File

@@ -0,0 +1,61 @@
package org.springframework.ai.chat;
import org.springframework.ai.chat.messages.Media;
import org.springframework.util.MimeType;
import java.util.List;
import java.util.Map;
public class FluentChatClient {
private final ChatClient chatClient;
public FluentChatClient(ChatClient chatClient) {
this.chatClient = chatClient;
}
public ChatSpec chat() {
return new DefaultChatSpec(this.chatClient);
}
public interface ChatSpec {
ChatUserSpec user();
<T> T execute(T... varargsOfT);
}
public interface ChatUserSpec {
ChatParamSpec text(String text);
ChatMediaParamSpec media();
ChatSpec and();
}
public interface ChatMediaParamSpec {
ChatMediaParamSpec param(List<Media> mediaList);
ChatMediaParamSpec param(Media media);
ChatMediaParamSpec param(MimeType mimeType, Object data);
ChatUserSpec and();
}
public interface ChatParamSpec {
ChatParamSpec param(String name, Object value);
ChatParamSpec params(Map<String, ?> paramMap);
ChatUserSpec and();
}
}

View File

@@ -0,0 +1,56 @@
package org.springframework.ai.chat;
import org.springframework.ai.chat.messages.Media;
import org.springframework.util.MimeType;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class FluentChatClientLambda {
private final ChatClient chatClient;
public FluentChatClientLambda(ChatClient chatClient) {
this.chatClient = chatClient;
}
public void chat(Consumer<FluentChatClientLambda.ChatSpec> chatSpecConsumer) {
}
public interface ChatSpec {
ChatUserSpec user(Consumer<FluentChatClientLambda.ChatUserSpec> chatUserSpecConsumer);
<T> T execute(T... varargsOfT);
}
public interface ChatUserSpec {
ChatParamSpec text(String text, Consumer<ChatParamSpec> chatParamSpecConsumer);
ChatMediaParamSpec media(Consumer<FluentChatClientLambda.ChatMediaParamSpec> chatMediaParamSpecConsumer);
}
public interface ChatMediaParamSpec {
ChatMediaParamSpec param(List<Media> mediaList);
ChatMediaParamSpec param(Media media);
ChatMediaParamSpec param(MimeType mimeType, Object data);
}
public interface ChatParamSpec {
ChatParamSpec param(String name, Object value);
ChatParamSpec params(Map<String, ?> paramMap);
}
}