Skip to content

Commit 8e1f251

Browse files
committed
merge main
Signed-off-by: lambochen <[email protected]>
2 parents 4269f37 + 9beee1d commit 8e1f251

File tree

16 files changed

+302
-36
lines changed

16 files changed

+302
-36
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: Auto Cherry-Pick
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- '*.x'
8+
9+
jobs:
10+
cherry-pick-commit:
11+
uses: spring-io/spring-github-workflows/.github/workflows/spring-cherry-pick.yml@v5
12+
secrets:
13+
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: Backport Issue
2+
3+
on:
4+
push:
5+
branches:
6+
- '*.x'
7+
8+
jobs:
9+
backport-issue:
10+
uses: spring-io/spring-github-workflows/.github/workflows/spring-backport-issue.yml@v5
11+
secrets:
12+
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ shell.log
4545

4646
**/.claude/settings.local.json
4747
.devcontainer
48+
49+
qodana.yaml

CONTRIBUTING.adoc

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ what kind of changes are likely to be accepted; and what to expect from the Spri
77
Please refer back to this document as a checklist before issuing any pull request; this will save time for everyone!
88

99
== Code of Conduct
10-
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
10+
This project adheres to the Contributor Covenant https://github.com/spring-projects/spring-ai#coc-ov-file[code of conduct].
1111
By participating, you are expected to uphold this code. Please report unacceptable behavior to
1212
1313

@@ -77,17 +77,17 @@ project to which you’re trying to contribute but that you don’t maintain."_
7777

7878
== Keeping your Local Code in Sync
7979
* As mentioned above, you should always work on topic branches (since 'main' is a moving target). However, you do want
80-
to always keep your own 'origin' main branch in synch with the 'upstream' main.
80+
to always keep your own 'origin' main branch in sync with the 'upstream' main.
8181
* Within your local working directory, you can sync up all remotes' branches with: `git fetch --all`
8282
* While on your own local main branch: `git pull upstream main` (which is the equivalent of fetching upstream/main
8383
and merging that into the branch you are in currently)
84-
* Now that you're in synch, switch to the topic branch where you plan to work, e.g.: `git checkout -b GH-123`
84+
* Now that you're in sync, switch to the topic branch where you plan to work, e.g.: `git checkout -b GH-123`
8585
* When you get to a stopping point: `git commit`
86-
* If changes have occurred on the upstream/main while you were working you can synch again:
86+
* If changes have occurred on the upstream/main while you were working you can sync again:
8787
- Switch back to main: `git checkout main`
8888
- Then: `git pull upstream main`
8989
- Switch back to the topic branch: `git checkout GH-123` (no -b needed since the branch already exists)
90-
- Rebase the topic branch to minimize the distance between it and your recently synched main branch: `git rebase main`
90+
- Rebase the topic branch to minimize the distance between it and your recently synced main branch: `git rebase main`
9191
(Again, for more detail see https://git-scm.com/book/en/Git-Branching-Rebasing[the Pro Git section on rebasing]).
9292
* **Note** You cannot rebase if you have already pushed your branch to your remote because you'd be rewriting history
9393
(see **'The Perils of Rebasing'** in the article).
@@ -133,6 +133,25 @@ This command, will provide the following output, which in this case shows a nice
133133
If you see intersecting lines, that usually means that you forgot to rebase you branch.
134134
As mentioned earlier, **please rebase against main** before issuing a pull request.
135135

136+
== Enabling Checkstyle
137+
138+
Checkstyles are currently disabled in the project.
139+
However, we encourage all PR contributors to run checkstyles by enabling them before submitting a PR.
140+
141+
You can enable them by doing the following:
142+
143+
```shell
144+
./mvnw clean package -DskipTests -Ddisable.checks=false
145+
```
146+
147+
=== Source Code Style
148+
149+
Spring AI source code checkstyle tries to follow the checkstyle guidelines used by the core Spring Framework project with some exceptions.
150+
The wiki pages
151+
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
152+
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
153+
define the source file coding standards we use along with some IDEA editor settings we customize.
154+
136155
== Mind the whitespace
137156

138157
Please carefully follow the whitespace and formatting conventions already present in the framework.
@@ -265,4 +284,4 @@ Also by using specific
265284
https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword[keywords]
266285
you can link to a GitHub issue like so:
267286

268-
Closes #10
287+
Closes #10

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,23 +115,36 @@ To build the docs
115115

116116
The docs are then in the directory `spring-ai-docs/target/antora/site/index.html`
117117

118+
### Formatting the Source Code
119+
118120
To reformat using the [java-format plugin](https://github.com/spring-io/spring-javaformat)
119121
```shell
120122
./mvnw spring-javaformat:apply
121123
```
124+
### Updating License Headers
122125

123126
To update the year on license headers using the [license-maven-plugin](https://oss.carbou.me/license-maven-plugin/#goals)
124127
```shell
125128
./mvnw license:update-file-header -Plicense
126129
```
130+
### Javadocs
127131

128132
To check javadocs using the [javadoc:javadoc](https://maven.apache.org/plugins/maven-javadoc-plugin/)
129133
```shell
130134
./mvnw javadoc:javadoc -Pjavadoc
131135
```
136+
### Enabling Checkstyle
132137

133-
To build with checkstyles enabled.
134138
Checkstyles are currently disabled, but you can enable them by doing the following:
139+
135140
```shell
136141
./mvnw clean package -DskipTests -Ddisable.checks=false
137142
```
143+
144+
#### Source Code Style
145+
146+
Spring AI source code checkstyle tries to follow the checkstyle guidelines used by the core Spring Framework project with some exceptions.
147+
The wiki pages
148+
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
149+
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
150+
define the source file coding standards we use along with some IDEA editor settings we customize.

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,13 @@ private int getPageNumber(PDOutlineItem current) throws IOException {
140140
return -1;
141141
}
142142
PDPage currentPage = current.findDestinationPage(this.document);
143-
PDPageTree pages = this.document.getDocumentCatalog().getPages();
144-
for (int i = 0; i < pages.getCount(); i++) {
145-
var page = pages.get(i);
146-
if (page.equals(currentPage)) {
147-
return i + 1;
143+
if (currentPage != null) {
144+
PDPageTree pages = this.document.getDocumentCatalog().getPages();
145+
for (int i = 0; i < pages.getCount(); i++) {
146+
var page = pages.get(i);
147+
if (page.equals(currentPage)) {
148+
return i + 1;
149+
}
148150
}
149151
}
150152
return -1;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -94,7 +94,7 @@ private double numberOfSpacesBetweenTwoCharacters(final TextPosition textPositio
9494

9595
private char getCharacterFromTextPosition(final TextPosition textPosition) {
9696
String string = textPosition.getUnicode();
97-
char character = string.charAt(0);
97+
char character = !string.isEmpty() ? string.charAt(0) : '\0';
9898
return character;
9999
}
100100

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,16 @@ public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest c
241241
public enum ChatModel implements ChatModelDescription {
242242

243243
// @formatter:off
244+
/**
245+
* The claude-opus-4-0 model.
246+
*/
247+
CLAUDE_OPUS_4("claude-opus-4-0"),
248+
249+
/**
250+
* The claude-sonnet-4-0 model.
251+
*/
252+
CLAUDE_SONNET_4("claude-sonnet-4-0"),
253+
244254
/**
245255
* The claude-3-7-sonnet-latest model.
246256
*/

models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
* @author Jihoon Kim
8585
* @author Alexandros Pappas
8686
* @author Ilayaperumal Gopinathan
87+
* @author Sun Yuhan
8788
* @author lambochen
8889
* @since 1.0.0
8990
* @see ToolCallingChatOptions
@@ -172,18 +173,21 @@ static ChatResponseMetadata from(OllamaApi.ChatResponse response, ChatResponse p
172173
Duration totalDuration = response.getTotalDuration();
173174

174175
if (previousChatResponse != null && previousChatResponse.getMetadata() != null) {
175-
if (previousChatResponse.getMetadata().get(METADATA_EVAL_DURATION) != null) {
176-
evalDuration = evalDuration.plus(previousChatResponse.getMetadata().get(METADATA_EVAL_DURATION));
176+
Object metadataEvalDuration = previousChatResponse.getMetadata().get(METADATA_EVAL_DURATION);
177+
if (metadataEvalDuration != null && evalDuration != null) {
178+
evalDuration = evalDuration.plus((Duration) metadataEvalDuration);
177179
}
178-
if (previousChatResponse.getMetadata().get(METADATA_PROMPT_EVAL_DURATION) != null) {
179-
promptEvalDuration = promptEvalDuration
180-
.plus(previousChatResponse.getMetadata().get(METADATA_PROMPT_EVAL_DURATION));
180+
Object metadataPromptEvalDuration = previousChatResponse.getMetadata().get(METADATA_PROMPT_EVAL_DURATION);
181+
if (metadataPromptEvalDuration != null && promptEvalDuration != null) {
182+
promptEvalDuration = promptEvalDuration.plus((Duration) metadataPromptEvalDuration);
181183
}
182-
if (previousChatResponse.getMetadata().get(METADATA_LOAD_DURATION) != null) {
183-
loadDuration = loadDuration.plus(previousChatResponse.getMetadata().get(METADATA_LOAD_DURATION));
184+
Object metadataLoadDuration = previousChatResponse.getMetadata().get(METADATA_LOAD_DURATION);
185+
if (metadataLoadDuration != null && loadDuration != null) {
186+
loadDuration = loadDuration.plus((Duration) metadataLoadDuration);
184187
}
185-
if (previousChatResponse.getMetadata().get(METADATA_TOTAL_DURATION) != null) {
186-
totalDuration = totalDuration.plus(previousChatResponse.getMetadata().get(METADATA_TOTAL_DURATION));
188+
Object metadataTotalDuration = previousChatResponse.getMetadata().get(METADATA_TOTAL_DURATION);
189+
if (metadataTotalDuration != null && totalDuration != null) {
190+
totalDuration = totalDuration.plus((Duration) metadataTotalDuration);
187191
}
188192
if (previousChatResponse.getMetadata().getUsage() != null) {
189193
promptTokens += previousChatResponse.getMetadata().getUsage().getPromptTokens();

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@
3737
import org.springframework.ai.ollama.management.ModelManagementOptions;
3838

3939
import static org.assertj.core.api.Assertions.assertThat;
40-
import static org.junit.jupiter.api.Assertions.assertEquals;
41-
import static org.junit.jupiter.api.Assertions.assertThrows;
40+
import static org.junit.jupiter.api.Assertions.*;
4241

4342
/**
4443
* @author Jihoon Kim
@@ -146,4 +145,28 @@ void buildChatResponseMetadataAggregationWithNonEmptyMetadata() {
146145
assertEquals(promptEvalCount + 66, (Integer) metadata.get("prompt-eval-count"));
147146
}
148147

148+
@Test
149+
void buildChatResponseMetadataAggregationWithNonEmptyMetadataButEmptyEval() {
150+
151+
OllamaApi.ChatResponse response = new OllamaApi.ChatResponse("model", Instant.now(), null, null, null, null,
152+
null, null, null, null, null);
153+
154+
ChatResponse previousChatResponse = ChatResponse.builder()
155+
.generations(List.of())
156+
.metadata(ChatResponseMetadata.builder()
157+
.usage(new DefaultUsage(66, 99))
158+
.keyValue("eval-duration", Duration.ofSeconds(2))
159+
.keyValue("prompt-eval-duration", Duration.ofSeconds(2))
160+
.build())
161+
.build();
162+
163+
ChatResponseMetadata metadata = OllamaChatModel.from(response, previousChatResponse);
164+
165+
assertNull(metadata.get("eval-duration"));
166+
assertNull(metadata.get("prompt-eval-duration"));
167+
assertEquals(Integer.valueOf(99), metadata.get("eval-count"));
168+
assertEquals(Integer.valueOf(66), metadata.get("prompt-eval-count"));
169+
170+
}
171+
149172
}

0 commit comments

Comments
 (0)