Skip to content

Commit fd9c986

Browse files
committed
API refactoring
* Added more natural method names to DocumentReader, Transformer, Writer * Changed Content getMedia to return Collection, deprecated use of List * Change constructor for Media to accept URL and Resource, deprecate Object constructor * Update ETL documentation and a few tests to avoid now deprecated APIs.
1 parent 227f070 commit fd9c986

File tree

17 files changed

+165
-60
lines changed

17 files changed

+165
-60
lines changed

models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatClientIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,12 @@ void beanStreamOutputConverterRecords() {
182182
@Test
183183
void multiModalityTest() throws IOException {
184184

185-
byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray();
185+
var imageData = new ClassPathResource("/test.png");
186186

187187
var userMessage = new UserMessage("Explain what do you see on this picture?",
188188
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData)));
189189

190-
ChatResponse response = chatClient.call(new Prompt(List.of(userMessage)));
190+
var response = chatClient.call(new Prompt(List.of(userMessage)));
191191

192192
logger.info(response.getResult().getOutput().getContent());
193193
assertThat(response.getResult().getOutput().getContent()).contains("bananas", "apple", "basket");

models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatClientIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,12 @@ void beanStreamOutputConverterRecords() {
206206
@Test
207207
void multiModalityTest() throws IOException {
208208

209-
byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray();
209+
var imageData = new ClassPathResource("/test.png");
210210

211211
var userMessage = new UserMessage("Explain what do you see o this picture?",
212212
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData)));
213213

214-
ChatResponse response = client.call(new Prompt(List.of(userMessage)));
214+
var response = client.call(new Prompt(List.of(userMessage)));
215215

216216
logger.info(response.getResult().getOutput().getContent());
217217
assertThat(response.getResult().getOutput().getContent()).contains("bananas", "apple", "basket");

models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatClientMultimodalIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ public static void beforeAll() throws IOException, InterruptedException {
7171
@Test
7272
void multiModalityTest() throws IOException {
7373

74-
byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray();
74+
var imageData = new ClassPathResource("/test.png");
7575

7676
var userMessage = new UserMessage("Explain what do you see on this picture?",
7777
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData)));
7878

79-
ChatResponse response = client.call(new Prompt(List.of(userMessage)));
79+
var response = client.call(new Prompt(List.of(userMessage)));
8080

8181
logger.info(response.getResult().getOutput().getContent());
8282
assertThat(response.getResult().getOutput().getContent()).contains("bananas", "apple", "basket");

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.ai.openai.chat;
1717

1818
import java.io.IOException;
19+
import java.net.URL;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.List;
@@ -249,12 +250,12 @@ void streamFunctionCallTest() {
249250
@ValueSource(strings = { "gpt-4-vision-preview", "gpt-4o" })
250251
void multiModalityEmbeddedImage(String modelName) throws IOException {
251252

252-
byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray();
253+
var imageData = new ClassPathResource("/test.png");
253254

254255
var userMessage = new UserMessage("Explain what do you see on this picture?",
255256
List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData)));
256257

257-
ChatResponse response = chatClient
258+
var response = chatClient
258259
.call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().withModel(modelName).build()));
259260

260261
logger.info(response.getResult().getOutput().getContent());
@@ -266,9 +267,9 @@ void multiModalityEmbeddedImage(String modelName) throws IOException {
266267
@ValueSource(strings = { "gpt-4-vision-preview", "gpt-4o" })
267268
void multiModalityImageUrl(String modelName) throws IOException {
268269

269-
var userMessage = new UserMessage("Explain what do you see on this picture?",
270-
List.of(new Media(MimeTypeUtils.IMAGE_PNG,
271-
"https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png")));
270+
var userMessage = new UserMessage("Explain what do you see on this picture?", List
271+
.of(new Media(MimeTypeUtils.IMAGE_PNG,
272+
new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png"))));
272273

273274
ChatResponse response = chatClient
274275
.call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().withModel(modelName).build()));
@@ -281,9 +282,9 @@ void multiModalityImageUrl(String modelName) throws IOException {
281282
@Test
282283
void streamingMultiModalityImageUrl() throws IOException {
283284

284-
var userMessage = new UserMessage("Explain what do you see on this picture?",
285-
List.of(new Media(MimeTypeUtils.IMAGE_PNG,
286-
"https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png")));
285+
var userMessage = new UserMessage("Explain what do you see on this picture?", List
286+
.of(new Media(MimeTypeUtils.IMAGE_PNG,
287+
new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png"))));
287288

288289
Flux<ChatResponse> response = streamingChatClient.stream(new Prompt(List.of(userMessage),
289290
OpenAiChatOptions.builder().withModel(OpenAiApi.ChatModel.GPT_4_VISION_PREVIEW.getValue()).build()));

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.ai.openai.chat.service;
1818

1919
import java.util.List;
20+
import java.util.function.Supplier;
2021

2122
import io.qdrant.client.QdrantClient;
2223
import io.qdrant.client.QdrantGrpcClient;
@@ -112,7 +113,7 @@ void simpleChat() {
112113
void loadData() {
113114
JsonReader jsonReader = new JsonReader(bikesResource, "name", "price", "shortDescription", "description");
114115
var textSplitter = new TokenTextSplitter();
115-
List<Document> splitDocuments = textSplitter.apply(jsonReader.get());
116+
List<Document> splitDocuments = textSplitter.split(jsonReader.get());
116117

117118
for (Document splitDocument : splitDocuments) {
118119
splitDocument.getMetadata().put(TransformerContentType.EXTERNAL_KNOWLEDGE, "true");
@@ -121,6 +122,21 @@ void loadData() {
121122
vectorStore.accept(splitDocuments);
122123
}
123124

125+
void loadData2() {
126+
JsonReader jsonReader = null;
127+
TokenTextSplitter tokenTextSplitter = null;
128+
VectorStore vectorStore = null;
129+
130+
List<Document> documents = jsonReader.read();
131+
List<Document> splitDocuments = tokenTextSplitter.split(documents);
132+
vectorStore.write(splitDocuments);
133+
134+
// Now in java.util.Function style.
135+
136+
Supplier<List<Document>> docs = jsonReader::read;
137+
138+
}
139+
124140
@SpringBootConfiguration
125141
static class Config {
126142

models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClientIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,12 @@ void beanStreamOutputConverterRecords() {
186186
@Test
187187
void multiModalityTest() throws IOException {
188188

189-
byte[] data = new ClassPathResource("/vertex.test.png").getContentAsByteArray();
189+
var data = new ClassPathResource("/vertex.test.png");
190190

191191
var userMessage = new UserMessage("Explain what do you see o this picture?",
192192
List.of(new Media(MimeTypeUtils.IMAGE_PNG, data)));
193193

194-
ChatResponse response = client.call(new Prompt(List.of(userMessage)));
194+
var response = client.call(new Prompt(List.of(userMessage)));
195195

196196
// Response should contain something like:
197197
// I see a bunch of bananas in a golden basket. The bananas are ripe and yellow.

spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@
1818
import java.io.IOException;
1919
import java.io.InputStream;
2020
import java.nio.charset.Charset;
21-
import java.util.ArrayList;
22-
import java.util.Collections;
23-
import java.util.HashMap;
24-
import java.util.List;
25-
import java.util.Map;
26-
import java.util.Objects;
21+
import java.util.*;
2722

2823
import org.springframework.core.io.Resource;
2924
import org.springframework.util.Assert;
@@ -44,7 +39,7 @@ public abstract class AbstractMessage implements Message {
4439

4540
protected final String textContent;
4641

47-
protected final List<Media> mediaData;
42+
protected final List<Media> media;
4843

4944
/**
5045
* Additional options for the message to influence the response, not a generative map.
@@ -59,25 +54,25 @@ protected AbstractMessage(MessageType messageType, String content, Map<String, O
5954
Assert.notNull(messageType, "Message type must not be null");
6055
this.messageType = messageType;
6156
this.textContent = content;
62-
this.mediaData = new ArrayList<>();
57+
this.media = new ArrayList<>();
6358
this.metadata = new HashMap<>(metadata);
6459
this.metadata.put(MESSAGE_TYPE, messageType);
6560
}
6661

67-
protected AbstractMessage(MessageType messageType, String textContent, List<Media> mediaData) {
68-
this(messageType, textContent, mediaData, Map.of(MESSAGE_TYPE, messageType));
62+
protected AbstractMessage(MessageType messageType, String textContent, List<Media> media) {
63+
this(messageType, textContent, media, Map.of(MESSAGE_TYPE, messageType));
6964
}
7065

71-
protected AbstractMessage(MessageType messageType, String textContent, List<Media> mediaData,
66+
protected AbstractMessage(MessageType messageType, String textContent, Collection<Media> media,
7267
Map<String, Object> metadata) {
7368

7469
Assert.notNull(messageType, "Message type must not be null");
7570
Assert.notNull(textContent, "Content must not be null");
76-
Assert.notNull(mediaData, "media data must not be null");
71+
Assert.notNull(media, "media data must not be null");
7772

7873
this.messageType = messageType;
7974
this.textContent = textContent;
80-
this.mediaData = new ArrayList<>(mediaData);
75+
this.media = new ArrayList<>(media);
8176
this.metadata = new HashMap<>(metadata);
8277
this.metadata.put(MESSAGE_TYPE, messageType);
8378
}
@@ -94,7 +89,7 @@ protected AbstractMessage(MessageType messageType, Resource resource, Map<String
9489
this.messageType = messageType;
9590
this.metadata = new HashMap<>(metadata);
9691
this.metadata.put(MESSAGE_TYPE, messageType);
97-
this.mediaData = new ArrayList<>();
92+
this.media = new ArrayList<>();
9893

9994
try (InputStream inputStream = resource.getInputStream()) {
10095
this.textContent = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
@@ -110,8 +105,8 @@ public String getContent() {
110105
}
111106

112107
@Override
113-
public List<Media> getMedia() {
114-
return this.mediaData;
108+
public List<Media> getMedia(String... dummy) {
109+
return this.media;
115110
}
116111

117112
@Override
@@ -126,7 +121,7 @@ public MessageType getMessageType() {
126121

127122
@Override
128123
public int hashCode() {
129-
return Objects.hash(this.messageType, this.textContent, this.mediaData, this.metadata);
124+
return Objects.hash(this.messageType, this.textContent, this.media, this.metadata);
130125
}
131126

132127
@Override
@@ -139,8 +134,8 @@ public boolean equals(Object obj) {
139134
}
140135
AbstractMessage other = (AbstractMessage) obj;
141136
return Objects.equals(this.messageType, other.messageType)
142-
&& Objects.equals(this.textContent, other.textContent)
143-
&& Objects.equals(this.mediaData, other.mediaData) && Objects.equals(this.metadata, other.metadata);
137+
&& Objects.equals(this.textContent, other.textContent) && Objects.equals(this.media, other.media)
138+
&& Objects.equals(this.metadata, other.metadata);
144139
}
145140

146141
}

spring-ai-core/src/main/java/org/springframework/ai/chat/messages/Media.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
*/
1616
package org.springframework.ai.chat.messages;
1717

18+
import org.springframework.core.io.Resource;
1819
import org.springframework.util.Assert;
1920
import org.springframework.util.MimeType;
2021

22+
import java.io.IOException;
23+
import java.net.URL;
24+
2125
/**
2226
* The Media class represents the data and metadata of a media attachment in a message. It
2327
* consists of a MIME type and the raw data.
@@ -33,16 +37,46 @@ public class Media {
3337

3438
private final Object data;
3539

40+
/**
41+
* The Media class represents the data and metadata of a media attachment in a
42+
* message. It consists of a MIME type and the raw data.
43+
*
44+
* This class is used as a parameter in the constructor of the UserMessage class.
45+
* @deprecated This constructor is deprecated since version 1.0.0 M1 and will be
46+
* removed in a future release.
47+
*/
48+
@Deprecated(since = "1.0.0 M1", forRemoval = true)
3649
public Media(MimeType mimeType, Object data) {
3750
Assert.notNull(mimeType, "MimeType must not be null");
3851
this.mimeType = mimeType;
3952
this.data = data;
4053
}
4154

55+
public Media(MimeType mimeType, URL url) {
56+
Assert.notNull(mimeType, "MimeType must not be null");
57+
this.mimeType = mimeType;
58+
this.data = url.toString();
59+
}
60+
61+
public Media(MimeType mimeType, Resource resource) {
62+
Assert.notNull(mimeType, "MimeType must not be null");
63+
this.mimeType = mimeType;
64+
try {
65+
this.data = resource.getContentAsByteArray();
66+
}
67+
catch (IOException e) {
68+
throw new RuntimeException(e);
69+
}
70+
}
71+
4272
public MimeType getMimeType() {
4373
return this.mimeType;
4474
}
4575

76+
/**
77+
* Get the media data object
78+
* @return a java.net.URL.toString() or a byte[]
79+
*/
4680
public Object getData() {
4781
return this.data;
4882
}

spring-ai-core/src/main/java/org/springframework/ai/chat/messages/UserMessage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.ai.chat.messages;
1717

1818
import java.util.Arrays;
19+
import java.util.Collection;
1920
import java.util.List;
2021
import java.util.Map;
2122

@@ -44,7 +45,7 @@ public UserMessage(String textContent, Media... media) {
4445
this(textContent, Arrays.asList(media));
4546
}
4647

47-
public UserMessage(String textContent, List<Media> mediaList, Map<String, Object> metadata) {
48+
public UserMessage(String textContent, Collection<Media> mediaList, Map<String, Object> metadata) {
4849
super(MessageType.USER, textContent, mediaList, metadata);
4950
}
5051

spring-ai-core/src/main/java/org/springframework/ai/document/Document.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
*/
1616
package org.springframework.ai.document;
1717

18-
import java.util.ArrayList;
19-
import java.util.HashMap;
20-
import java.util.List;
21-
import java.util.Map;
18+
import java.util.*;
2219

2320
import com.fasterxml.jackson.annotation.JsonCreator;
2421
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -112,7 +109,7 @@ public String getContent() {
112109
}
113110

114111
@Override
115-
public List<Media> getMedia() {
112+
public List<Media> getMedia(String... dummy) {
116113
return this.media;
117114
}
118115

spring-ai-core/src/main/java/org/springframework/ai/document/DocumentReader.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@
2020

2121
public interface DocumentReader extends Supplier<List<Document>> {
2222

23+
default List<Document> read() {
24+
return get();
25+
}
26+
2327
}

spring-ai-core/src/main/java/org/springframework/ai/document/DocumentTransformer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@
2020

2121
public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
2222

23+
default List<Document> transform(List<Document> transform) {
24+
return apply(transform);
25+
}
26+
2327
}

spring-ai-core/src/main/java/org/springframework/ai/document/DocumentWriter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@
2323
*/
2424
public interface DocumentWriter extends Consumer<List<Document>> {
2525

26+
default void write(List<Document> documents) {
27+
accept(documents);
28+
}
29+
2630
}

spring-ai-core/src/main/java/org/springframework/ai/model/Content.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.springframework.ai.chat.messages.Media;
44

5+
import java.util.Collection;
56
import java.util.List;
67
import java.util.Map;
78

@@ -24,7 +25,19 @@ public interface Content {
2425
/**
2526
* Get the media associated with the content.
2627
*/
27-
List<Media> getMedia();
28+
default Collection<Media> getMedia() {
29+
return getMedia("");
30+
}
31+
32+
/**
33+
* Retrieves the collection of media attachments associated with the content.
34+
* @param dummy a dummy parameter to ensure method signature uniqueness
35+
* @return a list of Media objects representing the media attachments
36+
* @deprecated This method is deprecated since version 1.0.0 M1 and will be removed in
37+
* a future release
38+
*/
39+
@Deprecated(since = "1.0.0 M1", forRemoval = true)
40+
List<Media> getMedia(String... dummy);
2841

2942
/**
3043
* return Get the metadata associated with the content.

0 commit comments

Comments
 (0)