diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
index 0e7e61203..422d1887b 100644
--- a/.github/dependabot.yaml
+++ b/.github/dependabot.yaml
@@ -22,8 +22,6 @@ updates:
versions: [ ">=5.0.0" ]
- dependency-name: "com.github.victools:jsonschema-module-jackson"
versions: [ ">=5.0.0" ]
- - dependency-name: "org.springframework.ai:spring-ai-bom"
- versions: [ ">=2.0.0" ]
groups:
production-minor-patch:
dependency-type: "production"
diff --git a/.github/workflows/e2e-test.yaml b/.github/workflows/e2e-test.yaml
index 60592c9ae..769de1b19 100644
--- a/.github/workflows/e2e-test.yaml
+++ b/.github/workflows/e2e-test.yaml
@@ -105,7 +105,7 @@ jobs:
run: wget -qO- -S localhost:8080
- name: "Slack Notification"
- if: failure()
+ if: github.ref_name == 'main' && failure()
uses: slackapi/slack-github-action@v4.0.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK }}
diff --git a/docs/release_notes.md b/docs/release_notes.md
index 5745d6681..108feb04e 100644
--- a/docs/release_notes.md
+++ b/docs/release_notes.md
@@ -8,7 +8,7 @@
### 🔧 Compatibility Notes
--
+-[Orchestration] Spring AI support was upgraded to version `2.0.1`
### ✨ New Functionality
diff --git a/foundation-models/openai/pom.xml b/foundation-models/openai/pom.xml
index 7e6e9f9af..c1d2a0c26 100644
--- a/foundation-models/openai/pom.xml
+++ b/foundation-models/openai/pom.xml
@@ -94,6 +94,10 @@
com.github.victools
jsonschema-module-jackson
+
+ tools.jackson.core
+ jackson-databind
+
io.vavr
vavr
diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiTool.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiTool.java
index af14b1be5..84f7f9962 100644
--- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiTool.java
+++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiTool.java
@@ -86,7 +86,13 @@ public static Builder1 forFunction(@Nonnull final Function {
final Function exec =
s -> function.apply(deserializeArgument(inputClass, s));
- final var schema = GENERATOR.generateSchema(inputClass);
+ final var jackson3Schema = GENERATOR.generateSchema(inputClass);
+ final ObjectNode schema;
+ try {
+ schema = (ObjectNode) JACKSON.readTree(jackson3Schema.toString());
+ } catch (JsonProcessingException e) {
+ throw new IllegalStateException("Failed to parse generated JSON schema", e);
+ }
return new OpenAiTool(name, exec, schema, null, null);
};
}
diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModel.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModel.java
index a38d99ff7..22a998ad4 100644
--- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModel.java
+++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModel.java
@@ -1,7 +1,5 @@
package com.sap.ai.sdk.foundationmodels.openai.spring;
-import static org.springframework.ai.model.tool.ToolCallingChatOptions.isInternalToolExecutionEnabled;
-
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -35,7 +33,7 @@
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
-import org.springframework.ai.model.tool.DefaultToolCallingManager;
+import org.springframework.ai.model.tool.DefaultToolCallingChatOptions;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import reactor.core.publisher.Flux;
@@ -49,8 +47,10 @@ public class OpenAiChatModel implements ChatModel {
private final OpenAiClient client;
@Nonnull
- private final DefaultToolCallingManager toolCallingManager =
- DefaultToolCallingManager.builder().build();
+ @Override
+ public ChatOptions getOptions() {
+ return DefaultToolCallingChatOptions.builder().toolCallbacks(List.of()).build();
+ }
@Override
@Nonnull
@@ -66,18 +66,7 @@ public ChatResponse call(@Nonnull final Prompt prompt) {
}
val result = client.chatCompletion(request);
- val response = new ChatResponse(toGenerations(result));
-
- if (options != null && isInternalToolExecutionEnabled(options) && response.hasToolCalls()) {
- val toolCalls =
- response.getResult().getOutput().getToolCalls().stream().map(ToolCall::name).toList();
- log.info("Executing {} tool call(s) - {}.", toolCalls.size(), toolCalls);
- val toolExecutionResult = toolCallingManager.executeToolCalls(prompt, response);
- // Send the tool execution result back to the model.
- log.debug("Re-invoking model with tool execution results.");
- return call(new Prompt(toolExecutionResult.conversationHistory(), options));
- }
- return response;
+ return new ChatResponse(toGenerations(result));
}
@Override
@@ -129,14 +118,15 @@ private static List extractMessages(final Prompt prompt) {
private static void addAssistantMessage(
final List result, final AssistantMessage message) {
- if (message.getText() != null) {
- result.add(OpenAiMessage.assistant(message.getText()));
+ final var toolCalls = message.getToolCalls();
+ if (toolCalls != null && !toolCalls.isEmpty()) {
+ final Function callTranslate =
+ toolCall -> OpenAiToolCall.function(toolCall.id(), toolCall.name(), toolCall.arguments());
+ val calls = toolCalls.stream().map(callTranslate).toList();
+ result.add(OpenAiMessage.assistant(calls));
return;
}
- final Function callTranslate =
- toolCall -> OpenAiToolCall.function(toolCall.id(), toolCall.name(), toolCall.arguments());
- val calls = message.getToolCalls().stream().map(callTranslate).toList();
- result.add(OpenAiMessage.assistant(calls));
+ Option.of(message.getText()).peek(t -> result.add(OpenAiMessage.assistant(t)));
}
private static void addToolMessages(
diff --git a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModelTest.java b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModelTest.java
index 3f92e35ee..6fb1478c7 100644
--- a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModelTest.java
+++ b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModelTest.java
@@ -134,9 +134,10 @@ void testToolCallsWithoutExecution() throws IOException {
.withHeader("Content-Type", "application/json")
.withBodyFile("weatherToolResponse.json")));
- var options = new DefaultToolCallingChatOptions();
- options.setToolCallbacks(List.of(ToolCallbacks.from(new WeatherMethod())));
- options.setInternalToolExecutionEnabled(false);
+ var options =
+ DefaultToolCallingChatOptions.builder()
+ .toolCallbacks(ToolCallbacks.from(new WeatherMethod()))
+ .build();
val prompt = new Prompt("What is the weather in Potsdam and in Toulouse?", options);
val result = client.call(prompt);
@@ -178,10 +179,16 @@ void testToolCallsWithExecution() throws IOException {
.withBodyFile("weatherToolResponse2.json")
.withHeader("Content-Type", "application/json")));
- var options = new DefaultToolCallingChatOptions();
- options.setToolCallbacks(List.of(ToolCallbacks.from(new WeatherMethod())));
- val prompt = new Prompt("What is the weather in Potsdam and in Toulouse?", options);
- val result = client.call(prompt);
+ var options =
+ DefaultToolCallingChatOptions.builder()
+ .toolCallbacks(ToolCallbacks.from(new WeatherMethod()))
+ .build();
+ val chatClient = ChatClient.builder(client).build();
+ val result =
+ chatClient
+ .prompt(new Prompt("What is the weather in Potsdam and in Toulouse?", options))
+ .call()
+ .chatResponse();
assertThat(result.getResult().getOutput().getText())
.isEqualTo("The current temperature in Potsdam is 30°C and in Toulouse 30°C.");
diff --git a/orchestration/pom.xml b/orchestration/pom.xml
index 8b7b07bae..e27cff450 100644
--- a/orchestration/pom.xml
+++ b/orchestration/pom.xml
@@ -118,6 +118,10 @@
com.github.victools
jsonschema-module-jackson
+
+ tools.jackson.core
+ jackson-databind
+
com.fasterxml.jackson.dataformat
jackson-dataformat-yaml
diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ResponseJsonSchema.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ResponseJsonSchema.java
index 718db8755..a60f0759c 100644
--- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ResponseJsonSchema.java
+++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ResponseJsonSchema.java
@@ -73,8 +73,12 @@ public static ResponseJsonSchema fromType(@Nonnull final Type classType) {
.with(module)
.build());
val jsonSchema = generator.generateSchema(classType);
- val mapper = new ObjectMapper();
- val schemaMap = mapper.convertValue(jsonSchema, new TypeReference
+
+
+ com.github.victools
+ jsonschema-module-swagger-2
+ 5.0.0
+
+
+
+ com.networknt
+ json-schema-validator
+ 3.0.1
+
org.junit
@@ -176,17 +188,15 @@
org.springframework.ai
- spring-ai-autoconfigure-mcp-client
- 1.0.9
+ spring-ai-autoconfigure-mcp-client-common
+ 2.0.1
+ runtime
+
+
+ org.springframework.ai
+ spring-ai-mcp-annotations
+ 2.0.1
runtime
-
-
-
- org.springframework.boot
- spring-boot-starter
-
-
org.springframework.boot
diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOpenAiService.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOpenAiService.java
index 48796e833..94d912200 100644
--- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOpenAiService.java
+++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOpenAiService.java
@@ -91,11 +91,16 @@ public Flux streamChatCompletion() {
*/
@Nonnull
public ChatResponse toolCalling(final boolean internalToolExecutionEnabled) {
- val options = new DefaultToolCallingChatOptions();
- options.setToolCallbacks(List.of(ToolCallbacks.from(new WeatherMethod())));
- options.setInternalToolExecutionEnabled(internalToolExecutionEnabled);
-
+ val options =
+ DefaultToolCallingChatOptions.builder()
+ .toolCallbacks(ToolCallbacks.from(new WeatherMethod()))
+ .build();
val prompt = new Prompt("What is the weather in Potsdam and in Toulouse?", options);
+ if (internalToolExecutionEnabled) {
+ return Objects.requireNonNull(
+ ChatClient.builder(chatClient).build().prompt(prompt).call().chatResponse(),
+ "Chat response is null");
+ }
return chatClient.call(prompt);
}