Skip to content

Commit d6f4b71

Browse files
committed
Improve ChatClient API docs
1 parent a8fa186 commit d6f4b71

File tree

4 files changed

+88
-44
lines changed

4 files changed

+88
-44
lines changed

models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ class OpenAiChatClientIT extends AbstractIT {
5757
@Value("classpath:/prompts/system-message.st")
5858
private Resource systemTextResource;
5959

60+
record ActorsFilms(String actor, List<String> movies) {
61+
}
62+
6063
@Test
6164
void roleTest() {
6265

@@ -92,16 +95,15 @@ void listOutputConverter() {
9295
void listOutputConverter2() {
9396

9497
// @formatter:off
95-
List<ActorsFilmsRecord> actorsFilms = ChatClient.builder(chatModel).build().prompt()
98+
List<ActorsFilms> actorsFilms = ChatClient.builder(chatModel).build().prompt()
9699
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
97100
.call()
98-
.entity(new ParameterizedTypeReference<List<ActorsFilmsRecord>>() {
101+
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
99102
});
100103
// @formatter:on
101104

102105
logger.info("" + actorsFilms);
103106
assertThat(actorsFilms).hasSize(2);
104-
105107
}
106108

107109
@Test
@@ -129,20 +131,17 @@ void beanOutputConverter() {
129131
// @formatter:on
130132

131133
logger.info("" + actorsFilms);
132-
assertThat(actorsFilms.getActor()).isNotBlank();
133-
}
134-
135-
record ActorsFilmsRecord(String actor, List<String> movies) {
134+
assertThat(actorsFilms.actor()).isNotBlank();
136135
}
137136

138137
@Test
139138
void beanOutputConverterRecords() {
140139

141140
// @formatter:off
142-
ActorsFilmsRecord actorsFilms = ChatClient.builder(chatModel).build().prompt()
141+
ActorsFilms actorsFilms = ChatClient.builder(chatModel).build().prompt()
143142
.user("Generate the filmography of 5 movies for Tom Hanks.")
144143
.call()
145-
.entity(ActorsFilmsRecord.class);
144+
.entity(ActorsFilms.class);
146145
// @formatter:on
147146

148147
logger.info("" + actorsFilms);
@@ -153,7 +152,7 @@ void beanOutputConverterRecords() {
153152
@Test
154153
void beanStreamOutputConverterRecords() {
155154

156-
BeanOutputConverter<ActorsFilmsRecord> outputConverter = new BeanOutputConverter<>(ActorsFilmsRecord.class);
155+
BeanOutputConverter<ActorsFilms> outputConverter = new BeanOutputConverter<>(ActorsFilms.class);
157156

158157
// @formatter:off
159158
Flux<String> chatResponse = ChatClient.builder(chatModel)
@@ -172,7 +171,7 @@ void beanStreamOutputConverterRecords() {
172171
.collect(Collectors.joining());
173172
// @formatter:on
174173

175-
ActorsFilmsRecord actorsFilms = outputConverter.convert(generationTextFromStream);
174+
ActorsFilms actorsFilms = outputConverter.convert(generationTextFromStream);
176175

177176
logger.info("" + actorsFilms);
178177
assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks");

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
The `ChatClient` offers a fluent API for stateless interaction with an AI Model. It supports both a synchronous and reactive programming model.
55

6-
The fluent API has methods for building up the constituent parts of a `Prompt` that is passed to the AI model as input.
6+
The fluent API has methods for building up the constituent parts of a xref:api/prompt.adoc#_prompts[Prompt] that is passed to the AI model as input.
77
The `Prompt` contains the instructional text to guide the AI model's output and behavior. From the API point of view, prompts consist of a collection of messages.
88

99
The AI model processes two main types of messages: user messages, which are direct inputs from the user, and system messages, which are generated by the system to guide the conversation.
@@ -12,12 +12,18 @@ These messages often contain template placeholders that are substituted at runti
1212

1313
There are also Prompt options that can be specified., such as the name of the AI Model to generate content and the temperature setting that controls the randomness or creativity of the generated output.
1414

15-
== Using an autoconfigured ChatClient.Builder
15+
== Creating a ChatClient
16+
17+
The `ChatClient` is created using a `ChatClient.Builder` object.
18+
You can obtain an autoconfigured `ChatClient.Builder` instance for any xref:api/chatmodel.adoc[ChatModel] Spring Boot autoconfiguration or create one programmatically.
19+
20+
=== Using an autoconfigured ChatClient.Builder
1621

1722
In the most simple use case, Spring AI provides Spring Boot autoconfiguration, creating a prototype `ChatClient.Builder` bean for you to inject into your class.
1823
Here is a simple example of retrieving a String response to a simple user request.
1924

20-
```java
25+
[source,java]
26+
----
2127
@RestController
2228
class MyController {
2329
@@ -35,61 +41,100 @@ class MyController {
3541
.content();
3642
}
3743
}
38-
```
44+
----
3945

40-
In this simple example, the user input sets the contents of the user message. The call method sends a request to the AI model, and the context method returns the AI model's response as a String.
46+
In this simple example, the user input sets the contents of the user message.
47+
The call method sends a request to the AI model, and the context method returns the AI model's response as a String.
4148

49+
=== Create a ChatClient programmatically
4250

43-
== Returing a `ChatResponse`
51+
You can disable the `ChatClient.Builder` autoconfiguration by setting the property `spring.ai.chat.client.enabled=false`.
52+
This is useful if multiple chat models are used together.
53+
Then create a `ChatClient.Builder` instance for for every `ChatModel` programmatically:
4454

45-
The response from the AI model is a rich structure defined by the type ChatResponse.
46-
ChatResponse includes metadata about how the response was generated and can also contain multiple responses, known as generations, each with its own metadata.
47-
The metadata includes the number of tokens (each token is approximately 3/4 of a word) used to create the response. This information is important because hosted AI models charge based on the number of tokens used per request.
55+
[source,java]
56+
----
57+
ChatModel myChatModel = ... // usually autowired
4858
49-
An example to return the `ChatResponse` object that contains the metadata is shown below by invoking `chatResponse()` after the `call()` method.
59+
ChatClient.Builder builder = ChatClient.builder(myChatModel);
60+
61+
// or create a ChatClient with the default builder settings:
62+
63+
ChatClient chatClient = ChatClient.create(myChatModel);
64+
----
65+
66+
== ChatClient Responses
67+
68+
The ChatClient API offers several ways to format the response from the AI Model.
5069

70+
=== Returning a ChatResponse
5171

52-
```java
53-
ChatResponse chatResponse = this.chatClient.prompt()
72+
The response from the AI model is a rich structure defined by the type xref:api/chatmodel.adoc#_chatresponse[ChatResponse].
73+
It includes metadata about how the response was generated and can also contain multiple responses, known as xref:api/chatmodel.adoc#_generation[Generation]s, each with its own metadata.
74+
The metadata includes the number of tokens (each token is approximately 3/4 of a word) used to create the response.
75+
This information is important because hosted AI models charge based on the number of tokens used per request.
76+
77+
An example to return the `ChatResponse` object that contains the metadata is shown below by invoking `chatResponse()` after the `call()` method.
78+
79+
[source,java]
80+
----
81+
ChatResponse chatResponse = chatClient.prompt()
5482
.user("Tell me a joke")
5583
.call()
5684
.chatResponse();
57-
```
85+
----
5886

59-
== Returning an Entity
87+
=== Returning an Entity
6088

61-
You often want to return an entity class that is mapped from the returned String. The `entity` method provides this functionality.
89+
You often want to return an entity class that is mapped from the returned `String`.
90+
The `entity` method provides this functionality.
6291

6392
For example, given the Java record:
6493

65-
```java
94+
[source,java]
95+
----
6696
record ActorFilms(String actor, List<String> movies) {
6797
}
68-
```
98+
----
6999

70100
You can easily map the AI model's output to this record using the `entity` method, as shown below:
71101

72-
```java
102+
[source,java]
103+
----
73104
ActorFilms actorFilms = chatClient.prompt()
74105
.user("Generate the filmography for a random actor.")
75106
.call()
76107
.entity(ActorFilms.class);
77-
```
108+
----
78109

79-
There is also an overloaded `entity` method with the signature `entity(ParameterizedTypeReference<T> type)` that lets you specify types such as generic Lists.
110+
There is also an overloaded `entity` method with the signature `entity(ParameterizedTypeReference<T> type)` that lets you specify types such as generic Lists:
80111

81-
== Streaming Responses
112+
[source,java]
113+
----
114+
List<ActorFilms> actorFilms = chatClient.prompt()
115+
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
116+
.call()
117+
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
118+
});
119+
----
120+
121+
=== Streaming Responses
82122

83123
The `stream` lets you get an asynchronous response as shown below
84-
```java
85-
Flux<String> output = this.chatClient.prompt()
124+
125+
[source,java]
126+
----
127+
Flux<String> output = chatClient.prompt()
86128
.user("Tell me a joke")
87129
.stream()
88130
.content();
89-
```
131+
----
90132

91133
You can also stream the `ChatResponse` using the method `Flux<ChatResponse> chatResponse()`.
92134

135+
TIP: The chunked stream response can not be converted into a Java entities automatically.
136+
Use the xref:api/structured-output-converter.adoc#_structuredoutputconverter[Structured Output Converter] to convert the aggregated response.
137+
93138
== Using defaults and parameters
94139

95140
It is often useful to create a `ChatClient` with default user and/or system text defined at design time.

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ This section provides a guide to the Spring AI Chat Model API interface and asso
1717

1818
=== ChatModel
1919

20-
Here is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/ChatModel.java[ChatModel] interface definition:
20+
Here is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat//model/ChatModel.java[ChatModel] interface definition:
2121

2222
[source,java]
2323
----
@@ -37,7 +37,7 @@ In real-world applications, it is more common to use the `call` method that take
3737

3838
=== StreamingChatModel
3939

40-
Here is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/StreamingChatModel.java[StreamingChatModel] interface definition:
40+
Here is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/model/StreamingChatModel.java[StreamingChatModel] interface definition:
4141

4242
[source,java]
4343
----
@@ -136,6 +136,7 @@ This is a powerful feature that allows developers to use model specific options
136136

137137
image::chat-options-flow.jpg[align="center", width="800px"]
138138

139+
[[ChatResponse]]
139140
=== ChatResponse
140141

141142
The structure of the `ChatResponse` class is as follows:
@@ -157,13 +158,14 @@ public class ChatResponse implements ModelResponse<Generation> {
157158
}
158159
----
159160

160-
The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/ChatResponse.java[ChatResponse] class holds the AI Model's output, with each `Generation` instance containing one of potentially multiple outputs resulting from a single prompt.
161+
The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/model/ChatResponse.java[ChatResponse] class holds the AI Model's output, with each `Generation` instance containing one of potentially multiple outputs resulting from a single prompt.
161162

162163
The `ChatResponse` class also carries a `ChatResponseMetadata` metadata about the AI Model's response.
163164

165+
[[Generation]]
164166
=== Generation
165167

166-
Finally, the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/Generation.java[Generation] class extends from the `ModelResult` to represent the output assistant message response and related metadata about this result:
168+
Finally, the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/model/Generation.java[Generation] class extends from the `ModelResult` to represent the output assistant message response and related metadata about this result:
167169

168170
[source,java]
169171
----

spring-ai-docs/src/main/antora/modules/ROOT/pages/concepts.adoc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,15 @@ Anthropic's Claude AI model features a 100K token limit, and Meta's recent resea
136136
To summarize the collected works of Shakespeare with GPT4, you need to devise software engineering strategies to chop up the data and present the data within the model's context window limits.
137137
The Spring AI project helps you with this task.
138138

139-
== Output Parsing
139+
== Structured Output
140140

141141
The output of AI models traditionally arrives as a `java.lang.String`, even if you ask for the reply to be in JSON.
142142
It may be the correct JSON, but it is not a JSON data structure. It is just a string.
143143
Also, asking "`for JSON`" as part of the prompt is not 100% accurate.
144144

145-
This intricacy has led to the emergence of a specialized field involving the creation of prompts to yield the intended output, followed by parsing the resulting simple string into a usable data structure for application integration.
145+
This intricacy has led to the emergence of a specialized field involving the creation of prompts to yield the intended output, followed by converting the resulting simple string into a usable data structure for application integration.
146146

147-
Output parsing employs meticulously crafted prompts, often necessitating multiple interactions with the model to achieve the desired formatting.
148-
149-
This challenge has prompted OpenAI to introduce 'OpenAI Functions' as a means to specify the desired output format from the model precisely.
147+
The xref:api/structured-output-converter.adoc#_structuredoutputconverter[Structured output conversion] employs meticulously crafted prompts, often necessitating multiple interactions with the model to achieve the desired formatting.
150148

151149
== Bringing Your Data to the AI model
152150

0 commit comments

Comments
 (0)