Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
77b6637
Call Orchestration with image and multiString via executeRequestFromJ…
Jonas-Isr Jan 6, 2025
026771b
Merge branch 'main' into orchestration-image-support
Jonas-Isr Jan 14, 2025
09816d2
Work in Progress
Jonas-Isr Jan 21, 2025
54a2c19
Align System- nad AssintantMessage with UserMessage
Jonas-Isr Jan 23, 2025
98ba774
Improve exceptions
Jonas-Isr Jan 23, 2025
75afed1
Improve tests
Jonas-Isr Jan 23, 2025
38f3fd4
Prepare draft
Jonas-Isr Jan 23, 2025
f8ea4d0
- Restrict Multi- and ImageContent to appropriate classes;
Jonas-Isr Jan 27, 2025
cd20ca8
Rename addTextMessages() to addText()
Jonas-Isr Jan 27, 2025
a2fe90f
Remove or hide unneeded constructors, use Message.user() etc. instead
Jonas-Isr Jan 27, 2025
ec899a4
Add simple e2e tests
Jonas-Isr Jan 27, 2025
f173057
Prepare draft
Jonas-Isr Jan 28, 2025
c57bd1d
Small change
Jonas-Isr Jan 28, 2025
0524fd7
Refactor newly introduced classes
Jonas-Isr Jan 28, 2025
60e8120
WIP
Jonas-Isr Jan 30, 2025
3754d9c
add test for base64 image
Jonas-Isr Jan 31, 2025
8ab1514
Small cleanups, no more unnecessary Exceptions
Jonas-Isr Jan 31, 2025
e99d7b9
Small changes: ImageItem.DetailLevel is not mandatory by spec, refact…
Jonas-Isr Jan 31, 2025
7c05d89
Delete MessageContent.toString()
Jonas-Isr Jan 31, 2025
5a72a33
Bit of clean up
Jonas-Isr Jan 31, 2025
6100502
Add/improve javadocs
Jonas-Isr Jan 31, 2025
85d077f
Add/improve javadocs some more
Jonas-Isr Feb 3, 2025
7113283
Add annotations
Jonas-Isr Feb 3, 2025
60b97b0
Add explicit allArgs constructor to ImageItem
Jonas-Isr Feb 3, 2025
fa1add0
Fix codestyle etc.
Jonas-Isr Feb 3, 2025
682794a
Fix order in add methods
Jonas-Isr Feb 3, 2025
b671bc7
Improve unit test
Jonas-Isr Feb 3, 2025
3bd521c
Improve e2e test
Jonas-Isr Feb 3, 2025
a152c2f
Merge branch 'main' into orchestration-image-support
Jonas-Isr Feb 3, 2025
0f928a8
Small fixes after merge
Jonas-Isr Feb 3, 2025
6ee7290
Add unit test for message construction
Jonas-Isr Feb 3, 2025
57330d4
Fix sample app after merge
Jonas-Isr Feb 4, 2025
65d01c6
Simplify multiMessage unit test
Jonas-Isr Feb 4, 2025
8a0c79d
Formatting
bot-sdk-js Feb 4, 2025
13c0387
Add constructor for multiple strings for UserMessage and MessageContent
Jonas-Isr Feb 4, 2025
f95c4d3
Small changes
Jonas-Isr Feb 4, 2025
eaf2632
Merge remote-tracking branch 'origin/orchestration-image-support' int…
Jonas-Isr Feb 4, 2025
be70506
Add documentation and release notes
Jonas-Isr Feb 4, 2025
fb0a89f
Improve documentation
Jonas-Isr Feb 5, 2025
8357b89
Update orchestration/src/main/java/com/sap/ai/sdk/orchestration/Messa…
Jonas-Isr Feb 5, 2025
8d32086
Minor changes
Jonas-Isr Feb 5, 2025
dff4fb8
Make `.content()` @Beta
Jonas-Isr Feb 5, 2025
b0e6701
change method names from `addXYZ()` to `andXYZ()`
Jonas-Isr Feb 5, 2025
29ceee1
Delete unnecessary @Nonnull from ImageItem constructor
Jonas-Isr Feb 6, 2025
d8fd060
Improve tests
Jonas-Isr Feb 6, 2025
bcf0d99
Merge branch 'main' into orchestration-image-support
Jonas-Isr Feb 6, 2025
162cb6b
increase coverage
CharlesDuboisSAP Feb 6, 2025
adf81da
We hate Jacoco
CharlesDuboisSAP Feb 6, 2025
a099ee0
Update docs/release-notes/release_notes.md
Jonas-Isr Feb 6, 2025
4899f36
Simplify logic
Jonas-Isr Feb 6, 2025
837fd66
Small fixes
Jonas-Isr Feb 6, 2025
3dd3085
Reduce and streamline amount of public API
Jonas-Isr Feb 7, 2025
d91ca3b
Rename convenience methods to `withXyz()`
Jonas-Isr Feb 7, 2025
e438330
Simplify code and adapt jacoco coverage
Jonas-Isr Feb 7, 2025
a87fa5d
Small change
Jonas-Isr Feb 7, 2025
27a1033
Add release number to javadocs
Jonas-Isr Feb 7, 2025
b505272
Fit jacoco coverage
Jonas-Isr Feb 7, 2025
72f66b5
Merge branch 'main' into orchestration-image-support
Jonas-Isr Feb 7, 2025
dc69ad1
Rename MessageContent.contentItemList to MessageContent.items
Jonas-Isr Feb 7, 2025
3d8315e
Update docs
Jonas-Isr Feb 7, 2025
cd08312
Merge branch 'main' into orchestration-image-support
Jonas-Isr Feb 10, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,15 @@ public class AssistantMessage implements Message {
@Nonnull String role = "assistant";

/** The content of the message. */
@Nonnull String content;
@Nonnull MessageContentSingle content;

@Override
@Nonnull
public MessageContent content() {
return content;
}

public AssistantMessage(String singleMessage) {
content = new MessageContentSingle(singleMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import com.google.common.annotations.Beta;
import com.sap.ai.sdk.orchestration.model.ChatMessage;
import com.sap.ai.sdk.orchestration.model.ChatMessagesInner;
import com.sap.ai.sdk.orchestration.model.ImageContent;
import com.sap.ai.sdk.orchestration.model.ImageContentImageUrl;
import com.sap.ai.sdk.orchestration.model.MultiChatMessage;
import com.sap.ai.sdk.orchestration.model.TextContent;
import javax.annotation.Nonnull;

/** Interface representing convenience wrappers of chat message to the orchestration service. */
Expand All @@ -18,6 +23,17 @@ static UserMessage user(@Nonnull final String msg) {
return new UserMessage(msg);
}

/**
* A convenience method to create a user message.
*
* @param msg the message content.
* @return the user message.
*/
@Nonnull
static UserMessage user(@Nonnull final MessageContent msg) {
return new UserMessage(msg);
}

/**
* A convenience method to create an assistant message.
*
Expand All @@ -40,14 +56,57 @@ static SystemMessage system(@Nonnull final String msg) {
return new SystemMessage(msg);
}

/**
* A convenience method to create a system message.
*
* @param msg the message content.
* @return the system message.
*/
@Nonnull
static SystemMessage system(@Nonnull final MessageContent msg) {
return new SystemMessage(msg);
}

/**
* Converts the message to a serializable ChatMessage object.
*
* @return the corresponding {@code ChatMessage} object.
*/
@Nonnull
default ChatMessage createChatMessage() {
return ChatMessage.create().role(role()).content(content());
default ChatMessagesInner createChatMessage() {
if (this.content() instanceof MessageContentSingle) {
return ChatMessage.create()
.role(role())
.content(((MessageContentSingle) content()).content());
} else if (this.content() instanceof MessageContentMulti mCMulti) {
return MultiChatMessage.create()
.role(role())
.content(
mCMulti.multiContentList().stream()
.map(
multiMessageContent -> {
if (multiMessageContent instanceof MultiMessageTextContent mMTContent) {
return TextContent.create()
.type(TextContent.TypeEnum.TEXT)
.text(mMTContent.text());
} else if (multiMessageContent
instanceof MultiMessageImageContent mMIContent) {
return ImageContent.create()
.type(ImageContent.TypeEnum.IMAGE_URL)
.imageUrl(
ImageContentImageUrl.create()
.url(mMIContent.imageUrl())
.detail(mMIContent.detailLevel().toString()));
} else {
throw new IllegalArgumentException(
"Unknown subtype of MultiMessageContent: "
+ multiMessageContent.getClass());
}
})
.toList());
} else {
throw new IllegalArgumentException("Unknown content type: " + this.content().getClass());
}
}

/**
Expand All @@ -65,5 +124,5 @@ default ChatMessage createChatMessage() {
*/
@Nonnull
@Beta
String content();
Object content();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.sap.ai.sdk.orchestration;

public sealed interface MessageContent permits MessageContentSingle, MessageContentMulti {

static String toString(MessageContent content) {
if (content instanceof MessageContentSingle mCSingle) {
return mCSingle.content();
} else if (content instanceof MessageContentMulti mCMulti) {
var strBuilder = new StringBuilder();
mCMulti
.multiContentList()
.forEach(
multiContent -> {
if (multiContent instanceof MultiMessageTextContent mMText) {
strBuilder.append(mMText.text()).append("; ");
} else if (multiContent instanceof MultiMessageImageContent mMImage) {
strBuilder.append(mMImage.imageUrl());
} else {
throw new IllegalArgumentException(
"Unknown subtype of MultiChatMessageContent: " + multiContent.getClass());
}
});
return strBuilder.toString();
} else {
throw new IllegalArgumentException(
"Unknown subtype of MessageContent: " + content.getClass());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sap.ai.sdk.orchestration;

import static com.sap.ai.sdk.orchestration.MultiMessageImageContent.DetailLevel;

import com.sap.ai.sdk.orchestration.model.ImageContent;
import com.sap.ai.sdk.orchestration.model.MultiChatMessageContent;
import com.sap.ai.sdk.orchestration.model.TextContent;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public record MessageContentMulti(List<MultiMessageContent> multiContentList)
implements MessageContent {

public MessageContentMulti(MultiChatMessageContent... multiChatContents) {
this(convertIntoMultiMessageList(multiChatContents));
}

private static List<MultiMessageContent> convertIntoMultiMessageList(
MultiChatMessageContent... multiChatContents) {
List<MultiMessageContent> multiContentList = new java.util.ArrayList<>(List.of());
for (MultiChatMessageContent multiChatContent : multiChatContents) {
if (multiChatContent instanceof TextContent textContent) {
multiContentList.add(new MultiMessageTextContent(textContent.getText()));
} else if (multiChatContent instanceof ImageContent imageContent) {
var imageUrl = imageContent.getImageUrl();
multiContentList.add(
new MultiMessageImageContent(
imageUrl.getUrl(), DetailLevel.fromString(imageUrl.getDetail())));
} else {
throw new IllegalArgumentException(
"Unknown subtype of MultiChatMessageContent: " + multiChatContent.getClass());
}
}
return multiContentList;
}

MessageContentMulti(
@Nonnull List<? extends MultiMessageContent> newContent,
@Nullable MessageContent oldContent) {
this(mergeContent(newContent, oldContent));
}

private static List<MultiMessageContent> mergeContent(
@Nonnull List<? extends MultiMessageContent> newContent,
@Nullable MessageContent oldContent) {
var multiContentList = new ArrayList<MultiMessageContent>();
if (oldContent instanceof MessageContentSingle mCSingle) {
multiContentList.add(new MultiMessageTextContent(mCSingle.content()));
} else if (oldContent != null) {
multiContentList.addAll(((MessageContentMulti) oldContent).multiContentList());
}
multiContentList.addAll(newContent);
return multiContentList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.sap.ai.sdk.orchestration;

public record MessageContentSingle(String content) implements MessageContent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.sap.ai.sdk.orchestration;

public sealed interface MultiMessageContent
permits MultiMessageTextContent, MultiMessageImageContent {
public String type();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.sap.ai.sdk.orchestration;

public record MultiMessageImageContent(String imageUrl, DetailLevel detailLevel)
implements MultiMessageContent {
private static final String type = "image_url";

public String type() {
return type;
}

public enum DetailLevel {
low,
high,
auto;

public static DetailLevel fromString(String str) {
try {
return DetailLevel.valueOf(str.toLowerCase());
} catch (IllegalArgumentException e) {
return DetailLevel.low;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.sap.ai.sdk.orchestration;

public record MultiMessageTextContent(String text) implements MultiMessageContent {
private static final String type = "text";

public String type() {
return type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.sap.ai.sdk.orchestration.model.CompletionPostResponse;
import com.sap.ai.sdk.orchestration.model.LLMChoice;
import com.sap.ai.sdk.orchestration.model.LLMModuleResultSynchronous;
import com.sap.ai.sdk.orchestration.model.MultiChatMessage;
import com.sap.ai.sdk.orchestration.model.MultiChatMessageContent;
import com.sap.ai.sdk.orchestration.model.TokenUsage;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -51,31 +53,52 @@ public TokenUsage getTokenUsage() {
/**
* Get all messages. This can be used for subsequent prompts as a message history.
*
* @throws UnsupportedOperationException if the MultiChatMessage type message in chat.
* @throws IllegalArgumentException if the MultiChatMessage type message in chat.
* @return A list of all messages.
*/
@Nonnull
public List<Message> getAllMessages() throws UnsupportedOperationException {
public List<Message> getAllMessages() throws IllegalArgumentException {
final var messages = new ArrayList<Message>();

for (final ChatMessagesInner chatMessage :
originalResponse.getModuleResults().getTemplating()) {
if (chatMessage instanceof ChatMessage simpleMsg) {
final var message =
switch (simpleMsg.getRole()) {
case "user" -> new UserMessage(simpleMsg.getContent());
case "assistant" -> new AssistantMessage(simpleMsg.getContent());
case "system" -> new SystemMessage(simpleMsg.getContent());
default -> throw new IllegalStateException("Unexpected role: " + simpleMsg.getRole());
case "user" -> Message.user(simpleMsg.getContent());
case "assistant" -> Message.assistant(simpleMsg.getContent());
case "system" -> Message.system(simpleMsg.getContent());
default ->
throw new IllegalArgumentException(
"Unexpected role: "
+ simpleMsg.getRole()
+ ". Only 'user', 'assistant' and 'system' are supported.");
};
messages.add(message);
} else if (chatMessage instanceof MultiChatMessage mCMessage) {
messages.add(
switch (mCMessage.getRole()) {
case "user" ->
Message.user(
new MessageContentMulti(
mCMessage.getContent().toArray(MultiChatMessageContent[]::new)));
case "system" ->
Message.system(
new MessageContentMulti(
mCMessage.getContent().toArray(MultiChatMessageContent[]::new)));
default ->
throw new IllegalArgumentException(
"Unexpected role using MultiChatMessage: "
+ mCMessage.getRole()
+ ". Only 'user' and 'system' are supported.");
});
} else {
throw new UnsupportedOperationException(
"Messages of MultiChatMessage type not supported by convenience API");
throw new IllegalArgumentException(
"Messages of type " + chatMessage.getClass() + " are not supported by convenience API");
}
}

messages.add(new AssistantMessage(getChoice().getMessage().getContent()));
messages.add(Message.assistant(getChoice().getMessage().getContent()));
return messages;
}

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

import java.util.stream.Stream;
import javax.annotation.Nonnull;
import lombok.Value;
import lombok.experimental.Accessors;
Expand All @@ -13,5 +14,37 @@ public class SystemMessage implements Message {
@Nonnull String role = "system";

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

@Override
@Nonnull
public MessageContent content() {
return content;
}

public SystemMessage(String singleMessage) {
content = new MessageContentSingle(singleMessage);
}

SystemMessage(MessageContent messageContent) {
if (!(messageContent instanceof MessageContentSingle)) {
((MessageContentMulti) messageContent)
.multiContentList().stream()
.filter(mCMC -> !(mCMC instanceof MultiMessageTextContent))
.findAny()
.ifPresent(
mCMC -> {
throw new IllegalArgumentException(
"Only TextContent is supported for SystemMessage");
});
}
content = messageContent;
}

@Nonnull
public SystemMessage addText(@Nonnull String... messages) {
return new SystemMessage(
new MessageContentMulti(
Stream.of(messages).map(MultiMessageTextContent::new).toList(), content));
}
}
Loading
Loading