Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a1e9160
full implementation
n-o-u-r-h-a-n Jul 9, 2026
6390559
Merge branch 'main' into reasoning-content
n-o-u-r-h-a-n Jul 9, 2026
8c74197
adding ReasoningItem convenience record and changing files accordingly.
n-o-u-r-h-a-n Jul 9, 2026
d0385d9
Merge branch 'main' into reasoning-content
n-o-u-r-h-a-n Jul 9, 2026
f2201da
removing redundant getters
n-o-u-r-h-a-n Jul 9, 2026
3c2c626
Merge remote-tracking branch 'origin/reasoning-content' into reasonin…
n-o-u-r-h-a-n Jul 9, 2026
e85d7a0
returning string - not exposing signature
n-o-u-r-h-a-n Jul 9, 2026
0204cbe
Merge branch 'main' into reasoning-content
n-o-u-r-h-a-n Jul 9, 2026
cafb667
removing public API streamReasoning from OrchestrationClient.java
n-o-u-r-h-a-n Jul 9, 2026
aba4b03
Merge remote-tracking branch 'origin/reasoning-content' into reasonin…
n-o-u-r-h-a-n Jul 9, 2026
a845572
Merge branch 'main' into reasoning-content
n-o-u-r-h-a-n Jul 14, 2026
55a0523
Merge branch 'main' into reasoning-content
n-o-u-r-h-a-n Jul 14, 2026
6a3da98
Update sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/contro…
n-o-u-r-h-a-n Jul 14, 2026
8d3d2ca
Update sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/contro…
n-o-u-r-h-a-n Jul 14, 2026
49c4f2b
Update sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/contro…
n-o-u-r-h-a-n Jul 14, 2026
3931633
Merge branch 'main' into reasoning-content
n-o-u-r-h-a-n Jul 14, 2026
233b950
fixes
n-o-u-r-h-a-n Jul 14, 2026
6b5f4ab
wiring extra json files
n-o-u-r-h-a-n Jul 14, 2026
5805d62
removing accumulator fix
n-o-u-r-h-a-n Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions orchestration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
</scm>
<properties>
<project.rootdir>${project.basedir}/../</project.rootdir>
<coverage.complexity>82%</coverage.complexity>
<coverage.line>94%</coverage.line>
<coverage.instruction>93%</coverage.instruction>
<coverage.branch>75%</coverage.branch>
<coverage.complexity>83%</coverage.complexity>
<coverage.line>95%</coverage.line>
<coverage.instruction>94%</coverage.instruction>
<coverage.branch>77%</coverage.branch>
<coverage.method>95%</coverage.method>
<coverage.class>100%</coverage.class>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,39 @@
import com.sap.ai.sdk.orchestration.model.ChatMessage;
import com.sap.ai.sdk.orchestration.model.ChatMessageContent;
import com.sap.ai.sdk.orchestration.model.MessageToolCall;
import com.sap.ai.sdk.orchestration.model.ReasoningBlock;
import com.sap.ai.sdk.orchestration.model.TextContent;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.Accessors;
import lombok.val;

/** Represents a chat message as 'assistant' to the orchestration service. */
@Value
@Getter
@Accessors(fluent = true)
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class AssistantMessage implements Message {

/** The role of the assistant. */
@Nonnull String role = "assistant";

/** The content of the message. */
@Nonnull @Getter MessageContent content;
@Nonnull MessageContent content;

/** Tool call if there is any. */
@Nullable List<MessageToolCall> toolCalls;

/** Reasoning (thinking) blocks from the model's previous turn. */
@Getter(AccessLevel.NONE)
@Nullable
List<ReasoningBlock> reasoningContent;

/**
* Creates a new assistant message with the given single message.
*
Expand All @@ -39,6 +47,7 @@ public class AssistantMessage implements Message {
public AssistantMessage(@Nonnull final String singleMessage) {
content = new MessageContent(List.of(new TextItem(singleMessage)));
toolCalls = null;
reasoningContent = null;
}

/**
Expand All @@ -49,6 +58,7 @@ public AssistantMessage(@Nonnull final String singleMessage) {
AssistantMessage(@Nonnull final MessageContent content) {
this.content = content;
toolCalls = null;
reasoningContent = null;
}

/**
Expand All @@ -61,12 +71,7 @@ public AssistantMessage(@Nonnull final String singleMessage) {
public AssistantMessage(@Nonnull final List<MessageToolCall> toolCalls) {
content = new MessageContent(List.of());
this.toolCalls = toolCalls;
}

private AssistantMessage(
@Nonnull final MessageContent content, @Nullable final List<MessageToolCall> toolCalls) {
this.content = content;
this.toolCalls = toolCalls;
reasoningContent = null;
}

/**
Expand All @@ -81,7 +86,18 @@ private AssistantMessage(
public AssistantMessage withToolCalls(@Nonnull final List<MessageToolCall> toolCalls) {
val newToolcalls = new ArrayList<>(this.toolCalls != null ? this.toolCalls : List.of());
newToolcalls.addAll(toolCalls);
return new AssistantMessage(this.content, newToolcalls);
return new AssistantMessage(this.content, newToolcalls, this.reasoningContent);
}

/**
* Returns a new AssistantMessage instance carrying the given reasoning blocks.
*
* @param reasoningContent the reasoning blocks from the previous assistant turn.
* @return a new AssistantMessage instance with the reasoning content set.
*/
@Nonnull
AssistantMessage withReasoningContent(@Nonnull final List<ReasoningBlock> reasoningContent) {
return new AssistantMessage(this.content, this.toolCalls, List.copyOf(reasoningContent));
Comment thread
n-o-u-r-h-a-n marked this conversation as resolved.
}

@Nonnull
Expand All @@ -93,6 +109,10 @@ public ChatMessage createChatMessage() {
assistantChatMessage.setToolCalls(toolCalls);
}

if (reasoningContent != null) {
assistantChatMessage.setReasoningContent(reasoningContent);
}

ChatMessageContent text;
if (content.items().size() == 1 && content.items().get(0) instanceof TextItem textItem) {
text = ChatMessageContent.create(textItem.text());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,19 @@ public <ValueT> OrchestrationAiModel withParam(
return withParam(param.getName(), value);
}

/**
* Set the {@code reasoning_effort} parameter on this model.
*
* @param effort the reasoning effort level.
* @return a new model with the {@code reasoning_effort} parameter set.
* @see <a href="https://help.sap.com/docs/sap-ai-core/generative-ai/reasoning">SAP AI Core:
* Orchestration - Reasoning</a>
*/
@Nonnull
public OrchestrationAiModel withReasoningEffort(@Nonnull final ReasoningEffort effort) {
return withParam(Parameter.REASONING_EFFORT, effort.getValue());
}

/**
* Parameter key for a model.
*
Expand All @@ -539,6 +552,9 @@ public interface Parameter<ValueT> {
/** The number of chat completion choices to generate for each input message. */
Parameter<Integer> N = () -> "n";

/** The reasoning effort for reasoning-capable models. */
Parameter<String> REASONING_EFFORT = () -> "reasoning_effort";

/**
* The name of the parameter.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.sap.ai.sdk.core.common.StreamedDelta;
import com.sap.ai.sdk.orchestration.model.CompletionPostResponseStreaming;
import com.sap.ai.sdk.orchestration.model.ReasoningBlock;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.val;
Expand All @@ -27,6 +29,24 @@ public String getDeltaContent() {
return "";
}

/**
* Get the reasoning (thinking) text carried by this chunk.
*
* @return the reasoning text of each block in this chunk; never {@code null}, may be empty.
* @see <a href="https://help.sap.com/docs/sap-ai-core/generative-ai/reasoning">SAP AI Core:
* Orchestration - Reasoning</a>
*/
@Nonnull
public List<String> getDeltaReasoningContent() {
val choices = getFinalResult().getChoices();
if (!choices.isEmpty() && choices.get(0).getIndex() == 0) {
return choices.get(0).getDelta().getReasoningContent().stream()
.map(ReasoningBlock::getContent)
.toList();
}
return List.of();
}

@Nullable
@Override
public String getFinishReason() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.sap.ai.sdk.orchestration.model.ChatMessageContent;
import com.sap.ai.sdk.orchestration.model.CompletionPostResponse;
import com.sap.ai.sdk.orchestration.model.LLMChoice;
import com.sap.ai.sdk.orchestration.model.ReasoningBlock;
import com.sap.ai.sdk.orchestration.model.SystemChatMessage;
import com.sap.ai.sdk.orchestration.model.TokenUsage;
import com.sap.ai.sdk.orchestration.model.ToolChatMessage;
Expand Down Expand Up @@ -67,6 +68,44 @@ public TokenUsage getTokenUsage() {
return originalResponse.getFinalResult().getUsage();
}

/**
* Get the reasoning (thinking) content produced by the model, as text, if any.
*
* @return the reasoning text of each block; never {@code null}, may be empty.
* @see <a href="https://help.sap.com/docs/sap-ai-core/generative-ai/reasoning">SAP AI Core:
* Orchestration - Reasoning</a>
*/
@Nonnull
public List<String> getReasoningContent() {
return getChoice().getMessage().getReasoningContent().stream()
.map(ReasoningBlock::getContent)
.toList();
}

/**
* Get the reasoning (thinking) content as a single joined string, for display purposes. The
* reasoning blocks are concatenated with no separator between them.
*
* @return the concatenated reasoning text; empty if there is no reasoning.
* @see #getReasoningText(String)
*/
@Nonnull
public String getReasoningText() {
return getReasoningText("");
}

/**
* Get the reasoning (thinking) content as a single joined string, for display purposes, with the
* reasoning blocks separated by the given delimiter.
*
* @param delimiter the delimiter to place between reasoning blocks.
* @return the joined reasoning text; empty if there is no reasoning.
*/
@Nonnull
public String getReasoningText(@Nonnull final String delimiter) {
return String.join(delimiter, getReasoningContent());
}
Comment thread
n-o-u-r-h-a-n marked this conversation as resolved.

/**
* Get all messages. This can be used for subsequent prompts as a message history.
*
Expand All @@ -80,13 +119,19 @@ public List<Message> getAllMessages() throws IllegalArgumentException {
originalResponse.getIntermediateResults().getTemplating()) {
if (chatMessage instanceof AssistantChatMessage assistantChatMessage) {
val toolCalls = assistantChatMessage.getToolCalls();
AssistantMessage assistantMessage;
if (!toolCalls.isEmpty()) {
messages.add(new AssistantMessage(toolCalls));
assistantMessage = new AssistantMessage(toolCalls);
} else {
messages.add(
assistantMessage =
new AssistantMessage(
MessageContent.fromChatMessageContent(assistantChatMessage.getContent())));
MessageContent.fromChatMessageContent(assistantChatMessage.getContent()));
}
final var reasoning = assistantChatMessage.getReasoningContent();
if (!reasoning.isEmpty()) {
assistantMessage = assistantMessage.withReasoningContent(reasoning);
}
messages.add(assistantMessage);
} else if (chatMessage instanceof SystemChatMessage systemChatMessage) {
messages.add(
new SystemMessage(
Expand All @@ -106,7 +151,12 @@ public List<Message> getAllMessages() throws IllegalArgumentException {
}
}

messages.add(Message.assistant(getChoice().getMessage().getContent()));
var lastAssistant = Message.assistant(getChoice().getMessage().getContent());
final var lastReasoning = getChoice().getMessage().getReasoningContent();
if (!lastReasoning.isEmpty()) {
lastAssistant = lastAssistant.withReasoningContent(lastReasoning);
}
messages.add(lastAssistant);
return messages;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.sap.ai.sdk.orchestration;

import javax.annotation.Nonnull;

/**
* Reasoning effort levels for the harmonized {@code reasoning_effort} model parameter.
*
* <p>Not all reasoning-capable models support the full range of values. Where a model does not
* support a given effort, orchestration transparently maps it to the closest equivalent.
*
* @see <a href="https://help.sap.com/docs/sap-ai-core/generative-ai/reasoning">SAP AI Core:
* Orchestration - Reasoning</a>
*/
public enum ReasoningEffort {
/** Minimal reasoning effort. */
MINIMAL("minimal"),
/** Low reasoning effort. */
LOW("low"),
/** Medium reasoning effort. */
MEDIUM("medium"),
/** High reasoning effort. */
HIGH("high"),
/** Disable reasoning when the model supports doing so. */
NONE("none");

private final String value;

ReasoningEffort(@Nonnull final String value) {
this.value = value;
}

/**
* Wire string sent as the value of the {@code reasoning_effort} model parameter.
*
* @return the wire value.
*/
@Nonnull
public String getValue() {
return value;
}
}
Loading