Add overload methods for defaultSytem()/defaultUer() and system() methods.
Most ChatModel enums extend the new ModelDescription interface. Add ChatClientTest mockito testing
This commit is contained in:
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -116,7 +117,7 @@ public class AnthropicApi {
|
||||
* "https://docs.anthropic.com/claude/docs/models-overview#model-comparison">model
|
||||
* comparison</a> for additional details and options.
|
||||
*/
|
||||
public enum ChatModel {
|
||||
public enum ChatModel implements ModelDescription {
|
||||
|
||||
// @formatter:off
|
||||
CLAUDE_3_OPUS("claude-3-opus-20240229"),
|
||||
@@ -140,6 +141,11 @@ public class AnthropicApi {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,7 @@ import software.amazon.awssdk.regions.Region;
|
||||
import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatRequest;
|
||||
import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatResponse;
|
||||
import org.springframework.ai.bedrock.api.AbstractBedrockApi;
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -225,7 +226,7 @@ public class AnthropicChatBedrockApi extends
|
||||
/**
|
||||
* Anthropic models version.
|
||||
*/
|
||||
public enum AnthropicChatModel {
|
||||
public enum AnthropicChatModel implements ModelDescription {
|
||||
/**
|
||||
* anthropic.claude-instant-v1
|
||||
*/
|
||||
@@ -251,6 +252,11 @@ public class AnthropicChatBedrockApi extends
|
||||
AnthropicChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.An
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatResponse;
|
||||
import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatStreamingResponse;
|
||||
import org.springframework.ai.bedrock.api.AbstractBedrockApi;
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
@@ -436,7 +437,7 @@ public class Anthropic3ChatBedrockApi extends
|
||||
/**
|
||||
* Anthropic models version.
|
||||
*/
|
||||
public enum AnthropicChatModel {
|
||||
public enum AnthropicChatModel implements ModelDescription {
|
||||
|
||||
/**
|
||||
* anthropic.claude-instant-v1
|
||||
@@ -476,6 +477,11 @@ public class Anthropic3ChatBedrockApi extends
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,6 +30,7 @@ import software.amazon.awssdk.regions.Region;
|
||||
import org.springframework.ai.bedrock.api.AbstractBedrockApi;
|
||||
import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest;
|
||||
import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatResponse;
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -366,7 +367,7 @@ public class CohereChatBedrockApi extends
|
||||
/**
|
||||
* Cohere models version.
|
||||
*/
|
||||
public enum CohereChatModel {
|
||||
public enum CohereChatModel implements ModelDescription {
|
||||
|
||||
/**
|
||||
* cohere.command-light-text-v14
|
||||
@@ -390,6 +391,11 @@ public class CohereChatBedrockApi extends
|
||||
CohereChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.ai.bedrock.api.AbstractBedrockApi;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatRequest;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatResponse;
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
@@ -371,7 +372,7 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
/**
|
||||
* Ai21 Jurassic2 models version.
|
||||
*/
|
||||
public enum Ai21Jurassic2ChatModel {
|
||||
public enum Ai21Jurassic2ChatModel implements ModelDescription {
|
||||
|
||||
/**
|
||||
* ai21.j2-mid-v1
|
||||
@@ -395,6 +396,11 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
Ai21Jurassic2ChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,6 +26,7 @@ import software.amazon.awssdk.regions.Region;
|
||||
import org.springframework.ai.bedrock.api.AbstractBedrockApi;
|
||||
import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatRequest;
|
||||
import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatResponse;
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@@ -204,7 +205,7 @@ public class LlamaChatBedrockApi extends
|
||||
/**
|
||||
* Llama models version.
|
||||
*/
|
||||
public enum LlamaChatModel {
|
||||
public enum LlamaChatModel implements ModelDescription {
|
||||
|
||||
/**
|
||||
* meta.llama2-13b-chat-v1
|
||||
@@ -238,6 +239,11 @@ public class LlamaChatBedrockApi extends
|
||||
LlamaChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatReq
|
||||
import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponse;
|
||||
import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponse.CompletionReason;
|
||||
import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponseChunk;
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
|
||||
/**
|
||||
* Java client for the Bedrock Titan chat model.
|
||||
@@ -265,7 +266,7 @@ public class TitanChatBedrockApi extends
|
||||
/**
|
||||
* Titan models version.
|
||||
*/
|
||||
public enum TitanChatModel {
|
||||
public enum TitanChatModel implements ModelDescription {
|
||||
|
||||
/**
|
||||
* amazon.titan-text-lite-v1
|
||||
@@ -294,6 +295,11 @@ public class TitanChatBedrockApi extends
|
||||
TitanChatModel(String value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,8 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
@@ -111,7 +113,7 @@ public class MiniMaxApi {
|
||||
* MiniMax Chat Completion Models:
|
||||
* <a href="https://www.minimaxi.com/document/algorithm-concept">MiniMax Model</a>.
|
||||
*/
|
||||
public enum ChatModel {
|
||||
public enum ChatModel implements ModelDescription {
|
||||
ABAB_6_Chat("abab6-chat"),
|
||||
ABAB_5_5_Chat("abab5.5-chat"),
|
||||
ABAB_5_5_S_Chat("abab5.5s-chat");
|
||||
@@ -125,6 +127,11 @@ public class MiniMaxApi {
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
@@ -706,7 +707,7 @@ public class MistralAiApi {
|
||||
* <li><b>LARGE</b> - mistral-large-latest (aka mistral-large-2402)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum ChatModel {
|
||||
public enum ChatModel implements ModelDescription {
|
||||
|
||||
// @formatter:off
|
||||
TINY("open-mistral-7b"),
|
||||
@@ -726,6 +727,11 @@ public class MistralAiApi {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
*/
|
||||
package org.springframework.ai.ollama.api;
|
||||
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
|
||||
/**
|
||||
* Helper class for common Ollama models.
|
||||
*
|
||||
* @author Siarhei Blashuk
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public enum OllamaModel {
|
||||
public enum OllamaModel implements ModelDescription {
|
||||
|
||||
/**
|
||||
* Llama 2 is a collection of language models ranging from 7B to 70B parameters.
|
||||
@@ -99,4 +101,9 @@ public enum OllamaModel {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
@@ -113,7 +114,7 @@ public class OpenAiApi {
|
||||
* - <a href="https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo">GPT-4 and GPT-4 Turbo</a>
|
||||
* - <a href="https://platform.openai.com/docs/models/gpt-3-5-turbo">GPT-3.5 Turbo</a>.
|
||||
*/
|
||||
public enum ChatModel {
|
||||
public enum ChatModel implements ModelDescription {
|
||||
/**
|
||||
* Multimodal flagship model that’s cheaper and faster than GPT-4 Turbo.
|
||||
* Currently points to gpt-4o-2024-05-13.
|
||||
@@ -199,6 +200,11 @@ public class OpenAiApi {
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,9 +33,8 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.chat.ChatClient;
|
||||
import org.springframework.ai.chat.ChatResponse;
|
||||
import org.springframework.ai.chat.Generation;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.converter.BeanOutputConverter;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.OpenAiTestConfiguration;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
@@ -62,14 +61,17 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
@Test
|
||||
void roleTest() {
|
||||
|
||||
// @formatter:off
|
||||
ChatResponse response = ChatClient.builder(modelCaller).build().prompt()
|
||||
.system(s -> s.text(systemTextResource)
|
||||
.param("name", "Bob")
|
||||
.param("voice", "pirate"))
|
||||
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
|
||||
.call().chatResponse();
|
||||
.call()
|
||||
.chatResponse();
|
||||
// @formatter:on
|
||||
|
||||
System.out.println(response);
|
||||
logger.info("" + response);
|
||||
// UserMessage userMessage = new UserMessage(
|
||||
// "Tell me about 3 famous pirates from the Golden Age of Piracy and what they
|
||||
// did.");
|
||||
@@ -88,10 +90,13 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
void listOutputConverter() {
|
||||
|
||||
// TODO: there is a problem here.
|
||||
// @formatter:off
|
||||
Collection<String> list = ChatClient.builder(modelCaller).build().prompt()
|
||||
.user(u -> u.text("List five {subject}")
|
||||
.param("subject", "ice cream flavors"))
|
||||
.call().list(String.class);
|
||||
.call()
|
||||
.list(String.class);
|
||||
// @formatter:on
|
||||
|
||||
// DefaultConversionService conversionService = new DefaultConversionService();
|
||||
// ListOutputConverter outputConverter = new
|
||||
@@ -115,12 +120,13 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
|
||||
@Test
|
||||
void mapOutputConverter() {
|
||||
|
||||
// @formatter:off
|
||||
Map<String, Object> result = ChatClient.builder(modelCaller).build().prompt()
|
||||
.user(u -> u.text("Provide me a List of {subject}")
|
||||
.param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'"))
|
||||
.call().single(new ParameterizedTypeReference<Map<String, Object>>() {
|
||||
});
|
||||
// @formatter:on
|
||||
|
||||
// MapOutputConverter outputConverter = new MapOutputConverter();
|
||||
|
||||
@@ -143,10 +149,12 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
@Test
|
||||
void beanOutputConverter() {
|
||||
|
||||
// @formatter:off
|
||||
ActorsFilms actorsFilms = ChatClient.builder(modelCaller).build().prompt()
|
||||
.user("Generate the filmography for a random actor.")
|
||||
.call()
|
||||
.single(ActorsFilms.class);
|
||||
// @formatter:on
|
||||
|
||||
// BeanOutputConverter<ActorsFilms> outputConverter = new
|
||||
// BeanOutputConverter<>(ActorsFilms.class);
|
||||
@@ -173,10 +181,12 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
@Test
|
||||
void beanOutputConverterRecords() {
|
||||
|
||||
// @formatter:off
|
||||
ActorsFilmsRecord actorsFilms = ChatClient.builder(modelCaller).build().prompt()
|
||||
.user("Generate the filmography of 5 movies for Tom Hanks.")
|
||||
.call()
|
||||
.single(ActorsFilmsRecord.class);
|
||||
// @formatter:on
|
||||
|
||||
// BeanOutputConverter<ActorsFilmsRecord> outputConverter = new
|
||||
// BeanOutputConverter<>(ActorsFilmsRecord.class);
|
||||
@@ -203,6 +213,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
|
||||
BeanOutputConverter<ActorsFilmsRecord> outputConverter = new BeanOutputConverter<>(ActorsFilmsRecord.class);
|
||||
|
||||
// @formatter:off
|
||||
Flux<String> chatResponse = ChatClient.builder(modelCaller)
|
||||
.build()
|
||||
.prompt()
|
||||
@@ -217,6 +228,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
.block()
|
||||
.stream()
|
||||
.collect(Collectors.joining());
|
||||
// @formatter:on
|
||||
|
||||
// String generationTextFromStream = chatResponse.collectList()
|
||||
// .block()
|
||||
@@ -254,11 +266,13 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.builder(modelCaller).build().prompt()
|
||||
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
|
||||
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
|
||||
.call()
|
||||
.content();
|
||||
// @formatter:on
|
||||
|
||||
// UserMessage userMessage = new UserMessage("What's the weather like in San
|
||||
// Francisco, Tokyo, and Paris?");
|
||||
@@ -287,11 +301,13 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
@Test
|
||||
void streamFunctionCallTest() {
|
||||
|
||||
// @formatter:off
|
||||
Flux<String> response = ChatClient.builder(modelCaller).build().prompt()
|
||||
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
|
||||
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
|
||||
.stream()
|
||||
.content();
|
||||
// @formatter:on
|
||||
|
||||
// UserMessage userMessage = new UserMessage("What's the weather like in San
|
||||
// Francisco, Tokyo, and Paris?");
|
||||
@@ -311,10 +327,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
// Flux<ChatResponse> response = streamingChatClient.stream(new Prompt(messages,
|
||||
// promptOptions));
|
||||
|
||||
String content = response.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
.collect(Collectors.joining());
|
||||
String content = response.collectList().block().stream().collect(Collectors.joining());
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).containsAnyOf("30.0", "30");
|
||||
@@ -326,6 +339,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
@ValueSource(strings = { "gpt-4-vision-preview", "gpt-4o" })
|
||||
void multiModalityEmbeddedImage(String modelName) throws IOException {
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.builder(modelCaller).build().prompt()
|
||||
// TODO consider adding model(...) method to ChatClient as a shortcut to
|
||||
// OpenAiChatOptions.builder().withModel(modelName).build()
|
||||
@@ -334,6 +348,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
.media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/test.png")))
|
||||
.call()
|
||||
.content();
|
||||
// @formatter:on
|
||||
|
||||
// var imageData = new ClassPathResource("/test.png");
|
||||
|
||||
@@ -356,6 +371,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
// TODO: add url method that wrapps the checked exception.
|
||||
URL url = new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png");
|
||||
|
||||
// @formatter:off
|
||||
String response = ChatClient.builder(modelCaller)
|
||||
.build()
|
||||
.prompt()
|
||||
@@ -365,6 +381,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
.user(u -> u.text("Explain what do you see on this picture?").media(MimeTypeUtils.IMAGE_PNG, url))
|
||||
.call()
|
||||
.content();
|
||||
// @formatter:on
|
||||
|
||||
// var userMessage = new UserMessage("Explain what do you see on this picture?",
|
||||
// List
|
||||
@@ -387,6 +404,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
// TODO: add url method that wrapps the checked exception.
|
||||
URL url = new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png");
|
||||
|
||||
// @formatter:off
|
||||
Flux<String> response = ChatClient.builder(modelCaller).build().prompt()
|
||||
.options(OpenAiChatOptions.builder().withModel(OpenAiApi.ChatModel.GPT_4_VISION_PREVIEW.getValue())
|
||||
.build())
|
||||
@@ -394,6 +412,7 @@ class OpenAiChatClientIT extends AbstractIT {
|
||||
.media(MimeTypeUtils.IMAGE_PNG, url))
|
||||
.stream()
|
||||
.content();
|
||||
// @formatter:on
|
||||
|
||||
// var userMessage = new UserMessage("Explain what do you see on this picture?",
|
||||
// List.of(new Media(MimeTypeUtils.IMAGE_PNG,
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.ai.chat.messages.MessageType;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.AbstractFunctionCallSupport;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
@@ -95,7 +96,7 @@ public class VertexAiGeminiModelCaller
|
||||
|
||||
}
|
||||
|
||||
public enum ChatModel {
|
||||
public enum ChatModel implements ModelDescription {
|
||||
|
||||
GEMINI_PRO_VISION("gemini-pro-vision"),
|
||||
|
||||
@@ -115,6 +116,11 @@ public class VertexAiGeminiModelCaller
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public VertexAiGeminiModelCaller(VertexAI vertexAI) {
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.ai.zhipuai.api;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.model.ModelDescription;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
@@ -110,7 +112,7 @@ public class ZhiPuAiApi {
|
||||
* ZhiPuAI Chat Completion Models:
|
||||
* <a href="https://open.bigmodel.cn/dev/howuse/model">ZhiPuAI Model</a>.
|
||||
*/
|
||||
public enum ChatModel {
|
||||
public enum ChatModel implements ModelDescription {
|
||||
GLM_4("GLM-4"),
|
||||
GLM_3_Turbo("GLM-3-Turbo");
|
||||
|
||||
@@ -123,6 +125,11 @@ public class ZhiPuAiApi {
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -246,6 +246,11 @@ public interface ChatClient {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatClientRequest system(String text) {
|
||||
this.systemText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatClientRequest system(Consumer<SystemSpec> consumer) {
|
||||
var ss = new SystemSpec();
|
||||
consumer.accept(ss);
|
||||
@@ -364,7 +369,7 @@ public interface ChatClient {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public <T> Collection<T> list(Class<T> clzz) {
|
||||
public <T> Collection<T> list(Class<T> type) {
|
||||
return single(new ParameterizedTypeReference<List<T>>() {
|
||||
});
|
||||
}
|
||||
@@ -461,15 +466,13 @@ public interface ChatClient {
|
||||
}
|
||||
|
||||
public Flux<String> content() {
|
||||
return doGetFluxChatResponse(this.request.userText)
|
||||
.map(r -> {
|
||||
if (r.getResult() == null || r.getResult().getOutput() == null
|
||||
|| r.getResult().getOutput().getContent() == null) {
|
||||
return "";
|
||||
}
|
||||
return r.getResult().getOutput().getContent();
|
||||
})
|
||||
.filter(v -> StringUtils.hasText(v));
|
||||
return doGetFluxChatResponse(this.request.userText).map(r -> {
|
||||
if (r.getResult() == null || r.getResult().getOutput() == null
|
||||
|| r.getResult().getOutput().getContent() == null) {
|
||||
return "";
|
||||
}
|
||||
return r.getResult().getOutput().getContent();
|
||||
}).filter(v -> StringUtils.hasText(v));
|
||||
}
|
||||
|
||||
// @SuppressWarnings("unused")
|
||||
@@ -515,11 +518,21 @@ public interface ChatClient {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatClientBuilder defaultUser(String text) {
|
||||
this.defaultRequest.user(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatClientBuilder defaultUser(Consumer<UserSpec> userSpecConsumer) {
|
||||
this.defaultRequest.user(userSpecConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatClientBuilder defaultSystem(String text) {
|
||||
this.defaultRequest.system(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatClientBuilder defaultSystem(Consumer<SystemSpec> systemSpecConsumer) {
|
||||
this.defaultRequest.system(systemSpecConsumer);
|
||||
return this;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2024-2024 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.model;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public interface ModelDescription {
|
||||
|
||||
String getModelName();
|
||||
|
||||
default String getDescription() {
|
||||
return "";
|
||||
}
|
||||
|
||||
default String getVersion() {
|
||||
return "";
|
||||
}
|
||||
|
||||
default int getContextLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2024-2024 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.chat;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.MessageType;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ChatClientTest {
|
||||
|
||||
@Mock
|
||||
ChatCaller modelCaller;
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<Prompt> promptCaptor;
|
||||
|
||||
@Test
|
||||
public void call() throws MalformedURLException {
|
||||
|
||||
var options = FunctionCallingOptions.builder().build();
|
||||
when(modelCaller.getDefaultOptions()).thenReturn(options);
|
||||
when(modelCaller.call(promptCaptor.capture()))
|
||||
.thenReturn(new ChatResponse(List.of(new Generation("response"))));
|
||||
|
||||
var url = new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png");
|
||||
|
||||
// @formatter:off
|
||||
ChatClient client = ChatClient.builder(modelCaller)
|
||||
.defaultSystem("System text")
|
||||
.defaultFunctions("function1")
|
||||
.build();
|
||||
|
||||
String response = client.prompt()
|
||||
.user(u -> u.text("User text {music}").param("music", "Rock").media(MimeTypeUtils.IMAGE_PNG, url))
|
||||
.call()
|
||||
.content();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(response).isEqualTo("response");
|
||||
assertThat(promptCaptor.getValue().getInstructions()).hasSize(2);
|
||||
|
||||
Message systemMessage = promptCaptor.getValue().getInstructions().get(0);
|
||||
assertThat(systemMessage.getContent()).isEqualTo("System text");
|
||||
assertThat(systemMessage.getMessageType()).isEqualTo(MessageType.SYSTEM);
|
||||
|
||||
Message userMessage = promptCaptor.getValue().getInstructions().get(1);
|
||||
assertThat(userMessage.getContent()).isEqualTo("User text Rock");
|
||||
assertThat(userMessage.getMessageType()).isEqualTo(MessageType.USER);
|
||||
assertThat(userMessage.getMedia()).hasSize(1);
|
||||
assertThat(userMessage.getMedia().iterator().next().getMimeType()).isEqualTo(MimeTypeUtils.IMAGE_PNG);
|
||||
assertThat(userMessage.getMedia().iterator().next().getData())
|
||||
.isEqualTo("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png");
|
||||
|
||||
assertThat(options.getFunctions()).containsExactly("function1");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024-2024 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.chat;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class Main {
|
||||
|
||||
@Mock
|
||||
ChatCaller modelCaller;
|
||||
|
||||
@Test
|
||||
public void test() throws MalformedURLException {
|
||||
|
||||
var url = new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png");
|
||||
|
||||
ChatClient client = ChatClient.builder(modelCaller)
|
||||
.defaultSystem(s -> s.text("System text {music}"))
|
||||
.defaultUser(u -> u.param("music", "Jazz"))
|
||||
.defaultFunctions("function1")
|
||||
.build();
|
||||
|
||||
String response = client.prompt()
|
||||
.user(u -> u.text("User text {music}").param("music", "Rock").media(MimeTypeUtils.IMAGE_PNG, url))
|
||||
.call()
|
||||
.single(String.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,9 +39,9 @@ public class FunctionCallbackInPrompt2IT {
|
||||
private final Logger logger = LoggerFactory.getLogger(FunctionCallbackInPromptIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class));
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
@@ -51,6 +51,7 @@ public class FunctionCallbackInPrompt2IT {
|
||||
|
||||
ChatClient chatClient = ChatClient.builder(caller).build();
|
||||
|
||||
// @formatter:off
|
||||
chatClient.prompt()
|
||||
.user("Tell me a joke?")
|
||||
.call().content();
|
||||
@@ -59,6 +60,7 @@ public class FunctionCallbackInPrompt2IT {
|
||||
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
|
||||
.function("CurrentWeatherService", "Get the weather in location", new MockWeatherService())
|
||||
.call().content();
|
||||
// @formatter:on
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
@@ -74,6 +76,7 @@ public class FunctionCallbackInPrompt2IT {
|
||||
|
||||
OpenAiModelCaller caller = context.getBean(OpenAiModelCaller.class);
|
||||
|
||||
// @formatter:off
|
||||
String content = ChatClient.builder(caller).build().prompt()
|
||||
.user("What's the weather like in Amsterdam?")
|
||||
.function("CurrentWeatherService", "Get the weather in location",
|
||||
@@ -84,7 +87,7 @@ public class FunctionCallbackInPrompt2IT {
|
||||
}
|
||||
})
|
||||
.call().content();
|
||||
|
||||
// @formatter:on
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).contains("18");
|
||||
@@ -98,11 +101,13 @@ public class FunctionCallbackInPrompt2IT {
|
||||
|
||||
OpenAiModelCaller caller = context.getBean(OpenAiModelCaller.class);
|
||||
|
||||
// @formatter:off
|
||||
String content = ChatClient.builder(caller).build().prompt()
|
||||
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
|
||||
.function("CurrentWeatherService", "Get the weather in location", new MockWeatherService())
|
||||
.stream().content()
|
||||
.collectList().block().stream().collect(Collectors.joining());
|
||||
// @formatter:on
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ public class FunctionCallbackWrapper2IT {
|
||||
|
||||
OpenAiModelCaller caller = context.getBean(OpenAiModelCaller.class);
|
||||
|
||||
// @formatter:off
|
||||
ChatClient chatClient = ChatClient.builder(caller)
|
||||
.defaultFunctions("WeatherInfo")
|
||||
.defaultUser(u -> u.text("What's the weather like in {cities}?"))
|
||||
@@ -61,6 +62,7 @@ public class FunctionCallbackWrapper2IT {
|
||||
String content = chatClient.prompt()
|
||||
.user(u -> u.param("cities", "San Francisco, Tokyo, Paris"))
|
||||
.call().content();
|
||||
// @formatter:on
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
@@ -76,11 +78,13 @@ public class FunctionCallbackWrapper2IT {
|
||||
|
||||
OpenAiModelCaller caller = context.getBean(OpenAiModelCaller.class);
|
||||
|
||||
// @formatter:off
|
||||
String content = ChatClient.builder(caller).build().prompt()
|
||||
.functions("WeatherInfo")
|
||||
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
|
||||
.stream().content()
|
||||
.collectList().block().stream().collect(Collectors.joining());
|
||||
// @formatter:on
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user