Skip to content

Commit 285b9ff

Browse files
committed
Make Document support single text or media content
The Document class previously allowed multiple media entries while also having a text field, leading to ambiguity in content handling. This change enforces a clear separation between text and media documents to prevent content type confusion and simplify document processing. A Document now must contain either text content or a single media entry, but never both. This aligns with the class's primary use in ETL pipelines where clear content type boundaries are essential for proper embedding generation and vector database storage. Additional architectural changes: - Document now implements a cleaner API by removing deprecated methods - Removed MediaContent interface implementation from Document class - Document.getMedia() now returns a single Media object instead of Collection - Removed EMPTY_TEXT constant in favor of proper null handling - Constructor signatures simplified and streamlined - Builder pattern improved to enforce single content type constraint The breaking changes include: - Media is now a single entry instead of a collection - Content field renamed to text for clarity - Removed support for mixed content types - Simplified builder API to prevent ambiguous construction We prefer using text-related methods over deprecated content methods to better reflect the actual content type being handled and improve API clarity.
1 parent e01fd11 commit 285b9ff

File tree

186 files changed

+974
-829
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

186 files changed

+974
-829
lines changed

document-readers/markdown-reader/src/main/java/org/springframework/ai/reader/markdown/MarkdownDocumentReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ private void buildAndFlush() {
226226
if (!this.currentParagraphs.isEmpty()) {
227227
String content = String.join("", this.currentParagraphs);
228228

229-
Document.Builder builder = this.currentDocumentBuilder.content(content);
229+
Document.Builder builder = this.currentDocumentBuilder.text(content);
230230

231231
this.config.additionalMetadata.forEach(builder::metadata);
232232

models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
378378
.filter(message -> message.getMessageType() != MessageType.SYSTEM)
379379
.map(message -> {
380380
if (message.getMessageType() == MessageType.USER) {
381-
List<ContentBlock> contents = new ArrayList<>(List.of(new ContentBlock(message.getContent())));
381+
List<ContentBlock> contents = new ArrayList<>(List.of(new ContentBlock(message.getText())));
382382
if (message instanceof UserMessage userMessage) {
383383
if (!CollectionUtils.isEmpty(userMessage.getMedia())) {
384384
List<ContentBlock> mediaContent = userMessage.getMedia().stream().map(media -> {
@@ -395,8 +395,8 @@ ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
395395
else if (message.getMessageType() == MessageType.ASSISTANT) {
396396
AssistantMessage assistantMessage = (AssistantMessage) message;
397397
List<ContentBlock> contentBlocks = new ArrayList<>();
398-
if (StringUtils.hasText(message.getContent())) {
399-
contentBlocks.add(new ContentBlock(message.getContent()));
398+
if (StringUtils.hasText(message.getText())) {
399+
contentBlocks.add(new ContentBlock(message.getText()));
400400
}
401401
if (!CollectionUtils.isEmpty(assistantMessage.getToolCalls())) {
402402
for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {
@@ -423,7 +423,7 @@ else if (message.getMessageType() == MessageType.TOOL) {
423423
String systemPrompt = prompt.getInstructions()
424424
.stream()
425425
.filter(m -> m.getMessageType() == MessageType.SYSTEM)
426-
.map(m -> m.getContent())
426+
.map(m -> m.getText())
427427
.collect(Collectors.joining(System.lineSeparator()));
428428

429429
ChatCompletionRequest request = new ChatCompletionRequest(this.defaultOptions.getModel(), userMessages,

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ void roleTest(String modelName) {
105105
.isEqualTo(response.getMetadata().getUsage().getPromptTokens()
106106
+ response.getMetadata().getUsage().getGenerationTokens());
107107
Generation generation = response.getResults().get(0);
108-
assertThat(generation.getOutput().getContent()).contains("Blackbeard");
108+
assertThat(generation.getOutput().getText()).contains("Blackbeard");
109109
assertThat(generation.getMetadata().getFinishReason()).isEqualTo("end_turn");
110110
logger.info(response.toString());
111111
}
@@ -120,13 +120,13 @@ void testMessageHistory() {
120120
AnthropicChatOptions.builder().withModel("claude-3-sonnet-20240229").build());
121121

122122
ChatResponse response = this.chatModel.call(prompt);
123-
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Blackbeard", "Bartholomew");
123+
assertThat(response.getResult().getOutput().getText()).containsAnyOf("Blackbeard", "Bartholomew");
124124

125125
var promptWithMessageHistory = new Prompt(List.of(new UserMessage("Dummy"), response.getResult().getOutput(),
126126
new UserMessage("Repeat the last assistant message.")));
127127
response = this.chatModel.call(promptWithMessageHistory);
128128

129-
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Blackbeard", "Bartholomew");
129+
assertThat(response.getResult().getOutput().getText()).containsAnyOf("Blackbeard", "Bartholomew");
130130
}
131131

132132
@Test
@@ -162,7 +162,7 @@ void listOutputConverter() {
162162
Prompt prompt = new Prompt(promptTemplate.createMessage());
163163
Generation generation = this.chatModel.call(prompt).getResult();
164164

165-
List<String> list = listOutputConverter.convert(generation.getOutput().getContent());
165+
List<String> list = listOutputConverter.convert(generation.getOutput().getText());
166166
assertThat(list).hasSize(5);
167167
}
168168

@@ -180,7 +180,7 @@ void mapOutputConverter() {
180180
Prompt prompt = new Prompt(promptTemplate.createMessage());
181181
Generation generation = this.chatModel.call(prompt).getResult();
182182

183-
Map<String, Object> result = mapOutputConverter.convert(generation.getOutput().getContent());
183+
Map<String, Object> result = mapOutputConverter.convert(generation.getOutput().getText());
184184
assertThat(result.get("numbers")).isEqualTo(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
185185

186186
}
@@ -199,7 +199,7 @@ void beanOutputConverterRecords() {
199199
Prompt prompt = new Prompt(promptTemplate.createMessage());
200200
Generation generation = this.chatModel.call(prompt).getResult();
201201

202-
ActorsFilmsRecord actorsFilms = beanOutputConverter.convert(generation.getOutput().getContent());
202+
ActorsFilmsRecord actorsFilms = beanOutputConverter.convert(generation.getOutput().getText());
203203
logger.info("" + actorsFilms);
204204
assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks");
205205
assertThat(actorsFilms.movies()).hasSize(5);
@@ -225,7 +225,7 @@ void beanStreamOutputConverterRecords() {
225225
.map(ChatResponse::getResults)
226226
.flatMap(List::stream)
227227
.map(Generation::getOutput)
228-
.map(AssistantMessage::getContent)
228+
.map(AssistantMessage::getText)
229229
.collect(Collectors.joining());
230230

231231
ActorsFilmsRecord actorsFilms = beanOutputConverter.convert(generationTextFromStream);
@@ -244,8 +244,8 @@ void multiModalityTest() throws IOException {
244244

245245
var response = this.chatModel.call(new Prompt(List.of(userMessage)));
246246

247-
logger.info(response.getResult().getOutput().getContent());
248-
assertThat(response.getResult().getOutput().getContent()).contains("banan", "apple", "basket");
247+
logger.info(response.getResult().getOutput().getText());
248+
assertThat(response.getResult().getOutput().getText()).contains("banan", "apple", "basket");
249249
}
250250

251251
@Test
@@ -262,7 +262,7 @@ void multiModalityPdfTest() throws IOException {
262262
.withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
263263
.build()));
264264

265-
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Spring AI", "portable API");
265+
assertThat(response.getResult().getOutput().getText()).containsAnyOf("Spring AI", "portable API");
266266
}
267267

268268
@Test
@@ -288,7 +288,7 @@ void functionCallTest() {
288288
logger.info("Response: {}", response);
289289

290290
Generation generation = response.getResult();
291-
assertThat(generation.getOutput().getContent()).contains("30", "10", "15");
291+
assertThat(generation.getOutput().getText()).contains("30", "10", "15");
292292
}
293293

294294
@Test
@@ -317,7 +317,7 @@ void streamFunctionCallTest() {
317317
.block()
318318
.stream()
319319
.filter(cr -> cr.getResult() != null)
320-
.map(cr -> cr.getResult().getOutput().getContent())
320+
.map(cr -> cr.getResult().getOutput().getText())
321321
.collect(Collectors.joining());
322322

323323
logger.info("Response: {}", content);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ void observationForChatOperation() {
7979
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
8080

8181
ChatResponse chatResponse = this.chatModel.call(prompt);
82-
assertThat(chatResponse.getResult().getOutput().getContent()).isNotEmpty();
82+
assertThat(chatResponse.getResult().getOutput().getText()).isNotEmpty();
8383

8484
ChatResponseMetadata responseMetadata = chatResponse.getMetadata();
8585
assertThat(responseMetadata).isNotNull();
@@ -109,7 +109,7 @@ void observationForStreamingChatOperation() {
109109
String aggregatedResponse = responses.subList(0, responses.size() - 1)
110110
.stream()
111111
.filter(r -> r.getResult() != null)
112-
.map(r -> r.getResult().getOutput().getContent())
112+
.map(r -> r.getResult().getOutput().getText())
113113
.collect(Collectors.joining());
114114
assertThat(aggregatedResponse).isNotEmpty();
115115

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ void call() {
8484

8585
logger.info("" + response);
8686
assertThat(response.getResults()).hasSize(1);
87-
assertThat(response.getResults().get(0).getOutput().getContent()).contains("Blackbeard");
87+
assertThat(response.getResults().get(0).getOutput().getText()).contains("Blackbeard");
8888
}
8989

9090
@Test

models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ private List<ChatRequestMessage> fromSpringAiMessage(Message message) {
402402
case USER:
403403
// https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/openai/azure-ai-openai/README.md#text-completions-with-images
404404
List<ChatMessageContentItem> items = new ArrayList<>();
405-
items.add(new ChatMessageTextContentItem(message.getContent()));
405+
items.add(new ChatMessageTextContentItem(message.getText()));
406406
if (message instanceof UserMessage userMessage) {
407407
if (!CollectionUtils.isEmpty(userMessage.getMedia())) {
408408
items.addAll(userMessage.getMedia()
@@ -413,7 +413,7 @@ private List<ChatRequestMessage> fromSpringAiMessage(Message message) {
413413
}
414414
return List.of(new ChatRequestUserMessage(items));
415415
case SYSTEM:
416-
return List.of(new ChatRequestSystemMessage(message.getContent()));
416+
return List.of(new ChatRequestSystemMessage(message.getText()));
417417
case ASSISTANT:
418418
AssistantMessage assistantMessage = (AssistantMessage) message;
419419
List<ChatCompletionsToolCall> toolCalls = null;
@@ -425,7 +425,7 @@ private List<ChatRequestMessage> fromSpringAiMessage(Message message) {
425425
.map(tc -> ((ChatCompletionsToolCall) tc)) // !!!
426426
.toList();
427427
}
428-
var azureAssistantMessage = new ChatRequestAssistantMessage(message.getContent());
428+
var azureAssistantMessage = new ChatRequestAssistantMessage(message.getText());
429429
azureAssistantMessage.setToolCalls(toolCalls);
430430
return List.of(azureAssistantMessage);
431431
case TOOL:

models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatClientIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ void call() {
6868
// @formatter:on
6969

7070
assertThat(response.getResults()).hasSize(1);
71-
assertThat(response.getResults().get(0).getOutput().getContent()).contains("Blackbeard");
71+
assertThat(response.getResults().get(0).getOutput().getText()).contains("Blackbeard");
7272
}
7373

7474
@Test
@@ -94,7 +94,7 @@ void beanStreamOutputConverterRecords() {
9494

9595
String generationTextFromStream = chatResponses
9696
.stream()
97-
.map(cr -> cr.getResult().getOutput().getContent())
97+
.map(cr -> cr.getResult().getOutput().getText())
9898
.collect(Collectors.joining());
9999
// @formatter:on
100100

models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatModelIT.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void roleTest() {
7777

7878
Prompt prompt = new Prompt(List.of(userMessage, systemMessage));
7979
ChatResponse response = this.chatModel.call(prompt);
80-
assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard");
80+
assertThat(response.getResult().getOutput().getText()).contains("Blackbeard");
8181
}
8282

8383
@Test
@@ -96,14 +96,14 @@ void testMessageHistory() {
9696
Prompt prompt = new Prompt(List.of(userMessage, systemMessage));
9797

9898
ChatResponse response = this.chatModel.call(prompt);
99-
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Blackbeard");
99+
assertThat(response.getResult().getOutput().getText()).containsAnyOf("Blackbeard");
100100

101101
var promptWithMessageHistory = new Prompt(List.of(new UserMessage("Dummy"), response.getResult().getOutput(),
102102
new UserMessage("Repeat the last assistant message.")));
103103
response = this.chatModel.call(promptWithMessageHistory);
104104

105-
System.out.println(response.getResult().getOutput().getContent());
106-
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("Blackbeard");
105+
System.out.println(response.getResult().getOutput().getText());
106+
assertThat(response.getResult().getOutput().getText()).containsAnyOf("Blackbeard");
107107
}
108108

109109
@Test
@@ -121,7 +121,7 @@ void listOutputConverter() {
121121
Prompt prompt = new Prompt(promptTemplate.createMessage());
122122
Generation generation = this.chatModel.call(prompt).getResult();
123123

124-
List<String> list = outputConverter.convert(generation.getOutput().getContent());
124+
List<String> list = outputConverter.convert(generation.getOutput().getText());
125125
assertThat(list).hasSize(5);
126126

127127
}
@@ -140,7 +140,7 @@ void mapOutputConverter() {
140140
Prompt prompt = new Prompt(promptTemplate.createMessage());
141141
Generation generation = this.chatModel.call(prompt).getResult();
142142

143-
Map<String, Object> result = outputConverter.convert(generation.getOutput().getContent());
143+
Map<String, Object> result = outputConverter.convert(generation.getOutput().getText());
144144
assertThat(result.get("numbers")).isEqualTo(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
145145

146146
}
@@ -159,7 +159,7 @@ void beanOutputConverter() {
159159
Prompt prompt = new Prompt(promptTemplate.createMessage());
160160
Generation generation = this.chatModel.call(prompt).getResult();
161161

162-
ActorsFilms actorsFilms = outputConverter.convert(generation.getOutput().getContent());
162+
ActorsFilms actorsFilms = outputConverter.convert(generation.getOutput().getText());
163163
assertThat(actorsFilms.actor()).isNotNull();
164164
}
165165

@@ -177,7 +177,7 @@ void beanOutputConverterRecords() {
177177
Prompt prompt = new Prompt(promptTemplate.createMessage());
178178
Generation generation = this.chatModel.call(prompt).getResult();
179179

180-
ActorsFilmsRecord actorsFilms = outputConverter.convert(generation.getOutput().getContent());
180+
ActorsFilmsRecord actorsFilms = outputConverter.convert(generation.getOutput().getText());
181181
logger.info("" + actorsFilms);
182182
assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks");
183183
assertThat(actorsFilms.movies()).hasSize(5);
@@ -203,7 +203,7 @@ void beanStreamOutputConverterRecords() {
203203
.map(ChatResponse::getResults)
204204
.flatMap(List::stream)
205205
.map(Generation::getOutput)
206-
.map(AssistantMessage::getContent)
206+
.map(AssistantMessage::getText)
207207
.filter(Objects::nonNull)
208208
.collect(Collectors.joining());
209209

models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatModelObservationIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void observationForImperativeChatOperation() {
7676
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
7777

7878
ChatResponse chatResponse = this.chatModel.call(prompt);
79-
assertThat(chatResponse.getResult().getOutput().getContent()).isNotEmpty();
79+
assertThat(chatResponse.getResult().getOutput().getText()).isNotEmpty();
8080

8181
ChatResponseMetadata responseMetadata = chatResponse.getMetadata();
8282
assertThat(responseMetadata).isNotNull();
@@ -106,7 +106,7 @@ void observationForStreamingChatOperation() {
106106

107107
String aggregatedResponse = responses.subList(0, responses.size() - 1)
108108
.stream()
109-
.map(r -> r.getResult().getOutput().getContent())
109+
.map(r -> r.getResult().getOutput().getText())
110110
.collect(Collectors.joining());
111111
assertThat(aggregatedResponse).isNotEmpty();
112112

models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/function/AzureOpenAiChatModelFunctionCallIT.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ void functionCallTest() {
8080

8181
logger.info("Response: {}", response);
8282

83-
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
83+
assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15");
8484
}
8585

8686
@Test
@@ -104,7 +104,7 @@ void functionCallSequentialTest() {
104104

105105
logger.info("Response: {}", response);
106106

107-
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
107+
assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15");
108108
}
109109

110110
@Test
@@ -132,7 +132,7 @@ void streamFunctionCallTest() {
132132
.map(ChatResponse::getResults)
133133
.flatMap(List::stream)
134134
.map(Generation::getOutput)
135-
.map(AssistantMessage::getContent)
135+
.map(AssistantMessage::getText)
136136
.collect(Collectors.joining());
137137
logger.info("Response: {}", content);
138138

@@ -169,7 +169,7 @@ void functionCallSequentialAndStreamTest() {
169169
.map(ChatResponse::getResults)
170170
.flatMap(List::stream)
171171
.map(Generation::getOutput)
172-
.map(AssistantMessage::getContent)
172+
.map(AssistantMessage::getText)
173173
.filter(Objects::nonNull)
174174
.collect(Collectors.joining());
175175

0 commit comments

Comments
 (0)