Few doc aligments with the new ChatClient API

This commit is contained in:
Christian Tzolov
2024-06-04 06:28:56 +02:00
parent 1758d561f6
commit 7cf3525f1a
5 changed files with 80 additions and 33 deletions

View File

@@ -65,7 +65,7 @@ class OpenAiChatClientIT extends AbstractIT {
void call() {
// @formatter:off
ChatResponse response = ChatClient.builder(chatModel).build().prompt()
ChatResponse response = ChatClient.create(chatModel).prompt()
.system(s -> s.text(systemTextResource)
.param("name", "Bob")
.param("voice", "pirate"))
@@ -82,7 +82,7 @@ class OpenAiChatClientIT extends AbstractIT {
@Test
void listOutputConverterString() {
// @formatter:off
List<String> collection = ChatClient.builder(chatModel).build().prompt()
List<String> collection = ChatClient.create(chatModel).prompt()
.user(u -> u.text("List five {subject}")
.param("subject", "ice cream flavors"))
.call()
@@ -97,7 +97,7 @@ class OpenAiChatClientIT extends AbstractIT {
void listOutputConverterBean() {
// @formatter:off
List<ActorsFilms> actorsFilms = ChatClient.builder(chatModel).build().prompt()
List<ActorsFilms> actorsFilms = ChatClient.create(chatModel).prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
@@ -114,7 +114,7 @@ class OpenAiChatClientIT extends AbstractIT {
var toStringListConverter = new ListOutputConverter(new DefaultConversionService());
// @formatter:off
List<String> flavors = ChatClient.builder(chatModel).build().prompt()
List<String> flavors = ChatClient.create(chatModel).prompt()
.user(u -> u.text("List five {subject}")
.param("subject", "ice cream flavors"))
.call()
@@ -129,7 +129,7 @@ class OpenAiChatClientIT extends AbstractIT {
@Test
void mapOutputConverter() {
// @formatter:off
Map<String, Object> result = ChatClient.builder(chatModel).build().prompt()
Map<String, Object> result = ChatClient.create(chatModel).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()
@@ -144,7 +144,7 @@ class OpenAiChatClientIT extends AbstractIT {
void beanOutputConverter() {
// @formatter:off
ActorsFilms actorsFilms = ChatClient.builder(chatModel).build().prompt()
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorsFilms.class);
@@ -158,7 +158,7 @@ class OpenAiChatClientIT extends AbstractIT {
void beanOutputConverterRecords() {
// @formatter:off
ActorsFilms actorsFilms = ChatClient.builder(chatModel).build().prompt()
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
.user("Generate the filmography of 5 movies for Tom Hanks.")
.call()
.entity(ActorsFilms.class);
@@ -175,8 +175,7 @@ class OpenAiChatClientIT extends AbstractIT {
BeanOutputConverter<ActorsFilms> outputConverter = new BeanOutputConverter<>(ActorsFilms.class);
// @formatter:off
Flux<String> chatResponse = ChatClient.builder(chatModel)
.build()
Flux<String> chatResponse = ChatClient.create(chatModel)
.prompt()
.user(u -> u
.text("Generate the filmography of 5 movies for Tom Hanks. " + System.lineSeparator()
@@ -202,7 +201,7 @@ class OpenAiChatClientIT extends AbstractIT {
void functionCallTest() {
// @formatter:off
String response = ChatClient.builder(chatModel).build().prompt()
String response = ChatClient.create(chatModel).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()
@@ -238,7 +237,7 @@ class OpenAiChatClientIT extends AbstractIT {
void streamFunctionCallTest() {
// @formatter:off
Flux<String> response = ChatClient.builder(chatModel).build().prompt()
Flux<String> response = ChatClient.create(chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.stream()
@@ -258,9 +257,7 @@ class OpenAiChatClientIT extends AbstractIT {
void multiModalityEmbeddedImage(String modelName) throws IOException {
// @formatter:off
String response = ChatClient.builder(chatModel).build().prompt()
// TODO consider adding model(...) method to ChatClient as a shortcut to
// OpenAiChatOptions.builder().withModel(modelName).build()
String response = ChatClient.create(chatModel).prompt()
.options(OpenAiChatOptions.builder().withModel(modelName).build())
.user(u -> u.text("Explain what do you see on this picture?")
.media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/test.png")))
@@ -281,7 +278,7 @@ class OpenAiChatClientIT extends AbstractIT {
URL url = new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png");
// @formatter:off
String response = ChatClient.builder(chatModel).build().prompt()
String response = ChatClient.create(chatModel).prompt()
// TODO consider adding model(...) method to ChatClient as a shortcut to
// OpenAiChatOptions.builder().withModel(modelName).build()
.options(OpenAiChatOptions.builder().withModel(modelName).build())
@@ -302,7 +299,7 @@ class OpenAiChatClientIT extends AbstractIT {
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(chatModel).build().prompt()
Flux<String> response = ChatClient.create(chatModel).prompt()
.options(OpenAiChatOptions.builder().withModel(OpenAiApi.ChatModel.GPT_4_VISION_PREVIEW.getValue())
.build())
.user(u -> u.text("Explain what do you see on this picture?")

View File

@@ -3,7 +3,7 @@
The integration of function support in AI models, permits the model to request the execution of client-side functions, thereby accessing necessary information or performing tasks dynamically as required.
image::function-calling-basic-flow2.jpg[Function calling, width=500, align="center"]
image::function-calling-basic-flow2.jpg[Function calling, width=700, align="center"]
Spring AI currently supports Function invocation for the following AI Models

View File

@@ -51,6 +51,18 @@ var userMessage = new UserMessage(
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage)));
----
or with the fluent xref::api/chatclient.adoc[ChatClient] API:
[source,java]
----
String response = ChatClient.create(chatModel).prompt()
.user(u -> u.text("Explain what do you see on this picture?")
.media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/multimodal.test.png")))
.call()
.content();
----
and produce a response like:
> This is an image of a fruit bowl with a simple design. The bowl is made of metal with curved wire edges that create an open structure, allowing the fruit to be visible from all angles. Inside the bowl, there are two yellow bananas resting on top of what appears to be a red apple. The bananas are slightly overripe, as indicated by the brown spots on their peels. The bowl has a metal ring at the top, likely to serve as a handle for carrying. The bowl is placed on a flat surface with a neutral-colored background that provides a clear view of the fruit inside.
@@ -58,7 +70,7 @@ and produce a response like:
Latest version of Spring AI provides multimodal support for the following Chat Clients:
* xref:api/chat/openai-chat.adoc#_multimodal[Open AI - (GPT-4-Vision and GPT-4o models)]
* xref:api/chat/openai-chat.adoc#_multimodal[Ollama - (LlaVa and Baklava models)]
* xref:api/chat/ollama-chat.adoc#_multimodal[Ollama - (LlaVa and Baklava models)]
* xref:api/chat/vertexai-gemini-chat.adoc#_multimodal[Vertex AI Gemini - (gemini-pro-vision model)]
* xref:api/chat/anthropic-chat.adoc#_multimodal[Anthropic Claude 3]
* xref:api/chat/bedrock/bedrock-anthropic3.adoc#_multimodal[AWS Bedrock Anthropic Claude 3]

View File

@@ -107,7 +107,18 @@ record ActorsFilms(String actor, List<String> movies) {
}
----
Here is how to apply the BeanOutputConverter:
Here is how to apply the BeanOutputConverter using the new, fluent ChatClient API:
[source,java]
----
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
.user(u -> u.text("Generate the filmography of 5 movies for {actor}.")
.param("actor", "Tom Hanks"))
.call()
.entity(ActorsFilms.class);
----
or using the low-level, ChatModel API directly:
[source,java]
----
@@ -136,8 +147,19 @@ For example, to represent a list of actors and their filmographies:
[source,java]
----
BeanOutputConverter<List<ActorsFilmsRecord>> outputConverter = new BeanOutputConverter<>(
new ParameterizedTypeReference<List<ActorsFilmsRecord>>() { });
List<ActorsFilms> actorsFilms = ChatClient.create(chatModel).prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
});
----
or using the low-level, ChatModel API directly:
[source,java]
----
BeanOutputConverter<List<ActorsFilms>> outputConverter = new BeanOutputConverter<>(
new ParameterizedTypeReference<List<ActorsFilms>>() { });
String format = outputConverter.getFormat();
String template = """
@@ -149,13 +171,25 @@ Prompt prompt = new Prompt(new PromptTemplate(template, Map.of("format", format)
Generation generation = chatModel.call(prompt).getResult();
List<ActorsFilmsRecord> actorsFilms = outputConverter.convert(generation.getOutput().getContent());
List<ActorsFilms> actorsFilms = outputConverter.convert(generation.getOutput().getContent());
----
=== Map Output Converter
Following sniped shows how to use `MapOutputConverter` to generate a list of numbers.
[source,java]
----
Map<String, Object> result = ChatClient.create(chatModel).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()
.entity(new ParameterizedTypeReference<Map<String, Object>>() {
});
----
or using the low-level, ChatModel API directly:
[source,java]
----
MapOutputConverter mapOutputConverter = new MapOutputConverter();
@@ -177,6 +211,17 @@ Map<String, Object> result = mapOutputConverter.convert(generation.getOutput().g
Following snippet shows how to use `ListOutputConverter` to generate a list of ice cream flavors.
[source,java]
----
List<String> flavors = ChatClient.create(chatModel).prompt()
.user(u -> u.text("List five {subject}")
.param("subject", "ice cream flavors"))
.call()
.entity(new ListOutputConverter(new DefaultConversionService()));
----
or using the low-level, ChatModel API directly:
[source,java]
----
ListOutputConverter listOutputConverter = new ListOutputConverter(new DefaultConversionService());

View File

@@ -191,23 +191,16 @@ This is the reason to use a vector database. It is very good at finding similar
image::spring-ai-rag.jpg[Spring AI RAG, width=1000, align="center"]
There are several concepts that are used in implementing RAG.
The concepts map onto classes in Spring AI:
* `DocumentReader`: A Java functional interface that is responsible for loading a `List<Document>` from a data source. Common data sources are PDF, Markdown, and JSON.
* `Document`: A text-based representation of your data source that also contains metadata to describe the contents.
* `DocumentTransformer`: Responsible for processing the data in various ways (for example, splitting documents into smaller pieces or adding additional metadata to the `Document`).
* `DocumentWriter`: Lets you persist the Documents into a database (most commonly in the AI stack, a vector database).
* `Embedding`: A representation of your data as a `List<Double>` that is used by the vector database to compute the "`similarity`" of a user's query to relevant documents.
* The xref::api/etl-pipeline.adoc[ETL pipeline] provides further information about orchestrating the flow of extracting data from the data sources and stor it in a structured vector store, ensuring data is in the optimal format for retrieval by the AI model.
* The xref::api/chatclient.adoc#_retrieval_augmented_generation[ChatClient - RAG] explains how to use the `QuestionAnswerAdvisor` advisor to enable the RAG capability to your application.
== Function Calling
Large Language Models (LLMs) are frozen after training, leading to stale knowledge and they are unable to access or modify external data.
image::function-calling-basic-flow2.jpg[Function calling, width=800, align="center"]
image::function-calling-basic-flow2.jpg[Function calling, width=700, align="center"]
The `Function Calling` mechanism addresses these shortcomings.
The xref::api/functions.adoc[Function Calling] mechanism addresses these shortcomings.
It allows you to register your own functions to connect the large language models to the APIs of external systems.
These systems can provide LLMs with real-time data and perform data processing actions on their behalf.