Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
@@ -1,5 +1,7 @@
package com.sap.ai.sdk.orchestration;

import com.sap.ai.sdk.orchestration.model.MultiChatMessageContent;
import java.util.List;
import javax.annotation.Nonnull;
import lombok.Value;
import lombok.experimental.Accessors;
Expand All @@ -13,5 +15,42 @@ public class AssistantMessage implements Message {
@Nonnull String role = "assistant";

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

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

@Override
@Nonnull
public MessageContent getContent() {
return content != null ? content : new MessageContentSingle("");
}

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

public AssistantMessage(MessageContent messageContent) {
content = messageContent;
}

public AssistantMessage(List<MultiChatMessageContent> multiChatMessageContentList) {
content =
new MessageContentMulti(
multiChatMessageContentList.toArray(MultiChatMessageContent[]::new));
}

@Nonnull
public AssistantMessage addTextMessages(@Nonnull String... messages) {
return ((AssistantMessage) Message.addTextMessages(content, role, messages));
}

@Nonnull
public AssistantMessage addImage(
@Nonnull String imageUrl, MultiMessageImageContent.DetailLevel detailLevel) {
return ((AssistantMessage) Message.addImage(content, role, imageUrl, detailLevel));
}
}
113 changes: 111 additions & 2 deletions orchestration/src/main/java/com/sap/ai/sdk/orchestration/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

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 java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/** Interface representing convenience wrappers of chat message to the orchestration service. */
public sealed interface Message permits UserMessage, AssistantMessage, SystemMessage {
Expand Down Expand Up @@ -46,8 +55,96 @@ static SystemMessage system(@Nonnull final String msg) {
* @return the corresponding {@code ChatMessage} object.
*/
@Nonnull
default ChatMessage createChatMessage() {
return ChatMessage.create().role(role()).content(content());
default ChatMessagesInner createChatMessage() {
if (this.getContent() instanceof MessageContentSingle) {
return ChatMessage.create().role(role()).content(content());
} else if (this.getContent() 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.getContent().getClass());
}
}

static Message addTextMessages(
@Nullable MessageContent oldContent, @Nonnull String role, @Nonnull String... messages) {
if (oldContent == null && messages.length == 1) {
return switch (role) {
case "user" -> new UserMessage(messages[0]);
case "assistant" -> new AssistantMessage(messages[0]);
case "system" -> new SystemMessage(messages[0]);
default -> throw new IllegalArgumentException("Unknown role: " + role);
};
}
var messagesAsMultiMessageContents =
Stream.of(messages).map(MultiMessageTextContent::new).toList();
return switch (role) {
case "user" ->
new UserMessage(createMultiContent(messagesAsMultiMessageContents, oldContent));
case "assistant" ->
new AssistantMessage(createMultiContent(messagesAsMultiMessageContents, oldContent));
case "system" ->
new SystemMessage(createMultiContent(messagesAsMultiMessageContents, oldContent));
default -> throw new IllegalArgumentException("Unknown role: " + role);
};
}

static Message addImage(
@Nullable MessageContent oldContent,
@Nonnull String role,
@Nonnull String imageUrl,
@Nonnull MultiMessageImageContent.DetailLevel detailLevel) {
return switch (role) {
case "user" ->
new UserMessage(
createMultiContent(
List.of(new MultiMessageImageContent(imageUrl, detailLevel)), oldContent));
case "assistant" ->
new AssistantMessage(
createMultiContent(
List.of(new MultiMessageImageContent(imageUrl, detailLevel)), oldContent));
case "system" ->
new SystemMessage(
createMultiContent(
List.of(new MultiMessageImageContent(imageUrl, detailLevel)), oldContent));
default -> throw new IllegalArgumentException("Unknown role: " + role);
};
}

private static MessageContentMulti createMultiContent(
@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 new MessageContentMulti(multiContentList);
}

/**
Expand All @@ -66,4 +163,16 @@ default ChatMessage createChatMessage() {
@Nonnull
@Beta
String content();

@Nonnull
@Beta
MessageContent getContent();

@Nonnull
@Beta
Message addTextMessages(@Nonnull String... messages);

@Nonnull
@Beta
Message addImage(@Nonnull String imageUrl, MultiMessageImageContent.DetailLevel detailLevel);
}
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,36 @@
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.List;

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

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;
}
}
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,7 @@
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.TokenUsage;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -51,11 +52,11 @@ 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 :
Expand All @@ -66,12 +67,15 @@ public List<Message> getAllMessages() throws UnsupportedOperationException {
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());
default ->
throw new IllegalArgumentException("Unexpected role: " + simpleMsg.getRole());
};
messages.add(message);
} else if (chatMessage instanceof MultiChatMessage mCMessage) {
messages.add(new UserMessage(mCMessage.getContent()));
} 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");
}
}

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

import com.sap.ai.sdk.orchestration.model.MultiChatMessageContent;
import java.util.List;
import javax.annotation.Nonnull;
import lombok.Value;
import lombok.experimental.Accessors;
Expand All @@ -13,5 +15,42 @@ public class SystemMessage implements Message {
@Nonnull String role = "system";

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

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

@Override
@Nonnull
public MessageContent getContent() {
return content != null ? content : new MessageContentSingle("");
}

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

public SystemMessage(MessageContent messageContent) {
content = messageContent;
}

public SystemMessage(List<MultiChatMessageContent> multiChatMessageContentList) {
content =
new MessageContentMulti(
multiChatMessageContentList.toArray(MultiChatMessageContent[]::new));
}

@Nonnull
public AssistantMessage addTextMessages(@Nonnull String... messages) {
return ((AssistantMessage) Message.addTextMessages(content, role, messages));
}

@Nonnull
public AssistantMessage addImage(
@Nonnull String imageUrl, MultiMessageImageContent.DetailLevel detailLevel) {
return ((AssistantMessage) Message.addImage(content, role, imageUrl, detailLevel));
}
}
Loading
Loading