Skip to content

Commit 876a0b7

Browse files
Merge branch 'spring-projects:main' into main
2 parents 96e2626 + 1280c2a commit 876a0b7

File tree

779 files changed

+22318
-11913
lines changed

Some content is hidden

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

779 files changed

+22318
-11913
lines changed

.github/workflows/source-code-format-check.yml renamed to .github/workflows/pr-check.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Source Code Format
1+
name: PR Check
22

33
on:
44
pull_request:
@@ -20,6 +20,6 @@ jobs:
2020
distribution: 'temurin'
2121
cache: 'maven'
2222

23-
- name: Source code formatting check
23+
- name: Run tests
2424
run: |
25-
./mvnw spring-javaformat:validate
25+
./mvnw test

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Please refer to the [Getting Started Guide](https://docs.spring.io/spring-ai/ref
4646
* [Issues](https://github.com/spring-projects/spring-ai/issues)
4747
<!-- * [Discussions](https://github.com/spring-projects/spring-ai/discussions) - Go here if you have a question, suggestion, or feedback! -->
4848
* [Awesome Spring AI](https://github.com/danvega/awesome-spring-ai) - A curated list of awesome resources, tools, tutorials, and projects for building generative AI applications using Spring AI
49+
* [Spring AI Examples](https://github.com/spring-projects/spring-ai-examples) contains example projects that explain specific features in more detail.
4950

5051
## Breaking changes
5152

@@ -133,7 +134,4 @@ To build with checkstyles enabled.
133134
Checkstyles are currently disabled, but you can enable them by doing the following:
134135
```shell
135136
./mvnw clean package -DskipTests -Ddisable.checks=false
136-
```
137-
138-
139-
137+
```

document-readers/markdown-reader/src/test/java/org/springframework/ai/reader/markdown/MarkdownDocumentReaderTest.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ void testOnlyHeadersWithParagraphs() {
3939
List<Document> documents = reader.get();
4040

4141
assertThat(documents).hasSize(4)
42-
.extracting(Document::getMetadata, Document::getContent)
42+
.extracting(Document::getMetadata, Document::getText)
4343
.containsOnly(tuple(Map.of("category", "header_1", "title", "Header 1a"),
4444
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur diam eros, laoreet sit amet cursus vitae, varius sed nisi. Cras sit amet quam quis velit commodo porta consectetur id nisi. Phasellus tincidunt pulvinar augue."),
4545
tuple(Map.of("category", "header_1", "title", "Header 1b"),
@@ -57,7 +57,7 @@ void testWithFormatting() {
5757
List<Document> documents = reader.get();
5858

5959
assertThat(documents).hasSize(2)
60-
.extracting(Document::getMetadata, Document::getContent)
60+
.extracting(Document::getMetadata, Document::getText)
6161
.containsOnly(tuple(Map.of("category", "header_1", "title", "This is a fancy header name"),
6262
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt velit non bibendum gravida. Cras accumsan tincidunt ornare. Donec hendrerit consequat tellus blandit accumsan. Aenean aliquam metus at arcu elementum dignissim."),
6363
tuple(Map.of("category", "header_3", "title", "Header 3"),
@@ -75,7 +75,7 @@ void testDocumentDividedViaHorizontalRules() {
7575
List<Document> documents = reader.get();
7676

7777
assertThat(documents).hasSize(7)
78-
.extracting(Document::getMetadata, Document::getContent)
78+
.extracting(Document::getMetadata, Document::getText)
7979
.containsOnly(tuple(Map.of(),
8080
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt velit non bibendum gravida."),
8181
tuple(Map.of(),
@@ -105,7 +105,7 @@ void testDocumentNotDividedViaHorizontalRulesWhenIsDisabled() {
105105

106106
Document documentsFirst = documents.get(0);
107107
assertThat(documentsFirst.getMetadata()).isEmpty();
108-
assertThat(documentsFirst.getContent()).startsWith("Lorem ipsum dolor sit amet, consectetur adipiscing elit")
108+
assertThat(documentsFirst.getText()).startsWith("Lorem ipsum dolor sit amet, consectetur adipiscing elit")
109109
.endsWith("Phasellus eget tellus sed nibh ornare interdum eu eu mi.");
110110
}
111111

@@ -119,7 +119,7 @@ void testSimpleMarkdownDocumentWithHardAndSoftLineBreaks() {
119119

120120
Document documentsFirst = documents.get(0);
121121
assertThat(documentsFirst.getMetadata()).isEmpty();
122-
assertThat(documentsFirst.getContent()).isEqualTo(
122+
assertThat(documentsFirst.getText()).isEqualTo(
123123
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt velit non bibendum gravida. Cras accumsan tincidunt ornare. Donec hendrerit consequat tellus blandit accumsan. Aenean aliquam metus at arcu elementum dignissim.Nullam nisi dui, egestas nec sem nec, interdum lobortis enim. Pellentesque odio orci, faucibus eu luctus nec, venenatis et magna. Vestibulum nec eros non felis fermentum posuere eget ac risus.Aenean eu leo eu nibh tristique posuere quis quis massa. Nullam lacinia luctus sem ut vehicula.");
124124
}
125125

@@ -135,22 +135,22 @@ void testCode() {
135135

136136
assertThat(documents).satisfiesExactly(document -> {
137137
assertThat(document.getMetadata()).isEqualTo(Map.of());
138-
assertThat(document.getContent()).isEqualTo("This is a Java sample application:");
138+
assertThat(document.getText()).isEqualTo("This is a Java sample application:");
139139
}, document -> {
140140
assertThat(document.getMetadata()).isEqualTo(Map.of("lang", "java", "category", "code_block"));
141-
assertThat(document.getContent()).startsWith("package com.example.demo;")
141+
assertThat(document.getText()).startsWith("package com.example.demo;")
142142
.contains("SpringApplication.run(DemoApplication.class, args);");
143143
}, document -> {
144144
assertThat(document.getMetadata()).isEqualTo(Map.of("category", "code_inline"));
145-
assertThat(document.getContent()).isEqualTo(
145+
assertThat(document.getText()).isEqualTo(
146146
"Markdown also provides the possibility to use inline code formatting throughout the entire sentence.");
147147
}, document -> {
148148
assertThat(document.getMetadata()).isEqualTo(Map.of());
149-
assertThat(document.getContent())
149+
assertThat(document.getText())
150150
.isEqualTo("Another possibility is to set block code without specific highlighting:");
151151
}, document -> {
152152
assertThat(document.getMetadata()).isEqualTo(Map.of("lang", "", "category", "code_block"));
153-
assertThat(document.getContent()).isEqualTo("./mvnw spring-javaformat:apply\n");
153+
assertThat(document.getText()).isEqualTo("./mvnw spring-javaformat:apply\n");
154154
});
155155
}
156156

@@ -167,15 +167,15 @@ void testCodeWhenCodeBlockShouldNotBeSeparatedDocument() {
167167

168168
assertThat(documents).satisfiesExactly(document -> {
169169
assertThat(document.getMetadata()).isEqualTo(Map.of("lang", "java", "category", "code_block"));
170-
assertThat(document.getContent()).startsWith("This is a Java sample application: package com.example.demo")
170+
assertThat(document.getText()).startsWith("This is a Java sample application: package com.example.demo")
171171
.contains("SpringApplication.run(DemoApplication.class, args);");
172172
}, document -> {
173173
assertThat(document.getMetadata()).isEqualTo(Map.of("category", "code_inline"));
174-
assertThat(document.getContent()).isEqualTo(
174+
assertThat(document.getText()).isEqualTo(
175175
"Markdown also provides the possibility to use inline code formatting throughout the entire sentence.");
176176
}, document -> {
177177
assertThat(document.getMetadata()).isEqualTo(Map.of("lang", "", "category", "code_block"));
178-
assertThat(document.getContent()).isEqualTo(
178+
assertThat(document.getText()).isEqualTo(
179179
"Another possibility is to set block code without specific highlighting: ./mvnw spring-javaformat:apply\n");
180180
});
181181
}
@@ -187,7 +187,7 @@ void testBlockquote() {
187187
List<Document> documents = reader.get();
188188

189189
assertThat(documents).hasSize(2)
190-
.extracting(Document::getMetadata, Document::getContent)
190+
.extracting(Document::getMetadata, Document::getText)
191191
.containsOnly(tuple(Map.of(),
192192
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur diam eros, laoreet sit amet cursus vitae, varius sed nisi. Cras sit amet quam quis velit commodo porta consectetur id nisi. Phasellus tincidunt pulvinar augue."),
193193
tuple(Map.of("category", "blockquote"),
@@ -208,7 +208,7 @@ void testBlockquoteWhenBlockquoteShouldNotBeSeparatedDocument() {
208208

209209
Document documentsFirst = documents.get(0);
210210
assertThat(documentsFirst.getMetadata()).isEqualTo(Map.of("category", "blockquote"));
211-
assertThat(documentsFirst.getContent()).isEqualTo(
211+
assertThat(documentsFirst.getText()).isEqualTo(
212212
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur diam eros, laoreet sit amet cursus vitae, varius sed nisi. Cras sit amet quam quis velit commodo porta consectetur id nisi. Phasellus tincidunt pulvinar augue. Proin vel laoreet leo, sed luctus augue. Sed et ligula commodo, commodo lacus at, consequat turpis. Maecenas eget sapien odio. Maecenas urna lectus, pellentesque in accumsan aliquam, congue eu libero. Ut rhoncus nec justo a porttitor. Pellentesque auctor pharetra eros, viverra sodales lorem aliquet id. Curabitur semper nisi vel sem interdum suscipit.");
213213
}
214214

@@ -219,7 +219,7 @@ void testLists() {
219219
List<Document> documents = reader.get();
220220

221221
assertThat(documents).hasSize(2)
222-
.extracting(Document::getMetadata, Document::getContent)
222+
.extracting(Document::getMetadata, Document::getText)
223223
.containsOnly(tuple(Map.of("category", "header_2", "title", "Ordered list"),
224224
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur diam eros, laoreet sit amet cursus vitae, varius sed nisi. Cras sit amet quam quis velit commodo porta consectetur id nisi. Phasellus tincidunt pulvinar augue. Proin vel laoreet leo, sed luctus augue. Sed et ligula commodo, commodo lacus at, consequat turpis. Maecenas eget sapien odio. Pellentesque auctor pharetra eros, viverra sodales lorem aliquet id. Curabitur semper nisi vel sem interdum suscipit. Maecenas urna lectus, pellentesque in accumsan aliquam, congue eu libero. Ut rhoncus nec justo a porttitor."),
225225
tuple(Map.of("category", "header_2", "title", "Unordered list"),
@@ -241,7 +241,7 @@ void testWithAdditionalMetadata() {
241241

242242
Document documentsFirst = documents.get(0);
243243
assertThat(documentsFirst.getMetadata()).isEqualTo(Map.of("service", "some-service-name", "env", "prod"));
244-
assertThat(documentsFirst.getContent()).startsWith("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
244+
assertThat(documentsFirst.getText()).startsWith("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
245245
}
246246

247247
}

document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public List<Document> get() {
145145
while (itr.hasNext()) {
146146
var next = itr.next();
147147
Document document = toDocument(current, next);
148-
if (document != null && StringUtils.hasText(document.getContent())) {
148+
if (document != null && StringUtils.hasText(document.getText())) {
149149
documents.add(toDocument(current, next));
150150
}
151151
current = next;

document-readers/pdf-reader/src/test/java/org/springframework/ai/reader/pdf/PagePdfDocumentReaderTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void classpathRead() {
4444
.withNumberOfTopTextLinesToDelete(0)
4545
.withNumberOfBottomTextLinesToDelete(3)
4646
.withNumberOfTopPagesToSkipBeforeDelete(0)
47+
.overrideLineSeparator("\n")
4748
.build())
4849
.withPagesPerDocument(1)
4950
.build());
@@ -52,7 +53,7 @@ void classpathRead() {
5253

5354
assertThat(docs).hasSize(4);
5455

55-
String allText = docs.stream().map(Document::getContent).collect(Collectors.joining(System.lineSeparator()));
56+
String allText = docs.stream().map(Document::getText).collect(Collectors.joining(System.lineSeparator()));
5657

5758
assertThat(allText).doesNotContain(
5859
List.of("Page 1 of 4", "Page 2 of 4", "Page 3 of 4", "Page 4 of 4", "PDF Bookmark Sample"));

document-readers/tika-reader/src/test/java/org/springframework/ai/reader/tika/TikaDocumentReaderTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void testDocx(String resourceUri, String resourceName, String contentSnip
4343

4444
assertThat(doc.getMetadata()).containsKeys(TikaDocumentReader.METADATA_SOURCE);
4545
assertThat(doc.getMetadata().get(TikaDocumentReader.METADATA_SOURCE)).isEqualTo(resourceName);
46-
assertThat(doc.getContent()).contains(contentSnipped);
46+
assertThat(doc.getText()).contains(contentSnipped);
4747
}
4848

4949
}

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

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
import org.springframework.ai.chat.messages.UserMessage;
4848
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
4949
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
50+
import org.springframework.ai.chat.metadata.EmptyUsage;
51+
import org.springframework.ai.chat.metadata.Usage;
52+
import org.springframework.ai.chat.metadata.UsageUtils;
5053
import org.springframework.ai.chat.model.AbstractToolCallSupport;
5154
import org.springframework.ai.chat.model.ChatModel;
5255
import org.springframework.ai.chat.model.ChatResponse;
@@ -78,6 +81,7 @@
7881
* @author Mariusz Bernacki
7982
* @author Thomas Vitale
8083
* @author Claudio Silva Junior
84+
* @author Alexandros Pappas
8185
* @since 1.0.0
8286
*/
8387
public class AnthropicChatModel extends AbstractToolCallSupport implements ChatModel {
@@ -124,9 +128,9 @@ public class AnthropicChatModel extends AbstractToolCallSupport implements ChatM
124128
public AnthropicChatModel(AnthropicApi anthropicApi) {
125129
this(anthropicApi,
126130
AnthropicChatOptions.builder()
127-
.withModel(DEFAULT_MODEL_NAME)
128-
.withMaxTokens(DEFAULT_MAX_TOKENS)
129-
.withTemperature(DEFAULT_TEMPERATURE)
131+
.model(DEFAULT_MODEL_NAME)
132+
.maxTokens(DEFAULT_MAX_TOKENS)
133+
.temperature(DEFAULT_TEMPERATURE)
130134
.build());
131135
}
132136

@@ -210,6 +214,10 @@ public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaul
210214

211215
@Override
212216
public ChatResponse call(Prompt prompt) {
217+
return this.internalCall(prompt, null);
218+
}
219+
220+
public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
213221
ChatCompletionRequest request = createRequest(prompt, false);
214222

215223
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
@@ -226,8 +234,14 @@ public ChatResponse call(Prompt prompt) {
226234
ResponseEntity<ChatCompletionResponse> completionEntity = this.retryTemplate
227235
.execute(ctx -> this.anthropicApi.chatCompletionEntity(request));
228236

229-
ChatResponse chatResponse = toChatResponse(completionEntity.getBody());
237+
AnthropicApi.ChatCompletionResponse completionResponse = completionEntity.getBody();
238+
AnthropicApi.Usage usage = completionResponse.usage();
230239

240+
Usage currentChatResponseUsage = usage != null ? AnthropicUsage.from(completionResponse.usage())
241+
: new EmptyUsage();
242+
Usage accumulatedUsage = UsageUtils.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
243+
244+
ChatResponse chatResponse = toChatResponse(completionEntity.getBody(), accumulatedUsage);
231245
observationContext.setResponse(chatResponse);
232246

233247
return chatResponse;
@@ -236,14 +250,18 @@ public ChatResponse call(Prompt prompt) {
236250
if (!isProxyToolCalls(prompt, this.defaultOptions) && response != null
237251
&& this.isToolCall(response, Set.of("tool_use"))) {
238252
var toolCallConversation = handleToolCalls(prompt, response);
239-
return this.call(new Prompt(toolCallConversation, prompt.getOptions()));
253+
return this.internalCall(new Prompt(toolCallConversation, prompt.getOptions()), response);
240254
}
241255

242256
return response;
243257
}
244258

245259
@Override
246260
public Flux<ChatResponse> stream(Prompt prompt) {
261+
return this.internalStream(prompt, null);
262+
}
263+
264+
public Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousChatResponse) {
247265
return Flux.deferContextual(contextView -> {
248266
ChatCompletionRequest request = createRequest(prompt, true);
249267

@@ -263,11 +281,14 @@ public Flux<ChatResponse> stream(Prompt prompt) {
263281

264282
// @formatter:off
265283
Flux<ChatResponse> chatResponseFlux = response.switchMap(chatCompletionResponse -> {
266-
ChatResponse chatResponse = toChatResponse(chatCompletionResponse);
284+
AnthropicApi.Usage usage = chatCompletionResponse.usage();
285+
Usage currentChatResponseUsage = usage != null ? AnthropicUsage.from(chatCompletionResponse.usage()) : new EmptyUsage();
286+
Usage accumulatedUsage = UsageUtils.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
287+
ChatResponse chatResponse = toChatResponse(chatCompletionResponse, accumulatedUsage);
267288

268289
if (!isProxyToolCalls(prompt, this.defaultOptions) && this.isToolCall(chatResponse, Set.of("tool_use"))) {
269290
var toolCallConversation = handleToolCalls(prompt, chatResponse);
270-
return this.stream(new Prompt(toolCallConversation, prompt.getOptions()));
291+
return this.internalStream(new Prompt(toolCallConversation, prompt.getOptions()), chatResponse);
271292
}
272293

273294
return Mono.just(chatResponse);
@@ -281,7 +302,7 @@ public Flux<ChatResponse> stream(Prompt prompt) {
281302
});
282303
}
283304

284-
private ChatResponse toChatResponse(ChatCompletionResponse chatCompletion) {
305+
private ChatResponse toChatResponse(ChatCompletionResponse chatCompletion, Usage usage) {
285306

286307
if (chatCompletion == null) {
287308
logger.warn("Null chat completion returned");
@@ -327,19 +348,22 @@ private ChatResponse toChatResponse(ChatCompletionResponse chatCompletion) {
327348
allGenerations.add(toolCallGeneration);
328349
}
329350

330-
return new ChatResponse(allGenerations, this.from(chatCompletion));
351+
return new ChatResponse(allGenerations, this.from(chatCompletion, usage));
331352
}
332353

333354
private ChatResponseMetadata from(AnthropicApi.ChatCompletionResponse result) {
355+
return from(result, AnthropicUsage.from(result.usage()));
356+
}
357+
358+
private ChatResponseMetadata from(AnthropicApi.ChatCompletionResponse result, Usage usage) {
334359
Assert.notNull(result, "Anthropic ChatCompletionResult must not be null");
335-
AnthropicUsage usage = AnthropicUsage.from(result.usage());
336360
return ChatResponseMetadata.builder()
337-
.withId(result.id())
338-
.withModel(result.model())
339-
.withUsage(usage)
340-
.withKeyValue("stop-reason", result.stopReason())
341-
.withKeyValue("stop-sequence", result.stopSequence())
342-
.withKeyValue("type", result.type())
361+
.id(result.id())
362+
.model(result.model())
363+
.usage(usage)
364+
.keyValue("stop-reason", result.stopReason())
365+
.keyValue("stop-sequence", result.stopSequence())
366+
.keyValue("type", result.type())
343367
.build();
344368
}
345369

0 commit comments

Comments
 (0)