generated from SAP/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 11
feat: SpringAI integration in OpenAI #538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
82 commits
Select commit
Hold shift + click to select a range
9ff262b
first draft
Jonas-Isr b14d70c
Align with docs
Jonas-Isr b32b999
Codestyle
Jonas-Isr 32d1bb2
Merge branch 'main' into agent-workflow-examples
Jonas-Isr 5720581
Merge branch 'main' into agent-workflow-examples
Jonas-Isr 64c8b0e
Merge branch 'main' into agent-workflow-examples
Jonas-Isr 98ab1af
Merge branch 'main' into agent-workflow-examples
CharlesDuboisSAP 0e59667
feat: [OpenAI] Spring AI integration
CharlesDuboisSAP 71898c5
test
CharlesDuboisSAP b056414
Merge branch 'agent-workflow-examples' into openai-springai
CharlesDuboisSAP 025eba1
test
CharlesDuboisSAP d402c64
Merge branch 'agent-workflow-examples' into openai-springai
CharlesDuboisSAP 7ed6baa
Merge branch 'main' into openai-springai
CharlesDuboisSAP 3b0273b
Fixing errors in OpenAiChatOptions according to "Upgrade to Spring AI…
n-o-u-r-h-a-n 8c2a638
Fixing errors in OpenAiChatOptions according to "Upgrade to Spring AI…
n-o-u-r-h-a-n 8eb5459
Fixing SpringAiAgenticWorkflowService according to "Upgrade to Spring…
n-o-u-r-h-a-n ab1f795
Implementation of completion and streamChatCompletion in SpringAiOpen…
n-o-u-r-h-a-n 9d4bdcd
Removing a comment
n-o-u-r-h-a-n 72198d9
Removing a comment
n-o-u-r-h-a-n 88904c8
Chat Memory test working.
n-o-u-r-h-a-n 47d1bc0
Formatting for SpringAiOpenAiService.
n-o-u-r-h-a-n 443e464
Removing unneccessary imports in SpringAiOpenAiService.
n-o-u-r-h-a-n 00ba4ad
Updating the toOpenAiRequest in OpenAiChatModel.java.
n-o-u-r-h-a-n 55c85d9
Implementing the new approach
n-o-u-r-h-a-n 56cda93
Editing the approach.
n-o-u-r-h-a-n 0183c83
Fix compilation and format and annotations and javadoc
newtork 02d5f54
Merge remote-tracking branch 'origin/main' into chatcompletion-for-sp…
newtork ee94941
Remove unrelated code
newtork dec85d6
implementation hint
newtork d99dc0e
Formatting
bot-sdk-js c073c8d
Updating OpenAiChatOptions.java with our Config Object.
n-o-u-r-h-a-n 6ed3a13
Formatting
bot-sdk-js 9f4a403
Passing our Config Object as an input parameter for OpenAiChatOptions()
n-o-u-r-h-a-n 687fe2e
Fixing NullPointerException in toOpenAiRequest method for ToolCallng …
n-o-u-r-h-a-n efa3831
Adding topK for the Config Class ??
n-o-u-r-h-a-n 817d3cb
Formatting
bot-sdk-js ad4241a
Update foundation-models/openai/src/main/java/com/sap/ai/sdk/foundati…
n-o-u-r-h-a-n 203eba6
Failing Test of testToolCallingWithoutExecution() in SpringAiOpenAiTe…
n-o-u-r-h-a-n f7c7ece
Resolving Reviewed Issues.
n-o-u-r-h-a-n 84546c0
Resolving Reviewed Issues.
n-o-u-r-h-a-n c52720e
--> still having testToolCallingWithoutExecution() in SpringAiOpenAiT…
n-o-u-r-h-a-n cd3501c
format
n-o-u-r-h-a-n 7f447d7
format
n-o-u-r-h-a-n 9e1760c
Removing wild cards imports
n-o-u-r-h-a-n 222924c
Sucessful build of OpenAi
n-o-u-r-h-a-n de9ef56
Sucessful build of Spring Boot app.
n-o-u-r-h-a-n 9fb6321
Merge branch 'main' into chatcompletion-for-springopenai
n-o-u-r-h-a-n 9284fe3
Formatting
bot-sdk-js 8d40dfa
Removing this test for now.
n-o-u-r-h-a-n be51dd3
Fix nullcheck
newtork d0ea158
Merge remote-tracking branch 'origin/chatcompletion-for-springopenai'…
newtork 3c161c0
Fix unit test
newtork 35d89cb
Merge branch 'main' into chatcompletion-for-springopenai
n-o-u-r-h-a-n c186aea
chore: Reduce constructor visibility in OpenAI / SpringAI PR (#531)
newtork 75a2361
Replacing config.toolsExecutable with getter-usage + adding tolerate …
n-o-u-r-h-a-n ddcb88c
Merge remote-tracking branch 'origin/chatcompletion-for-springopenai'…
n-o-u-r-h-a-n 9f27cb4
Merge branch 'main' into chatcompletion-for-springopenai
n-o-u-r-h-a-n 5e4b131
2
n-o-u-r-h-a-n 3cfbd5d
Assistant message wiht tools calls
ba3f337
unit test
CharlesDuboisSAP 05f75ea
Creating OpenAiChatModelTest.java and updating the controller and ind…
n-o-u-r-h-a-n e1f8e46
Merge branch 'main' into chatcompletion-for-springopenai2
n-o-u-r-h-a-n 2cda676
Formatting
bot-sdk-js c427c17
formatting
n-o-u-r-h-a-n 3c02160
Merge remote-tracking branch 'origin/chatcompletion-for-springopenai2…
n-o-u-r-h-a-n b1a68cc
Finishing the tests
n-o-u-r-h-a-n 9b2b5bf
Merge branch 'main' into chatcompletion-for-springopenai2
CharlesDuboisSAP f9a719e
Added more options and metadata
CharlesDuboisSAP 074d794
Formatting
bot-sdk-js 554e45a
Fixing Format/Style (Minor).
n-o-u-r-h-a-n 47ca557
Merge remote-tracking branch 'origin/chatcompletion-for-springopenai2…
n-o-u-r-h-a-n 43bc1fc
Merge branch 'main' into chatcompletion-for-springopenai2
n-o-u-r-h-a-n 7ee82a7
Fixing Format/Style (Minor).
n-o-u-r-h-a-n 50043e0
Updating the Release notes according to integrating SpringAI with our…
n-o-u-r-h-a-n c9fb23f
Update docs/release_notes.md
n-o-u-r-h-a-n 6abcdce
Handling JsonProcessingException
n-o-u-r-h-a-n 2e7860c
Merge branch 'main' into chatcompletion-for-springopenai2
n-o-u-r-h-a-n 613d0df
Handling JsonProcessingException
n-o-u-r-h-a-n 1154537
Handling JsonProcessingException
n-o-u-r-h-a-n bfe6202
Formatting
bot-sdk-js f91d32b
Merge branch 'main' into chatcompletion-for-springopenai2
CharlesDuboisSAP 3b5bad8
protected extractOptions
CharlesDuboisSAP File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 17 additions & 1 deletion
18
...on-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiToolCall.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,27 @@ | ||
package com.sap.ai.sdk.foundationmodels.openai; | ||
|
||
import com.google.common.annotations.Beta; | ||
import javax.annotation.Nonnull; | ||
|
||
/** | ||
* Represents a tool called by an OpenAI model. | ||
* | ||
* @since 1.6.0 | ||
*/ | ||
@Beta | ||
public sealed interface OpenAiToolCall permits OpenAiFunctionCall {} | ||
public sealed interface OpenAiToolCall permits OpenAiFunctionCall { | ||
/** | ||
* Creates a new instance of {@link OpenAiToolCall}. | ||
* | ||
* @param id The unique identifier for the tool call. | ||
* @param name The name of the tool to be called. | ||
* @param arguments The arguments for the tool call, encoded as a JSON string. | ||
* @return A new instance of {@link OpenAiToolCall}. | ||
* @since 1.10.0 | ||
*/ | ||
@Nonnull | ||
static OpenAiToolCall function( | ||
@Nonnull final String id, @Nonnull final String name, @Nonnull final String arguments) { | ||
return new OpenAiFunctionCall(id, name, arguments); | ||
} | ||
} |
223 changes: 223 additions & 0 deletions
223
...s/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiChatModel.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
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; | ||
import com.sap.ai.sdk.foundationmodels.openai.OpenAiChatCompletionDelta; | ||
import com.sap.ai.sdk.foundationmodels.openai.OpenAiChatCompletionRequest; | ||
import com.sap.ai.sdk.foundationmodels.openai.OpenAiChatCompletionResponse; | ||
import com.sap.ai.sdk.foundationmodels.openai.OpenAiClient; | ||
import com.sap.ai.sdk.foundationmodels.openai.OpenAiMessage; | ||
import com.sap.ai.sdk.foundationmodels.openai.OpenAiToolCall; | ||
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionMessageToolCall; | ||
import com.sap.ai.sdk.foundationmodels.openai.generated.model.ChatCompletionTool; | ||
import com.sap.ai.sdk.foundationmodels.openai.generated.model.CreateChatCompletionResponseChoicesInner; | ||
import com.sap.ai.sdk.foundationmodels.openai.generated.model.FunctionObject; | ||
import io.vavr.control.Option; | ||
import java.math.BigDecimal; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.function.Function; | ||
import javax.annotation.Nonnull; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import lombok.val; | ||
import org.springframework.ai.chat.messages.AssistantMessage; | ||
import org.springframework.ai.chat.messages.AssistantMessage.ToolCall; | ||
import org.springframework.ai.chat.messages.Message; | ||
import org.springframework.ai.chat.messages.ToolResponseMessage; | ||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata; | ||
import org.springframework.ai.chat.model.ChatModel; | ||
import org.springframework.ai.chat.model.ChatResponse; | ||
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.ToolCallingChatOptions; | ||
import reactor.core.publisher.Flux; | ||
|
||
/** | ||
* OpenAI Chat Model implementation that interacts with the OpenAI API to generate chat completions. | ||
*/ | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class OpenAiChatModel implements ChatModel { | ||
|
||
private final OpenAiClient client; | ||
|
||
@Nonnull | ||
private final DefaultToolCallingManager toolCallingManager = | ||
DefaultToolCallingManager.builder().build(); | ||
|
||
@Override | ||
@Nonnull | ||
public ChatResponse call(@Nonnull final Prompt prompt) { | ||
val options = prompt.getOptions(); | ||
var request = new OpenAiChatCompletionRequest(extractMessages(prompt)); | ||
|
||
if (options != null) { | ||
request = extractOptions(request, options); | ||
} | ||
if ((options instanceof ToolCallingChatOptions toolOptions)) { | ||
request = request.withTools(extractTools(toolOptions)); | ||
} | ||
|
||
val result = client.chatCompletion(request); | ||
val response = new ChatResponse(toGenerations(result)); | ||
|
||
if (options != null && isInternalToolExecutionEnabled(options) && response.hasToolCalls()) { | ||
val toolExecutionResult = toolCallingManager.executeToolCalls(prompt, response); | ||
// Send the tool execution result back to the model. | ||
return call(new Prompt(toolExecutionResult.conversationHistory(), options)); | ||
} | ||
return response; | ||
} | ||
|
||
@Override | ||
@Nonnull | ||
public Flux<ChatResponse> stream(@Nonnull final Prompt prompt) { | ||
val options = prompt.getOptions(); | ||
var request = new OpenAiChatCompletionRequest(extractMessages(prompt)); | ||
|
||
if (options != null) { | ||
request = extractOptions(request, options); | ||
} | ||
if ((options instanceof ToolCallingChatOptions toolOptions)) { | ||
request = request.withTools(extractTools(toolOptions)); | ||
} | ||
|
||
val stream = client.streamChatCompletionDeltas(request); | ||
final Flux<OpenAiChatCompletionDelta> flux = | ||
Flux.generate( | ||
stream::iterator, | ||
(iterator, sink) -> { | ||
if (iterator.hasNext()) { | ||
sink.next(iterator.next()); | ||
} else { | ||
sink.complete(); | ||
} | ||
return iterator; | ||
}); | ||
return flux.map( | ||
delta -> { | ||
val assistantMessage = new AssistantMessage(delta.getDeltaContent(), Map.of()); | ||
val metadata = | ||
ChatGenerationMetadata.builder().finishReason(delta.getFinishReason()).build(); | ||
return new ChatResponse(List.of(new Generation(assistantMessage, metadata))); | ||
}); | ||
} | ||
|
||
private static List<OpenAiMessage> extractMessages(final Prompt prompt) { | ||
final List<OpenAiMessage> result = new ArrayList<>(); | ||
for (final Message message : prompt.getInstructions()) { | ||
switch (message.getMessageType()) { | ||
case USER -> Option.of(message.getText()).peek(t -> result.add(OpenAiMessage.user(t))); | ||
case SYSTEM -> Option.of(message.getText()).peek(t -> result.add(OpenAiMessage.system(t))); | ||
case ASSISTANT -> addAssistantMessage(result, (AssistantMessage) message); | ||
case TOOL -> addToolMessages(result, (ToolResponseMessage) message); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
private static void addAssistantMessage( | ||
final List<OpenAiMessage> result, final AssistantMessage message) { | ||
if (message.getText() != null) { | ||
result.add(OpenAiMessage.assistant(message.getText())); | ||
return; | ||
} | ||
final Function<ToolCall, OpenAiToolCall> callTranslate = | ||
toolCall -> OpenAiToolCall.function(toolCall.id(), toolCall.name(), toolCall.arguments()); | ||
val calls = message.getToolCalls().stream().map(callTranslate).toList(); | ||
result.add(OpenAiMessage.assistant(calls)); | ||
} | ||
|
||
private static void addToolMessages( | ||
final List<OpenAiMessage> result, final ToolResponseMessage message) { | ||
for (final ToolResponseMessage.ToolResponse response : message.getResponses()) { | ||
result.add(OpenAiMessage.tool(response.responseData(), response.id())); | ||
} | ||
} | ||
|
||
@Nonnull | ||
private static List<Generation> toGenerations( | ||
@Nonnull final OpenAiChatCompletionResponse result) { | ||
return result.getOriginalResponse().getChoices().stream() | ||
.map(OpenAiChatModel::toGeneration) | ||
.toList(); | ||
} | ||
|
||
@Nonnull | ||
private static Generation toGeneration( | ||
@Nonnull final CreateChatCompletionResponseChoicesInner choice) { | ||
val metadata = | ||
ChatGenerationMetadata.builder().finishReason(choice.getFinishReason().getValue()); | ||
metadata.metadata("index", choice.getIndex()); | ||
if (choice.getLogprobs() != null && !choice.getLogprobs().getContent().isEmpty()) { | ||
metadata.metadata("logprobs", choice.getLogprobs().getContent()); | ||
} | ||
val message = choice.getMessage(); | ||
val calls = new ArrayList<ToolCall>(); | ||
if (message.getToolCalls() != null) { | ||
for (final ChatCompletionMessageToolCall c : message.getToolCalls()) { | ||
val fnc = c.getFunction(); | ||
calls.add( | ||
new ToolCall(c.getId(), c.getType().getValue(), fnc.getName(), fnc.getArguments())); | ||
} | ||
} | ||
|
||
val assistantMessage = new AssistantMessage(message.getContent(), Map.of(), calls); | ||
return new Generation(assistantMessage, metadata.build()); | ||
} | ||
|
||
/** | ||
* Adds options to the request. | ||
* | ||
* @param request the request to modify | ||
* @param options the options to extract | ||
* @return the modified request with options applied | ||
*/ | ||
@Nonnull | ||
protected static OpenAiChatCompletionRequest extractOptions( | ||
@Nonnull OpenAiChatCompletionRequest request, @Nonnull final ChatOptions options) { | ||
request = request.withStop(options.getStopSequences()).withMaxTokens(options.getMaxTokens()); | ||
if (options.getTemperature() != null) { | ||
request = request.withTemperature(BigDecimal.valueOf(options.getTemperature())); | ||
} | ||
if (options.getTopP() != null) { | ||
request = request.withTopP(BigDecimal.valueOf(options.getTopP())); | ||
} | ||
if (options.getPresencePenalty() != null) { | ||
request = request.withPresencePenalty(BigDecimal.valueOf(options.getPresencePenalty())); | ||
} | ||
if (options.getFrequencyPenalty() != null) { | ||
request = request.withFrequencyPenalty(BigDecimal.valueOf(options.getFrequencyPenalty())); | ||
} | ||
return request; | ||
} | ||
|
||
private static List<ChatCompletionTool> extractTools(final ToolCallingChatOptions options) { | ||
val tools = new ArrayList<ChatCompletionTool>(); | ||
for (val toolCallback : options.getToolCallbacks()) { | ||
val toolDefinition = toolCallback.getToolDefinition(); | ||
try { | ||
final Map<String, Object> params = | ||
new ObjectMapper().readValue(toolDefinition.inputSchema(), new TypeReference<>() {}); | ||
val toolType = ChatCompletionTool.TypeEnum.FUNCTION; | ||
val toolFunction = | ||
new FunctionObject() | ||
.name(toolDefinition.name()) | ||
.description(toolDefinition.description()) | ||
.parameters(params); | ||
val tool = new ChatCompletionTool().type(toolType).function(toolFunction); | ||
tools.add(tool); | ||
} catch (JsonProcessingException e) { | ||
log.warn("Failed to add tool to the chat request: {}", e.getMessage()); | ||
} | ||
} | ||
return tools; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.